Introduces internal/provider/kvm with the side-effect-free pieces of the provider: providerID format/parse, the namespaced <metadata> element that carries the GC contract, q35 domain XML over pool volumes, volume XML for overlays and raw seeds, the NoCloud seed ISO built with kdomanski/iso9660, and config defaults. All unit-tested. Step 2 of docs/plans/2026-09-04-1404-kvm-provider.md. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
92 lines
2.5 KiB
Go
92 lines
2.5 KiB
Go
package kvm
|
|
|
|
import (
|
|
"bytes"
|
|
"io"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/kdomanski/iso9660"
|
|
)
|
|
|
|
func readSeed(t *testing.T, iso []byte) (label string, files map[string]string) {
|
|
t.Helper()
|
|
img, err := iso9660.OpenImage(bytes.NewReader(iso))
|
|
if err != nil {
|
|
t.Fatalf("OpenImage() error = %v", err)
|
|
}
|
|
label, err = img.Label()
|
|
if err != nil {
|
|
t.Fatalf("Label() error = %v", err)
|
|
}
|
|
root, err := img.RootDir()
|
|
if err != nil {
|
|
t.Fatalf("RootDir() error = %v", err)
|
|
}
|
|
children, err := root.GetChildren()
|
|
if err != nil {
|
|
t.Fatalf("GetChildren() error = %v", err)
|
|
}
|
|
files = make(map[string]string, len(children))
|
|
for _, c := range children {
|
|
body, err := io.ReadAll(c.Reader())
|
|
if err != nil {
|
|
t.Fatalf("reading %s: %v", c.Name(), err)
|
|
}
|
|
files[c.Name()] = string(body)
|
|
}
|
|
return label, files
|
|
}
|
|
|
|
func TestBuildSeedISO_contents(t *testing.T) {
|
|
t.Parallel()
|
|
userData := "#cloud-config\npackages: [squid]\n"
|
|
iso, err := buildSeedISO("proxy-abc123def456ghij", userData, defaultVendorData)
|
|
if err != nil {
|
|
t.Fatalf("buildSeedISO() error = %v", err)
|
|
}
|
|
label, files := readSeed(t, iso)
|
|
if !strings.EqualFold(label, seedLabel) {
|
|
t.Errorf("label = %q, want %q", label, seedLabel)
|
|
}
|
|
if len(files) != 3 {
|
|
t.Errorf("files = %v, want exactly user-data, meta-data, vendor-data", files)
|
|
}
|
|
if files["user-data"] != userData {
|
|
t.Errorf("user-data = %q, want verbatim cloud-init", files["user-data"])
|
|
}
|
|
if files["vendor-data"] != defaultVendorData {
|
|
t.Errorf("vendor-data = %q", files["vendor-data"])
|
|
}
|
|
if got, want := files["meta-data"], "instance-id: proxy-abc123def456ghij\nlocal-hostname: proxy-abc123def456ghij\n"; got != want {
|
|
t.Errorf("meta-data = %q, want %q", got, want)
|
|
}
|
|
}
|
|
|
|
func TestBuildSeedISO_deterministic(t *testing.T) {
|
|
t.Parallel()
|
|
a, err := buildSeedISO("proxy-x", "#cloud-config\n", "")
|
|
if err != nil {
|
|
t.Fatalf("first buildSeedISO() error = %v", err)
|
|
}
|
|
b, err := buildSeedISO("proxy-x", "#cloud-config\n", "")
|
|
if err != nil {
|
|
t.Fatalf("second buildSeedISO() error = %v", err)
|
|
}
|
|
if !bytes.Equal(a, b) {
|
|
t.Error("two builds for the same inputs differ; a re-created seed must be byte-identical")
|
|
}
|
|
}
|
|
|
|
func TestBuildSeedISO_emptyUserData(t *testing.T) {
|
|
t.Parallel()
|
|
iso, err := buildSeedISO("proxy-x", "", defaultVendorData)
|
|
if err != nil {
|
|
t.Fatalf("buildSeedISO() error = %v", err)
|
|
}
|
|
_, files := readSeed(t, iso)
|
|
if body, ok := files["user-data"]; !ok || body != "" {
|
|
t.Errorf("user-data = %q (present=%v), want an empty file to still exist", body, ok)
|
|
}
|
|
}
|