fix(api): сохранять ошибки наблюдения BGP для оповещений

This commit is contained in:
Denozordec committed 2026-10-14 00:40:41 +07:00
1 parent 5ac5a25dfc
commit 4f084222ad
18 files changed
+478 -25

No files matched your search

+2 -1
View File
@@ -39,7 +39,8 @@ const catalog = await app.inject({ method: "GET", url: "/api/bgp/catalog" })
assert.equal(catalog.statusCode, 200)
assert.deepEqual(catalog.json(), mixed)
const legacy = await app.inject({ method: "GET", url: "/api/bgp/sessions" })
assert.deepEqual(legacy.json(), [row])
assert.equal(legacy.statusCode, 503)
assert.deepEqual(legacy.json().failures, mixed.failures)
const selected = await app.inject({ method: "GET", url: "/api/servers/2/bgp/sessions" })
assert.equal(selected.statusCode, 503)
assert.equal(selected.json().error, "Таймаут BGP")
+3
View File
@@ -51,6 +51,9 @@ export function createBgpRoutes(dependencies: BgpDependencies = defaults): Fasti
app.get("/bgp/sessions", async (_req, reply) => {
const devices = await dependencies.listEnabledServers()
const catalog = await pollBgpCatalog(devices, dependencies.readSessions)
if (catalog.failures.length) return reply.status(503).send({
error: "Не удалось опросить все устройства BGP", failures: catalog.failures,
})
return reply.send(catalog.sessions)
})
@@ -0,0 +1,15 @@
import assert from 'node:assert/strict'
import {fetchBgpSessionsForAlerts} from './bgp-peers-live.js'
import {collectGreBgpSnapshotOnce} from './gre-bgp-snapshot-collector.js'
import {servers} from '../db/schema.js'
import type {BgpSessionRead} from '../types/server.js'
const devices=[{id:1,name:'Первый'},{id:2,name:'Второй'}] as typeof servers.$inferSelect[]
let calls=0
for(const phase of ['populated','empty','partial','failed','disabled','malformed'] as const){
const deps={listEnabledServers:async()=>phase==='disabled'?[]:devices,readSessions:async(server:typeof servers.$inferSelect)=>{calls++;if(phase==='failed'||(phase==='partial'&&server.id===2))throw Error('native401');if(phase==='malformed')return [{id:'*1',serverId:99}] as BgpSessionRead[];return phase==='populated'?[{id:'*1',serverId:server.id,serverName:server.name,name:'own-peer',state:'Established'} as BgpSessionRead]:[]}}
const before=calls,result=await collectGreBgpSnapshotOnce({fetchGre:async()=>[{targetLabel:'GRE-control',status:'up'}],fetchBgp:()=>fetchBgpSessionsForAlerts(deps)})
assert.equal(result.greWritten,1);assert.equal(result.bgpWritten,phase==='populated'?2:0);assert.equal(result.errors.length,['partial','failed','malformed'].includes(phase)?1:0)
if(result.errors.length)assert.match(result.errors[0],/BGP: Не опрошены устройства BGP/)
if(phase==='disabled')assert.equal(calls,before)
}
console.log('BGP alert observations: populated/empty/partial/failed/disabled/malformed; source errors recorded, GRE preserved')
+15 -15
View File
@@ -14,21 +14,21 @@ export function bgpPeerAlertKey(s: Pick<BgpSessionRead, "serverName" | "name">):
return `${sn}: ${nm}`.trim()
}
/** Агрегат BGP-сессий с включённых серверов — как `/api/bgp/sessions`. */
export async function fetchBgpSessionsForAlerts(): Promise<BgpSessionRead[]> {
const allServers = await db.select().from(servers).where(eq(servers.enabled, true))
const results = await Promise.all(
allServers.map(async (server) => {
try {
const client = MikrotikClient.fromServer(server)
const raw = await client.getBgpSessions()
return parseBgpSessions(server, raw)
} catch {
return [] as BgpSessionRead[]
}
}),
)
return results.flat()
interface BgpAlertDependencies {
listEnabledServers: () => Promise<Array<typeof servers.$inferSelect>>
readSessions: (server: typeof servers.$inferSelect) => Promise<BgpSessionRead[]>
}
const alertDefaults: BgpAlertDependencies = {
listEnabledServers: () => db.select().from(servers).where(eq(servers.enabled, true)),
readSessions: async server => parseBgpSessions(server, await MikrotikClient.fromServer(server).getBgpSessions()),
}
/** An incomplete poll is an observation error, never a confirmed empty snapshot. */
export async function fetchBgpSessionsForAlerts(dependencies: BgpAlertDependencies = alertDefaults): Promise<BgpSessionRead[]> {
const catalog = await pollBgpCatalog(await dependencies.listEnabledServers(), dependencies.readSessions)
if (catalog.failures.length) throw new Error("Не опрошены устройства BGP: " +
catalog.failures.map(failure => `${failure.serverName}: ${failure.error}`).join("; "))
return catalog.sessions
}
export async function countBgpSessions():Promise<number|undefined>{