diff --git a/internal/incus/install_test.go b/internal/incus/install_test.go new file mode 100644 index 0000000..d6bc653 --- /dev/null +++ b/internal/incus/install_test.go @@ -0,0 +1,90 @@ +package incus + +import ( + "os/exec" + "strings" + "testing" +) + +// The install script is generated, so a quoting slip would only show on a live machine. +// Build it for the shapes of machine we have and let sh check the syntax. +func TestInstallScriptShapes(t *testing.T) { + if _, err := exec.LookPath("sh"); err != nil { + t.Skip("no sh") + } + m := New("/home/core/apps/hamlab", "core", "test") + cases := []struct { + name string + st Status + o InstallOpts + want []string + }{ + {"grid: zfs, apt, docker", Status{State: "installable", PM: "apt", Arch: "x86_64", Drivers: []string{"zfs", "btrfs", "dir"}, Docker: true, ZFSHost: true, DiskFree: 700 << 30, MemTotal: 62 << 30}, + InstallOpts{Driver: "zfs", PoolSizeGiB: 300, Subnet: "172.16.100.1/24", DockerRules: true, ARCMaxGiB: 16}, + []string{"apt-get", "zpool create", "incus storage create default zfs source=hamlab-virt", "ipv4.address='172.16.100.1/24'", "DOCKER-USER", "zfs_arc_max", "usermod -aG incus-admin 'core'", "SupplementaryGroups=incus-admin", "systemd-run"}}, + {"core: btrfs, apt, docker, no tools yet", Status{State: "installable", PM: "apt", Arch: "x86_64", Drivers: []string{"btrfs", "dir"}, Docker: true, DiskFree: 170 << 30, MemTotal: 8 << 30}, + InstallOpts{Driver: "btrfs", PoolSizeGiB: 40, Subnet: "172.16.100.1/24", DockerRules: true}, + []string{"btrfs-progs", "mkfs.btrfs", "/etc/fstab", "incus storage create default btrfs source="}}, + {"mac: dir, dnf, arm64", Status{State: "installable", PM: "dnf", Arch: "aarch64", Drivers: []string{"dir"}, DiskFree: 200 << 30, MemTotal: 15 << 30}, + InstallOpts{Driver: "dir", Subnet: "172.16.101.1/24"}, + []string{"dnf -y install incus qemu-system-aarch64-core edk2-aarch64", "incus storage create default dir source="}}, + {"repair: already installed", Status{State: "repair", PM: "apt", Arch: "x86_64", Drivers: []string{"dir"}, DiskFree: 10 << 30, MemTotal: 4 << 30}, + InstallOpts{Driver: "dir", Subnet: "172.16.100.1/24"}, + []string{"already installed"}}, + } + for _, c := range cases { + script, lay, err := m.InstallScript(c.st, c.o) + if err != nil { + t.Fatalf("%s: %v", c.name, err) + } + for _, w := range c.want { + if !strings.Contains(script, w) { + t.Errorf("%s: script lacks %q", c.name, w) + } + } + if lay.Driver != c.o.Driver || lay.Subnet != c.o.Subnet { + t.Errorf("%s: layout %+v", c.name, lay) + } + cmd := exec.Command("sh", "-n") + cmd.Stdin = strings.NewReader(script) + if out, err := cmd.CombinedOutput(); err != nil { + t.Errorf("%s: sh -n: %v\n%s", c.name, err, out) + } + } +} + +func TestInstallScriptRefusals(t *testing.T) { + m := New("/x", "core", "test") + st := Status{State: "installable", PM: "apt", Drivers: []string{"btrfs", "dir"}, DiskFree: 20 << 30, MemTotal: 8 << 30} + for _, o := range []InstallOpts{ + {Driver: "zfs", PoolSizeGiB: 10, Subnet: "172.16.100.1/24"}, // not offered here + {Driver: "btrfs", PoolSizeGiB: 50, Subnet: "172.16.100.1/24"}, // more than is free + {Driver: "btrfs", PoolSizeGiB: 10, Subnet: "172.16.100.0/24"}, // the network address, not the bridge's + {Driver: "btrfs", PoolSizeGiB: 10, Subnet: "10.0.0.1/8"}, // too wide + {Driver: "btrfs", PoolSizeGiB: 10, Subnet: "172.16.100.1/24", ARCMaxGiB: 64}, // more RAM than there is + } { + if _, _, err := m.InstallScript(st, o); err == nil { + t.Errorf("accepted %+v", o) + } + } +} + +func TestCreateArgs(t *testing.T) { + args, err := CreateArgs(CreateSpec{Name: "web-1", Image: "images:debian/13", VM: true, CPU: "2", Memory: "2GiB", Disk: "20GiB", Network: "incusbr0", Start: true}, true) + if err != nil { + t.Fatal(err) + } + want := "launch images:debian/13 web-1 --vm -c limits.cpu=2 -c limits.memory=2GiB -d root,size=20GiB -n incusbr0" + if got := strings.Join(args, " "); got != want { + t.Fatalf("got %q", got) + } + if _, err := CreateArgs(CreateSpec{Name: "web-1", Image: "images:debian/13", VM: true}, false); err == nil { + t.Fatal("a VM without KVM must be refused") + } + if _, err := CreateArgs(CreateSpec{Name: "1bad", Image: "images:debian/13"}, true); err == nil { + t.Fatal("a name starting with a digit must be refused") + } + if _, err := CreateArgs(CreateSpec{Name: "ok", Image: "images:debian/13", Memory: "2 gigs"}, true); err == nil { + t.Fatal("a malformed size must be refused") + } +}