Unverified Commit d7552b8a authored by Adam C. Stephens's avatar Adam C. Stephens
Browse files

incus: refactor tests

parent 967137ab
Loading
Loading
Loading
Loading
+2 −2
Original line number Diff line number Diff line
@@ -466,8 +466,8 @@ in {
  iftop = handleTest ./iftop.nix {};
  immich = handleTest ./web-apps/immich.nix {};
  incron = handleTest ./incron.nix {};
  incus = pkgs.recurseIntoAttrs (handleTest ./incus { inherit handleTestOn; inherit (pkgs) incus; });
  incus-lts = pkgs.recurseIntoAttrs (handleTest ./incus { inherit handleTestOn; });
  incus = pkgs.recurseIntoAttrs (handleTest ./incus { lts = false; });
  incus-lts = pkgs.recurseIntoAttrs (handleTest ./incus { });
  influxdb = handleTest ./influxdb.nix {};
  influxdb2 = handleTest ./influxdb2.nix {};
  initrd-network-openvpn = handleTestOn [ "x86_64-linux" "i686-linux" ] ./initrd-network-openvpn {};

nixos/tests/incus/container.nix

deleted100644 → 0
+0 −154
Original line number Diff line number Diff line
import ../make-test-python.nix (
  {
    pkgs,
    lib,
    extra ? { },
    name ? "incus-container",
    incus ? pkgs.incus-lts,
    ...
  }:

  let
    releases = import ../../release.nix {
      configuration = lib.recursiveUpdate {
        # Building documentation makes the test unnecessarily take a longer time:
        documentation.enable = lib.mkForce false;

        boot.kernel.sysctl."net.ipv4.ip_forward" = "1";
      } extra;
    };

    container-image-metadata = "${
      releases.incusContainerMeta.${pkgs.stdenv.hostPlatform.system}
    }/tarball/nixos-image-lxc-*-${pkgs.stdenv.hostPlatform.system}.tar.xz";
    container-image-rootfs = "${
      releases.incusContainerImage.${pkgs.stdenv.hostPlatform.system}
    }/nixos-lxc-image-${pkgs.stdenv.hostPlatform.system}.squashfs";
  in
  {
    inherit name;

    meta = {
      maintainers = lib.teams.lxc.members;
    };

    nodes.machine =
      { ... }:
      {
        virtualisation = {
          # Ensure test VM has enough resources for creating and managing guests
          cores = 2;
          memorySize = 1024;
          diskSize = 4096;

          incus = {
            enable = true;
            package = incus;
          };
        };
        networking.nftables.enable = true;
      };

    testScript = # python
      ''
        def instance_is_up(_) -> bool:
            status, _ = machine.execute("incus exec container --disable-stdin --force-interactive /run/current-system/sw/bin/systemctl -- is-system-running")
            return status == 0

        def set_container(config):
            machine.succeed(f"incus config set container {config}")
            machine.succeed("incus restart container")
            with machine.nested("Waiting for instance to start and be usable"):
              retry(instance_is_up)

        def check_sysctl(instance):
            with subtest("systemd sysctl settings are applied"):
                machine.succeed(f"incus exec {instance} -- systemctl status systemd-sysctl")
                sysctl = machine.succeed(f"incus exec {instance} -- sysctl net.ipv4.ip_forward").strip().split(" ")[-1]
                assert "1" == sysctl, f"systemd-sysctl configuration not correctly applied, {sysctl} != 1"

        machine.wait_for_unit("incus.service")

        # no preseed should mean no service
        machine.fail("systemctl status incus-preseed.service")

        machine.succeed("incus admin init --minimal")

        with subtest("Container image can be imported"):
            machine.succeed("incus image import ${container-image-metadata} ${container-image-rootfs} --alias nixos")

        with subtest("Container can be launched and managed"):
            machine.succeed("incus launch nixos container")
            with machine.nested("Waiting for instance to start and be usable"):
              retry(instance_is_up)
            machine.succeed("echo true | incus exec container /run/current-system/sw/bin/bash -")

        with subtest("Container mounts lxcfs overlays"):
            machine.succeed("incus exec container mount | grep 'lxcfs on /proc/cpuinfo type fuse.lxcfs'")
            machine.succeed("incus exec container mount | grep 'lxcfs on /proc/meminfo type fuse.lxcfs'")

        with subtest("resource limits"):
            with subtest("Container CPU limits can be managed"):
                set_container("limits.cpu 1")
                cpuinfo = machine.succeed("incus exec container grep -- -c ^processor /proc/cpuinfo").strip()
                assert cpuinfo == "1", f"Wrong number of CPUs reported from /proc/cpuinfo, want: 1, got: {cpuinfo}"

                set_container("limits.cpu 2")
                cpuinfo = machine.succeed("incus exec container grep -- -c ^processor /proc/cpuinfo").strip()
                assert cpuinfo == "2", f"Wrong number of CPUs reported from /proc/cpuinfo, want: 2, got: {cpuinfo}"

            with subtest("Container memory limits can be managed"):
                set_container("limits.memory 64MB")
                meminfo = machine.succeed("incus exec container grep -- MemTotal /proc/meminfo").strip()
                meminfo_bytes = " ".join(meminfo.split(' ')[-2:])
                assert meminfo_bytes == "62500 kB", f"Wrong amount of memory reported from /proc/meminfo, want: '62500 kB', got: '{meminfo_bytes}'"

                set_container("limits.memory 128MB")
                meminfo = machine.succeed("incus exec container grep -- MemTotal /proc/meminfo").strip()
                meminfo_bytes = " ".join(meminfo.split(' ')[-2:])
                assert meminfo_bytes == "125000 kB", f"Wrong amount of memory reported from /proc/meminfo, want: '125000 kB', got: '{meminfo_bytes}'"

        with subtest("virtual tpm can be configured"):
            machine.succeed("incus config device add container vtpm tpm path=/dev/tpm0 pathrm=/dev/tpmrm0")
            machine.succeed("incus exec container -- test -e /dev/tpm0")
            machine.succeed("incus exec container -- test -e /dev/tpmrm0")
            machine.succeed("incus config device remove container vtpm")
            machine.fail("incus exec container -- test -e /dev/tpm0")

        with subtest("lxc-generator"):
            with subtest("lxc-container generator configures plain container"):
                # reuse the existing container to save some time
                machine.succeed("incus exec container test -- -e /run/systemd/system/service.d/zzz-lxc-service.conf")
                check_sysctl("container")

            with subtest("lxc-container generator configures nested container"):
                machine.execute("incus delete --force container")
                machine.succeed("incus launch nixos container --config security.nesting=true")
                with machine.nested("Waiting for instance to start and be usable"):
                  retry(instance_is_up)

                machine.fail("incus exec container test -- -e /run/systemd/system/service.d/zzz-lxc-service.conf")
                target = machine.succeed("incus exec container readlink -- -f /run/systemd/system/systemd-binfmt.service").strip()
                assert target == "/dev/null", "lxc generator did not correctly mask /run/systemd/system/systemd-binfmt.service"

                check_sysctl("container")

            with subtest("lxc-container generator configures privileged container"):
                machine.execute("incus delete --force container")
                machine.succeed("incus launch nixos container --config security.privileged=true")
                with machine.nested("Waiting for instance to start and be usable"):
                  retry(instance_is_up)

                machine.succeed("incus exec container test -- -e /run/systemd/system/service.d/zzz-lxc-service.conf")

                check_sysctl("container")

        with subtest("softDaemonRestart"):
            with subtest("Instance remains running when softDaemonRestart is enabled and services is stopped"):
                pid = machine.succeed("incus info container | grep 'PID'").split(":")[1].strip()
                machine.succeed(f"ps {pid}")
                machine.succeed("systemctl stop incus")
                machine.succeed(f"ps {pid}")
      '';
  }
)
+35 −22
Original line number Diff line number Diff line
{
  system ? builtins.currentSystem,
  config ? { },
  pkgs ? import ../../.. { inherit system config; },
  handleTestOn,
  incus ? pkgs.incus-lts,
  lts ? true,
  ...
}:
let
  incusTest = import ./incus-tests.nix;
