The previous check tried to bind 127.0.0.1:42069 and warned on 'address already in use' — which is exactly the state when the banger daemon is running, the case the user ran 'doctor' to confirm. The warning was actively misleading. Now, on 'address already in use', probe the listener with a *.vm DNS query that only banger's vmdns server answers authoritatively (NXDOMAIN with Authoritative=true). If the shape matches we pass; if the port is held by something else we still warn. Tests cover both branches: a real vmdns server is accepted, and a silent UDP listener on the same port is rejected. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
188 lines
5.5 KiB
Go
188 lines
5.5 KiB
Go
package daemon
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"net"
|
|
"reflect"
|
|
"testing"
|
|
|
|
"banger/internal/firecracker"
|
|
"banger/internal/guestconfig"
|
|
"banger/internal/model"
|
|
"banger/internal/system"
|
|
"banger/internal/vmdns"
|
|
)
|
|
|
|
type testCapability struct {
|
|
name string
|
|
prepare func(context.Context, *model.VMRecord, model.Image) error
|
|
cleanup func(context.Context, model.VMRecord) error
|
|
contribute func(*guestconfig.Builder, model.VMRecord, model.Image)
|
|
contributeFC func(*firecracker.MachineConfig, model.VMRecord, model.Image)
|
|
configChange func(context.Context, model.VMRecord, model.VMRecord) error
|
|
doctor func(context.Context, *system.Report)
|
|
startPreflight func(context.Context, *system.Preflight, model.VMRecord, model.Image)
|
|
}
|
|
|
|
func (c testCapability) Name() string { return c.name }
|
|
|
|
func (c testCapability) PrepareHost(ctx context.Context, vm *model.VMRecord, image model.Image) error {
|
|
if c.prepare != nil {
|
|
return c.prepare(ctx, vm, image)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (c testCapability) Cleanup(ctx context.Context, vm model.VMRecord) error {
|
|
if c.cleanup != nil {
|
|
return c.cleanup(ctx, vm)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (c testCapability) ContributeGuest(builder *guestconfig.Builder, vm model.VMRecord, image model.Image) {
|
|
if c.contribute != nil {
|
|
c.contribute(builder, vm, image)
|
|
}
|
|
}
|
|
|
|
func (c testCapability) ContributeMachine(cfg *firecracker.MachineConfig, vm model.VMRecord, image model.Image) {
|
|
if c.contributeFC != nil {
|
|
c.contributeFC(cfg, vm, image)
|
|
}
|
|
}
|
|
|
|
func (c testCapability) ApplyConfigChange(ctx context.Context, before, after model.VMRecord) error {
|
|
if c.configChange != nil {
|
|
return c.configChange(ctx, before, after)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (c testCapability) AddDoctorChecks(ctx context.Context, report *system.Report) {
|
|
if c.doctor != nil {
|
|
c.doctor(ctx, report)
|
|
}
|
|
}
|
|
|
|
func (c testCapability) AddStartPreflight(ctx context.Context, checks *system.Preflight, vm model.VMRecord, image model.Image) {
|
|
if c.startPreflight != nil {
|
|
c.startPreflight(ctx, checks, vm, image)
|
|
}
|
|
}
|
|
|
|
func TestPrepareCapabilityHostsRollsBackPreparedCapabilitiesInReverseOrder(t *testing.T) {
|
|
vm := testVM("devbox", "image", "172.16.0.2")
|
|
var cleanupOrder []string
|
|
|
|
d := &Daemon{
|
|
vmCaps: []vmCapability{
|
|
testCapability{
|
|
name: "first",
|
|
prepare: func(context.Context, *model.VMRecord, model.Image) error {
|
|
return nil
|
|
},
|
|
cleanup: func(context.Context, model.VMRecord) error {
|
|
cleanupOrder = append(cleanupOrder, "first")
|
|
return nil
|
|
},
|
|
},
|
|
testCapability{
|
|
name: "second",
|
|
prepare: func(context.Context, *model.VMRecord, model.Image) error {
|
|
return nil
|
|
},
|
|
cleanup: func(context.Context, model.VMRecord) error {
|
|
cleanupOrder = append(cleanupOrder, "second")
|
|
return nil
|
|
},
|
|
},
|
|
testCapability{
|
|
name: "broken",
|
|
prepare: func(context.Context, *model.VMRecord, model.Image) error {
|
|
return errors.New("boom")
|
|
},
|
|
},
|
|
},
|
|
}
|
|
wireServices(d)
|
|
|
|
err := d.prepareCapabilityHosts(context.Background(), &vm, model.Image{})
|
|
if err == nil || err.Error() != "boom" {
|
|
t.Fatalf("prepareCapabilityHosts() error = %v, want boom", err)
|
|
}
|
|
if !reflect.DeepEqual(cleanupOrder, []string{"second", "first"}) {
|
|
t.Fatalf("cleanup order = %v, want reverse prepared order", cleanupOrder)
|
|
}
|
|
}
|
|
|
|
func TestContributeHooksPopulateGuestAndMachineConfig(t *testing.T) {
|
|
d := &Daemon{
|
|
vmCaps: []vmCapability{
|
|
testCapability{
|
|
name: "guest",
|
|
contribute: func(builder *guestconfig.Builder, _ model.VMRecord, _ model.Image) {
|
|
builder.AddMount(guestconfig.MountSpec{Source: "/dev/vdb", Target: "/work", FSType: "ext4"})
|
|
},
|
|
contributeFC: func(cfg *firecracker.MachineConfig, _ model.VMRecord, _ model.Image) {
|
|
cfg.Drives = append(cfg.Drives, firecracker.DriveConfig{ID: "work", Path: "/tmp/work.ext4"})
|
|
},
|
|
},
|
|
},
|
|
}
|
|
wireServices(d)
|
|
|
|
builder := guestconfig.NewBuilder()
|
|
d.contributeGuestConfig(builder, model.VMRecord{}, model.Image{})
|
|
|
|
cfg := firecracker.MachineConfig{Drives: []firecracker.DriveConfig{{ID: "rootfs", Path: "/dev/root", IsRoot: true}}}
|
|
d.contributeMachineConfig(&cfg, model.VMRecord{}, model.Image{})
|
|
|
|
fstab := builder.RenderFSTab("")
|
|
if !reflect.DeepEqual(cfg.Drives[1], firecracker.DriveConfig{ID: "work", Path: "/tmp/work.ext4"}) {
|
|
t.Fatalf("machine drives = %+v, want contributed work drive", cfg.Drives)
|
|
}
|
|
if want := "/dev/vdb /work ext4 defaults 0 0\n"; fstab != want {
|
|
t.Fatalf("guest fstab = %q, want %q", fstab, want)
|
|
}
|
|
}
|
|
|
|
func TestProbeBangerDNSAcceptsRealServer(t *testing.T) {
|
|
server, err := vmdns.New("127.0.0.1:0", nil)
|
|
if err != nil {
|
|
t.Fatalf("vmdns.New: %v", err)
|
|
}
|
|
t.Cleanup(func() { _ = server.Close() })
|
|
|
|
if !probeBangerDNS(server.Addr()) {
|
|
t.Fatal("probeBangerDNS rejected the real banger DNS server")
|
|
}
|
|
}
|
|
|
|
func TestProbeBangerDNSRejectsSilentListener(t *testing.T) {
|
|
// A UDP listener that drops every datagram. The probe should
|
|
// time out and return false — i.e. "this is not banger".
|
|
conn, err := net.ListenPacket("udp", "127.0.0.1:0")
|
|
if err != nil {
|
|
t.Fatalf("ListenPacket: %v", err)
|
|
}
|
|
t.Cleanup(func() { _ = conn.Close() })
|
|
|
|
if probeBangerDNS(conn.LocalAddr().String()) {
|
|
t.Fatal("probeBangerDNS accepted a silent non-DNS listener")
|
|
}
|
|
}
|
|
|
|
func TestDefaultCapabilitiesInOrder(t *testing.T) {
|
|
d := &Daemon{}
|
|
wireServices(d)
|
|
var names []string
|
|
for _, capability := range d.vmCaps {
|
|
names = append(names, capability.Name())
|
|
}
|
|
want := []string{"work-disk", "dns", "nat"}
|
|
if !reflect.DeepEqual(names, want) {
|
|
t.Fatalf("capabilities = %v, want %v", names, want)
|
|
}
|
|
}
|