Simplify the KVM provider after the /simplify review

Collapse duplicated logic the review found in PR #5 without changing
behaviour: one releaseFinalizer helper for the four finalizer exits in
reconcileDelete, classify reusing isClassified and provider.Class, the
image URL check and format/firmware values shared from the provider
package, imageSource and the metadata parser embedding their config
structs instead of copying fields, and the SSH loader as a small struct
that keeps only the SSH fields and is seeded by New instead of loading
the key twice. Tracing builds its V(2)-only lists only at V(2).

Tests: drop a duplicate New test, fold near-identical tests into tables
(connection drops, metadata parsing, cached images), add mustSSHAuth,
and use wantClass where classes were checked by hand.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-09-11 06:24:43 +00:00
co-authored by Claude Opus 5
parent 2b4b61cdc4
commit 82eeeef9da
13 changed files with 303 additions and 376 deletions
+25 -24
View File
@@ -26,10 +26,10 @@ const (
defaultDiskSizeGB = 10
defaultSocket = "/var/run/libvirt/libvirt-sock"
defaultPool = "default"
formatQcow2 = "qcow2"
formatRaw = "raw"
firmwareUEFI = "uefi"
firmwareBIOS = "bios"
formatQcow2 = provider.KVMFormatQcow2
formatRaw = provider.KVMFormatRaw
firmwareUEFI = provider.KVMFirmwareUEFI
firmwareBIOS = provider.KVMFirmwareBIOS
// undefineFlags removes everything libvirt may keep alongside a domain
// definition, so an undefine never fails on leftover state.
@@ -44,13 +44,7 @@ func withDefaults(cfg provider.KVMConfig) provider.KVMConfig {
}
images := make(map[string]provider.KVMImage, len(cfg.Images))
for k, img := range cfg.Images {
if img.Format == "" {
img.Format = formatQcow2
}
if img.Firmware == "" {
img.Firmware = firmwareUEFI
}
images[k] = img
images[k] = imageDefaults(img)
}
cfg.Images = images
hosts := make([]provider.KVMHost, len(cfg.Hosts))
@@ -76,34 +70,41 @@ type Provider struct {
now func() time.Time
}
func imageDefaults(img provider.KVMImage) provider.KVMImage {
if img.Format == "" {
img.Format = formatQcow2
}
if img.Firmware == "" {
img.Firmware = firmwareUEFI
}
return img
}
// New builds a Provider that reaches its hosts over SSH. The key and
// known_hosts files are read on the first connection, not here: they come
// from an optional Secret mount, and a constructor that failed without it
// would abort the whole provider set and crash-loop the manager, taking
// the kubernetes and gcp providers down with it. Unreadable files are
// still reported at startup, as a warning. Satisfies registry.Constructor.
// known_hosts files are tried once here and kept when usable, but a failure
// is only a warning, retried on the first connection: they come from an
// optional Secret mount, and a constructor that failed without it would
// abort the whole provider set and crash-loop the manager, taking the
// kubernetes and gcp providers down with it. Satisfies registry.Constructor.
func New(ctx context.Context, pc provider.ProviderConfig) (provider.Provider, error) {
cfg := provider.KVMConfig{}
if pc.KVM != nil {
cfg = *pc.KVM
}
if _, err := newSSHAuth(cfg); err != nil {
loader := newLazySSH(cfg)
if _, err := loader.load(); err != nil {
logf.FromContext(ctx).WithName("kvm").WithValues("provider", pc.Name).Info(
"WARNING: kvm ssh material is not usable; proxies on this provider will fail until it is",
"error", err.Error())
}
return newWithDialer(pc, lazyDialer(cfg), nil), nil
return newWithDialer(pc.Name, cfg, loader.dial, nil), nil
}
// newWithDialer is the test seam: the dialer decides what libvirtAPI each
// host resolves to, and the HTTP client is what image downloads go through.
func newWithDialer(pc provider.ProviderConfig, dial hostDialer, httpClient *http.Client) *Provider {
cfg := provider.KVMConfig{}
if pc.KVM != nil {
cfg = *pc.KVM
}
func newWithDialer(name string, cfg provider.KVMConfig, dial hostDialer, httpClient *http.Client) *Provider {
return &Provider{
name: pc.Name,
name: name,
cfg: withDefaults(cfg),
hosts: newHostPool(dial),
images: newImageCache(httpClient),