at 25.11-pre 20 kB view raw
1import ../make-test-python.nix ( 2 { 3 pkgs, 4 lib, 5 6 lts ? true, 7 8 allTests ? false, 9 10 appArmor ? false, 11 featureUser ? allTests, 12 initLegacy ? true, 13 initSystemd ? true, 14 instanceContainer ? allTests, 15 instanceVm ? allTests, 16 networkOvs ? allTests, 17 storageLvm ? allTests, 18 storageZfs ? allTests, 19 ... 20 }: 21 22 let 23 releases = 24 init: 25 import ../../release.nix { 26 configuration = { 27 # Building documentation makes the test unnecessarily take a longer time: 28 documentation.enable = lib.mkForce false; 29 30 boot.initrd.systemd.enable = init == "systemd"; 31 32 # Arbitrary sysctl modification to ensure containers can update sysctl 33 boot.kernel.sysctl."net.ipv4.ip_forward" = "1"; 34 }; 35 }; 36 37 images = init: { 38 container = { 39 metadata = 40 (releases init).incusContainerMeta.${pkgs.stdenv.hostPlatform.system} 41 + "/tarball/nixos-image-lxc-*-${pkgs.stdenv.hostPlatform.system}.tar.xz"; 42 43 rootfs = 44 (releases init).incusContainerImage.${pkgs.stdenv.hostPlatform.system} 45 + "/nixos-lxc-image-${pkgs.stdenv.hostPlatform.system}.squashfs"; 46 }; 47 48 virtual-machine = { 49 metadata = 50 (releases init).incusVirtualMachineImageMeta.${pkgs.stdenv.hostPlatform.system} + "/*/*.tar.xz"; 51 disk = (releases init).incusVirtualMachineImage.${pkgs.stdenv.hostPlatform.system} + "/nixos.qcow2"; 52 }; 53 }; 54 55 initVariants = lib.optionals initLegacy [ "legacy" ] ++ lib.optionals initSystemd [ "systemd" ]; 56 57 canTestVm = instanceVm && pkgs.stdenv.isLinux && pkgs.stdenv.isx86_64; 58 in 59 { 60 name = "incus" + lib.optionalString lts "-lts"; 61 62 meta = { 63 maintainers = lib.teams.lxc.members; 64 }; 65 66 nodes.machine = { 67 virtualisation = { 68 cores = 2; 69 memorySize = 2048; 70 diskSize = 12 * 1024; 71 emptyDiskImages = [ 72 # vdb for zfs 73 2048 74 # vdc for lvm 75 2048 76 ]; 77 78 incus = { 79 enable = true; 80 package = if lts then pkgs.incus-lts else pkgs.incus; 81 82 preseed = { 83 networks = 84 [ 85 { 86 name = "incusbr0"; 87 type = "bridge"; 88 config = { 89 "ipv4.address" = "10.0.10.1/24"; 90 "ipv4.nat" = "true"; 91 }; 92 } 93 ] 94 ++ lib.optionals networkOvs [ 95 { 96 name = "ovsbr0"; 97 type = "bridge"; 98 config = { 99 "bridge.driver" = "openvswitch"; 100 "ipv4.address" = "10.0.20.1/24"; 101 "ipv4.nat" = "true"; 102 }; 103 } 104 ]; 105 profiles = [ 106 { 107 name = "default"; 108 devices = { 109 eth0 = { 110 name = "eth0"; 111 network = "incusbr0"; 112 type = "nic"; 113 }; 114 root = { 115 path = "/"; 116 pool = "default"; 117 size = "35GiB"; 118 type = "disk"; 119 }; 120 }; 121 } 122 ]; 123 storage_pools = [ 124 { 125 name = "default"; 126 driver = "dir"; 127 } 128 ]; 129 }; 130 }; 131 132 vswitch.enable = networkOvs; 133 }; 134 135 boot.supportedFilesystems = lib.optionals storageZfs [ "zfs" ]; 136 boot.zfs.forceImportRoot = false; 137 138 environment.systemPackages = [ pkgs.parted ]; 139 140 networking.hostId = "01234567"; 141 networking.firewall.trustedInterfaces = [ "incusbr0" ]; 142 143 security.apparmor.enable = appArmor; 144 services.dbus.apparmor = (if appArmor then "enabled" else "disabled"); 145 146 services.lvm = { 147 boot.thin.enable = storageLvm; 148 dmeventd.enable = storageLvm; 149 }; 150 151 networking.nftables.enable = true; 152 153 users.users.testuser = { 154 isNormalUser = true; 155 shell = pkgs.bashInteractive; 156 group = "incus"; 157 uid = 1000; 158 }; 159 }; 160 161 testScript = # python 162 '' 163 import json 164 165 def wait_for_instance(name: str, project: str = "default"): 166 machine.wait_until_succeeds(f"incus exec {name} --disable-stdin --force-interactive --project {project} -- /run/current-system/sw/bin/systemctl is-system-running") 167 168 169 def wait_incus_exec_success(name: str, command: str, timeout: int = 900, project: str = "default"): 170 def check_command(_) -> bool: 171 status, _ = machine.execute(f"incus exec {name} --disable-stdin --force-interactive --project {project} -- {command}") 172 return status == 0 173 174 with machine.nested(f"Waiting for successful exec: {command}"): 175 retry(check_command, timeout) 176 177 178 def set_config(name: str, config: str, restart: bool = False, unset: bool = False): 179 if restart: 180 machine.succeed(f"incus stop {name}") 181 182 if unset: 183 machine.succeed(f"incus config unset {name} {config}") 184 else: 185 machine.succeed(f"incus config set {name} {config}") 186 187 if restart: 188 machine.succeed(f"incus start {name}") 189 wait_for_instance(name) 190 else: 191 # give a moment to settle 192 machine.sleep(1) 193 194 195 def cleanup(): 196 # avoid conflict between preseed and cleanup operations 197 machine.execute("systemctl kill incus-preseed.service") 198 199 instances = json.loads(machine.succeed("incus list --format json --all-projects")) 200 with subtest("Stopping all running instances"): 201 for instance in [a for a in instances if a['status'] == 'Running']: 202 machine.execute(f"incus stop --force {instance['name']} --project {instance['project']}") 203 machine.execute(f"incus delete --force {instance['name']} --project {instance['project']}") 204 205 206 def check_sysctl(name: str): 207 with subtest("systemd sysctl settings are applied"): 208 machine.succeed(f"incus exec {name} -- systemctl status systemd-sysctl") 209 sysctl = machine.succeed(f"incus exec {name} -- sysctl net.ipv4.ip_forward").strip().split(" ")[-1] 210 assert "1" == sysctl, f"systemd-sysctl configuration not correctly applied, {sysctl} != 1" 211 212 213 with subtest("Wait for startup"): 214 machine.wait_for_unit("incus.service") 215 machine.wait_for_unit("incus-preseed.service") 216 217 218 with subtest("Verify preseed resources created"): 219 machine.succeed("incus profile show default") 220 machine.succeed("incus network info incusbr0") 221 machine.succeed("incus storage show default") 222 223 '' 224 + lib.optionalString instanceContainer ( 225 lib.foldl ( 226 acc: variant: 227 acc 228 # python 229 + '' 230 metadata = "${(images variant).container.metadata}" 231 rootfs = "${(images variant).container.rootfs}" 232 alias = "nixos/container/${variant}" 233 variant = "${variant}" 234 235 with subtest("container image can be imported"): 236 machine.succeed(f"incus image import {metadata} {rootfs} --alias {alias}") 237 238 239 with subtest("container can be launched and managed"): 240 machine.succeed(f"incus launch {alias} container-{variant}1") 241 wait_for_instance(f"container-{variant}1") 242 243 244 with subtest("container mounts lxcfs overlays"): 245 machine.succeed(f"incus exec container-{variant}1 mount | grep 'lxcfs on /proc/cpuinfo type fuse.lxcfs'") 246 machine.succeed(f"incus exec container-{variant}1 mount | grep 'lxcfs on /proc/meminfo type fuse.lxcfs'") 247 248 249 with subtest("container CPU limits can be managed"): 250 set_config(f"container-{variant}1", "limits.cpu 1", restart=True) 251 wait_incus_exec_success(f"container-{variant}1", "nproc | grep '^1$'", timeout=90) 252 253 254 with subtest("container CPU limits can be hotplug changed"): 255 set_config(f"container-{variant}1", "limits.cpu 2") 256 wait_incus_exec_success(f"container-{variant}1", "nproc | grep '^2$'", timeout=90) 257 258 259 with subtest("container memory limits can be managed"): 260 set_config(f"container-{variant}1", "limits.memory 128MB", restart=True) 261 wait_incus_exec_success(f"container-{variant}1", "grep 'MemTotal:[[:space:]]*125000 kB' /proc/meminfo", timeout=90) 262 263 264 with subtest("container memory limits can be hotplug changed"): 265 set_config(f"container-{variant}1", "limits.memory 256MB") 266 wait_incus_exec_success(f"container-{variant}1", "grep 'MemTotal:[[:space:]]*250000 kB' /proc/meminfo", timeout=90) 267 268 269 with subtest("container software tpm can be configured"): 270 machine.succeed(f"incus config device add container-{variant}1 vtpm tpm path=/dev/tpm0 pathrm=/dev/tpmrm0") 271 machine.succeed(f"incus exec container-{variant}1 -- test -e /dev/tpm0") 272 machine.succeed(f"incus exec container-{variant}1 -- test -e /dev/tpmrm0") 273 machine.succeed(f"incus config device remove container-{variant}1 vtpm") 274 machine.fail(f"incus exec container-{variant}1 -- test -e /dev/tpm0") 275 276 277 with subtest("container lxc-generator compatibility"): 278 with subtest("lxc-container generator configures plain container"): 279 # default container is plain 280 machine.succeed(f"incus exec container-{variant}1 test -- -e /run/systemd/system/service.d/zzz-lxc-service.conf") 281 282 check_sysctl(f"container-{variant}1") 283 284 with subtest("lxc-container generator configures nested container"): 285 set_config(f"container-{variant}1", "security.nesting=true", restart=True) 286 287 machine.fail(f"incus exec container-{variant}1 test -- -e /run/systemd/system/service.d/zzz-lxc-service.conf") 288 target = machine.succeed(f"incus exec container-{variant}1 readlink -- -f /run/systemd/system/systemd-binfmt.service").strip() 289 assert target == "/dev/null", "lxc generator did not correctly mask /run/systemd/system/systemd-binfmt.service" 290 291 check_sysctl(f"container-{variant}1") 292 293 with subtest("lxc-container generator configures privileged container"): 294 # Create a new instance for a clean state 295 machine.succeed(f"incus launch {alias} container-{variant}2") 296 wait_for_instance(f"container-{variant}2") 297 298 machine.succeed(f"incus exec container-{variant}2 test -- -e /run/systemd/system/service.d/zzz-lxc-service.conf") 299 300 check_sysctl(f"container-{variant}2") 301 302 with subtest("container supports per-instance lxcfs"): 303 machine.succeed(f"incus stop container-{variant}1") 304 machine.fail(f"pgrep -a lxcfs | grep 'incus/devices/container-{variant}1/lxcfs'") 305 306 machine.succeed("incus config set instances.lxcfs.per_instance=true") 307 308 machine.succeed(f"incus start container-{variant}1") 309 wait_for_instance(f"container-{variant}1") 310 machine.succeed(f"pgrep -a lxcfs | grep 'incus/devices/container-{variant}1/lxcfs'") 311 312 313 with subtest("container can successfully restart"): 314 machine.succeed(f"incus restart container-{variant}1") 315 wait_for_instance(f"container-{variant}1") 316 317 318 with subtest("container remains running when softDaemonRestart is enabled and service is stopped"): 319 pid = machine.succeed(f"incus info container-{variant}1 | grep 'PID'").split(":")[1].strip() 320 machine.succeed(f"ps {pid}") 321 machine.succeed("systemctl stop incus") 322 machine.succeed(f"ps {pid}") 323 machine.succeed("systemctl start incus") 324 325 with subtest("containers stop with incus-startup.service"): 326 pid = machine.succeed(f"incus info container-{variant}1 | grep 'PID'").split(":")[1].strip() 327 machine.succeed(f"ps {pid}") 328 machine.succeed("systemctl stop incus-startup.service") 329 machine.wait_until_fails(f"ps {pid}", timeout=120) 330 machine.succeed("systemctl start incus-startup.service") 331 332 333 cleanup() 334 '' 335 ) "" initVariants 336 ) 337 + lib.optionalString canTestVm ( 338 (lib.foldl ( 339 acc: variant: 340 acc 341 # python 342 + '' 343 metadata = "${(images variant).virtual-machine.metadata}" 344 disk = "${(images variant).virtual-machine.disk}" 345 alias = "nixos/virtual-machine/${variant}" 346 variant = "${variant}" 347 348 with subtest("virtual-machine image can be imported"): 349 machine.succeed(f"incus image import {metadata} {disk} --alias {alias}") 350 351 352 with subtest("virtual-machine can be created"): 353 machine.succeed(f"incus create {alias} vm-{variant}1 --vm --config limits.memory=512MB --config security.secureboot=false") 354 355 356 with subtest("virtual-machine software tpm can be configured"): 357 machine.succeed(f"incus config device add vm-{variant}1 vtpm tpm path=/dev/tpm0") 358 359 360 with subtest("virtual-machine can be launched and become available"): 361 machine.succeed(f"incus start vm-{variant}1") 362 wait_for_instance(f"vm-{variant}1") 363 364 365 with subtest("virtual-machine incus-agent is started"): 366 machine.succeed(f"incus exec vm-{variant}1 systemctl is-active incus-agent") 367 368 369 with subtest("virtual-machine incus-agent has a valid path"): 370 machine.succeed(f"incus exec vm-{variant}1 -- bash -c 'true'") 371 372 373 with subtest("virtual-machine CPU limits can be managed"): 374 set_config(f"vm-{variant}1", "limits.cpu 1", restart=True) 375 wait_incus_exec_success(f"vm-{variant}1", "nproc | grep '^1$'", timeout=90) 376 377 378 with subtest("virtual-machine CPU limits can be hotplug changed"): 379 set_config(f"vm-{variant}1", "limits.cpu 2") 380 wait_incus_exec_success(f"vm-{variant}1", "nproc | grep '^2$'", timeout=90) 381 382 383 with subtest("virtual-machine can successfully restart"): 384 machine.succeed(f"incus restart vm-{variant}1") 385 wait_for_instance(f"vm-{variant}1") 386 387 388 with subtest("virtual-machine remains running when softDaemonRestart is enabled and service is stopped"): 389 pid = machine.succeed(f"incus info vm-{variant}1 | grep 'PID'").split(":")[1].strip() 390 machine.succeed(f"ps {pid}") 391 machine.succeed("systemctl stop incus") 392 machine.succeed(f"ps {pid}") 393 machine.succeed("systemctl start incus") 394 395 396 with subtest("virtual-machines stop with incus-startup.service"): 397 pid = machine.succeed(f"incus info vm-{variant}1 | grep 'PID'").split(":")[1].strip() 398 machine.succeed(f"ps {pid}") 399 machine.succeed("systemctl stop incus-startup.service") 400 machine.wait_until_fails(f"ps {pid}", timeout=120) 401 machine.succeed("systemctl start incus-startup.service") 402 403 404 cleanup() 405 '' 406 ) "" initVariants) 407 + 408 # python 409 '' 410 with subtest("virtual-machine can launch CSM (BIOS)"): 411 machine.succeed("incus init csm --vm --empty -c security.csm=true -c security.secureboot=false") 412 machine.succeed("incus start csm") 413 414 415 cleanup() 416 '' 417 ) 418 + 419 lib.optionalString featureUser # python 420 '' 421 with subtest("incus-user allows restricted access for users"): 422 machine.fail("incus project show user-1000") 423 machine.succeed("su - testuser bash -c 'incus list'") 424 # a project is created dynamically for the user 425 machine.succeed("incus project show user-1000") 426 # users shouldn't be able to list storage pools 427 machine.fail("su - testuser bash -c 'incus storage list'") 428 429 430 with subtest("incus-user allows users to launch instances"): 431 machine.succeed("su - testuser bash -c 'incus image import ${(images "systemd").container.metadata} ${(images "systemd").container.rootfs} --alias nixos'") 432 machine.succeed("su - testuser bash -c 'incus launch nixos instance2'") 433 wait_for_instance("instance2", "user-1000") 434 435 cleanup() 436 '' 437 + 438 lib.optionalString networkOvs # python 439 '' 440 with subtest("Verify openvswitch bridge"): 441 machine.succeed("incus network info ovsbr0") 442 443 444 with subtest("Verify openvswitch bridge"): 445 machine.succeed("ovs-vsctl br-exists ovsbr0") 446 '' 447 448 + 449 lib.optionalString storageZfs # python 450 '' 451 with subtest("Verify zfs pool created and usable"): 452 machine.succeed( 453 "zpool status", 454 "parted --script /dev/vdb mklabel gpt", 455 "zpool create zfs_pool /dev/vdb", 456 ) 457 458 machine.succeed("incus storage create zfs_pool zfs source=zfs_pool/incus") 459 machine.succeed("zfs list zfs_pool/incus") 460 461 machine.succeed("incus storage volume create zfs_pool test_fs --type filesystem") 462 machine.succeed("incus storage volume create zfs_pool test_vol --type block") 463 464 machine.succeed("incus storage show zfs_pool") 465 machine.succeed("incus storage volume list zfs_pool") 466 machine.succeed("incus storage volume show zfs_pool test_fs") 467 machine.succeed("incus storage volume show zfs_pool test_vol") 468 469 machine.succeed("incus create zfs1 --empty --storage zfs_pool") 470 machine.succeed("incus list zfs1") 471 '' 472 473 + 474 lib.optionalString storageLvm # python 475 '' 476 with subtest("Verify lvm pool created and usable"): 477 machine.succeed("incus storage create lvm_pool lvm source=/dev/vdc lvm.vg_name=incus_pool") 478 machine.succeed("vgs incus_pool") 479 480 machine.succeed("incus storage volume create lvm_pool test_fs --type filesystem") 481 machine.succeed("incus storage volume create lvm_pool test_vol --type block") 482 483 machine.succeed("incus storage show lvm_pool") 484 485 machine.succeed("incus storage volume list lvm_pool") 486 machine.succeed("incus storage volume show lvm_pool test_fs") 487 machine.succeed("incus storage volume show lvm_pool test_vol") 488 489 machine.succeed("incus create lvm1 --empty --storage lvm_pool") 490 machine.succeed("incus list lvm1") 491 ''; 492 } 493)