package kvm2 import ( "encoding/xml" "errors" "regexp" "testing" "gitea.home.hrajfrisbee.cz/kacerr/egress-proxies-operator/internal/provider" ) type checkedVolume struct { Type string `xml:"type,attr"` Name string `xml:"name"` Capacity struct { Unit string `xml:"unit,attr"` Value int64 `xml:",chardata"` } `xml:"capacity"` Target struct { Format struct { Type string `xml:"type,attr"` } `xml:"format"` } `xml:"target"` BackingStores []struct { Path string `xml:"path"` Format struct { Type string `xml:"type,attr"` } `xml:"format"` } `xml:"backingStore"` } func mustBuildVolume(t *testing.T, v volumeSpec) checkedVolume { t.Helper() out, err := buildVolumeXML(v) if err != nil { t.Fatalf("buildVolumeXML: %v", err) } var c checkedVolume if err := xml.Unmarshal([]byte(out), &c); err != nil { t.Fatalf("unmarshalling produced XML: %v\n%s", err, out) } return c } func TestVolumeXML_overlayBackingStore(t *testing.T) { t.Parallel() c := mustBuildVolume(t, volumeSpec{ Name: "proxy-abc.qcow2", CapacityBytes: 10 << 30, Format: provider.KVMFormatQcow2, BackingPath: "/var/lib/libvirt/images/base-debian-13.qcow2", BackingFormat: provider.KVMFormatQcow2, }) if c.Type != "file" || c.Name != "proxy-abc.qcow2" { t.Errorf("type=%q name=%q, want file/proxy-abc.qcow2", c.Type, c.Name) } if c.Capacity.Unit != "bytes" || c.Capacity.Value != 10<<30 { t.Errorf("capacity = %d %s, want %d bytes", c.Capacity.Value, c.Capacity.Unit, 10<<30) } if c.Target.Format.Type != "qcow2" { t.Errorf("target format = %q, want qcow2", c.Target.Format.Type) } if len(c.BackingStores) != 1 { t.Fatalf("got %d backingStore elements, want 1", len(c.BackingStores)) } bs := c.BackingStores[0] if bs.Path != "/var/lib/libvirt/images/base-debian-13.qcow2" || bs.Format.Type != "qcow2" { t.Errorf("backingStore = %+v, want the base path with qcow2 format", bs) } } func TestVolumeXML_rawWithoutBacking(t *testing.T) { t.Parallel() c := mustBuildVolume(t, volumeSpec{Name: "proxy-abc-seed.iso", CapacityBytes: 385024, Format: provider.KVMFormatRaw}) if c.Target.Format.Type != "raw" { t.Errorf("target format = %q, want raw", c.Target.Format.Type) } if c.Capacity.Value != 385024 || c.Capacity.Unit != "bytes" { t.Errorf("capacity = %d %s, want 385024 bytes", c.Capacity.Value, c.Capacity.Unit) } if len(c.BackingStores) != 0 { t.Errorf("got backingStore %+v, want none", c.BackingStores) } } func TestVolumeXML_rejectsBadFormat(t *testing.T) { t.Parallel() tests := []struct { name string spec volumeSpec }{ {name: "vmdk format", spec: volumeSpec{Name: "x", CapacityBytes: 1, Format: "vmdk"}}, {name: "empty format", spec: volumeSpec{Name: "x", CapacityBytes: 1}}, {name: "bad backing format", spec: volumeSpec{Name: "x", CapacityBytes: 1, Format: "qcow2", BackingPath: "/b", BackingFormat: "vdi"}}, {name: "backing path without format", spec: volumeSpec{Name: "x", CapacityBytes: 1, Format: "qcow2", BackingPath: "/b"}}, {name: "empty name", spec: volumeSpec{CapacityBytes: 1, Format: "qcow2"}}, {name: "zero capacity", spec: volumeSpec{Name: "x", Format: "qcow2"}}, } for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { t.Parallel() out, err := buildVolumeXML(tc.spec) if !errors.Is(err, provider.ErrPermanent) { t.Errorf("err = %v, want ErrPermanent", err) } if out != "" { t.Errorf("got XML %q alongside the error", out) } }) } } func TestVolumeNames(t *testing.T) { t.Parallel() tests := []struct { name string got string want string }{ {name: "overlay", got: overlayVolumeName("proxy-abc"), want: "proxy-abc.qcow2"}, {name: "seed", got: seedVolumeName("proxy-abc"), want: "proxy-abc-seed.iso"}, {name: "base qcow2", got: baseVolumeName("debian-13", provider.KVMFormatQcow2), want: "base-debian-13.qcow2"}, {name: "base raw", got: baseVolumeName("alpine", provider.KVMFormatRaw), want: "base-alpine.raw"}, {name: "marker", got: markerVolumeName("base-debian-13.qcow2"), want: "base-debian-13.qcow2.sha256"}, {name: "marker of url base", got: markerVolumeName(baseVolumeName(urlImageKey("https://x/y.img"), "qcow2")), want: "base-" + urlImageKey("https://x/y.img") + ".qcow2.sha256"}, } for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { t.Parallel() if tc.got != tc.want { t.Errorf("got %q, want %q", tc.got, tc.want) } }) } t.Run("url key", func(t *testing.T) { t.Parallel() hex16 := regexp.MustCompile(`^[0-9a-f]{16}$`) a := urlImageKey("https://cloud.debian.org/images/cloud/trixie/latest/debian-13-generic-amd64.qcow2") b := urlImageKey("https://cloud.debian.org/images/cloud/trixie/latest/debian-13-generic-amd64.qcow2") c := urlImageKey("https://cloud.debian.org/images/cloud/bookworm/latest/debian-12-generic-amd64.qcow2") if !hex16.MatchString(a) { t.Errorf("urlImageKey = %q, want 16 lowercase hex chars", a) } if a != b { t.Errorf("urlImageKey not deterministic: %q vs %q", a, b) } if a == c { t.Errorf("urlImageKey gives %q for two different URLs", a) } }) }