fix(alerts): учитывать ошибки реального источника GRE
This commit is contained in:
1 parent
8b60ab040a
commit
9c4a0992d7
15 files changed
+370
-22
No files matched your search
@@ -0,0 +1,14 @@
|
||||
import assert from 'node:assert/strict'
|
||||
import {fetchGreTunnelLiveRows} from './gre-tunnels-live.js'
|
||||
import {collectGreBgpSnapshotOnce} from './gre-bgp-snapshot-collector.js'
|
||||
import {servers} from '../db/schema.js'
|
||||
const devices=[{id:1,name:'Первый'},{id:2,name:'Второй'}] as typeof servers.$inferSelect[]
|
||||
for(const phase of ['populated','empty','partial','failed','disabled','sync-failed'] as const){
|
||||
let calls=0
|
||||
const deps={listEnabledServers:async()=>phase==='disabled'?[]:devices,readTunnels:(server:typeof servers.$inferSelect)=>{calls++;if(phase==='sync-failed')throw Error('sync denied');return (async()=>{if(phase==='failed'||(phase==='partial'&&server.id===2))throw Error('native401');return phase==='populated'?[{targetLabel:'Own GRE / '+server.name,status:'up' as const}]:[]})()}}
|
||||
const result=await collectGreBgpSnapshotOnce({fetchGre:()=>fetchGreTunnelLiveRows(deps),fetchBgp:async()=>[{serverName:'BGP-control',name:'own-peer',state:'Established'}]})
|
||||
assert.equal(result.bgpWritten,1);assert.equal(result.greWritten,phase==='populated'?2:0);assert.equal(result.errors.length,['partial','failed','sync-failed'].includes(phase)?1:0)
|
||||
if(result.errors.length){assert.match(result.errors[0],/GRE: Не опрошены устройства GRE/);assert.match(result.errors[0],/Второй/);if(phase!=='partial')assert.match(result.errors[0],/Первый/)}
|
||||
if(phase==='disabled')assert.equal(calls,0)
|
||||
}
|
||||
console.log('GRE alert source: populated/empty/partial/all-failed/disabled/sync error; BGP control preserved, per-device errors retained')
|
||||
@@ -2,6 +2,7 @@ import { eq } from "drizzle-orm"
|
||||
import { db } from "../db/index.js"
|
||||
import { servers } from "../db/schema.js"
|
||||
import { MikrotikClient } from "./mikrotik.js"
|
||||
import { readInternetPathRows } from "./internet-path-snapshot.js"
|
||||
|
||||
type ServerRow = typeof servers.$inferSelect
|
||||
|
||||
@@ -40,21 +41,26 @@ function mapGreRow(server: ServerRow, g: RosGre, idx: number): GreTunnelLiveRow
|
||||
|
||||
async function fetchGreForServer(server: ServerRow): Promise<GreTunnelLiveRow[]> {
|
||||
const client = MikrotikClient.fromServer(server)
|
||||
const greRaw = await client.get<RosGre[]>("/interface/gre")
|
||||
const greRaw = readInternetPathRows(await client.get<unknown>("/interface/gre"), "GRE alert source", [".id", "name"])
|
||||
if (new Set(greRaw.map(row => row[".id"])).size !== greRaw.length) throw new Error("Повторяющиеся ID GRE")
|
||||
return greRaw.map((g, idx) => mapGreRow(server, g, idx))
|
||||
}
|
||||
|
||||
interface GreAlertDependencies {
|
||||
listEnabledServers: () => Promise<ServerRow[]>
|
||||
readTunnels: (server: ServerRow) => Promise<GreTunnelLiveRow[]>
|
||||
}
|
||||
const alertDefaults: GreAlertDependencies = {
|
||||
listEnabledServers: () => db.select().from(servers).where(eq(servers.enabled, true)),
|
||||
readTunnels: fetchGreForServer,
|
||||
}
|
||||
|
||||
/** GRE с включённых серверов — тот же смысл, что `/api/filters/gre-tunnels` (без правил фильтров). */
|
||||
export async function fetchGreTunnelLiveRows(): Promise<GreTunnelLiveRow[]> {
|
||||
const enabledServers = await db.select().from(servers).where(eq(servers.enabled, true))
|
||||
const results = await Promise.all(
|
||||
enabledServers.map(async (server) => {
|
||||
try {
|
||||
return await fetchGreForServer(server)
|
||||
} catch {
|
||||
return [] as GreTunnelLiveRow[]
|
||||
}
|
||||
}),
|
||||
)
|
||||
return results.flat()
|
||||
export async function fetchGreTunnelLiveRows(dependencies: GreAlertDependencies = alertDefaults): Promise<GreTunnelLiveRow[]> {
|
||||
const devices = await dependencies.listEnabledServers()
|
||||
const results = await Promise.allSettled(devices.map(server => Promise.resolve().then(() => dependencies.readTunnels(server))))
|
||||
const failures = results.flatMap((result, index) => result.status === "rejected"
|
||||
? [`${devices[index].name}: ${result.reason instanceof Error ? result.reason.message : "Ошибка опроса устройства"}`] : [])
|
||||
if (failures.length) throw new Error("Не опрошены устройства GRE: " + failures.join("; "))
|
||||
return results.flatMap(result => result.status === "fulfilled" ? result.value : [])
|
||||
}
|
||||
Reference in new issue
Block a user