docs: postmortem + changelog for 2026-06-06 template/DHCP session
Lint / pssa (push) Successful in 16s
Lint / python (push) Successful in 39s

Postmortem of the Linux template deploy/prepare 'no IP' investigation:
separates the three distinct causes (apt index stale from --skip-update,
deploy --force snapshot-chain, wedged host vmnet-dhcpd) and documents the
recovery (restart vmware-networks). Changelog entry summarising the
toolchain, xvfb, --force cleanup and cloud-init seal changes.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-06 13:38:34 +02:00
parent 6f51d44f92
commit 210b4f5f16
2 changed files with 206 additions and 0 deletions
+24
View File
@@ -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`
@@ -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.