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>
11 KiB
kvm2 provider review: scope
Branch feat/kvm2-provider at 2510842. Seed: grep -ril 'kvm2\|libvirt' excluding vendor,
generated, testdata, docs. The minikube layout hints in the brief do not apply; this repo is
the egress-proxies operator and kvm2 is internal/provider/kvm2/, a pure-Go provider
(digitalocean/go-libvirt RPC over an SSH-tunnelled socket, no cgo, no driver plugin binary).
Checklist items with no counterpart here, so reviewers can answer them quickly rather than hunt:
- libvirt network lifecycle / DHCP / static IP: kvm2 creates no networks; it attaches to a
configured bridge and reads the address via
DomainInterfaceAddresses. The live questions are IP detection, timeouts and stale addresses. - GPU/PCI/NUMA passthrough: none.
- driver plugin RPC boundary: none. Closest analogue is the libvirt RPC link over SSH
(
link,call, budgets, dead-link detection, libvirt version differences). - cgo/build tags: no cgo. Only tag is
//go:build integration. - Handle leaks: go-libvirt handles are plain values (no
Free); the leakable resources are SSH connections, the libvirt link, goroutines (keepalive,await,call), HTTP bodies, temp dirs and half-written storage volumes.
CORE (kvm2-owned)
internal/provider/kvm2/ — 18 source files (~3.2k lines), 22 test files (~7.3k lines).
| File | Lines | What |
|---|---|---|
| kvm2.go | 243 | Provider, New, options, withDefaults, host, wrapErr, lockName, keyedLocks |
| ssh.go | 216 | dialStage/dialError, loadCredentials, sshSocket + keepalive, dialSSHSocket, await |
| hosts.go | 162 | dialer, hostTable (per-host cached backend, get/dialFresh), libvirtDialer |
| backend.go | 369 | backend interface, budgets, link (dead marking), call/run, libvirtBackend |
| observe.go | 263 | observed backend decorator: span + log per libvirt call |
| create.go | 280 | Create, create, ensureOverlay, writeSeed, ensureRunning, waitRunning |
| delete.go | 98 | Delete, removeDomain, removeVolumes |
| get.go | 119 | Get, get, readMetadata, address |
| list.go | 56 | ListByTag |
| state.go | 49 | mapState, isActive, firstIPv4 |
| id.go | 28 | formatProviderID, parseProviderID |
| metadata.go | 78 | GC mark in domain <metadata>: marshal/parse, managed, createdAt |
| errors.go | 97 | permanentf/transientf, classify, classifyLibvirt, libvirtCode, hasCode |
| image.go | 467 | resolveImage, lockImage, ensureImage, cachedImage, marker read/write, fetchImage, uploadImage, do, checkResponse, pool space checks |
| volume.go | 95 | volume naming, urlImageKey, buildVolumeXML |
| xml.go | 256 | domainParams, buildDomainXML and the XML structs |
| seed.go | 124 | buildSeedISO (temp dir, iso9660), buildVendorData |
| check.go | 189 | Check, checkHost, probeHost, checkReason, formatting helpers |
Tests (each reviewed with its source file): *_test.go next to every file above, plus
provider_test.go (contract tests), fake_test.go (fake backend), sshserver_test.go
(in-process SSH server), kvm2_integration_test.go (//go:build integration, TestKVM2*).
Other kvm2-owned bits:
cmd/main.go:62,206— import and"kvm2": kvm2.NewregistrationMakefile:73-75—test-integration-kvm2config/samples/proxy_kvm2.yaml,config/samples/kustomization.yaml:8config/samples/providers-config.yaml:58-,config/manager/providers_config.yaml:54-(commented kvm2 entries)
Reference only, not under review: docs/kvm2-provider-spec.md, docs/kvm2-provider.md,
docs/provider-contract.md. Reviewers may read them to judge code against stated intent.
SHARED (on the kvm2 call path; listed symbols only)
Called by kvm2:
internal/provider/config_kvm.go—KVMConfig,KVMImage,KVMMachineType,KVMHost,KVMFormat*/KVMFirmware*consts,IsHTTPURL,validateKVM,validateKVMBlock(the validator is what stands between user YAML and kvm2's XML/URL/volume-name building)internal/provider/config.go—ProviderConfig.KVM2,setBlocks, thekvm2arms ofConfig.validateinternal/provider/provider.go—Providerinterface,CreateRequest,Placement,Instance,InstanceState+State*consts,LabelManagedYesinternal/provider/errors.go—Error,Wrap,Class,ErrTransient/ErrPermanent/ErrNotFound/ErrQuotaExceededinternal/provider/check.go—Checker,TargetStatus,Reason*constsinternal/provider/name.go—NameFromUID(produces the domain name kvm2 puts in XML and volume names)internal/tracing—StartSpan,Tracer,Option
Calling into kvm2:
internal/controller/proxy_controller.go—reconcileManaged(Create/Get/Delete calls at 156/197/218),replaceInstance,reconcileDelete,providerFailure,deletionFailure,resolveCloudInit,placementFrominternal/gc/gc.go—Sweeper.sweep(ListByTagat 105,Deleteat 125)internal/providerhealth/monitor.go—Monitor.checkOnce,Monitor.run(Checkat 214; the timeout it passes)internal/provider/registry/registry.go—Constructor,Buildinternal/provider/metrics.go,internal/provider/tracing.go—WithMetrics/WithTracingdecorators (instrumented,traced): error passthrough, and that they hideCheckercmd/main.go:196-225— registry build, checker extraction from unwrapped providers, wrappingconfig/manager/manager.yaml:150-178—kvm-sshSecret mount,/tmpemptyDir (64Mi) thatbuildSeedISOuses
EXCLUDED but considered
internal/provider/kvm/— the older kvm provider; kvm2 is a clean-room rewrite and imports nothing from it.internal/provider/config_kvm_test.go,config_test.go— reviewed only for kvm2 cases, inside chunk 7; rest is kvm.internal/provider/gcp/,internal/provider/kubernetes/— other providers, not on a kvm2 path.internal/health,internal/discovery,internal/lease— consumeProxystatus, never call the provider.internal/metrics,internal/tracinginternals — only the three entry points above are on the path.internal/providerhealth/envtest_test.go— mentions kvm2 only as a type string in a fixture.api/v1alpha1/providerhealth_types.go:71,config/crd/bases/...providerhealths.yaml— comment-only / generated.go.mod,go.sum— dependency pins (go-libvirt20260814 pseudo-version,iso9660v0.4.0,x/cryptov0.54.0); noted, not reviewed. Third-party source may be read to understand a call, not reported on.config/samples/proxy_kvm.yaml,Makefile:69-71,185-196(test-integration-kvm,kvm-ssh-secret) — kvm-only or shared tooling with no kvm2 logic.kvm-ssh-secretis mentioned in chunk 7 for context only.README.md,CHANGELOG.md,AGENTS.md,.claude/settings.json,docs/— prose / tooling..gitea/workflows/build.yaml,Dockerfile— no kvm2-specific content; integration test is not run in CI (worth stating in chunk 7, not a file to review).
Proposed phase 1 chunking (7 chunks)
-
Connection layer (SSH + libvirt link) Files:
ssh.go,hosts.go,backend.go, +ssh_test.go,sshserver_test.go,hosts_test.go,backend_test.go. SHARED in play:KVMConfig(SSHKeyFile, KnownHostsFile, InsecureIgnoreHostKey),KVMHost. Focus: connection/goroutine leaks on error paths, dead-link detection and redial races inhostTable, budgets vs caller context, keepalive, host key handling. -
Domain lifecycle (Create / Delete) Files:
kvm2.go,create.go,delete.go,id.go, +kvm2_test.go,create_test.go,delete_test.go,id_test.go,provider_test.go. SHARED in play:CreateRequest,Placement,NameFromUID;reconcileManaged,replaceInstance,reconcileDelete(how Create/Delete are retried). Focus: partial-create cleanup, idempotency, foreign-domain refusal, half-deleted domains,keyedLockscorrectness,waitRunningtimeout/cancellation. -
Observation, state and error classification (Get / ListByTag) Files:
get.go,list.go,state.go,errors.go, +get_test.go,list_test.go,state_test.go,errors_test.go. SHARED in play:Instance,InstanceState,Error/Wrap/Classand theErr*classes;Sweeper.sweep,providerFailure,deletionFailure. Focus: libvirt state mapping (paused/crashed/pmsuspended/shutoff), IP detection and stale addresses, NotFound vs transient, one dead host failing the whole list and what GC does with a partial list. -
Base image cache and volumes Files:
image.go,volume.go, +image_test.go,volume_test.go. SHARED in play:KVMImage,KVMFormat*,IsHTTPURL,validateKVMBlock(image key charset). Focus: download/upload streaming, sha256 verify + marker protocol, cross-replica and cross-Create races on the same base volume, cleanup of partial volumes, URL handling (SSRF, redirects, size caps), pool space check, volume-name injection via URL/key. -
XML and cloud-init seed templating Files:
xml.go,seed.go,metadata.go, (buildVolumeXMLinvolume.go), +xml_test.go,seed_test.go,metadata_test.go. SHARED in play:KVMMachineType,KVMHost(bridge, pool),KVMFirmware*,CreateRequest.CloudInit,NameFromUID;resolveCloudInit;manager.yaml/tmpemptyDir. Focus: escaping of config- and user-supplied values, validity across libvirt versions, metadata parse robustness (GC trusts it), temp-dir cleanup and size limit, vendor-data merging. -
Health check and observability Files:
check.go,observe.go, +check_test.go,observe_test.go. SHARED in play:Checker,TargetStatus,Reason*;Monitor.checkOnce/run;tracing.StartSpan/Tracer;WithMetrics/WithTracingdecorators;cmd/main.go:213-225. Focus: dedicated check connections closed on every path, check timeout vs dial budget, reason mapping, secrets or cloud-init leaking into logs/spans, span end on every path. -
Config, registration, deployment and test gating Files:
internal/provider/config.go+config_kvm.go(kvm2 arms), their tests' kvm2 cases,internal/provider/registry/registry.go,cmd/main.go:196-225,config/manager/manager.yaml:150-178,config/manager/providers_config.yaml,config/samples/providers-config.yaml,config/samples/proxy_kvm2.yaml,Makefile:73-75,kvm2_integration_test.go,fake_test.go. SHARED in play:validateKVMBlock,setBlocks,Config.validate,registry.Build,Constructor. Focus: validation gaps that let bad values reach chunks 4/5,Newbehaviour with a nil block or unreadable key, Secret mount perms, integration test gating/cleanup and whether the fake backend diverges from real libvirt in ways that hide bugs.