kvm2: record the review validation, enable the provider entry, changelog

Closes out the review-fix campaign. The kvm2 provider was deployed
in-cluster and validated against beelink -- the first time any of this
code had run against real libvirt rather than the in-memory fake.

config/manager/providers_config.yaml ships the kvm2-home entry enabled,
since the operator is now running it for real.

review/ carries the code review the four H-commits reference, so H1..H4
resolve to something rather than dangling: scope, the seven-chunk sweep,
the escalations, the ten deep dives and the final report. .claude/agents
carries the two reviewer definitions that produced them.

What the validation did not prove is recorded next to what it did: H2's
error paths need a body that fails part way through, and a healthy 433 MB
download never finishes short, so the unverified libvirtd short-stream
behaviour is still open. The kvm2 integration test has also still never
been run.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-09-17 20:50:45 +00:00
co-authored by Claude Opus 5
parent 4114d43e87
commit 41fbd5e7e6
10 changed files with 1064 additions and 29 deletions
@@ -7,7 +7,7 @@ Plan: [docs/plans/2026-09-17-1515-kvm2-review-fixes.md](../plans/2026-09-17-1515
- [x] Step 3 — H3: an unreadable address must not look like a missing one
- [x] Step 4 — H4: the base image cache must not be shared across provider entries
- [ ] Step 5 — Validate H1-H4 in-cluster against beelink (in progress)
- [x] Step 5 — Validate H1-H4 in-cluster against beelink
- [x] Step 6 — Route klog into the structured logger
Remaining after those: the 48 mediums and lows in `review/REPORT.md`. Each gets its own
@@ -467,3 +467,65 @@ opts := zap.Options{
```
Left out of this step as scope the plan did not cover.
## Step 5 — Validation against beelink
First contact between the kvm2 provider and real libvirt. Everything before this ran against
an in-memory fake of the twenty libvirt calls the provider uses.
Run in the existing `kind-proxy-operator-demo` cluster rather than a fresh one, deliberately.
That cluster already owned two live `kvm-home` VMs on beelink, and orphan GC deletes any
instance whose UID is missing from *its own* cluster's live set after `gc-min-age`. A second
operator carrying the `kvm-home` entry would have destroyed both within ten minutes. Using the
existing cluster made its live set the thing that protected them — the isolation argument
pointed the opposite way from instinct.
Baseline recorded first (4 Ready proxies, 3 ProviderHealth entries, beelink's domains, volumes
and pool space), so "did we break something" stayed answerable.
The image tag had to change from the running `kvm-dev`. `make deploy` sets the image through
kustomize, so an identical tag leaves the Deployment spec untouched, no rollout happens, the
new process never starts, and the providers config — which is read once per process — is never
re-read. The ConfigMap carries no generator hash either, so nothing else would have forced it.
Results, in the order they were checked:
| Check | Outcome |
|---|---|
| `kvm2-home` ProviderHealth | `Ready 1/1`, `Reachable`, 189 ms — hostname, libvirt 11.6.0, pool running, 247.8 GiB free |
| First Create, cold cache | `base image cached`, 433,651,712 bytes in 8.3 s, digest verified |
| Volume naming (H4) | `base-kvm2-home-debian-13.qcow2` created **beside** kvm's untouched `base-debian-13.qcow2` |
| Second Proxy | no `fetching` line at all — cache hit under the new name |
| State reporting (H3) | `Running` held steady across every poll, no flapping to Provisioning |
| Squid | `Healthy=True` on a real CONNECT probe |
| Delete | domain and both per-VM volumes gone; base image survived |
| Existing infrastructure | all four pre-existing proxies stayed Ready across two rollouts |
The health check alone is worth calling out as a gate: it dials SSH, verifies the host key,
completes the libvirt handshake, runs four RPCs and tears down a dedicated connection — the
exact path H1 changed — without creating anything. Putting the stop gate there meant the
riskiest unknown was settled before a single byte was written to the hypervisor.
### What this run did NOT prove
**H2's error paths.** The unverified claim — that libvirtd answers a short stream finish with
OK rather than an error — only arises when a body *fails* part way through, and a healthy
433 MB download never finishes short. The 19-minute HTTP deadline never fired either. What was
proved is narrower: the wrapper does not break normal uploads. Settling the rest still needs
the scratch-pool probe in the Step 2 entry, and it would be easy to mistake this green run for
more than it is.
**The `kvm2` integration test still has not run.** `make test-integration-kvm2` remains
unexecuted; this was a cluster-level validation, not that suite.
### Worth noting
`config/manager/providers_config.yaml` now ships with the `kvm2-home` entry **enabled**, since
the operator is running it for real. `config/manager/kustomization.yaml` was left at its
committed tag: `make deploy` rewrites it to whatever local image was last deployed, which is
churn rather than work.
Two host-side leftovers were *not* touched, both pre-existing and unrelated to kvm2: five
orphan `proxy-*` volumes from earlier kvm runs, and the `base-test-image.qcow2` pair from the
kvm integration test. Every write to beelink in this step came from the operator itself; no
`virsh` write was issued by hand.