Update
This commit is contained in:
parent
6c00e35072
commit
b8ab1f0583
|
|
@ -3,11 +3,15 @@ import { textfsmResults } from './../ultils/templates/index.js'
|
||||||
import net from 'node:net'
|
import net from 'node:net'
|
||||||
import {
|
import {
|
||||||
appendLog,
|
appendLog,
|
||||||
|
applyRules,
|
||||||
|
classifyLog,
|
||||||
cleanData,
|
cleanData,
|
||||||
detectScenarioByModel,
|
detectScenarioByModel,
|
||||||
isValidJson,
|
isValidJson,
|
||||||
|
LogStreamBuffer,
|
||||||
mapToLineFormat,
|
mapToLineFormat,
|
||||||
sleep,
|
sleep,
|
||||||
|
TestSession,
|
||||||
} from '../ultils/helper.js'
|
} from '../ultils/helper.js'
|
||||||
import Scenario from '#models/scenario'
|
import Scenario from '#models/scenario'
|
||||||
import path from 'node:path'
|
import path from 'node:path'
|
||||||
|
|
@ -105,10 +109,11 @@ export default class LineConnection {
|
||||||
private waitingScenario: boolean
|
private waitingScenario: boolean
|
||||||
private outputInventory: string
|
private outputInventory: string
|
||||||
private outputScenario: string
|
private outputScenario: string
|
||||||
// private bufferCommand: string
|
private bufferLog: LogStreamBuffer
|
||||||
public dataDPELP: DataDPELP | string
|
public dataDPELP: DataDPELP | string
|
||||||
private listScenarios: number[]
|
private listScenarios: number[]
|
||||||
public handleClearLine: () => void
|
public handleClearLine: () => void
|
||||||
|
private session: TestSession
|
||||||
|
|
||||||
constructor(config: LineConfig, socketIO: any, handleClearLine: () => void) {
|
constructor(config: LineConfig, socketIO: any, handleClearLine: () => void) {
|
||||||
this.config = config
|
this.config = config
|
||||||
|
|
@ -120,7 +125,7 @@ export default class LineConnection {
|
||||||
this.waitingScenario = false
|
this.waitingScenario = false
|
||||||
this.outputInventory = ''
|
this.outputInventory = ''
|
||||||
this.outputScenario = ''
|
this.outputScenario = ''
|
||||||
// this.bufferCommand = ''
|
this.bufferLog = new LogStreamBuffer()
|
||||||
this.dataDPELP = {
|
this.dataDPELP = {
|
||||||
line: this.config.lineNumber,
|
line: this.config.lineNumber,
|
||||||
pid: '',
|
pid: '',
|
||||||
|
|
@ -133,6 +138,7 @@ export default class LineConnection {
|
||||||
summary: '',
|
summary: '',
|
||||||
}
|
}
|
||||||
this.listScenarios = []
|
this.listScenarios = []
|
||||||
|
this.session = new TestSession()
|
||||||
this.handleClearLine = handleClearLine
|
this.handleClearLine = handleClearLine
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -159,12 +165,15 @@ export default class LineConnection {
|
||||||
lineNumber,
|
lineNumber,
|
||||||
status: 'connected',
|
status: 'connected',
|
||||||
})
|
})
|
||||||
|
// this.checkLog()
|
||||||
resolve()
|
resolve()
|
||||||
}, 1000)
|
}, 1000)
|
||||||
})
|
})
|
||||||
|
|
||||||
this.client.on('data', (data) => {
|
this.client.on('data', (data) => {
|
||||||
let message = this.connecting ? cleanData(data.toString()) : data.toString()
|
let message = this.connecting ? cleanData(data.toString()) : data.toString()
|
||||||
|
// const lines = this.bufferLog.push(data)
|
||||||
|
// lines.forEach(this.handleLogLine)
|
||||||
let rawData = ''
|
let rawData = ''
|
||||||
if (this.isRunningScript) {
|
if (this.isRunningScript) {
|
||||||
this.waitingScenario = true
|
this.waitingScenario = true
|
||||||
|
|
@ -449,9 +458,14 @@ export default class LineConnection {
|
||||||
})
|
})
|
||||||
const scenario = await detectScenarioByModel(pid, this.listScenarios)
|
const scenario = await detectScenarioByModel(pid, this.listScenarios)
|
||||||
console.log(pid, scenario?.title, this.listScenarios)
|
console.log(pid, scenario?.title, this.listScenarios)
|
||||||
if (scenario && scenario.id !== script.id) {
|
if (
|
||||||
|
scenario &&
|
||||||
|
scenario.id !== script.id &&
|
||||||
|
scenario.title.includes('DPELP') &&
|
||||||
|
script.title.includes('DPELP')
|
||||||
|
) {
|
||||||
this.listScenarios.push(scenario.id)
|
this.listScenarios.push(scenario.id)
|
||||||
// this.outputScenario = ''
|
this.outputScenario = ''
|
||||||
this.runScript(scenario, userName)
|
this.runScript(scenario, userName)
|
||||||
// this.socketIO.emit('confirm_scenario', {
|
// this.socketIO.emit('confirm_scenario', {
|
||||||
// scenario: scenario,
|
// scenario: scenario,
|
||||||
|
|
@ -770,4 +784,33 @@ export default class LineConnection {
|
||||||
const items = await redis.zrange(key, 0, -1)
|
const items = await redis.zrange(key, 0, -1)
|
||||||
return items.map((i) => JSON.parse(i))
|
return items.map((i) => JSON.parse(i))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handleLogLine = (line: string) => {
|
||||||
|
try {
|
||||||
|
const parsed = classifyLog(line)
|
||||||
|
const matchedRules = applyRules(parsed)
|
||||||
|
|
||||||
|
matchedRules.forEach((rule) => {
|
||||||
|
// console.log(rule)
|
||||||
|
this.session.applyRule(rule)
|
||||||
|
})
|
||||||
|
} catch (error) {
|
||||||
|
console.log('handleLogLine', error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
checkLog() {
|
||||||
|
const interval = setInterval(async () => {
|
||||||
|
try {
|
||||||
|
if (this.config.status !== 'connected') {
|
||||||
|
clearInterval(interval)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const result = this.session.finalize()
|
||||||
|
console.log('FINAL RESULT:', this.config.apcName, this.config.lineNumber, result)
|
||||||
|
} catch (err: any) {
|
||||||
|
console.error('Error checking log:', err)
|
||||||
|
}
|
||||||
|
}, 30000)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ import fs from 'node:fs'
|
||||||
import path from 'node:path'
|
import path from 'node:path'
|
||||||
import nodeMailer from 'nodemailer'
|
import nodeMailer from 'nodemailer'
|
||||||
import zulip from 'zulip-js'
|
import zulip from 'zulip-js'
|
||||||
|
import { LogRule, ParsedLog } from './types.js'
|
||||||
|
|
||||||
type DetectAI = {
|
type DetectAI = {
|
||||||
status: string[]
|
status: string[]
|
||||||
|
|
@ -309,6 +310,7 @@ export function sendMessageToZulip(
|
||||||
// Catch scenario with key longer
|
// Catch scenario with key longer
|
||||||
export const detectScenarioByModel = async (model: string, listScenarios: number[]) => {
|
export const detectScenarioByModel = async (model: string, listScenarios: number[]) => {
|
||||||
let scenarios = await Scenario.query().preload('brand').preload('category')
|
let scenarios = await Scenario.query().preload('brand').preload('category')
|
||||||
|
let scenarioDefault = await Scenario.findBy('title', 'DPELP DEFAULT')
|
||||||
const normalizedModel = model.trim().toUpperCase()
|
const normalizedModel = model.trim().toUpperCase()
|
||||||
let matched: { scenario: Scenario; score: number } | null = null
|
let matched: { scenario: Scenario; score: number } | null = null
|
||||||
|
|
||||||
|
|
@ -331,5 +333,95 @@ export const detectScenarioByModel = async (model: string, listScenarios: number
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return matched?.scenario || null
|
return matched?.scenario ? matched?.scenario : listScenarios.length === 0 ? scenarioDefault : null
|
||||||
|
}
|
||||||
|
|
||||||
|
export function classifyLog(line: string): ParsedLog {
|
||||||
|
if (/System Bootstrap|IOS XE Software|Booting/.test(line)) return { raw: line, category: 'BOOT' }
|
||||||
|
|
||||||
|
if (/LICENSE|Smart Licensing|Evaluation/.test(line)) return { raw: line, category: 'LICENSE' }
|
||||||
|
|
||||||
|
if (/LINK-3-UPDOWN|line protocol/.test(line)) return { raw: line, category: 'INTERFACE' }
|
||||||
|
|
||||||
|
if (/FAN|TEMP|POWER|PSU/.test(line)) return { raw: line, category: 'HARDWARE' }
|
||||||
|
|
||||||
|
if (/ERROR|FAIL|CRITICAL|Traceback/.test(line)) return { raw: line, category: 'ERROR' }
|
||||||
|
|
||||||
|
return { raw: line, category: 'UNKNOWN' }
|
||||||
|
}
|
||||||
|
|
||||||
|
export const RULES: LogRule[] = [
|
||||||
|
{
|
||||||
|
id: 'BOOT_OK',
|
||||||
|
category: 'BOOT',
|
||||||
|
match: /IOS XE Software|System Bootstrap/,
|
||||||
|
result: 'PASS',
|
||||||
|
message: 'Boot successful',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'BOOT_LOOP',
|
||||||
|
category: 'BOOT',
|
||||||
|
match: /boot loop|reloading|restart/i,
|
||||||
|
result: 'FAIL',
|
||||||
|
message: 'Boot loop detected',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'LICENSE_EXPIRED',
|
||||||
|
category: 'LICENSE',
|
||||||
|
match: /Evaluation.*expired|license expired/i,
|
||||||
|
result: 'WARN',
|
||||||
|
message: 'License expired',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'HW_FAIL',
|
||||||
|
category: 'HARDWARE',
|
||||||
|
match: /(FAN|PSU).*(FAIL|CRITICAL)/i,
|
||||||
|
result: 'FAIL',
|
||||||
|
message: 'Hardware failure',
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
export function applyRules(log: ParsedLog): LogRule[] {
|
||||||
|
return RULES.filter((rule) => rule.category === log.category && rule.match.test(log.raw))
|
||||||
|
}
|
||||||
|
|
||||||
|
export class TestSession {
|
||||||
|
boot = false
|
||||||
|
hasHwFail = false
|
||||||
|
licenseWarn = false
|
||||||
|
issues: string[] = []
|
||||||
|
|
||||||
|
public applyRule(rule: LogRule) {
|
||||||
|
if (rule.id === 'BOOT_OK') this.boot = true
|
||||||
|
if (rule.result === 'FAIL') this.hasHwFail = true
|
||||||
|
if (rule.result === 'WARN') this.licenseWarn = true
|
||||||
|
this.issues.push(rule.message)
|
||||||
|
}
|
||||||
|
|
||||||
|
public finalize() {
|
||||||
|
if (!this.boot) return 'FAIL'
|
||||||
|
if (this.hasHwFail) return 'FAIL'
|
||||||
|
if (this.licenseWarn) return 'PARTIAL'
|
||||||
|
return 'PASS'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class LogStreamBuffer {
|
||||||
|
private buffer = ''
|
||||||
|
|
||||||
|
public push(chunk: Buffer): string[] {
|
||||||
|
this.buffer += chunk.toString('utf8')
|
||||||
|
|
||||||
|
const lines = this.buffer.split(/\r?\n/)
|
||||||
|
this.buffer = lines.pop() || ''
|
||||||
|
|
||||||
|
return lines.map((l) => l.trim()).filter(Boolean)
|
||||||
|
}
|
||||||
|
|
||||||
|
public flush(): string | null {
|
||||||
|
if (!this.buffer) return null
|
||||||
|
const last = this.buffer
|
||||||
|
this.buffer = ''
|
||||||
|
return last
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,3 +7,21 @@ export interface CustomSocket extends Socket {
|
||||||
export interface CustomServer extends Server {
|
export interface CustomServer extends Server {
|
||||||
userKeys?: string[]
|
userKeys?: string[]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type LogCategory = 'BOOT' | 'LICENSE' | 'INTERFACE' | 'HARDWARE' | 'ERROR' | 'UNKNOWN'
|
||||||
|
|
||||||
|
export interface ParsedLog {
|
||||||
|
raw: string
|
||||||
|
category: LogCategory
|
||||||
|
timestamp?: Date
|
||||||
|
}
|
||||||
|
|
||||||
|
type RuleResult = 'PASS' | 'FAIL' | 'WARN'
|
||||||
|
|
||||||
|
export interface LogRule {
|
||||||
|
id: string
|
||||||
|
category: LogCategory
|
||||||
|
match: RegExp
|
||||||
|
result: RuleResult
|
||||||
|
message: string
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1092,7 +1092,7 @@ export class WebSocketIo {
|
||||||
<td style="width:270px">${item.ios || ''}</td>
|
<td style="width:270px">${item.ios || ''}</td>
|
||||||
<td style="width:200px;">${licenseHTML}</td>
|
<td style="width:200px;">${licenseHTML}</td>
|
||||||
<td style="width:200px; text-wrap: wrap;">${item.summary || ''}</td>
|
<td style="width:200px; text-wrap: wrap;">${item.summary || ''}</td>
|
||||||
<td>${item.issues?.length ? `- ` + item.issues.join(`<br>- `) : ''}</td>
|
<td>${item.issues?.length ? `- ` + item.issues.join(`<br>- `) : '- No issues detected.'}</td>
|
||||||
</tr>
|
</tr>
|
||||||
`
|
`
|
||||||
}
|
}
|
||||||
|
|
@ -1127,7 +1127,7 @@ export class WebSocketIo {
|
||||||
// Format issues
|
// Format issues
|
||||||
const issuesMd = item.issues?.length
|
const issuesMd = item.issues?.length
|
||||||
? item.issues.map((i: string) => `• ${i}`).join(' --')
|
? item.issues.map((i: string) => `• ${i}`).join(' --')
|
||||||
: ''
|
: '- No issues detected.'
|
||||||
|
|
||||||
msg +=
|
msg +=
|
||||||
`| ${item.line || ''}` +
|
`| ${item.line || ''}` +
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue