diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index c9a4bf0..13c881b 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -5,6 +5,30 @@ Open backlog items remain in [TODO.md](../TODO.md). --- +## 2026-06-06 — Linux template: GUI toolchain, headless builds, deploy hardening + +- **Toolchain** (`Install-CIToolchain-Linux2404.sh`): new Step 4a installs + `python3-gi gir1.2-gtk-4.0 xvfb` (headless PyGObject/GTK4 GUI tests). Step 1 + now always runs `apt-get update`; `--skip-update` only skips the full upgrade + (previously it skipped the index refresh too, so newly-added packages could + not be located). +- **`build run` / `job`**: new `-Xvfb` / `--xvfb` switch (Linux only) runs the + build under `xvfb-run` so GUI toolkits get a `$DISPLAY`. Ignored for Windows. + See [WORKFLOW-AUTHORING.md](WORKFLOW-AUTHORING.md) §5/§8.2. +- **`deploy-linux --force`**: now clears stale snapshot-chain artifacts + (`-NNNNNN.vmdk`, `.vmsn`, `.vmsd`, `.lck`) before the Step 4 resize, fixing + *"disk is part of a snapshot chain"*. +- **`prepare-linux`**: Step 7d seals cloud-init (drops the stale + `50-cloud-init.yaml`, keeps the MAC-agnostic `99-ci-dhcp-allnics`, disables + cloud-init) so it never re-runs on linked clones; deploy user-data sets + `package_update/upgrade: false` and the Step 7 IP-detect timeout is raised to + 600 s. +- **Postmortem** [postmortem-2026-06-06-linux-template-no-ip.md](postmortem-2026-06-06-linux-template-no-ip.md): + intermittent deploy/prepare "no IP" traced to a wedged host `vmnet-dhcpd`; + recovery is `vmware-networks --stop/--start`. + +--- + ## 2026-05-13 — Sprint 16: Medium/Low hardening (final-master-plan) - **Watch-DiskSpace.ps1**: Event Log source renamed `CI-DiskAlert` → `CI-DiskSpaceAlert` diff --git a/docs/postmortem-2026-06-06-linux-template-no-ip.md b/docs/postmortem-2026-06-06-linux-template-no-ip.md new file mode 100644 index 0000000..25920e1 --- /dev/null +++ b/docs/postmortem-2026-06-06-linux-template-no-ip.md @@ -0,0 +1,182 @@ +# Postmortem — Template Linux: deploy/prepare "no IP" (2026-06-06) + +## Sintesi + +Durante l'aggiunta di dipendenze GUI al toolchain del template Linux +(`python3-gi gir1.2-gtk-4.0 xvfb`), una serie di fallimenti ha aperto una lunga +sessione di debug. Sono emersi **due problemi distinti**, più una causa +ambientale che ha confuso la diagnosi: + +1. **Origine reale della sessione** — `prepare-linux` falliva allo Step 4a con + `Unable to locate package gir1.2-gtk-4.0`. Causa: `--skip-update` saltava + anche `apt-get update`, lasciando l'indice apt vecchio. **Non c'entrava la + rete.** Fix banale, applicato. + +2. **Bug `deploy-linux --force`** — il resize allo Step 4 falliva con + *"disk is part of a snapshot chain"* perché `--force` riusava la directory + senza rimuovere la snapshot chain precedente. Bug deterministico, colpito + due volte. Fix applicato. + +3. **Saga "no IP" (ambientale)** — il rilevamento IP via open-vm-tools andava in + timeout in modo **intermittente**. Causa finale: il servizio DHCP/NAT di + vmnet8 sull'host si era **inceppato** a causa della churn di debug (decine di + `vmrun start/stop`, `tcpdump` promiscuo, DISCOVER ripetuti del guest). + Risolto con un **restart di `vmware-networks`**. Non è un bug del codice né + del CI normale. + +Stato finale: template `BaseClean-Linux` ricostruito da powered-off, con gtk/xvfb +e cloud-init sigillato. Tutti i test passano (coverage 94.4%). + +--- + +## Cronologia + +| Ora (host) | Evento | +|-----------|--------| +| ~10:50 | `prepare-linux --skip-update` → fallisce allo Step 4a (`gir1.2-gtk-4.0` non trovato) per indice apt stale | +| ~11:00 | Dopo fix apt + revert snapshot: `prepare-linux` riesce, guest ottiene IP `.153`, snapshot ripreso | +| ~11:03+ | I boot successivi dello snapshot non ottengono IP utile → inizio debug rete | +| 11:20 | Cattura sul filo: DHCP OFFER/ACK presenti, ma il guest non applica l'IPv4 | +| 12:06 | Deploy fresco: il guest **ottiene** `.143` (cloud-init), ma deploy Step 7 va in timeout sui tools | +| 12:26 | Reboot: stesso guest **non** ottiene IP (intermittenza) | +| ~12:30 | `vmware-networks --stop/--start` → l'IP torna stabile. Sblocco confermato | +| dopo | Implementati e testati i fix; template rigenerato | + +--- + +## Analisi cause + +### 2.1 Step 4a apt (origine della sessione) + +`Install-CIToolchain-Linux2404.sh` faceva `apt-get update` **solo** se non era +passato `--skip-update`. Ma `--skip-update` dovrebbe saltare il solo *upgrade* +(lento), non il refresh dell'indice. Con indice vecchio, i pacchetti nuovi +(`gir1.2-gtk-4.0`, `xvfb`) non venivano trovati. + +A riprova che a quel punto la rete funzionava: quel `prepare-linux` era entrato +in SSH nel guest senza problemi. + +### 2.2 deploy-linux --force e la snapshot chain + +`deploy-linux --force` riusa la directory della VM ma non rimuoveva gli artefatti +di un'eventuale snapshot precedente (`-NNNNNN.vmdk`, `.vmsn`, `.vmsd`). Con la +chain presente, `vmware-vdiskmanager` rifiuta il resize dello Step 4: + +``` +This disk is part of a snapshot chain in '.../LinuxBuild2404.vmx'. +The selected operation can only be executed on a disk with no snapshots. +``` + +### 2.3 La saga "no IP" — vmnet8 DHCP inceppato + +Sintomo: `vmrun getGuestIPAddress` → *"VMware Tools are not running"* / +*"Unable to get the IP address"*, **in modo intermittente**. Il guest in realtà: + +- bootava correttamente (login prompt), +- aveva open-vm-tools installato e attivo, +- ma `systemd-networkd` a volte **non applicava** un lease DHCPv4 (link up, + solo IPv6LL, `networkd-wait-online` in timeout). + +Sul filo il DHCP a volte completava (`.143` applicato e raggiungibile), a volte +no. Il server rispondeva (altre VM ottenevano lease). La causa finale: il demone +`vmnet-dhcpd`/`vmnet-natd` di vmnet8 era andato in uno stato degradato dopo la +churn di debug. Il **restart** `vmware-networks --stop/--start` lo ha sbloccato +e l'IP è tornato stabile e immediato. + +#### Ipotesi scartate (vicoli ciechi) +- **Controller pvscsi vs lsilogic** — il boot funzionava con pvscsi; irrilevante. +- **Naming NIC `ens192` vs `ens160`** — cloud-init si adattava; l'IP arrivava lo + stesso quando la rete funzionava. +- **Saturazione del pool DHCP** — il pool era di fatto libero (0 lease attivi); + i MAC multipli per IP nel lease DB sono storia normale di ISC dhcpd. +- **Seal cloud-init / machine-id** — difensivi ma non erano la causa del no-IP. + +--- + +## Fix applicati (branch `fix/linux-template-toolchain-deploy`) + +### Commit 1 — toolchain +`template/Install-CIToolchain-Linux2404.sh` +- **Step 4a**: installa `python3-gi gir1.2-gtk-4.0 xvfb` con assert su + `import gi` e `Xvfb` (richiesta originale). +- **Step 1**: `apt-get update` ora gira **sempre**; `--skip-update` salta solo + l'upgrade. Risolve il fallimento Step 4a. + +### Commit 2 — robustezza deploy/prepare +`src/ci_orchestrator/commands/template.py` (+ test) +- **`_clean_force_artifacts`**: con `--force` rimuove snapshot delta, `.vmsn`, + `.vmsd` e `.lck` prima del resize → niente più errore snapshot-chain. + Coperto da 3 unit test. +- **prepare-linux Step 7d (seal cloud-init)**: rimuove la `50-cloud-init.yaml` + stale, rigenera netplan (resta solo il `99-ci-dhcp-allnics` MAC-agnostic), + disabilita cloud-init (`/etc/cloud/cloud-init.disabled`) così non rigira sui + cloni. +- **deploy user-data**: `package_update/upgrade: false` per non tenere il lock + dpkg al primo boot e non ritardare open-vm-tools oltre il poll dello Step 7. +- **Step 7 timeout**: `300s → 600s` come rete di sicurezza per primi boot lenti. + +Verifica: `ruff` ✅, `mypy --strict` (23 file) ✅, suite completa ✅, +coverage **94.4%** (gate 90%). Prod venv reinstallato (non-editable). + +--- + +## Procedura di recovery: deploy/prepare non ottiene IP + +Quando `deploy-linux` (Step 7) o `prepare-linux` non riescono a rilevare l'IP +**ma il guest sembra sano** (booting, tools attivi), prima di sospettare +immagine o codice, riavvia il networking VMware sull'host: + +```bash +sudo /usr/bin/vmware-networks --stop +sudo /usr/bin/vmware-networks --start +``` + +Flush opzionale dei lease (backup prima), **solo se nessuna VM è accesa**: + +```bash +sudo cp /etc/vmware/vmnet8/dhcpd/dhcpd.leases /tmp/dhcpd.leases.bak +sudo sh -c ': > /etc/vmware/vmnet8/dhcpd/dhcpd.leases' +``` + +Distinto da: +- **Tools-not-running sul template** → revert a `BaseClean-Linux` + (vedi `template-live-state-recovery`). +- **vmmon/vmnet non compilati dopo bump kernel** → il guard systemd ricostruisce + i moduli al boot (vedi `vmware-modules-kernel-upgrade`). + +--- + +## Raccomandazioni (NON applicate di proposito) + +### Cosa NON fare +- **Restart/flush automatico ad ogni clone/deploy/prepare.** `vmware-networks` + è globale (bridge + hostonly + NAT insieme): un restart sotto un job in corso + rompe la rete di **tutte** le VM in esecuzione → job concorrenti falliti. Il + flush dei lease con VM attive causa conflitti IP al rinnovo. Sproporzionato + rispetto a un problema non dimostrato ricorrente. + +### Cosa fare +- **Monitorare.** L'inceppamento è nato dalla churn anomala di debug, non dal CI + normale. Tenere la recovery manuale e agire solo se ricapita con dati reali. + +### Se servisse hardening preventivo (a freddo, con cautela) +- Abbassare `max-lease-time` (i cloni vivono minuti → IP liberati in fretta; i + job lunghi rinnovano da soli via networkd). +- Allargare il range DHCP per più margine. +- **Caveat**: `/etc/vmware/vmnet8/dhcpd/dhcpd.conf` è **auto-generato da VMware** + ("DO NOT MODIFY", validato con `CFG_HASH` in `/etc/vmware/networking`). Edit a + mano fragili: rischio rigenerazione al prossimo `--configure`/update. Da fare + solo se il problema si dimostra ricorrente. + +--- + +## Lezioni + +- L'attività di debug intensa sulla rete host (start/stop ripetuti, sniffing + promiscuo) può degradare `vmnet-dhcpd`: in futuro, minimizzare la churn e + preferire osservazione mirata. +- Tre cause distinte si sono sovrapposte; separarle prima ha richiesto tempo. Il + guest era fondamentalmente sano per quasi tutta la sessione — l'errore di + rilevamento IP non implicava un guest rotto. +- Non sovrastimare: i fix committati coprono i bug **veri e provati**; il DHCP è + stato lasciato invariato per evitare over-engineering.