in
{
  container-legacy-init = import ./container.nix {
    name = "container-legacy-init";
    inherit incus system pkgs;
  all = incusTest {
    inherit lts;
    allTests = true;
  };
  container-systemd-init = import ./container.nix {
    name = "container-systemd-init";
    inherit incus system pkgs;
    extra = {
      boot.initrd.systemd.enable = true;

  container = incusTest {
    inherit lts;
    instanceContainer = true;
  };

  lvm = incusTest {
    inherit lts;
    storageLvm = true;
  };
  incusd-options = import ./incusd-options.nix { inherit incus system pkgs; };
  lxd-to-incus = import ./lxd-to-incus.nix { inherit incus system pkgs; };
  openvswitch = import ./openvswitch.nix { inherit incus system pkgs; };
  socket-activated = import ./socket-activated.nix { inherit incus system pkgs; };
  storage = import ./storage.nix { inherit incus system pkgs; };
  ui = import ./ui.nix { inherit incus system pkgs; };
  virtual-machine = handleTestOn [ "x86_64-linux" ] ./virtual-machine.nix {
    inherit incus system pkgs;

  lxd-to-incus = import ./lxd-to-incus.nix { };

  openvswitch = incusTest {
    inherit lts;
    networkOvs = true;
  };

  ui = import ./ui.nix { };

  virtual-machine = incusTest {
    inherit lts;
    instanceVm = true;
  };

  zfs = incusTest {
    inherit lts;
    storageLvm = true;
  };
}
+442 −0

File added.

Preview size limit exceeded, changes collapsed.

+0 −140
Original line number Diff line number Diff line
# this is a set of tests for non-default options. typically the default options
# will be handled by the other tests
import ../make-test-python.nix (
  {
    pkgs,
    lib,
    incus ? pkgs.incus-lts,
    ...
  }:

  let
    releases = import ../../release.nix {
      configuration = {
        # Building documentation makes the test unnecessarily take a longer time:
        documentation.enable = lib.mkForce false;
      };
    };

    container-image-metadata = "${
      releases.incusContainerMeta.${pkgs.stdenv.hostPlatform.system}
    }/tarball/nixos-image-lxc-*-${pkgs.stdenv.hostPlatform.system}.tar.xz";
    container-image-rootfs = "${
      releases.incusContainerImage.${pkgs.stdenv.hostPlatform.system}
    }/nixos-lxc-image-${pkgs.stdenv.hostPlatform.system}.squashfs";
  in
  {
    name = "incusd-options";

    meta = {
      maintainers = lib.teams.lxc.members;
    };

    nodes.machine = {
      virtualisation = {
        cores = 2;
        memorySize = 1024;
        diskSize = 4096;

        incus = {
          enable = true;
          package = incus;
          softDaemonRestart = false;

          preseed = {
            networks = [
              {
                name = "incusbr0";
                type = "bridge";
                config = {
                  "ipv4.address" = "10.0.100.1/24";
                  "ipv4.nat" = "true";
                };
              }
            ];
            profiles = [
              {
                name = "default";
                devices = {
                  eth0 = {
                    name = "eth0";
                    network = "incusbr0";
                    type = "nic";
                  };
                  root = {
                    path = "/";
                    pool = "default";
                    size = "35GiB";
                    type = "disk";
                  };
                };
              }
            ];
            storage_pools = [
              {
                name = "default";
                driver = "dir";
              }
            ];
          };
        };

      };

      networking.nftables.enable = true;

      users.users.testuser = {
        isNormalUser = true;
        shell = pkgs.bashInteractive;
        group = "incus";
        uid = 1000;
      };
    };

    testScript = # python
      ''
        def wait_for_instance(name: str, project: str = "default"):
            def instance_is_up(_) -> bool:
                status, _ = machine.execute(f"incus exec {name} --disable-stdin --force-interactive --project {project} -- /run/current-system/sw/bin/systemctl is-system-running")
                return status == 0

            with machine.nested(f"Waiting for instance {name} to start and be usable"):
              retry(instance_is_up)

        machine.wait_for_unit("incus.service")
        machine.wait_for_unit("incus-preseed.service")

        with subtest("Container image can be imported"):
            machine.succeed("incus image import ${container-image-metadata} ${container-image-rootfs} --alias nixos")

        with subtest("Container can be launched and managed"):
            machine.succeed("incus launch nixos instance1")
            wait_for_instance("instance1")
            machine.succeed("echo true | incus exec instance1 /run/current-system/sw/bin/bash -")

        with subtest("Verify preseed resources created"):
            machine.succeed("incus profile show default")
            machine.succeed("incus network info incusbr0")
            machine.succeed("incus storage show default")

        with subtest("Instance is stopped when softDaemonRestart is disabled and services is stopped"):
            pid = machine.succeed("incus info instance1 | grep 'PID'").split(":")[1].strip()
            machine.succeed(f"ps {pid}")
            machine.succeed("systemctl stop incus")
            machine.fail(f"ps {pid}")

        with subtest("incus-user allows restricted access for users"):
            machine.fail("incus project show user-1000")
            machine.succeed("su - testuser bash -c 'incus list'")
            # a project is created dynamically for the user
            machine.succeed("incus project show user-1000")
            # users shouldn't be able to list storage pools
            machine.fail("su - testuser bash -c 'incus storage list'")

        with subtest("incus-user allows users to launch instances"):
            machine.succeed("su - testuser bash -c 'incus image import ${container-image-metadata} ${container-image-rootfs} --alias nixos'")
            machine.succeed("su - testuser bash -c 'incus launch nixos instance2'")
            wait_for_instance("instance2", "user-1000")
      '';
  }
)
Loading