incus: format

This commit is contained in:
Adam C. Stephens 2024-11-30 00:04:27 -05:00
parent ed30be523a
commit 9ab59bb5fb
No known key found for this signature in database
13 changed files with 600 additions and 482 deletions

View file

@ -1,51 +1,71 @@
{ lib, config, pkgs, ... }: {
lib,
config,
pkgs,
...
}:
let let
templateSubmodule = {...}: { templateSubmodule =
options = { { ... }:
enable = lib.mkEnableOption "this template"; {
options = {
enable = lib.mkEnableOption "this template";
target = lib.mkOption { target = lib.mkOption {
description = "Path in the container"; description = "Path in the container";
type = lib.types.path; type = lib.types.path;
}; };
template = lib.mkOption { template = lib.mkOption {
description = ".tpl file for rendering the target"; description = ".tpl file for rendering the target";
type = lib.types.path; type = lib.types.path;
}; };
when = lib.mkOption { when = lib.mkOption {
description = "Events which trigger a rewrite (create, copy)"; description = "Events which trigger a rewrite (create, copy)";
type = lib.types.listOf (lib.types.str); type = lib.types.listOf (lib.types.str);
}; };
properties = lib.mkOption { properties = lib.mkOption {
description = "Additional properties"; description = "Additional properties";
type = lib.types.attrs; type = lib.types.attrs;
default = {}; default = { };
};
}; };
}; };
};
toYAML = name: data: pkgs.writeText name (lib.generators.toYAML {} data); toYAML = name: data: pkgs.writeText name (lib.generators.toYAML { } data);
cfg = config.virtualisation.lxc; cfg = config.virtualisation.lxc;
templates = if cfg.templates != {} then let templates =
list = lib.mapAttrsToList (name: value: { inherit name; } // value) if cfg.templates != { } then
(lib.filterAttrs (name: value: value.enable) cfg.templates); let
in list = lib.mapAttrsToList (name: value: { inherit name; } // value) (
{ lib.filterAttrs (name: value: value.enable) cfg.templates
files = map (tpl: { );
source = tpl.template; in
target = "/templates/${tpl.name}.tpl"; {
}) list; files = map (tpl: {
properties = lib.listToAttrs (map (tpl: lib.nameValuePair tpl.target { source = tpl.template;
when = tpl.when; target = "/templates/${tpl.name}.tpl";
template = "${tpl.name}.tpl"; }) list;
properties = tpl.properties; properties = lib.listToAttrs (
}) list); map (
} tpl:
else { files = []; properties = {}; }; lib.nameValuePair tpl.target {
when = tpl.when;
template = "${tpl.name}.tpl";
properties = tpl.properties;
}
) list
);
}
else
{
files = [ ];
properties = { };
};
in { in
{
imports = [ imports = [
../image/file-options.nix ../image/file-options.nix
]; ];
@ -59,7 +79,7 @@ in {
templates = lib.mkOption { templates = lib.mkOption {
description = "Templates for LXD"; description = "Templates for LXD";
type = lib.types.attrsOf (lib.types.submodule templateSubmodule); type = lib.types.attrsOf (lib.types.submodule templateSubmodule);
default = {}; default = { };
example = lib.literalExpression '' example = lib.literalExpression ''
{ {
# create /etc/hostname on container creation # create /etc/hostname on container creation
@ -91,7 +111,10 @@ in {
}; };
config = { config = {
system.nixos.tags = [ "lxc" "metadata" ]; system.nixos.tags = [
"lxc"
"metadata"
];
image.extension = "tar.xz"; image.extension = "tar.xz";
image.filePath = "tarball/${config.image.fileName}"; image.filePath = "tarball/${config.image.fileName}";
system.build.image = config.system.build.metadata; system.build.image = config.system.build.metadata;
@ -100,7 +123,9 @@ in {
contents = [ contents = [
{ {
source = toYAML "metadata.yaml" { source = toYAML "metadata.yaml" {
architecture = builtins.elemAt (builtins.match "^([a-z0-9_]+).+" (toString pkgs.stdenv.hostPlatform.system)) 0; architecture = builtins.elemAt (builtins.match "^([a-z0-9_]+).+" (
toString pkgs.stdenv.hostPlatform.system
)) 0;
creation_date = 1; creation_date = 1;
properties = { properties = {
description = "${config.system.nixos.distroName} ${config.system.nixos.codeName} ${config.system.nixos.label} ${pkgs.stdenv.hostPlatform.system}"; description = "${config.system.nixos.distroName} ${config.system.nixos.codeName} ${config.system.nixos.label} ${pkgs.stdenv.hostPlatform.system}";

View file

@ -1,4 +1,4 @@
{lib, ...}: { lib, ... }:
{ {
meta = { meta = {

View file

@ -1,6 +1,11 @@
# LXC Configuration # LXC Configuration
{ config, lib, pkgs, ... }: {
config,
lib,
pkgs,
...
}:
let let
cfg = config.virtualisation.lxc; cfg = config.virtualisation.lxc;
@ -12,58 +17,53 @@ in
}; };
options.virtualisation.lxc = { options.virtualisation.lxc = {
enable = enable = lib.mkOption {
lib.mkOption { type = lib.types.bool;
type = lib.types.bool; default = false;
default = false; description = ''
description = '' This enables Linux Containers (LXC), which provides tools
This enables Linux Containers (LXC), which provides tools for creating and managing system or application containers
for creating and managing system or application containers on Linux.
on Linux. '';
''; };
};
unprivilegedContainers = lib.mkEnableOption "support for unprivileged users to launch containers"; unprivilegedContainers = lib.mkEnableOption "support for unprivileged users to launch containers";
systemConfig = systemConfig = lib.mkOption {
lib.mkOption { type = lib.types.lines;
type = lib.types.lines; default = "";
default = ""; description = ''
description = '' This is the system-wide LXC config. See
This is the system-wide LXC config. See {manpage}`lxc.system.conf(5)`.
{manpage}`lxc.system.conf(5)`. '';
''; };
};
package = lib.mkPackageOption pkgs "lxc" { }; package = lib.mkPackageOption pkgs "lxc" { };
defaultConfig = defaultConfig = lib.mkOption {
lib.mkOption { type = lib.types.lines;
type = lib.types.lines; default = "";
default = ""; description = ''
description = '' Default config (default.conf) for new containers, i.e. for
Default config (default.conf) for new containers, i.e. for network config. See {manpage}`lxc.container.conf(5)`.
network config. See {manpage}`lxc.container.conf(5)`. '';
''; };
};
usernetConfig = usernetConfig = lib.mkOption {
lib.mkOption { type = lib.types.lines;
type = lib.types.lines; default = "";
default = ""; description = ''
description = '' This is the config file for managing unprivileged user network
This is the config file for managing unprivileged user network administration access in LXC. See {manpage}`lxc-usernet(5)`.
administration access in LXC. See {manpage}`lxc-usernet(5)`. '';
''; };
};
bridgeConfig = bridgeConfig = lib.mkOption {
lib.mkOption { type = lib.types.lines;
type = lib.types.lines; default = "";
default = ""; description = ''
description = '' This is the config file for override lxc-net bridge default settings.
This is the config file for override lxc-net bridge default settings. '';
''; };
};
}; };
###### implementation ###### implementation
@ -88,7 +88,7 @@ in
}; };
# We don't need the `lxc-user` group, unless the unprivileged containers are enabled. # We don't need the `lxc-user` group, unless the unprivileged containers are enabled.
users.groups = lib.mkIf cfg.unprivilegedContainers { lxc-user = {}; }; users.groups = lib.mkIf cfg.unprivilegedContainers { lxc-user = { }; };
# `lxc-user-nic` needs suid to attach to bridge for unpriv containers. # `lxc-user-nic` needs suid to attach to bridge for unpriv containers.
security.wrappers = lib.mkIf cfg.unprivilegedContainers { security.wrappers = lib.mkIf cfg.unprivilegedContainers {
@ -108,7 +108,12 @@ in
lxc-net = { lxc-net = {
enable = true; enable = true;
wantedBy = [ "multi-user.target" ]; wantedBy = [ "multi-user.target" ];
path = [ pkgs.iproute2 pkgs.iptables pkgs.getent pkgs.dnsmasq ]; path = [
pkgs.iproute2
pkgs.iptables
pkgs.getent
pkgs.dnsmasq
];
}; };
}; };
}; };

View file

@ -1,29 +1,34 @@
# LXC Configuration # LXC Configuration
{ config, lib, pkgs, ... }: {
config,
lib,
pkgs,
...
}:
let let
cfg = config.virtualisation.lxc.lxcfs; cfg = config.virtualisation.lxc.lxcfs;
in { in
{
meta = { meta = {
maintainers = lib.teams.lxc.members; maintainers = lib.teams.lxc.members;
}; };
###### interface ###### interface
options.virtualisation.lxc.lxcfs = { options.virtualisation.lxc.lxcfs = {
enable = enable = lib.mkOption {
lib.mkOption { type = lib.types.bool;
type = lib.types.bool; default = false;
default = false; description = ''
description = '' This enables LXCFS, a FUSE filesystem for LXC.
This enables LXCFS, a FUSE filesystem for LXC. To use lxcfs in include the following configuration in your
To use lxcfs in include the following configuration in your container configuration:
container configuration: ```
``` virtualisation.lxc.defaultConfig = "lxc.include = ''${pkgs.lxcfs}/share/lxc/config/common.conf.d/00-lxcfs.conf";
virtualisation.lxc.defaultConfig = "lxc.include = ''${pkgs.lxcfs}/share/lxc/config/common.conf.d/00-lxcfs.conf"; ```
``` '';
''; };
};
}; };
###### implementation ###### implementation
@ -34,11 +39,11 @@ in {
before = [ "lxc.service" ]; before = [ "lxc.service" ];
restartIfChanged = false; restartIfChanged = false;
serviceConfig = { serviceConfig = {
ExecStartPre="${pkgs.coreutils}/bin/mkdir -p /var/lib/lxcfs"; ExecStartPre = "${pkgs.coreutils}/bin/mkdir -p /var/lib/lxcfs";
ExecStart="${pkgs.lxcfs}/bin/lxcfs /var/lib/lxcfs"; ExecStart = "${pkgs.lxcfs}/bin/lxcfs /var/lib/lxcfs";
ExecStopPost="-${pkgs.fuse}/bin/fusermount -u /var/lib/lxcfs"; ExecStopPost = "-${pkgs.fuse}/bin/fusermount -u /var/lib/lxcfs";
KillMode="process"; KillMode = "process";
Restart="on-failure"; Restart = "on-failure";
}; };
}; };
}; };

View file

@ -1,140 +1,154 @@
import ../make-test-python.nix ({ pkgs, lib, extra ? {}, name ? "incus-container", incus ? pkgs.incus-lts, ... } : import ../make-test-python.nix (
{
pkgs,
lib,
extra ? { },
name ? "incus-container",
incus ? pkgs.incus-lts,
...
}:
let let
releases = import ../../release.nix { releases = import ../../release.nix {
configuration = lib.recursiveUpdate { configuration = lib.recursiveUpdate {
# Building documentation makes the test unnecessarily take a longer time: # Building documentation makes the test unnecessarily take a longer time:
documentation.enable = lib.mkForce false; documentation.enable = lib.mkForce false;
boot.kernel.sysctl."net.ipv4.ip_forward" = "1"; boot.kernel.sysctl."net.ipv4.ip_forward" = "1";
} } extra;
extra;
};
container-image-metadata = "${releases.incusContainerMeta.${pkgs.stdenv.hostPlatform.system}}/tarball/nixos-system-${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 container-image-metadata = "${
'' releases.incusContainerMeta.${pkgs.stdenv.hostPlatform.system}
def instance_is_up(_) -> bool: }/tarball/nixos-system-${pkgs.stdenv.hostPlatform.system}.tar.xz";
status, _ = machine.execute("incus exec container --disable-stdin --force-interactive /run/current-system/sw/bin/systemctl -- is-system-running") container-image-rootfs = "${
return status == 0 releases.incusContainerImage.${pkgs.stdenv.hostPlatform.system}
}/nixos-lxc-image-${pkgs.stdenv.hostPlatform.system}.squashfs";
in
{
inherit name;
def set_container(config): meta = {
machine.succeed(f"incus config set container {config}") maintainers = lib.teams.lxc.members;
machine.succeed("incus restart container") };
with machine.nested("Waiting for instance to start and be usable"):
retry(instance_is_up)
def check_sysctl(instance): nodes.machine =
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] virtualisation = {
assert "1" == sysctl, f"systemd-sysctl configuration not correctly applied, {sysctl} != 1" # Ensure test VM has enough resources for creating and managing guests
cores = 2;
memorySize = 1024;
diskSize = 4096;
machine.wait_for_unit("incus.service") incus = {
enable = true;
package = incus;
};
};
networking.nftables.enable = true;
};
# no preseed should mean no service testScript = # python
machine.fail("systemctl status incus-preseed.service") ''
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
machine.succeed("incus admin init --minimal") def set_container(config):
machine.succeed(f"incus config set container {config}")
with subtest("Container image can be imported"): machine.succeed("incus restart container")
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"): with machine.nested("Waiting for instance to start and be usable"):
retry(instance_is_up) retry(instance_is_up)
machine.fail("incus exec container test -- -e /run/systemd/system/service.d/zzz-lxc-service.conf") def check_sysctl(instance):
target = machine.succeed("incus exec container readlink -- -f /run/systemd/system/systemd-binfmt.service").strip() with subtest("systemd sysctl settings are applied"):
assert target == "/dev/null", "lxc generator did not correctly mask /run/systemd/system/systemd-binfmt.service" 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"
check_sysctl("container") machine.wait_for_unit("incus.service")
with subtest("lxc-container generator configures privileged container"): # no preseed should mean no service
machine.execute("incus delete --force container") machine.fail("systemctl status incus-preseed.service")
machine.succeed("incus launch nixos container --config security.privileged=true")
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"): with machine.nested("Waiting for instance to start and be usable"):
retry(instance_is_up) retry(instance_is_up)
machine.succeed("echo true | incus exec container /run/current-system/sw/bin/bash -")
machine.succeed("incus exec container test -- -e /run/systemd/system/service.d/zzz-lxc-service.conf") 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'")
check_sysctl("container") 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}"
with subtest("softDaemonRestart"): set_container("limits.cpu 2")
with subtest("Instance remains running when softDaemonRestart is enabled and services is stopped"): cpuinfo = machine.succeed("incus exec container grep -- -c ^processor /proc/cpuinfo").strip()
pid = machine.succeed("incus info container | grep 'PID'").split(":")[1].strip() assert cpuinfo == "2", f"Wrong number of CPUs reported from /proc/cpuinfo, want: 2, got: {cpuinfo}"
machine.succeed(f"ps {pid}")
machine.succeed("systemctl stop incus") with subtest("Container memory limits can be managed"):
machine.succeed(f"ps {pid}") 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}")
'';
}
)

View file

@ -1,69 +1,78 @@
import ../make-test-python.nix ({ pkgs, lib, incus ? pkgs.incus-lts, ... } : import ../make-test-python.nix (
{
pkgs,
lib,
incus ? pkgs.incus-lts,
...
}:
{ {
name = "incus-openvswitch"; name = "incus-openvswitch";
meta = { meta = {
maintainers = lib.teams.lxc.members; maintainers = lib.teams.lxc.members;
};
nodes.machine = { lib, ... }: {
virtualisation = {
incus = {
enable = true;
package = incus;
};
vswitch.enable = true;
incus.preseed = {
networks = [
{
name = "nixostestbr0";
type = "bridge";
config = {
"bridge.driver" = "openvswitch";
"ipv4.address" = "10.0.100.1/24";
"ipv4.nat" = "true";
};
}
];
profiles = [
{
name = "nixostest_default";
devices = {
eth0 = {
name = "eth0";
network = "nixostestbr0";
type = "nic";
};
root = {
path = "/";
pool = "default";
size = "35GiB";
type = "disk";
};
};
}
];
storage_pools = [
{
name = "nixostest_pool";
driver = "dir";
}
];
};
}; };
networking.nftables.enable = true;
};
testScript = '' nodes.machine =
machine.wait_for_unit("incus.service") { lib, ... }:
machine.wait_for_unit("incus-preseed.service") {
virtualisation = {
incus = {
enable = true;
package = incus;
};
with subtest("Verify openvswitch bridge"): vswitch.enable = true;
machine.succeed("incus network info nixostestbr0") incus.preseed = {
networks = [
{
name = "nixostestbr0";
type = "bridge";
config = {
"bridge.driver" = "openvswitch";
"ipv4.address" = "10.0.100.1/24";
"ipv4.nat" = "true";
};
}
];
profiles = [
{
name = "nixostest_default";
devices = {
eth0 = {
name = "eth0";
network = "nixostestbr0";
type = "nic";
};
root = {
path = "/";
pool = "default";
size = "35GiB";
type = "disk";
};
};
}
];
storage_pools = [
{
name = "nixostest_pool";
driver = "dir";
}
];
};
};
networking.nftables.enable = true;
};
with subtest("Verify openvswitch bridge"): testScript = ''
machine.succeed("ovs-vsctl br-exists nixostestbr0") machine.wait_for_unit("incus.service")
''; machine.wait_for_unit("incus-preseed.service")
})
with subtest("Verify openvswitch bridge"):
machine.succeed("incus network info nixostestbr0")
with subtest("Verify openvswitch bridge"):
machine.succeed("ovs-vsctl br-exists nixostestbr0")
'';
}
)

View file

@ -1,32 +1,41 @@
import ../make-test-python.nix ({ pkgs, lib, incus ? pkgs.incus-lts, ... } : import ../make-test-python.nix (
{
pkgs,
lib,
incus ? pkgs.incus-lts,
...
}:
{ {
name = "incus-socket-activated"; name = "incus-socket-activated";
meta = { meta = {
maintainers = lib.teams.lxc.members; maintainers = lib.teams.lxc.members;
};
nodes.machine = { lib, ... }: {
virtualisation = {
incus = {
enable = true;
package = incus;
socketActivation = true;
};
}; };
networking.nftables.enable = true;
};
testScript = '' nodes.machine =
machine.wait_for_unit("incus.socket") { lib, ... }:
{
virtualisation = {
incus = {
enable = true;
package = incus;
socketActivation = true;
};
};
networking.nftables.enable = true;
};
# ensure service is not running by default testScript = ''
machine.fail("systemctl is-active incus.service") machine.wait_for_unit("incus.socket")
machine.fail("systemctl is-active incus-preseed.service")
# access the socket and ensure the service starts # ensure service is not running by default
machine.succeed("incus list") machine.fail("systemctl is-active incus.service")
machine.wait_for_unit("incus.service") machine.fail("systemctl is-active incus-preseed.service")
'';
}) # access the socket and ensure the service starts
machine.succeed("incus list")
machine.wait_for_unit("incus.service")
'';
}
)

View file

@ -1,67 +1,84 @@
import ../make-test-python.nix ({ pkgs, lib, incus ? pkgs.incus-lts, ... }: { import ../make-test-python.nix (
name = "incus-ui"; {
pkgs,
lib,
incus ? pkgs.incus-lts,
...
}:
{
name = "incus-ui";
meta = { meta = {
maintainers = lib.teams.lxc.members; maintainers = lib.teams.lxc.members;
};
nodes.machine = { lib, ... }: {
virtualisation = {
incus = {
enable = true;
package = incus;
};
incus.ui.enable = true;
}; };
networking.nftables.enable = true;
environment.systemPackages = nodes.machine =
let { lib, ... }:
seleniumScript = pkgs.writers.writePython3Bin "selenium-script" {
{ virtualisation = {
libraries = with pkgs.python3Packages; [ selenium ]; incus = {
} '' enable = true;
from selenium import webdriver package = incus;
from selenium.webdriver.common.by import By };
from selenium.webdriver.firefox.options import Options incus.ui.enable = true;
from selenium.webdriver.support.ui import WebDriverWait };
networking.nftables.enable = true;
options = Options() environment.systemPackages =
options.add_argument("--headless") let
service = webdriver.FirefoxService(executable_path="${lib.getExe pkgs.geckodriver}") # noqa: E501 seleniumScript =
pkgs.writers.writePython3Bin "selenium-script"
{
libraries = with pkgs.python3Packages; [ selenium ];
}
''
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.firefox.options import Options
from selenium.webdriver.support.ui import WebDriverWait
driver = webdriver.Firefox(options=options, service=service) options = Options()
driver.implicitly_wait(10) options.add_argument("--headless")
driver.get("https://localhost:8443/ui") service = webdriver.FirefoxService(executable_path="${lib.getExe pkgs.geckodriver}") # noqa: E501
wait = WebDriverWait(driver, 60) driver = webdriver.Firefox(options=options, service=service)
driver.implicitly_wait(10)
driver.get("https://localhost:8443/ui")
assert len(driver.find_elements(By.CLASS_NAME, "l-application")) > 0 wait = WebDriverWait(driver, 60)
assert len(driver.find_elements(By.CLASS_NAME, "l-navigation__drawer")) > 0
driver.close() assert len(driver.find_elements(By.CLASS_NAME, "l-application")) > 0
''; assert len(driver.find_elements(By.CLASS_NAME, "l-navigation__drawer")) > 0
in
with pkgs; [ curl firefox-unwrapped geckodriver seleniumScript ];
};
driver.close()
'';
in
with pkgs;
[
curl
firefox-unwrapped
geckodriver
seleniumScript
];
};
testScript = '' testScript = ''
machine.wait_for_unit("sockets.target") machine.wait_for_unit("sockets.target")
machine.wait_for_unit("incus.service") machine.wait_for_unit("incus.service")
machine.wait_for_file("/var/lib/incus/unix.socket") machine.wait_for_file("/var/lib/incus/unix.socket")
# Configure incus listen address # Configure incus listen address
machine.succeed("incus config set core.https_address :8443") machine.succeed("incus config set core.https_address :8443")
machine.succeed("systemctl restart incus") machine.succeed("systemctl restart incus")
# Check that the INCUS_UI environment variable is populated in the systemd unit # Check that the INCUS_UI environment variable is populated in the systemd unit
machine.succeed("cat /etc/systemd/system/incus.service | grep 'INCUS_UI'") machine.succeed("cat /etc/systemd/system/incus.service | grep 'INCUS_UI'")
# Ensure the endpoint returns an HTML page with 'Incus UI' in the title # Ensure the endpoint returns an HTML page with 'Incus UI' in the title
machine.succeed("curl -kLs https://localhost:8443/ui | grep '<title>Incus UI</title>'") machine.succeed("curl -kLs https://localhost:8443/ui | grep '<title>Incus UI</title>'")
# Ensure the application is actually rendered by the Javascript # Ensure the application is actually rendered by the Javascript
machine.succeed("PYTHONUNBUFFERED=1 selenium-script") machine.succeed("PYTHONUNBUFFERED=1 selenium-script")
''; '';
}) }
)

View file

@ -1,86 +1,95 @@
import ../make-test-python.nix ({ pkgs, lib, incus ? pkgs.incus-lts, ... }: import ../make-test-python.nix (
{
pkgs,
lib,
incus ? pkgs.incus-lts,
...
}:
let let
releases = import ../../release.nix { releases = import ../../release.nix {
configuration = { configuration = {
# Building documentation makes the test unnecessarily take a longer time: # Building documentation makes the test unnecessarily take a longer time:
documentation.enable = lib.mkForce false; documentation.enable = lib.mkForce false;
# Our tests require `grep` & friends: # Our tests require `grep` & friends:
environment.systemPackages = with pkgs; [busybox]; environment.systemPackages = with pkgs; [ busybox ];
};
};
vm-image-metadata = releases.incusVirtualMachineImageMeta.${pkgs.stdenv.hostPlatform.system};
vm-image-disk = releases.incusVirtualMachineImage.${pkgs.stdenv.hostPlatform.system};
instance-name = "instance1";
in
{
name = "incus-virtual-machine";
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 vm-image-metadata = releases.incusVirtualMachineImageMeta.${pkgs.stdenv.hostPlatform.system};
'' vm-image-disk = releases.incusVirtualMachineImage.${pkgs.stdenv.hostPlatform.system};
def instance_is_up(_) -> bool:
status, _ = machine.execute("incus exec ${instance-name} --disable-stdin --force-interactive /run/current-system/sw/bin/systemctl -- is-system-running")
return status == 0
machine.wait_for_unit("incus.service") instance-name = "instance1";
in
{
name = "incus-virtual-machine";
machine.succeed("incus admin init --minimal") meta = {
maintainers = lib.teams.lxc.members;
};
with subtest("virtual-machine image can be imported"): nodes.machine =
machine.succeed("incus image import ${vm-image-metadata}/*/*.tar.xz ${vm-image-disk}/nixos.qcow2 --alias nixos") { ... }:
{
virtualisation = {
# Ensure test VM has enough resources for creating and managing guests
cores = 2;
memorySize = 1024;
diskSize = 4096;
with subtest("virtual-machine can be created"): incus = {
machine.succeed("incus create nixos ${instance-name} --vm --config limits.memory=512MB --config security.secureboot=false") enable = true;
package = incus;
};
};
networking.nftables.enable = true;
};
with subtest("virtual tpm can be configured"): testScript = # python
machine.succeed("incus config device add ${instance-name} vtpm tpm path=/dev/tpm0") ''
def instance_is_up(_) -> bool:
status, _ = machine.execute("incus exec ${instance-name} --disable-stdin --force-interactive /run/current-system/sw/bin/systemctl -- is-system-running")
return status == 0
with subtest("virtual-machine can be launched and become available"): machine.wait_for_unit("incus.service")
machine.succeed("incus start ${instance-name}")
with machine.nested("Waiting for instance to start and be usable"):
retry(instance_is_up)
with subtest("incus-agent is started"): machine.succeed("incus admin init --minimal")
machine.succeed("incus exec ${instance-name} systemctl is-active incus-agent")
with subtest("incus-agent has a valid path"): with subtest("virtual-machine image can be imported"):
machine.succeed("incus exec ${instance-name} -- bash -c 'true'") machine.succeed("incus image import ${vm-image-metadata}/*/*.tar.xz ${vm-image-disk}/nixos.qcow2 --alias nixos")
with subtest("guest supports cpu hotplug"): with subtest("virtual-machine can be created"):
machine.succeed("incus config set ${instance-name} limits.cpu=1") machine.succeed("incus create nixos ${instance-name} --vm --config limits.memory=512MB --config security.secureboot=false")
count = int(machine.succeed("incus exec ${instance-name} -- nproc").strip())
assert count == 1, f"Wrong number of CPUs reported, want: 1, got: {count}"
machine.succeed("incus config set ${instance-name} limits.cpu=2") with subtest("virtual tpm can be configured"):
count = int(machine.succeed("incus exec ${instance-name} -- nproc").strip()) machine.succeed("incus config device add ${instance-name} vtpm tpm path=/dev/tpm0")
assert count == 2, f"Wrong number of CPUs reported, want: 2, got: {count}"
with subtest("Instance remains running when softDaemonRestart is enabled and services is stopped"): with subtest("virtual-machine can be launched and become available"):
pid = machine.succeed("incus info ${instance-name} | grep 'PID'").split(":")[1].strip() machine.succeed("incus start ${instance-name}")
machine.succeed(f"ps {pid}") with machine.nested("Waiting for instance to start and be usable"):
machine.succeed("systemctl stop incus") retry(instance_is_up)
machine.succeed(f"ps {pid}")
''; with subtest("incus-agent is started"):
}) machine.succeed("incus exec ${instance-name} systemctl is-active incus-agent")
with subtest("incus-agent has a valid path"):
machine.succeed("incus exec ${instance-name} -- bash -c 'true'")
with subtest("guest supports cpu hotplug"):
machine.succeed("incus config set ${instance-name} limits.cpu=1")
count = int(machine.succeed("incus exec ${instance-name} -- nproc").strip())
assert count == 1, f"Wrong number of CPUs reported, want: 1, got: {count}"
machine.succeed("incus config set ${instance-name} limits.cpu=2")
count = int(machine.succeed("incus exec ${instance-name} -- nproc").strip())
assert count == 2, f"Wrong number of CPUs reported, want: 2, got: {count}"
with subtest("Instance remains running when softDaemonRestart is enabled and services is stopped"):
pid = machine.succeed("incus info ${instance-name} | grep 'PID'").split(":")[1].strip()
machine.succeed(f"ps {pid}")
machine.succeed("systemctl stop incus")
machine.succeed(f"ps {pid}")
'';
}
)

View file

@ -1,4 +1,14 @@
{ stdenvNoCC, lib, src, version, makeWrapper, coreutils, findutils, gnugrep, systemd }: {
stdenvNoCC,
lib,
src,
version,
makeWrapper,
coreutils,
findutils,
gnugrep,
systemd,
}:
stdenvNoCC.mkDerivation { stdenvNoCC.mkDerivation {
name = "distrobuilder-nixos-generator"; name = "distrobuilder-nixos-generator";
@ -14,6 +24,13 @@ stdenvNoCC.mkDerivation {
installPhase = '' installPhase = ''
install -D -m 0555 distrobuilder/lxc.generator $out/lib/systemd/system-generators/lxc install -D -m 0555 distrobuilder/lxc.generator $out/lib/systemd/system-generators/lxc
wrapProgram $out/lib/systemd/system-generators/lxc --prefix PATH : ${lib.makeBinPath [coreutils findutils gnugrep systemd]}:${systemd}/lib/systemd wrapProgram $out/lib/systemd/system-generators/lxc --prefix PATH : ${
lib.makeBinPath [
coreutils
findutils
gnugrep
systemd
]
}:${systemd}/lib/systemd
''; '';
} }

View file

@ -1,34 +1,37 @@
{ lib {
, buildGoModule lib,
, callPackage buildGoModule,
, cdrkit callPackage,
, coreutils cdrkit,
, debootstrap coreutils,
, fetchFromGitHub debootstrap,
, gnupg fetchFromGitHub,
, gnutar gnupg,
, hivex gnutar,
, makeWrapper hivex,
, nixosTests makeWrapper,
, pkg-config nixosTests,
, squashfsTools pkg-config,
, stdenv squashfsTools,
, wimlib stdenv,
wimlib,
}: }:
let let
bins = [ bins =
coreutils [
debootstrap coreutils
gnupg debootstrap
gnutar gnupg
squashfsTools gnutar
] ++ lib.optionals stdenv.hostPlatform.isx86_64 [ squashfsTools
# repack-windows deps ]
cdrkit ++ lib.optionals stdenv.hostPlatform.isx86_64 [
hivex # repack-windows deps
wimlib cdrkit
]; hivex
wimlib
];
in in
buildGoModule rec { buildGoModule rec {
pname = "distrobuilder"; pname = "distrobuilder";
@ -46,7 +49,6 @@ buildGoModule rec {
buildInputs = bins; buildInputs = bins;
# tests require a local keyserver (mkg20001/nixpkgs branch distrobuilder-with-tests) but gpg is currently broken in tests # tests require a local keyserver (mkg20001/nixpkgs branch distrobuilder-with-tests) but gpg is currently broken in tests
doCheck = false; doCheck = false;

View file

@ -1,12 +1,13 @@
{ lib {
, stdenv lib,
, fetchFromGitHub stdenv,
, fetchYarnDeps fetchFromGitHub,
, nodejs fetchYarnDeps,
, fixup-yarn-lock nodejs,
, yarn fixup-yarn-lock,
, nixosTests yarn,
, git nixosTests,
git,
}: }:
stdenv.mkDerivation rec { stdenv.mkDerivation rec {

View file

@ -53,7 +53,12 @@ stdenv.mkDerivation rec {
postInstall = '' postInstall = ''
# `mount` hook requires access to the `mount` command from `util-linux` and `readlink` from `coreutils`: # `mount` hook requires access to the `mount` command from `util-linux` and `readlink` from `coreutils`:
wrapProgram "$out/share/lxcfs/lxc.mount.hook" --prefix PATH : ${lib.makeBinPath [ coreutils util-linux ]} wrapProgram "$out/share/lxcfs/lxc.mount.hook" --prefix PATH : ${
lib.makeBinPath [
coreutils
util-linux
]
}
''; '';
postFixup = '' postFixup = ''