From 4b618dcb852cb2fb1291dbc5fa44dff63245334b Mon Sep 17 00:00:00 2001 From: Marius Date: Thu, 27 Aug 2026 15:37:48 +0300 Subject: [PATCH] feat(ups): ramura "pe baterie" se poate testa fara scoaterea din priza Lantul de protectie are doua jumatati. Prima - "UPS-ul chiar raporteaza trecerea pe baterie?" - e deja dovedita de perechile de evenimente Kernel-Power 105 de la panele reale din 2026. A doua - decizie, alerta, oprirea curata a Oracle - nu avea cum sa fie exercitata decat asteptand o pana. Acum monitorul accepta un SIMULARE.json care suprascrie citirea de alimentare. Doua garzi, ca un fisier uitat sa nu opreasca serverul pe date inventate: "expira" e obligatoriu si e sters automat la depasire, iar oprirea e in gol daca nu se cere explicit "dryRun": false. Cat e activa, fiecare ciclu scrie WARN in log si -Mode Status o afiseaza. test-battery.ps1 conduce simularea si urmareste logul; sterge fisierul si daca e intrerupt. Cu -ConfirmReal 'DA-OPRESTE-SERVERUL' devine repetitie reala pentru o fereastra de mentenanta - singurul mod de a masura cat dureaza shutdown immediate pe baza de productie, informatie de care depinde alegerea pragurilor. Validat in gol pe 27.08.2026: detectie la 15:36:09 + email de pana, apoi exact trei cicluri de confirmare, declansare pe pragul de 35% la 15:36:40 si secventa parcursa integral. Oracle a ramas Running. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01RRyaDj39hPQ89SZS6URRpS --- docs/ups-server36-monitorizare.md | 58 +++++++++++-- scripts/ups-monitor/test-battery.ps1 | 121 +++++++++++++++++++++++++++ scripts/ups-monitor/ups-monitor.ps1 | 74 ++++++++++++++++ 3 files changed, 245 insertions(+), 8 deletions(-) create mode 100644 scripts/ups-monitor/test-battery.ps1 diff --git a/docs/ups-server36-monitorizare.md b/docs/ups-server36-monitorizare.md index 731139c..f9faf22 100644 --- a/docs/ups-server36-monitorizare.md +++ b/docs/ups-server36-monitorizare.md @@ -96,6 +96,7 @@ canal care să se strice separat. |---|---| | `scripts/ups-monitor/ups-monitor.ps1` | monitorul propriu-zis (buclă + moduri de test) | | `scripts/ups-monitor/install-ups-monitor.ps1` | instalare: copiere, `config.json`, ACL, task programat | +| `scripts/ups-monitor/test-battery.ps1` | test: simulează „pe baterie" fără scoaterea din priză | | `scripts/ups-monitor/test-comms-loss.ps1` | test: dezactivează temporar bateria HID și verifică alerta de comunicație | | `scripts/ups-monitor/config.example.json` | șablon; **fișierul real nu se comite** (conține parola SMTP) | @@ -170,9 +171,45 @@ powershell -ExecutionPolicy Bypass -File C:\ROMFAST\ups-monitor\ups-monitor.ps1 | Pierderea comunicației (`test-comms-loss.ps1`) | dispozitiv oprit 15:24:44 → alertă **15:26:44** (exact 8 cicluri × 15 s) + email; reactivare → „comunicația a revenit" + email; `Win32_Battery` înapoi la 1 instanță | | Taskul rulează ca SYSTEM | proces `ups-monitor.ps1 -Mode Run`, `state.json` actualizat la fiecare ciclu | -**Netestat, pentru că cere pană reală:** trecerea pe baterie, pragurile de oprire și oprirea -efectivă a Oracle. Ramura de decizie e comună cu cea testată în dry-run, dar declanșatorul nu -a fost exercitat pe hardware. Vezi punctul 1 din secțiunea 6. +### Testul „pe baterie", fără scoaterea din priză + +Lanțul de protecție are două jumătăți, și se testează separat: + +| Jumătatea | Cum se validează | +|---|---| +| UPS → Windows (*chiar raportează trecerea pe baterie?*) | **deja dovedită de istoric**: perechile de evenimente Kernel-Power **105** la panele reale din 10 iun, 13 mai, 5 mai, 25 feb 2026 | +| Windows → script → alertă → oprirea Oracle | `test-battery.ps1`, oricând, fără hardware | + +```powershell +# repetiție în gol - sigură oricând, în producție +powershell -ExecutionPolicy Bypass -File C:\ROMFAST\_deploy\test-battery.ps1 -Percent 30 -Minute 3 + +# doar trecerea pe baterie, fără atingerea pragului de oprire +powershell -ExecutionPolicy Bypass -File C:\ROMFAST\_deploy\test-battery.ps1 -Percent 80 -Minute 3 +``` + +Scriptul scrie `SIMULARE.json` lângă `config.json`; cât există, monitorul folosește valorile +din el în locul citirii reale. Două gărzi, ca un fișier uitat să nu oprească serverul pe date +inventate: + +- **`expira` e obligatoriu.** Fără el fișierul e ignorat; la depășire, monitorul îl șterge singur. +- **Oprirea e în gol**, dacă nu se cere explicit `"dryRun": false`. + +Cât e activă simularea, fiecare ciclu scrie în log o linie `WARN SIMULARE ACTIVA`, iar +`-Mode Status` o afișează. `test-battery.ps1` șterge fișierul și dacă e întrerupt. + +**Repetiție reală**, într-o fereastră de mentenanță — Oracle chiar se oprește și serverul +chiar se închide, dar UPS-ul rămâne în priză: + +```powershell +powershell -ExecutionPolicy Bypass -File C:\ROMFAST\_deploy\test-battery.ps1 -ConfirmReal 'DA-OPRESTE-SERVERUL' +``` + +Asta e singurul mod de a verifica de-adevăratelea cât durează `shutdown immediate` pe baza de +producție — informația de care depinde alegerea pragurilor. + +**Rămâne netestabilă prin simulare** doar autonomia reală a UPS-ului: câte minute ține sub +sarcina actuală. Aceea cere scoaterea din priză (secțiunea 6, punctul 1). **Dezarmare de urgență** (monitorul rămâne pornit, dar nu mai ia nicio decizie): @@ -252,11 +289,16 @@ nu are constrângerea asta. ## 6. Rămâne de făcut 1. **Testul de autonomie reală.** Pragurile de 10 min / 35 % sunt alese conservator, nu - măsurate. Nu se știe cât ține UPS-ul sub sarcina actuală, deci nu se știe dacă cele 10 - minute lasă destul timp pentru `shutdown immediate` + oprirea Windows-ului. Se măsoară - scoțând UPS-ul din priză, cu monitorul **dezarmat** (`DEZACTIVAT`) și cu cineva care - urmărește `-Mode Status`. Până atunci, pragurile sunt o presupunere rezonabilă, nu o - garanție. + măsurate. Sunt două necunoscute distincte, iar doar una cere scoaterea din priză: + + - *cât durează oprirea curată* (`shutdown immediate` + închiderea Windows-ului) — se află + dintr-o **repetiție reală** cu `test-battery.ps1 -ConfirmReal ...`, într-o fereastră de + mentenanță, cu UPS-ul în priză; + - *cât ține UPS-ul sub sarcina actuală* — asta chiar cere scoaterea din priză, cu monitorul + **dezarmat** (`DEZACTIVAT`) și cu cineva care urmărește `-Mode Status`. + + Pragul corect e „autonomia măsurată minus durata opririi, cu marjă". Până când ambele sunt + măsurate, pragurile rămân o presupunere rezonabilă, nu o garanție. 2. **Vârsta acumulatorilor.** Necunoscută. Pe cluster există deja criteriul de vârstă în testul lunar (`proxmox/cluster/ups/`); aici nu există nimic echivalent. 3. **BIOS: *Restore on AC power loss*.** De verificat la prima intervenție fizică — altfel diff --git a/scripts/ups-monitor/test-battery.ps1 b/scripts/ups-monitor/test-battery.ps1 new file mode 100644 index 0000000..60e4be6 --- /dev/null +++ b/scripts/ups-monitor/test-battery.ps1 @@ -0,0 +1,121 @@ +<# +.SYNOPSIS + Testeaza ramura "pe baterie" fara sa scoti UPS-ul din priza: scrie SIMULARE.json, + urmareste logul, apoi curata dupa el. + +.DESCRIPTION + Se ruleaza PE SERVER, ridicat (Administrator), cu monitorul deja pornit. + + Implicit, oprirea e parcursa IN GOL: se trimit emailurile, se scriu in log toti + pasii, dar nu se opreste nici Oracle, nici Windows. Asta valideaza tot lantul + decizie -> alerta -> secventa, adica exact partea pe care o pana reala ar + exercita-o. + + Ce NU acopera: faptul ca Windows chiar raporteaza trecerea pe baterie. Acela e + deja dovedit de perechile de evenimente Kernel-Power 105 de la panele reale + (10 iun, 13 mai, 5 mai 2026) - vezi docs/ups-server36-monitorizare.md. + + Simularea expira singura si e stearsa de monitor; scriptul o sterge oricum la + final, inclusiv daca e intrerupt. + +.PARAMETER Percent + Incarcarea raportata. Sub pragul shutdownAtBatteryPercent (implicit 35), + declansarea vine dupa confirmPolls cicluri - adica in ~45 de secunde. + +.PARAMETER Minute + Cat timp ramane activa simularea. + +.PARAMETER ConfirmReal + Repetitia exacta 'DA-OPRESTE-SERVERUL' transforma testul intr-o repetitie + REALA: Oracle chiar se opreste si Windows chiar se inchide. De folosit doar + intr-o fereastra de mentenanta anuntata. + +.EXAMPLE + # repetitie in gol - sigura oricand + powershell -ExecutionPolicy Bypass -File .\test-battery.ps1 + +.EXAMPLE + # doar trecerea pe baterie, fara sa se atinga pragul de oprire + powershell -ExecutionPolicy Bypass -File .\test-battery.ps1 -Percent 80 -Minute 3 + +.EXAMPLE + # repetitie REALA, in fereastra de mentenanta: opreste Oracle si serverul + powershell -ExecutionPolicy Bypass -File .\test-battery.ps1 -ConfirmReal 'DA-OPRESTE-SERVERUL' +#> +[CmdletBinding()] +param( + [string]$InstallDir = 'C:\ROMFAST\ups-monitor', + [ValidateRange(0, 100)][int]$Percent = 30, + [ValidateRange(1, 60)][int]$Minute = 4, + [string]$ConfirmReal +) + +Set-StrictMode -Version 2.0 +$ErrorActionPreference = 'Stop' + +$simPath = Join-Path $InstallDir 'SIMULARE.json' +$real = ($ConfirmReal -eq 'DA-OPRESTE-SERVERUL') + +if ($ConfirmReal -and -not $real) { + throw "-ConfirmReal trebuie sa fie exact 'DA-OPRESTE-SERVERUL'. Am primit: '$ConfirmReal'" +} + +if (Test-Path -LiteralPath (Join-Path $InstallDir 'DEZACTIVAT')) { + Write-Warning 'Exista fisierul DEZACTIVAT: monitorul nu evalueaza nimic, deci testul nu va arata nimic.' +} + +$cfg = Get-Content -LiteralPath (Join-Path $InstallDir 'config.json') -Raw -Encoding UTF8 | ConvertFrom-Json +$expira = (Get-Date).AddMinutes($Minute) + +if ($real) { + Write-Host '' + Write-Host '*** REPETITIE REALA: Oracle SE VA OPRI si serverul SE VA INCHIDE. ***' -ForegroundColor Red + Write-Host '' +} else { + Write-Host 'Repetitie in gol: emailuri si log da, oprire efectiva nu.' +} +Write-Host ("Simulez: pe baterie, incarcare $Percent%. Prag de oprire: {0}% sau {1} min." -f ` + $cfg.shutdownAtBatteryPercent, $cfg.shutdownAfterMinutesOnBattery) +Write-Host ("Simularea expira la {0:HH:mm:ss}." -f $expira) +Write-Host '' + +$sim = [ordered]@{ + onBattery = $true + upsPresent = $true + percent = $Percent + runtimeSec = 600 + dryRun = (-not $real) + expira = $expira.ToString('o') +} + +try { + $sim | ConvertTo-Json | Set-Content -LiteralPath $simPath -Encoding UTF8 + Write-Host "Scris $simPath" + + $log = Join-Path $InstallDir ('log\ups-monitor-{0}.log' -f (Get-Date -Format 'yyyy-MM')) + $de = 0 + if (Test-Path -LiteralPath $log) { $de = @(Get-Content -LiteralPath $log).Count } + + $pana = (Get-Date).AddMinutes($Minute) + while ((Get-Date) -lt $pana) { + Start-Sleep -Seconds 15 + if (Test-Path -LiteralPath $log) { + $linii = @(Get-Content -LiteralPath $log) + if ($linii.Count -gt $de) { + $linii[$de..($linii.Count - 1)] | ForEach-Object { " $_" } + $de = $linii.Count + } + } + if (-not (Test-Path -LiteralPath $simPath)) { + Write-Host ' (simularea a expirat si a fost stearsa de monitor)' + break + } + } +} finally { + Remove-Item -LiteralPath $simPath -Force -ErrorAction SilentlyContinue + Write-Host '' + Write-Host 'Simulare oprita, citirea reala e din nou activa.' + Start-Sleep -Seconds 20 + Write-Host '--- stare dupa test ---' + & powershell -NoProfile -ExecutionPolicy Bypass -File (Join-Path $InstallDir 'ups-monitor.ps1') -Mode Status +} diff --git a/scripts/ups-monitor/ups-monitor.ps1 b/scripts/ups-monitor/ups-monitor.ps1 index cae70cd..1581633 100644 --- a/scripts/ups-monitor/ups-monitor.ps1 +++ b/scripts/ups-monitor/ups-monitor.ps1 @@ -113,6 +113,61 @@ function Get-PowerReading { } } +# --------------------------------------------------------------------------- +# Simulare - testarea ramurii "pe baterie" fara sa scoti UPS-ul din priza +# --------------------------------------------------------------------------- +# +# Cat timp exista SIMULARE.json langa config.json, citirea reala e suprascrisa +# cu valorile din el. Doua garzi, ca un fisier uitat sa nu opreasca serverul pe +# date inventate: +# - "expira" e OBLIGATORIU; fara el fisierul e ignorat, iar la depasire e sters; +# - oprirea e in gol (dry-run) daca nu se cere explicit "dryRun": false. + +function Get-Simulation { + param([string]$Path) + + if (-not (Test-Path -LiteralPath $Path)) { return $null } + + try { + $s = Get-Content -LiteralPath $Path -Raw -Encoding UTF8 | ConvertFrom-Json + } catch { + Write-Log "SIMULARE.json nu se poate citi, il ignor: $($_.Exception.Message)" -Level WARN + return $null + } + + if (-not ($s.PSObject.Properties.Name -contains 'expira') -or -not $s.expira) { + Write-Log 'SIMULARE.json nu are camp "expira" - ignorat, ca sa nu ramana activ la nesfarsit' -Level WARN + return $null + } + + if ((Get-Date) -gt [datetime]$s.expira) { + Write-Log 'SIMULARE.json a expirat - il sterg si revin la citirea reala' -Level WARN + Remove-Item -LiteralPath $Path -Force -ErrorAction SilentlyContinue + return $null + } + + return $s +} + +function Merge-Simulation { + param([object]$Reading, [object]$Sim) + + $o = $Reading.PSObject.Copy() + $map = @{ + onBattery = 'OnBattery' + percent = 'Percent' + runtimeSec = 'RuntimeSec' + upsPresent = 'UpsPresent' + } + foreach ($k in $map.Keys) { + if ($Sim.PSObject.Properties.Name -contains $k) { + $o.($map[$k]) = $Sim.$k + } + } + $o.AcKnown = $true + return $o +} + function Get-UpsDeviceInfo { # Confirmare a doua, din WMI - folosita doar in alerte si in modul Status, # ca sa nu interogam WMI la fiecare ciclu. @@ -425,6 +480,17 @@ function Invoke-Cycle { $now = Get-Date $r = Get-PowerReading + # --- simulare (daca e activa, suprascrie citirea reala) ----------------- + $sim = Get-Simulation -Path $script:SimPath + if ($sim) { + $r = Merge-Simulation -Reading $r -Sim $sim + $simDryRun = $true + if ($sim.PSObject.Properties.Name -contains 'dryRun') { $simDryRun = [bool]$sim.dryRun } + if ($simDryRun) { $DryRun = $true } + Write-Log ("SIMULARE ACTIVA pana la {0} (dryRun={1}): onBattery={2} percent={3} upsPresent={4}" -f ` + $sim.expira, $simDryRun, $r.OnBattery, $r.Percent, $r.UpsPresent) -Level WARN + } + # --- comunicatia cu UPS-ul --------------------------------------------- if (-not $r.UpsPresent) { $script:CommsMissCount++ @@ -529,6 +595,7 @@ Urmeaza un email si atunci. $cfg = Get-Config -Path $ConfigPath $script:LogDir = $cfg.logPath +$script:SimPath = Join-Path (Split-Path -Parent $ConfigPath) 'SIMULARE.json' $script:OnBatteryCount = 0 $script:CommsMissCount = 0 @@ -540,6 +607,13 @@ switch ($Mode) { Write-Host '' $st = Get-State -Path $cfg.stateFile Write-Host "Stare salvata : power=$($st.powerState) comms=$($st.commsState) onBatterySince=$($st.onBatterySince)" + if (Test-Path -LiteralPath $script:SimPath) { + Write-Host "SIMULARE : ACTIVA -> $script:SimPath" + Get-Content -LiteralPath $script:SimPath -Raw | ForEach-Object { " $_" } + } + if (Test-Path -LiteralPath (Join-Path (Split-Path -Parent $ConfigPath) 'DEZACTIVAT')) { + Write-Host 'AUTOMATISM : DEZARMAT (exista fisierul DEZACTIVAT)' + } try { $task = Get-ScheduledTask -TaskName 'ROMFAST UPS Monitor' -ErrorAction Stop $info = Get-ScheduledTaskInfo -TaskName 'ROMFAST UPS Monitor'