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
+54 -68
View File
@@ -37,12 +37,11 @@ type imageServer struct {
status int
noLength bool
delay time.Duration
body []byte
}
func newImageServer(t *testing.T) *imageServer {
t.Helper()
s := &imageServer{status: http.StatusOK, body: testImage}
s := &imageServer{status: http.StatusOK}
s.Server = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
s.hits.Add(1)
time.Sleep(s.delay)
@@ -55,16 +54,16 @@ func newImageServer(t *testing.T) *imageServer {
if s.noLength {
w.(http.Flusher).Flush()
} else {
w.Header().Set("Content-Length", strconv.Itoa(len(s.body)))
w.Header().Set("Content-Length", strconv.Itoa(len(testImage)))
}
_, _ = w.Write(s.body)
_, _ = w.Write(testImage)
}))
t.Cleanup(s.Close)
return s
}
func testSource(srv *imageServer, sha string) imageSource {
return imageSource{Key: "debian-13", URL: srv.URL + "/debian.qcow2", SHA256: sha, Format: formatQcow2}
return imageSource{Key: "debian-13", KVMImage: provider.KVMImage{URL: srv.URL + "/debian.qcow2", SHA256: sha, Format: formatQcow2}}
}
func ensureTest(t *testing.T, api *fakeAPI, src imageSource) (string, error) {
@@ -88,9 +87,9 @@ func TestResolveImage(t *testing.T) {
want imageSource
wantErr bool
}{
{"configured key", "debian-13", imageSource{Key: "debian-13", URL: "https://cloud.debian.org/d.qcow2", SHA256: "abc", Format: "qcow2", Firmware: "uefi"}, false},
{"direct https url", "https://x.example/img.qcow2", imageSource{URL: "https://x.example/img.qcow2", Format: "qcow2", Firmware: "uefi"}, false},
{"direct http url", "http://mirror/img.qcow2", imageSource{URL: "http://mirror/img.qcow2", Format: "qcow2", Firmware: "uefi"}, false},
{"configured key", "debian-13", imageSource{Key: "debian-13", KVMImage: provider.KVMImage{URL: "https://cloud.debian.org/d.qcow2", SHA256: "abc", Format: "qcow2", Firmware: "uefi"}}, false},
{"direct https url", "https://x.example/img.qcow2", imageSource{KVMImage: provider.KVMImage{URL: "https://x.example/img.qcow2", Format: "qcow2", Firmware: "uefi"}}, false},
{"direct http url", "http://mirror/img.qcow2", imageSource{KVMImage: provider.KVMImage{URL: "http://mirror/img.qcow2", Format: "qcow2", Firmware: "uefi"}}, false},
{"unknown key", "ubuntu", imageSource{}, true},
{"file url", "file:///tmp/x.qcow2", imageSource{}, true},
{"empty", "", imageSource{}, true},
@@ -100,9 +99,7 @@ func TestResolveImage(t *testing.T) {
t.Parallel()
got, err := resolveImage(cfg, tc.image)
if tc.wantErr {
if !errors.Is(err, provider.ErrPermanent) {
t.Fatalf("resolveImage(%q) error = %v, want ErrPermanent", tc.image, err)
}
wantClass(t, err, provider.ErrPermanent)
return
}
if err != nil {
@@ -117,16 +114,16 @@ func TestResolveImage(t *testing.T) {
func TestImageSource_volumeNames(t *testing.T) {
t.Parallel()
keyed := imageSource{Key: "debian-13", URL: "https://a/b.qcow2", Format: "qcow2"}
keyed := imageSource{Key: "debian-13", KVMImage: provider.KVMImage{URL: "https://a/b.qcow2", Format: "qcow2"}}
if keyed.volumeName() != "base-debian-13.qcow2" || keyed.markerName() != "base-debian-13.qcow2.sha256" {
t.Errorf("keyed names = %s / %s", keyed.volumeName(), keyed.markerName())
}
direct := imageSource{URL: "https://a/b.qcow2", Format: "qcow2"}
direct := imageSource{KVMImage: provider.KVMImage{URL: "https://a/b.qcow2", Format: "qcow2"}}
name := direct.volumeName()
if !strings.HasPrefix(name, "base-") || !strings.HasSuffix(name, ".qcow2") || len(name) != len("base-")+16+len(".qcow2") {
t.Errorf("direct-url name = %q, want base-<16 hex>.qcow2", name)
}
other := imageSource{URL: "https://a/c.qcow2", Format: "qcow2"}
other := imageSource{KVMImage: provider.KVMImage{URL: "https://a/c.qcow2", Format: "qcow2"}}
if other.volumeName() == name {
t.Error("different URLs produced the same volume name")
}
@@ -181,57 +178,47 @@ func TestImageCache_ensure_noChecksumConfigured(t *testing.T) {
}
}
func TestImageCache_ensure_alreadyCached(t *testing.T) {
func TestImageCache_ensure_existingVolumes(t *testing.T) {
t.Parallel()
srv := newImageServer(t)
api := newFakeAPI("default")
api.putVol("default", "base-debian-13.qcow2", "qcow2", testImage)
api.putVol("default", "base-debian-13.qcow2.sha256", "raw", []byte(testImageSHA()))
path, err := ensureTest(t, api, testSource(srv, testImageSHA()))
if err != nil {
t.Fatalf("ensure() error = %v", err)
tests := []struct {
name string
base []byte
marker []byte
wantHits int32
}{
{"already cached", testImage, []byte(testImageSHA()), 0},
{"marker missing is a partial upload", []byte("half an image"), nil, 1},
{"configured checksum changed", []byte("an older release"), []byte(strings.Repeat("0", 64)), 1},
}
if path != "/var/lib/libvirt/images/base-debian-13.qcow2" {
t.Errorf("path = %q", path)
}
if srv.hits.Load() != 0 {
t.Errorf("http hits = %d, want 0 for a cached image", srv.hits.Load())
}
if api.hasCall("StorageVolCreateXML") || api.hasCall("StorageVolDelete") {
t.Errorf("cached image was touched: calls = %v", api.calls)
}
}
func TestImageCache_ensure_markerMissingRedownloads(t *testing.T) {
t.Parallel()
srv := newImageServer(t)
api := newFakeAPI("default")
api.putVol("default", "base-debian-13.qcow2", "qcow2", []byte("half an image"))
if _, err := ensureTest(t, api, testSource(srv, testImageSHA())); err != nil {
t.Fatalf("ensure() error = %v", err)
}
if srv.hits.Load() != 1 {
t.Errorf("http hits = %d, want 1: a base without its marker is a partial upload", srv.hits.Load())
}
if base := api.vol("default", "base-debian-13.qcow2"); base == nil || !bytes.Equal(base.data, testImage) {
t.Error("partial base volume was not replaced")
}
}
func TestImageCache_ensure_checksumChangedRedownloads(t *testing.T) {
t.Parallel()
srv := newImageServer(t)
api := newFakeAPI("default")
api.putVol("default", "base-debian-13.qcow2", "qcow2", []byte("an older release"))
api.putVol("default", "base-debian-13.qcow2.sha256", "raw", []byte(strings.Repeat("0", 64)))
if _, err := ensureTest(t, api, testSource(srv, testImageSHA())); err != nil {
t.Fatalf("ensure() error = %v", err)
}
if srv.hits.Load() != 1 {
t.Errorf("http hits = %d, want 1 after the configured checksum changed", srv.hits.Load())
}
if marker := api.vol("default", "base-debian-13.qcow2.sha256"); marker == nil || string(marker.data) != testImageSHA() {
t.Errorf("marker not updated: %+v", marker)
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
srv := newImageServer(t)
api := newFakeAPI("default")
api.putVol("default", "base-debian-13.qcow2", "qcow2", tc.base)
if tc.marker != nil {
api.putVol("default", "base-debian-13.qcow2.sha256", "raw", tc.marker)
}
path, err := ensureTest(t, api, testSource(srv, testImageSHA()))
if err != nil {
t.Fatalf("ensure() error = %v", err)
}
if path != "/var/lib/libvirt/images/base-debian-13.qcow2" {
t.Errorf("path = %q", path)
}
if srv.hits.Load() != tc.wantHits {
t.Errorf("http hits = %d, want %d", srv.hits.Load(), tc.wantHits)
}
if touched := api.hasCall("StorageVolCreateXML") || api.hasCall("StorageVolDelete"); touched != (tc.wantHits > 0) {
t.Errorf("volumes touched = %v, want %v: calls = %v", touched, tc.wantHits > 0, api.calls)
}
if base := api.vol("default", "base-debian-13.qcow2"); base == nil || !bytes.Equal(base.data, testImage) {
t.Error("base volume does not hold the verified image")
}
if marker := api.vol("default", "base-debian-13.qcow2.sha256"); marker == nil || string(marker.data) != testImageSHA() {
t.Errorf("marker = %+v, want the verified digest", marker)
}
})
}
}
@@ -240,9 +227,7 @@ func TestImageCache_ensure_checksumMismatchIsPermanent(t *testing.T) {
srv := newImageServer(t)
api := newFakeAPI("default")
_, err := ensureTest(t, api, testSource(srv, strings.Repeat("a", 64)))
if !errors.Is(err, provider.ErrPermanent) {
t.Fatalf("ensure() error = %v, want ErrPermanent", err)
}
wantClass(t, err, provider.ErrPermanent)
if names := api.volNames("default"); len(names) != 0 {
t.Errorf("volumes left behind after a checksum mismatch: %v", names)
}
@@ -283,8 +268,9 @@ func TestImageCache_ensure_noContentLengthIsPermanent(t *testing.T) {
srv.noLength = true
api := newFakeAPI("default")
_, err := ensureTest(t, api, testSource(srv, ""))
if !errors.Is(err, provider.ErrPermanent) || !strings.Contains(err.Error(), "Content-Length") {
t.Fatalf("ensure() error = %v, want ErrPermanent about Content-Length", err)
wantClass(t, err, provider.ErrPermanent)
if !strings.Contains(err.Error(), "Content-Length") {
t.Errorf("ensure() error = %v, want one about Content-Length", err)
}
}