fix(api): проверять однозначность Router ID оптимизатора
This commit is contained in:
1 parent
7077844655
commit
338335ddc0
6 files changed
+63
-23
No files matched your search
@@ -11,6 +11,7 @@ import type {
|
||||
} from "../types/server.js"
|
||||
import { parseOspfGateway, parseOspfRouteType, parseOspfCatalogRouteType } from "../services/ospf-route-parse.js"
|
||||
import { z } from "zod"
|
||||
import {resolveOptimizerRouterOwners} from "../services/ospf-optimizer-identity.js"
|
||||
import {readOspfOptimizerObserved} from "../services/ospf-optimizer-metrics.js"
|
||||
import type {OspfOptimizerObserved} from "@mmapp/contracts"
|
||||
import {readEditableOspfTemplates,validateOspfOptimizationPlan} from "../services/ospf-optimizer-plan.js"
|
||||
@@ -362,27 +363,10 @@ async function buildOspfOptimizationPlan(server: ServerRow, pingWeight: number):
|
||||
|
||||
async function buildRouterIdToServerMap(needed: Set<string>): Promise<Map<string, ServerRow["id"]>> {
|
||||
const enabledServers = await db.select().from(servers).where(eq(servers.enabled, true))
|
||||
const out = new Map<string, ServerRow["id"]>()
|
||||
const neededLeft = new Set(needed)
|
||||
|
||||
for (const s of enabledServers) {
|
||||
if (neededLeft.size === 0) break
|
||||
try {
|
||||
const c = MikrotikClient.fromServer(s)
|
||||
const inst = await c.getOspfInstances()
|
||||
for (const i of inst) {
|
||||
const rid = String(i["router-id"] ?? "").trim()
|
||||
if (!rid) continue
|
||||
if (neededLeft.has(rid)) {
|
||||
out.set(rid, s.id)
|
||||
neededLeft.delete(rid)
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
// ignore per-server errors, preview should still work
|
||||
}
|
||||
}
|
||||
return out
|
||||
return resolveOptimizerRouterOwners(enabledServers,needed,async id=>{
|
||||
const target=enabledServers.find(server=>server.id===id)!
|
||||
return MikrotikClient.fromServer(target).getOspfInstances()
|
||||
})
|
||||
}
|
||||
|
||||
const routerIdToServerId = await buildRouterIdToServerMap(neededRouterIds)
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
import assert from "node:assert/strict"
|
||||
import {resolveOptimizerRouterOwners} from "./ospf-optimizer-identity.js"
|
||||
const devices=[{id:1},{id:2}],needed=new Set(["192.0.2.1"])
|
||||
const instance={"router-id":"192.0.2.1",disabled:"false"}
|
||||
let calls:number[]=[]
|
||||
const resolved=await resolveOptimizerRouterOwners(devices,needed,async id=>{calls.push(id);return id===1?[instance,instance]:[{"router-id":"192.0.2.2",disabled:"false"}]})
|
||||
assert.equal(resolved.get("192.0.2.1"),1);assert.deepEqual(calls,[1,2])
|
||||
await assert.rejects(()=>resolveOptimizerRouterOwners(devices,needed,async()=>[instance]),/нескольким/)
|
||||
await assert.rejects(()=>resolveOptimizerRouterOwners(devices,needed,async id=>{if(id===2)throw new Error("offline");return [instance]}),/проверить/)
|
||||
for(const raw of [null,{},[{"router-id":"main",disabled:"false"}],[{"router-id":"0.0.0.0",disabled:"false"}],[{"router-id":"192.0.2.1"}],[{"router-id":"192.00.2.1",disabled:"false"}]])await assert.rejects(()=>resolveOptimizerRouterOwners(devices,needed,async()=>raw))
|
||||
assert.equal((await resolveOptimizerRouterOwners(devices,needed,async()=>[{disabled:"true"}])).size,0)
|
||||
assert.equal((await resolveOptimizerRouterOwners(devices,needed,async()=>[])).size,0)
|
||||
calls=[];await resolveOptimizerRouterOwners(devices,new Set(),async id=>{calls.push(id);return []});assert.equal(calls.length,0)
|
||||
console.log("OSPF optimizer identity: complete inventory, literal IDs, duplicate ownership/unknown/failed refusal; no early first-match")
|
||||
@@ -0,0 +1,31 @@
|
||||
import {z} from "zod"
|
||||
import {observedBoolean} from "./ospf-observed-values.js"
|
||||
const schema=z.array(z.object({"router-id":z.string().optional(),disabled:z.string().optional()}))
|
||||
function literal(value:string|undefined){
|
||||
const id=value?.trim(),parts=id?.split(".")
|
||||
return id && id!=="0.0.0.0" && parts?.length===4 && parts.every(part=>/^(0|[1-9]\d{0,2})$/.test(part)&&Number(part)<=255)?id:null
|
||||
}
|
||||
export async function resolveOptimizerRouterOwners(devices:readonly {id:number}[],needed:Set<string>,read:(id:number)=>Promise<unknown>):Promise<Map<string,number>>{
|
||||
if(needed.size===0)return new Map()
|
||||
const owners=new Map<string,Set<number>>()
|
||||
for(const device of devices){
|
||||
let raw:unknown
|
||||
try {raw=await read(device.id)} catch {throw new Error("Не удалось проверить Router ID устройства "+device.id)}
|
||||
const parsed=schema.safeParse(raw)
|
||||
if(!parsed.success)throw new Error("Недостоверный каталог OSPF устройства "+device.id)
|
||||
for(const instance of parsed.data){
|
||||
const disabled=observedBoolean(instance.disabled)
|
||||
if(disabled===true)continue
|
||||
const id=literal(instance["router-id"])
|
||||
if(disabled===null||id===null)throw new Error("Неизвестен Router ID или disabled устройства "+device.id)
|
||||
const set=owners.get(id)??new Set<number>();set.add(device.id);owners.set(id,set)
|
||||
}
|
||||
}
|
||||
const result=new Map<string,number>()
|
||||
for(const id of needed){
|
||||
const set=owners.get(id)
|
||||
if(set && set.size>1)throw new Error("Router ID "+id+" принадлежит нескольким устройствам")
|
||||
if(set?.size===1)result.set(id,[...set][0])
|
||||
}
|
||||
return result
|
||||
}
|
||||
Reference in new issue
Block a user