Files
MikrotikManager/backend/src/services/ospf-legacy-optimization.ts
T

38 lines
2.8 KiB
TypeScript

import { z } from "zod"
import { validateOspfOptimizationPlan } from "./ospf-optimizer-plan.js"
import { OspfCostWriteError } from "./ospf-template-cost-write.js"
import { MikrotikError } from "./mikrotik.js"
export type LegacyOspfRanked = ReturnType<typeof validateOspfOptimizationPlan>[number]
export interface LegacyOspfPreparation {serverId:number;serverName:string;pingWeight:number;interfaces:LegacyOspfRanked[]}
export interface LegacyOspfApplied {interface:string;from:number;to:number}
export class LegacyOspfApplyError extends Error {
constructor(readonly original:unknown,readonly applied:LegacyOspfApplied[],readonly failedInterface:string,readonly state:"rejected"|"partial"|"unknown"){
super("Применение legacy OSPF плана остановлено",{cause:original})
}
}
/** Preserve the legacy ranking/provider; callers supply their own access/source fences. */
export async function prepareLegacyOspfOptimization(server:{id:number;name:string;host:string;enabled:boolean},pingWeight:number,readRanked:()=>Promise<unknown>,authorize:()=>Promise<unknown>=async()=>{}):Promise<LegacyOspfPreparation>{
if(!server.enabled)throw Error("Устройство OSPF отключено")
z.number().int().positive().safe().parse(server.id)
z.number().int().min(0).max(100).parse(pingWeight)
await authorize()
const interfaces=validateOspfOptimizationPlan(await readRanked())
if(interfaces.length>1000)throw Error("Legacy OSPF план превышает 1000 интерфейсов")
await authorize()
return {serverId:server.id,serverName:server.name||server.host,pingWeight,interfaces}
}
/** Ordered, single-attempt writes with a fresh caller fence before each command. */
export async function applyLegacyOspfOptimization(plan:LegacyOspfPreparation,write:(item:LegacyOspfRanked)=>Promise<unknown>,beforeEach:(item:LegacyOspfRanked,index:number,applied:readonly LegacyOspfApplied[])=>Promise<unknown>=async()=>{}){
const ranked=validateOspfOptimizationPlan(plan.interfaces),changed=ranked.filter(item=>item.currentCost!==item.optimalCost),applied:LegacyOspfApplied[]=[]
for(const [index,item]of changed.entries()){
try{await beforeEach(item,index,applied)}catch(error){throw new LegacyOspfApplyError(error,[...applied],item.interface,applied.length?"partial":"rejected")}
try{await write(item)}catch(error){
const rejected=error instanceof OspfCostWriteError&&!error.writeAttempted||error instanceof MikrotikError&&error.statusCode>=400&&error.statusCode<500
throw new LegacyOspfApplyError(error,[...applied],item.interface,rejected?(applied.length?"partial":"rejected"):"unknown")
}
applied.push({interface:item.interface,from:item.currentCost,to:item.optimalCost})
}
return {serverId:plan.serverId,serverName:plan.serverName,pingWeight:plan.pingWeight,optimizedCount:changed.length,applied,interfaces:ranked}
}