Add KVM provider pure builders: domain XML, volumes, seed ISO, metadata
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>
This commit is contained in:
@@ -0,0 +1,79 @@
|
||||
package kvm
|
||||
|
||||
import (
|
||||
"encoding/xml"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestInstanceMetadata_roundTrip(t *testing.T) {
|
||||
t.Parallel()
|
||||
created := time.Date(2026, 9, 4, 12, 30, 0, 0, time.FixedZone("CEST", 2*3600))
|
||||
raw, err := xml.Marshal(newInstanceMetadata("uid-1", created))
|
||||
if err != nil {
|
||||
t.Fatalf("Marshal() error = %v", err)
|
||||
}
|
||||
if !strings.Contains(string(raw), `xmlns="`+metadataURI+`"`) {
|
||||
t.Errorf("marshalled metadata lacks the namespace declaration: %s", raw)
|
||||
}
|
||||
m, err := parseInstanceMetadata(string(raw))
|
||||
if err != nil {
|
||||
t.Fatalf("parseInstanceMetadata() error = %v", err)
|
||||
}
|
||||
if !m.managed() || m.UID != "uid-1" {
|
||||
t.Errorf("parsed = %+v, want managed with uid-1", m)
|
||||
}
|
||||
if got := m.createdAt(); !got.Equal(created) || got.Location() != time.UTC {
|
||||
t.Errorf("createdAt() = %v, want %v in UTC", got, created)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseInstanceMetadata_libvirtPrefixedForm(t *testing.T) {
|
||||
t.Parallel()
|
||||
// libvirt re-serialises the stored element with whatever prefix it
|
||||
// chooses; only the namespace URI is contractual.
|
||||
in := `<op:instance xmlns:op="` + metadataURI + `">` +
|
||||
`<op:managed>true</op:managed><op:uid>uid-2</op:uid>` +
|
||||
`<op:createdAt>2026-09-04T10:00:00Z</op:createdAt></op:instance>`
|
||||
m, err := parseInstanceMetadata(in)
|
||||
if err != nil {
|
||||
t.Fatalf("parseInstanceMetadata() error = %v", err)
|
||||
}
|
||||
if !m.managed() || m.UID != "uid-2" || m.createdAt().Hour() != 10 {
|
||||
t.Errorf("parsed = %+v", m)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseInstanceMetadata_notManaged(t *testing.T) {
|
||||
t.Parallel()
|
||||
tests := []struct {
|
||||
name string
|
||||
in string
|
||||
wantErr bool
|
||||
}{
|
||||
{"managed false", `<instance xmlns="` + metadataURI + `"><managed>false</managed><uid>u</uid></instance>`, false},
|
||||
{"foreign element", `<foo xmlns="urn:other"><managed>true</managed></foo>`, true},
|
||||
{"garbage", `not xml`, true},
|
||||
}
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
m, err := parseInstanceMetadata(tc.in)
|
||||
if (err != nil) != tc.wantErr {
|
||||
t.Fatalf("error = %v, wantErr %v", err, tc.wantErr)
|
||||
}
|
||||
if m.managed() {
|
||||
t.Errorf("managed() = true for %q", tc.in)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestInstanceMetadata_createdAtMalformedIsZero(t *testing.T) {
|
||||
t.Parallel()
|
||||
m := instanceMetadata{CreatedAt: "yesterday"}
|
||||
if !m.createdAt().IsZero() {
|
||||
t.Errorf("createdAt() = %v, want zero time", m.createdAt())
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user