Shorter start wait with immediate verdicts for dead or paused domains, ListByTag without state or address RPCs, Timeout for an expired check round, no adoption of unmarked domains, reference-counted keyed locks, dead options removed, HEAD fallback on any non-2xx. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
214 lines
7.2 KiB
Go
214 lines
7.2 KiB
Go
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, `<instance><managed>false</managed><uid>x</uid></instance>`)
|
|
fx.fake.addDomain("unrelated", libvirt.DomainShutoff, `<instance><note>hand made</note></instance>`)
|
|
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].UID != "uid-a" || list[1].UID != "uid-z" || list[2].UID != "uid-b" {
|
|
t.Errorf("uids = %s, %s, %s, want uid-a, uid-z, uid-b", list[0].UID, list[1].UID, list[2].UID)
|
|
}
|
|
}
|
|
|
|
func TestListByTag_doesNotReadStateOrAddresses(t *testing.T) {
|
|
t.Parallel()
|
|
fx := newFixture(t, 2)
|
|
id := fx.create(testName)
|
|
fx.setAddrs(testName, libvirt.DomainInterfaceAddressesSrcAgent, "10.0.0.5")
|
|
fx.fake.addDomain("proxy-off", libvirt.DomainShutoff, managedMetadata("uid-off", time.Now()))
|
|
before := len(fx.fake.callLog())
|
|
|
|
list, err := fx.p.ListByTag(fx.ctx)
|
|
if err != nil {
|
|
t.Fatalf("ListByTag() error = %v", err)
|
|
}
|
|
if len(list) != 2 || list[0].ID != id || list[1].ID != formatProviderID(testHost, "proxy-off") {
|
|
t.Fatalf("ListByTag = %v, want the two managed domains", listIDs(list))
|
|
}
|
|
for _, inst := range list {
|
|
if inst.State != "" || inst.IP != "" {
|
|
t.Errorf("listed %s = state %q ip %q, want both empty: GC does not use them", inst.ID, inst.State, inst.IP)
|
|
}
|
|
if inst.UID == "" || inst.CreatedAt.IsZero() {
|
|
t.Errorf("listed %s lacks uid or createdAt: %+v", inst.ID, inst)
|
|
}
|
|
}
|
|
for _, rpc := range fx.fake.callLog()[before:] {
|
|
switch rpc {
|
|
case "ConnectListAllDomains", "DomainGetMetadata":
|
|
default:
|
|
t.Errorf("ListByTag made %s; want only the listing and one metadata read per domain", rpc)
|
|
}
|
|
}
|
|
if got := fx.logs.matching("kvm2 listed instance"); len(got) != 2 || strings.Contains(got[0], `"state"=`) {
|
|
t.Errorf("listed instance lines = %v, want two without a state", got)
|
|
}
|
|
}
|
|
|
|
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, "<bogus>not an instance</bogus>")
|
|
},
|
|
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: "domain gone between listing and metadata read",
|
|
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.ErrNoDomain, "Domain not found")
|
|
}
|
|
return nil
|
|
}
|
|
fx.fake.mu.Unlock()
|
|
},
|
|
line: "skipping domain with unreadable metadata",
|
|
},
|
|
}
|
|
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].UID != testUID {
|
|
t.Errorf("ListByTag = %+v, want the shut-off domain listed with its uid", list)
|
|
}
|
|
}
|