fix(ospf): подтвердить права и результат оптимизации на RouterOS

This commit is contained in:
Denozordec committed 2026-10-09 20:40:24 +07:00
1 parent 3bfd4ae6cf
commit 36c6a21ca5
19 files changed
+1069 -15

No files matched your search

+7 -3
View File
@@ -3,6 +3,7 @@ import https from "node:https"
import { randomBytes } from "node:crypto"
import type { Server } from "../db/schema.js"
import { parseRosDataSizeBytes } from "./ros-metric-parse.js"
import { readOspfEffectiveDisabled } from "./ospf-effective-disabled.js"
import type {
RosIdentity, RosInterface, RosIpAddress, RosResource,
RosBgpSession,
@@ -639,15 +640,18 @@ export class MikrotikClient {
}
async getOspfAreas(): Promise<RosOspfArea[]> {
return this.get<RosOspfArea[]>("/routing/ospf/area")
return readOspfEffectiveDisabled<RosOspfArea>(await this.get<unknown>("/routing/ospf/area"), id =>
this.post("/routing/ospf/area/get", { ".id": id, "value-name": "disabled" }))
}
async getOspfInterfaceTemplates(): Promise<RosOspfInterfaceTemplate[]> {
return this.get<RosOspfInterfaceTemplate[]>("/routing/ospf/interface-template")
return readOspfEffectiveDisabled<RosOspfInterfaceTemplate>(await this.get<unknown>("/routing/ospf/interface-template"), id =>
this.post("/routing/ospf/interface-template/get", { ".id": id, "value-name": "disabled" }))
}
async getOspfInstances(): Promise<RosOspfInstance[]> {
return this.get<RosOspfInstance[]>("/routing/ospf/instance")
return readOspfEffectiveDisabled<RosOspfInstance>(await this.get<unknown>("/routing/ospf/instance"), id =>
this.post("/routing/ospf/instance/get", { ".id": id, "value-name": "disabled" }))
}
async getBfdSessions(): Promise<RosBfdSession[]> {
@@ -0,0 +1,18 @@
import assert from "node:assert/strict"
import { readOspfEffectiveDisabled } from "./ospf-effective-disabled.js"
let calls: string[] = []
const input = [{ ".id": "*1", disabled: "true" }, { ".id": "*2" }, { ".id": "*3" }]
const rows = await readOspfEffectiveDisabled(input, async id => { calls.push(id); return { ret: id === "*2" ? "false" : "true" } })
assert.deepEqual(rows.map(row => row.disabled), ["true", "false", "true"])
assert.deepEqual(calls.sort(), ["*2", "*3"]); assert.equal("disabled" in input[1]!, false)
for (const payload of [null, {}, [null], [{ ".id": "" }], [{ ".id": "*1" }, { ".id": "*1" }]]) {
calls = []; await assert.rejects(readOspfEffectiveDisabled(payload, async id => { calls.push(id); return { ret: "false" } })); assert.equal(calls.length, 0)
}
for (const value of [null, [], {}, { ret: "" }, { ret: "maybe" }, { ret: false }]) await assert.rejects(readOspfEffectiveDisabled([{ ".id": "*1" }], async () => value))
await assert.rejects(readOspfEffectiveDisabled([{ ".id": "*1" }], async () => { throw Error("RouterOS недоступен") }), /недоступен/)
let active = 0, maximum = 0
await readOspfEffectiveDisabled(Array.from({ length: 20 }, (_, i) => ({ ".id": "*" + i })), async () => {
maximum = Math.max(maximum, ++active); await new Promise(resolve => setTimeout(resolve, 1)); active--; return { ret: "false" }
})
assert.equal(maximum, 4)
console.log("OSPF omitted disabled: effective native value, no guessed false, malformed/error refusal, bounded reads")
@@ -0,0 +1,20 @@
import { observedBoolean } from "./ospf-observed-values.js"
/** RouterOS can omit an unset disabled property while /get returns its effective value. */
export async function readOspfEffectiveDisabled<T extends { ".id": string; disabled?: string }>(payload: unknown, readDisabled: (id: string) => Promise<unknown>): Promise<T[]> {
if (!Array.isArray(payload) || payload.some(row => !row || typeof row !== "object" || Array.isArray(row) || typeof row[".id"] !== "string" || !row[".id"].trim())) throw new Error("Недостоверный каталог шаблонов OSPF")
if (new Set(payload.map(row => row[".id"])).size !== payload.length) throw new Error("Повторяющиеся ID шаблонов OSPF")
const rows: T[] = payload.map(row => ({ ...row }))
const missing = rows.filter(row => row.disabled === undefined)
let cursor = 0
await Promise.all(Array.from({ length: Math.min(4, missing.length) }, async () => {
while (cursor < missing.length) {
const row = missing[cursor++]!
const response = await readDisabled(row[".id"])
const value = response && typeof response === "object" && "ret" in response ? response.ret : undefined
if (typeof value !== "string" || observedBoolean(value) === null) throw new Error("Не подтверждён disabled шаблона OSPF " + row[".id"])
row.disabled = observedBoolean(value) ? "true" : "false"
}
}))
return rows
}