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>
80 lines
2.3 KiB
Go
80 lines
2.3 KiB
Go
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())
|
|
}
|
|
}
|