From d7b4007af8975fa9699dd87f5013fa0c6ef4b747 Mon Sep 17 00:00:00 2001 From: Marius Date: Sat, 29 Aug 2026 21:13:34 +0300 Subject: [PATCH] feat(cluster): corosync ring1 pe insula, test de repornire cu WoL, fixuri in scripturi 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) Claude-Session: https://claude.ai/code/session_01RJFQZcA7uerXRaL1NrsXpS --- .../usb-lan-hotplug/99-usb-lan-hotplug.rules | 25 ++- .../usb-lan-hotplug/usb-lan-hotplug@.service | 9 +- .../docs/oprire-planificata-cluster.md | 159 +++++++++++++---- .../docs/switch-2.5g-dedicat-noduri.md | 102 +++++++++-- proxmox/cluster/scripts/cluster-shutdown.ps1 | 70 ++++++-- proxmox/cluster/scripts/cluster-shutdown.sh | 45 ++++- proxmox/cluster/scripts/cluster-startup.ps1 | 152 +++++++++++++++- proxmox/cluster/scripts/cluster-startup.sh | 128 +++++++++++++- .../scripts/deploy-usb-lan-hotplug.ps1 | 60 +++++-- proxmox/cluster/scripts/wake-cluster.ps1 | 164 ++++++++++++++++++ 10 files changed, 801 insertions(+), 113 deletions(-) create mode 100644 proxmox/cluster/scripts/wake-cluster.ps1 diff --git a/proxmox/cluster/config/usb-lan-hotplug/99-usb-lan-hotplug.rules b/proxmox/cluster/config/usb-lan-hotplug/99-usb-lan-hotplug.rules index e18eca6..047c9b5 100644 --- a/proxmox/cluster/config/usb-lan-hotplug/99-usb-lan-hotplug.rules +++ b/proxmox/cluster/config/usb-lan-hotplug/99-usb-lan-hotplug.rules @@ -1,17 +1,24 @@ # Reatasarea adaptoarelor USB LAN dupa un reset pe magistrala USB. # -# De ce exista: adaptoarele Realtek RTL8156 (driver r8152) folosite ca placa de -# retea principala pe pve1 si pveelite se reseteaza singure pe xHCI. Kernelul -# sterge interfata si o recreeaza, dar nimeni NU o readauga in vmbr0 si nu o -# ridica - interfata e declarata "inet manual", fara "auto" si fara -# "allow-hotplug", deci se ridica doar ca efect secundar la boot. +# De ce exista: adaptoarele Realtek RTL8156 (driver r8152) de pe pve1 si pveelite +# se reseteaza singure pe xHCI. Kernelul sterge interfata si o recreeaza, dar +# nimeni NU o reconfigureaza - se ridica doar ca efect secundar la boot. # -# Rezultatul: nodul merge perfect si e invizibil in retea, pe termen nelimitat. -# Pe pveelite asta a insemnat 16 ore de tacere in noaptea de 27 spre 28 august -# 2026 - vezi ../../incidents/2026-08-27-pveelite-down.md +# Cand a fost scrisa regula, dongle-ul era portul puntii vmbr0, adica placa de +# retea principala: un reset lasa nodul perfect functional si invizibil in retea, +# pe termen nelimitat. Pe pveelite asta a insemnat 16 ore de tacere in noaptea de +# 27 spre 28 august 2026 - vezi ../../incidents/2026-08-27-pveelite-down.md +# +# Din 2026-08-29 vmbr0 e pe interfata onboard, iar dongle-ul tine doar IP-ul +# retelei dedicate de cluster (10.10.10.200 / 10.10.10.202, strofa "auto ... +# inet static"). Miza s-a schimbat: un reset nu mai ia nodul offline, ci opreste +# tacut replicarea ZFS si NFS-ul backup-pvemini. +# Vezi ../../docs/switch-2.5g-dedicat-noduri.md # # Regula prinde momentul in care interfata reapare si reaplica configuratia. -# Se declanseaza si la boot; e inofensiv, ifreload -a e idempotent. +# Se declanseaza si la boot - inofensiv (ifreload -a e idempotent) si chiar util: +# acopera cazul in care USB-ul se enumereaza dupa networking.service, singurul +# risc ramas pentru reteaua de insula. ACTION=="add", SUBSYSTEM=="net", ENV{ID_NET_DRIVER}=="r8152", RUN+="/usr/bin/systemctl --no-block start usb-lan-hotplug@$env{INTERFACE}.service" diff --git a/proxmox/cluster/config/usb-lan-hotplug/usb-lan-hotplug@.service b/proxmox/cluster/config/usb-lan-hotplug/usb-lan-hotplug@.service index 6298706..414c55e 100644 --- a/proxmox/cluster/config/usb-lan-hotplug/usb-lan-hotplug@.service +++ b/proxmox/cluster/config/usb-lan-hotplug/usb-lan-hotplug@.service @@ -1,5 +1,5 @@ [Unit] -Description=Reataseaza adaptorul USB LAN %I in punte dupa un reset USB +Description=Reaplica configuratia de retea pentru adaptorul USB LAN %I dupa un reset USB Documentation=file:///etc/udev/rules.d/70-usb-lan-hotplug.rules After=network.target @@ -11,9 +11,10 @@ Type=oneshot # inainte ca astea sa se aseze prinde interfata intr-o stare intermediara. ExecStartPre=/bin/sleep 3 -# ifreload -a, nu ifup: interfata e "inet manual" si e port de punte, iar doar -# ifreload reface legatura cu vmbr0. E si comanda verificata in teren - exact ea -# a readus pveelite in retea pe 28.08 la 09:49, de la consola fizica. +# ifreload -a, nu ifup: e comanda verificata in teren - exact ea a readus pveelite +# in retea pe 28.08 la 09:49, de la consola fizica. Atunci interfata era port de +# punte si doar ifreload refacea legatura cu vmbr0; din 2026-08-29 are strofa +# proprie cu IP static pe reteaua de cluster, pe care ifreload o reaplica la fel. # Aplica numai diferentele fata de /etc/network/interfaces, deci nu atinge # interfetele tap/veth ale guest-urilor. ExecStart=/usr/sbin/ifreload -a diff --git a/proxmox/cluster/docs/oprire-planificata-cluster.md b/proxmox/cluster/docs/oprire-planificata-cluster.md index 92f640f..34fb41d 100644 --- a/proxmox/cluster/docs/oprire-planificata-cluster.md +++ b/proxmox/cluster/docs/oprire-planificata-cluster.md @@ -6,7 +6,15 @@ relocheze resursele** și **fără ca watchdog-ul să reseteze hard vreun nod**. Se aplică oricărei opriri planificate a întregului cluster: lucrări electrice, mutare rack, mentenanță UPS, intervenții la switch. -**Autor:** Claude Code · **Ultima verificare pe cluster live:** 2026-08-27 +**Autor:** Claude Code · **Ultima verificare pe cluster live:** 2026-08-29 + +> **Atenție, s-a schimbat ceva important pe 2026-08-29.** Clusterul are acum o rețea +> dedicată, `10.10.10.0/24` („insula"), pe un switch separat — vezi +> [switch 2.5G dedicat nodurilor](switch-2.5g-dedicat-noduri.md). De ea atârnă +> **toată replicarea ZFS** (`migration: network=10.10.10.0/24` în `datacenter.cfg`) +> și storage-ul NFS `backup-pvemini-nfs` (server `10.10.10.201`). Pe pve1 și +> pveelite IP-ul de insulă stă pe **dongle-ul USB**, deci la revenire trebuie +> verificat că a urcat — vezi [Repornirea, pasul 3](#repornirea). --- @@ -24,11 +32,18 @@ cd E:\proiecte\ROMFASTSQL\proxmox\cluster\scripts .\cluster-shutdown.ps1 -DryRun # arată exact ce ar face, nu atinge nimic .\cluster-shutdown.ps1 # oprire completă, cu confirmări -# ... după revenirea curentului, cu nodurile pornite fizic ... +# ... după revenirea curentului ... +.\wake-cluster.ps1 # trezește nodurile prin Wake-on-LAN .\cluster-startup.ps1 # repornire completă ``` +**Wake-on-LAN merge pe toate trei nodurile** — verificat pe 2026-08-29, trezire în ~15 secunde. +E posibil abia de la mutarea lui `vmbr0` pe interfețele onboard: dongle-urile USB nu suportă WoL +și pierd alimentarea la oprire. Dacă lucrarea e la rețeaua electrică și nodurile sunt oprite +de tine, `wake-cluster.ps1` te scutește de drumul până la ele. Stația de admin trebuie să fie +pe același segment L2 (`10.0.20.0/24`) — magic packet-ul e broadcast, nu trece prin router. + Cere doar clientul OpenSSH din Windows (`C:\Windows\System32\OpenSSH\ssh.exe`), deja prezent, și cheia SSH pentru root pe noduri. Testate pe PowerShell 5.1. @@ -57,13 +72,15 @@ necesar și cum se face manual din GUI, dacă preferi. ## TL;DR (procedura manuală) -1. **Datacenter → Options → HA Settings → Shutdown Policy = `Freeze`** ← fără asta, HA mută resursele +1. **Shutdown Policy = `Freeze`** ← fără asta, HA mută resursele. Din 2026-08-29 e deja setat permanent — doar verifică (Pas 0), nu adăuga o linie nouă în `datacenter.cfg` 2. Dezactivează cron-urile de alertă (`pvemini-down-alert`, `vm109-watchdog`, `pveelite-down-alert`, `oom-alert`) 3. Oprește guest-urile manual — **Oracle CT 108 primul**, cu `shutdown immediate` în DB 4. `ha-manager status` → toate serviciile trebuie să fie `stopped` 5. Oprește nodurile: **pveelite → pve1 → pvemini** -La revenire: pornești **toate trei nodurile aproape simultan** (altfel nu ai quorum). +La revenire: pornești **toate trei nodurile aproape simultan** (altfel nu ai quorum), +apoi verifici că insula `10.10.10.0/24` a urcat pe pve1 și pveelite **înainte** de a +porni guest-urile. --- @@ -71,8 +88,8 @@ La revenire: pornești **toate trei nodurile aproape simultan** (altfel nu ai qu ### Capcana 1 — politica implicită de shutdown este `conditional` -Pe clusterul `romfast` **nu există** `/etc/pve/datacenter.cfg`, deci PVE folosește -default-ul `shutdown_policy=conditional`: +Fără o linie `ha:` în `/etc/pve/datacenter.cfg`, PVE folosește default-ul +`shutdown_policy=conditional`: | Acțiune pe nod | Comportament HA cu `conditional` | |----------------|----------------------------------| @@ -84,6 +101,11 @@ VM 201 și restul resurselor HA pe pve1/pveelite — pe noduri care oricum urmea să fie și ele oprite, cu 16 GB RAM pe pveelite și riscul din [incidentul 2026-04-20](../incidents/2026-04-20-cluster-outage.md). +> **Din 2026-08-29 capcana asta e deja dezamorsată permanent.** Fișierul +> `/etc/pve/datacenter.cfg` **există** și conține `ha: shutdown_policy=freeze`. +> Pasul 0 de mai jos e, în mod normal, doar o verificare. Nu presupune că fișierul +> lipsește — mai conține și linia `migration:`, care nu trebuie pierdută. + ### Capcana 2 — watchdog-ul se auto-fence la pierderea quorumului Cu 3 noduri, quorum = 2. Dacă oprești două noduri și al treilea **încă are @@ -106,14 +128,37 @@ inofensivă. ## Pas 0 — Shutdown Policy = Freeze -**GUI:** `Datacenter → Options → HA Settings → Edit → Shutdown Policy: Freeze` - -**CLI (echivalent):** +De obicei **nu ai nimic de făcut aici** — politica e `freeze` din 2026-08-29. +Verifică întâi: ```bash -ssh root@10.0.20.201 "echo 'ha: shutdown_policy=freeze' >> /etc/pve/datacenter.cfg" +ssh root@10.0.20.201 "cat /etc/pve/datacenter.cfg" ``` +Așteptat (ambele linii): + +``` +ha: shutdown_policy=freeze +migration: network=10.10.10.0/24,type=insecure +``` + +Dacă `shutdown_policy` **nu** e `freeze`, **nu adăuga o linie nouă cu `>>`** — +fișierul are deja o linie `ha:`, iar a doua e o duplicare. Comanda corectă +înlocuiește linia existentă și lasă `migration:` neatinsă: + +```bash +ssh root@10.0.20.201 "cp /etc/pve/datacenter.cfg /root/datacenter.cfg.backup-mentenanta && \ + sed -i '/^ha:/d' /etc/pve/datacenter.cfg && \ + printf 'ha: shutdown_policy=freeze\n' >> /etc/pve/datacenter.cfg && \ + cat /etc/pve/datacenter.cfg" +``` + +**GUI (echivalent):** `Datacenter → Options → HA Settings → Edit → Shutdown Policy: Freeze` + +> **Nu șterge linia `migration:`.** Fără ea replicarea ZFS se întoarce tăcut pe +> rețeaua de producție — merge, dar pe altă cale decât cea proiectată, iar +> traficul de replicare ajunge peste producție. + `Freeze` = la oprirea nodului serviciile HA sunt înghețate, **nu relocate**; repornesc pe același nod când nodul revine. @@ -200,17 +245,18 @@ VM 201 (IIS) și CT 104 (flowise) folosesc baza din CT 108. | # | Nod | Guest | În HA | Cum | |---|-----|-------|-------|-----| | 1 | pvemini | VM 303 Win11-Adina | Nu | Shutdown | -| 2 | pvemini | VM 302 oracle-test | Nu | Shutdown | -| 3 | pvemini | VM 201 roacentral | Da | Shutdown (guest agent) | -| 4 | pve1 | CT 101 minecraft | Da | Shutdown | -| 5 | pve1 | CT 110 moltbot | Da | Shutdown | -| 6 | pvemini | CT 104 flowise | Da | Shutdown | -| 7 | pvemini | CT 106 gitea | Da | Shutdown | -| 8 | pvemini | CT 103 dokploy | Da | Shutdown | -| 9 | pvemini | CT 102 docker.romfast.ro | Nu | Shutdown | -| 10 | pvemini | CT 100 portainer | Da | Shutdown | -| 11 | pvemini | CT 171 claude-agent | Da | Shutdown | -| 12 | pvemini | **CT 108 central-oracle** | Da | `shutdown immediate` în DB, apoi Shutdown CT | +| 2 | pvemini | VM 304 Win11-Marius | Nu | Shutdown | +| 3 | pvemini | VM 302 oracle-test | Nu | Shutdown | +| 4 | pvemini | VM 201 roacentral | Da | Shutdown (guest agent) | +| 5 | pve1 | CT 101 minecraft | Da | Shutdown | +| 6 | pve1 | CT 110 moltbot | Da | Shutdown | +| 7 | pvemini | CT 104 flowise | Da | Shutdown | +| 8 | pvemini | CT 106 gitea | Da | Shutdown | +| 9 | pvemini | CT 103 dokploy | Da | Shutdown | +| 10 | pvemini | CT 102 docker.romfast.ro | Nu | Shutdown | +| 11 | pvemini | CT 100 portainer | Da | Shutdown | +| 12 | pvemini | CT 171 claude-agent | Da | Shutdown | +| 13 | pvemini | **CT 108 central-oracle** | Da | `shutdown immediate` în DB, apoi Shutdown CT | pveelite nu are nimic pornit în mod normal (CT 301 și VM 109 sunt `stopped`). @@ -275,9 +321,10 @@ Așteaptă ca nodul să nu mai răspundă la ping înainte de a-l opri pe următ ## Repornirea -1. **Pornește toate trei nodurile aproximativ simultan.** Un singur nod pornit - nu are quorum → `/etc/pve` rămâne read-only → nu poți porni niciun guest și - nu poți modifica nimic în GUI. +1. **Pornește toate trei nodurile aproximativ simultan** — fizic, sau cu + `.\wake-cluster.ps1` (Wake-on-LAN, verificat pe 2026-08-29). Un singur nod + pornit nu are quorum → `/etc/pve` rămâne read-only → nu poți porni niciun guest + și nu poți modifica nimic în GUI. 2. Așteaptă quorum complet: @@ -286,19 +333,56 @@ Așteaptă ca nodul să nu mai răspundă la ping înainte de a-l opri pe următ ssh root@10.0.20.201 "ha-manager status" ``` -3. **Pornește guest-urile în ordine inversă:** CT 108 Oracle întâi, apoi VM 201, +3. **Verifică rețeaua de cluster `10.10.10.0/24` — pasul cel mai ușor de uitat.** + + Pe pve1 și pveelite IP-ul de insulă stă pe dongle-ul USB, adus la boot de un + `auto enx...` din `/etc/network/interfaces`. Dacă nucleul enumerează adaptorul + USB **după** ce `networking.service` a terminat, interfața rămâne fără IP. + Nodul pornește perfect normal pe producție — dar **replicarea ZFS și storage-ul + NFS `backup-pvemini` tac**, fără nicio alertă. + + ```bash + ssh root@10.0.20.200 "ip -br a | grep -E 'vmbr0|enx'" # 10.10.10.200 pe dongle + ssh root@10.0.20.202 "ip -br a | grep -E 'vmbr0|enx'" # 10.10.10.202 pe dongle + ssh root@10.0.20.201 "ip -br a | grep -E 'vmbr0|enp87s0'" # 10.10.10.201 pe enp87s0 + ssh root@10.0.20.201 "ping -c1 -W2 10.10.10.200; ping -c1 -W2 10.10.10.202" + ssh root@10.0.20.200 "findmnt -no SOURCE /mnt/pve/backup-pvemini" # 10.10.10.201:/mnt/backup + ``` + + Dacă un IP lipsește, reparația e `ifreload -a` pe nodul respectiv: + + ```bash + ssh root@10.0.20.202 "PATH=/usr/sbin:/sbin:/usr/bin:/bin ifreload -a" + ``` + + pvemini are interfața de insulă onboard (Intel `enp87s0`) — el nu are problema asta. + + **Testat pe 2026-08-29, la o oprire completă reală: insula a urcat singură pe + toate trei nodurile.** Ipoteza enumerării târzii nu s-a materializat. Pe pve1 + s-a declanșat în plus și regula udev `usb-lan-hotplug@` la boot, care oricum ar + fi reparat-o. Verificarea rămâne totuși în procedură — un singur test reușit nu + exclude o cursă de sincronizare care depinde de temporizări. + + **Ce a găsit totuși testul:** o linie rămasă în `/etc/fstab` pe pve1 și pveelite + monta storage-ul NFS de pe `10.0.20.201` (producție) înainte ca `pvestatd` să + apuce, deci backup-ul mergea tăcut pe rețeaua greșită, cu `storage.cfg` arătând + corect. Liniile au fost comentate (backup: `/root/fstab.bak-2026-08-29`). + Dacă vezi vreodată NFS-ul montat de pe alt server decât `10.10.10.201`, acolo + să te uiți întâi. `cluster-startup` verifică asta automat. + +4. **Pornește guest-urile în ordine inversă:** CT 108 Oracle întâi, apoi VM 201, apoi restul. Pornirea unui guest HA îi repune state-ul pe `started`. -4. Verifică Oracle: +5. Verifică Oracle: ```bash ssh root@10.0.20.201 "pct exec 108 -- docker exec oracle-xe bash -lc \ \"printf 'select status from v\\\$instance;\nexit\n' | sqlplus -s / as sysdba\"" ``` -5. **Re-activează cron-urile** dezactivate la Pasul 1 și job-urile de backup. +6. **Re-activează cron-urile** dezactivate la Pasul 1 și job-urile de backup. -6. Verifică replicarea: +7. Verifică replicarea: ```bash ssh root@10.0.20.201 "pvesr status" @@ -306,7 +390,8 @@ Așteaptă ca nodul să nu mai răspundă la ping înainte de a-l opri pe următ Job-urile `*/15` (108, 171, 201) au ratat rulări; recuperează la primul tick. -7. Decide ce faci cu Shutdown Policy — vezi recomandarea de la Pasul 0. +8. Decide ce faci cu Shutdown Policy — vezi recomandarea de la Pasul 0. + În mod normal rămâne `freeze`, deci nu ai ce face. --- @@ -329,7 +414,7 @@ orchestrat care poate intra peste procedura de aici. --- -## Stare de referință a clusterului (2026-08-27) +## Stare de referință a clusterului (2026-08-29) Resurse HA configurate — 10 în total: @@ -346,16 +431,28 @@ Resurse HA configurate — 10 în total: | vm:109 | pveelite | ha-prefer-pveelite | oracle-dr-windows (`stopped`) | | vm:201 | pvemini | ha-prefer-pvemini | roacentral | -Guest-uri **în afara** HA: CT 102, CT 301, VM 302, VM 303, VM 310. +Guest-uri **în afara** HA: CT 102, CT 301, VM 302, VM 303, **VM 304**, VM 310. `nofailback` este `1` doar pe `ha-prefer-pveelite`; celelalte două grupuri au `nofailback 0`, deci resursele se întorc automat pe nodul preferat când acesta revine. +Rețea și storage de care depinde procedura, verificate pe 2026-08-29: + +| Ce | Valoare | +|---|---| +| `datacenter.cfg` | `ha: shutdown_policy=freeze` + `migration: network=10.10.10.0/24,type=insecure` | +| insulă pve1 / pvemini / pveelite | `10.10.10.200` (USB) / `10.10.10.201` (`enp87s0`) / `10.10.10.202` (USB) | +| NFS `backup-pvemini-nfs` | server `10.10.10.201`, montat pe pve1 și pveelite | +| corosync | **două linkuri**: `LINK ID 0` pe `10.0.20.20x`, `LINK ID 1` pe `10.10.10.20x`, `config_version: 17` | +| Wake-on-LAN | merge pe toate 3 (`ethtool ` → `Wake-on: g`); unealta: `../scripts/wake-cluster.ps1` | + --- ## Referințe +- [Switch 2.5G dedicat nodurilor — rețeaua `10.10.10.0/24`](switch-2.5g-dedicat-noduri.md) - [Strategia de failover și HA](../failover/README.md) - [Incident 2026-04-20 — cluster outage](../incidents/2026-04-20-cluster-outage.md) +- [Incident 2026-08-27 — pveelite fără rețea 16h (USB LAN reset)](../incidents/2026-08-27-pveelite-down.md) - [Cluster README — SSH, storage, noduri](../README.md) - [UPS](../ups/README.md) diff --git a/proxmox/cluster/docs/switch-2.5g-dedicat-noduri.md b/proxmox/cluster/docs/switch-2.5g-dedicat-noduri.md index f7573c6..ef7318e 100644 --- a/proxmox/cluster/docs/switch-2.5g-dedicat-noduri.md +++ b/proxmox/cluster/docs/switch-2.5g-dedicat-noduri.md @@ -4,8 +4,12 @@ **Autor:** Claude Code (mmarius28@gmail.com) **Status: TERMINAT pe 2026-08-29.** Rețeaua dedicată de cluster `10.10.10.0/24` e activă pe toate 3 nodurile, `vmbr0` a fost mutat pe interfețele onboard, uplink-ul dintre switch-uri a fost scos, -iar replicarea și backup-ul NFS trec verificat pe insulă. Singurele lucruri rămase sunt -opționale — vezi „Ce a rămas de făcut". +iar replicarea și backup-ul NFS trec verificat pe insulă. + +**Completat în seara aceleiași zile:** al doilea inel corosync (ring1) pe insulă, verificat prin +oprirea reală a lui ring0; testul de repornire completă a clusterului, cu Wake-on-LAN. Testul a +scos la iveală o linie rămasă în `/etc/fstab` care fixa tăcut backup-ul NFS pe rețeaua de +producție. Toate trei sunt descrise mai jos. Singurul lucru rămas e opțional (MTU 9000). --- @@ -278,13 +282,42 @@ fiecare pas: link 2500 Mbps, ping pe toată insula, **262 / 272 MB/s**, job real Opționale, în ordinea valorii: -1. **corosync ring1** pe `10.10.10.0/24` — vezi mai jos, cere fereastră dedicată. +1. ~~**corosync ring1** pe `10.10.10.0/24`~~ — **APLICAT pe 2026-08-29 seara**, vezi mai jos. 2. **MTU 9000** pe segmentul dedicat — de testat, nu se știe dacă switch-ul suportă. -3. **Test la reboot.** Configurația nu a fost validată printr-o repornire. Dongle-urile USB au - acum `auto` în loc să fie porturi de punte; dacă la boot USB-ul se enumeră târziu, interfața - de insulă poate rămâne neconfigurată. Chiar și atunci nodul pornește normal pe producție și - doar replicarea are de suferit — invers față de cum era înainte, când o problemă pe dongle - lua nodul offline complet. De verificat la următoarea repornire planificată. +3. ~~**Test la reboot.**~~ — **FĂCUT pe 2026-08-29 seara**, vezi mai jos. + +### Test la repornire — făcut pe 2026-08-29, ipoteza infirmată + +Clusterul a fost oprit complet (toate 3 nodurile) și pornit înapoi. **Insula a urcat singură pe +toate trei nodurile**, fără intervenție: enumerarea târzie a USB-ului nu s-a produs. Pe pve1 +s-a declanșat în plus și regula udev `usb-lan-hotplug@` la boot (20:27:33, terminată cu succes), +care oricum ar fi reparat situația; pe pveelite a fost suficientă strofa `auto`. + +**Wake-on-LAN merge pe toate trei nodurile** — nodurile oprite au fost trezite cu magic packet +de pe stația de admin, timp de răspuns ~15 secunde. E câștigul mutării lui `vmbr0` pe interfețele +onboard: dongle-urile USB nu suportă WoL. Unealta: `../scripts/wake-cluster.ps1` (MAC-urile de +producție sunt în ea). `Wake-on: g` era deja armat pe toate trei, iar BIOS-urile nu taie +alimentarea plăcii în S5. + +### Ce a găsit testul: o linie rămasă în `/etc/fstab` + +Repornirea a scos la iveală o deriva tăcută pe **pve1 și pveelite**: + +``` +10.0.20.201:/mnt/backup /mnt/pve/backup-pvemini nfs4 defaults,_netdev 0 0 +``` + +Montarea NFS a storage-ului PVE e treaba lui `pvestatd`, din `/etc/pve/storage.cfg` (unde +serverul e corect, `10.10.10.201`). Linia din fstab o lua însă înainte la boot, ocupa punctul +de montare, iar PVE o lăsa așa. Rezultatul: **tot traficul de backup trecea pe rețeaua de +producție**, cu `storage.cfg` arătând perfect corect. + +Remontarea manuală făcută pe 29 august a corectat simptomul, dar nu și cauza — starea bună a +supraviețuit exact până la prima repornire. Liniile au fost comentate pe ambele noduri +(backup: `/root/fstab.bak-2026-08-29`), montările refăcute și verificate: +`clientaddr=10.10.10.200` / `10.10.10.202`, scriibile. + +`cluster-startup` verifică acum asta automat la fiecare pornire. **De reținut:** `vmbr0` și-a schimbat MAC-ul pe toate 3 nodurile (a preluat MAC-ul noii interfețe). Dacă apar reguli pe router legate de MAC, acolo e cauza. @@ -293,7 +326,7 @@ interfețe). Dacă apar reguli pe router legate de MAC, acolo e cauza. ## Îmbunătățiri posibile, în ordinea impactului -### 1. Al doilea inel corosync (link1) pe switch-ul nou — cel mai valoros pas următor +### 1. Al doilea inel corosync (link1) pe switch-ul nou — APLICAT pe 2026-08-29 Cluster-ul are azi un singur inel corosync (`linknumber: 0`, peste `vmbr0`/IP-ul principal — vezi `/etc/pve/corosync.conf`). Când adaptorul USB al lui pveelite s-a resetat (aprilie și @@ -305,14 +338,49 @@ inclusiv pe pve1 dacă i s-ar întâmpla la fel. verificate. Ring0 stă acum pe interfețele onboard (mai robuste), iar ring1 pe insulă ar acoperi și cazul în care dongle-ul USB sau portul din switch cedează. +#### Cum s-a aplicat + +Fereastra: guest-urile oprite cu `cluster-shutdown.ps1 -NoNodes`, care lasă exact starea +necesară — toate resursele HA în `stopped`, watchdog-ul dezarmat, dar clusterul **pornit și +cvorat**, fiindcă `corosync.conf` stă pe pmxcfs și fără cvorum devine read-only. + ```bash -pvecm status # verifică starea curentă înainte de orice modificare -# adăugare link1 se face editând /etc/pve/corosync.conf, incrementând config_version, -# adăugând ring1_addr = 10.10.10.20x la fiecare nod + interface { linknumber: 1 } în totem +# backup pe FIECARE nod, si copia din /etc/pve, si cea locala pe care o citeste corosync +cp /etc/pve/corosync.conf /root/corosync.conf.bak-2026-08-29 +cp /etc/corosync/corosync.conf /root/corosync.local.bak-2026-08-29 + +# validare INAINTE de instalare - corosync stie sa-si testeze propriul fisier +COROSYNC_MAIN_CONFIG_FILE=/root/corosync.new.conf corosync -t # exit 0 = sintaxa buna + +cp /root/corosync.new.conf /etc/pve/corosync.conf.new +mv /etc/pve/corosync.conf.new /etc/pve/corosync.conf # pmxcfs propaga singur ``` -**Neaplicat** — schimbarea corosync.conf e sensibilă (poate rupe cvorumul dacă sintaxa greșește) -și merită o fereastră dedicată. +Modificările: `ring1_addr: 10.10.10.20x` la fiecare nod, un al doilea bloc +`interface { linknumber: 1 }` în `totem`, `config_version` 16 → 17. `link_mode` era deja +`passive`, potrivit pentru două linkuri. Propagarea pe toate 3 nodurile a durat sub 12 secunde. + +#### Verificat prin oprirea reală a lui ring0 + +Nu doar configurat — **testat**. Cu `eno1` scos administrativ pe pveelite (plasă de siguranță: +`ifreload -a` programat la 120s), corosync a arătat de pe pvemini: + +``` +LINK ID 0 nodeid 3: disconnected +LINK ID 1 nodeid 3: connected +Quorate: Yes, Total votes: 3 +``` + +Exact situația din 27 august, când pveelite a fost declarat mort 16 ore. Acum clusterul nici +nu clipește. Restaurarea s-a făcut prin **calea out-of-band de pe insulă** +(`ssh` de pe pvemini către `10.10.10.202`), care a funcționat cu nodul invizibil pe producție. + +#### Ce NU rezolvă ring1 — de reținut + +În timpul testului, **pveelite a apărut gri/indisponibil în GUI-ul Proxmox**, deși cvorumul era +intact. E corect: GUI-ul nu se uită la corosync, ci vorbește prin API pe `8006`, peste IP-ul de +management din `10.0.20.x`. Ring1 protejează **cvorumul, pmxcfs și HA** — nu accesul de +management. Pentru management out-of-band există SSH pe insulă, de pe un nod pe altul. ### 2, 3 și 4 — aplicate pe 2026-08-29 @@ -327,9 +395,9 @@ pe 1G nu costă nimic pe pve1/pveelite. ## Ce NU s-a schimbat -- `/etc/pve/corosync.conf` — **neatins.** Ring0 folosește în continuare IP-urile `10.0.20.20x`, - care acum stau pe interfețele onboard — deci corosync a câștigat robustețe fără nicio - modificare de configurație. +- `/etc/pve/corosync.conf` — neatins la mutarea rețelei; **modificat seara aceleiași zile**, + pentru ring1 (`config_version: 17`) — vezi mai sus. Ring0 folosește în continuare IP-urile + `10.0.20.20x`, care acum stau pe interfețele onboard. - Cablurile nodurilor — **niciunul mutat de scripturi.** Toate mutările fizice au fost făcute manual de utilizator (routerul pe switch 2, dongle-ul lui pveelite pe p4, al lui pve1 pe p3). - Cage-urile SFP+ ale plăcii X710 de pe pvemini — rămân goale, placa nefolosită. La verificări, diff --git a/proxmox/cluster/scripts/cluster-shutdown.ps1 b/proxmox/cluster/scripts/cluster-shutdown.ps1 index 3e23478..3a22c6e 100644 --- a/proxmox/cluster/scripts/cluster-shutdown.ps1 +++ b/proxmox/cluster/scripts/cluster-shutdown.ps1 @@ -53,6 +53,7 @@ $NodeShutdownOrder = @('pveelite', 'pve1', 'pvemini') # Orice guest pornit care nu apare aici e oprit la final, intr-o maturare. $GuestShutdownOrder = @( 303, # Win11-Adina - desktop, fara dependente + 304, # Win11-Marius - desktop, fara dependente 302, # oracle-test-302 - VM de test 310, # Win11-Template - template 201, # roacentral - IIS reverse proxy, consumator Oracle @@ -110,6 +111,21 @@ function Invoke-Node { return $out } +# 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" +} + # Ca Invoke-Node, dar respecta -DryRun (pentru comenzi care modifica starea). function Invoke-NodeChange { param([string]$Node, [string]$Command) @@ -226,10 +242,22 @@ if ($running.Count -eq 0) { # reporneasca VM-uri oprite intentionat (302, 310, 301...). # Format identic cu varianta bash, ca cele doua sa fie interschimbabile. if (-not $DryRun) { - $lines = @("# stare cluster salvata la $(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')") - foreach ($g in $running) { $lines += "$($g.Type) $($g.Node) $($g.Id) $($g.Status) $($g.Ha)" } - Set-Content -Path $StateFile -Value $lines -Encoding utf8 - Write-Ok "stare salvata in $StateFile" + # Nu rescrie peste o stare existenta cu un inventar gol. Cazul real: scriptul + # e rulat a doua oara (dupa -NoNodes, sau dupa o eroare), cand guest-urile sunt + # deja oprite - inventarul e gol si am pierde exact lista de repornit. + $existing = @() + if (Test-Path $StateFile) { + $existing = @(Get-Content $StateFile | Where-Object { $_ -notmatch '^\s*#' -and $_.Trim() }) + } + if ($running.Count -eq 0 -and $existing.Count -gt 0) { + Write-Warn "Niciun guest pornit, dar $StateFile contine deja $($existing.Count) intrari - il PASTREZ." + Write-Warn "Altfel cluster-startup n-ar mai sti ce sa porneasca." + } else { + $lines = @("# stare cluster salvata la $(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')") + foreach ($g in $running) { $lines += "$($g.Type) $($g.Node) $($g.Id) $($g.Status) $($g.Ha)" } + Set-Content -Path $StateFile -Value $lines -Encoding utf8 + Write-Ok "stare salvata in $StateFile" + } } Confirm-Step "Se opresc $($running.Count) guest-uri, apoi nodurile: $($NodeShutdownOrder -join ', ')." @@ -246,6 +274,9 @@ if ($currentPolicy -eq 'freeze') { Write-Ok "deja pe freeze" } else { if ($null -ne $currentPolicy -or ($dcCfg | Measure-Object).Count -gt 0) { + # sed sterge DOAR linia 'ha:', nu rescrie fisierul: din 2026-08-29 acolo + # sta si 'migration: network=10.10.10.0/24,type=insecure', fara de care + # replicarea se intoarce tacut pe reteaua de productie. Invoke-NodeChange -Node 'pvemini' -Command 'cp /etc/pve/datacenter.cfg /root/datacenter.cfg.backup-mentenanta' | Out-Null Invoke-NodeChange -Node 'pvemini' -Command "sed -i '/^ha:/d' /etc/pve/datacenter.cfg; printf 'ha: shutdown_policy=freeze\n' >> /etc/pve/datacenter.cfg" | Out-Null } else { @@ -262,7 +293,10 @@ Write-Step "Dezactivare cron-uri de alerta" $sedCron = "crontab -l | sed -E '/^[^#]/ s%^(.*($CronPattern)\.sh.*)`$%#MENTENANTA \1%' | crontab -" foreach ($node in $NodeIp.Keys) { - Invoke-NodeChange -Node $node -Command "crontab -l > $CronBackup 2>/dev/null || true" | Out-Null + # Backup DOAR daca crontab-ul curent nu e deja comentat. Altfel o a doua rulare + # a scriptului (dupa o eroare, de exemplu) ar salva peste backup versiunea deja + # comentata, iar cluster-startup ar "restaura" monitorizarea oprita - tacut. + Invoke-NodeChange -Node $node -Command "if crontab -l 2>/dev/null | grep -q '^#MENTENANTA'; then echo 'backup pastrat'; else crontab -l > $CronBackup 2>/dev/null || true; echo 'backup nou'; fi" | Out-Null Invoke-NodeChange -Node $node -Command $sedCron | Out-Null if (-not $DryRun) { $n = Invoke-Node -Node $node -Command "crontab -l | grep -c '^#MENTENANTA' || true" @@ -285,13 +319,27 @@ if (-not $oracleGuest) { if ($DryRun) { Write-Host " [dry-run] shutdown immediate in $OracleDocker" -ForegroundColor DarkGray } else { - $sqlCmd = 'pct exec ' + $OracleCt + ' -- docker exec ' + $OracleDocker + ' bash -c "printf ''shutdown immediate\nexit\n'' | sqlplus -s / as sysdba"' - $out = Invoke-Node -Node $oracleGuest.Node -Command $sqlCmd - if ($LASTEXITCODE -ne 0) { - Write-Warn "Shutdown-ul bazei a raportat eroare. Verifica manual inainte de a continua!" - $out | Select-Object -Last 5 | Write-Host + # 'bash -lc', NU 'bash -c': fara shell de login, PATH-ul Oracle nu e incarcat + # si comanda esueaza cu "sqlplus: command not found" - adica baza ramane + # DESCHISA, iar containerul ar fi oprit peste ea. + # '2>&1' se face pe nod, nu in PowerShell: altfel stderr-ul lui ssh devine + # ErrorRecord in PS 5.1 si arunca exceptie in loc sa fie tratat aici. + # 'timeout 180': un 'shutdown immediate' poate dura, dar nu la infinit. Fara + # el, un sqlplus blocat ar tine scriptul agatat fara niciun mesaj (patit pe + # 2026-08-29, la sonda echivalenta din cluster-startup). + $sqlScript = @" +timeout 180 pct exec $OracleCt -- docker exec $OracleDocker bash -lc "printf 'shutdown immediate\nexit\n' | sqlplus -s / as sysdba" 2>&1 +"@ + $out = Invoke-NodeScript -Node $oracleGuest.Node -Script $sqlScript + + # Codul de iesire nu e o dovada: sqlplus intoarce 0 si cand n-a inchis nimic. + # Singura confirmare reala e mesajul instantei. + if (($out -join "`n") -match 'ORACLE instance shut down') { + Write-Ok "instanta Oracle oprita curat" } else { - Write-Ok "instanta Oracle oprita" + Write-Warn "Baza NU a confirmat 'ORACLE instance shut down'. Ultimele linii:" + $out | Select-Object -Last 8 | Write-Host + Confirm-Step "Oprirea CT $OracleCt peste o baza posibil deschisa. Continui?" } } } diff --git a/proxmox/cluster/scripts/cluster-shutdown.sh b/proxmox/cluster/scripts/cluster-shutdown.sh index e62ec96..7f0b6d4 100755 --- a/proxmox/cluster/scripts/cluster-shutdown.sh +++ b/proxmox/cluster/scripts/cluster-shutdown.sh @@ -34,6 +34,7 @@ NODE_SHUTDOWN_ORDER=(pveelite pve1 pvemini) # Orice guest pornit care nu apare aici e oprit la final, într-o măturare. GUEST_SHUTDOWN_ORDER=( 303 # Win11-Adina — desktop, fără dependențe + 304 # Win11-Marius — desktop, fără dependențe 302 # oracle-test-302 — VM de test 310 # Win11-Template — template 201 # roacentral — IIS reverse proxy, consumator Oracle @@ -202,9 +203,19 @@ fi # Salvează starea LOCAL — cluster-startup.sh pornește exact ce era pornit, # ca să nu repornească VM-uri oprite intenționat (302, 310, 301...). if (( ! DRY_RUN )); then - { echo "# stare cluster salvată la $(date '+%Y-%m-%d %H:%M:%S')" - printf '%s\n' "${RUNNING:-}"; } > "$STATE_FILE" - ok "stare salvată în $STATE_FILE" + # Nu rescrie peste o stare existentă cu un inventar gol. Cazul real: scriptul + # e rulat a doua oară (după --no-nodes, sau după o eroare), când guest-urile + # sunt deja oprite — inventarul e gol și am pierde exact lista de repornit. + EXISTING=0 + [[ -f "$STATE_FILE" ]] && EXISTING=$(grep -cv -e '^[[:space:]]*#' -e '^[[:space:]]*$' "$STATE_FILE" || true) + if [[ -z "$RUNNING" ]] && (( EXISTING > 0 )); then + warn "Niciun guest pornit, dar $STATE_FILE conține deja $EXISTING intrări — îl PĂSTREZ." + warn "Altfel cluster-startup.sh n-ar mai ști ce să pornească." + else + { echo "# stare cluster salvată la $(date '+%Y-%m-%d %H:%M:%S')" + printf '%s\n' "${RUNNING:-}"; } > "$STATE_FILE" + ok "stare salvată în $STATE_FILE" + fi fi confirm "Se opresc $RUNNING_COUNT guest-uri, apoi nodurile: ${NODE_SHUTDOWN_ORDER[*]}." @@ -218,6 +229,9 @@ if [[ "$CURRENT_POLICY" == "freeze" ]]; then ok "deja pe freeze" else if nssh pvemini "test -f /etc/pve/datacenter.cfg"; then + # sed șterge DOAR linia 'ha:', nu rescrie fișierul: din 2026-08-29 acolo + # stă și 'migration: network=10.10.10.0/24,type=insecure', fără de care + # replicarea se întoarce tăcut pe rețeaua de producție. run pvemini "cp /etc/pve/datacenter.cfg /root/datacenter.cfg.backup-mentenanta" run pvemini "sed -i '/^ha:/d' /etc/pve/datacenter.cfg; printf 'ha: shutdown_policy=freeze\n' >> /etc/pve/datacenter.cfg" else @@ -231,7 +245,10 @@ fi step "Dezactivare cron-uri de alertă" for node in "${!NODE_IP[@]}"; do - run "$node" "crontab -l > $CRON_BACKUP 2>/dev/null || true" + # Backup DOAR dacă crontab-ul curent nu e deja comentat. Altfel o a doua rulare + # a scriptului (după o eroare, de exemplu) ar salva peste backup versiunea deja + # comentată, iar cluster-startup.sh ar „restaura" monitorizarea oprită — tăcut. + run "$node" "if crontab -l 2>/dev/null | grep -q '^#MENTENANTA'; then echo 'backup păstrat'; else crontab -l > $CRON_BACKUP 2>/dev/null || true; fi" run "$node" "crontab -l | sed -E '/^[^#]/ s%^(.*($CRON_PATTERN)\.sh.*)\$%#MENTENANTA \1%' | crontab -" if (( ! DRY_RUN )); then n=$(nssh "$node" "crontab -l | grep -c '^#MENTENANTA' || true") @@ -253,9 +270,23 @@ else if (( DRY_RUN )); then printf '%s [dry-run] shutdown immediate în %s%s\n' "$c_dim" "$ORACLE_DOCKER" "$c_off" else - nssh "$ORACLE_NODE" "pct exec $ORACLE_CT -- docker exec $ORACLE_DOCKER bash -c \"printf 'shutdown immediate\nexit\n' | sqlplus -s / as sysdba\"" \ - || warn "Shutdown-ul bazei a raportat eroare. Verifică manual înainte de a continua!" - ok "instanța Oracle oprită" + # `bash -lc`, NU `bash -c`: fără shell de login, PATH-ul Oracle nu e încărcat + # și comanda eșuează cu „sqlplus: command not found" — adică baza rămâne + # DESCHISĂ, iar containerul ar fi oprit peste ea. + # `timeout 180`: un „shutdown immediate" poate dura, dar nu la infinit. Fără + # el, un sqlplus blocat ar ține scriptul agățat fără niciun mesaj (pățit pe + # 2026-08-29, la sonda echivalentă din cluster-startup.sh). + SQL_OUT=$(nssh "$ORACLE_NODE" "timeout 180 pct exec $ORACLE_CT -- docker exec $ORACLE_DOCKER bash -lc \"printf 'shutdown immediate\nexit\n' | sqlplus -s / as sysdba\" 2>&1" || true) + + # Codul de ieșire nu e o dovadă: sqlplus întoarce 0 și când n-a închis nimic. + # Singura confirmare reală e mesajul instanței. + if grep -q 'ORACLE instance shut down' <<<"$SQL_OUT"; then + ok "instanța Oracle oprită curat" + else + warn "Baza NU a confirmat „ORACLE instance shut down\". Ultimele linii:" + tail -8 <<<"$SQL_OUT" + confirm "Oprirea CT $ORACLE_CT peste o bază posibil deschisă. Continui?" + fi fi fi diff --git a/proxmox/cluster/scripts/cluster-startup.ps1 b/proxmox/cluster/scripts/cluster-startup.ps1 index 43e7c5b..d8b5525 100644 --- a/proxmox/cluster/scripts/cluster-startup.ps1 +++ b/proxmox/cluster/scripts/cluster-startup.ps1 @@ -61,6 +61,7 @@ $GuestStartOrder = @( 101, # minecraft 110, # moltbot 303, # Win11-Adina + 304, # Win11-Marius 302, # oracle-test-302 109, # oracle-dr-windows 301, # docker-portainer-template @@ -74,9 +75,26 @@ $GuestStartOrder = @( # 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' -$OracleWait = 600 # secunde asteptate pana baza raspunde la interogari +# 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 @@ -119,6 +137,21 @@ function Invoke-NodeChange { 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 } @@ -209,7 +242,92 @@ Write-Ok "quorum OK, $votes/3 voturi" Invoke-Node -Node 'pvemini' -Command 'ha-manager status' | Select-Object -First 4 | Write-Host -# ------------------------------------------------- pas 3: ce trebuie pornit +# ------------------------------------ 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@ `"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" @@ -262,7 +380,7 @@ foreach ($g in $toStart) { Confirm-Step "Se pornesc guest-urile de mai sus, Oracle primul." -# ------------------------------------------------- pas 4: pornire guest-uri +# ------------------------------------------------- pas 5: pornire guest-uri Write-Step "Pornire guest-uri" @@ -306,11 +424,24 @@ function Wait-Oracle { Write-Log "astept ca instanta Oracle sa accepte interogari..." if ($DryRun) { return } - $sql = 'pct exec ' + $OracleCt + ' -- docker exec ' + $OracleDocker + ' bash -c "printf ''set pagesize 0 feedback off\nselect global_name from global_name;\nexit\n'' | sqlplus -s / as sysdba"' + # '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-Node -Node $Node -Command $sql - if ($LASTEXITCODE -eq 0 -and (($out -join '') -match '[A-Z]')) { + $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 @@ -342,7 +473,7 @@ foreach ($g in $toStart) { if (-not (Start-Guest -Guest $g)) { $failed += "$($g.Type):$($g.Id)" } } -# ------------------------------------------------ pas 5: restaurare cron-uri +# ------------------------------------------------ pas 6: restaurare cron-uri Write-Step "Restaurare cron-uri de monitorizare" @@ -363,7 +494,7 @@ foreach ($node in $NodeIp.Keys) { } } -# ------------------------------------------------------- pas 6: verificare +# ------------------------------------------------------- pas 7: verificare Write-Step "Verificare finala" @@ -396,6 +527,9 @@ if ($failed.Count -gt 0) { Write-Host "" Write-Host " De verificat manual:" -Write-Host " - shutdown_policy e inca 'freeze' (recomandat sa ramana asa)" +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 "" diff --git a/proxmox/cluster/scripts/cluster-startup.sh b/proxmox/cluster/scripts/cluster-startup.sh index dfea0ce..28b125a 100755 --- a/proxmox/cluster/scripts/cluster-startup.sh +++ b/proxmox/cluster/scripts/cluster-startup.sh @@ -46,6 +46,7 @@ GUEST_START_ORDER=( 101 # minecraft 110 # moltbot 303 # Win11-Adina + 304 # Win11-Marius 302 # oracle-test-302 109 # oracle-dr-windows — DR, în mod normal rămâne oprit 301 # docker-portainer-template @@ -59,9 +60,26 @@ GUEST_START_ORDER=( # 301, 310 — template-uri, nu servicii. NEVER_AUTOSTART=(109 301 310) +# Rețeaua dedicată de cluster („insula"), existentă din 2026-08-29. De ea atârnă +# replicarea ZFS (migration: network=10.10.10.0/24 în datacenter.cfg) și storage-ul +# NFS backup-pvemini. Pe pve1 și pveelite IP-ul stă pe dongle-ul USB, adus la boot +# de un „auto enx..." din /etc/network/interfaces: dacă nucleul enumerează USB-ul +# DUPĂ ce networking.service a terminat, interfața rămâne fără IP, iar nodul pare +# perfect sănătos. Detalii: ../docs/switch-2.5g-dedicat-noduri.md +declare -A ISLAND_IP=( + [pve1]=10.10.10.200 + [pvemini]=10.10.10.201 + [pveelite]=10.10.10.202 +) +ISLAND_NFS_MOUNT=/mnt/pve/backup-pvemini +ISLAND_NFS_SERVER=10.10.10.201 +ISLAND_NFS_NODES=(pve1 pveelite) + ORACLE_CT=108 ORACLE_DOCKER=oracle-xe -ORACLE_WAIT=600 # secunde așteptate până baza răspunde la interogări +# Măsurat pe 2026-08-29, la pornire de la rece după oprirea completă a clusterului: +# instanța a avut nevoie de ~10 minute ca să ajungă OPEN. 600s erau prea strâmți. +ORACLE_WAIT=1200 # secunde așteptate până baza răspunde la interogări NODE_WAIT=1800 # secunde așteptate până apar toate nodurile QUORUM_WAIT=300 @@ -177,7 +195,91 @@ ok "quorum OK, $VOTES/3 voturi" nssh pvemini "ha-manager status | head -4" -# ------------------------------------------------- pas 3: ce trebuie pornit +# ----------------------------------- pas 3: rețeaua dedicată de cluster (insula) + +# Se verifică ÎNAINTE de pornirea guest-urilor: dacă insula e jos, replicarea și +# backup-ul NFS tac fără nicio alertă, iar simptomul apare abia peste ore. +# Nu blochează pornirea — la revenirea curentului, Oracle sus contează mai mult +# decât replicarea — dar nu te lasă să treci pe lângă problemă fără să o vezi. + +step "Verific rețeaua de cluster (10.10.10.0/24)" + +if (( DRY_RUN )); then + warn "dry-run — sar peste verificarea insulei" +else + ISLAND_BAD=() + + for node in "${!NODE_IP[@]}"; do + want="${ISLAND_IP[$node]}" + have=$(nssh "$node" "ip -4 -o addr show | grep -c ' $want/' || true") + + if [[ "$have" == "0" ]]; then + # Cazul așteptat de eșec: dongle-ul USB enumerat târziu la boot. + # ifreload -a aplică doar diferențele față de /etc/network/interfaces, + # deci nu atinge interfețele tap/veth ale guest-urilor. + warn "$node — lipsește $want, încerc 'ifreload -a'" + nssh "$node" "PATH=/usr/sbin:/sbin:/usr/bin:/bin ifreload -a" || true + sleep 8 + have=$(nssh "$node" "ip -4 -o addr show | grep -c ' $want/' || true") + fi + + if [[ "$have" == "0" ]]; then + ISLAND_BAD+=("$node") + warn "$node — TOT fără $want după ifreload" + else + ok "$node — $want prezent" + fi + done + + # Un IP configurat local nu garantează și cablul/switch-ul. pvemini e sursa + # replicării și serverul NFS, deci se verifică din el spre celelalte două. + for node in pve1 pveelite; do + ip="${ISLAND_IP[$node]}" + if nssh pvemini "ping -c1 -W2 $ip >/dev/null 2>&1"; then + ok "pvemini → $node ($ip) — ping OK" + else + printf '%s\n' "${ISLAND_BAD[@]:-}" | grep -qx "$node" || ISLAND_BAD+=("$node") + warn "pvemini → $node ($ip) — NU răspunde (cablu? switch?)" + fi + done + + NFS_BAD=() + for node in "${ISLAND_NFS_NODES[@]}"; do + src=$(nssh "$node" "findmnt -no SOURCE $ISLAND_NFS_MOUNT 2>/dev/null || true") + if [[ "$src" == *"$ISLAND_NFS_SERVER"* ]]; then + ok "$node — NFS $ISLAND_NFS_MOUNT montat de pe $ISLAND_NFS_SERVER" + elif [[ -n "${src// /}" ]]; then + # Prins pe 2026-08-29 la primul boot la rece: o linie rămasă în /etc/fstab + # monta storage-ul de pe IP-ul de producție ÎNAINTE ca pvestatd să apuce, + # deci backup-ul mergea tăcut pe rețeaua greșită, cu storage.cfg corect. + NFS_BAD+=("$node") + warn "$node — NFS montat de pe ALT server: $src (așteptat $ISLAND_NFS_SERVER)" + warn " verifică: ssh root@${NODE_IP[$node]} 'grep nfs /etc/fstab'" + else + # pvestatd remontează singur în câteva zeci de secunde dacă rețeaua e sus. + warn "$node — NFS $ISLAND_NFS_MOUNT NEMONTAT (pvestatd îl reia dacă insula urcă)" + fi + done + + if (( ${#NFS_BAD[@]} )); then + warn "Backup-ul NFS trece pe altă rețea decât cea proiectată pe: ${NFS_BAD[*]}" + fi + + if (( ${#ISLAND_BAD[@]} )); then + warn "" + warn "INSULA E JOS pe: ${ISLAND_BAD[*]}" + warn "Consecință: replicarea ZFS și storage-ul NFS backup-pvemini NU vor funcționa." + warn "Guest-urile pornesc și merg normal — problema e tăcută, se vede abia în pvesr status." + warn "Verifică: ssh root@ \"ip -br a; dmesg | tail -20\" (dongle USB enumerat târziu?)" + confirm "Continui cu pornirea guest-urilor, cu replicarea jos?" + elif (( ${#NFS_BAD[@]} == 0 )); then + ok "insulă completă — replicarea și NFS-ul au calea liberă" + else + warn "insula e sus, dar NFS-ul nu o folosește — vezi mai sus" + fi +fi + +# ------------------------------------------------- pas 4: ce trebuie pornit step "Ce se pornește" @@ -223,7 +325,7 @@ done <<<"$TO_START" confirm "Se pornesc guest-urile de mai sus, în ordinea: ${GUEST_START_ORDER[*]}" -# ------------------------------------------------- pas 4: pornire guest-uri +# ------------------------------------------------- pas 5: pornire guest-uri step "Pornire guest-uri" @@ -269,7 +371,16 @@ wait_oracle() { (( DRY_RUN )) && return 0 local waited=0 while (( waited < ORACLE_WAIT )); do - if nssh "$node" "pct exec $ORACLE_CT -- docker exec $ORACLE_DOCKER bash -c \"printf 'set pagesize 0 feedback off\nselect global_name from global_name;\nexit\n' | sqlplus -s / as sysdba\" 2>/dev/null | grep -q '[A-Z]'"; then + # `bash -lc`, NU `bash -c`: fără shell de login nu există sqlplus în PATH, iar + # așteptarea ar expira după $ORACLE_WAIT secunde chiar cu baza perfect deschisă. + # + # `timeout 30` e OBLIGATORIU, nu o precauție. Pe 2026-08-29, la pornirea de la + # rece, sqlplus lansat către o instanță încă în startup s-a blocat definitiv: + # docker exec a moștenit blocajul, ssh a așteptat, iar bucla n-a apucat să + # facă nici măcar o iterație. Scriptul a stat agățat 14 minute fără niciun + # mesaj, cu propriul prag de așteptare nefolosit. Sonda trebuie să răspundă + # mereu, fie și cu eșec. + if nssh "$node" "timeout 30 pct exec $ORACLE_CT -- docker exec $ORACLE_DOCKER bash -lc \"printf 'set pagesize 0 feedback off\nselect status from v\\\$instance;\nexit\n' | sqlplus -s / as sysdba\" 2>/dev/null | grep -q OPEN"; then ok "Oracle deschis și interogabil (${waited}s)" return 0 fi @@ -305,7 +416,7 @@ while read -r type node id status ha; do start_guest "$type" "$node" "$id" "$ha" || FAILED+=("$type:$id") done <<<"$TO_START" -# ------------------------------------------------ pas 5: restaurare cron-uri +# ------------------------------------------------ pas 6: restaurare cron-uri step "Restaurare cron-uri de monitorizare" @@ -325,7 +436,7 @@ for node in "${!NODE_IP[@]}"; do fi done -# ------------------------------------------------------- pas 6: verificare +# ------------------------------------------------------- pas 7: verificare step "Verificare finală" @@ -354,7 +465,10 @@ fi cat </dev/null) + case "$d" in *r8152) ;; *) continue ;; esac + case " $port " in *" $i "*) echo "$i: port al puntii vmbr0"; continue ;; esac + if grep -qE "^[[:space:]]*(auto|allow-hotplug)[[:space:]]+$i([[:space:]]|$)" /etc/network/interfaces; then + echo "$i: strofa proprie ($(sed -n "s/^[[:space:]]*iface[[:space:]]\+$i[[:space:]]\+//p" /etc/network/interfaces | head -1)), IP: $(ip -4 -o addr show "$i" | awk '{print $4}' | tr '\n' ' ')" + else + echo "$i: NECONFIGURAT in /etc/network/interfaces" + fi +done '@ - Write-Info "bridge-ports vmbr0: $port" - if ($ifs -notcontains $port) { - Write-Warn "adaptorul USB nu e portul puntii - instalez oricum, dar verifica manual" + $rol | ForEach-Object { Write-Info $_ } + if ($rol -match 'NECONFIGURAT') { + Write-Warn "un adaptor r8152 nu e nici port de punte, nici cu strofa proprie -" + Write-Warn "regula se instaleaza, dar pentru el ifreload -a n-are ce aplica" } if ($DryRun) { diff --git a/proxmox/cluster/scripts/wake-cluster.ps1 b/proxmox/cluster/scripts/wake-cluster.ps1 new file mode 100644 index 0000000..a9c150a --- /dev/null +++ b/proxmox/cluster/scripts/wake-cluster.ps1 @@ -0,0 +1,164 @@ +<# +.SYNOPSIS + Porneste nodurile Proxmox prin Wake-on-LAN. + +.DESCRIPTION + Trimite magic packet catre placile de retea de PRODUCTIE ale nodurilor si + asteapta sa apara pe retea. + + De ce merge abia din 2026-08-29: pana atunci pve1 si pveelite aveau IP-ul + principal pe dongle-uri USB Realtek, care nu suporta WoL si oricum pierd + alimentarea cand nodul e oprit. Mutarea lui vmbr0 pe interfetele onboard a + facut WoL posibil - vezi ../docs/switch-2.5g-dedicat-noduri.md. + + NU trezi un singur nod si atat, daca ai oprit tot clusterul: un nod pornit + singur nu are quorum, /etc/pve ramane read-only si nu poti porni niciun guest. + Implicit scriptul le trezeste pe toate trei. + + RULEAZA DE PE STATIA DE ADMIN, care trebuie sa fie pe acelasi segment L2 + (10.0.20.0/24) - magic packet-ul e broadcast si nu trece prin router. + +.PARAMETER Node + Trezeste doar nodul dat. Implicit: toate trei. + +.PARAMETER Wait + Secunde de asteptat sa apara nodurile (implicit 300). + +.PARAMETER NoWait + Trimite pachetele si iese, fara sa astepte. + +.EXAMPLE + .\wake-cluster.ps1 +.EXAMPLE + .\wake-cluster.ps1 -Node pveelite +#> +[CmdletBinding()] +param( + [ValidateSet('pve1','pvemini','pveelite')] + [string]$Node, + [int]$Wait = 300, + [switch]$NoWait +) + +$ErrorActionPreference = 'Stop' + +# MAC-ul interfetei de PRODUCTIE (portul puntii vmbr0), nu al dongle-ului de insula. +# Citite de pe noduri pe 2026-08-29 cu: ethtool ; cat /sys/class/net//address +$Nodes = [ordered]@{ + pve1 = @{ Ip = '10.0.20.200'; Mac = 'fc:3f:db:0a:0d:d8'; If = 'eno1' } + pvemini = @{ Ip = '10.0.20.201'; Mac = '58:47:ca:7d:51:3c'; If = 'enp90s0' } + pveelite = @{ Ip = '10.0.20.202'; Mac = '84:69:93:57:b2:ea'; If = 'eno1' } +} + +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-Info { param([string]$Text) Write-Host " $Text" -ForegroundColor DarkGray } + +function Test-Alive { + param([string]$Address, [int]$TimeoutMs = 1500) + try { + $p = New-Object System.Net.NetworkInformation.Ping + return ($p.Send($Address, $TimeoutMs).Status -eq 'Success') + } catch { return $false } +} + +function Send-MagicPacket { + param([string]$Mac) + + $bytes = ($Mac -split '[:-]') | ForEach-Object { [byte]([Convert]::ToInt32($_, 16)) } + if ($bytes.Count -ne 6) { throw "MAC invalid: $Mac" } + + # Magic packet: 6 octeti 0xFF, apoi MAC-ul repetat de 16 ori. + $packet = New-Object byte[] 102 + for ($i = 0; $i -lt 6; $i++) { $packet[$i] = 0xFF } + for ($r = 0; $r -lt 16; $r++) { [Array]::Copy($bytes, 0, $packet, 6 + $r * 6, 6) } + + # Trimis si pe broadcast global, si pe cel de subretea: unele stive le trateaza + # diferit, iar pachetul e mic - nu costa nimic sa mearga pe ambele. + # Porturile 9 si 7 sunt cele uzuale; placile asculta oricum dupa continut. + $udp = New-Object System.Net.Sockets.UdpClient + try { + $udp.EnableBroadcast = $true + foreach ($dest in @('255.255.255.255', '10.0.20.255')) { + foreach ($port in @(9, 7)) { + [void]$udp.Send($packet, $packet.Length, $dest, $port) + } + } + } finally { + $udp.Close() + } +} + +$targets = if ($Node) { @($Node) } else { $Nodes.Keys } + +Write-Step "Trimit magic packet" + +$deja = @() +foreach ($n in $targets) { + if (Test-Alive $Nodes[$n].Ip) { + Write-Ok "$n ($($Nodes[$n].Ip)) - deja pornit, nu trimit" + $deja += $n + continue + } + Send-MagicPacket -Mac $Nodes[$n].Mac + Write-Ok "$n -> $($Nodes[$n].Mac) ($($Nodes[$n].If))" +} + +$asteptate = @($targets | Where-Object { $deja -notcontains $_ }) + +if ($asteptate.Count -eq 0) { + Write-Warn "Toate nodurile cerute erau deja pornite." + exit 0 +} + +if ($NoWait) { + Write-Info "-NoWait: nu astept. Verifica singur cu ping." + exit 0 +} + +Write-Step "Astept nodurile (max ${Wait}s)" + +# Retrimitem periodic: daca placa nu era inca alimentata la primul pachet +# (alimentare tocmai revenita, de exemplu), al doilea o prinde. +$waited = 0 +$up = @() +while ($waited -lt $Wait) { + foreach ($n in $asteptate) { + if ($up -contains $n) { continue } + if (Test-Alive $Nodes[$n].Ip) { + $up += $n + Write-Ok "$n a pornit (${waited}s)" + } + } + if ($up.Count -eq $asteptate.Count) { break } + + Start-Sleep -Seconds 5 + $waited += 5 + + if ($waited % 60 -eq 0) { + foreach ($n in $asteptate) { + if ($up -notcontains $n) { Send-MagicPacket -Mac $Nodes[$n].Mac } + } + Write-Info "am retrimis pachetele (${waited}s)" + } +} + +$lipsa = @($asteptate | Where-Object { $up -notcontains $_ }) + +Write-Step "Rezultat" + +if ($lipsa.Count -eq 0) { + Write-Ok "toate nodurile cerute au pornit" + Write-Host "" + Write-Host " Urmatorul pas: .\cluster-startup.ps1" + Write-Host "" +} else { + Write-Warn "NU au pornit: $($lipsa -join ', ')" + Write-Info "Cauza cea mai probabila e in BIOS, nu in retea:" + Write-Info " - 'Wake on LAN' / 'Power On by PCI-E' dezactivat" + Write-Info " - ErP Ready / Deep Sleep ACTIVAT (taie alimentarea placii in S5)" + Write-Info "Verifica si ca statia de admin e pe acelasi segment L2 (10.0.20.0/24)." + Write-Info "Pe nodurile pornite, starea WoL se vede cu: ethtool | grep Wake-on" + exit 1 +}