fix(dr): Windows Update rebota VM 109 in mijlocul testului DR
Testul DR din 2026-08-08 a raportat "Restore failed" dupa 11 secunde, fara niciun log RMAN. Cauza nu a fost restore-ul: KB5101001 fusese descarcat in timpul testului din 2026-08-01 (singurul moment in care VM 109 e pornit), a ramas staged dupa qm stop si s-a finalizat la boot-ul testului urmator. Cronologie din Event Log-ul guest-ului: 06:00:58 RestartManager 10010 - nu poate reporni powershell.exe (restore-ul) 06:01:04 SCM 7034 - OpenSSH SSH Server terminat neasteptat 06:01:06 pveelite: client_loop: send disconnect: Broken pipe -> FAILED 06:01:38 VM-ul se reboteaza singur Fereastra testului (Sambata 06:00) era in afara Active Hours (08:00-17:00), deci pentru Windows era fereastra de mentenanta valida - iar VM 109 fiind pornit doar in timpul testului, aceea era singura fereastra posibila. Agravant: sshd nu avea acsiuni de recovery (RESET_PERIOD 0), deci dupa ce a murit a ramas mort si au esuat si colectarea logului si shutdown-ul gratios. Masuri: - NoAutoUpdate=1 + AUOptions=2 pe VM 109 (aplicat direct in registry) - actiuni de recovery pentru sshd: restart la 5s/10s/30s, reset=86400 - guard "STEP 3b: Windows servicing" inainte de restore (check_servicing.ps1): asteapta idle 300s, consuma controlat un reboot in asteptare, altfel abandoneaza cu "ABORTED - Windows servicing" in loc de un "Restore failed" inselator. Fail-open daca checkul lipseste - nu are voie sa pice testul. - fereastra lunara de patching (vm109-patch-window.sh + install_updates.ps1), prima duminica 03:00, cu re-armare NoAutoUpdate=1 indiferent de rezultat Adaugat si .gitattributes: cu core.autocrlf=true scripturile .sh ajungeau in working tree cu CRLF, iar ele se deployeaza prin scp direct pe Proxmox. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BhQBTegE4PiMPPaapLHjkc
This commit is contained in:
@@ -31,6 +31,81 @@ ssh root@10.0.20.202 "qm status 109" # trebuie stopped între teste
|
||||
|
||||
Script-urile refuză să ruleze dacă cealaltă parte e accesibilă (anti-split-brain).
|
||||
|
||||
## 🩹 Windows Update pe VM 109 — incident 2026-08-08
|
||||
|
||||
**Simptom:** testul DR din 2026-08-08 a raportat `FAILED`, `Database Restore: Restore failed` după 11 secunde, `Tables restored: 0`, iar raportul nu conținea niciun log RMAN („No restore logs or RMAN scripts found"). Testul din 2026-08-01 trecuse în 18 minute pe exact același cod (scripturile nu s-au modificat din 2026-04-25).
|
||||
|
||||
**Cauza reală: RMAN nici nu a apucat să pornească.** Windows Update a rebootat VM-ul în mijlocul testului.
|
||||
|
||||
Cronologia din Event Log-ul guest-ului:
|
||||
|
||||
| Ora | Eveniment |
|
||||
|-----|-----------|
|
||||
| 06:00:26 | boot VM 109 |
|
||||
| 06:00:53 | scriptul DR confirmă SSH + PowerShell |
|
||||
| 06:00:55 | STEP 4 pornește `rman_restore_from_zero.ps1` |
|
||||
| 06:00:58 | `RestartManager 10010` — nu poate reporni `powershell.exe` (pid 5964) = **procesul de restore** |
|
||||
| 06:00:59 | `Winlogon 6004` — TrustedInstaller a eșuat o notificare critică |
|
||||
| 06:01:02 | `SCM 7023` — Update Orchestrator Service terminat |
|
||||
| 06:01:04 | `SCM 7034` — **OpenSSH SSH Server terminat neașteptat** |
|
||||
| 06:01:06 | pveelite vede `client_loop: send disconnect: Broken pipe` → restore marcat FAILED |
|
||||
| 06:01:38 | **VM-ul se reboteaza singur** (al doilea eveniment `Kernel-General 12`) |
|
||||
| 06:02:20 | scriptul renunță la SSH și forțează `qm stop 109` |
|
||||
|
||||
Update-ul vinovat: **KB5101001**, `InstalledOn 8/8/2026`.
|
||||
|
||||
**De ce s-a întâmplat exact atunci — problemă structurală, nu ghinion:**
|
||||
|
||||
- VM 109 e pornit **doar** în timpul testului DR. Deci fereastra testului era singura fereastră în care Windows Update putea rula vreodată.
|
||||
- Update-ul fusese descărcat **în timpul testului precedent** — evenimentele `WindowsUpdateClient id=44` sunt la 08-01 06:13–06:17, adică în interiorul rulării din 1 august. A rămas staged după `qm stop` și s-a finalizat la boot-ul următor = testul următor.
|
||||
- Active Hours erau 08:00–17:00, deci 06:00 era pentru Windows o fereastră de mentenanță **validă**.
|
||||
- `NoAutoRebootWithLoggedOnUsers=1` nu ajută: o sesiune SSH nu e logon interactiv.
|
||||
|
||||
**Factor agravant:** `sc qfailure sshd` nu avea nicio acțiune de recovery (`RESET_PERIOD: 0`). După ce sshd a murit a rămas mort, deci și STEP 6 (colectare log) și STEP 7 (shutdown grațios) au eșuat — de aici raportul fără loguri.
|
||||
|
||||
**Ce NU a fost:** host-ul e curat — fără OOM, fără erori de storage, cluster quorate, replicarea ZFS a rulat normal la 06:00. Backup-urile nu au fost implicate în niciun fel.
|
||||
|
||||
### Măsuri aplicate (2026-08-08)
|
||||
|
||||
| # | Măsură | Unde |
|
||||
|---|--------|------|
|
||||
| 1 | `NoAutoUpdate=1` + `AUOptions=2` — Windows Update nu mai pornește singur | registry VM 109, `HKLM\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate\AU` |
|
||||
| 2 | Acțiuni de recovery pentru sshd: restart la 5s / 10s / 30s, `reset=86400` | serviciu `sshd` pe VM 109 |
|
||||
| 3 | Guard „STEP 3b: Windows servicing" înainte de restore | `weekly-dr-test-proxmox.sh` + `check_servicing.ps1` |
|
||||
| 4 | Fereastră lunară dedicată de patching | `vm109-patch-window.sh` + `install_updates.ps1` |
|
||||
|
||||
**3 — guard-ul de servicing.** Între verificarea NFS și restore, scriptul rulează `check_servicing.ps1` pe guest, care întoarce `STATE=IDLE` sau `STATE=BUSY REBOOT_PENDING=... REASONS=...` (CBS RebootPending, WU RebootRequired, PendingFileRename, TiWorker, TrustedInstaller, UsoSvc). Comportament:
|
||||
|
||||
- așteaptă până la 300s ca servicing-ul să devină idle;
|
||||
- dacă e reboot în așteptare, îl consumă controlat (un singur reboot) și reia verificarea;
|
||||
- dacă tot nu e curat, **abandonează înainte de restore** cu `ABORTED - Windows servicing` în loc de un „Restore failed" înșelător, care arată ca o problemă de backup;
|
||||
- dacă `check_servicing.ps1` lipsește sau nu răspunde, guard-ul se auto-dezactivează cu un warning — nu are voie să pice testul din cauza propriei absențe.
|
||||
|
||||
**4 — fereastra de patching.** Prima duminică din lună, 03:00, pe nodul care găzduiește VM 109:
|
||||
|
||||
```bash
|
||||
0 3 1-7 * * /opt/scripts/vm109-patch-window.sh > /dev/null 2>&1 # garda „duminică" e în script
|
||||
```
|
||||
|
||||
Scriptul pornește VM 109 (cu `vm109-debug.flag` setat, fiindcă patching-ul depășește limita de 60 min a watchdog-ului), permite temporar Windows Update (`NoAutoUpdate=0`), aplică update-urile prin API-ul COM `Microsoft.Update.Session`, tolerează până la 3 cicluri de reboot, **re-armează `NoAutoUpdate=1` indiferent de rezultat**, verifică starea finală și oprește VM-ul. Trimite mail doar când rezultatul nu e curat.
|
||||
|
||||
Rulare manuală (ignoră garda de calendar):
|
||||
|
||||
```bash
|
||||
ssh root@10.0.20.202 "/opt/scripts/vm109-patch-window.sh --now"
|
||||
tail -f /var/log/oracle-dr/patch-window.log
|
||||
```
|
||||
|
||||
**Verificare stare Windows Update pe VM 109:**
|
||||
|
||||
```bash
|
||||
ssh root@10.0.20.202 "ssh -p 22122 romfast@10.0.20.37 \
|
||||
'powershell -ExecutionPolicy Bypass -File D:\\oracle\\scripts\\check_servicing.ps1'"
|
||||
# Așteptat între ferestre: STATE=IDLE
|
||||
```
|
||||
|
||||
**Zgomot preexistent, fără legătură cu incidentul:** la fiecare boot apar `VSS 8213` + `SCM 7023` pentru `OracleVssWriterROA` („General access denied"). Serviciul VSS writer al Oracle nu are drepturi suficiente. Nu afectează restore-ul RMAN (care nu folosește VSS) — de rezolvat separat.
|
||||
|
||||
---
|
||||
|
||||
# 🛡️ Oracle DR System - Complete Architecture
|
||||
@@ -156,6 +231,8 @@ Scheduled Tasks:
|
||||
/opt/scripts/
|
||||
├── oracle-backup-monitor-proxmox.sh # Daily backup monitoring
|
||||
├── weekly-dr-test-proxmox.sh # Weekly DR test
|
||||
├── vm109-patch-window.sh # Monthly Windows patching (incident 08-08)
|
||||
├── vm109-watchdog.sh # Force-stop VM 109 outside test window
|
||||
└── PROXMOX_NOTIFICATIONS_README.md # Documentation
|
||||
|
||||
/mnt/pve/oracle-backups/ROA/autobackup/
|
||||
@@ -166,6 +243,8 @@ Scheduled Tasks:
|
||||
Cron Jobs:
|
||||
0 9 * * * /opt/scripts/oracle-backup-monitor-proxmox.sh
|
||||
0 6 * * 6 /opt/scripts/weekly-dr-test-proxmox.sh
|
||||
0 3 1-7 * * /opt/scripts/vm109-patch-window.sh # prima duminică (gardă în script)
|
||||
* * * * * /opt/scripts/vm109-watchdog.sh
|
||||
```
|
||||
|
||||
### 📁 DR VM 109 (10.0.20.37) - When Running
|
||||
@@ -173,6 +252,8 @@ Cron Jobs:
|
||||
D:\oracle\scripts\
|
||||
├── rman_restore_from_zero.cmd # Main restore script ⭐
|
||||
├── cleanup_database.cmd # Cleanup after test
|
||||
├── check_servicing.ps1 # Servicing guard (incident 08-08)
|
||||
├── install_updates.ps1 # Used only by vm109-patch-window.sh
|
||||
└── mount-nfs.bat # Mount F:\ at startup
|
||||
|
||||
F:\ (NFS mount from Proxmox)
|
||||
@@ -248,6 +329,14 @@ ssh root@10.0.20.202 "qm stop 109"
|
||||
|
||||
## 🐛 Troubleshooting
|
||||
|
||||
### ⚡ Simptome frecvente — unde te uiți întâi
|
||||
|
||||
| Simptom în raportul DR | Cauză probabilă |
|
||||
|---|---|
|
||||
| `Restore failed` în < 60s, **fără log RMAN**, `Broken pipe` în log-ul bash | Nu e problemă de backup — sesiunea SSH a murit. Vezi [Windows Update pe VM 109](#-windows-update-pe-vm-109--incident-2026-08-08). Verifică `SCM 7034 sshd` în Event Log-ul guest-ului. |
|
||||
| `ABORTED - Windows servicing` | Guard-ul STEP 3b a oprit testul intenționat: Windows Update era activ. Backup-urile nu sunt implicate. Rulează fereastra de patching manual. |
|
||||
| `Restore failed` după 10+ minute, cu log RMAN | Problemă reală de restore — continuă cu secțiunile de mai jos. |
|
||||
|
||||
### 🔍 Debugging Restore Tests
|
||||
|
||||
#### Check Backup Files on Proxmox (10.0.20.202)
|
||||
|
||||
72
proxmox/vm109-windows-dr/scripts/check_servicing.ps1
Normal file
72
proxmox/vm109-windows-dr/scripts/check_servicing.ps1
Normal file
@@ -0,0 +1,72 @@
|
||||
# check_servicing.ps1 — raportează dacă stack-ul de servicing Windows este activ.
|
||||
#
|
||||
# De ce există: incident 2026-08-08. KB5101001 a fost descărcat în timpul testului
|
||||
# DR din 2026-08-01 (singurul moment în care VM 109 este pornit), a rămas staged
|
||||
# după `qm stop`, și s-a finalizat la următorul boot — adică exact la testul
|
||||
# următor. RestartManager a omorât procesul powershell.exe care rula restore-ul
|
||||
# (event 10010), apoi sshd (SCM 7034), iar VM-ul a rebootat la 72s după boot.
|
||||
# Testul a raportat "Restore failed" fără niciun log RMAN, deși RMAN nici măcar
|
||||
# nu apucase să pornească.
|
||||
#
|
||||
# Ieșire (o singură linie, parsabilă din bash):
|
||||
# STATE=IDLE
|
||||
# STATE=BUSY REBOOT_PENDING=<bool> REASONS=<listă separată prin virgulă>
|
||||
#
|
||||
# Cod de ieșire: 0 = IDLE, 1 = BUSY. Nu aruncă niciodată excepții — un check
|
||||
# care crapă nu are voie să pice testul DR.
|
||||
|
||||
$ErrorActionPreference = "SilentlyContinue"
|
||||
|
||||
$reasons = @()
|
||||
$rebootPending = $false
|
||||
|
||||
# 1. Component Based Servicing: update aplicat, așteaptă reboot.
|
||||
if (Test-Path "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Component Based Servicing\RebootPending") {
|
||||
$reasons += "CBS_RebootPending"
|
||||
$rebootPending = $true
|
||||
}
|
||||
|
||||
# 2. Windows Update: reboot cerut explicit.
|
||||
if (Test-Path "HKLM:\SOFTWARE\Microsoft\Windows\NT\CurrentVersion\WindowsUpdate\Auto Update\RebootRequired") {
|
||||
$reasons += "WU_RebootRequired"
|
||||
$rebootPending = $true
|
||||
}
|
||||
|
||||
# 3. Fișiere programate pentru redenumire la boot (semnătură clasică de servicing).
|
||||
$pfro = (Get-ItemProperty "HKLM:\SYSTEM\CurrentControlSet\Control\Session Manager" -Name PendingFileRenameOperations).PendingFileRenameOperations
|
||||
if ($pfro) {
|
||||
$reasons += "PendingFileRename"
|
||||
$rebootPending = $true
|
||||
}
|
||||
|
||||
# 4. Redenumire de computer în așteptare — tot reboot cere.
|
||||
if (Test-Path "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Auto Update\PostRebootReporting") {
|
||||
$reasons += "PostRebootReporting"
|
||||
}
|
||||
|
||||
# 5. TiWorker.exe = worker-ul de servicing. Prezent => CBS lucrează chiar acum.
|
||||
if (Get-Process -Name "TiWorker" -ErrorAction SilentlyContinue) {
|
||||
$reasons += "TiWorker_running"
|
||||
}
|
||||
|
||||
# 6. TrustedInstaller pornit => Windows Modules Installer aplică ceva.
|
||||
if ((Get-Service -Name "TrustedInstaller" -ErrorAction SilentlyContinue).Status -eq "Running") {
|
||||
$reasons += "TrustedInstaller_running"
|
||||
}
|
||||
|
||||
# 7. Update Orchestrator activ => descărcare/scheduling în curs.
|
||||
if ((Get-Service -Name "UsoSvc" -ErrorAction SilentlyContinue).Status -eq "Running") {
|
||||
$reasons += "UsoSvc_running"
|
||||
}
|
||||
|
||||
# Notă: wuauserv nu este verificat intenționat — pornește și se oprește de la
|
||||
# sine în mod normal (inclusiv pentru definițiile Defender) și ar produce
|
||||
# fals-pozitive la fiecare rulare.
|
||||
|
||||
if ($reasons.Count -eq 0) {
|
||||
Write-Output "STATE=IDLE"
|
||||
exit 0
|
||||
} else {
|
||||
Write-Output ("STATE=BUSY REBOOT_PENDING=" + $rebootPending.ToString().ToLower() + " REASONS=" + ($reasons -join ","))
|
||||
exit 1
|
||||
}
|
||||
87
proxmox/vm109-windows-dr/scripts/install_updates.ps1
Normal file
87
proxmox/vm109-windows-dr/scripts/install_updates.ps1
Normal file
@@ -0,0 +1,87 @@
|
||||
# install_updates.ps1 — aplică update-urile Windows pe VM 109, controlat.
|
||||
#
|
||||
# Rulat exclusiv din vm109-patch-window.sh (fereastra lunară de patching).
|
||||
# În restul timpului VM 109 are NoAutoUpdate=1, ca Windows Update să nu mai
|
||||
# poată porni singur în timpul testului DR săptămânal (incident 2026-08-08).
|
||||
#
|
||||
# Folosește API-ul COM Microsoft.Update.Session — funcționează într-o sesiune
|
||||
# SSH non-interactivă, spre deosebire de UsoClient care raportează asincron și
|
||||
# nu întoarce niciun status utilizabil.
|
||||
#
|
||||
# Ieșire (linii parsabile din bash):
|
||||
# FOUND=<n>
|
||||
# INSTALLED=<n>
|
||||
# FAILED=<n>
|
||||
# REBOOT_REQUIRED=<true|false>
|
||||
# RESULT=<OK|NO_UPDATES|ERROR>
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
try {
|
||||
$session = New-Object -ComObject Microsoft.Update.Session
|
||||
$searcher = $session.CreateUpdateSearcher()
|
||||
|
||||
Write-Output "Searching for updates..."
|
||||
$result = $searcher.Search("IsInstalled=0 AND IsHidden=0")
|
||||
|
||||
Write-Output ("FOUND=" + $result.Updates.Count)
|
||||
|
||||
if ($result.Updates.Count -eq 0) {
|
||||
Write-Output "INSTALLED=0"
|
||||
Write-Output "FAILED=0"
|
||||
Write-Output "REBOOT_REQUIRED=false"
|
||||
Write-Output "RESULT=NO_UPDATES"
|
||||
exit 0
|
||||
}
|
||||
|
||||
$toInstall = New-Object -ComObject Microsoft.Update.UpdateColl
|
||||
foreach ($u in $result.Updates) {
|
||||
if ($u.EulaAccepted -eq $false) { $u.AcceptEula() }
|
||||
Write-Output (" + " + $u.Title)
|
||||
$toInstall.Add($u) | Out-Null
|
||||
}
|
||||
|
||||
Write-Output "Downloading..."
|
||||
$downloader = $session.CreateUpdateDownloader()
|
||||
$downloader.Updates = $toInstall
|
||||
$downloader.Download() | Out-Null
|
||||
|
||||
$ready = New-Object -ComObject Microsoft.Update.UpdateColl
|
||||
foreach ($u in $toInstall) {
|
||||
if ($u.IsDownloaded) { $ready.Add($u) | Out-Null }
|
||||
}
|
||||
|
||||
if ($ready.Count -eq 0) {
|
||||
Write-Output "INSTALLED=0"
|
||||
Write-Output "FAILED=0"
|
||||
Write-Output "REBOOT_REQUIRED=false"
|
||||
Write-Output "RESULT=ERROR"
|
||||
exit 1
|
||||
}
|
||||
|
||||
Write-Output "Installing..."
|
||||
$installer = $session.CreateUpdateInstaller()
|
||||
$installer.Updates = $ready
|
||||
$installResult = $installer.Install()
|
||||
|
||||
# ResultCode: 2 = succeeded, 3 = succeeded with errors, restul = eșec.
|
||||
$ok = 0
|
||||
$ko = 0
|
||||
for ($i = 0; $i -lt $ready.Count; $i++) {
|
||||
$code = $installResult.GetUpdateResult($i).ResultCode
|
||||
if ($code -eq 2 -or $code -eq 3) { $ok++ } else { $ko++ }
|
||||
}
|
||||
|
||||
Write-Output ("INSTALLED=" + $ok)
|
||||
Write-Output ("FAILED=" + $ko)
|
||||
Write-Output ("REBOOT_REQUIRED=" + $installResult.RebootRequired.ToString().ToLower())
|
||||
|
||||
if ($ko -gt 0) { Write-Output "RESULT=ERROR"; exit 1 }
|
||||
Write-Output "RESULT=OK"
|
||||
exit 0
|
||||
}
|
||||
catch {
|
||||
Write-Output ("ERROR: " + $_.Exception.Message)
|
||||
Write-Output "RESULT=ERROR"
|
||||
exit 1
|
||||
}
|
||||
202
proxmox/vm109-windows-dr/scripts/vm109-patch-window.sh
Normal file
202
proxmox/vm109-windows-dr/scripts/vm109-patch-window.sh
Normal file
@@ -0,0 +1,202 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
# vm109-patch-window.sh — fereastră lunară de patching pentru VM 109 (DR Oracle).
|
||||
#
|
||||
# De ce există: incident 2026-08-08.
|
||||
# VM 109 este pornit doar în timpul testului DR săptămânal (Sâmbătă 06:00).
|
||||
# Prin urmare acela era singurul moment în care Windows Update putea rula —
|
||||
# iar 06:00 este în afara Active Hours (08:00-17:00), deci Windows îl trata ca
|
||||
# fereastră de mentenanță validă. KB5101001 a fost descărcat în timpul testului
|
||||
# din 2026-08-01, a rămas staged după `qm stop`, și s-a finalizat la boot-ul
|
||||
# testului din 2026-08-08: RestartManager a omorât powershell.exe (restore-ul)
|
||||
# și sshd, VM-ul a rebootat la 72s după boot, iar testul a raportat
|
||||
# "Restore failed" fără niciun log RMAN.
|
||||
#
|
||||
# Soluția: VM 109 are acum NoAutoUpdate=1 (Windows Update nu mai pornește
|
||||
# singur), iar patching-ul se face aici — într-o fereastră dedicată, unde
|
||||
# reboot-urile sunt așteptate și inofensive.
|
||||
#
|
||||
# Instalare (pe nodul care găzduiește VM 109 — și pe pvemini, pentru failover):
|
||||
# cp vm109-patch-window.sh /opt/scripts/
|
||||
# chmod +x /opt/scripts/vm109-patch-window.sh
|
||||
# crontab -e
|
||||
# # Prima duminică din lună, 03:00 — cron face OR între DOM și DOW,
|
||||
# # deci restricția pe duminică se face în script (GUARD_FIRST_SUNDAY).
|
||||
# 0 3 1-7 * * /opt/scripts/vm109-patch-window.sh >/dev/null 2>&1
|
||||
#
|
||||
# Rulare manuală (ignoră garda de calendar):
|
||||
# /opt/scripts/vm109-patch-window.sh --now
|
||||
|
||||
set -euo pipefail
|
||||
export PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
|
||||
|
||||
DR_VM_ID="109"
|
||||
DR_VM_IP="10.0.20.37"
|
||||
DR_VM_PORT="22122"
|
||||
DR_VM_USER="romfast"
|
||||
DEBUG_FLAG="/var/run/vm109-debug.flag"
|
||||
LOG="/var/log/oracle-dr/patch-window.log"
|
||||
MAX_BOOT_WAIT=300 # secunde de așteptare pentru SSH după boot/reboot
|
||||
MAX_REBOOTS=3 # cicluri de reboot permise într-o fereastră
|
||||
MAIL_TO="root"
|
||||
|
||||
mkdir -p "$(dirname "$LOG")"
|
||||
log() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" | tee -a "$LOG"; }
|
||||
|
||||
# Rulăm doar pe nodul care găzduiește efectiv VM 109 (cluster-aware, ca watchdog-ul).
|
||||
[ -f "/etc/pve/qemu-server/${DR_VM_ID}.conf" ] || exit 0
|
||||
|
||||
# Garda de calendar: prima duminică din lună. Cron nu poate exprima asta singur.
|
||||
if [ "${1:-}" != "--now" ]; then
|
||||
if [ "$(date +%u)" != "7" ]; then exit 0; fi
|
||||
if [ "$(date +%-d)" -gt 7 ]; then exit 0; fi
|
||||
fi
|
||||
|
||||
# Nu ne suprapunem peste testul DR (Sâmbătă 05:55-07:30) și nici peste altă rulare.
|
||||
exec 9>/var/run/vm109-patch-window.lock
|
||||
if ! flock -n 9; then
|
||||
log "Another patch window is already running, exiting"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
VM_STARTED_BY_US=false
|
||||
FLAG_SET_BY_US=false
|
||||
RESULT="UNKNOWN"
|
||||
|
||||
cleanup() {
|
||||
local rc=$?
|
||||
if [ "$VM_STARTED_BY_US" = "true" ] && qm status "$DR_VM_ID" 2>/dev/null | grep -q running; then
|
||||
log "Cleanup: stopping VM $DR_VM_ID"
|
||||
ssh -p "$DR_VM_PORT" -o ConnectTimeout=10 "$DR_VM_USER@$DR_VM_IP" "shutdown /s /t 15 /f" 2>/dev/null || true
|
||||
sleep 45
|
||||
qm stop "$DR_VM_ID" 2>/dev/null || true
|
||||
fi
|
||||
if [ "$FLAG_SET_BY_US" = "true" ]; then
|
||||
rm -f "$DEBUG_FLAG"
|
||||
log "Cleanup: watchdog debug flag cleared"
|
||||
fi
|
||||
log "Patch window finished with result=$RESULT (rc=$rc)"
|
||||
exit $rc
|
||||
}
|
||||
trap cleanup EXIT
|
||||
|
||||
wait_for_ssh() {
|
||||
local waited=0
|
||||
while [ $waited -lt $MAX_BOOT_WAIT ]; do
|
||||
if ssh -p "$DR_VM_PORT" -o ConnectTimeout=10 -o StrictHostKeyChecking=no -o BatchMode=yes \
|
||||
"$DR_VM_USER@$DR_VM_IP" "powershell -Command 'Write-Output ready'" >/dev/null 2>&1; then
|
||||
log "VM responsive after ${waited}s"
|
||||
return 0
|
||||
fi
|
||||
sleep 10
|
||||
waited=$((waited + 10))
|
||||
done
|
||||
log "ERROR: VM did not become responsive within ${MAX_BOOT_WAIT}s"
|
||||
return 1
|
||||
}
|
||||
|
||||
# NoAutoUpdate: 1 = Windows Update dezactivat (starea normală a VM 109),
|
||||
# 0 = permis, doar pe durata acestei ferestre.
|
||||
set_auto_update() {
|
||||
local value="$1"
|
||||
ssh -p "$DR_VM_PORT" -o ConnectTimeout=15 "$DR_VM_USER@$DR_VM_IP" \
|
||||
"powershell -Command \"Set-ItemProperty -Path 'HKLM:\\SOFTWARE\\Policies\\Microsoft\\Windows\\WindowsUpdate\\AU' -Name NoAutoUpdate -Value $value -Type DWord\"" 2>/dev/null
|
||||
}
|
||||
|
||||
log "=========================================="
|
||||
log "VM $DR_VM_ID monthly patch window - Starting"
|
||||
log "=========================================="
|
||||
|
||||
# Watchdog-ul oprește forțat VM 109 dacă rulează > 60 min în afara ferestrei de
|
||||
# test. Patching-ul depășește ușor 60 min, deci cerem exceptarea documentată.
|
||||
if [ ! -f "$DEBUG_FLAG" ]; then
|
||||
touch "$DEBUG_FLAG"
|
||||
FLAG_SET_BY_US=true
|
||||
log "Watchdog debug flag set (patching exceeds the 60 min watchdog limit)"
|
||||
fi
|
||||
|
||||
if qm status "$DR_VM_ID" 2>/dev/null | grep -q running; then
|
||||
log "ERROR: VM $DR_VM_ID is already running - refusing to interfere"
|
||||
RESULT="SKIPPED_VM_BUSY"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
log "Starting VM $DR_VM_ID"
|
||||
qm start "$DR_VM_ID"
|
||||
VM_STARTED_BY_US=true
|
||||
wait_for_ssh || { RESULT="BOOT_FAILED"; exit 1; }
|
||||
|
||||
log "Enabling Windows Update for the duration of this window"
|
||||
set_auto_update 0
|
||||
|
||||
reboots=0
|
||||
while :; do
|
||||
log "Running update scan/download/install..."
|
||||
set +e
|
||||
out=$(ssh -p "$DR_VM_PORT" -o ConnectTimeout=20 "$DR_VM_USER@$DR_VM_IP" \
|
||||
"powershell -ExecutionPolicy Bypass -File D:\\oracle\\scripts\\install_updates.ps1" 2>&1 | tr -d '\r')
|
||||
set -e
|
||||
echo "$out" >> "$LOG"
|
||||
|
||||
found=$(echo "$out" | grep -oP '^FOUND=\K\d+' | tail -1 || echo 0)
|
||||
installed=$(echo "$out"| grep -oP '^INSTALLED=\K\d+' | tail -1 || echo 0)
|
||||
failed=$(echo "$out" | grep -oP '^FAILED=\K\d+' | tail -1 || echo 0)
|
||||
reboot_req=$(echo "$out" | grep -oP '^REBOOT_REQUIRED=\K\S+' | tail -1 || echo false)
|
||||
ps_result=$(echo "$out"| grep -oP '^RESULT=\K\S+' | tail -1 || echo ERROR)
|
||||
|
||||
log "Scan result: found=$found installed=$installed failed=$failed reboot=$reboot_req status=$ps_result"
|
||||
|
||||
if [ "$ps_result" = "NO_UPDATES" ]; then
|
||||
log "System fully patched"
|
||||
RESULT="OK_NO_UPDATES"
|
||||
break
|
||||
fi
|
||||
|
||||
if [ "$ps_result" = "ERROR" ]; then
|
||||
log "ERROR: update installation reported failures"
|
||||
RESULT="UPDATE_ERRORS"
|
||||
break
|
||||
fi
|
||||
|
||||
if [ "$reboot_req" != "true" ]; then
|
||||
log "Updates installed, no reboot required"
|
||||
RESULT="OK_PATCHED"
|
||||
break
|
||||
fi
|
||||
|
||||
reboots=$((reboots + 1))
|
||||
if [ $reboots -gt $MAX_REBOOTS ]; then
|
||||
log "ERROR: exceeded $MAX_REBOOTS reboot cycles, giving up"
|
||||
RESULT="TOO_MANY_REBOOTS"
|
||||
break
|
||||
fi
|
||||
|
||||
log "Reboot required - rebooting VM (cycle $reboots/$MAX_REBOOTS)"
|
||||
ssh -p "$DR_VM_PORT" -o ConnectTimeout=10 "$DR_VM_USER@$DR_VM_IP" "shutdown /r /t 5 /f" 2>/dev/null || true
|
||||
sleep 60
|
||||
wait_for_ssh || { RESULT="REBOOT_FAILED"; break; }
|
||||
done
|
||||
|
||||
# Re-armăm blocarea, indiferent de rezultat: VM 109 nu are voie să intre în
|
||||
# testul DR de sâmbătă cu Windows Update activ.
|
||||
log "Re-arming NoAutoUpdate=1"
|
||||
set_auto_update 1
|
||||
|
||||
# Verificăm că sistemul rămâne într-o stare curată pentru testul următor.
|
||||
set +e
|
||||
servicing=$(ssh -p "$DR_VM_PORT" -o ConnectTimeout=15 "$DR_VM_USER@$DR_VM_IP" \
|
||||
"powershell -ExecutionPolicy Bypass -File D:\\oracle\\scripts\\check_servicing.ps1" 2>/dev/null | tr -d '\r')
|
||||
set -e
|
||||
log "Post-patch servicing state: ${servicing:-unavailable}"
|
||||
|
||||
if ! echo "${servicing:-}" | grep -q "STATE=IDLE"; then
|
||||
log "WARNING: servicing not idle after patch window - next DR test may be affected"
|
||||
RESULT="${RESULT}_DIRTY"
|
||||
fi
|
||||
|
||||
# Notificare doar când e ceva de semnalat — succesul tăcut e suficient.
|
||||
if [ "$RESULT" != "OK_NO_UPDATES" ] && [ "$RESULT" != "OK_PATCHED" ]; then
|
||||
printf 'VM %s monthly patch window ended with result: %s\n\nLog: %s\n' \
|
||||
"$DR_VM_ID" "$RESULT" "$LOG" \
|
||||
| mail -s "[DR] VM $DR_VM_ID patch window: $RESULT" "$MAIL_TO" 2>/dev/null || true
|
||||
fi
|
||||
@@ -357,6 +357,7 @@ run_dr_test() {
|
||||
local cleanup_freed=0
|
||||
local backup_count=0
|
||||
local restore_log="Not collected"
|
||||
local servicing_abort=false
|
||||
|
||||
log "=========================================="
|
||||
log "Oracle DR Weekly Test - Starting"
|
||||
@@ -467,12 +468,92 @@ run_dr_test() {
|
||||
WARNINGS+=("NFS mount may need manual intervention")
|
||||
fi
|
||||
|
||||
# Step 3b: Windows servicing guard
|
||||
#
|
||||
# Incident 2026-08-08: KB5101001 descărcat în timpul testului din
|
||||
# 2026-08-01 s-a finalizat la boot-ul următorului test. RestartManager
|
||||
# a omorât powershell.exe (restore-ul) la 06:00:58, sshd la 06:01:04,
|
||||
# iar VM-ul a rebootat la 06:01:38 — testul a raportat "Restore failed"
|
||||
# la 11s, fără log RMAN, deși RMAN nu pornise deloc.
|
||||
#
|
||||
# Măsura principală este NoAutoUpdate=1 pe VM 109 (vezi
|
||||
# vm109-patch-window.sh pentru fereastra lunară de patching).
|
||||
# Guard-ul de aici este plasa de siguranță: dacă totuși stack-ul de
|
||||
# servicing este activ, nu pornim restore-ul într-un VM care e pe
|
||||
# cale să se reboteze — raportăm cauza reală în loc de un
|
||||
# "Restore failed" care arată ca o problemă de backup.
|
||||
step_start=$(date +%s)
|
||||
log "STEP 3b: Checking Windows servicing state"
|
||||
|
||||
local servicing_out=""
|
||||
local servicing_idle=false
|
||||
local SERVICING_WAIT=300
|
||||
local servicing_elapsed=0
|
||||
|
||||
while [ $servicing_elapsed -lt $SERVICING_WAIT ]; do
|
||||
if servicing_out=$(ssh -p "$DR_VM_PORT" -o ConnectTimeout=10 -o StrictHostKeyChecking=no "$DR_VM_USER@$DR_VM_IP" \
|
||||
"powershell -ExecutionPolicy Bypass -File D:\\oracle\\scripts\\check_servicing.ps1" 2>/dev/null | tr -d '\r'); then
|
||||
servicing_idle=true
|
||||
break
|
||||
fi
|
||||
|
||||
# Check absent (ieșire non-zero fără output) => nu blocăm testul.
|
||||
if [ -z "$servicing_out" ]; then
|
||||
log_warning "check_servicing.ps1 did not respond, skipping servicing guard"
|
||||
WARNINGS+=("Servicing guard skipped: check_servicing.ps1 missing or unreachable on VM $DR_VM_ID")
|
||||
servicing_idle=true
|
||||
break
|
||||
fi
|
||||
|
||||
log "Windows servicing busy: $servicing_out (${servicing_elapsed}s/${SERVICING_WAIT}s)"
|
||||
sleep 15
|
||||
servicing_elapsed=$((servicing_elapsed + 15))
|
||||
done
|
||||
|
||||
if [ "$servicing_idle" = true ]; then
|
||||
track_step "Windows Servicing Check" true "Servicing stack idle" "$step_start"
|
||||
else
|
||||
# Reboot în așteptare: îl consumăm controlat, o singură dată,
|
||||
# ca testul să poată continua pe un sistem stabil.
|
||||
if echo "$servicing_out" | grep -q "REBOOT_PENDING=true"; then
|
||||
log_warning "Pending reboot detected, rebooting VM $DR_VM_ID once before restore"
|
||||
ssh -p "$DR_VM_PORT" -o ConnectTimeout=10 "$DR_VM_USER@$DR_VM_IP" "shutdown /r /t 5 /f" 2>/dev/null || true
|
||||
sleep 45
|
||||
|
||||
local reboot_elapsed=0
|
||||
while [ $reboot_elapsed -lt 300 ]; do
|
||||
if servicing_out=$(ssh -p "$DR_VM_PORT" -o ConnectTimeout=10 -o StrictHostKeyChecking=no -o BatchMode=yes "$DR_VM_USER@$DR_VM_IP" \
|
||||
"powershell -ExecutionPolicy Bypass -File D:\\oracle\\scripts\\check_servicing.ps1" 2>/dev/null | tr -d '\r'); then
|
||||
servicing_idle=true
|
||||
break
|
||||
fi
|
||||
sleep 15
|
||||
reboot_elapsed=$((reboot_elapsed + 15))
|
||||
done
|
||||
fi
|
||||
|
||||
if [ "$servicing_idle" = true ]; then
|
||||
track_step "Windows Servicing Check" true "Servicing completed after controlled reboot" "$step_start"
|
||||
WARNINGS+=("VM $DR_VM_ID had a pending servicing reboot; consumed before restore. Check NoAutoUpdate policy.")
|
||||
else
|
||||
track_step "Windows Servicing Check" false \
|
||||
"Windows Update/servicing active, restore not attempted - backups NOT implicated ($servicing_out)" "$step_start"
|
||||
test_result="ABORTED - Windows servicing"
|
||||
servicing_abort=true
|
||||
fi
|
||||
fi
|
||||
|
||||
# Step 4: Run restore
|
||||
step_start=$(date +%s)
|
||||
local restore_start=$step_start
|
||||
log "STEP 4: Running database restore"
|
||||
|
||||
if ssh -p "$DR_VM_PORT" "$DR_VM_USER@$DR_VM_IP" \
|
||||
if [ "$servicing_abort" = true ]; then
|
||||
log_error "Skipping restore: Windows servicing active on VM $DR_VM_ID"
|
||||
# Fără track_step aici: eșecul e deja raportat de "Windows Servicing
|
||||
# Check", iar un al doilea ERRORS ar duplica alarma pentru o singură cauză.
|
||||
restore_log="Restore not attempted. Windows Update/servicing was active on VM $DR_VM_ID: $servicing_out"
|
||||
elif ssh -p "$DR_VM_PORT" "$DR_VM_USER@$DR_VM_IP" \
|
||||
"powershell -ExecutionPolicy Bypass -File D:\\oracle\\scripts\\rman_restore_from_zero.ps1 -TestMode" 2>&1 | tee -a "$LOG_FILE"; then
|
||||
|
||||
local restore_end=$(date +%s)
|
||||
|
||||
Reference in New Issue
Block a user