about summary refs log tree commit diff
path: root/nixos/tests/incus/container.nix
blob: 10262cf2132b89aa455533b5194e0404d02d797d (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
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.lxdContainerMeta.${pkgs.stdenv.hostPlatform.system};
  container-image-rootfs = releases.lxdContainerImage.${pkgs.stdenv.hostPlatform.system};
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 = ''
    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}/*/*.tar.xz ${container-image-rootfs}/*/*.tar.xz --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("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}")
  '';
})