fix(routing): исключить опрос выключенных устройств BGP и OSPF
This commit is contained in:
1 parent
89de7e134b
commit
8795969871
10 files changed
+299
-13
No files matched your search
@@ -50,3 +50,15 @@ assert.equal((await app.inject({ method: "GET", url: "/api/servers/0/bgp/session
|
||||
assert.equal(reads, before)
|
||||
await app.close()
|
||||
console.log("BGP catalog: partial/empty/failed/context/duplicates and HTTP compatibility/503 passed")
|
||||
for (const type of ["home-router", "exit-node"]) {
|
||||
let enabled = false, polled = 0
|
||||
const testApp = Fastify(); testApp.setValidatorCompiler(validatorCompiler); testApp.setSerializerCompiler(serializerCompiler)
|
||||
await testApp.register(createBgpRoutes({listEnabledServers:async()=>[],findServer:async id=>id===1?({id:1,name:"test",host:"192.0.2.1",enabled,type} as typeof servers.$inferSelect):undefined,readSessions:async()=>{polled++;return []},readRawSessions:async()=>{polled++;return []}}),{prefix:"/api"})
|
||||
for (const url of ["/api/bgp/sessions/raw/1","/api/servers/1/bgp/sessions"]) {const r=await testApp.inject({method:"GET",url});assert.equal(r.statusCode,409);assert.match(r.json().error,/выключен/)}
|
||||
assert.equal(polled,0)
|
||||
for(const url of ["/api/bgp/sessions/raw/99","/api/servers/99/bgp/sessions"])assert.equal((await testApp.inject({method:"GET",url})).statusCode,404)
|
||||
assert.equal((await testApp.inject({method:"GET",url:"/api/bgp/sessions/raw/1oops"})).statusCode,400);assert.equal(polled,0)
|
||||
enabled=true;assert.equal((await testApp.inject({method:"GET",url:"/api/bgp/sessions/raw/1"})).statusCode,200);assert.equal(polled,1)
|
||||
await testApp.close()
|
||||
}
|
||||
console.log("BGP selected/raw: disabled home-router/exit-node do not poll; unknown/invalid IDs and enabled read preserved")
|
||||
@@ -15,6 +15,7 @@ interface BgpDependencies {
|
||||
findServer: (id: number) => Promise<ServerRow | undefined>
|
||||
readCatalogSessions?: (server: ServerRow) => Promise<BgpCatalogSessionRead[]>
|
||||
readSessions: (server: ServerRow) => Promise<BgpSessionRead[]>
|
||||
readRawSessions?: (server: ServerRow) => Promise<unknown[]>
|
||||
}
|
||||
const defaults: BgpDependencies = {
|
||||
listEnabledServers: () => db.select().from(servers).where(eq(servers.enabled, true)),
|
||||
@@ -33,12 +34,13 @@ export function createBgpRoutes(dependencies: BgpDependencies = defaults): Fasti
|
||||
// GET /api/bgp/sessions/raw/:id — raw RouterOS response for a single server (debug)
|
||||
app.get("/bgp/sessions/raw/:id", { schema: { params: ServerIdParamSchema } }, async (req, reply) => {
|
||||
const params = req.params as ServerIdParams
|
||||
const server = (await db.select().from(servers).where(eq(servers.id, params.id)).limit(1))[0]
|
||||
const server = await dependencies.findServer(params.id)
|
||||
if (!server) return reply.status(404).send({ error: "Server not found" })
|
||||
if (server.enabled === false) return reply.status(409).send({ error: "Сервер выключен. Опрос BGP не выполнялся" })
|
||||
try {
|
||||
const client = MikrotikClient.fromServer(server)
|
||||
const raw = await client.getBgpSessionsRaw()
|
||||
app.log.info({ serverId: server.id, host: server.host, raw }, "BGP raw response")
|
||||
const raw = await (dependencies.readRawSessions ?? (s => MikrotikClient.fromServer(s).getBgpSessionsRaw()))(server)
|
||||
if (!Array.isArray(raw)) throw new Error("Некорректный список BGP sessions")
|
||||
app.log.info({ serverId: server.id, sessionCount: raw.length }, "BGP raw response")
|
||||
return reply.send({ server: { id: server.id, host: server.host, name: server.name }, raw })
|
||||
} catch (err) {
|
||||
return reply.status(500).send({ error: String(err) })
|
||||
@@ -58,6 +60,7 @@ export function createBgpRoutes(dependencies: BgpDependencies = defaults): Fasti
|
||||
const server = await dependencies.findServer(params.id)
|
||||
|
||||
if (!server) return reply.status(404).send({ error: "Server not found" })
|
||||
if (server.enabled === false) return reply.status(409).send({ error: "Сервер выключен. Опрос BGP не выполнялся" })
|
||||
|
||||
try {
|
||||
const catalog = await pollBgpCatalog([server], dependencies.readSessions)
|
||||
|
||||
@@ -71,3 +71,17 @@ assert.equal(parseBfdCatalogSessions(bfdServer, rawBfd("up"))[0].multihop, null)
|
||||
assert.equal(parseBfdCatalogSessions(bfdServer, [{ ...rawBfd("up")[0], multihop: "false" }])[0].multihop, false)
|
||||
assert.equal(parseCatalogRoutes(bfdServer, [rawRoute])[0].type, "Unknown")
|
||||
assert.equal(parseCatalogRoutes(bfdServer, [{ ...rawRoute, "ospf-type": "intra-area" }])[0].type, "O")
|
||||
for (const type of ["home-router", "exit-node"]) {
|
||||
const {MikrotikClient}=await import("../services/mikrotik.js");const original=MikrotikClient.fromServer;let reads=0
|
||||
MikrotikClient.fromServer=()=>{reads++;throw Error("must not poll")}
|
||||
const testApp=Fastify();testApp.setValidatorCompiler(validatorCompiler);testApp.setSerializerCompiler(serializerCompiler)
|
||||
try {await testApp.register(createOspfRoutes({listEnabledServers:async()=>[],readCatalog:async()=>{throw Error("must not poll")},findServer:async id=>id===1?({id:1,name:"test",enabled:false,type} as typeof servers.$inferSelect):undefined}),{prefix:"/api"});const r=await testApp.inject({method:"GET",url:"/api/servers/1/ospf"});assert.equal(r.statusCode,409);assert.match(r.json().error,/выключен/);assert.equal((await testApp.inject({method:"GET",url:"/api/servers/99/ospf"})).statusCode,404);assert.equal((await testApp.inject({method:"GET",url:"/api/servers/1oops/ospf"})).statusCode,400);assert.equal(reads,0)}finally{await testApp.close();MikrotikClient.fromServer=original}
|
||||
}
|
||||
console.log("OSPF selected: disabled home-router/exit-node do not poll; unknown/invalid IDs fail before RouterOS")
|
||||
{
|
||||
const {MikrotikClient}=await import("../services/mikrotik.js"),original=MikrotikClient.fromServer
|
||||
MikrotikClient.fromServer=()=>({getOspfNeighbors:async()=>[],getOspfAreas:async()=>[],getOspfInterfaceTemplates:async()=>[],getOspfInstances:async()=>[],getBfdSessions:async()=>{throw Error("BFD unavailable")},getIpRoutes:async()=>[]} as unknown as MikrotikClient)
|
||||
const testApp=Fastify();testApp.setValidatorCompiler(validatorCompiler);testApp.setSerializerCompiler(serializerCompiler)
|
||||
try{await testApp.register(createOspfRoutes({listEnabledServers:async()=>[],readCatalog:async()=>emptyOspfCatalog(),findServer:async()=>({id:1,name:"test",enabled:true} as typeof servers.$inferSelect)}),{prefix:"/api"});const r=await testApp.inject({method:"GET",url:"/api/servers/1/ospf"});assert.equal(r.statusCode,503);assert.match(r.json().error,/BFD unavailable/)}finally{await testApp.close();MikrotikClient.fromServer=original}
|
||||
}
|
||||
console.log("OSPF selected: failed BFD does not become successful empty data")
|
||||
@@ -553,6 +553,7 @@ async function buildOspfOptimizationPlan(server: ServerRow, pingWeight: number):
|
||||
interface OspfCatalogDependencies {
|
||||
listEnabledServers: () => Promise<ServerRow[]>
|
||||
readCatalog: (server: ServerRow) => Promise<OspfCatalogCollections>
|
||||
findServer?: (id: number) => Promise<ServerRow | undefined>
|
||||
getOptimizationServer?: (id:number)=>Promise<ServerRow | undefined>
|
||||
buildOptimizationPlan?: (server:ServerRow,weight:number)=>Promise<OspfRankedInterface[]>
|
||||
setTemplateCost?: (server:ServerRow,id:string,cost:number)=>Promise<void>
|
||||
@@ -676,15 +677,16 @@ export function createOspfRoutes(dependencies: OspfCatalogDependencies = catalog
|
||||
// GET /api/servers/:id/ospf — single server OSPF + BFD data
|
||||
app.get("/servers/:id/ospf", { schema: { params: ServerIdParamSchema } }, async (req, reply) => {
|
||||
const params = req.params as ServerIdParams
|
||||
const server = (await db
|
||||
const server = dependencies.findServer ? await dependencies.findServer(params.id) : (await db
|
||||
.select().from(servers)
|
||||
.where(eq(servers.id, params.id))
|
||||
.limit(1))[0]
|
||||
|
||||
if (!server) return reply.status(404).send({ error: "Server not found" })
|
||||
if (server.enabled === false) return reply.status(409).send({ error: "Сервер выключен. Опрос OSPF не выполнялся" })
|
||||
|
||||
try {
|
||||
const { neighbors, areas, ifaceTemplates, instances, bfdSessions, ipRoutes } = await fetchServerOspf(server)
|
||||
const { neighbors, areas, ifaceTemplates, instances, bfdSessions, ipRoutes } = await readOspfCatalogRaw(MikrotikClient.fromServer(server))
|
||||
const areaMap = buildAreaMap(areas)
|
||||
return reply.send({
|
||||
neighbors: parseNeighbors(server, neighbors, areaMap),
|
||||
@@ -695,7 +697,7 @@ export function createOspfRoutes(dependencies: OspfCatalogDependencies = catalog
|
||||
areas: areas.map(a => ({ name: a.name, areaId: a["area-id"] ?? "0.0.0.0", type: a.type, disabled: a.disabled === "true", inactive: a.inactive === "true", instance: a.instance })),
|
||||
})
|
||||
} catch (err) {
|
||||
return reply.status(500).send({ error: String(err) })
|
||||
return reply.status(503).send({ error: String(err) })
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
Reference in new issue
Block a user