fix(bgp): отображать только подтверждённые показатели сессий
This commit is contained in:
1 parent
7046808429
commit
8fdc4b917b
13 files changed
+148
-169
No files matched your search
@@ -2,7 +2,7 @@ import { eq } from "drizzle-orm"
|
||||
import type { FastifyPluginAsyncZod } from "@fastify/type-provider-zod"
|
||||
import { db } from "../db/index.js"
|
||||
import { servers } from "../db/schema.js"
|
||||
import { parseBgpSessions } from "../services/bgp-parse-sessions.js"
|
||||
import { parseBgpSessions, parseBgpCatalogSessions, type BgpCatalogSessionRead } from "../services/bgp-parse-sessions.js"
|
||||
import { MikrotikClient } from "../services/mikrotik.js"
|
||||
import { ServerIdParamSchema, type ServerIdParams } from "../types/server.js"
|
||||
import type { BgpSessionRead } from "../types/server.js"
|
||||
@@ -13,11 +13,13 @@ type ServerRow = typeof servers.$inferSelect
|
||||
interface BgpDependencies {
|
||||
listEnabledServers: () => Promise<ServerRow[]>
|
||||
findServer: (id: number) => Promise<ServerRow | undefined>
|
||||
readCatalogSessions?: (server: ServerRow) => Promise<BgpCatalogSessionRead[]>
|
||||
readSessions: (server: ServerRow) => Promise<BgpSessionRead[]>
|
||||
}
|
||||
const defaults: BgpDependencies = {
|
||||
listEnabledServers: () => db.select().from(servers).where(eq(servers.enabled, true)),
|
||||
findServer: async id => (await db.select().from(servers).where(eq(servers.id, id)).limit(1))[0],
|
||||
readCatalogSessions: async server => parseBgpCatalogSessions(server, await MikrotikClient.fromServer(server).getBgpSessions()),
|
||||
readSessions: async server => parseBgpSessions(server, await MikrotikClient.fromServer(server).getBgpSessions()),
|
||||
}
|
||||
export function createBgpRoutes(dependencies: BgpDependencies = defaults): FastifyPluginAsyncZod {
|
||||
@@ -25,7 +27,7 @@ export function createBgpRoutes(dependencies: BgpDependencies = defaults): Fasti
|
||||
// Отдельный каталог сохраняет совместимость старого массива для оповещений.
|
||||
app.get("/bgp/catalog", async (_req, reply) => {
|
||||
const devices = await dependencies.listEnabledServers()
|
||||
return reply.send(await pollBgpCatalog(devices, dependencies.readSessions))
|
||||
return reply.send(await pollBgpCatalog<ServerRow, BgpCatalogSessionRead | BgpSessionRead>(devices, dependencies.readCatalogSessions ?? dependencies.readSessions))
|
||||
})
|
||||
|
||||
// GET /api/bgp/sessions/raw/:id — raw RouterOS response for a single server (debug)
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
import assert from "node:assert/strict"
|
||||
import { parseBgpCatalogSessions, parseBgpSessions } from "./bgp-parse-sessions.js"
|
||||
import { servers } from "../db/schema.js"
|
||||
const server = { id: 1, name: "Тест", host: "192.0.2.1", site: "", country: "RU" } as typeof servers.$inferSelect
|
||||
const raw = { ".id": "*1", "total-updates-received": "42", "total-updates-sent": "99" }
|
||||
const missing = parseBgpCatalogSessions(server, [raw])[0]
|
||||
assert.equal(missing.state, "Unknown")
|
||||
for (const field of ["prefixesRx", "prefixesTx", "holdTime", "keepalive", "inputMessages", "outputMessages", "afi"] as const) assert.equal(missing[field], null)
|
||||
assert.equal(parseBgpSessions(server, [raw])[0].prefixesRx, 42)
|
||||
const observed = parseBgpCatalogSessions(server, [{ ".id": "*2", established: "true", "prefix-count": "0", "hold-time": "0", "keepalive-time": "1m30s", "remote.messages": "0", "local.messages": "2", "remote.afi": "ip,ipv6" }])[0]
|
||||
assert.equal(observed.state, "Established")
|
||||
assert.equal(observed.prefixesRx, 0)
|
||||
assert.equal(observed.holdTime, 0)
|
||||
assert.equal(observed.keepalive, 90)
|
||||
assert.equal(observed.inputMessages, 0)
|
||||
assert.equal(observed.afi, "ip,ipv6")
|
||||
const invalid = parseBgpCatalogSessions(server, [{ ".id": "*3", "prefix-count": "12bad", "hold-time": "invalid", "remote.messages": "-1" }])[0]
|
||||
assert.equal(invalid.prefixesRx, null)
|
||||
assert.equal(invalid.holdTime, null)
|
||||
assert.equal(invalid.inputMessages, null)
|
||||
console.log("BGP parser: unknown/observed zero/timers/AFI, no UPDATE-prefix substitution, legacy preserved")
|
||||
@@ -135,3 +135,33 @@ export function parseBgpSessions(server: ServerRow, raw: RosBgpSession[]): BgpSe
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/** Каталог не заменяет отсутствующие показатели значениями по умолчанию старого API. */
|
||||
export type BgpCatalogSessionRead = Omit<BgpSessionRead, "holdTime" | "keepalive" | "prefixesRx" | "prefixesTx" | "inputMessages" | "outputMessages"> & {
|
||||
holdTime: number | null; keepalive: number | null; prefixesRx: number | null; prefixesTx: number | null;
|
||||
inputMessages: number | null; outputMessages: number | null; afi: string | null;
|
||||
}
|
||||
function readCounter(value: string | undefined): number | null {
|
||||
if (value === undefined || !/^\d+$/.test(value.trim())) return null
|
||||
const parsed = Number(value)
|
||||
return Number.isSafeInteger(parsed) ? parsed : null
|
||||
}
|
||||
function readTimer(value: string | undefined): number | null {
|
||||
if (value === "0" || value === "infinity") return 0
|
||||
if (!value || !/^(?:\d+(?:w|d|h|ms|m|s))+$/.test(value)) return null
|
||||
return parseDuration(value)
|
||||
}
|
||||
export function parseBgpCatalogSessions(server: ServerRow, raw: RosBgpSession[]): BgpCatalogSessionRead[] {
|
||||
return parseBgpSessions(server, raw).map((session, index) => {
|
||||
const s = raw[index]
|
||||
return { ...session,
|
||||
state: s.established === "true" ? "Established" : s.state?.trim() ? detectState(s) : "Unknown",
|
||||
holdTime: readTimer(s["hold-time"]) ?? readCounter(s["active-holdtime"]),
|
||||
keepalive: readTimer(s["keepalive-time"]),
|
||||
prefixesRx: readCounter(s["prefix-count"]), prefixesTx: null,
|
||||
inputMessages: readCounter(s["remote.messages"] ?? s["total-messages-received"]),
|
||||
outputMessages: readCounter(s["local.messages"] ?? s["total-messages-sent"]),
|
||||
afi: s["remote.afi"]?.trim() || null,
|
||||
}
|
||||
})
|
||||
}
|
||||
Reference in new issue
Block a user