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
+13 -34
View File
@@ -95,7 +95,8 @@ func testConfig(hosts ...string) provider.ProviderConfig {
}
func newTestProvider(fakes hostFakes, hosts ...string) *Provider {
p := newWithDialer(testConfig(hosts...), fakes.dial, nil)
pc := testConfig(hosts...)
p := newWithDialer(pc.Name, *pc.KVM, fakes.dial, nil)
p.now = func() time.Time { return testNow }
return p
}
@@ -103,7 +104,7 @@ func newTestProvider(fakes hostFakes, hosts ...string) *Provider {
// cacheImage pre-populates the pool with the configured image and its
// marker so Create never touches the network.
func cacheImage(api *fakeAPI) {
src := imageSource{Key: "debian-13", URL: testImageURL, Format: formatQcow2}
src := imageSource{Key: "debian-13", KVMImage: provider.KVMImage{URL: testImageURL, Format: formatQcow2}}
api.putVol("default", src.volumeName(), formatQcow2, []byte("qcow2"))
api.putVol("default", src.markerName(), formatRaw, []byte(testImageSHA()))
}
@@ -241,7 +242,7 @@ func TestCreate_fetchesImageOnFirstUse(t *testing.T) {
api := newFakeAPI("default")
cfg := testConfig("nas1")
cfg.KVM.Images["debian-13"] = provider.KVMImage{URL: srv.URL + "/debian.qcow2", SHA256: testImageSHA()}
p := newWithDialer(cfg, hostFakes{"nas1": api}.dial, nil)
p := newWithDialer(cfg.Name, *cfg.KVM, hostFakes{"nas1": api}.dial, nil)
if _, err := p.Create(context.Background(), testRequest()); err != nil {
t.Fatalf("Create: %v", err)
@@ -479,28 +480,25 @@ func TestCreate_hostFailures(t *testing.T) {
t.Parallel()
cases := []struct {
name string
setup func() (hostFakes, string)
setup func() hostFakes
class error
}{
{
name: "host unreachable",
setup: func() (hostFakes, string) { return hostFakes{}, "nas1" },
setup: func() hostFakes { return hostFakes{} },
class: provider.ErrTransient,
},
{
name: "pool missing",
setup: func() (hostFakes, string) {
api := newFakeAPI("other")
return hostFakes{"nas1": api}, "nas1"
},
name: "pool missing",
setup: func() hostFakes { return hostFakes{"nas1": newFakeAPI("other")} },
class: provider.ErrPermanent,
},
{
name: "domain xml rejected",
setup: func() (hostFakes, string) {
setup: func() hostFakes {
api := readyHost()
api.fail["DomainDefineXML"] = lvError(libvirt.ErrConfigUnsupported, "no q35 here")
return hostFakes{"nas1": api}, "nas1"
return hostFakes{"nas1": api}
},
class: provider.ErrPermanent,
},
@@ -508,8 +506,7 @@ func TestCreate_hostFailures(t *testing.T) {
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
fakes, host := tc.setup()
p := newTestProvider(fakes, host)
p := newTestProvider(tc.setup(), "nas1")
_, err := p.Create(context.Background(), testRequest())
wantClass(t, err, tc.class)
})
@@ -747,9 +744,8 @@ func TestDelete_unconfiguredHostIsNotAnError(t *testing.T) {
}
// Get still refuses, so the live path cannot mistake a config typo for a
// missing VM and recreate it somewhere else.
if _, err := p.Get(context.Background(), "hosts/nas9/domains/"+testName); provider.Class(err) != provider.ErrPermanent {
t.Errorf("Get class = %v, want ErrPermanent", provider.Class(err))
}
_, err := p.Get(context.Background(), "hosts/nas9/domains/"+testName)
wantClass(t, err, provider.ErrPermanent)
}
func TestDelete_errors(t *testing.T) {
@@ -909,23 +905,6 @@ func TestMapState(t *testing.T) {
}
}
// A bad key path no longer fails the constructor: registry.Build aborts the
// whole provider set on a constructor error, so that took the manager down
// with every other provider. It degrades per-proxy instead — see
// TestNew_missingSSHMaterialStillBuilds and TestLazyAuth_healsWhenTheKeyAppears.
func TestNew_badKeyPathDefersToFirstUse(t *testing.T) {
t.Parallel()
cfg := testConfig("nas1")
cfg.KVM.SSHKeyFile = t.TempDir() + "/missing"
p, err := New(context.Background(), cfg)
if err != nil {
t.Fatalf("New() error = %v, want a provider that fails only when used", err)
}
if err := p.Delete(context.Background(), "hosts/nas1/domains/proxy-x"); provider.Class(err) != provider.ErrTransient {
t.Errorf("Delete class = %v, want ErrTransient", provider.Class(err))
}
}
// runAllOps drives one full lifecycle over a ready fake host so logging
// tests see every provider method and every libvirt call.
func runAllOps(t *testing.T, ctx context.Context, req provider.CreateRequest) {