package kvm2 import ( "errors" "strings" "testing" "time" "github.com/digitalocean/go-libvirt" "gitea.home.hrajfrisbee.cz/kacerr/egress-proxies-operator/internal/provider" ) func listIDs(list []provider.Instance) []string { ids := make([]string, len(list)) for i, inst := range list { ids[i] = inst.ID } return ids } func TestListByTag_excludesUnmarkedDomains(t *testing.T) { t.Parallel() fx := newFixture(t, 1) id := fx.create(testName) fx.fake.addDomain("legacy-vm", libvirt.DomainRunning, "") fx.fake.addDomain("unmarked", libvirt.DomainRunning, `falsex`) fx.fake.addDomain("unrelated", libvirt.DomainShutoff, `hand made`) list, err := fx.p.ListByTag(fx.ctx) if err != nil { t.Fatalf("ListByTag() error = %v", err) } if got := listIDs(list); len(got) != 1 || got[0] != id { t.Errorf("ListByTag = %v, want only %s", got, id) } if list[0].UID != testUID || list[0].CreatedAt.IsZero() { t.Errorf("listed = %+v, want uid and createdAt", list[0]) } if got := fx.logs.matching("kvm2 domains listed"); len(got) != 1 || !strings.Contains(got[0], `"hosts"=1`) || !strings.Contains(got[0], `"managed"=1`) { t.Errorf("listed line = %v", got) } } func TestListByTag_emptyUIDStillListed(t *testing.T) { t.Parallel() fx := newFixture(t, 1) fx.fake.addDomain("proxy-nouid", libvirt.DomainRunning, managedMetadata("", time.Now())) list, err := fx.p.ListByTag(fx.ctx) if err != nil { t.Fatalf("ListByTag() error = %v", err) } if len(list) != 1 || list[0].UID != "" || list[0].ID != formatProviderID(testHost, "proxy-nouid") { t.Errorf("ListByTag = %+v, want the domain listed with an empty UID", list) } } func TestListByTag_acrossHostsInConfigOrder(t *testing.T) { t.Parallel() cfg := twoHosts() cfg.Hosts[1].Pool = "" first, second := newFakeBackend(), newFakeBackend() first.addDomain("proxy-z", libvirt.DomainRunning, managedMetadata("uid-z", time.Now())) first.addDomain("proxy-a", libvirt.DomainShutoff, managedMetadata("uid-a", time.Now())) second.addDomain("proxy-b", libvirt.DomainRunning, managedMetadata("uid-b", time.Now())) p := newProvider(testProvider, cfg, &hostDialer{backends: map[string]backend{"beelink": first, "lab2": second}}) ctx, _ := captureContext() list, err := p.ListByTag(ctx) if err != nil { t.Fatalf("ListByTag() error = %v", err) } want := []string{formatProviderID("beelink", "proxy-a"), formatProviderID("beelink", "proxy-z"), formatProviderID("lab2", "proxy-b")} if got := listIDs(list); strings.Join(got, ",") != strings.Join(want, ",") { t.Errorf("ListByTag = %v, want %v (config order, then name order)", got, want) } if list[0].State != provider.StateStopped || list[1].State != provider.StateProvisioning { t.Errorf("states = %s, %s, want Stopped, Provisioning", list[0].State, list[1].State) } } func TestListByTag_unreachableHostFailsWhole(t *testing.T) { t.Parallel() first := newFakeBackend() first.addDomain("proxy-a", libvirt.DomainRunning, managedMetadata("uid-a", time.Now())) d := &hostDialer{ backends: map[string]backend{"beelink": first}, errs: map[string]error{"lab2": errors.New("dial tcp 192.0.2.2:22: i/o timeout")}, } p := newProvider(testProvider, twoHosts(), d) ctx, _ := captureContext() list, err := p.ListByTag(ctx) if list != nil { t.Errorf("ListByTag = %v, want nil with the error", list) } assertProviderError(t, err, "list", provider.ErrTransient) } func TestListByTag_unreadableDomainSkipped(t *testing.T) { t.Parallel() tests := []struct { name string setup func(fx *fixture) line string }{ { name: "corrupt metadata on one domain", setup: func(fx *fixture) { fx.fake.addDomain("proxy-bad", libvirt.DomainRunning, "not an instance") }, line: "skipping domain with unreadable metadata", }, { name: "metadata rpc fails for the first domain only", setup: func(fx *fixture) { fx.fake.addDomain("proxy-bad", libvirt.DomainRunning, managedMetadata("uid-bad", time.Now())) var calls int fx.fake.mu.Lock() fx.fake.hooks["DomainGetMetadata"] = func() error { calls++ if calls == 1 { return fakeErr(libvirt.ErrInternalError, "internal error: metadata read failed") } return nil } fx.fake.mu.Unlock() }, line: "skipping domain with unreadable metadata", }, { name: "state rpc fails for the first domain only", setup: func(fx *fixture) { fx.fake.addDomain("proxy-bad", libvirt.DomainRunning, managedMetadata("uid-bad", time.Now())) var calls int fx.fake.mu.Lock() fx.fake.hooks["DomainGetState"] = func() error { calls++ if calls == 1 { return fakeErr(libvirt.ErrNoDomain, "Domain not found") } return nil } fx.fake.mu.Unlock() }, line: "skipping domain that could not be inspected", }, } for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { t.Parallel() fx := newFixture(t, 1) fx.fake.addDomain("proxy-good", libvirt.DomainRunning, managedMetadata("uid-good", time.Now())) tc.setup(fx) list, err := fx.p.ListByTag(fx.ctx) if err != nil { t.Fatalf("ListByTag() error = %v", err) } if got := listIDs(list); len(got) != 1 || got[0] != formatProviderID(testHost, "proxy-good") { t.Errorf("ListByTag = %v, want only proxy-good", got) } if got := fx.logs.matching(tc.line); len(got) != 1 || !strings.Contains(got[0], `"name"="proxy-bad"`) { t.Errorf("skip line = %v, want one naming proxy-bad", got) } }) } } func TestListByTag_includesInactiveDomains(t *testing.T) { t.Parallel() fx := newFixture(t, 1) id := fx.create(testName) fx.withDomain(testName, func(d *fakeDomain) { d.state = libvirt.DomainShutoff }) list, err := fx.p.ListByTag(fx.ctx) if err != nil { t.Fatalf("ListByTag() error = %v", err) } if len(list) != 1 || list[0].ID != id || list[0].State != provider.StateStopped { t.Errorf("ListByTag = %+v, want the shut-off domain as Stopped", list) } }