269 lines
8.0 KiB
Go
269 lines
8.0 KiB
Go
package kvm2
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"strings"
|
|
"sync"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/go-logr/logr/funcr"
|
|
logf "sigs.k8s.io/controller-runtime/pkg/log"
|
|
|
|
"gitea.home.hrajfrisbee.cz/kacerr/egress-proxies-operator/internal/provider"
|
|
)
|
|
|
|
// captureContext collects funcr output at V(2). The sink is locked because
|
|
// Check logs from one goroutine per host.
|
|
func captureContext() (context.Context, *[]string) {
|
|
lines := &[]string{}
|
|
var mu sync.Mutex
|
|
log := funcr.New(func(prefix, args string) {
|
|
mu.Lock()
|
|
defer mu.Unlock()
|
|
*lines = append(*lines, prefix+" "+args)
|
|
}, funcr.Options{Verbosity: 2})
|
|
return logf.IntoContext(context.Background(), log), lines
|
|
}
|
|
|
|
func twoHosts() provider.KVMConfig {
|
|
return provider.KVMConfig{
|
|
SSHKeyFile: "/nonexistent/kvm2/id_ed25519",
|
|
KnownHostsFile: "/nonexistent/kvm2/known_hosts",
|
|
MachineTypes: map[string]provider.KVMMachineType{"small": {VCPUs: 1, MemoryMiB: 1024}},
|
|
Hosts: []provider.KVMHost{
|
|
{Name: "beelink", Address: "beelink:22", User: "virt", Bridge: "br0"},
|
|
{Name: "lab2", Address: "lab2:22", User: "virt", Bridge: "br1", Socket: "/run/s", Pool: "fast"},
|
|
},
|
|
}
|
|
}
|
|
|
|
func TestNew_doesNotDialAndWarnsOnMissingFiles(t *testing.T) {
|
|
t.Parallel()
|
|
ctx, lines := captureContext()
|
|
cfg := twoHosts()
|
|
cfg.Hosts[0].Address = "192.0.2.1:22"
|
|
start := time.Now()
|
|
p, err := New(ctx, provider.ProviderConfig{Name: "kvm2-home", Type: "kvm2", KVM2: &cfg})
|
|
elapsed := time.Since(start)
|
|
if err != nil {
|
|
t.Fatalf("New() error = %v, want nil", err)
|
|
}
|
|
if p == nil {
|
|
t.Fatal("New() = nil provider")
|
|
}
|
|
if elapsed > 100*time.Millisecond {
|
|
t.Errorf("New() took %v, want under 100ms (it must not dial)", elapsed)
|
|
}
|
|
var warnings int
|
|
for _, l := range *lines {
|
|
if strings.Contains(l, "WARNING:") {
|
|
warnings++
|
|
}
|
|
if !strings.Contains(l, `"provider"="kvm2-home"`) {
|
|
t.Errorf("log line lacks the provider value: %s", l)
|
|
}
|
|
}
|
|
if warnings != 2 {
|
|
t.Errorf("got %d WARNING: lines, want 2 (key and known_hosts):\n%s", warnings, strings.Join(*lines, "\n"))
|
|
}
|
|
}
|
|
|
|
func TestNew_insecureIgnoreHostKeyWarns(t *testing.T) {
|
|
t.Parallel()
|
|
ctx, lines := captureContext()
|
|
cfg := twoHosts()
|
|
cfg.KnownHostsFile = ""
|
|
cfg.InsecureIgnoreHostKey = true
|
|
if _, err := New(ctx, provider.ProviderConfig{Name: "kvm2-home", Type: "kvm2", KVM2: &cfg}); err != nil {
|
|
t.Fatalf("New() error = %v, want nil", err)
|
|
}
|
|
found := false
|
|
for _, l := range *lines {
|
|
if strings.Contains(l, "WARNING:") && strings.Contains(l, "host keys are not verified") {
|
|
found = true
|
|
}
|
|
if strings.Contains(l, "known_hosts") {
|
|
t.Errorf("known_hosts warning emitted although host keys are ignored: %s", l)
|
|
}
|
|
}
|
|
if !found {
|
|
t.Errorf("no insecureIgnoreHostKey warning in:\n%s", strings.Join(*lines, "\n"))
|
|
}
|
|
}
|
|
|
|
func TestNew_nilBlockDoesNotPanic(t *testing.T) {
|
|
t.Parallel()
|
|
ctx, _ := captureContext()
|
|
p, err := New(ctx, provider.ProviderConfig{Name: "kvm2-empty", Type: "kvm2"})
|
|
if err != nil {
|
|
t.Fatalf("New() error = %v, want nil", err)
|
|
}
|
|
kp, ok := p.(*Provider)
|
|
if !ok {
|
|
t.Fatalf("New() = %T, want *Provider", p)
|
|
}
|
|
if kp.name != "kvm2-empty" || kp.cfg.DiskSizeGB != defaultDiskSizeGB || kp.now == nil {
|
|
t.Errorf("provider = %+v, want name kvm2-empty, default disk size and a clock", kp)
|
|
}
|
|
}
|
|
|
|
func TestHost_table(t *testing.T) {
|
|
t.Parallel()
|
|
tests := []struct {
|
|
name string
|
|
hosts []provider.KVMHost
|
|
zone string
|
|
wantName string
|
|
wantErr string
|
|
}{
|
|
{
|
|
name: "one host and empty zone selects it",
|
|
hosts: twoHosts().Hosts[:1],
|
|
zone: "",
|
|
wantName: "beelink",
|
|
},
|
|
{
|
|
name: "several hosts and empty zone is permanent",
|
|
hosts: twoHosts().Hosts,
|
|
zone: "",
|
|
wantErr: "placement.zone is required when kvm2.hosts lists more than one host",
|
|
},
|
|
{
|
|
name: "known name",
|
|
hosts: twoHosts().Hosts,
|
|
zone: "lab2",
|
|
wantName: "lab2",
|
|
},
|
|
{
|
|
name: "unknown name is permanent",
|
|
hosts: twoHosts().Hosts,
|
|
zone: "nas9",
|
|
wantErr: `host "nas9" is not in kvm2.hosts`,
|
|
},
|
|
{
|
|
name: "no hosts at all is permanent",
|
|
hosts: nil,
|
|
zone: "",
|
|
wantErr: "placement.zone is required",
|
|
},
|
|
}
|
|
for _, tc := range tests {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
t.Parallel()
|
|
p := newProvider("kvm2-home", provider.KVMConfig{Hosts: tc.hosts}, &stubDialer{})
|
|
h, err := p.host(tc.zone)
|
|
if tc.wantErr != "" {
|
|
if err == nil {
|
|
t.Fatalf("host(%q) = %+v, want error containing %q", tc.zone, h, tc.wantErr)
|
|
}
|
|
if !errors.Is(err, provider.ErrPermanent) {
|
|
t.Errorf("host(%q) error = %v, want ErrPermanent", tc.zone, err)
|
|
}
|
|
if !strings.Contains(err.Error(), tc.wantErr) {
|
|
t.Errorf("host(%q) error = %q, want substring %q", tc.zone, err, tc.wantErr)
|
|
}
|
|
return
|
|
}
|
|
if err != nil {
|
|
t.Fatalf("host(%q) error = %v, want nil", tc.zone, err)
|
|
}
|
|
if h.Name != tc.wantName {
|
|
t.Errorf("host(%q).Name = %q, want %q", tc.zone, h.Name, tc.wantName)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestWithDefaults_table(t *testing.T) {
|
|
t.Parallel()
|
|
tests := []struct {
|
|
name string
|
|
in provider.KVMConfig
|
|
check func(t *testing.T, got provider.KVMConfig)
|
|
}{
|
|
{
|
|
name: "empty config gets disk size and empty maps",
|
|
in: provider.KVMConfig{},
|
|
check: func(t *testing.T, got provider.KVMConfig) {
|
|
if got.DiskSizeGB != 10 {
|
|
t.Errorf("DiskSizeGB = %d, want 10", got.DiskSizeGB)
|
|
}
|
|
if got.Hosts == nil || got.Images == nil {
|
|
t.Errorf("Hosts = %v, Images = %v, want non-nil", got.Hosts, got.Images)
|
|
}
|
|
},
|
|
},
|
|
{
|
|
name: "explicit disk size kept",
|
|
in: provider.KVMConfig{DiskSizeGB: 40},
|
|
check: func(t *testing.T, got provider.KVMConfig) {
|
|
if got.DiskSizeGB != 40 {
|
|
t.Errorf("DiskSizeGB = %d, want 40", got.DiskSizeGB)
|
|
}
|
|
},
|
|
},
|
|
{
|
|
name: "host socket and pool default",
|
|
in: provider.KVMConfig{Hosts: []provider.KVMHost{{Name: "a"}, {Name: "b", Socket: "/run/s", Pool: "fast"}}},
|
|
check: func(t *testing.T, got provider.KVMConfig) {
|
|
if got.Hosts[0].Socket != "/var/run/libvirt/libvirt-sock" || got.Hosts[0].Pool != "default" {
|
|
t.Errorf("hosts[0] = %+v, want default socket and pool", got.Hosts[0])
|
|
}
|
|
if got.Hosts[1].Socket != "/run/s" || got.Hosts[1].Pool != "fast" {
|
|
t.Errorf("hosts[1] = %+v, want explicit socket and pool kept", got.Hosts[1])
|
|
}
|
|
},
|
|
},
|
|
{
|
|
name: "image format and firmware default",
|
|
in: provider.KVMConfig{Images: map[string]provider.KVMImage{
|
|
"d": {URL: "https://x/d.qcow2"},
|
|
"u": {URL: "https://x/u.img", Format: provider.KVMFormatRaw, Firmware: provider.KVMFirmwareBIOS},
|
|
}},
|
|
check: func(t *testing.T, got provider.KVMConfig) {
|
|
if got.Images["d"].Format != provider.KVMFormatQcow2 || got.Images["d"].Firmware != provider.KVMFirmwareUEFI {
|
|
t.Errorf("images[d] = %+v, want qcow2 and uefi", got.Images["d"])
|
|
}
|
|
if got.Images["u"].Format != provider.KVMFormatRaw || got.Images["u"].Firmware != provider.KVMFirmwareBIOS {
|
|
t.Errorf("images[u] = %+v, want raw and bios kept", got.Images["u"])
|
|
}
|
|
},
|
|
},
|
|
}
|
|
for _, tc := range tests {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
t.Parallel()
|
|
tc.check(t, withDefaults(tc.in))
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestWithDefaults_doesNotAliasInput(t *testing.T) {
|
|
t.Parallel()
|
|
in := provider.KVMConfig{
|
|
Hosts: []provider.KVMHost{{Name: "a"}},
|
|
Images: map[string]provider.KVMImage{"d": {URL: "https://x/d.qcow2"}},
|
|
}
|
|
got := withDefaults(in)
|
|
got.Hosts[0].Name = "changed"
|
|
got.Images["d"] = provider.KVMImage{URL: "changed"}
|
|
if in.Hosts[0].Name != "a" || in.Images["d"].URL != "https://x/d.qcow2" {
|
|
t.Errorf("input mutated through the defaulted copy: %+v", in)
|
|
}
|
|
}
|
|
|
|
func TestWrapErr_carriesProviderNameOpAndID(t *testing.T) {
|
|
t.Parallel()
|
|
p := newProvider("kvm2-home", twoHosts(), &stubDialer{})
|
|
err := p.wrapErr("get", "hosts/beelink/domains/proxy-x", permanentf("boom"))
|
|
var pe *provider.Error
|
|
if !errors.As(err, &pe) {
|
|
t.Fatalf("wrapErr() = %T, want *provider.Error", err)
|
|
}
|
|
if pe.Provider != "kvm2-home" || pe.Op != "get" || pe.ID != "hosts/beelink/domains/proxy-x" || pe.Class != provider.ErrPermanent {
|
|
t.Errorf("wrapErr() = %+v, want provider kvm2-home, op get, the id and ErrPermanent", pe)
|
|
}
|
|
}
|