Ring1 aplicat si verificat pe cluster live: config_version 17, ring1_addr pe
10.10.10.20x la fiecare nod, interface{linknumber:1}. Validat cu `corosync -t`
inainte de instalare. Testat prin oprirea reala a lui ring0 pe pveelite: link0
disconnected, link1 connected, cvorum 3/3 neatins - exact scenariul care pe
27 august a lasat nodul mort 16 ore.
Test de repornire completa a clusterului, cu Wake-on-LAN (trezire in ~15s).
Insula a urcat singura pe toate trei nodurile; ipoteza enumerarii tarzii a
USB-ului nu s-a materializat. Unealta noua: wake-cluster.ps1.
Testul a scos la iveala o linie ramasa in /etc/fstab pe pve1 si pveelite, care
monta storage-ul NFS de pe IP-ul de productie inaintea lui pvestatd. Backup-ul
trecea tacut pe reteaua gresita, cu storage.cfg corect. cluster-startup verifica
acum asta automat, impreuna cu IP-urile de insula si conectivitatea reala.
Patru bug-uri gasite prin rulare pe cluster live:
- sonda Oracle nu avea timeout: un sqlplus agatat pe o instanta in pornire a
blocat cluster-startup 14 minute, fara mesaj, cu propriul prag de 600s
nefolosit, fiindca bucla n-a apucat o iteratie;
- `bash -c` in loc de `bash -lc`: sqlplus lipsea din PATH, deci baza nu se
oprea si containerul s-ar fi inchis peste ea;
- PowerShell 5.1 pierde ghilimelele duble catre exe-uri native, deci comanda
ajungea rupta pe nod - trecut pe trimitere codificata base64;
- backup-ul de crontab si fisierul de stare se rescriau la o a doua rulare,
lasand monitorizarea oprita permanent si lista de repornit goala.
Documentatia de oprire planificata pornea de la o afirmatie devenita falsa
("nu exista datacenter.cfg") si de la o comanda care ar fi adaugat o a doua
linie `ha:`. Actualizata, impreuna cu inventarul de guest-uri (VM 304 lipsea
din toate listele de ordine si cadea in maturarea de dupa Oracle).
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RJFQZcA7uerXRaL1NrsXpS
536 lines
21 KiB
PowerShell
536 lines
21 KiB
PowerShell
<#
|
|
.SYNOPSIS
|
|
Repornirea clusterului Proxmox `romfast` dupa o oprire planificata.
|
|
|
|
.DESCRIPTION
|
|
Asteapta nodurile, asteapta quorumul, porneste guest-urile in ordinea corecta
|
|
de dependente (Oracle primul), restaureaza cron-urile de monitorizare.
|
|
|
|
Porneste DOAR guest-urile care rulau inainte de oprire, citite din fisierul de
|
|
stare scris de cluster-shutdown. Fara fisier de stare, foloseste lista
|
|
implicita, minus guest-urile din NeverAutostart.
|
|
|
|
RULEAZA DE PE STATIA DE ADMIN, nu de pe un nod.
|
|
|
|
NODURILE TREBUIE PORNITE FIZIC INTAI. Scriptul le asteapta.
|
|
Porneste-le pe TOATE TREI aproximativ simultan: un singur nod pornit nu are
|
|
quorum, /etc/pve ramane read-only si nu se poate porni niciun guest.
|
|
|
|
.PARAMETER Yes
|
|
Nu cere confirmari.
|
|
|
|
.PARAMETER DryRun
|
|
Arata ce ar face, fara sa execute nimic care modifica starea.
|
|
|
|
.PARAMETER All
|
|
Ignora fisierul de stare, foloseste lista implicita.
|
|
|
|
.EXAMPLE
|
|
.\cluster-startup.ps1 -DryRun
|
|
.EXAMPLE
|
|
.\cluster-startup.ps1
|
|
#>
|
|
[CmdletBinding()]
|
|
param(
|
|
[switch]$Yes,
|
|
[switch]$DryRun,
|
|
[switch]$All
|
|
)
|
|
|
|
$ErrorActionPreference = 'Stop'
|
|
|
|
# ---------------------------------------------------------------- configurare
|
|
|
|
$NodeIp = [ordered]@{
|
|
pve1 = '10.0.20.200'
|
|
pvemini = '10.0.20.201'
|
|
pveelite = '10.0.20.202'
|
|
}
|
|
|
|
# Ordinea de PORNIRE: baza de date intai, consumatorii dupa.
|
|
# Inversul ordinii de oprire din cluster-shutdown.
|
|
$GuestStartOrder = @(
|
|
108, # central-oracle - PRIMUL, restul depind de el
|
|
201, # roacentral - IIS reverse proxy, consumator Oracle
|
|
104, # flowise - consumator Oracle
|
|
100, # portainer
|
|
102, # docker.romfast.ro
|
|
103, # dokploy
|
|
106, # gitea
|
|
171, # claude-agent
|
|
101, # minecraft
|
|
110, # moltbot
|
|
303, # Win11-Adina
|
|
304, # Win11-Marius
|
|
302, # oracle-test-302
|
|
109, # oracle-dr-windows
|
|
301, # docker-portainer-template
|
|
310 # Win11-Template
|
|
)
|
|
|
|
# Guest-uri care NU se pornesc automat cand LIPSESTE fisierul de stare.
|
|
# Daca fisierul de stare exista, el decide - daca erau pornite, se repornesc.
|
|
# 109 - VM DR. Pornirea lui nedorita a declansat bucla OOM din incidentul
|
|
# 2026-04-20; in mod normal sta oprit si e pornit doar de testul DR.
|
|
# 301, 310 - template-uri, nu servicii.
|
|
$NeverAutostart = @(109, 301, 310)
|
|
|
|
# Reteaua dedicata de cluster ("insula"), existenta din 2026-08-29. De ea atarna
|
|
# replicarea ZFS (migration: network=10.10.10.0/24 in datacenter.cfg) si storage-ul
|
|
# NFS backup-pvemini. Pe pve1 si pveelite IP-ul sta pe dongle-ul USB, adus la boot
|
|
# de un "auto enx..." din /etc/network/interfaces: daca nucleul enumereaza USB-ul
|
|
# DUPA ce networking.service a terminat, interfata ramane fara IP, iar nodul pare
|
|
# perfect sanatos. Detalii: ../docs/switch-2.5g-dedicat-noduri.md
|
|
$IslandIp = [ordered]@{
|
|
pve1 = '10.10.10.200'
|
|
pvemini = '10.10.10.201'
|
|
pveelite = '10.10.10.202'
|
|
}
|
|
$IslandNfsMount = '/mnt/pve/backup-pvemini'
|
|
$IslandNfsServer = '10.10.10.201'
|
|
$IslandNfsNodes = @('pve1', 'pveelite')
|
|
|
|
$OracleCt = 108
|
|
$OracleDocker = 'oracle-xe'
|
|
# Masurat pe 2026-08-29, la pornire de la rece dupa oprirea completa a clusterului:
|
|
# instanta a avut nevoie de ~10 minute ca sa ajunga OPEN. 600s erau prea strimti.
|
|
$OracleWait = 1200 # secunde asteptate pana baza raspunde la interogari
|
|
|
|
$NodeWait = 1800 # secunde asteptate pana apar toate nodurile
|
|
$QuorumWait = 300
|
|
$GuestWait = 300
|
|
$CronBackup = '/root/crontab.backup-mentenanta.txt'
|
|
|
|
$ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
|
|
$StateFile = if ($env:CLUSTER_STATE_FILE) { $env:CLUSTER_STATE_FILE } else { Join-Path $ScriptDir '.cluster-state.txt' }
|
|
|
|
# ------------------------------------------------------------------- utilitare
|
|
|
|
function Write-Step { param([string]$Text) Write-Host ""; Write-Host "== $Text" -ForegroundColor Green }
|
|
function Write-Ok { param([string]$Text) Write-Host " [ok] $Text" -ForegroundColor Green }
|
|
function Write-Warn { param([string]$Text) Write-Host " [!] $Text" -ForegroundColor Yellow }
|
|
function Write-Log { param([string]$Text) Write-Host "[$(Get-Date -Format HH:mm:ss)] $Text" -ForegroundColor DarkGray }
|
|
function Stop-WithError { param([string]$Text) Write-Host " [X] $Text" -ForegroundColor Red; exit 1 }
|
|
|
|
function Test-Alive {
|
|
param([string]$Address, [int]$TimeoutMs = 2000)
|
|
try {
|
|
$p = New-Object System.Net.NetworkInformation.Ping
|
|
return ($p.Send($Address, $TimeoutMs).Status -eq 'Success')
|
|
} catch {
|
|
return $false
|
|
}
|
|
}
|
|
|
|
function Invoke-Node {
|
|
param([string]$Node, [string]$Command)
|
|
$out = ssh -o BatchMode=yes -o ConnectTimeout=8 -o StrictHostKeyChecking=accept-new -o LogLevel=ERROR "root@$($NodeIp[$Node])" $Command
|
|
return $out
|
|
}
|
|
|
|
function Invoke-NodeChange {
|
|
param([string]$Node, [string]$Command)
|
|
if ($DryRun) {
|
|
Write-Host " [dry-run] ${Node}: $Command" -ForegroundColor DarkGray
|
|
return @()
|
|
}
|
|
return Invoke-Node -Node $Node -Command $Command
|
|
}
|
|
|
|
# Comenzi cu ghilimele imbricate NU se trimit prin Invoke-Node.
|
|
#
|
|
# PowerShell 5.1 pierde ghilimelele duble cand paseaza un argument catre un
|
|
# executabil nativ: `bash -lc "printf '...' | sqlplus"` ajunge pe nod ca
|
|
# `bash -lc printf '...' | sqlplus`, adica printf fara format, iar sqlplus rulat
|
|
# pe NOD, unde nu exista. Esecul e usor de citit gresit ca "lipseste sqlplus".
|
|
# In loc sa ne luptam cu nivelurile de citare, trimitem scriptul codificat.
|
|
# Verificat pe 2026-08-29, dupa ce varianta directa a esuat pe cluster live.
|
|
function Invoke-NodeScript {
|
|
param([string]$Node, [string]$Script)
|
|
$lf = $Script -replace "`r`n", "`n"
|
|
$b64 = [Convert]::ToBase64String([Text.Encoding]::UTF8.GetBytes($lf))
|
|
return Invoke-Node -Node $Node -Command "echo $b64 | base64 -d | bash"
|
|
}
|
|
|
|
function Confirm-Step {
|
|
param([string]$Message)
|
|
if ($Yes -or $DryRun) { return }
|
|
Write-Host ""
|
|
Write-Host $Message -ForegroundColor Yellow
|
|
$reply = Read-Host "Scrie DA ca sa continui"
|
|
if ($reply -ne 'DA') { Stop-WithError "Anulat de utilizator." }
|
|
}
|
|
|
|
function Get-NodeGuests {
|
|
param([string]$Node)
|
|
$result = @()
|
|
|
|
foreach ($line in ((Invoke-Node -Node $Node -Command 'pct list') | Select-Object -Skip 1)) {
|
|
$f = ($line -split '\s+') | Where-Object { $_ -ne '' }
|
|
if ($f.Count -ge 2) {
|
|
$result += [pscustomobject]@{ Type='ct'; Node=$Node; Id=[int]$f[0]; Status=$f[1] }
|
|
}
|
|
}
|
|
foreach ($line in ((Invoke-Node -Node $Node -Command 'qm list') | Select-Object -Skip 1)) {
|
|
$f = ($line -split '\s+') | Where-Object { $_ -ne '' }
|
|
if ($f.Count -ge 3) {
|
|
$result += [pscustomobject]@{ Type='vm'; Node=$Node; Id=[int]$f[0]; Status=$f[2] }
|
|
}
|
|
}
|
|
return $result
|
|
}
|
|
|
|
function Get-GuestStatus {
|
|
param([string]$Node, [string]$Type, [int]$Id)
|
|
$cmd = if ($Type -eq 'ct') { "pct status $Id" } else { "qm status $Id" }
|
|
$out = Invoke-Node -Node $Node -Command $cmd
|
|
if ($out -match 'status:\s*(\w+)') { return $Matches[1] }
|
|
return 'unknown'
|
|
}
|
|
|
|
# ------------------------------------------------- pas 1: asteapta nodurile
|
|
|
|
Write-Step "Astept nodurile"
|
|
Write-Warn "Nodurile trebuie pornite FIZIC. Porneste-le pe toate trei aproximativ simultan."
|
|
|
|
$waited = 0
|
|
$missing = @()
|
|
while ($waited -lt $NodeWait) {
|
|
$missing = @()
|
|
foreach ($node in $NodeIp.Keys) {
|
|
if (-not (Test-Alive $NodeIp[$node])) { $missing += $node }
|
|
}
|
|
if ($missing.Count -eq 0) { break }
|
|
Write-Host "`r astept: $($missing -join ', ') (${waited}s) " -NoNewline -ForegroundColor DarkGray
|
|
Start-Sleep -Seconds 10
|
|
$waited += 10
|
|
}
|
|
Write-Host "`r `r" -NoNewline
|
|
if ($missing.Count -ne 0) { Stop-WithError "Nodurile $($missing -join ', ') nu au aparut in ${NodeWait}s." }
|
|
|
|
foreach ($node in $NodeIp.Keys) {
|
|
$w = 0
|
|
while ($true) {
|
|
$h = Invoke-Node -Node $node -Command 'uptime -p'
|
|
if ($LASTEXITCODE -eq 0 -and $h) { break }
|
|
$w += 10
|
|
if ($w -gt 300) { Stop-WithError "$node raspunde la ping dar nu la SSH." }
|
|
Start-Sleep -Seconds 10
|
|
}
|
|
Write-Ok "$node - up, SSH OK ($h)"
|
|
}
|
|
|
|
# --------------------------------------------------- pas 2: asteapta quorum
|
|
|
|
Write-Step "Astept quorumul"
|
|
|
|
$waited = 0
|
|
while ($true) {
|
|
$q = Invoke-Node -Node 'pvemini' -Command 'pvecm status'
|
|
if (($q -join "`n") -match 'Quorate:\s*Yes') { break }
|
|
if ($waited -ge $QuorumWait) { Stop-WithError "Clusterul nu a atins quorumul in ${QuorumWait}s." }
|
|
Write-Host "`r astept quorum (${waited}s)... " -NoNewline -ForegroundColor DarkGray
|
|
Start-Sleep -Seconds 10
|
|
$waited += 10
|
|
}
|
|
Write-Host "`r `r" -NoNewline
|
|
|
|
$votes = 0
|
|
if (($q -join "`n") -match 'Total votes:\s*(\d+)') { $votes = [int]$Matches[1] }
|
|
if ($votes -ne 3) { Write-Warn "Quorum atins, dar doar $votes/3 voturi. Un nod lipseste din cluster." }
|
|
Write-Ok "quorum OK, $votes/3 voturi"
|
|
|
|
Invoke-Node -Node 'pvemini' -Command 'ha-manager status' | Select-Object -First 4 | Write-Host
|
|
|
|
# ------------------------------------ pas 3: reteaua dedicata de cluster (insula)
|
|
|
|
# Se verifica INAINTE de pornirea guest-urilor: daca insula e jos, replicarea si
|
|
# backup-ul NFS tac fara nicio alerta, iar simptomul apare abia peste ore.
|
|
# Nu blocheaza pornirea - la revenirea curentului, Oracle sus conteaza mai mult
|
|
# decat replicarea - dar nu te lasa sa treci pe langa problema fara sa o vezi.
|
|
|
|
Write-Step "Verific reteaua de cluster (10.10.10.0/24)"
|
|
|
|
if ($DryRun) {
|
|
Write-Warn "dry-run - sar peste verificarea insulei"
|
|
} else {
|
|
$islandBad = @()
|
|
|
|
foreach ($node in $NodeIp.Keys) {
|
|
$want = $IslandIp[$node]
|
|
$have = (Invoke-Node -Node $node -Command "ip -4 -o addr show | grep -c ' $want/' || true") -join ''
|
|
|
|
if ($have.Trim() -eq '0') {
|
|
# Cazul asteptat de esec: dongle-ul USB enumerat tarziu la boot.
|
|
# ifreload -a aplica doar diferentele fata de /etc/network/interfaces,
|
|
# deci nu atinge interfetele tap/veth ale guest-urilor.
|
|
Write-Warn "$node - lipseste $want, incerc 'ifreload -a'"
|
|
Invoke-Node -Node $node -Command 'PATH=/usr/sbin:/sbin:/usr/bin:/bin ifreload -a' | Out-Null
|
|
Start-Sleep -Seconds 8
|
|
$have = (Invoke-Node -Node $node -Command "ip -4 -o addr show | grep -c ' $want/' || true") -join ''
|
|
}
|
|
|
|
if ($have.Trim() -eq '0') {
|
|
$islandBad += $node
|
|
Write-Warn "$node - TOT fara $want dupa ifreload"
|
|
} else {
|
|
Write-Ok "$node - $want prezent"
|
|
}
|
|
}
|
|
|
|
# Un IP configurat local nu garanteaza si cablul/switch-ul. pvemini e sursa
|
|
# replicarii si serverul NFS, deci se verifica din el spre celelalte doua.
|
|
foreach ($node in @('pve1', 'pveelite')) {
|
|
$ip = $IslandIp[$node]
|
|
$p = (Invoke-Node -Node 'pvemini' -Command "ping -c1 -W2 $ip >/dev/null 2>&1 && echo OK || echo FAIL") -join ''
|
|
if ($p.Trim() -eq 'OK') {
|
|
Write-Ok "pvemini -> $node ($ip) - ping OK"
|
|
} else {
|
|
if ($islandBad -notcontains $node) { $islandBad += $node }
|
|
Write-Warn "pvemini -> $node ($ip) - NU raspunde (cablu? switch?)"
|
|
}
|
|
}
|
|
|
|
$nfsBad = @()
|
|
foreach ($node in $IslandNfsNodes) {
|
|
$src = (Invoke-Node -Node $node -Command "findmnt -no SOURCE $IslandNfsMount 2>/dev/null || true") -join ''
|
|
if ($src -match [regex]::Escape($IslandNfsServer)) {
|
|
Write-Ok "$node - NFS $IslandNfsMount montat de pe $IslandNfsServer"
|
|
} elseif ($src.Trim()) {
|
|
# Prins pe 2026-08-29 la primul boot la rece: o linie ramasa in /etc/fstab
|
|
# monta storage-ul de pe IP-ul de productie INAINTE ca pvestatd sa apuce,
|
|
# deci backup-ul mergea tacut pe reteaua gresita, cu storage.cfg corect.
|
|
$nfsBad += $node
|
|
Write-Warn "$node - NFS montat de pe ALT server: $src (asteptat $IslandNfsServer)"
|
|
Write-Warn " verifica: ssh root@$($NodeIp[$node]) 'grep nfs /etc/fstab'"
|
|
} else {
|
|
# pvestatd remonteaza singur in cateva zeci de secunde daca reteaua e sus.
|
|
Write-Warn "$node - NFS $IslandNfsMount NEMONTAT (pvestatd il reia daca insula urca)"
|
|
}
|
|
}
|
|
|
|
if ($nfsBad.Count -gt 0) {
|
|
Write-Warn "Backup-ul NFS trece pe alta retea decat cea proiectata pe: $($nfsBad -join ', ')"
|
|
}
|
|
|
|
if ($islandBad.Count -gt 0) {
|
|
Write-Warn ""
|
|
Write-Warn "INSULA E JOS pe: $($islandBad -join ', ')"
|
|
Write-Warn "Consecinta: replicarea ZFS si storage-ul NFS backup-pvemini NU vor functiona."
|
|
Write-Warn "Guest-urile pornesc si merg normal - problema e tacuta, se vede abia in pvesr status."
|
|
Write-Warn "Verifica: ssh root@<nod> `"ip -br a; dmesg | tail -20`" (dongle USB enumerat tarziu?)"
|
|
Confirm-Step "Continui cu pornirea guest-urilor, cu replicarea jos?"
|
|
} elseif ($nfsBad.Count -eq 0) {
|
|
Write-Ok "insula completa - replicarea si NFS-ul au calea libera"
|
|
} else {
|
|
Write-Warn "insula e sus, dar NFS-ul nu o foloseste - vezi mai sus"
|
|
}
|
|
}
|
|
|
|
# ------------------------------------------------- pas 4: ce trebuie pornit
|
|
|
|
Write-Step "Ce se porneste"
|
|
|
|
$toStart = @()
|
|
|
|
if ((-not $All) -and (Test-Path $StateFile)) {
|
|
foreach ($line in (Get-Content $StateFile)) {
|
|
if ($line -match '^\s*#' -or $line -match '^\s*$') { continue }
|
|
$f = ($line -split '\s+') | Where-Object { $_ -ne '' }
|
|
if ($f.Count -ge 5) {
|
|
$toStart += [pscustomobject]@{ Type=$f[0]; Node=$f[1]; Id=[int]$f[2]; Status=$f[3]; Ha=$f[4] }
|
|
}
|
|
}
|
|
Write-Ok "citit din $StateFile ($($toStart.Count) guest-uri)"
|
|
} else {
|
|
if (-not $All) { Write-Warn "Nu exista $StateFile - folosesc lista implicita." }
|
|
|
|
$haSids = @()
|
|
foreach ($line in (Invoke-Node -Node 'pvemini' -Command 'ha-manager config')) {
|
|
if ($line -match '^(ct|vm):(\d+)') { $haSids += "$($Matches[1]):$($Matches[2])" }
|
|
}
|
|
|
|
$allGuests = @()
|
|
foreach ($node in $NodeIp.Keys) {
|
|
foreach ($g in (Get-NodeGuests -Node $node)) {
|
|
$sid = "$($g.Type):$($g.Id)"
|
|
$g | Add-Member -NotePropertyName Ha -NotePropertyValue $(if ($haSids -contains $sid) { 'ha' } else { 'noha' })
|
|
$allGuests += $g
|
|
}
|
|
}
|
|
|
|
# Fara fisier de stare pornim doar ce e explicit in lista de ordine SI nu e
|
|
# in NeverAutostart. Altfel am porni DR-ul si template-urile, care erau
|
|
# oprite intentionat.
|
|
foreach ($id in $GuestStartOrder) {
|
|
if ($NeverAutostart -contains $id) {
|
|
Write-Warn "$id sarit (NeverAutostart) - porneste-l manual daca chiar il vrei"
|
|
continue
|
|
}
|
|
$g = $allGuests | Where-Object { $_.Id -eq $id } | Select-Object -First 1
|
|
if ($g) { $toStart += $g }
|
|
}
|
|
}
|
|
|
|
if ($toStart.Count -eq 0) { Stop-WithError "Nimic de pornit." }
|
|
|
|
foreach ($g in $toStart) {
|
|
" {0,-3} {1,-9} {2,-4} {3}" -f $g.Type, $g.Node, $g.Id, $g.Ha | Write-Host
|
|
}
|
|
|
|
Confirm-Step "Se pornesc guest-urile de mai sus, Oracle primul."
|
|
|
|
# ------------------------------------------------- pas 5: pornire guest-uri
|
|
|
|
Write-Step "Pornire guest-uri"
|
|
|
|
$failed = @()
|
|
|
|
function Start-Guest {
|
|
param([pscustomobject]$Guest)
|
|
|
|
$sid = "$($Guest.Type):$($Guest.Id)"
|
|
$cmd = if ($Guest.Type -eq 'ct') { 'pct' } else { 'qm' }
|
|
|
|
if ((Get-GuestStatus -Node $Guest.Node -Type $Guest.Type -Id $Guest.Id) -eq 'running') {
|
|
Write-Ok "$sid deja pornit"
|
|
return $true
|
|
}
|
|
|
|
if ($Guest.Ha -eq 'ha') {
|
|
# Pentru resursele HA, --state started repune resursa sub controlul CRM.
|
|
Invoke-NodeChange -Node $Guest.Node -Command "ha-manager set $sid --state started" | Out-Null
|
|
} else {
|
|
Invoke-NodeChange -Node $Guest.Node -Command "$cmd start $($Guest.Id)" | Out-Null
|
|
}
|
|
|
|
if ($DryRun) { return $true }
|
|
|
|
$waited = 0
|
|
while ($waited -lt $GuestWait) {
|
|
if ((Get-GuestStatus -Node $Guest.Node -Type $Guest.Type -Id $Guest.Id) -eq 'running') {
|
|
Write-Ok "$sid pornit (${waited}s)"
|
|
return $true
|
|
}
|
|
Start-Sleep -Seconds 5
|
|
$waited += 5
|
|
}
|
|
Write-Warn "$sid nu a pornit in ${GuestWait}s - verifica manual"
|
|
return $false
|
|
}
|
|
|
|
function Wait-Oracle {
|
|
param([string]$Node)
|
|
Write-Log "astept ca instanta Oracle sa accepte interogari..."
|
|
if ($DryRun) { return }
|
|
|
|
# 'bash -lc', NU 'bash -c': fara shell de login nu exista sqlplus in PATH, iar
|
|
# asteptarea ar expira dupa $OracleWait secunde chiar cu baza perfect deschisa.
|
|
#
|
|
# 'timeout 30' e OBLIGATORIU, nu o precautie. Pe 2026-08-29, la pornirea de la
|
|
# rece, sqlplus lansat catre o instanta inca in startup s-a blocat definitiv:
|
|
# docker exec a mostenit blocajul, ssh a asteptat, iar bucla de mai jos n-a
|
|
# apucat sa faca nici macar o iteratie. Scriptul a stat agatat 14 minute fara
|
|
# niciun mesaj, cu propriul prag de asteptare nefolosit. Sonda trebuie sa
|
|
# raspunda mereu, fie si cu esec.
|
|
$sql = @"
|
|
timeout 30 pct exec $OracleCt -- docker exec $OracleDocker bash -lc "printf 'set pagesize 0 feedback off\nselect status from v\`$instance;\nexit\n' | sqlplus -s / as sysdba" 2>&1
|
|
"@
|
|
$waited = 0
|
|
while ($waited -lt $OracleWait) {
|
|
$out = Invoke-NodeScript -Node $Node -Script $sql
|
|
# 'OPEN' e raspunsul instantei deschise; orice altceva (ORA-, container
|
|
# inca in pornire) inseamna ca mai asteptam.
|
|
if (($out -join '') -match 'OPEN') {
|
|
Write-Host "`r `r" -NoNewline
|
|
Write-Ok "Oracle deschis si interogabil (${waited}s)"
|
|
return
|
|
}
|
|
Write-Host "`r Oracle inca nu raspunde (${waited}s)... " -NoNewline -ForegroundColor DarkGray
|
|
Start-Sleep -Seconds 15
|
|
$waited += 15
|
|
}
|
|
Write-Host "`r `r" -NoNewline
|
|
Write-Warn "Oracle nu a raspuns in ${OracleWait}s. Continui, dar VERIFICA MANUAL."
|
|
Write-Warn " ssh root@$($NodeIp[$Node]) `"pct exec $OracleCt -- docker logs --tail 50 $OracleDocker`""
|
|
}
|
|
|
|
foreach ($id in $GuestStartOrder) {
|
|
$g = $toStart | Where-Object { $_.Id -eq $id } | Select-Object -First 1
|
|
if (-not $g) { continue }
|
|
|
|
Write-Log "pornesc $($g.Type):$($g.Id) pe $($g.Node) ($($g.Ha))"
|
|
if (-not (Start-Guest -Guest $g)) { $failed += "$($g.Type):$($g.Id)" }
|
|
|
|
# Oracle e blocant: nimic care depinde de el nu porneste pana nu e deschis.
|
|
if ($g.Id -eq $OracleCt) { Wait-Oracle -Node $g.Node }
|
|
}
|
|
|
|
# orice a mai ramas in lista si nu era in ordinea definita
|
|
foreach ($g in $toStart) {
|
|
if ($GuestStartOrder -contains $g.Id) { continue }
|
|
Write-Warn "guest neplanificat: $($g.Type):$($g.Id) pe $($g.Node) - il pornesc"
|
|
if (-not (Start-Guest -Guest $g)) { $failed += "$($g.Type):$($g.Id)" }
|
|
}
|
|
|
|
# ------------------------------------------------ pas 6: restaurare cron-uri
|
|
|
|
Write-Step "Restaurare cron-uri de monitorizare"
|
|
|
|
foreach ($node in $NodeIp.Keys) {
|
|
$exists = Invoke-Node -Node $node -Command "test -f $CronBackup; echo `$?"
|
|
if (($exists -join '') -notmatch '^0') {
|
|
Write-Warn "$node - nu exista $CronBackup, sar peste"
|
|
continue
|
|
}
|
|
Invoke-NodeChange -Node $node -Command "crontab $CronBackup" | Out-Null
|
|
if (-not $DryRun) {
|
|
$left = Invoke-Node -Node $node -Command "crontab -l | grep -c '^#MENTENANTA' || true"
|
|
if (($left -join '').Trim() -eq '0') {
|
|
Write-Ok "$node - cron-uri restaurate"
|
|
} else {
|
|
Write-Warn "$node - au ramas $left linii #MENTENANTA, verifica manual"
|
|
}
|
|
}
|
|
}
|
|
|
|
# ------------------------------------------------------- pas 7: verificare
|
|
|
|
Write-Step "Verificare finala"
|
|
|
|
if ($DryRun) {
|
|
Write-Warn "dry-run - sar peste verificare"
|
|
} else {
|
|
Invoke-Node -Node 'pvemini' -Command 'ha-manager status' | Write-Host
|
|
Write-Host ""
|
|
$rep = Invoke-Node -Node 'pvemini' -Command 'pvesr status'
|
|
$rep | Write-Host
|
|
Write-Host ""
|
|
|
|
$badRep = @($rep | Select-Object -Skip 1 | Where-Object { $_ -notmatch 'OK\s*$' -and $_.Trim() -ne '' })
|
|
if ($badRep.Count -eq 0) {
|
|
Write-Ok "replicare: toate job-urile OK"
|
|
} else {
|
|
Write-Warn "job-uri de replicare cu probleme:"
|
|
$badRep | Write-Host
|
|
}
|
|
}
|
|
|
|
Write-Step "Cluster pornit"
|
|
|
|
if ($failed.Count -gt 0) {
|
|
Write-Warn "Guest-uri care NU au pornit: $($failed -join ', ')"
|
|
Write-Warn "Verifica-le manual inainte de a considera repornirea terminata."
|
|
} else {
|
|
Write-Ok "toate guest-urile au pornit"
|
|
}
|
|
|
|
Write-Host ""
|
|
Write-Host " De verificat manual:"
|
|
Write-Host " - datacenter.cfg are inca AMBELE linii: 'ha: shutdown_policy=freeze'"
|
|
Write-Host " si 'migration: network=10.10.10.0/24,type=insecure'"
|
|
Write-Host " - daca lipsesc date recente, compara cu ultima replicare din pvesr status"
|
|
Write-Host " - daca a fost prima repornire dupa 2026-08-29: noteaza in"
|
|
Write-Host " docs/switch-2.5g-dedicat-noduri.md daca insula a urcat singura la boot"
|
|
Write-Host ""
|