uboot: (firmwareOdroidC2/C4) don't invoke patch tool, use patches = [] instead
https://github.com/NixOS/nixpkgs/blob/master/pkgs/stdenv/generic/setup.sh#L948 this can do it nicely. Signed-off-by: Anton Arapov <anton@deadbeef.mx>
This commit is contained in:
commit
56de2bcd43
30691 changed files with 3076956 additions and 0 deletions
189
nixos/tests/3proxy.nix
Normal file
189
nixos/tests/3proxy.nix
Normal file
|
|
@ -0,0 +1,189 @@
|
|||
import ./make-test-python.nix ({ pkgs, ...} : {
|
||||
name = "3proxy";
|
||||
meta = with pkgs.lib.maintainers; {
|
||||
maintainers = [ misuzu ];
|
||||
};
|
||||
|
||||
nodes = {
|
||||
peer0 = { lib, ... }: {
|
||||
networking.useDHCP = false;
|
||||
networking.interfaces.eth1 = {
|
||||
ipv4.addresses = [
|
||||
{
|
||||
address = "192.168.0.1";
|
||||
prefixLength = 24;
|
||||
}
|
||||
{
|
||||
address = "216.58.211.111";
|
||||
prefixLength = 24;
|
||||
}
|
||||
];
|
||||
};
|
||||
};
|
||||
|
||||
peer1 = { lib, ... }: {
|
||||
networking.useDHCP = false;
|
||||
networking.interfaces.eth1 = {
|
||||
ipv4.addresses = [
|
||||
{
|
||||
address = "192.168.0.2";
|
||||
prefixLength = 24;
|
||||
}
|
||||
{
|
||||
address = "216.58.211.112";
|
||||
prefixLength = 24;
|
||||
}
|
||||
];
|
||||
};
|
||||
# test that binding to [::] is working when ipv6 is disabled
|
||||
networking.enableIPv6 = false;
|
||||
services._3proxy = {
|
||||
enable = true;
|
||||
services = [
|
||||
{
|
||||
type = "admin";
|
||||
bindPort = 9999;
|
||||
auth = [ "none" ];
|
||||
}
|
||||
{
|
||||
type = "proxy";
|
||||
bindPort = 3128;
|
||||
auth = [ "none" ];
|
||||
}
|
||||
];
|
||||
};
|
||||
networking.firewall.allowedTCPPorts = [ 3128 9999 ];
|
||||
};
|
||||
|
||||
peer2 = { lib, ... }: {
|
||||
networking.useDHCP = false;
|
||||
networking.interfaces.eth1 = {
|
||||
ipv4.addresses = [
|
||||
{
|
||||
address = "192.168.0.3";
|
||||
prefixLength = 24;
|
||||
}
|
||||
{
|
||||
address = "216.58.211.113";
|
||||
prefixLength = 24;
|
||||
}
|
||||
];
|
||||
};
|
||||
services._3proxy = {
|
||||
enable = true;
|
||||
services = [
|
||||
{
|
||||
type = "admin";
|
||||
bindPort = 9999;
|
||||
auth = [ "none" ];
|
||||
}
|
||||
{
|
||||
type = "proxy";
|
||||
bindPort = 3128;
|
||||
auth = [ "iponly" ];
|
||||
acl = [
|
||||
{
|
||||
rule = "allow";
|
||||
}
|
||||
];
|
||||
}
|
||||
];
|
||||
};
|
||||
networking.firewall.allowedTCPPorts = [ 3128 9999 ];
|
||||
};
|
||||
|
||||
peer3 = { lib, ... }: {
|
||||
networking.useDHCP = false;
|
||||
networking.interfaces.eth1 = {
|
||||
ipv4.addresses = [
|
||||
{
|
||||
address = "192.168.0.4";
|
||||
prefixLength = 24;
|
||||
}
|
||||
{
|
||||
address = "216.58.211.114";
|
||||
prefixLength = 24;
|
||||
}
|
||||
];
|
||||
};
|
||||
services._3proxy = {
|
||||
enable = true;
|
||||
usersFile = pkgs.writeText "3proxy.passwd" ''
|
||||
admin:CR:$1$.GUV4Wvk$WnEVQtaqutD9.beO5ar1W/
|
||||
'';
|
||||
services = [
|
||||
{
|
||||
type = "admin";
|
||||
bindPort = 9999;
|
||||
auth = [ "none" ];
|
||||
}
|
||||
{
|
||||
type = "proxy";
|
||||
bindPort = 3128;
|
||||
auth = [ "strong" ];
|
||||
acl = [
|
||||
{
|
||||
rule = "allow";
|
||||
}
|
||||
];
|
||||
}
|
||||
];
|
||||
};
|
||||
networking.firewall.allowedTCPPorts = [ 3128 9999 ];
|
||||
};
|
||||
};
|
||||
|
||||
testScript = ''
|
||||
start_all()
|
||||
|
||||
peer0.wait_for_unit("network-online.target")
|
||||
|
||||
peer1.wait_for_unit("3proxy.service")
|
||||
peer1.wait_for_open_port("9999")
|
||||
|
||||
# test none auth
|
||||
peer0.succeed(
|
||||
"${pkgs.wget}/bin/wget -e use_proxy=yes -e http_proxy=http://192.168.0.2:3128 -S -O /dev/null http://216.58.211.112:9999"
|
||||
)
|
||||
peer0.succeed(
|
||||
"${pkgs.wget}/bin/wget -e use_proxy=yes -e http_proxy=http://192.168.0.2:3128 -S -O /dev/null http://192.168.0.2:9999"
|
||||
)
|
||||
peer0.succeed(
|
||||
"${pkgs.wget}/bin/wget -e use_proxy=yes -e http_proxy=http://192.168.0.2:3128 -S -O /dev/null http://127.0.0.1:9999"
|
||||
)
|
||||
|
||||
peer2.wait_for_unit("3proxy.service")
|
||||
peer2.wait_for_open_port("9999")
|
||||
|
||||
# test iponly auth
|
||||
peer0.succeed(
|
||||
"${pkgs.wget}/bin/wget -e use_proxy=yes -e http_proxy=http://192.168.0.3:3128 -S -O /dev/null http://216.58.211.113:9999"
|
||||
)
|
||||
peer0.fail(
|
||||
"${pkgs.wget}/bin/wget -e use_proxy=yes -e http_proxy=http://192.168.0.3:3128 -S -O /dev/null http://192.168.0.3:9999"
|
||||
)
|
||||
peer0.fail(
|
||||
"${pkgs.wget}/bin/wget -e use_proxy=yes -e http_proxy=http://192.168.0.3:3128 -S -O /dev/null http://127.0.0.1:9999"
|
||||
)
|
||||
|
||||
peer3.wait_for_unit("3proxy.service")
|
||||
peer3.wait_for_open_port("9999")
|
||||
|
||||
# test strong auth
|
||||
peer0.succeed(
|
||||
"${pkgs.wget}/bin/wget -e use_proxy=yes -e http_proxy=http://admin:bigsecret\@192.168.0.4:3128 -S -O /dev/null http://216.58.211.114:9999"
|
||||
)
|
||||
peer0.fail(
|
||||
"${pkgs.wget}/bin/wget -e use_proxy=yes -e http_proxy=http://admin:bigsecret\@192.168.0.4:3128 -S -O /dev/null http://192.168.0.4:9999"
|
||||
)
|
||||
peer0.fail(
|
||||
"${pkgs.wget}/bin/wget -e use_proxy=yes -e http_proxy=http://192.168.0.4:3128 -S -O /dev/null http://216.58.211.114:9999"
|
||||
)
|
||||
peer0.fail(
|
||||
"${pkgs.wget}/bin/wget -e use_proxy=yes -e http_proxy=http://192.168.0.4:3128 -S -O /dev/null http://192.168.0.4:9999"
|
||||
)
|
||||
peer0.fail(
|
||||
"${pkgs.wget}/bin/wget -e use_proxy=yes -e http_proxy=http://192.168.0.4:3128 -S -O /dev/null http://127.0.0.1:9999"
|
||||
)
|
||||
'';
|
||||
})
|
||||
597
nixos/tests/acme.nix
Normal file
597
nixos/tests/acme.nix
Normal file
|
|
@ -0,0 +1,597 @@
|
|||
import ./make-test-python.nix ({ pkgs, lib, ... }: let
|
||||
commonConfig = ./common/acme/client;
|
||||
|
||||
dnsServerIP = nodes: nodes.dnsserver.config.networking.primaryIPAddress;
|
||||
|
||||
dnsScript = nodes: let
|
||||
dnsAddress = dnsServerIP nodes;
|
||||
in pkgs.writeShellScript "dns-hook.sh" ''
|
||||
set -euo pipefail
|
||||
echo '[INFO]' "[$2]" 'dns-hook.sh' $*
|
||||
if [ "$1" = "present" ]; then
|
||||
${pkgs.curl}/bin/curl --data '{"host": "'"$2"'", "value": "'"$3"'"}' http://${dnsAddress}:8055/set-txt
|
||||
else
|
||||
${pkgs.curl}/bin/curl --data '{"host": "'"$2"'"}' http://${dnsAddress}:8055/clear-txt
|
||||
fi
|
||||
'';
|
||||
|
||||
dnsConfig = nodes: {
|
||||
dnsProvider = "exec";
|
||||
dnsPropagationCheck = false;
|
||||
credentialsFile = pkgs.writeText "wildcard.env" ''
|
||||
EXEC_PATH=${dnsScript nodes}
|
||||
EXEC_POLLING_INTERVAL=1
|
||||
EXEC_PROPAGATION_TIMEOUT=1
|
||||
EXEC_SEQUENCE_INTERVAL=1
|
||||
'';
|
||||
};
|
||||
|
||||
documentRoot = pkgs.runCommand "docroot" {} ''
|
||||
mkdir -p "$out"
|
||||
echo hello world > "$out/index.html"
|
||||
'';
|
||||
|
||||
vhostBase = {
|
||||
forceSSL = true;
|
||||
locations."/".root = documentRoot;
|
||||
};
|
||||
|
||||
vhostBaseHttpd = {
|
||||
forceSSL = true;
|
||||
inherit documentRoot;
|
||||
};
|
||||
|
||||
# Base specialisation config for testing general ACME features
|
||||
webserverBasicConfig = {
|
||||
services.nginx.enable = true;
|
||||
services.nginx.virtualHosts."a.example.test" = vhostBase // {
|
||||
enableACME = true;
|
||||
};
|
||||
};
|
||||
|
||||
# Generate specialisations for testing a web server
|
||||
mkServerConfigs = { server, group, vhostBaseData, extraConfig ? {} }: let
|
||||
baseConfig = { nodes, config, specialConfig ? {} }: lib.mkMerge [
|
||||
{
|
||||
security.acme = {
|
||||
defaults = (dnsConfig nodes);
|
||||
# One manual wildcard cert
|
||||
certs."example.test" = {
|
||||
domain = "*.example.test";
|
||||
};
|
||||
};
|
||||
|
||||
users.users."${config.services."${server}".user}".extraGroups = ["acme"];
|
||||
|
||||
services."${server}" = {
|
||||
enable = true;
|
||||
virtualHosts = {
|
||||
# Run-of-the-mill vhost using HTTP-01 validation
|
||||
"${server}-http.example.test" = vhostBaseData // {
|
||||
serverAliases = [ "${server}-http-alias.example.test" ];
|
||||
enableACME = true;
|
||||
};
|
||||
|
||||
# Another which inherits the DNS-01 config
|
||||
"${server}-dns.example.test" = vhostBaseData // {
|
||||
serverAliases = [ "${server}-dns-alias.example.test" ];
|
||||
enableACME = true;
|
||||
# Set acmeRoot to null instead of using the default of "/var/lib/acme/acme-challenge"
|
||||
# webroot + dnsProvider are mutually exclusive.
|
||||
acmeRoot = null;
|
||||
};
|
||||
|
||||
# One using the wildcard certificate
|
||||
"${server}-wildcard.example.test" = vhostBaseData // {
|
||||
serverAliases = [ "${server}-wildcard-alias.example.test" ];
|
||||
useACMEHost = "example.test";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
# Used to determine if service reload was triggered
|
||||
systemd.targets."test-renew-${server}" = {
|
||||
wants = [ "acme-${server}-http.example.test.service" ];
|
||||
after = [ "acme-${server}-http.example.test.service" "${server}-config-reload.service" ];
|
||||
};
|
||||
}
|
||||
specialConfig
|
||||
extraConfig
|
||||
];
|
||||
in {
|
||||
"${server}".configuration = { nodes, config, ... }: baseConfig {
|
||||
inherit nodes config;
|
||||
};
|
||||
|
||||
# Test that server reloads when an alias is removed (and subsequently test removal works in acme)
|
||||
"${server}-remove-alias".configuration = { nodes, config, ... }: baseConfig {
|
||||
inherit nodes config;
|
||||
specialConfig = {
|
||||
# Remove an alias, but create a standalone vhost in its place for testing.
|
||||
# This configuration results in certificate errors as useACMEHost does not imply
|
||||
# append extraDomains, and thus we can validate the SAN is removed.
|
||||
services."${server}" = {
|
||||
virtualHosts."${server}-http.example.test".serverAliases = lib.mkForce [];
|
||||
virtualHosts."${server}-http-alias.example.test" = vhostBaseData // {
|
||||
useACMEHost = "${server}-http.example.test";
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
# Test that the server reloads when only the acme configuration is changed.
|
||||
"${server}-change-acme-conf".configuration = { nodes, config, ... }: baseConfig {
|
||||
inherit nodes config;
|
||||
specialConfig = {
|
||||
security.acme.certs."${server}-http.example.test" = {
|
||||
keyType = "ec384";
|
||||
# Also test that postRun is exec'd as root
|
||||
postRun = "id | grep root";
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
in {
|
||||
name = "acme";
|
||||
meta.maintainers = lib.teams.acme.members;
|
||||
|
||||
nodes = {
|
||||
# The fake ACME server which will respond to client requests
|
||||
acme = { nodes, ... }: {
|
||||
imports = [ ./common/acme/server ];
|
||||
networking.nameservers = lib.mkForce [ (dnsServerIP nodes) ];
|
||||
};
|
||||
|
||||
# A fake DNS server which can be configured with records as desired
|
||||
# Used to test DNS-01 challenge
|
||||
dnsserver = { nodes, ... }: {
|
||||
networking.firewall.allowedTCPPorts = [ 8055 53 ];
|
||||
networking.firewall.allowedUDPPorts = [ 53 ];
|
||||
systemd.services.pebble-challtestsrv = {
|
||||
enable = true;
|
||||
description = "Pebble ACME challenge test server";
|
||||
wantedBy = [ "network.target" ];
|
||||
serviceConfig = {
|
||||
ExecStart = "${pkgs.pebble}/bin/pebble-challtestsrv -dns01 ':53' -defaultIPv6 '' -defaultIPv4 '${nodes.webserver.config.networking.primaryIPAddress}'";
|
||||
# Required to bind on privileged ports.
|
||||
AmbientCapabilities = [ "CAP_NET_BIND_SERVICE" ];
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
# A web server which will be the node requesting certs
|
||||
webserver = { nodes, config, ... }: {
|
||||
imports = [ commonConfig ];
|
||||
networking.nameservers = lib.mkForce [ (dnsServerIP nodes) ];
|
||||
networking.firewall.allowedTCPPorts = [ 80 443 ];
|
||||
|
||||
# OpenSSL will be used for more thorough certificate validation
|
||||
environment.systemPackages = [ pkgs.openssl ];
|
||||
|
||||
# Set log level to info so that we can see when the service is reloaded
|
||||
services.nginx.logError = "stderr info";
|
||||
|
||||
specialisation = {
|
||||
# First derivation used to test general ACME features
|
||||
general.configuration = { ... }: let
|
||||
caDomain = nodes.acme.config.test-support.acme.caDomain;
|
||||
email = config.security.acme.defaults.email;
|
||||
# Exit 99 to make it easier to track if this is the reason a renew failed
|
||||
accountCreateTester = ''
|
||||
test -e accounts/${caDomain}/${email}/account.json || exit 99
|
||||
'';
|
||||
in lib.mkMerge [
|
||||
webserverBasicConfig
|
||||
{
|
||||
# Used to test that account creation is collated into one service.
|
||||
# These should not run until after acme-finished-a.example.test.target
|
||||
systemd.services."b.example.test".preStart = accountCreateTester;
|
||||
systemd.services."c.example.test".preStart = accountCreateTester;
|
||||
|
||||
services.nginx.virtualHosts."b.example.test" = vhostBase // {
|
||||
enableACME = true;
|
||||
};
|
||||
services.nginx.virtualHosts."c.example.test" = vhostBase // {
|
||||
enableACME = true;
|
||||
};
|
||||
}
|
||||
];
|
||||
|
||||
# Test OCSP Stapling
|
||||
ocsp-stapling.configuration = { ... }: lib.mkMerge [
|
||||
webserverBasicConfig
|
||||
{
|
||||
security.acme.certs."a.example.test".ocspMustStaple = true;
|
||||
services.nginx.virtualHosts."a.example.test" = {
|
||||
extraConfig = ''
|
||||
ssl_stapling on;
|
||||
ssl_stapling_verify on;
|
||||
'';
|
||||
};
|
||||
}
|
||||
];
|
||||
|
||||
# Validate service relationships by adding a slow start service to nginx' wants.
|
||||
# Reproducer for https://github.com/NixOS/nixpkgs/issues/81842
|
||||
slow-startup.configuration = { ... }: lib.mkMerge [
|
||||
webserverBasicConfig
|
||||
{
|
||||
systemd.services.my-slow-service = {
|
||||
wantedBy = [ "multi-user.target" "nginx.service" ];
|
||||
before = [ "nginx.service" ];
|
||||
preStart = "sleep 5";
|
||||
script = "${pkgs.python3}/bin/python -m http.server";
|
||||
};
|
||||
|
||||
services.nginx.virtualHosts."slow.example.test" = {
|
||||
forceSSL = true;
|
||||
enableACME = true;
|
||||
locations."/".proxyPass = "http://localhost:8000";
|
||||
};
|
||||
}
|
||||
];
|
||||
|
||||
# Test lego internal server (listenHTTP option)
|
||||
# Also tests useRoot option
|
||||
lego-server.configuration = { ... }: {
|
||||
security.acme.useRoot = true;
|
||||
security.acme.certs."lego.example.test" = {
|
||||
listenHTTP = ":80";
|
||||
group = "nginx";
|
||||
};
|
||||
services.nginx.enable = true;
|
||||
services.nginx.virtualHosts."lego.example.test" = {
|
||||
useACMEHost = "lego.example.test";
|
||||
onlySSL = true;
|
||||
};
|
||||
};
|
||||
|
||||
# Test compatiblity with Caddy
|
||||
# It only supports useACMEHost, hence not using mkServerConfigs
|
||||
} // (let
|
||||
baseCaddyConfig = { nodes, config, ... }: {
|
||||
security.acme = {
|
||||
defaults = (dnsConfig nodes);
|
||||
# One manual wildcard cert
|
||||
certs."example.test" = {
|
||||
domain = "*.example.test";
|
||||
};
|
||||
};
|
||||
|
||||
users.users."${config.services.caddy.user}".extraGroups = ["acme"];
|
||||
|
||||
services.caddy = {
|
||||
enable = true;
|
||||
virtualHosts."a.exmaple.test" = {
|
||||
useACMEHost = "example.test";
|
||||
extraConfig = ''
|
||||
root * ${documentRoot}
|
||||
'';
|
||||
};
|
||||
};
|
||||
};
|
||||
in {
|
||||
caddy.configuration = baseCaddyConfig;
|
||||
|
||||
# Test that the server reloads when only the acme configuration is changed.
|
||||
"caddy-change-acme-conf".configuration = { nodes, config, ... }: lib.mkMerge [
|
||||
(baseCaddyConfig {
|
||||
inherit nodes config;
|
||||
})
|
||||
{
|
||||
security.acme.certs."example.test" = {
|
||||
keyType = "ec384";
|
||||
};
|
||||
}
|
||||
];
|
||||
|
||||
# Test compatibility with Nginx
|
||||
}) // (mkServerConfigs {
|
||||
server = "nginx";
|
||||
group = "nginx";
|
||||
vhostBaseData = vhostBase;
|
||||
})
|
||||
|
||||
# Test compatibility with Apache HTTPD
|
||||
// (mkServerConfigs {
|
||||
server = "httpd";
|
||||
group = "wwwrun";
|
||||
vhostBaseData = vhostBaseHttpd;
|
||||
extraConfig = {
|
||||
services.httpd.adminAddr = config.security.acme.defaults.email;
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
# The client will be used to curl the webserver to validate configuration
|
||||
client = { nodes, ... }: {
|
||||
imports = [ commonConfig ];
|
||||
networking.nameservers = lib.mkForce [ (dnsServerIP nodes) ];
|
||||
|
||||
# OpenSSL will be used for more thorough certificate validation
|
||||
environment.systemPackages = [ pkgs.openssl ];
|
||||
};
|
||||
};
|
||||
|
||||
testScript = { nodes, ... }:
|
||||
let
|
||||
caDomain = nodes.acme.config.test-support.acme.caDomain;
|
||||
newServerSystem = nodes.webserver.config.system.build.toplevel;
|
||||
switchToNewServer = "${newServerSystem}/bin/switch-to-configuration test";
|
||||
in
|
||||
# Note, wait_for_unit does not work for oneshot services that do not have RemainAfterExit=true,
|
||||
# this is because a oneshot goes from inactive => activating => inactive, and never
|
||||
# reaches the active state. Targets do not have this issue.
|
||||
''
|
||||
import time
|
||||
|
||||
|
||||
def switch_to(node, name):
|
||||
# On first switch, this will create a symlink to the current system so that we can
|
||||
# quickly switch between derivations
|
||||
root_specs = "/tmp/specialisation"
|
||||
node.execute(
|
||||
f"test -e {root_specs}"
|
||||
f" || ln -s $(readlink /run/current-system)/specialisation {root_specs}"
|
||||
)
|
||||
|
||||
switcher_path = f"/run/current-system/specialisation/{name}/bin/switch-to-configuration"
|
||||
rc, _ = node.execute(f"test -e '{switcher_path}'")
|
||||
if rc > 0:
|
||||
switcher_path = f"/tmp/specialisation/{name}/bin/switch-to-configuration"
|
||||
|
||||
node.succeed(
|
||||
f"{switcher_path} test"
|
||||
)
|
||||
|
||||
|
||||
# Ensures the issuer of our cert matches the chain
|
||||
# and matches the issuer we expect it to be.
|
||||
# It's a good validation to ensure the cert.pem and fullchain.pem
|
||||
# are not still selfsigned afer verification
|
||||
def check_issuer(node, cert_name, issuer):
|
||||
for fname in ("cert.pem", "fullchain.pem"):
|
||||
actual_issuer = node.succeed(
|
||||
f"openssl x509 -noout -issuer -in /var/lib/acme/{cert_name}/{fname}"
|
||||
).partition("=")[2]
|
||||
print(f"{fname} issuer: {actual_issuer}")
|
||||
assert issuer.lower() in actual_issuer.lower()
|
||||
|
||||
|
||||
# Ensure cert comes before chain in fullchain.pem
|
||||
def check_fullchain(node, cert_name):
|
||||
subject_data = node.succeed(
|
||||
f"openssl crl2pkcs7 -nocrl -certfile /var/lib/acme/{cert_name}/fullchain.pem"
|
||||
" | openssl pkcs7 -print_certs -noout"
|
||||
)
|
||||
for line in subject_data.lower().split("\n"):
|
||||
if "subject" in line:
|
||||
print(f"First subject in fullchain.pem: {line}")
|
||||
assert cert_name.lower() in line
|
||||
return
|
||||
|
||||
assert False
|
||||
|
||||
|
||||
def check_connection(node, domain, retries=3):
|
||||
assert retries >= 0, f"Failed to connect to https://{domain}"
|
||||
|
||||
result = node.succeed(
|
||||
"openssl s_client -brief -verify 2 -CAfile /tmp/ca.crt"
|
||||
f" -servername {domain} -connect {domain}:443 < /dev/null 2>&1"
|
||||
)
|
||||
|
||||
for line in result.lower().split("\n"):
|
||||
if "verification" in line and "error" in line:
|
||||
time.sleep(3)
|
||||
return check_connection(node, domain, retries - 1)
|
||||
|
||||
|
||||
def check_connection_key_bits(node, domain, bits, retries=3):
|
||||
assert retries >= 0, f"Did not find expected number of bits ({bits}) in key"
|
||||
|
||||
result = node.succeed(
|
||||
"openssl s_client -CAfile /tmp/ca.crt"
|
||||
f" -servername {domain} -connect {domain}:443 < /dev/null"
|
||||
" | openssl x509 -noout -text | grep -i Public-Key"
|
||||
)
|
||||
print("Key type:", result)
|
||||
|
||||
if bits not in result:
|
||||
time.sleep(3)
|
||||
return check_connection_key_bits(node, domain, bits, retries - 1)
|
||||
|
||||
|
||||
def check_stapling(node, domain, retries=3):
|
||||
assert retries >= 0, "OCSP Stapling check failed"
|
||||
|
||||
# Pebble doesn't provide a full OCSP responder, so just check the URL
|
||||
result = node.succeed(
|
||||
"openssl s_client -CAfile /tmp/ca.crt"
|
||||
f" -servername {domain} -connect {domain}:443 < /dev/null"
|
||||
" | openssl x509 -noout -ocsp_uri"
|
||||
)
|
||||
print("OCSP Responder URL:", result)
|
||||
|
||||
if "${caDomain}:4002" not in result.lower():
|
||||
time.sleep(3)
|
||||
return check_stapling(node, domain, retries - 1)
|
||||
|
||||
|
||||
def download_ca_certs(node, retries=5):
|
||||
assert retries >= 0, "Failed to connect to pebble to download root CA certs"
|
||||
|
||||
exit_code, _ = node.execute("curl https://${caDomain}:15000/roots/0 > /tmp/ca.crt")
|
||||
exit_code_2, _ = node.execute(
|
||||
"curl https://${caDomain}:15000/intermediate-keys/0 >> /tmp/ca.crt"
|
||||
)
|
||||
|
||||
if exit_code + exit_code_2 > 0:
|
||||
time.sleep(3)
|
||||
return download_ca_certs(node, retries - 1)
|
||||
|
||||
|
||||
start_all()
|
||||
|
||||
dnsserver.wait_for_unit("pebble-challtestsrv.service")
|
||||
client.wait_for_unit("default.target")
|
||||
|
||||
client.succeed(
|
||||
'curl --data \'{"host": "${caDomain}", "addresses": ["${nodes.acme.config.networking.primaryIPAddress}"]}\' http://${dnsServerIP nodes}:8055/add-a'
|
||||
)
|
||||
|
||||
acme.wait_for_unit("network-online.target")
|
||||
acme.wait_for_unit("pebble.service")
|
||||
|
||||
download_ca_certs(client)
|
||||
|
||||
# Perform general tests first
|
||||
switch_to(webserver, "general")
|
||||
|
||||
with subtest("Can request certificate with HTTP-01 challenge"):
|
||||
webserver.wait_for_unit("acme-finished-a.example.test.target")
|
||||
check_fullchain(webserver, "a.example.test")
|
||||
check_issuer(webserver, "a.example.test", "pebble")
|
||||
webserver.wait_for_unit("nginx.service")
|
||||
check_connection(client, "a.example.test")
|
||||
|
||||
with subtest("Runs 1 cert for account creation before others"):
|
||||
webserver.wait_for_unit("acme-finished-b.example.test.target")
|
||||
webserver.wait_for_unit("acme-finished-c.example.test.target")
|
||||
check_connection(client, "b.example.test")
|
||||
check_connection(client, "c.example.test")
|
||||
|
||||
with subtest("Certificates and accounts have safe + valid permissions"):
|
||||
# Nginx will set the group appropriately when enableACME is used
|
||||
group = "nginx"
|
||||
webserver.succeed(
|
||||
f"test $(stat -L -c '%a %U %G' /var/lib/acme/a.example.test/*.pem | tee /dev/stderr | grep '640 acme {group}' | wc -l) -eq 5"
|
||||
)
|
||||
webserver.succeed(
|
||||
f"test $(stat -L -c '%a %U %G' /var/lib/acme/.lego/a.example.test/**/a.example.test* | tee /dev/stderr | grep '600 acme {group}' | wc -l) -eq 4"
|
||||
)
|
||||
webserver.succeed(
|
||||
f"test $(stat -L -c '%a %U %G' /var/lib/acme/a.example.test | tee /dev/stderr | grep '750 acme {group}' | wc -l) -eq 1"
|
||||
)
|
||||
webserver.succeed(
|
||||
f"test $(find /var/lib/acme/accounts -type f -exec stat -L -c '%a %U %G' {{}} \\; | tee /dev/stderr | grep -v '600 acme {group}' | wc -l) -eq 0"
|
||||
)
|
||||
|
||||
# Selfsigned certs tests happen late so we aren't fighting the system init triggering cert renewal
|
||||
with subtest("Can generate valid selfsigned certs"):
|
||||
webserver.succeed("systemctl clean acme-a.example.test.service --what=state")
|
||||
webserver.succeed("systemctl start acme-selfsigned-a.example.test.service")
|
||||
check_fullchain(webserver, "a.example.test")
|
||||
check_issuer(webserver, "a.example.test", "minica")
|
||||
# Check selfsigned permissions
|
||||
webserver.succeed(
|
||||
f"test $(stat -L -c '%a %U %G' /var/lib/acme/a.example.test/*.pem | tee /dev/stderr | grep '640 acme {group}' | wc -l) -eq 5"
|
||||
)
|
||||
# Will succeed if nginx can load the certs
|
||||
webserver.succeed("systemctl start nginx-config-reload.service")
|
||||
|
||||
with subtest("Correctly implements OCSP stapling"):
|
||||
switch_to(webserver, "ocsp-stapling")
|
||||
webserver.wait_for_unit("acme-finished-a.example.test.target")
|
||||
check_stapling(client, "a.example.test")
|
||||
|
||||
with subtest("Can request certificate with HTTP-01 using lego's internal web server"):
|
||||
switch_to(webserver, "lego-server")
|
||||
webserver.wait_for_unit("acme-finished-lego.example.test.target")
|
||||
webserver.wait_for_unit("nginx.service")
|
||||
webserver.succeed("echo HENLO && systemctl cat nginx.service")
|
||||
webserver.succeed("test \"$(stat -c '%U' /var/lib/acme/* | uniq)\" = \"root\"")
|
||||
check_connection(client, "a.example.test")
|
||||
check_connection(client, "lego.example.test")
|
||||
|
||||
with subtest("Can request certificate with HTTP-01 when nginx startup is delayed"):
|
||||
webserver.execute("systemctl stop nginx")
|
||||
switch_to(webserver, "slow-startup")
|
||||
webserver.wait_for_unit("acme-finished-slow.example.test.target")
|
||||
check_issuer(webserver, "slow.example.test", "pebble")
|
||||
webserver.wait_for_unit("nginx.service")
|
||||
check_connection(client, "slow.example.test")
|
||||
|
||||
with subtest("Works with caddy"):
|
||||
switch_to(webserver, "caddy")
|
||||
webserver.wait_for_unit("acme-finished-example.test.target")
|
||||
webserver.wait_for_unit("caddy.service")
|
||||
# FIXME reloading caddy is not sufficient to load new certs.
|
||||
# Restart it manually until this is fixed.
|
||||
webserver.succeed("systemctl restart caddy.service")
|
||||
check_connection(client, "a.example.test")
|
||||
|
||||
with subtest("security.acme changes reflect on caddy"):
|
||||
switch_to(webserver, "caddy-change-acme-conf")
|
||||
webserver.wait_for_unit("acme-finished-example.test.target")
|
||||
webserver.wait_for_unit("caddy.service")
|
||||
# FIXME reloading caddy is not sufficient to load new certs.
|
||||
# Restart it manually until this is fixed.
|
||||
webserver.succeed("systemctl restart caddy.service")
|
||||
check_connection_key_bits(client, "a.example.test", "384")
|
||||
|
||||
domains = ["http", "dns", "wildcard"]
|
||||
for server, logsrc in [
|
||||
("nginx", "journalctl -n 30 -u nginx.service"),
|
||||
("httpd", "tail -n 30 /var/log/httpd/*.log"),
|
||||
]:
|
||||
wait_for_server = lambda: webserver.wait_for_unit(f"{server}.service")
|
||||
with subtest(f"Works with {server}"):
|
||||
try:
|
||||
switch_to(webserver, server)
|
||||
# Skip wildcard domain for this check ([:-1])
|
||||
for domain in domains[:-1]:
|
||||
webserver.wait_for_unit(
|
||||
f"acme-finished-{server}-{domain}.example.test.target"
|
||||
)
|
||||
except Exception as err:
|
||||
_, output = webserver.execute(
|
||||
f"{logsrc} && ls -al /var/lib/acme/acme-challenge"
|
||||
)
|
||||
print(output)
|
||||
raise err
|
||||
|
||||
wait_for_server()
|
||||
|
||||
for domain in domains[:-1]:
|
||||
check_issuer(webserver, f"{server}-{domain}.example.test", "pebble")
|
||||
for domain in domains:
|
||||
check_connection(client, f"{server}-{domain}.example.test")
|
||||
check_connection(client, f"{server}-{domain}-alias.example.test")
|
||||
|
||||
test_domain = f"{server}-{domains[0]}.example.test"
|
||||
|
||||
with subtest(f"Can reload {server} when timer triggers renewal"):
|
||||
# Switch to selfsigned first
|
||||
webserver.succeed(f"systemctl clean acme-{test_domain}.service --what=state")
|
||||
webserver.succeed(f"systemctl start acme-selfsigned-{test_domain}.service")
|
||||
check_issuer(webserver, test_domain, "minica")
|
||||
webserver.succeed(f"systemctl start {server}-config-reload.service")
|
||||
webserver.succeed(f"systemctl start test-renew-{server}.target")
|
||||
check_issuer(webserver, test_domain, "pebble")
|
||||
check_connection(client, test_domain)
|
||||
|
||||
with subtest("Can remove an alias from a domain + cert is updated"):
|
||||
test_alias = f"{server}-{domains[0]}-alias.example.test"
|
||||
switch_to(webserver, f"{server}-remove-alias")
|
||||
webserver.wait_for_unit(f"acme-finished-{test_domain}.target")
|
||||
wait_for_server()
|
||||
check_connection(client, test_domain)
|
||||
rc, _ = client.execute(
|
||||
f"openssl s_client -CAfile /tmp/ca.crt -connect {test_alias}:443"
|
||||
" </dev/null 2>/dev/null | openssl x509 -noout -text"
|
||||
f" | grep DNS: | grep {test_alias}"
|
||||
)
|
||||
assert rc > 0, "Removed extraDomainName was not removed from the cert"
|
||||
|
||||
with subtest("security.acme changes reflect on web server"):
|
||||
# Switch back to normal server config first, reset everything.
|
||||
switch_to(webserver, server)
|
||||
wait_for_server()
|
||||
switch_to(webserver, f"{server}-change-acme-conf")
|
||||
webserver.wait_for_unit(f"acme-finished-{test_domain}.target")
|
||||
wait_for_server()
|
||||
check_connection_key_bits(client, test_domain, "384")
|
||||
'';
|
||||
})
|
||||
57
nixos/tests/adguardhome.nix
Normal file
57
nixos/tests/adguardhome.nix
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
import ./make-test-python.nix {
|
||||
name = "adguardhome";
|
||||
|
||||
nodes = {
|
||||
minimalConf = { ... }: {
|
||||
services.adguardhome = { enable = true; };
|
||||
};
|
||||
|
||||
declarativeConf = { ... }: {
|
||||
services.adguardhome = {
|
||||
enable = true;
|
||||
|
||||
mutableSettings = false;
|
||||
settings = {
|
||||
dns = {
|
||||
bind_host = "0.0.0.0";
|
||||
bootstrap_dns = "127.0.0.1";
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
mixedConf = { ... }: {
|
||||
services.adguardhome = {
|
||||
enable = true;
|
||||
|
||||
mutableSettings = true;
|
||||
settings = {
|
||||
dns = {
|
||||
bind_host = "0.0.0.0";
|
||||
bootstrap_dns = "127.0.0.1";
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
testScript = ''
|
||||
with subtest("Minimal config test"):
|
||||
minimalConf.wait_for_unit("adguardhome.service")
|
||||
minimalConf.wait_for_open_port(3000)
|
||||
|
||||
with subtest("Declarative config test, DNS will be reachable"):
|
||||
declarativeConf.wait_for_unit("adguardhome.service")
|
||||
declarativeConf.wait_for_open_port(53)
|
||||
declarativeConf.wait_for_open_port(3000)
|
||||
|
||||
with subtest("Mixed config test, check whether merging works"):
|
||||
mixedConf.wait_for_unit("adguardhome.service")
|
||||
mixedConf.wait_for_open_port(53)
|
||||
mixedConf.wait_for_open_port(3000)
|
||||
# Test whether merging works properly, even if nothing is changed
|
||||
mixedConf.systemctl("restart adguardhome.service")
|
||||
mixedConf.wait_for_unit("adguardhome.service")
|
||||
mixedConf.wait_for_open_port(3000)
|
||||
'';
|
||||
}
|
||||
62
nixos/tests/aesmd.nix
Normal file
62
nixos/tests/aesmd.nix
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
import ./make-test-python.nix ({ pkgs, lib, ... }: {
|
||||
name = "aesmd";
|
||||
meta = {
|
||||
maintainers = with lib.maintainers; [ veehaitch ];
|
||||
};
|
||||
|
||||
nodes.machine = { lib, ... }: {
|
||||
services.aesmd = {
|
||||
enable = true;
|
||||
settings = {
|
||||
defaultQuotingType = "ecdsa_256";
|
||||
proxyType = "direct";
|
||||
whitelistUrl = "http://nixos.org";
|
||||
};
|
||||
};
|
||||
|
||||
# Should have access to the AESM socket
|
||||
users.users."sgxtest" = {
|
||||
isNormalUser = true;
|
||||
extraGroups = [ "sgx" ];
|
||||
};
|
||||
|
||||
# Should NOT have access to the AESM socket
|
||||
users.users."nosgxtest".isNormalUser = true;
|
||||
|
||||
# We don't have a real SGX machine in NixOS tests
|
||||
systemd.services.aesmd.unitConfig.AssertPathExists = lib.mkForce [ ];
|
||||
};
|
||||
|
||||
testScript = ''
|
||||
with subtest("aesmd.service starts"):
|
||||
machine.wait_for_unit("aesmd.service")
|
||||
status, main_pid = machine.systemctl("show --property MainPID --value aesmd.service")
|
||||
assert status == 0, "Could not get MainPID of aesmd.service"
|
||||
main_pid = main_pid.strip()
|
||||
|
||||
with subtest("aesmd.service runtime directory permissions"):
|
||||
runtime_dir = "/run/aesmd";
|
||||
res = machine.succeed(f"stat -c '%a %U %G' {runtime_dir}").strip()
|
||||
assert "750 aesmd sgx" == res, f"{runtime_dir} does not have the expected permissions: {res}"
|
||||
|
||||
with subtest("aesm.socket available on host"):
|
||||
socket_path = "/var/run/aesmd/aesm.socket"
|
||||
machine.wait_until_succeeds(f"test -S {socket_path}")
|
||||
machine.succeed(f"test 777 -eq $(stat -c '%a' {socket_path})")
|
||||
for op in [ "-r", "-w", "-x" ]:
|
||||
machine.succeed(f"sudo -u sgxtest test {op} {socket_path}")
|
||||
machine.fail(f"sudo -u nosgxtest test {op} {socket_path}")
|
||||
|
||||
with subtest("Copies white_list_cert_to_be_verify.bin"):
|
||||
whitelist_path = "/var/opt/aesmd/data/white_list_cert_to_be_verify.bin"
|
||||
whitelist_perms = machine.succeed(
|
||||
f"nsenter -m -t {main_pid} ${pkgs.coreutils}/bin/stat -c '%a' {whitelist_path}"
|
||||
).strip()
|
||||
assert "644" == whitelist_perms, f"white_list_cert_to_be_verify.bin has permissions {whitelist_perms}"
|
||||
|
||||
with subtest("Writes and binds aesm.conf in service namespace"):
|
||||
aesmd_config = machine.succeed(f"nsenter -m -t {main_pid} ${pkgs.coreutils}/bin/cat /etc/aesmd.conf")
|
||||
|
||||
assert aesmd_config == "whitelist url = http://nixos.org\nproxy type = direct\ndefault quoting type = ecdsa_256\n", "aesmd.conf differs"
|
||||
'';
|
||||
})
|
||||
50
nixos/tests/agda.nix
Normal file
50
nixos/tests/agda.nix
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
import ./make-test-python.nix ({ pkgs, ... }:
|
||||
|
||||
let
|
||||
hello-world = pkgs.writeText "hello-world" ''
|
||||
{-# OPTIONS --guardedness #-}
|
||||
open import IO
|
||||
open import Level
|
||||
|
||||
main = run {0ℓ} (putStrLn "Hello World!")
|
||||
'';
|
||||
in
|
||||
{
|
||||
name = "agda";
|
||||
meta = with pkgs.lib.maintainers; {
|
||||
maintainers = [ alexarice turion ];
|
||||
};
|
||||
|
||||
nodes.machine = { pkgs, ... }: {
|
||||
environment.systemPackages = [
|
||||
(pkgs.agda.withPackages {
|
||||
pkgs = p: [ p.standard-library ];
|
||||
})
|
||||
];
|
||||
virtualisation.memorySize = 2000; # Agda uses a lot of memory
|
||||
};
|
||||
|
||||
testScript = ''
|
||||
assert (
|
||||
"${pkgs.agdaPackages.lib.interfaceFile "Everything.agda"}" == "Everything.agdai"
|
||||
), "wrong interface file for Everything.agda"
|
||||
assert (
|
||||
"${pkgs.agdaPackages.lib.interfaceFile "tmp/Everything.agda.md"}" == "tmp/Everything.agdai"
|
||||
), "wrong interface file for tmp/Everything.agda.md"
|
||||
|
||||
# Minimal script that typechecks
|
||||
machine.succeed("touch TestEmpty.agda")
|
||||
machine.succeed("agda TestEmpty.agda")
|
||||
|
||||
# Hello world
|
||||
machine.succeed(
|
||||
"cp ${hello-world} HelloWorld.agda"
|
||||
)
|
||||
machine.succeed("agda -l standard-library -i . -c HelloWorld.agda")
|
||||
# Check execution
|
||||
assert "Hello World!" in machine.succeed(
|
||||
"./HelloWorld"
|
||||
), "HelloWorld does not run properly"
|
||||
'';
|
||||
}
|
||||
)
|
||||
28
nixos/tests/airsonic.nix
Normal file
28
nixos/tests/airsonic.nix
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
import ./make-test-python.nix ({ pkgs, ... }: {
|
||||
name = "airsonic";
|
||||
meta = with pkgs.lib.maintainers; {
|
||||
maintainers = [ sumnerevans ];
|
||||
};
|
||||
|
||||
nodes.machine =
|
||||
{ pkgs, ... }:
|
||||
{
|
||||
services.airsonic = {
|
||||
enable = true;
|
||||
maxMemory = 800;
|
||||
};
|
||||
};
|
||||
|
||||
testScript = ''
|
||||
def airsonic_is_up(_) -> bool:
|
||||
return machine.succeed("curl --fail http://localhost:4040/login")
|
||||
|
||||
|
||||
machine.start()
|
||||
machine.wait_for_unit("airsonic.service")
|
||||
machine.wait_for_open_port(4040)
|
||||
|
||||
with machine.nested("Waiting for UI to work"):
|
||||
retry(airsonic_is_up)
|
||||
'';
|
||||
})
|
||||
31
nixos/tests/all-terminfo.nix
Normal file
31
nixos/tests/all-terminfo.nix
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
import ./make-test-python.nix ({ pkgs, ... }: rec {
|
||||
name = "all-terminfo";
|
||||
meta = with pkgs.lib.maintainers; {
|
||||
maintainers = [ jkarlson ];
|
||||
};
|
||||
|
||||
nodes.machine = { pkgs, config, lib, ... }:
|
||||
let
|
||||
infoFilter = name: drv:
|
||||
let
|
||||
o = builtins.tryEval drv;
|
||||
in
|
||||
o.success && lib.isDerivation o.value && o.value ? outputs && builtins.elem "terminfo" o.value.outputs;
|
||||
terminfos = lib.filterAttrs infoFilter pkgs;
|
||||
excludedTerminfos = lib.filterAttrs (_: drv: !(builtins.elem drv.terminfo config.environment.systemPackages)) terminfos;
|
||||
includedOuts = lib.filterAttrs (_: drv: builtins.elem drv.out config.environment.systemPackages) terminfos;
|
||||
in
|
||||
{
|
||||
environment = {
|
||||
enableAllTerminfo = true;
|
||||
etc."terminfo-missing".text = builtins.concatStringsSep "\n" (builtins.attrNames excludedTerminfos);
|
||||
etc."terminfo-extra-outs".text = builtins.concatStringsSep "\n" (builtins.attrNames includedOuts);
|
||||
};
|
||||
};
|
||||
|
||||
testScript =
|
||||
''
|
||||
machine.fail("grep . /etc/terminfo-missing >&2")
|
||||
machine.fail("grep . /etc/terminfo-extra-outs >&2")
|
||||
'';
|
||||
})
|
||||
623
nixos/tests/all-tests.nix
Normal file
623
nixos/tests/all-tests.nix
Normal file
|
|
@ -0,0 +1,623 @@
|
|||
{ system, pkgs, callTest }:
|
||||
# The return value of this function will be an attrset with arbitrary depth and
|
||||
# the `anything` returned by callTest at its test leafs.
|
||||
# The tests not supported by `system` will be replaced with `{}`, so that
|
||||
# `passthru.tests` can contain links to those without breaking on architectures
|
||||
# where said tests are unsupported.
|
||||
# Example callTest that just extracts the derivation from the test:
|
||||
# callTest = t: t.test;
|
||||
|
||||
with pkgs.lib;
|
||||
|
||||
let
|
||||
discoverTests = val:
|
||||
if !isAttrs val then val
|
||||
else if hasAttr "test" val then callTest val
|
||||
else mapAttrs (n: s: discoverTests s) val;
|
||||
handleTest = path: args:
|
||||
discoverTests (import path ({ inherit system pkgs; } // args));
|
||||
handleTestOn = systems: path: args:
|
||||
if elem system systems then handleTest path args
|
||||
else {};
|
||||
|
||||
nixosLib = import ../lib {
|
||||
# Experimental features need testing too, but there's no point in warning
|
||||
# about it, so we enable the feature flag.
|
||||
featureFlags.minimalModules = {};
|
||||
};
|
||||
evalMinimalConfig = module: nixosLib.evalModules { modules = [ module ]; };
|
||||
|
||||
in {
|
||||
_3proxy = handleTest ./3proxy.nix {};
|
||||
acme = handleTest ./acme.nix {};
|
||||
adguardhome = handleTest ./adguardhome.nix {};
|
||||
aesmd = handleTest ./aesmd.nix {};
|
||||
agate = handleTest ./web-servers/agate.nix {};
|
||||
agda = handleTest ./agda.nix {};
|
||||
airsonic = handleTest ./airsonic.nix {};
|
||||
allTerminfo = handleTest ./all-terminfo.nix {};
|
||||
amazon-init-shell = handleTest ./amazon-init-shell.nix {};
|
||||
apfs = handleTest ./apfs.nix {};
|
||||
apparmor = handleTest ./apparmor.nix {};
|
||||
atd = handleTest ./atd.nix {};
|
||||
atop = handleTest ./atop.nix {};
|
||||
avahi = handleTest ./avahi.nix {};
|
||||
avahi-with-resolved = handleTest ./avahi.nix { networkd = true; };
|
||||
babeld = handleTest ./babeld.nix {};
|
||||
bazarr = handleTest ./bazarr.nix {};
|
||||
bcachefs = handleTestOn ["x86_64-linux" "aarch64-linux"] ./bcachefs.nix {};
|
||||
beanstalkd = handleTest ./beanstalkd.nix {};
|
||||
bees = handleTest ./bees.nix {};
|
||||
bind = handleTest ./bind.nix {};
|
||||
bird = handleTest ./bird.nix {};
|
||||
bitcoind = handleTest ./bitcoind.nix {};
|
||||
bittorrent = handleTest ./bittorrent.nix {};
|
||||
blockbook-frontend = handleTest ./blockbook-frontend.nix {};
|
||||
blocky = handleTest ./blocky.nix {};
|
||||
boot = handleTestOn ["x86_64-linux" "aarch64-linux"] ./boot.nix {};
|
||||
boot-stage1 = handleTest ./boot-stage1.nix {};
|
||||
borgbackup = handleTest ./borgbackup.nix {};
|
||||
botamusique = handleTest ./botamusique.nix {};
|
||||
bpf = handleTestOn ["x86_64-linux" "aarch64-linux"] ./bpf.nix {};
|
||||
breitbandmessung = handleTest ./breitbandmessung.nix {};
|
||||
brscan5 = handleTest ./brscan5.nix {};
|
||||
btrbk = handleTest ./btrbk.nix {};
|
||||
btrbk-no-timer = handleTest ./btrbk-no-timer.nix {};
|
||||
buildbot = handleTest ./buildbot.nix {};
|
||||
buildkite-agents = handleTest ./buildkite-agents.nix {};
|
||||
caddy = handleTest ./caddy.nix {};
|
||||
cadvisor = handleTestOn ["x86_64-linux"] ./cadvisor.nix {};
|
||||
cage = handleTest ./cage.nix {};
|
||||
cagebreak = handleTest ./cagebreak.nix {};
|
||||
calibre-web = handleTest ./calibre-web.nix {};
|
||||
cassandra_2_1 = handleTest ./cassandra.nix { testPackage = pkgs.cassandra_2_1; };
|
||||
cassandra_2_2 = handleTest ./cassandra.nix { testPackage = pkgs.cassandra_2_2; };
|
||||
cassandra_3_0 = handleTest ./cassandra.nix { testPackage = pkgs.cassandra_3_0; };
|
||||
cassandra_3_11 = handleTest ./cassandra.nix { testPackage = pkgs.cassandra_3_11; };
|
||||
ceph-multi-node = handleTestOn ["x86_64-linux"] ./ceph-multi-node.nix {};
|
||||
ceph-single-node = handleTestOn ["x86_64-linux"] ./ceph-single-node.nix {};
|
||||
ceph-single-node-bluestore = handleTestOn ["x86_64-linux"] ./ceph-single-node-bluestore.nix {};
|
||||
certmgr = handleTest ./certmgr.nix {};
|
||||
cfssl = handleTestOn ["x86_64-linux"] ./cfssl.nix {};
|
||||
charliecloud = handleTest ./charliecloud.nix {};
|
||||
chromium = (handleTestOn ["x86_64-linux"] ./chromium.nix {}).stable or {};
|
||||
cjdns = handleTest ./cjdns.nix {};
|
||||
clickhouse = handleTest ./clickhouse.nix {};
|
||||
cloud-init = handleTest ./cloud-init.nix {};
|
||||
cntr = handleTest ./cntr.nix {};
|
||||
cockroachdb = handleTestOn ["x86_64-linux"] ./cockroachdb.nix {};
|
||||
collectd = handleTest ./collectd.nix {};
|
||||
consul = handleTest ./consul.nix {};
|
||||
containers-bridge = handleTest ./containers-bridge.nix {};
|
||||
containers-custom-pkgs.nix = handleTest ./containers-custom-pkgs.nix {};
|
||||
containers-ephemeral = handleTest ./containers-ephemeral.nix {};
|
||||
containers-extra_veth = handleTest ./containers-extra_veth.nix {};
|
||||
containers-hosts = handleTest ./containers-hosts.nix {};
|
||||
containers-imperative = handleTest ./containers-imperative.nix {};
|
||||
containers-ip = handleTest ./containers-ip.nix {};
|
||||
containers-macvlans = handleTest ./containers-macvlans.nix {};
|
||||
containers-names = handleTest ./containers-names.nix {};
|
||||
containers-nested = handleTest ./containers-nested.nix {};
|
||||
containers-physical_interfaces = handleTest ./containers-physical_interfaces.nix {};
|
||||
containers-portforward = handleTest ./containers-portforward.nix {};
|
||||
containers-reloadable = handleTest ./containers-reloadable.nix {};
|
||||
containers-restart_networking = handleTest ./containers-restart_networking.nix {};
|
||||
containers-tmpfs = handleTest ./containers-tmpfs.nix {};
|
||||
convos = handleTest ./convos.nix {};
|
||||
corerad = handleTest ./corerad.nix {};
|
||||
coturn = handleTest ./coturn.nix {};
|
||||
couchdb = handleTest ./couchdb.nix {};
|
||||
cri-o = handleTestOn ["x86_64-linux"] ./cri-o.nix {};
|
||||
custom-ca = handleTest ./custom-ca.nix {};
|
||||
croc = handleTest ./croc.nix {};
|
||||
cryptpad = handleTest ./cryptpad.nix {};
|
||||
deluge = handleTest ./deluge.nix {};
|
||||
dendrite = handleTest ./dendrite.nix {};
|
||||
dex-oidc = handleTest ./dex-oidc.nix {};
|
||||
dhparams = handleTest ./dhparams.nix {};
|
||||
disable-installer-tools = handleTest ./disable-installer-tools.nix {};
|
||||
discourse = handleTest ./discourse.nix {};
|
||||
dnscrypt-proxy2 = handleTestOn ["x86_64-linux"] ./dnscrypt-proxy2.nix {};
|
||||
dnscrypt-wrapper = handleTestOn ["x86_64-linux"] ./dnscrypt-wrapper {};
|
||||
dnsdist = handleTest ./dnsdist.nix {};
|
||||
doas = handleTest ./doas.nix {};
|
||||
docker = handleTestOn ["x86_64-linux"] ./docker.nix {};
|
||||
docker-rootless = handleTestOn ["x86_64-linux"] ./docker-rootless.nix {};
|
||||
docker-registry = handleTest ./docker-registry.nix {};
|
||||
docker-tools = handleTestOn ["x86_64-linux"] ./docker-tools.nix {};
|
||||
docker-tools-cross = handleTestOn ["x86_64-linux" "aarch64-linux"] ./docker-tools-cross.nix {};
|
||||
docker-tools-overlay = handleTestOn ["x86_64-linux"] ./docker-tools-overlay.nix {};
|
||||
documize = handleTest ./documize.nix {};
|
||||
doh-proxy-rust = handleTest ./doh-proxy-rust.nix {};
|
||||
dokuwiki = handleTest ./dokuwiki.nix {};
|
||||
domination = handleTest ./domination.nix {};
|
||||
dovecot = handleTest ./dovecot.nix {};
|
||||
drbd = handleTest ./drbd.nix {};
|
||||
earlyoom = handleTestOn ["x86_64-linux"] ./earlyoom.nix {};
|
||||
ec2-config = (handleTestOn ["x86_64-linux"] ./ec2.nix {}).boot-ec2-config or {};
|
||||
ec2-nixops = (handleTestOn ["x86_64-linux"] ./ec2.nix {}).boot-ec2-nixops or {};
|
||||
ecryptfs = handleTest ./ecryptfs.nix {};
|
||||
ejabberd = handleTest ./xmpp/ejabberd.nix {};
|
||||
elk = handleTestOn ["x86_64-linux"] ./elk.nix {};
|
||||
emacs-daemon = handleTest ./emacs-daemon.nix {};
|
||||
engelsystem = handleTest ./engelsystem.nix {};
|
||||
enlightenment = handleTest ./enlightenment.nix {};
|
||||
env = handleTest ./env.nix {};
|
||||
envoy = handleTest ./envoy.nix {};
|
||||
ergo = handleTest ./ergo.nix {};
|
||||
ergochat = handleTest ./ergochat.nix {};
|
||||
etc = pkgs.callPackage ../modules/system/etc/test.nix { inherit evalMinimalConfig; };
|
||||
etcd = handleTestOn ["x86_64-linux"] ./etcd.nix {};
|
||||
etcd-cluster = handleTestOn ["x86_64-linux"] ./etcd-cluster.nix {};
|
||||
etebase-server = handleTest ./etebase-server.nix {};
|
||||
etesync-dav = handleTest ./etesync-dav.nix {};
|
||||
extra-python-packages = handleTest ./extra-python-packages.nix {};
|
||||
fancontrol = handleTest ./fancontrol.nix {};
|
||||
fcitx = handleTest ./fcitx {};
|
||||
fenics = handleTest ./fenics.nix {};
|
||||
ferm = handleTest ./ferm.nix {};
|
||||
firefox = handleTest ./firefox.nix { firefoxPackage = pkgs.firefox; };
|
||||
firefox-esr = handleTest ./firefox.nix { firefoxPackage = pkgs.firefox-esr; }; # used in `tested` job
|
||||
firefox-esr-91 = handleTest ./firefox.nix { firefoxPackage = pkgs.firefox-esr-91; };
|
||||
firejail = handleTest ./firejail.nix {};
|
||||
firewall = handleTest ./firewall.nix {};
|
||||
fish = handleTest ./fish.nix {};
|
||||
flannel = handleTestOn ["x86_64-linux"] ./flannel.nix {};
|
||||
fluentd = handleTest ./fluentd.nix {};
|
||||
fluidd = handleTest ./fluidd.nix {};
|
||||
fontconfig-default-fonts = handleTest ./fontconfig-default-fonts.nix {};
|
||||
freeswitch = handleTest ./freeswitch.nix {};
|
||||
frr = handleTest ./frr.nix {};
|
||||
fsck = handleTest ./fsck.nix {};
|
||||
ft2-clone = handleTest ./ft2-clone.nix {};
|
||||
mimir = handleTest ./mimir.nix {};
|
||||
gerrit = handleTest ./gerrit.nix {};
|
||||
geth = handleTest ./geth.nix {};
|
||||
ghostunnel = handleTest ./ghostunnel.nix {};
|
||||
gitdaemon = handleTest ./gitdaemon.nix {};
|
||||
gitea = handleTest ./gitea.nix {};
|
||||
gitlab = handleTest ./gitlab.nix {};
|
||||
gitolite = handleTest ./gitolite.nix {};
|
||||
gitolite-fcgiwrap = handleTest ./gitolite-fcgiwrap.nix {};
|
||||
glusterfs = handleTest ./glusterfs.nix {};
|
||||
gnome = handleTest ./gnome.nix {};
|
||||
gnome-xorg = handleTest ./gnome-xorg.nix {};
|
||||
go-neb = handleTest ./go-neb.nix {};
|
||||
gobgpd = handleTest ./gobgpd.nix {};
|
||||
gocd-agent = handleTest ./gocd-agent.nix {};
|
||||
gocd-server = handleTest ./gocd-server.nix {};
|
||||
google-oslogin = handleTest ./google-oslogin {};
|
||||
gotify-server = handleTest ./gotify-server.nix {};
|
||||
grafana = handleTest ./grafana.nix {};
|
||||
graphite = handleTest ./graphite.nix {};
|
||||
graylog = handleTest ./graylog.nix {};
|
||||
grocy = handleTest ./grocy.nix {};
|
||||
grub = handleTest ./grub.nix {};
|
||||
gvisor = handleTest ./gvisor.nix {};
|
||||
hadoop = import ./hadoop { inherit handleTestOn; package=pkgs.hadoop; };
|
||||
hadoop_3_2 = import ./hadoop { inherit handleTestOn; package=pkgs.hadoop_3_2; };
|
||||
hadoop2 = import ./hadoop { inherit handleTestOn; package=pkgs.hadoop2; };
|
||||
haka = handleTest ./haka.nix {};
|
||||
haste-server = handleTest ./haste-server.nix {};
|
||||
haproxy = handleTest ./haproxy.nix {};
|
||||
hardened = handleTest ./hardened.nix {};
|
||||
hbase1 = handleTest ./hbase.nix { package=pkgs.hbase1; };
|
||||
hbase2 = handleTest ./hbase.nix { package=pkgs.hbase2; };
|
||||
hbase3 = handleTest ./hbase.nix { package=pkgs.hbase3; };
|
||||
hedgedoc = handleTest ./hedgedoc.nix {};
|
||||
herbstluftwm = handleTest ./herbstluftwm.nix {};
|
||||
installed-tests = pkgs.recurseIntoAttrs (handleTest ./installed-tests {});
|
||||
invidious = handleTest ./invidious.nix {};
|
||||
oci-containers = handleTestOn ["x86_64-linux"] ./oci-containers.nix {};
|
||||
odoo = handleTest ./odoo.nix {};
|
||||
# 9pnet_virtio used to mount /nix partition doesn't support
|
||||
# hibernation. This test happens to work on x86_64-linux but
|
||||
# not on other platforms.
|
||||
hibernate = handleTestOn ["x86_64-linux"] ./hibernate.nix {};
|
||||
hibernate-systemd-stage-1 = handleTestOn ["x86_64-linux"] ./hibernate.nix { systemdStage1 = true; };
|
||||
hitch = handleTest ./hitch {};
|
||||
hledger-web = handleTest ./hledger-web.nix {};
|
||||
hocker-fetchdocker = handleTest ./hocker-fetchdocker {};
|
||||
hockeypuck = handleTest ./hockeypuck.nix { };
|
||||
home-assistant = handleTest ./home-assistant.nix {};
|
||||
hostname = handleTest ./hostname.nix {};
|
||||
hound = handleTest ./hound.nix {};
|
||||
hub = handleTest ./git/hub.nix {};
|
||||
hydra = handleTest ./hydra {};
|
||||
i3wm = handleTest ./i3wm.nix {};
|
||||
icingaweb2 = handleTest ./icingaweb2.nix {};
|
||||
iftop = handleTest ./iftop.nix {};
|
||||
ihatemoney = handleTest ./ihatemoney {};
|
||||
incron = handleTest ./incron.nix {};
|
||||
influxdb = handleTest ./influxdb.nix {};
|
||||
initrd-network-openvpn = handleTest ./initrd-network-openvpn {};
|
||||
initrd-network-ssh = handleTest ./initrd-network-ssh {};
|
||||
initrdNetwork = handleTest ./initrd-network.nix {};
|
||||
initrd-secrets = handleTest ./initrd-secrets.nix {};
|
||||
input-remapper = handleTest ./input-remapper.nix {};
|
||||
inspircd = handleTest ./inspircd.nix {};
|
||||
installer = handleTest ./installer.nix {};
|
||||
installer-systemd-stage-1 = handleTest ./installer-systemd-stage-1.nix {};
|
||||
invoiceplane = handleTest ./invoiceplane.nix {};
|
||||
iodine = handleTest ./iodine.nix {};
|
||||
ipfs = handleTest ./ipfs.nix {};
|
||||
ipv6 = handleTest ./ipv6.nix {};
|
||||
iscsi-multipath-root = handleTest ./iscsi-multipath-root.nix {};
|
||||
iscsi-root = handleTest ./iscsi-root.nix {};
|
||||
isso = handleTest ./isso.nix {};
|
||||
jackett = handleTest ./jackett.nix {};
|
||||
jellyfin = handleTest ./jellyfin.nix {};
|
||||
jenkins = handleTest ./jenkins.nix {};
|
||||
jenkins-cli = handleTest ./jenkins-cli.nix {};
|
||||
jibri = handleTest ./jibri.nix {};
|
||||
jirafeau = handleTest ./jirafeau.nix {};
|
||||
jitsi-meet = handleTest ./jitsi-meet.nix {};
|
||||
k3s-single-node = handleTest ./k3s-single-node.nix {};
|
||||
k3s-single-node-docker = handleTest ./k3s-single-node-docker.nix {};
|
||||
kafka = handleTest ./kafka.nix {};
|
||||
kanidm = handleTest ./kanidm.nix {};
|
||||
kbd-setfont-decompress = handleTest ./kbd-setfont-decompress.nix {};
|
||||
kbd-update-search-paths-patch = handleTest ./kbd-update-search-paths-patch.nix {};
|
||||
kea = handleTest ./kea.nix {};
|
||||
keepalived = handleTest ./keepalived.nix {};
|
||||
keepassxc = handleTest ./keepassxc.nix {};
|
||||
kerberos = handleTest ./kerberos/default.nix {};
|
||||
kernel-generic = handleTest ./kernel-generic.nix {};
|
||||
kernel-latest-ath-user-regd = handleTest ./kernel-latest-ath-user-regd.nix {};
|
||||
kexec = handleTest ./kexec.nix {};
|
||||
keycloak = discoverTests (import ./keycloak.nix);
|
||||
keymap = handleTest ./keymap.nix {};
|
||||
knot = handleTest ./knot.nix {};
|
||||
krb5 = discoverTests (import ./krb5 {});
|
||||
ksm = handleTest ./ksm.nix {};
|
||||
kubernetes = handleTestOn ["x86_64-linux"] ./kubernetes {};
|
||||
latestKernel.login = handleTest ./login.nix { latestKernel = true; };
|
||||
leaps = handleTest ./leaps.nix {};
|
||||
libinput = handleTest ./libinput.nix {};
|
||||
libreddit = handleTest ./libreddit.nix {};
|
||||
libresprite = handleTest ./libresprite.nix {};
|
||||
libreswan = handleTest ./libreswan.nix {};
|
||||
lidarr = handleTest ./lidarr.nix {};
|
||||
lightdm = handleTest ./lightdm.nix {};
|
||||
limesurvey = handleTest ./limesurvey.nix {};
|
||||
litestream = handleTest ./litestream.nix {};
|
||||
locate = handleTest ./locate.nix {};
|
||||
login = handleTest ./login.nix {};
|
||||
logrotate = handleTest ./logrotate.nix {};
|
||||
loki = handleTest ./loki.nix {};
|
||||
lvm2 = handleTest ./lvm2 {};
|
||||
lxd = handleTest ./lxd.nix {};
|
||||
lxd-nftables = handleTest ./lxd-nftables.nix {};
|
||||
lxd-image-server = handleTest ./lxd-image-server.nix {};
|
||||
#logstash = handleTest ./logstash.nix {};
|
||||
lorri = handleTest ./lorri/default.nix {};
|
||||
maddy = handleTest ./maddy.nix {};
|
||||
maestral = handleTest ./maestral.nix {};
|
||||
magic-wormhole-mailbox-server = handleTest ./magic-wormhole-mailbox-server.nix {};
|
||||
magnetico = handleTest ./magnetico.nix {};
|
||||
mailcatcher = handleTest ./mailcatcher.nix {};
|
||||
mailhog = handleTest ./mailhog.nix {};
|
||||
man = handleTest ./man.nix {};
|
||||
mariadb-galera = handleTest ./mysql/mariadb-galera.nix {};
|
||||
mastodon = handleTestOn ["x86_64-linux" "i686-linux" "aarch64-linux"] ./web-apps/mastodon.nix {};
|
||||
matomo = handleTest ./matomo.nix {};
|
||||
matrix-appservice-irc = handleTest ./matrix-appservice-irc.nix {};
|
||||
matrix-conduit = handleTest ./matrix-conduit.nix {};
|
||||
matrix-synapse = handleTest ./matrix-synapse.nix {};
|
||||
mattermost = handleTest ./mattermost.nix {};
|
||||
mediatomb = handleTest ./mediatomb.nix {};
|
||||
mediawiki = handleTest ./mediawiki.nix {};
|
||||
meilisearch = handleTest ./meilisearch.nix {};
|
||||
memcached = handleTest ./memcached.nix {};
|
||||
metabase = handleTest ./metabase.nix {};
|
||||
minecraft = handleTest ./minecraft.nix {};
|
||||
minecraft-server = handleTest ./minecraft-server.nix {};
|
||||
minidlna = handleTest ./minidlna.nix {};
|
||||
miniflux = handleTest ./miniflux.nix {};
|
||||
minio = handleTest ./minio.nix {};
|
||||
misc = handleTest ./misc.nix {};
|
||||
mjolnir = handleTest ./matrix/mjolnir.nix {};
|
||||
mod_perl = handleTest ./mod_perl.nix {};
|
||||
molly-brown = handleTest ./molly-brown.nix {};
|
||||
mongodb = handleTest ./mongodb.nix {};
|
||||
moodle = handleTest ./moodle.nix {};
|
||||
moonraker = handleTest ./moonraker.nix {};
|
||||
morty = handleTest ./morty.nix {};
|
||||
mosquitto = handleTest ./mosquitto.nix {};
|
||||
moosefs = handleTest ./moosefs.nix {};
|
||||
mpd = handleTest ./mpd.nix {};
|
||||
mpv = handleTest ./mpv.nix {};
|
||||
mtp = handleTest ./mtp.nix {};
|
||||
mumble = handleTest ./mumble.nix {};
|
||||
musescore = handleTest ./musescore.nix {};
|
||||
munin = handleTest ./munin.nix {};
|
||||
mutableUsers = handleTest ./mutable-users.nix {};
|
||||
mxisd = handleTest ./mxisd.nix {};
|
||||
mysql = handleTest ./mysql/mysql.nix {};
|
||||
mysql-autobackup = handleTest ./mysql/mysql-autobackup.nix {};
|
||||
mysql-backup = handleTest ./mysql/mysql-backup.nix {};
|
||||
mysql-replication = handleTest ./mysql/mysql-replication.nix {};
|
||||
n8n = handleTest ./n8n.nix {};
|
||||
nagios = handleTest ./nagios.nix {};
|
||||
nar-serve = handleTest ./nar-serve.nix {};
|
||||
nat.firewall = handleTest ./nat.nix { withFirewall = true; };
|
||||
nat.firewall-conntrack = handleTest ./nat.nix { withFirewall = true; withConntrackHelpers = true; };
|
||||
nat.standalone = handleTest ./nat.nix { withFirewall = false; };
|
||||
nats = handleTest ./nats.nix {};
|
||||
navidrome = handleTest ./navidrome.nix {};
|
||||
nbd = handleTest ./nbd.nix {};
|
||||
ncdns = handleTest ./ncdns.nix {};
|
||||
ndppd = handleTest ./ndppd.nix {};
|
||||
nebula = handleTest ./nebula.nix {};
|
||||
neo4j = handleTest ./neo4j.nix {};
|
||||
netdata = handleTest ./netdata.nix {};
|
||||
networking.networkd = handleTest ./networking.nix { networkd = true; };
|
||||
networking.scripted = handleTest ./networking.nix { networkd = false; };
|
||||
specialisation = handleTest ./specialisation.nix {};
|
||||
netbox = handleTest ./web-apps/netbox.nix {};
|
||||
# TODO: put in networking.nix after the test becomes more complete
|
||||
networkingProxy = handleTest ./networking-proxy.nix {};
|
||||
nextcloud = handleTest ./nextcloud {};
|
||||
nexus = handleTest ./nexus.nix {};
|
||||
# TODO: Test nfsv3 + Kerberos
|
||||
nfs3 = handleTest ./nfs { version = 3; };
|
||||
nfs4 = handleTest ./nfs { version = 4; };
|
||||
nghttpx = handleTest ./nghttpx.nix {};
|
||||
nginx = handleTest ./nginx.nix {};
|
||||
nginx-auth = handleTest ./nginx-auth.nix {};
|
||||
nginx-etag = handleTest ./nginx-etag.nix {};
|
||||
nginx-http3 = handleTest ./nginx-http3.nix {};
|
||||
nginx-modsecurity = handleTest ./nginx-modsecurity.nix {};
|
||||
nginx-pubhtml = handleTest ./nginx-pubhtml.nix {};
|
||||
nginx-sandbox = handleTestOn ["x86_64-linux"] ./nginx-sandbox.nix {};
|
||||
nginx-sso = handleTest ./nginx-sso.nix {};
|
||||
nginx-variants = handleTest ./nginx-variants.nix {};
|
||||
nifi = handleTestOn ["x86_64-linux"] ./web-apps/nifi.nix {};
|
||||
nitter = handleTest ./nitter.nix {};
|
||||
nix-ld = handleTest ./nix-ld.nix {};
|
||||
nix-serve = handleTest ./nix-serve.nix {};
|
||||
nix-serve-ssh = handleTest ./nix-serve-ssh.nix {};
|
||||
nixops = handleTest ./nixops/default.nix {};
|
||||
nixos-generate-config = handleTest ./nixos-generate-config.nix {};
|
||||
nixpkgs = pkgs.callPackage ../modules/misc/nixpkgs/test.nix { inherit evalMinimalConfig; };
|
||||
node-red = handleTest ./node-red.nix {};
|
||||
nomad = handleTest ./nomad.nix {};
|
||||
noto-fonts = handleTest ./noto-fonts.nix {};
|
||||
novacomd = handleTestOn ["x86_64-linux"] ./novacomd.nix {};
|
||||
nsd = handleTest ./nsd.nix {};
|
||||
nzbget = handleTest ./nzbget.nix {};
|
||||
nzbhydra2 = handleTest ./nzbhydra2.nix {};
|
||||
oh-my-zsh = handleTest ./oh-my-zsh.nix {};
|
||||
ombi = handleTest ./ombi.nix {};
|
||||
openarena = handleTest ./openarena.nix {};
|
||||
openldap = handleTest ./openldap.nix {};
|
||||
openresty-lua = handleTest ./openresty-lua.nix {};
|
||||
opensmtpd = handleTest ./opensmtpd.nix {};
|
||||
opensmtpd-rspamd = handleTest ./opensmtpd-rspamd.nix {};
|
||||
openssh = handleTest ./openssh.nix {};
|
||||
openstack-image-metadata = (handleTestOn ["x86_64-linux"] ./openstack-image.nix {}).metadata or {};
|
||||
openstack-image-userdata = (handleTestOn ["x86_64-linux"] ./openstack-image.nix {}).userdata or {};
|
||||
opentabletdriver = handleTest ./opentabletdriver.nix {};
|
||||
owncast = handleTest ./owncast.nix {};
|
||||
image-contents = handleTest ./image-contents.nix {};
|
||||
orangefs = handleTest ./orangefs.nix {};
|
||||
os-prober = handleTestOn ["x86_64-linux"] ./os-prober.nix {};
|
||||
osrm-backend = handleTest ./osrm-backend.nix {};
|
||||
overlayfs = handleTest ./overlayfs.nix {};
|
||||
pacemaker = handleTest ./pacemaker.nix {};
|
||||
packagekit = handleTest ./packagekit.nix {};
|
||||
pam-file-contents = handleTest ./pam/pam-file-contents.nix {};
|
||||
pam-oath-login = handleTest ./pam/pam-oath-login.nix {};
|
||||
pam-u2f = handleTest ./pam/pam-u2f.nix {};
|
||||
pam-ussh = handleTest ./pam/pam-ussh.nix {};
|
||||
pantalaimon = handleTest ./matrix/pantalaimon.nix {};
|
||||
pantheon = handleTest ./pantheon.nix {};
|
||||
paperless = handleTest ./paperless.nix {};
|
||||
parsedmarc = handleTest ./parsedmarc {};
|
||||
pdns-recursor = handleTest ./pdns-recursor.nix {};
|
||||
peerflix = handleTest ./peerflix.nix {};
|
||||
peertube = handleTestOn ["x86_64-linux"] ./web-apps/peertube.nix {};
|
||||
pgadmin4 = handleTest ./pgadmin4.nix {};
|
||||
pgadmin4-standalone = handleTest ./pgadmin4-standalone.nix {};
|
||||
pgjwt = handleTest ./pgjwt.nix {};
|
||||
pgmanage = handleTest ./pgmanage.nix {};
|
||||
php = handleTest ./php {};
|
||||
php74 = handleTest ./php { php = pkgs.php74; };
|
||||
php80 = handleTest ./php { php = pkgs.php80; };
|
||||
php81 = handleTest ./php { php = pkgs.php81; };
|
||||
pict-rs = handleTest ./pict-rs.nix {};
|
||||
pinnwand = handleTest ./pinnwand.nix {};
|
||||
plasma5 = handleTest ./plasma5.nix {};
|
||||
plasma5-systemd-start = handleTest ./plasma5-systemd-start.nix {};
|
||||
plausible = handleTest ./plausible.nix {};
|
||||
pleroma = handleTestOn [ "x86_64-linux" "aarch64-linux" ] ./pleroma.nix {};
|
||||
plikd = handleTest ./plikd.nix {};
|
||||
plotinus = handleTest ./plotinus.nix {};
|
||||
podgrab = handleTest ./podgrab.nix {};
|
||||
podman = handleTestOn ["x86_64-linux"] ./podman/default.nix {};
|
||||
podman-dnsname = handleTestOn ["x86_64-linux"] ./podman/dnsname.nix {};
|
||||
podman-tls-ghostunnel = handleTestOn ["x86_64-linux"] ./podman/tls-ghostunnel.nix {};
|
||||
pomerium = handleTestOn ["x86_64-linux"] ./pomerium.nix {};
|
||||
postfix = handleTest ./postfix.nix {};
|
||||
postfix-raise-smtpd-tls-security-level = handleTest ./postfix-raise-smtpd-tls-security-level.nix {};
|
||||
postfixadmin = handleTest ./postfixadmin.nix {};
|
||||
postgis = handleTest ./postgis.nix {};
|
||||
postgresql = handleTest ./postgresql.nix {};
|
||||
postgresql-wal-receiver = handleTest ./postgresql-wal-receiver.nix {};
|
||||
powerdns = handleTest ./powerdns.nix {};
|
||||
powerdns-admin = handleTest ./powerdns-admin.nix {};
|
||||
power-profiles-daemon = handleTest ./power-profiles-daemon.nix {};
|
||||
pppd = handleTest ./pppd.nix {};
|
||||
predictable-interface-names = handleTest ./predictable-interface-names.nix {};
|
||||
printing = handleTest ./printing.nix {};
|
||||
privacyidea = handleTest ./privacyidea.nix {};
|
||||
privoxy = handleTest ./privoxy.nix {};
|
||||
prometheus = handleTest ./prometheus.nix {};
|
||||
prometheus-exporters = handleTest ./prometheus-exporters.nix {};
|
||||
prosody = handleTest ./xmpp/prosody.nix {};
|
||||
prosody-mysql = handleTest ./xmpp/prosody-mysql.nix {};
|
||||
proxy = handleTest ./proxy.nix {};
|
||||
prowlarr = handleTest ./prowlarr.nix {};
|
||||
pt2-clone = handleTest ./pt2-clone.nix {};
|
||||
public-inbox = handleTest ./public-inbox.nix {};
|
||||
pulseaudio = discoverTests (import ./pulseaudio.nix);
|
||||
qboot = handleTestOn ["x86_64-linux" "i686-linux"] ./qboot.nix {};
|
||||
quorum = handleTest ./quorum.nix {};
|
||||
rabbitmq = handleTest ./rabbitmq.nix {};
|
||||
radarr = handleTest ./radarr.nix {};
|
||||
radicale = handleTest ./radicale.nix {};
|
||||
rasdaemon = handleTest ./rasdaemon.nix {};
|
||||
redis = handleTest ./redis.nix {};
|
||||
redmine = handleTest ./redmine.nix {};
|
||||
resolv = handleTest ./resolv.nix {};
|
||||
restartByActivationScript = handleTest ./restart-by-activation-script.nix {};
|
||||
restic = handleTest ./restic.nix {};
|
||||
retroarch = handleTest ./retroarch.nix {};
|
||||
riak = handleTest ./riak.nix {};
|
||||
robustirc-bridge = handleTest ./robustirc-bridge.nix {};
|
||||
roundcube = handleTest ./roundcube.nix {};
|
||||
rspamd = handleTest ./rspamd.nix {};
|
||||
rss2email = handleTest ./rss2email.nix {};
|
||||
rstudio-server = handleTest ./rstudio-server.nix {};
|
||||
rsyncd = handleTest ./rsyncd.nix {};
|
||||
rsyslogd = handleTest ./rsyslogd.nix {};
|
||||
rxe = handleTest ./rxe.nix {};
|
||||
sabnzbd = handleTest ./sabnzbd.nix {};
|
||||
samba = handleTest ./samba.nix {};
|
||||
samba-wsdd = handleTest ./samba-wsdd.nix {};
|
||||
sanoid = handleTest ./sanoid.nix {};
|
||||
sddm = handleTest ./sddm.nix {};
|
||||
seafile = handleTest ./seafile.nix {};
|
||||
searx = handleTest ./searx.nix {};
|
||||
service-runner = handleTest ./service-runner.nix {};
|
||||
sfxr-qt = handleTest ./sfxr-qt.nix {};
|
||||
shadow = handleTest ./shadow.nix {};
|
||||
shadowsocks = handleTest ./shadowsocks {};
|
||||
shattered-pixel-dungeon = handleTest ./shattered-pixel-dungeon.nix {};
|
||||
shiori = handleTest ./shiori.nix {};
|
||||
signal-desktop = handleTest ./signal-desktop.nix {};
|
||||
simple = handleTest ./simple.nix {};
|
||||
slurm = handleTest ./slurm.nix {};
|
||||
smokeping = handleTest ./smokeping.nix {};
|
||||
snapcast = handleTest ./snapcast.nix {};
|
||||
snapper = handleTest ./snapper.nix {};
|
||||
soapui = handleTest ./soapui.nix {};
|
||||
sogo = handleTest ./sogo.nix {};
|
||||
solanum = handleTest ./solanum.nix {};
|
||||
solr = handleTest ./solr.nix {};
|
||||
sonarr = handleTest ./sonarr.nix {};
|
||||
sourcehut = handleTest ./sourcehut.nix {};
|
||||
spacecookie = handleTest ./spacecookie.nix {};
|
||||
spark = handleTestOn [ "x86_64-linux" "aarch64-linux" ] ./spark {};
|
||||
sslh = handleTest ./sslh.nix {};
|
||||
sssd = handleTestOn ["x86_64-linux"] ./sssd.nix {};
|
||||
sssd-ldap = handleTestOn ["x86_64-linux"] ./sssd-ldap.nix {};
|
||||
starship = handleTest ./starship.nix {};
|
||||
step-ca = handleTestOn ["x86_64-linux"] ./step-ca.nix {};
|
||||
strongswan-swanctl = handleTest ./strongswan-swanctl.nix {};
|
||||
sudo = handleTest ./sudo.nix {};
|
||||
sway = handleTest ./sway.nix {};
|
||||
switchTest = handleTest ./switch-test.nix {};
|
||||
sympa = handleTest ./sympa.nix {};
|
||||
syncthing = handleTest ./syncthing.nix {};
|
||||
syncthing-init = handleTest ./syncthing-init.nix {};
|
||||
syncthing-relay = handleTest ./syncthing-relay.nix {};
|
||||
systemd = handleTest ./systemd.nix {};
|
||||
systemd-analyze = handleTest ./systemd-analyze.nix {};
|
||||
systemd-binfmt = handleTestOn ["x86_64-linux"] ./systemd-binfmt.nix {};
|
||||
systemd-boot = handleTest ./systemd-boot.nix {};
|
||||
systemd-confinement = handleTest ./systemd-confinement.nix {};
|
||||
systemd-cryptenroll = handleTest ./systemd-cryptenroll.nix {};
|
||||
systemd-escaping = handleTest ./systemd-escaping.nix {};
|
||||
systemd-initrd-btrfs-raid = handleTest ./systemd-initrd-btrfs-raid.nix {};
|
||||
systemd-initrd-luks-keyfile = handleTest ./systemd-initrd-luks-keyfile.nix {};
|
||||
systemd-initrd-luks-password = handleTest ./systemd-initrd-luks-password.nix {};
|
||||
systemd-initrd-shutdown = handleTest ./systemd-shutdown.nix { systemdStage1 = true; };
|
||||
systemd-initrd-simple = handleTest ./systemd-initrd-simple.nix {};
|
||||
systemd-initrd-swraid = handleTest ./systemd-initrd-swraid.nix {};
|
||||
systemd-journal = handleTest ./systemd-journal.nix {};
|
||||
systemd-machinectl = handleTest ./systemd-machinectl.nix {};
|
||||
systemd-networkd = handleTest ./systemd-networkd.nix {};
|
||||
systemd-networkd-dhcpserver = handleTest ./systemd-networkd-dhcpserver.nix {};
|
||||
systemd-networkd-dhcpserver-static-leases = handleTest ./systemd-networkd-dhcpserver-static-leases.nix {};
|
||||
systemd-networkd-ipv6-prefix-delegation = handleTest ./systemd-networkd-ipv6-prefix-delegation.nix {};
|
||||
systemd-networkd-vrf = handleTest ./systemd-networkd-vrf.nix {};
|
||||
systemd-nspawn = handleTest ./systemd-nspawn.nix {};
|
||||
systemd-shutdown = handleTest ./systemd-shutdown.nix {};
|
||||
systemd-timesyncd = handleTest ./systemd-timesyncd.nix {};
|
||||
systemd-misc = handleTest ./systemd-misc.nix {};
|
||||
taskserver = handleTest ./taskserver.nix {};
|
||||
teeworlds = handleTest ./teeworlds.nix {};
|
||||
telegraf = handleTest ./telegraf.nix {};
|
||||
teleport = handleTest ./teleport.nix {};
|
||||
thelounge = handleTest ./thelounge.nix {};
|
||||
terminal-emulators = handleTest ./terminal-emulators.nix {};
|
||||
tiddlywiki = handleTest ./tiddlywiki.nix {};
|
||||
tigervnc = handleTest ./tigervnc.nix {};
|
||||
timezone = handleTest ./timezone.nix {};
|
||||
tinc = handleTest ./tinc {};
|
||||
tinydns = handleTest ./tinydns.nix {};
|
||||
tinywl = handleTest ./tinywl.nix {};
|
||||
tomcat = handleTest ./tomcat.nix {};
|
||||
tor = handleTest ./tor.nix {};
|
||||
# traefik test relies on docker-containers
|
||||
traefik = handleTestOn ["x86_64-linux"] ./traefik.nix {};
|
||||
trafficserver = handleTest ./trafficserver.nix {};
|
||||
transmission = handleTest ./transmission.nix {};
|
||||
trezord = handleTest ./trezord.nix {};
|
||||
trickster = handleTest ./trickster.nix {};
|
||||
trilium-server = handleTestOn ["x86_64-linux"] ./trilium-server.nix {};
|
||||
tsm-client-gui = handleTest ./tsm-client-gui.nix {};
|
||||
txredisapi = handleTest ./txredisapi.nix {};
|
||||
tuptime = handleTest ./tuptime.nix {};
|
||||
turbovnc-headless-server = handleTest ./turbovnc-headless-server.nix {};
|
||||
tuxguitar = handleTest ./tuxguitar.nix {};
|
||||
ucarp = handleTest ./ucarp.nix {};
|
||||
udisks2 = handleTest ./udisks2.nix {};
|
||||
unbound = handleTest ./unbound.nix {};
|
||||
unifi = handleTest ./unifi.nix {};
|
||||
unit-php = handleTest ./web-servers/unit-php.nix {};
|
||||
upnp = handleTest ./upnp.nix {};
|
||||
uptermd = handleTest ./uptermd.nix {};
|
||||
usbguard = handleTest ./usbguard.nix {};
|
||||
user-activation-scripts = handleTest ./user-activation-scripts.nix {};
|
||||
user-home-mode = handleTest ./user-home-mode.nix {};
|
||||
uwsgi = handleTest ./uwsgi.nix {};
|
||||
v2ray = handleTest ./v2ray.nix {};
|
||||
vault = handleTest ./vault.nix {};
|
||||
vault-postgresql = handleTest ./vault-postgresql.nix {};
|
||||
vaultwarden = handleTest ./vaultwarden.nix {};
|
||||
vector = handleTest ./vector.nix {};
|
||||
vengi-tools = handleTest ./vengi-tools.nix {};
|
||||
victoriametrics = handleTest ./victoriametrics.nix {};
|
||||
vikunja = handleTest ./vikunja.nix {};
|
||||
virtualbox = handleTestOn ["x86_64-linux"] ./virtualbox.nix {};
|
||||
vscodium = discoverTests (import ./vscodium.nix);
|
||||
vsftpd = handleTest ./vsftpd.nix {};
|
||||
wasabibackend = handleTest ./wasabibackend.nix {};
|
||||
wiki-js = handleTest ./wiki-js.nix {};
|
||||
wine = handleTest ./wine.nix {};
|
||||
wireguard = handleTest ./wireguard {};
|
||||
without-nix = handleTest ./without-nix.nix {};
|
||||
wmderland = handleTest ./wmderland.nix {};
|
||||
wpa_supplicant = handleTest ./wpa_supplicant.nix {};
|
||||
wordpress = handleTest ./wordpress.nix {};
|
||||
xandikos = handleTest ./xandikos.nix {};
|
||||
xautolock = handleTest ./xautolock.nix {};
|
||||
xfce = handleTest ./xfce.nix {};
|
||||
xmonad = handleTest ./xmonad.nix {};
|
||||
xmonad-xdg-autostart = handleTest ./xmonad-xdg-autostart.nix {};
|
||||
xrdp = handleTest ./xrdp.nix {};
|
||||
xss-lock = handleTest ./xss-lock.nix {};
|
||||
xterm = handleTest ./xterm.nix {};
|
||||
xxh = handleTest ./xxh.nix {};
|
||||
yabar = handleTest ./yabar.nix {};
|
||||
yggdrasil = handleTest ./yggdrasil.nix {};
|
||||
zammad = handleTest ./zammad.nix {};
|
||||
zfs = handleTest ./zfs.nix {};
|
||||
zigbee2mqtt = handleTest ./zigbee2mqtt.nix {};
|
||||
zoneminder = handleTest ./zoneminder.nix {};
|
||||
zookeeper = handleTest ./zookeeper.nix {};
|
||||
zrepl = handleTest ./zrepl.nix {};
|
||||
zsh-history = handleTest ./zsh-history.nix {};
|
||||
}
|
||||
40
nixos/tests/amazon-init-shell.nix
Normal file
40
nixos/tests/amazon-init-shell.nix
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
# This test verifies that the amazon-init service can treat the `user-data` ec2
|
||||
# metadata file as a shell script. If amazon-init detects that `user-data` is a
|
||||
# script (based on the presence of the shebang #! line) it executes it and
|
||||
# exits.
|
||||
# Note that other tests verify that amazon-init can treat user-data as a nixos
|
||||
# configuration expression.
|
||||
|
||||
{ system ? builtins.currentSystem,
|
||||
config ? {},
|
||||
pkgs ? import ../.. { inherit system config; }
|
||||
}:
|
||||
|
||||
with import ../lib/testing-python.nix { inherit system pkgs; };
|
||||
with pkgs.lib;
|
||||
|
||||
makeTest {
|
||||
name = "amazon-init";
|
||||
meta = with maintainers; {
|
||||
maintainers = [ urbas ];
|
||||
};
|
||||
nodes.machine = { ... }:
|
||||
{
|
||||
imports = [ ../modules/profiles/headless.nix ../modules/virtualisation/amazon-init.nix ];
|
||||
services.openssh.enable = true;
|
||||
networking.hostName = "";
|
||||
environment.etc."ec2-metadata/user-data" = {
|
||||
text = ''
|
||||
#!/usr/bin/bash
|
||||
|
||||
echo successful > /tmp/evidence
|
||||
'';
|
||||
};
|
||||
};
|
||||
testScript = ''
|
||||
# To wait until amazon-init terminates its run
|
||||
unnamed.wait_for_unit("amazon-init.service")
|
||||
|
||||
unnamed.succeed("grep -q successful /tmp/evidence")
|
||||
'';
|
||||
}
|
||||
54
nixos/tests/apfs.nix
Normal file
54
nixos/tests/apfs.nix
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
import ./make-test-python.nix ({ pkgs, ... }: {
|
||||
name = "apfs";
|
||||
meta.maintainers = with pkgs.lib.maintainers; [ Luflosi ];
|
||||
|
||||
nodes.machine = { pkgs, ... }: {
|
||||
virtualisation.emptyDiskImages = [ 1024 ];
|
||||
|
||||
boot.supportedFilesystems = [ "apfs" ];
|
||||
};
|
||||
|
||||
testScript = ''
|
||||
machine.wait_for_unit("basic.target")
|
||||
machine.succeed("mkdir /tmp/mnt")
|
||||
|
||||
with subtest("mkapfs refuses to work with a label that is too long"):
|
||||
machine.fail( "mkapfs -L '000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F202122232425262728292A2B2C2D2E2F303132333435363738393A3B3C3D3E3F404142434445464748494A4B4C4D4E4F505152535455565758595A5B5C5D5E5F606162636465666768696A6B6C6D6E6F707172737475767778797A7B7C7D7E7F' /dev/vdb")
|
||||
|
||||
with subtest("mkapfs works with the maximum label length"):
|
||||
machine.succeed("mkapfs -L '000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F202122232425262728292A2B2C2D2E2F303132333435363738393A3B3C3D3E3F404142434445464748494A4B4C4D4E4F505152535455565758595A5B5C5D5E5F606162636465666768696A6B6C6D6E6F707172737475767778797A7B7C7D7E7' /dev/vdb")
|
||||
|
||||
with subtest("Enable case sensitivity and normalization sensitivity"):
|
||||
machine.succeed(
|
||||
"mkapfs -s -z /dev/vdb",
|
||||
# Triggers a bug, see https://github.com/linux-apfs/linux-apfs-rw/issues/15
|
||||
# "mount -o cknodes,readwrite /dev/vdb /tmp/mnt",
|
||||
"mount -o readwrite /dev/vdb /tmp/mnt",
|
||||
"echo 'Hello World 1' > /tmp/mnt/test.txt",
|
||||
"[ ! -f /tmp/mnt/TeSt.TxT ] || false", # Test case sensitivity
|
||||
"echo 'Hello World 1' | diff - /tmp/mnt/test.txt",
|
||||
"echo 'Hello World 2' > /tmp/mnt/\u0061\u0301.txt",
|
||||
"echo 'Hello World 2' | diff - /tmp/mnt/\u0061\u0301.txt",
|
||||
"[ ! -f /tmp/mnt/\u00e1.txt ] || false", # Test Unicode normalization sensitivity
|
||||
"umount /tmp/mnt",
|
||||
"apfsck /dev/vdb",
|
||||
)
|
||||
with subtest("Disable case sensitivity and normalization sensitivity"):
|
||||
machine.succeed(
|
||||
"mkapfs /dev/vdb",
|
||||
"mount -o readwrite /dev/vdb /tmp/mnt",
|
||||
"echo 'bla bla bla' > /tmp/mnt/Test.txt",
|
||||
"echo -n 'Hello World' > /tmp/mnt/test.txt",
|
||||
"echo ' 1' >> /tmp/mnt/TEST.TXT",
|
||||
"umount /tmp/mnt",
|
||||
"apfsck /dev/vdb",
|
||||
"mount -o readwrite /dev/vdb /tmp/mnt",
|
||||
"echo 'Hello World 1' | diff - /tmp/mnt/TeSt.TxT", # Test case insensitivity
|
||||
"echo 'Hello World 2' > /tmp/mnt/\u0061\u0301.txt",
|
||||
"echo 'Hello World 2' | diff - /tmp/mnt/\u0061\u0301.txt",
|
||||
"echo 'Hello World 2' | diff - /tmp/mnt/\u00e1.txt", # Test Unicode normalization
|
||||
"umount /tmp/mnt",
|
||||
"apfsck /dev/vdb",
|
||||
)
|
||||
'';
|
||||
})
|
||||
82
nixos/tests/apparmor.nix
Normal file
82
nixos/tests/apparmor.nix
Normal file
|
|
@ -0,0 +1,82 @@
|
|||
import ./make-test-python.nix ({ pkgs, ... } : {
|
||||
name = "apparmor";
|
||||
meta = with pkgs.lib.maintainers; {
|
||||
maintainers = [ julm ];
|
||||
};
|
||||
|
||||
nodes.machine =
|
||||
{ lib, pkgs, config, ... }:
|
||||
with lib;
|
||||
{
|
||||
security.apparmor.enable = mkDefault true;
|
||||
};
|
||||
|
||||
testScript =
|
||||
''
|
||||
machine.wait_for_unit("multi-user.target")
|
||||
|
||||
with subtest("AppArmor profiles are loaded"):
|
||||
machine.succeed("systemctl status apparmor.service")
|
||||
|
||||
# AppArmor securityfs
|
||||
with subtest("AppArmor securityfs is mounted"):
|
||||
machine.succeed("mountpoint -q /sys/kernel/security")
|
||||
machine.succeed("cat /sys/kernel/security/apparmor/profiles")
|
||||
|
||||
# Test apparmorRulesFromClosure by:
|
||||
# 1. Prepending a string of the relevant packages' name and version on each line.
|
||||
# 2. Sorting according to those strings.
|
||||
# 3. Removing those prepended strings.
|
||||
# 4. Using `diff` against the expected output.
|
||||
with subtest("apparmorRulesFromClosure"):
|
||||
machine.succeed(
|
||||
"${pkgs.diffutils}/bin/diff ${pkgs.writeText "expected.rules" ''
|
||||
mr ${pkgs.bash}/lib/**.so*,
|
||||
r ${pkgs.bash},
|
||||
r ${pkgs.bash}/etc/**,
|
||||
r ${pkgs.bash}/lib/**,
|
||||
r ${pkgs.bash}/share/**,
|
||||
x ${pkgs.bash}/foo/**,
|
||||
mr ${pkgs.glibc}/lib/**.so*,
|
||||
r ${pkgs.glibc},
|
||||
r ${pkgs.glibc}/etc/**,
|
||||
r ${pkgs.glibc}/lib/**,
|
||||
r ${pkgs.glibc}/share/**,
|
||||
x ${pkgs.glibc}/foo/**,
|
||||
mr ${pkgs.libcap}/lib/**.so*,
|
||||
r ${pkgs.libcap},
|
||||
r ${pkgs.libcap}/etc/**,
|
||||
r ${pkgs.libcap}/lib/**,
|
||||
r ${pkgs.libcap}/share/**,
|
||||
x ${pkgs.libcap}/foo/**,
|
||||
mr ${pkgs.libcap.lib}/lib/**.so*,
|
||||
r ${pkgs.libcap.lib},
|
||||
r ${pkgs.libcap.lib}/etc/**,
|
||||
r ${pkgs.libcap.lib}/lib/**,
|
||||
r ${pkgs.libcap.lib}/share/**,
|
||||
x ${pkgs.libcap.lib}/foo/**,
|
||||
mr ${pkgs.libidn2.out}/lib/**.so*,
|
||||
r ${pkgs.libidn2.out},
|
||||
r ${pkgs.libidn2.out}/etc/**,
|
||||
r ${pkgs.libidn2.out}/lib/**,
|
||||
r ${pkgs.libidn2.out}/share/**,
|
||||
x ${pkgs.libidn2.out}/foo/**,
|
||||
mr ${pkgs.libunistring}/lib/**.so*,
|
||||
r ${pkgs.libunistring},
|
||||
r ${pkgs.libunistring}/etc/**,
|
||||
r ${pkgs.libunistring}/lib/**,
|
||||
r ${pkgs.libunistring}/share/**,
|
||||
x ${pkgs.libunistring}/foo/**,
|
||||
''} ${pkgs.runCommand "actual.rules" { preferLocalBuild = true; } ''
|
||||
${pkgs.gnused}/bin/sed -e 's:^[^ ]* ${builtins.storeDir}/[^,/-]*-\([^/,]*\):\1 \0:' ${
|
||||
pkgs.apparmorRulesFromClosure {
|
||||
name = "ping";
|
||||
additionalRules = ["x $path/foo/**"];
|
||||
} [ pkgs.libcap ]
|
||||
} |
|
||||
${pkgs.coreutils}/bin/sort -n -k1 |
|
||||
${pkgs.gnused}/bin/sed -e 's:^[^ ]* ::' >$out
|
||||
''}"
|
||||
)
|
||||
'';
|
||||
})
|
||||
31
nixos/tests/atd.nix
Normal file
31
nixos/tests/atd.nix
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
import ./make-test-python.nix ({ pkgs, ... }:
|
||||
|
||||
{
|
||||
name = "atd";
|
||||
meta = with pkgs.lib.maintainers; {
|
||||
maintainers = [ bjornfor ];
|
||||
};
|
||||
|
||||
nodes.machine =
|
||||
{ ... }:
|
||||
{ services.atd.enable = true;
|
||||
users.users.alice = { isNormalUser = true; };
|
||||
};
|
||||
|
||||
# "at" has a resolution of 1 minute
|
||||
testScript = ''
|
||||
start_all()
|
||||
|
||||
machine.wait_for_unit("atd.service") # wait for atd to start
|
||||
machine.fail("test -f ~root/at-1")
|
||||
machine.fail("test -f ~alice/at-1")
|
||||
|
||||
machine.succeed("echo 'touch ~root/at-1' | at now+1min")
|
||||
machine.succeed("su - alice -c \"echo 'touch at-1' | at now+1min\"")
|
||||
|
||||
machine.succeed("sleep 1.5m")
|
||||
|
||||
machine.succeed("test -f ~root/at-1")
|
||||
machine.succeed("test -f ~alice/at-1")
|
||||
'';
|
||||
})
|
||||
226
nixos/tests/atop.nix
Normal file
226
nixos/tests/atop.nix
Normal file
|
|
@ -0,0 +1,226 @@
|
|||
{ system ? builtins.currentSystem
|
||||
, config ? { }
|
||||
, pkgs ? import ../.. { inherit system config; }
|
||||
}:
|
||||
|
||||
with import ../lib/testing-python.nix { inherit system pkgs; };
|
||||
with pkgs.lib;
|
||||
|
||||
let assertions = rec {
|
||||
path = program: path: ''
|
||||
with subtest("The path of ${program} should be ${path}"):
|
||||
p = machine.succeed("type -p \"${program}\" | head -c -1")
|
||||
assert p == "${path}", f"${program} is {p}, expected ${path}"
|
||||
'';
|
||||
unit = name: state: ''
|
||||
with subtest("Unit ${name} should be ${state}"):
|
||||
if "${state}" == "active":
|
||||
machine.wait_for_unit("${name}")
|
||||
else:
|
||||
machine.require_unit_state("${name}", "${state}")
|
||||
'';
|
||||
version = ''
|
||||
import re
|
||||
|
||||
with subtest("binary should report the correct version"):
|
||||
pkgver = "${pkgs.atop.version}"
|
||||
ver = re.sub(r'(?s)^Version: (\d\.\d\.\d).*', r'\1', machine.succeed("atop -V"))
|
||||
assert ver == pkgver, f"Version is `{ver}`, expected `{pkgver}`"
|
||||
'';
|
||||
atoprc = contents:
|
||||
if builtins.stringLength contents > 0 then ''
|
||||
with subtest("/etc/atoprc should have the correct contents"):
|
||||
f = machine.succeed("cat /etc/atoprc")
|
||||
assert f == "${contents}", f"/etc/atoprc contents: '{f}', expected '${contents}'"
|
||||
'' else ''
|
||||
with subtest("/etc/atoprc should not be present"):
|
||||
machine.succeed("test ! -e /etc/atoprc")
|
||||
'';
|
||||
wrapper = present:
|
||||
if present then path "atop" "/run/wrappers/bin/atop" + ''
|
||||
with subtest("Wrapper should be setuid root"):
|
||||
stat = machine.succeed("stat --printf '%a %u' /run/wrappers/bin/atop")
|
||||
assert stat == "4511 0", f"Wrapper stat is {stat}, expected '4511 0'"
|
||||
''
|
||||
else path "atop" "/run/current-system/sw/bin/atop";
|
||||
atopService = present:
|
||||
if present then
|
||||
unit "atop.service" "active"
|
||||
+ ''
|
||||
with subtest("atop.service should write some data to /var/log/atop"):
|
||||
|
||||
def has_data_files(last: bool) -> bool:
|
||||
files = int(machine.succeed("ls -1 /var/log/atop | wc -l"))
|
||||
if files == 0:
|
||||
machine.log("Did not find at least one 1 data file")
|
||||
if not last:
|
||||
machine.log("Will retry...")
|
||||
return False
|
||||
return True
|
||||
|
||||
with machine.nested("Waiting for data files"):
|
||||
retry(has_data_files)
|
||||
'' else unit "atop.service" "inactive";
|
||||
atopRotateTimer = present:
|
||||
unit "atop-rotate.timer" (if present then "active" else "inactive");
|
||||
atopacctService = present:
|
||||
if present then
|
||||
unit "atopacct.service" "active"
|
||||
+ ''
|
||||
with subtest("atopacct.service should enable process accounting"):
|
||||
machine.wait_until_succeeds("test -f /run/pacct_source")
|
||||
|
||||
with subtest("atopacct.service should write data to /run/pacct_shadow.d"):
|
||||
|
||||
def has_data_files(last: bool) -> bool:
|
||||
files = int(machine.succeed("ls -1 /run/pacct_shadow.d | wc -l"))
|
||||
if files == 0:
|
||||
machine.log("Did not find at least one 1 data file")
|
||||
if not last:
|
||||
machine.log("Will retry...")
|
||||
return False
|
||||
return True
|
||||
|
||||
with machine.nested("Waiting for data files"):
|
||||
retry(has_data_files)
|
||||
'' else unit "atopacct.service" "inactive";
|
||||
netatop = present:
|
||||
if present then
|
||||
unit "netatop.service" "active"
|
||||
+ ''
|
||||
with subtest("The netatop kernel module should be loaded"):
|
||||
out = machine.succeed("modprobe -n -v netatop")
|
||||
assert out == "", f"Module should be loaded already, but modprobe would have done {out}."
|
||||
'' else ''
|
||||
with subtest("The netatop kernel module should be absent"):
|
||||
machine.fail("modprobe -n -v netatop")
|
||||
'';
|
||||
atopgpu = present:
|
||||
if present then
|
||||
(unit "atopgpu.service" "active") + (path "atopgpud" "/run/current-system/sw/bin/atopgpud")
|
||||
else (unit "atopgpu.service" "inactive") + ''
|
||||
with subtest("atopgpud should not be present"):
|
||||
machine.fail("type -p atopgpud")
|
||||
'';
|
||||
};
|
||||
in
|
||||
{
|
||||
justThePackage = makeTest {
|
||||
name = "atop-justThePackage";
|
||||
nodes.machine = {
|
||||
environment.systemPackages = [ pkgs.atop ];
|
||||
};
|
||||
testScript = with assertions; builtins.concatStringsSep "\n" [
|
||||
version
|
||||
(atoprc "")
|
||||
(wrapper false)
|
||||
(atopService false)
|
||||
(atopRotateTimer false)
|
||||
(atopacctService false)
|
||||
(netatop false)
|
||||
(atopgpu false)
|
||||
];
|
||||
};
|
||||
defaults = makeTest {
|
||||
name = "atop-defaults";
|
||||
nodes.machine = {
|
||||
programs.atop = {
|
||||
enable = true;
|
||||
};
|
||||
};
|
||||
testScript = with assertions; builtins.concatStringsSep "\n" [
|
||||
version
|
||||
(atoprc "")
|
||||
(wrapper false)
|
||||
(atopService true)
|
||||
(atopRotateTimer true)
|
||||
(atopacctService true)
|
||||
(netatop false)
|
||||
(atopgpu false)
|
||||
];
|
||||
};
|
||||
minimal = makeTest {
|
||||
name = "atop-minimal";
|
||||
nodes.machine = {
|
||||
programs.atop = {
|
||||
enable = true;
|
||||
atopService.enable = false;
|
||||
atopRotateTimer.enable = false;
|
||||
atopacctService.enable = false;
|
||||
};
|
||||
};
|
||||
testScript = with assertions; builtins.concatStringsSep "\n" [
|
||||
version
|
||||
(atoprc "")
|
||||
(wrapper false)
|
||||
(atopService false)
|
||||
(atopRotateTimer false)
|
||||
(atopacctService false)
|
||||
(netatop false)
|
||||
(atopgpu false)
|
||||
];
|
||||
};
|
||||
netatop = makeTest {
|
||||
name = "atop-netatop";
|
||||
nodes.machine = {
|
||||
programs.atop = {
|
||||
enable = true;
|
||||
netatop.enable = true;
|
||||
};
|
||||
};
|
||||
testScript = with assertions; builtins.concatStringsSep "\n" [
|
||||
version
|
||||
(atoprc "")
|
||||
(wrapper false)
|
||||
(atopService true)
|
||||
(atopRotateTimer true)
|
||||
(atopacctService true)
|
||||
(netatop true)
|
||||
(atopgpu false)
|
||||
];
|
||||
};
|
||||
atopgpu = makeTest {
|
||||
name = "atop-atopgpu";
|
||||
nodes.machine = {
|
||||
programs.atop = {
|
||||
enable = true;
|
||||
atopgpu.enable = true;
|
||||
};
|
||||
};
|
||||
testScript = with assertions; builtins.concatStringsSep "\n" [
|
||||
version
|
||||
(atoprc "")
|
||||
(wrapper false)
|
||||
(atopService true)
|
||||
(atopRotateTimer true)
|
||||
(atopacctService true)
|
||||
(netatop false)
|
||||
(atopgpu true)
|
||||
];
|
||||
};
|
||||
everything = makeTest {
|
||||
name = "atop-everthing";
|
||||
nodes.machine = {
|
||||
programs.atop = {
|
||||
enable = true;
|
||||
settings = {
|
||||
flags = "faf1";
|
||||
interval = 2;
|
||||
};
|
||||
setuidWrapper.enable = true;
|
||||
netatop.enable = true;
|
||||
atopgpu.enable = true;
|
||||
};
|
||||
};
|
||||
testScript = with assertions; builtins.concatStringsSep "\n" [
|
||||
version
|
||||
(atoprc "flags faf1\\ninterval 2\\n")
|
||||
(wrapper true)
|
||||
(atopService true)
|
||||
(atopRotateTimer true)
|
||||
(atopacctService true)
|
||||
(netatop true)
|
||||
(atopgpu true)
|
||||
];
|
||||
};
|
||||
}
|
||||
79
nixos/tests/avahi.nix
Normal file
79
nixos/tests/avahi.nix
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
{ system ? builtins.currentSystem
|
||||
, config ? {}
|
||||
, pkgs ? import ../.. { inherit system config; }
|
||||
# bool: whether to use networkd in the tests
|
||||
, networkd ? false
|
||||
} @ args:
|
||||
|
||||
# Test whether `avahi-daemon' and `libnss-mdns' work as expected.
|
||||
import ./make-test-python.nix {
|
||||
name = "avahi";
|
||||
meta = with pkgs.lib.maintainers; {
|
||||
maintainers = [ eelco ];
|
||||
};
|
||||
|
||||
nodes = let
|
||||
cfg = { ... }: {
|
||||
services.avahi = {
|
||||
enable = true;
|
||||
nssmdns = true;
|
||||
publish.addresses = true;
|
||||
publish.domain = true;
|
||||
publish.enable = true;
|
||||
publish.userServices = true;
|
||||
publish.workstation = true;
|
||||
extraServiceFiles.ssh = "${pkgs.avahi}/etc/avahi/services/ssh.service";
|
||||
};
|
||||
} // pkgs.lib.optionalAttrs (networkd) {
|
||||
networking = {
|
||||
useNetworkd = true;
|
||||
useDHCP = false;
|
||||
};
|
||||
};
|
||||
in {
|
||||
one = cfg;
|
||||
two = cfg;
|
||||
};
|
||||
|
||||
testScript = ''
|
||||
start_all()
|
||||
|
||||
# mDNS.
|
||||
one.wait_for_unit("network.target")
|
||||
two.wait_for_unit("network.target")
|
||||
|
||||
one.succeed("avahi-resolve-host-name one.local | tee out >&2")
|
||||
one.succeed('test "`cut -f1 < out`" = one.local')
|
||||
one.succeed("avahi-resolve-host-name two.local | tee out >&2")
|
||||
one.succeed('test "`cut -f1 < out`" = two.local')
|
||||
|
||||
two.succeed("avahi-resolve-host-name one.local | tee out >&2")
|
||||
two.succeed('test "`cut -f1 < out`" = one.local')
|
||||
two.succeed("avahi-resolve-host-name two.local | tee out >&2")
|
||||
two.succeed('test "`cut -f1 < out`" = two.local')
|
||||
|
||||
# Basic DNS-SD.
|
||||
one.succeed("avahi-browse -r -t _workstation._tcp | tee out >&2")
|
||||
one.succeed("test `wc -l < out` -gt 0")
|
||||
two.succeed("avahi-browse -r -t _workstation._tcp | tee out >&2")
|
||||
two.succeed("test `wc -l < out` -gt 0")
|
||||
|
||||
# More DNS-SD.
|
||||
one.execute('avahi-publish -s "This is a test" _test._tcp 123 one=1 >&2 &')
|
||||
one.sleep(5)
|
||||
two.succeed("avahi-browse -r -t _test._tcp | tee out >&2")
|
||||
two.succeed("test `wc -l < out` -gt 0")
|
||||
|
||||
# NSS-mDNS.
|
||||
one.succeed("getent hosts one.local >&2")
|
||||
one.succeed("getent hosts two.local >&2")
|
||||
two.succeed("getent hosts one.local >&2")
|
||||
two.succeed("getent hosts two.local >&2")
|
||||
|
||||
# extra service definitions
|
||||
one.succeed("avahi-browse -r -t _ssh._tcp | tee out >&2")
|
||||
one.succeed("test `wc -l < out` -gt 0")
|
||||
two.succeed("avahi-browse -r -t _ssh._tcp | tee out >&2")
|
||||
two.succeed("test `wc -l < out` -gt 0")
|
||||
'';
|
||||
} args
|
||||
142
nixos/tests/babeld.nix
Normal file
142
nixos/tests/babeld.nix
Normal file
|
|
@ -0,0 +1,142 @@
|
|||
|
||||
import ./make-test-python.nix ({ pkgs, lib, ...} : {
|
||||
name = "babeld";
|
||||
meta = with pkgs.lib.maintainers; {
|
||||
maintainers = [ hexa ];
|
||||
};
|
||||
|
||||
nodes =
|
||||
{ client = { pkgs, lib, ... }:
|
||||
{
|
||||
virtualisation.vlans = [ 10 ];
|
||||
|
||||
networking = {
|
||||
useDHCP = false;
|
||||
interfaces."eth1" = {
|
||||
ipv4.addresses = lib.mkForce [ { address = "192.168.10.2"; prefixLength = 24; } ];
|
||||
ipv4.routes = lib.mkForce [ { address = "0.0.0.0"; prefixLength = 0; via = "192.168.10.1"; } ];
|
||||
ipv6.addresses = lib.mkForce [ { address = "2001:db8:10::2"; prefixLength = 64; } ];
|
||||
ipv6.routes = lib.mkForce [ { address = "::"; prefixLength = 0; via = "2001:db8:10::1"; } ];
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
local_router = { pkgs, lib, ... }:
|
||||
{
|
||||
virtualisation.vlans = [ 10 20 ];
|
||||
|
||||
networking = {
|
||||
useDHCP = false;
|
||||
firewall.enable = false;
|
||||
|
||||
interfaces."eth1" = {
|
||||
ipv4.addresses = lib.mkForce [ { address = "192.168.10.1"; prefixLength = 24; } ];
|
||||
ipv6.addresses = lib.mkForce [ { address = "2001:db8:10::1"; prefixLength = 64; } ];
|
||||
};
|
||||
|
||||
interfaces."eth2" = {
|
||||
ipv4.addresses = lib.mkForce [ { address = "192.168.20.1"; prefixLength = 24; } ];
|
||||
ipv6.addresses = lib.mkForce [ { address = "2001:db8:20::1"; prefixLength = 64; } ];
|
||||
};
|
||||
};
|
||||
|
||||
services.babeld = {
|
||||
enable = true;
|
||||
interfaces.eth2 = {
|
||||
hello-interval = 1;
|
||||
type = "wired";
|
||||
};
|
||||
extraConfig = ''
|
||||
local-port-readwrite 33123
|
||||
|
||||
import-table 254 # main
|
||||
export-table 254 # main
|
||||
|
||||
in ip 192.168.10.0/24 deny
|
||||
in ip 192.168.20.0/24 deny
|
||||
in ip 2001:db8:10::/64 deny
|
||||
in ip 2001:db8:20::/64 deny
|
||||
|
||||
in ip 192.168.30.0/24 allow
|
||||
in ip 2001:db8:30::/64 allow
|
||||
|
||||
in deny
|
||||
|
||||
redistribute local proto 2
|
||||
redistribute local deny
|
||||
'';
|
||||
};
|
||||
};
|
||||
remote_router = { pkgs, lib, ... }:
|
||||
{
|
||||
virtualisation.vlans = [ 20 30 ];
|
||||
|
||||
networking = {
|
||||
useDHCP = false;
|
||||
firewall.enable = false;
|
||||
|
||||
interfaces."eth1" = {
|
||||
ipv4.addresses = lib.mkForce [ { address = "192.168.20.2"; prefixLength = 24; } ];
|
||||
ipv6.addresses = lib.mkForce [ { address = "2001:db8:20::2"; prefixLength = 64; } ];
|
||||
};
|
||||
|
||||
interfaces."eth2" = {
|
||||
ipv4.addresses = lib.mkForce [ { address = "192.168.30.1"; prefixLength = 24; } ];
|
||||
ipv6.addresses = lib.mkForce [ { address = "2001:db8:30::1"; prefixLength = 64; } ];
|
||||
};
|
||||
};
|
||||
|
||||
services.babeld = {
|
||||
enable = true;
|
||||
interfaces.eth1 = {
|
||||
hello-interval = 1;
|
||||
type = "wired";
|
||||
};
|
||||
extraConfig = ''
|
||||
local-port-readwrite 33123
|
||||
|
||||
import-table 254 # main
|
||||
export-table 254 # main
|
||||
|
||||
in ip 192.168.20.0/24 deny
|
||||
in ip 192.168.30.0/24 deny
|
||||
in ip 2001:db8:20::/64 deny
|
||||
in ip 2001:db8:30::/64 deny
|
||||
|
||||
in ip 192.168.10.0/24 allow
|
||||
in ip 2001:db8:10::/64 allow
|
||||
|
||||
in deny
|
||||
|
||||
redistribute local proto 2
|
||||
redistribute local deny
|
||||
'';
|
||||
};
|
||||
|
||||
};
|
||||
};
|
||||
|
||||
testScript =
|
||||
''
|
||||
start_all()
|
||||
|
||||
client.wait_for_unit("network-online.target")
|
||||
local_router.wait_for_unit("network-online.target")
|
||||
remote_router.wait_for_unit("network-online.target")
|
||||
|
||||
local_router.wait_for_unit("babeld.service")
|
||||
remote_router.wait_for_unit("babeld.service")
|
||||
|
||||
local_router.wait_until_succeeds("ip route get 192.168.30.1")
|
||||
local_router.wait_until_succeeds("ip route get 2001:db8:30::1")
|
||||
|
||||
remote_router.wait_until_succeeds("ip route get 192.168.10.1")
|
||||
remote_router.wait_until_succeeds("ip route get 2001:db8:10::1")
|
||||
|
||||
client.succeed("ping -c1 192.168.30.1")
|
||||
client.succeed("ping -c1 2001:db8:30::1")
|
||||
|
||||
remote_router.succeed("ping -c1 192.168.10.2")
|
||||
remote_router.succeed("ping -c1 2001:db8:10::2")
|
||||
'';
|
||||
})
|
||||
26
nixos/tests/bazarr.nix
Normal file
26
nixos/tests/bazarr.nix
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
import ./make-test-python.nix ({ lib, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
port = 42069;
|
||||
in
|
||||
{
|
||||
name = "bazarr";
|
||||
meta.maintainers = with maintainers; [ d-xo ];
|
||||
|
||||
nodes.machine =
|
||||
{ pkgs, ... }:
|
||||
{
|
||||
services.bazarr = {
|
||||
enable = true;
|
||||
listenPort = port;
|
||||
};
|
||||
};
|
||||
|
||||
testScript = ''
|
||||
machine.wait_for_unit("bazarr.service")
|
||||
machine.wait_for_open_port("${toString port}")
|
||||
machine.succeed("curl --fail http://localhost:${toString port}/")
|
||||
'';
|
||||
})
|
||||
33
nixos/tests/bcachefs.nix
Normal file
33
nixos/tests/bcachefs.nix
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
import ./make-test-python.nix ({ pkgs, ... }: {
|
||||
name = "bcachefs";
|
||||
meta.maintainers = with pkgs.lib.maintainers; [ Madouura ];
|
||||
|
||||
nodes.machine = { pkgs, ... }: {
|
||||
virtualisation.emptyDiskImages = [ 4096 ];
|
||||
networking.hostId = "deadbeef";
|
||||
boot.supportedFilesystems = [ "bcachefs" ];
|
||||
environment.systemPackages = with pkgs; [ parted keyutils ];
|
||||
};
|
||||
|
||||
testScript = ''
|
||||
machine.succeed("modprobe bcachefs")
|
||||
machine.succeed("bcachefs version")
|
||||
machine.succeed("ls /dev")
|
||||
|
||||
machine.succeed(
|
||||
"mkdir /tmp/mnt",
|
||||
"udevadm settle",
|
||||
"parted --script /dev/vdb mklabel msdos",
|
||||
"parted --script /dev/vdb -- mkpart primary 1024M 50% mkpart primary 50% -1s",
|
||||
"udevadm settle",
|
||||
"keyctl link @u @s",
|
||||
"echo password | bcachefs format --encrypted --metadata_replicas 2 --label vtest /dev/vdb1 /dev/vdb2",
|
||||
"echo password | bcachefs unlock /dev/vdb1",
|
||||
"mount -t bcachefs /dev/vdb1:/dev/vdb2 /tmp/mnt",
|
||||
"udevadm settle",
|
||||
"bcachefs fs usage /tmp/mnt",
|
||||
"umount /tmp/mnt",
|
||||
"udevadm settle",
|
||||
)
|
||||
'';
|
||||
})
|
||||
49
nixos/tests/beanstalkd.nix
Normal file
49
nixos/tests/beanstalkd.nix
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
import ./make-test-python.nix ({ pkgs, lib, ... }:
|
||||
|
||||
let
|
||||
pythonEnv = pkgs.python3.withPackages (p: [p.beanstalkc]);
|
||||
|
||||
produce = pkgs.writeScript "produce.py" ''
|
||||
#!${pythonEnv.interpreter}
|
||||
import beanstalkc
|
||||
|
||||
queue = beanstalkc.Connection(host='localhost', port=11300, parse_yaml=False);
|
||||
queue.put(b'this is a job')
|
||||
queue.put(b'this is another job')
|
||||
'';
|
||||
|
||||
consume = pkgs.writeScript "consume.py" ''
|
||||
#!${pythonEnv.interpreter}
|
||||
import beanstalkc
|
||||
|
||||
queue = beanstalkc.Connection(host='localhost', port=11300, parse_yaml=False);
|
||||
|
||||
job = queue.reserve(timeout=0)
|
||||
print(job.body.decode('utf-8'))
|
||||
job.delete()
|
||||
'';
|
||||
|
||||
in
|
||||
{
|
||||
name = "beanstalkd";
|
||||
meta.maintainers = [ lib.maintainers.aanderse ];
|
||||
|
||||
nodes.machine =
|
||||
{ ... }:
|
||||
{ services.beanstalkd.enable = true;
|
||||
};
|
||||
|
||||
testScript = ''
|
||||
start_all()
|
||||
|
||||
machine.wait_for_unit("beanstalkd.service")
|
||||
|
||||
machine.succeed("${produce}")
|
||||
assert "this is a job\n" == machine.succeed(
|
||||
"${consume}"
|
||||
)
|
||||
assert "this is another job\n" == machine.succeed(
|
||||
"${consume}"
|
||||
)
|
||||
'';
|
||||
})
|
||||
62
nixos/tests/bees.nix
Normal file
62
nixos/tests/bees.nix
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
import ./make-test-python.nix ({ lib, pkgs, ... }:
|
||||
{
|
||||
name = "bees";
|
||||
|
||||
nodes.machine = { config, pkgs, ... }: {
|
||||
boot.initrd.postDeviceCommands = ''
|
||||
${pkgs.btrfs-progs}/bin/mkfs.btrfs -f -L aux1 /dev/vdb
|
||||
${pkgs.btrfs-progs}/bin/mkfs.btrfs -f -L aux2 /dev/vdc
|
||||
'';
|
||||
virtualisation.emptyDiskImages = [ 4096 4096 ];
|
||||
virtualisation.fileSystems = {
|
||||
"/aux1" = { # filesystem configured to be deduplicated
|
||||
device = "/dev/disk/by-label/aux1";
|
||||
fsType = "btrfs";
|
||||
};
|
||||
"/aux2" = { # filesystem not configured to be deduplicated
|
||||
device = "/dev/disk/by-label/aux2";
|
||||
fsType = "btrfs";
|
||||
};
|
||||
};
|
||||
services.beesd.filesystems = {
|
||||
aux1 = {
|
||||
spec = "LABEL=aux1";
|
||||
hashTableSizeMB = 16;
|
||||
verbosity = "debug";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
testScript =
|
||||
let
|
||||
someContentIsShared = loc: pkgs.writeShellScript "some-content-is-shared" ''
|
||||
[[ $(btrfs fi du -s --raw ${lib.escapeShellArg loc}/dedup-me-{1,2} | awk 'BEGIN { count=0; } NR>1 && $3 == 0 { count++ } END { print count }') -eq 0 ]]
|
||||
'';
|
||||
in ''
|
||||
# shut down the instance started by systemd at boot, so we can test our test procedure
|
||||
machine.succeed("systemctl stop beesd@aux1.service")
|
||||
|
||||
machine.succeed(
|
||||
"dd if=/dev/urandom of=/aux1/dedup-me-1 bs=1M count=8",
|
||||
"cp --reflink=never /aux1/dedup-me-1 /aux1/dedup-me-2",
|
||||
"cp --reflink=never /aux1/* /aux2/",
|
||||
"sync",
|
||||
)
|
||||
machine.fail(
|
||||
"${someContentIsShared "/aux1"}",
|
||||
"${someContentIsShared "/aux2"}",
|
||||
)
|
||||
machine.succeed("systemctl start beesd@aux1.service")
|
||||
|
||||
# assert that "Set Shared" column is nonzero
|
||||
machine.wait_until_succeeds(
|
||||
"${someContentIsShared "/aux1"}",
|
||||
)
|
||||
machine.fail("${someContentIsShared "/aux2"}")
|
||||
|
||||
# assert that 16MB hash table size requested was honored
|
||||
machine.succeed(
|
||||
"[[ $(stat -c %s /aux1/.beeshome/beeshash.dat) = $(( 16 * 1024 * 1024)) ]]"
|
||||
)
|
||||
'';
|
||||
})
|
||||
28
nixos/tests/bind.nix
Normal file
28
nixos/tests/bind.nix
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
import ./make-test-python.nix {
|
||||
name = "bind";
|
||||
|
||||
nodes.machine = { pkgs, lib, ... }: {
|
||||
services.bind.enable = true;
|
||||
services.bind.extraOptions = "empty-zones-enable no;";
|
||||
services.bind.zones = lib.singleton {
|
||||
name = ".";
|
||||
master = true;
|
||||
file = pkgs.writeText "root.zone" ''
|
||||
$TTL 3600
|
||||
. IN SOA ns.example.org. admin.example.org. ( 1 3h 1h 1w 1d )
|
||||
. IN NS ns.example.org.
|
||||
|
||||
ns.example.org. IN A 192.168.0.1
|
||||
ns.example.org. IN AAAA abcd::1
|
||||
|
||||
1.0.168.192.in-addr.arpa IN PTR ns.example.org.
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
testScript = ''
|
||||
machine.wait_for_unit("bind.service")
|
||||
machine.wait_for_open_port(53)
|
||||
machine.succeed("host 192.168.0.1 127.0.0.1 | grep -qF ns.example.org")
|
||||
'';
|
||||
}
|
||||
129
nixos/tests/bird.nix
Normal file
129
nixos/tests/bird.nix
Normal file
|
|
@ -0,0 +1,129 @@
|
|||
# This test does a basic functionality check for all bird variants and demonstrates a use
|
||||
# of the preCheckConfig option.
|
||||
|
||||
{ system ? builtins.currentSystem
|
||||
, pkgs ? import ../.. { inherit system; config = { }; }
|
||||
}:
|
||||
|
||||
let
|
||||
inherit (import ../lib/testing-python.nix { inherit system pkgs; }) makeTest;
|
||||
inherit (pkgs.lib) optionalString;
|
||||
|
||||
makeBird2Host = hostId: { pkgs, ... }: {
|
||||
virtualisation.vlans = [ 1 ];
|
||||
|
||||
environment.systemPackages = with pkgs; [ jq ];
|
||||
|
||||
networking = {
|
||||
useNetworkd = true;
|
||||
useDHCP = false;
|
||||
firewall.enable = false;
|
||||
};
|
||||
|
||||
systemd.network.networks."01-eth1" = {
|
||||
name = "eth1";
|
||||
networkConfig.Address = "10.0.0.${hostId}/24";
|
||||
};
|
||||
|
||||
services.bird2 = {
|
||||
enable = true;
|
||||
|
||||
config = ''
|
||||
log syslog all;
|
||||
|
||||
debug protocols all;
|
||||
|
||||
router id 10.0.0.${hostId};
|
||||
|
||||
protocol device {
|
||||
}
|
||||
|
||||
protocol kernel kernel4 {
|
||||
ipv4 {
|
||||
import none;
|
||||
export all;
|
||||
};
|
||||
}
|
||||
|
||||
protocol static static4 {
|
||||
ipv4;
|
||||
include "static4.conf";
|
||||
}
|
||||
|
||||
protocol ospf v2 ospf4 {
|
||||
ipv4 {
|
||||
export all;
|
||||
};
|
||||
area 0 {
|
||||
interface "eth1" {
|
||||
hello 5;
|
||||
wait 5;
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
protocol kernel kernel6 {
|
||||
ipv6 {
|
||||
import none;
|
||||
export all;
|
||||
};
|
||||
}
|
||||
|
||||
protocol static static6 {
|
||||
ipv6;
|
||||
include "static6.conf";
|
||||
}
|
||||
|
||||
protocol ospf v3 ospf6 {
|
||||
ipv6 {
|
||||
export all;
|
||||
};
|
||||
area 0 {
|
||||
interface "eth1" {
|
||||
hello 5;
|
||||
wait 5;
|
||||
};
|
||||
};
|
||||
}
|
||||
'';
|
||||
|
||||
preCheckConfig = ''
|
||||
echo "route 1.2.3.4/32 blackhole;" > static4.conf
|
||||
echo "route fd00::/128 blackhole;" > static6.conf
|
||||
'';
|
||||
};
|
||||
|
||||
systemd.tmpfiles.rules = [
|
||||
"f /etc/bird/static4.conf - - - - route 10.10.0.${hostId}/32 blackhole;"
|
||||
"f /etc/bird/static6.conf - - - - route fdff::${hostId}/128 blackhole;"
|
||||
];
|
||||
};
|
||||
in
|
||||
makeTest {
|
||||
name = "bird2";
|
||||
|
||||
nodes.host1 = makeBird2Host "1";
|
||||
nodes.host2 = makeBird2Host "2";
|
||||
|
||||
testScript = ''
|
||||
start_all()
|
||||
|
||||
host1.wait_for_unit("bird2.service")
|
||||
host2.wait_for_unit("bird2.service")
|
||||
host1.succeed("systemctl reload bird2.service")
|
||||
|
||||
with subtest("Waiting for advertised IPv4 routes"):
|
||||
host1.wait_until_succeeds("ip --json r | jq -e 'map(select(.dst == \"10.10.0.2\")) | any'")
|
||||
host2.wait_until_succeeds("ip --json r | jq -e 'map(select(.dst == \"10.10.0.1\")) | any'")
|
||||
with subtest("Waiting for advertised IPv6 routes"):
|
||||
host1.wait_until_succeeds("ip --json -6 r | jq -e 'map(select(.dst == \"fdff::2\")) | any'")
|
||||
host2.wait_until_succeeds("ip --json -6 r | jq -e 'map(select(.dst == \"fdff::1\")) | any'")
|
||||
|
||||
with subtest("Check fake routes in preCheckConfig do not exists"):
|
||||
host1.fail("ip --json r | jq -e 'map(select(.dst == \"1.2.3.4\")) | any'")
|
||||
host2.fail("ip --json r | jq -e 'map(select(.dst == \"1.2.3.4\")) | any'")
|
||||
|
||||
host1.fail("ip --json -6 r | jq -e 'map(select(.dst == \"fd00::\")) | any'")
|
||||
host2.fail("ip --json -6 r | jq -e 'map(select(.dst == \"fd00::\")) | any'")
|
||||
'';
|
||||
}
|
||||
46
nixos/tests/bitcoind.nix
Normal file
46
nixos/tests/bitcoind.nix
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
import ./make-test-python.nix ({ pkgs, ... }: {
|
||||
name = "bitcoind";
|
||||
meta = with pkgs.lib; {
|
||||
maintainers = with maintainers; [ _1000101 ];
|
||||
};
|
||||
|
||||
nodes.machine = { ... }: {
|
||||
services.bitcoind."mainnet" = {
|
||||
enable = true;
|
||||
rpc = {
|
||||
port = 8332;
|
||||
users.rpc.passwordHMAC = "acc2374e5f9ba9e62a5204d3686616cf$53abdba5e67a9005be6a27ca03a93ce09e58854bc2b871523a0d239a72968033";
|
||||
users.rpc2.passwordHMAC = "1495e4a3ad108187576c68f7f9b5ddc5$accce0881c74aa01bb8960ff3bdbd39f607fd33178147679e055a4ac35f53225";
|
||||
};
|
||||
};
|
||||
services.bitcoind."testnet" = {
|
||||
enable = true;
|
||||
configFile = "/test.blank";
|
||||
testnet = true;
|
||||
rpc = {
|
||||
port = 18332;
|
||||
};
|
||||
extraCmdlineOptions = [ "-rpcuser=rpc" "-rpcpassword=rpc" "-rpcauth=rpc2:1495e4a3ad108187576c68f7f9b5ddc5$accce0881c74aa01bb8960ff3bdbd39f607fd33178147679e055a4ac35f53225" ];
|
||||
};
|
||||
};
|
||||
|
||||
testScript = ''
|
||||
start_all()
|
||||
|
||||
machine.wait_for_unit("bitcoind-mainnet.service")
|
||||
machine.wait_for_unit("bitcoind-testnet.service")
|
||||
|
||||
machine.wait_until_succeeds(
|
||||
'curl --fail --user rpc:rpc --data-binary \'{"jsonrpc": "1.0", "id":"curltest", "method": "getblockchaininfo", "params": [] }\' -H \'content-type: text/plain;\' localhost:8332 | grep \'"chain":"main"\' '
|
||||
)
|
||||
machine.wait_until_succeeds(
|
||||
'curl --fail --user rpc2:rpc2 --data-binary \'{"jsonrpc": "1.0", "id":"curltest", "method": "getblockchaininfo", "params": [] }\' -H \'content-type: text/plain;\' localhost:8332 | grep \'"chain":"main"\' '
|
||||
)
|
||||
machine.wait_until_succeeds(
|
||||
'curl --fail --user rpc:rpc --data-binary \'{"jsonrpc": "1.0", "id":"curltest", "method": "getblockchaininfo", "params": [] }\' -H \'content-type: text/plain;\' localhost:18332 | grep \'"chain":"test"\' '
|
||||
)
|
||||
machine.wait_until_succeeds(
|
||||
'curl --fail --user rpc2:rpc2 --data-binary \'{"jsonrpc": "1.0", "id":"curltest", "method": "getblockchaininfo", "params": [] }\' -H \'content-type: text/plain;\' localhost:18332 | grep \'"chain":"test"\' '
|
||||
)
|
||||
'';
|
||||
})
|
||||
164
nixos/tests/bittorrent.nix
Normal file
164
nixos/tests/bittorrent.nix
Normal file
|
|
@ -0,0 +1,164 @@
|
|||
# This test runs a Bittorrent tracker on one machine, and verifies
|
||||
# that two client machines can download the torrent using
|
||||
# `transmission'. The first client (behind a NAT router) downloads
|
||||
# from the initial seeder running on the tracker. Then we kill the
|
||||
# initial seeder. The second client downloads from the first client,
|
||||
# which only works if the first client successfully uses the UPnP-IGD
|
||||
# protocol to poke a hole in the NAT.
|
||||
|
||||
import ./make-test-python.nix ({ pkgs, ... }:
|
||||
|
||||
let
|
||||
|
||||
# Some random file to serve.
|
||||
file = pkgs.hello.src;
|
||||
|
||||
internalRouterAddress = "192.168.3.1";
|
||||
internalClient1Address = "192.168.3.2";
|
||||
externalRouterAddress = "80.100.100.1";
|
||||
externalClient2Address = "80.100.100.2";
|
||||
externalTrackerAddress = "80.100.100.3";
|
||||
|
||||
download-dir = "/var/lib/transmission/Downloads";
|
||||
transmissionConfig = { ... }: {
|
||||
environment.systemPackages = [ pkgs.transmission ];
|
||||
services.transmission = {
|
||||
enable = true;
|
||||
settings = {
|
||||
dht-enabled = false;
|
||||
message-level = 2;
|
||||
inherit download-dir;
|
||||
};
|
||||
};
|
||||
};
|
||||
in
|
||||
|
||||
{
|
||||
name = "bittorrent";
|
||||
meta = with pkgs.lib.maintainers; {
|
||||
maintainers = [ domenkozar eelco rob bobvanderlinden ];
|
||||
};
|
||||
|
||||
nodes = {
|
||||
tracker = { pkgs, ... }: {
|
||||
imports = [ transmissionConfig ];
|
||||
|
||||
virtualisation.vlans = [ 1 ];
|
||||
networking.firewall.enable = false;
|
||||
networking.interfaces.eth1.ipv4.addresses = [
|
||||
{ address = externalTrackerAddress; prefixLength = 24; }
|
||||
];
|
||||
|
||||
# We need Apache on the tracker to serve the torrents.
|
||||
services.httpd = {
|
||||
enable = true;
|
||||
virtualHosts = {
|
||||
"torrentserver.org" = {
|
||||
adminAddr = "foo@example.org";
|
||||
documentRoot = "/tmp";
|
||||
};
|
||||
};
|
||||
};
|
||||
services.opentracker.enable = true;
|
||||
};
|
||||
|
||||
router = { pkgs, nodes, ... }: {
|
||||
virtualisation.vlans = [ 1 2 ];
|
||||
networking.nat.enable = true;
|
||||
networking.nat.internalInterfaces = [ "eth2" ];
|
||||
networking.nat.externalInterface = "eth1";
|
||||
networking.firewall.enable = true;
|
||||
networking.firewall.trustedInterfaces = [ "eth2" ];
|
||||
networking.interfaces.eth0.ipv4.addresses = [];
|
||||
networking.interfaces.eth1.ipv4.addresses = [
|
||||
{ address = externalRouterAddress; prefixLength = 24; }
|
||||
];
|
||||
networking.interfaces.eth2.ipv4.addresses = [
|
||||
{ address = internalRouterAddress; prefixLength = 24; }
|
||||
];
|
||||
services.miniupnpd = {
|
||||
enable = true;
|
||||
externalInterface = "eth1";
|
||||
internalIPs = [ "eth2" ];
|
||||
appendConfig = ''
|
||||
ext_ip=${externalRouterAddress}
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
client1 = { pkgs, nodes, ... }: {
|
||||
imports = [ transmissionConfig ];
|
||||
environment.systemPackages = [ pkgs.miniupnpc ];
|
||||
|
||||
virtualisation.vlans = [ 2 ];
|
||||
networking.interfaces.eth0.ipv4.addresses = [];
|
||||
networking.interfaces.eth1.ipv4.addresses = [
|
||||
{ address = internalClient1Address; prefixLength = 24; }
|
||||
];
|
||||
networking.defaultGateway = internalRouterAddress;
|
||||
networking.firewall.enable = false;
|
||||
};
|
||||
|
||||
client2 = { pkgs, ... }: {
|
||||
imports = [ transmissionConfig ];
|
||||
|
||||
virtualisation.vlans = [ 1 ];
|
||||
networking.interfaces.eth0.ipv4.addresses = [];
|
||||
networking.interfaces.eth1.ipv4.addresses = [
|
||||
{ address = externalClient2Address; prefixLength = 24; }
|
||||
];
|
||||
networking.firewall.enable = false;
|
||||
};
|
||||
};
|
||||
|
||||
testScript = { nodes, ... }: ''
|
||||
start_all()
|
||||
|
||||
# Wait for network and miniupnpd.
|
||||
router.wait_for_unit("network-online.target")
|
||||
router.wait_for_unit("miniupnpd")
|
||||
|
||||
# Create the torrent.
|
||||
tracker.succeed("mkdir ${download-dir}/data")
|
||||
tracker.succeed(
|
||||
"cp ${file} ${download-dir}/data/test.tar.bz2"
|
||||
)
|
||||
tracker.succeed(
|
||||
"transmission-create ${download-dir}/data/test.tar.bz2 --private --tracker http://${externalTrackerAddress}:6969/announce --outfile /tmp/test.torrent"
|
||||
)
|
||||
tracker.succeed("chmod 644 /tmp/test.torrent")
|
||||
|
||||
# Start the tracker. !!! use a less crappy tracker
|
||||
tracker.wait_for_unit("network-online.target")
|
||||
tracker.wait_for_unit("opentracker.service")
|
||||
tracker.wait_for_open_port(6969)
|
||||
|
||||
# Start the initial seeder.
|
||||
tracker.succeed(
|
||||
"transmission-remote --add /tmp/test.torrent --no-portmap --no-dht --download-dir ${download-dir}/data"
|
||||
)
|
||||
|
||||
# Now we should be able to download from the client behind the NAT.
|
||||
tracker.wait_for_unit("httpd")
|
||||
client1.wait_for_unit("network-online.target")
|
||||
client1.succeed("transmission-remote --add http://${externalTrackerAddress}/test.torrent >&2 &")
|
||||
client1.wait_for_file("${download-dir}/test.tar.bz2")
|
||||
client1.succeed(
|
||||
"cmp ${download-dir}/test.tar.bz2 ${file}"
|
||||
)
|
||||
|
||||
# Bring down the initial seeder.
|
||||
# tracker.stop_job("transmission")
|
||||
|
||||
# Now download from the second client. This can only succeed if
|
||||
# the first client created a NAT hole in the router.
|
||||
client2.wait_for_unit("network-online.target")
|
||||
client2.succeed(
|
||||
"transmission-remote --add http://${externalTrackerAddress}/test.torrent --no-portmap --no-dht >&2 &"
|
||||
)
|
||||
client2.wait_for_file("${download-dir}/test.tar.bz2")
|
||||
client2.succeed(
|
||||
"cmp ${download-dir}/test.tar.bz2 ${file}"
|
||||
)
|
||||
'';
|
||||
})
|
||||
28
nixos/tests/blockbook-frontend.nix
Normal file
28
nixos/tests/blockbook-frontend.nix
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
import ./make-test-python.nix ({ pkgs, ... }: {
|
||||
name = "blockbook-frontend";
|
||||
meta = with pkgs.lib; {
|
||||
maintainers = with maintainers; [ _1000101 ];
|
||||
};
|
||||
|
||||
nodes.machine = { ... }: {
|
||||
services.blockbook-frontend."test" = {
|
||||
enable = true;
|
||||
};
|
||||
services.bitcoind.mainnet = {
|
||||
enable = true;
|
||||
rpc = {
|
||||
port = 8030;
|
||||
users.rpc.passwordHMAC = "acc2374e5f9ba9e62a5204d3686616cf$53abdba5e67a9005be6a27ca03a93ce09e58854bc2b871523a0d239a72968033";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
testScript = ''
|
||||
start_all()
|
||||
machine.wait_for_unit("blockbook-frontend-test.service")
|
||||
|
||||
machine.wait_for_open_port(9030)
|
||||
|
||||
machine.succeed("curl -sSfL http://localhost:9030 | grep 'Blockbook'")
|
||||
'';
|
||||
})
|
||||
34
nixos/tests/blocky.nix
Normal file
34
nixos/tests/blocky.nix
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
import ./make-test-python.nix {
|
||||
name = "blocky";
|
||||
|
||||
nodes = {
|
||||
server = { pkgs, ... }: {
|
||||
environment.systemPackages = [ pkgs.dnsutils ];
|
||||
services.blocky = {
|
||||
enable = true;
|
||||
|
||||
settings = {
|
||||
customDNS = {
|
||||
mapping = {
|
||||
"printer.lan" = "192.168.178.3,2001:0db8:85a3:08d3:1319:8a2e:0370:7344";
|
||||
};
|
||||
};
|
||||
upstream = {
|
||||
default = [ "8.8.8.8" "1.1.1.1" ];
|
||||
};
|
||||
port = 53;
|
||||
httpPort = 5000;
|
||||
logLevel = "info";
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
testScript = ''
|
||||
with subtest("Service test"):
|
||||
server.wait_for_unit("blocky.service")
|
||||
server.wait_for_open_port(53)
|
||||
server.wait_for_open_port(5000)
|
||||
server.succeed("dig @127.0.0.1 +short -x 192.168.178.3 | grep -qF printer.lan")
|
||||
'';
|
||||
}
|
||||
164
nixos/tests/boot-stage1.nix
Normal file
164
nixos/tests/boot-stage1.nix
Normal file
|
|
@ -0,0 +1,164 @@
|
|||
import ./make-test-python.nix ({ pkgs, ... }: {
|
||||
name = "boot-stage1";
|
||||
|
||||
nodes.machine = { config, pkgs, lib, ... }: {
|
||||
boot.extraModulePackages = let
|
||||
compileKernelModule = name: source: pkgs.runCommandCC name rec {
|
||||
inherit source;
|
||||
kdev = config.boot.kernelPackages.kernel.dev;
|
||||
kver = config.boot.kernelPackages.kernel.modDirVersion;
|
||||
ksrc = "${kdev}/lib/modules/${kver}/build";
|
||||
hardeningDisable = [ "pic" ];
|
||||
nativeBuildInputs = kdev.moduleBuildDependencies;
|
||||
} ''
|
||||
echo "obj-m += $name.o" > Makefile
|
||||
echo "$source" > "$name.c"
|
||||
make -C "$ksrc" M=$(pwd) modules
|
||||
install -vD "$name.ko" "$out/lib/modules/$kver/$name.ko"
|
||||
'';
|
||||
|
||||
# This spawns a kthread which just waits until it gets a signal and
|
||||
# terminates if that is the case. We want to make sure that nothing during
|
||||
# the boot process kills any kthread by accident, like what happened in
|
||||
# issue #15226.
|
||||
kcanary = compileKernelModule "kcanary" ''
|
||||
#include <linux/version.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/kthread.h>
|
||||
#include <linux/sched.h>
|
||||
#include <linux/signal.h>
|
||||
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 10, 0)
|
||||
#include <linux/sched/signal.h>
|
||||
#endif
|
||||
|
||||
MODULE_LICENSE("GPL");
|
||||
|
||||
struct task_struct *canaryTask;
|
||||
|
||||
static int kcanary(void *nothing)
|
||||
{
|
||||
allow_signal(SIGINT);
|
||||
allow_signal(SIGTERM);
|
||||
allow_signal(SIGKILL);
|
||||
while (!kthread_should_stop()) {
|
||||
set_current_state(TASK_INTERRUPTIBLE);
|
||||
schedule_timeout_interruptible(msecs_to_jiffies(100));
|
||||
if (signal_pending(current)) break;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int kcanaryInit(void)
|
||||
{
|
||||
kthread_run(&kcanary, NULL, "kcanary");
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void kcanaryExit(void)
|
||||
{
|
||||
kthread_stop(canaryTask);
|
||||
}
|
||||
|
||||
module_init(kcanaryInit);
|
||||
module_exit(kcanaryExit);
|
||||
'';
|
||||
|
||||
in lib.singleton kcanary;
|
||||
|
||||
boot.initrd.kernelModules = [ "kcanary" ];
|
||||
|
||||
boot.initrd.extraUtilsCommands = let
|
||||
compile = name: source: pkgs.runCommandCC name { inherit source; } ''
|
||||
mkdir -p "$out/bin"
|
||||
echo "$source" | gcc -Wall -o "$out/bin/$name" -xc -
|
||||
'';
|
||||
|
||||
daemonize = name: source: compile name ''
|
||||
#include <stdio.h>
|
||||
#include <unistd.h>
|
||||
|
||||
void runSource(void) {
|
||||
${source}
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
if (fork() > 0) return 0;
|
||||
setsid();
|
||||
runSource();
|
||||
return 1;
|
||||
}
|
||||
'';
|
||||
|
||||
mkCmdlineCanary = { name, cmdline ? "", source ? "" }: (daemonize name ''
|
||||
char *argv[] = {"${cmdline}", NULL};
|
||||
execvp("${name}-child", argv);
|
||||
'') // {
|
||||
child = compile "${name}-child" ''
|
||||
#include <stdio.h>
|
||||
#include <unistd.h>
|
||||
|
||||
int main(void) {
|
||||
${source}
|
||||
while (1) sleep(1);
|
||||
return 1;
|
||||
}
|
||||
'';
|
||||
};
|
||||
|
||||
copyCanaries = with lib; concatMapStrings (canary: ''
|
||||
${optionalString (canary ? child) ''
|
||||
copy_bin_and_libs "${canary.child}/bin/${canary.child.name}"
|
||||
''}
|
||||
copy_bin_and_libs "${canary}/bin/${canary.name}"
|
||||
'');
|
||||
|
||||
in copyCanaries [
|
||||
# Simple canary process which just sleeps forever and should be killed by
|
||||
# stage 2.
|
||||
(daemonize "canary1" "while (1) sleep(1);")
|
||||
|
||||
# We want this canary process to try mimicking a kthread using a cmdline
|
||||
# with a zero length so we can make sure that the process is properly
|
||||
# killed in stage 1.
|
||||
(mkCmdlineCanary {
|
||||
name = "canary2";
|
||||
source = ''
|
||||
FILE *f;
|
||||
f = fopen("/run/canary2.pid", "w");
|
||||
fprintf(f, "%d\n", getpid());
|
||||
fclose(f);
|
||||
'';
|
||||
})
|
||||
|
||||
# This canary process mimicks a storage daemon, which we do NOT want to be
|
||||
# killed before going into stage 2. For more on root storage daemons, see:
|
||||
# https://www.freedesktop.org/wiki/Software/systemd/RootStorageDaemons/
|
||||
(mkCmdlineCanary {
|
||||
name = "canary3";
|
||||
cmdline = "@canary3";
|
||||
})
|
||||
];
|
||||
|
||||
boot.initrd.postMountCommands = ''
|
||||
canary1
|
||||
canary2
|
||||
canary3
|
||||
# Make sure the pidfile of canary 2 is created so that we still can get
|
||||
# its former pid after the killing spree starts next within stage 1.
|
||||
while [ ! -s /run/canary2.pid ]; do sleep 0.1; done
|
||||
'';
|
||||
};
|
||||
|
||||
testScript = ''
|
||||
machine.wait_for_unit("multi-user.target")
|
||||
machine.succeed("test -s /run/canary2.pid")
|
||||
machine.fail("pgrep -a canary1")
|
||||
machine.fail("kill -0 $(< /run/canary2.pid)")
|
||||
machine.succeed('pgrep -a -f "^@canary3$"')
|
||||
machine.succeed('pgrep -a -f "^kcanary$"')
|
||||
'';
|
||||
|
||||
meta.maintainers = with pkgs.lib.maintainers; [ aszlig ];
|
||||
})
|
||||
148
nixos/tests/boot.nix
Normal file
148
nixos/tests/boot.nix
Normal file
|
|
@ -0,0 +1,148 @@
|
|||
{ system ? builtins.currentSystem,
|
||||
config ? {},
|
||||
pkgs ? import ../.. { inherit system config; }
|
||||
}:
|
||||
|
||||
with import ../lib/testing-python.nix { inherit system pkgs; };
|
||||
with pkgs.lib;
|
||||
|
||||
let
|
||||
qemu-common = import ../lib/qemu-common.nix { inherit (pkgs) lib pkgs; };
|
||||
|
||||
iso =
|
||||
(import ../lib/eval-config.nix {
|
||||
inherit system;
|
||||
modules = [
|
||||
../modules/installer/cd-dvd/installation-cd-minimal.nix
|
||||
../modules/testing/test-instrumentation.nix
|
||||
];
|
||||
}).config.system.build.isoImage;
|
||||
|
||||
sd =
|
||||
(import ../lib/eval-config.nix {
|
||||
inherit system;
|
||||
modules = [
|
||||
../modules/installer/sd-card/sd-image-x86_64.nix
|
||||
../modules/testing/test-instrumentation.nix
|
||||
{ sdImage.compressImage = false; }
|
||||
];
|
||||
}).config.system.build.sdImage;
|
||||
|
||||
pythonDict = params: "\n {\n ${concatStringsSep ",\n " (mapAttrsToList (name: param: "\"${name}\": \"${param}\"") params)},\n }\n";
|
||||
|
||||
makeBootTest = name: extraConfig:
|
||||
let
|
||||
machineConfig = pythonDict ({
|
||||
qemuBinary = qemu-common.qemuBinary pkgs.qemu_test;
|
||||
qemuFlags = "-m 768";
|
||||
} // extraConfig);
|
||||
in
|
||||
makeTest {
|
||||
name = "boot-" + name;
|
||||
nodes = { };
|
||||
testScript =
|
||||
''
|
||||
machine = create_machine(${machineConfig})
|
||||
machine.start()
|
||||
machine.wait_for_unit("multi-user.target")
|
||||
machine.succeed("nix store verify --no-trust -r --option experimental-features nix-command /run/current-system")
|
||||
|
||||
with subtest("Check whether the channel got installed correctly"):
|
||||
machine.succeed("nix-instantiate --dry-run '<nixpkgs>' -A hello")
|
||||
machine.succeed("nix-env --dry-run -iA nixos.procps")
|
||||
|
||||
machine.shutdown()
|
||||
'';
|
||||
};
|
||||
|
||||
makeNetbootTest = name: extraConfig:
|
||||
let
|
||||
config = (import ../lib/eval-config.nix {
|
||||
inherit system;
|
||||
modules =
|
||||
[ ../modules/installer/netboot/netboot.nix
|
||||
../modules/testing/test-instrumentation.nix
|
||||
{ key = "serial"; }
|
||||
];
|
||||
}).config;
|
||||
ipxeBootDir = pkgs.symlinkJoin {
|
||||
name = "ipxeBootDir";
|
||||
paths = [
|
||||
config.system.build.netbootRamdisk
|
||||
config.system.build.kernel
|
||||
config.system.build.netbootIpxeScript
|
||||
];
|
||||
};
|
||||
machineConfig = pythonDict ({
|
||||
qemuBinary = qemu-common.qemuBinary pkgs.qemu_test;
|
||||
qemuFlags = "-boot order=n -m 2000";
|
||||
netBackendArgs = "tftp=${ipxeBootDir},bootfile=netboot.ipxe";
|
||||
} // extraConfig);
|
||||
in
|
||||
makeTest {
|
||||
name = "boot-netboot-" + name;
|
||||
nodes = { };
|
||||
testScript = ''
|
||||
machine = create_machine(${machineConfig})
|
||||
machine.start()
|
||||
machine.wait_for_unit("multi-user.target")
|
||||
machine.shutdown()
|
||||
'';
|
||||
};
|
||||
uefiBinary = {
|
||||
x86_64-linux = "${pkgs.OVMF.fd}/FV/OVMF.fd";
|
||||
aarch64-linux = "${pkgs.OVMF.fd}/FV/QEMU_EFI.fd";
|
||||
}.${pkgs.stdenv.hostPlatform.system};
|
||||
in {
|
||||
uefiCdrom = makeBootTest "uefi-cdrom" {
|
||||
cdrom = "${iso}/iso/${iso.isoName}";
|
||||
bios = uefiBinary;
|
||||
};
|
||||
|
||||
uefiUsb = makeBootTest "uefi-usb" {
|
||||
usb = "${iso}/iso/${iso.isoName}";
|
||||
bios = uefiBinary;
|
||||
};
|
||||
|
||||
uefiNetboot = makeNetbootTest "uefi" {
|
||||
bios = uefiBinary;
|
||||
# Custom ROM is needed for EFI PXE boot. I failed to understand exactly why, because QEMU should still use iPXE for EFI.
|
||||
netFrontendArgs = "romfile=${pkgs.ipxe}/ipxe.efirom";
|
||||
};
|
||||
} // optionalAttrs (pkgs.stdenv.hostPlatform.system == "x86_64-linux") {
|
||||
biosCdrom = makeBootTest "bios-cdrom" {
|
||||
cdrom = "${iso}/iso/${iso.isoName}";
|
||||
};
|
||||
|
||||
biosUsb = makeBootTest "bios-usb" {
|
||||
usb = "${iso}/iso/${iso.isoName}";
|
||||
};
|
||||
|
||||
biosNetboot = makeNetbootTest "bios" {};
|
||||
|
||||
ubootExtlinux = let
|
||||
sdImage = "${sd}/sd-image/${sd.imageName}";
|
||||
mutableImage = "/tmp/linked-image.qcow2";
|
||||
|
||||
machineConfig = pythonDict {
|
||||
bios = "${pkgs.ubootQemuX86}/u-boot.rom";
|
||||
qemuFlags = "-m 768 -machine type=pc,accel=tcg -drive file=${mutableImage},if=ide,format=qcow2";
|
||||
};
|
||||
in makeTest {
|
||||
name = "boot-uboot-extlinux";
|
||||
nodes = { };
|
||||
testScript = ''
|
||||
import os
|
||||
|
||||
# Create a mutable linked image backed by the read-only SD image
|
||||
if os.system("qemu-img create -f qcow2 -F raw -b ${sdImage} ${mutableImage}") != 0:
|
||||
raise RuntimeError("Could not create mutable linked image")
|
||||
|
||||
machine = create_machine(${machineConfig})
|
||||
machine.start()
|
||||
machine.wait_for_unit("multi-user.target")
|
||||
machine.succeed("nix store verify -r --no-trust --option experimental-features nix-command /run/current-system")
|
||||
machine.shutdown()
|
||||
'';
|
||||
};
|
||||
}
|
||||
208
nixos/tests/borgbackup.nix
Normal file
208
nixos/tests/borgbackup.nix
Normal file
|
|
@ -0,0 +1,208 @@
|
|||
import ./make-test-python.nix ({ pkgs, ... }:
|
||||
|
||||
let
|
||||
passphrase = "supersecret";
|
||||
dataDir = "/ran:dom/data";
|
||||
excludeFile = "not_this_file";
|
||||
keepFile = "important_file";
|
||||
keepFileData = "important_data";
|
||||
localRepo = "/root/back:up";
|
||||
archiveName = "my_archive";
|
||||
remoteRepo = "borg@server:."; # No need to specify path
|
||||
privateKey = pkgs.writeText "id_ed25519" ''
|
||||
-----BEGIN OPENSSH PRIVATE KEY-----
|
||||
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
|
||||
QyNTUxOQAAACBx8UB04Q6Q/fwDFjakHq904PYFzG9pU2TJ9KXpaPMcrwAAAJB+cF5HfnBe
|
||||
RwAAAAtzc2gtZWQyNTUxOQAAACBx8UB04Q6Q/fwDFjakHq904PYFzG9pU2TJ9KXpaPMcrw
|
||||
AAAEBN75NsJZSpt63faCuaD75Unko0JjlSDxMhYHAPJk2/xXHxQHThDpD9/AMWNqQer3Tg
|
||||
9gXMb2lTZMn0pelo8xyvAAAADXJzY2h1ZXR6QGt1cnQ=
|
||||
-----END OPENSSH PRIVATE KEY-----
|
||||
'';
|
||||
publicKey = ''
|
||||
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIHHxQHThDpD9/AMWNqQer3Tg9gXMb2lTZMn0pelo8xyv root@client
|
||||
'';
|
||||
privateKeyAppendOnly = pkgs.writeText "id_ed25519" ''
|
||||
-----BEGIN OPENSSH PRIVATE KEY-----
|
||||
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
|
||||
QyNTUxOQAAACBacZuz1ELGQdhI7PF6dGFafCDlvh8pSEc4cHjkW0QjLwAAAJC9YTxxvWE8
|
||||
cQAAAAtzc2gtZWQyNTUxOQAAACBacZuz1ELGQdhI7PF6dGFafCDlvh8pSEc4cHjkW0QjLw
|
||||
AAAEAAhV7wTl5dL/lz+PF/d4PnZXuG1Id6L/mFEiGT1tZsuFpxm7PUQsZB2Ejs8Xp0YVp8
|
||||
IOW+HylIRzhweORbRCMvAAAADXJzY2h1ZXR6QGt1cnQ=
|
||||
-----END OPENSSH PRIVATE KEY-----
|
||||
'';
|
||||
publicKeyAppendOnly = ''
|
||||
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIFpxm7PUQsZB2Ejs8Xp0YVp8IOW+HylIRzhweORbRCMv root@client
|
||||
'';
|
||||
|
||||
in {
|
||||
name = "borgbackup";
|
||||
meta = with pkgs.lib; {
|
||||
maintainers = with maintainers; [ dotlambda ];
|
||||
};
|
||||
|
||||
nodes = {
|
||||
client = { ... }: {
|
||||
services.borgbackup.jobs = {
|
||||
|
||||
local = {
|
||||
paths = dataDir;
|
||||
repo = localRepo;
|
||||
preHook = ''
|
||||
# Don't append a timestamp
|
||||
archiveName="${archiveName}"
|
||||
'';
|
||||
encryption = {
|
||||
mode = "repokey";
|
||||
inherit passphrase;
|
||||
};
|
||||
compression = "auto,zlib,9";
|
||||
prune.keep = {
|
||||
within = "1y";
|
||||
yearly = 5;
|
||||
};
|
||||
exclude = [ "*/${excludeFile}" ];
|
||||
postHook = "echo post";
|
||||
startAt = [ ]; # Do not run automatically
|
||||
};
|
||||
|
||||
remote = {
|
||||
paths = dataDir;
|
||||
repo = remoteRepo;
|
||||
encryption.mode = "none";
|
||||
startAt = [ ];
|
||||
environment.BORG_RSH = "ssh -oStrictHostKeyChecking=no -i /root/id_ed25519";
|
||||
};
|
||||
|
||||
remoteAppendOnly = {
|
||||
paths = dataDir;
|
||||
repo = remoteRepo;
|
||||
encryption.mode = "none";
|
||||
startAt = [ ];
|
||||
environment.BORG_RSH = "ssh -oStrictHostKeyChecking=no -i /root/id_ed25519.appendOnly";
|
||||
};
|
||||
|
||||
commandSuccess = {
|
||||
dumpCommand = pkgs.writeScript "commandSuccess" ''
|
||||
echo -n test
|
||||
'';
|
||||
repo = remoteRepo;
|
||||
encryption.mode = "none";
|
||||
startAt = [ ];
|
||||
environment.BORG_RSH = "ssh -oStrictHostKeyChecking=no -i /root/id_ed25519";
|
||||
};
|
||||
|
||||
commandFail = {
|
||||
dumpCommand = "${pkgs.coreutils}/bin/false";
|
||||
repo = remoteRepo;
|
||||
encryption.mode = "none";
|
||||
startAt = [ ];
|
||||
environment.BORG_RSH = "ssh -oStrictHostKeyChecking=no -i /root/id_ed25519";
|
||||
};
|
||||
|
||||
};
|
||||
};
|
||||
|
||||
server = { ... }: {
|
||||
services.openssh = {
|
||||
enable = true;
|
||||
passwordAuthentication = false;
|
||||
kbdInteractiveAuthentication = false;
|
||||
};
|
||||
|
||||
services.borgbackup.repos.repo1 = {
|
||||
authorizedKeys = [ publicKey ];
|
||||
path = "/data/borgbackup";
|
||||
};
|
||||
|
||||
# Second repo to make sure the authorizedKeys options are merged correctly
|
||||
services.borgbackup.repos.repo2 = {
|
||||
authorizedKeysAppendOnly = [ publicKeyAppendOnly ];
|
||||
path = "/data/borgbackup";
|
||||
quota = ".5G";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
testScript = ''
|
||||
start_all()
|
||||
|
||||
client.fail('test -d "${remoteRepo}"')
|
||||
|
||||
client.succeed(
|
||||
"cp ${privateKey} /root/id_ed25519"
|
||||
)
|
||||
client.succeed("chmod 0600 /root/id_ed25519")
|
||||
client.succeed(
|
||||
"cp ${privateKeyAppendOnly} /root/id_ed25519.appendOnly"
|
||||
)
|
||||
client.succeed("chmod 0600 /root/id_ed25519.appendOnly")
|
||||
|
||||
client.succeed("mkdir -p ${dataDir}")
|
||||
client.succeed("touch ${dataDir}/${excludeFile}")
|
||||
client.succeed("echo '${keepFileData}' > ${dataDir}/${keepFile}")
|
||||
|
||||
with subtest("local"):
|
||||
borg = "BORG_PASSPHRASE='${passphrase}' borg"
|
||||
client.systemctl("start --wait borgbackup-job-local")
|
||||
client.fail("systemctl is-failed borgbackup-job-local")
|
||||
# Make sure exactly one archive has been created
|
||||
assert int(client.succeed("{} list '${localRepo}' | wc -l".format(borg))) > 0
|
||||
# Make sure excludeFile has been excluded
|
||||
client.fail(
|
||||
"{} list '${localRepo}::${archiveName}' | grep -qF '${excludeFile}'".format(borg)
|
||||
)
|
||||
# Make sure keepFile has the correct content
|
||||
client.succeed("{} extract '${localRepo}::${archiveName}'".format(borg))
|
||||
assert "${keepFileData}" in client.succeed("cat ${dataDir}/${keepFile}")
|
||||
# Make sure the same is true when using `borg mount`
|
||||
client.succeed(
|
||||
"mkdir -p /mnt/borg && {} mount '${localRepo}::${archiveName}' /mnt/borg".format(
|
||||
borg
|
||||
)
|
||||
)
|
||||
assert "${keepFileData}" in client.succeed(
|
||||
"cat /mnt/borg/${dataDir}/${keepFile}"
|
||||
)
|
||||
|
||||
with subtest("remote"):
|
||||
borg = "BORG_RSH='ssh -oStrictHostKeyChecking=no -i /root/id_ed25519' borg"
|
||||
server.wait_for_unit("sshd.service")
|
||||
client.wait_for_unit("network.target")
|
||||
client.systemctl("start --wait borgbackup-job-remote")
|
||||
client.fail("systemctl is-failed borgbackup-job-remote")
|
||||
|
||||
# Make sure we can't access repos other than the specified one
|
||||
client.fail("{} list borg\@server:wrong".format(borg))
|
||||
|
||||
# TODO: Make sure that data is actually deleted
|
||||
|
||||
with subtest("remoteAppendOnly"):
|
||||
borg = (
|
||||
"BORG_RSH='ssh -oStrictHostKeyChecking=no -i /root/id_ed25519.appendOnly' borg"
|
||||
)
|
||||
server.wait_for_unit("sshd.service")
|
||||
client.wait_for_unit("network.target")
|
||||
client.systemctl("start --wait borgbackup-job-remoteAppendOnly")
|
||||
client.fail("systemctl is-failed borgbackup-job-remoteAppendOnly")
|
||||
|
||||
# Make sure we can't access repos other than the specified one
|
||||
client.fail("{} list borg\@server:wrong".format(borg))
|
||||
|
||||
# TODO: Make sure that data is not actually deleted
|
||||
|
||||
with subtest("commandSuccess"):
|
||||
server.wait_for_unit("sshd.service")
|
||||
client.wait_for_unit("network.target")
|
||||
client.systemctl("start --wait borgbackup-job-commandSuccess")
|
||||
client.fail("systemctl is-failed borgbackup-job-commandSuccess")
|
||||
id = client.succeed("borg-job-commandSuccess list | tail -n1 | cut -d' ' -f1").strip()
|
||||
client.succeed(f"borg-job-commandSuccess extract ::{id} stdin")
|
||||
assert "test" == client.succeed("cat stdin")
|
||||
|
||||
with subtest("commandFail"):
|
||||
server.wait_for_unit("sshd.service")
|
||||
client.wait_for_unit("network.target")
|
||||
client.systemctl("start --wait borgbackup-job-commandFail")
|
||||
client.succeed("systemctl is-failed borgbackup-job-commandFail")
|
||||
'';
|
||||
})
|
||||
51
nixos/tests/botamusique.nix
Normal file
51
nixos/tests/botamusique.nix
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
import ./make-test-python.nix ({ pkgs, lib, ...} :
|
||||
|
||||
{
|
||||
name = "botamusique";
|
||||
meta.maintainers = with lib.maintainers; [ hexa ];
|
||||
|
||||
nodes = {
|
||||
machine = { config, ... }: {
|
||||
networking.extraHosts = ''
|
||||
127.0.0.1 all.api.radio-browser.info
|
||||
'';
|
||||
|
||||
services.murmur = {
|
||||
enable = true;
|
||||
registerName = "NixOS tests";
|
||||
};
|
||||
|
||||
services.botamusique = {
|
||||
enable = true;
|
||||
settings = {
|
||||
server = {
|
||||
channel = "NixOS tests";
|
||||
};
|
||||
bot = {
|
||||
version = false;
|
||||
auto_check_update = false;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
testScript = ''
|
||||
start_all()
|
||||
|
||||
machine.wait_for_unit("murmur.service")
|
||||
machine.wait_for_unit("botamusique.service")
|
||||
|
||||
machine.sleep(10)
|
||||
|
||||
machine.wait_until_succeeds(
|
||||
"journalctl -u murmur.service -e | grep -q '<1:botamusique(-1)> Authenticated'"
|
||||
)
|
||||
|
||||
with subtest("Check systemd hardening"):
|
||||
output = machine.execute("systemctl show botamusique.service")[1]
|
||||
machine.log(output)
|
||||
output = machine.execute("systemd-analyze security botamusique.service")[1]
|
||||
machine.log(output)
|
||||
'';
|
||||
})
|
||||
29
nixos/tests/bpf.nix
Normal file
29
nixos/tests/bpf.nix
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
import ./make-test-python.nix ({ pkgs, ... }: {
|
||||
name = "bpf";
|
||||
meta.maintainers = with pkgs.lib.maintainers; [ martinetd ];
|
||||
|
||||
nodes.machine = { pkgs, ... }: {
|
||||
programs.bcc.enable = true;
|
||||
environment.systemPackages = with pkgs; [ bpftrace ];
|
||||
};
|
||||
|
||||
testScript = ''
|
||||
## bcc
|
||||
# syscount -d 1 stops 1s after probe started so is good for that
|
||||
print(machine.succeed("syscount -d 1"))
|
||||
|
||||
## bpftrace
|
||||
# list probes
|
||||
machine.succeed("bpftrace -l")
|
||||
# simple BEGIN probe (user probe on bpftrace itself)
|
||||
print(machine.succeed("bpftrace -e 'BEGIN { print(\"ok\"); exit(); }'"))
|
||||
# tracepoint
|
||||
print(machine.succeed("bpftrace -e 'tracepoint:syscalls:sys_enter_* { print(probe); exit() }'"))
|
||||
# kprobe
|
||||
print(machine.succeed("bpftrace -e 'kprobe:schedule { print(probe); exit() }'"))
|
||||
# BTF
|
||||
print(machine.succeed("bpftrace -e 'kprobe:schedule { "
|
||||
" printf(\"tgid: %d\", ((struct task_struct*) curtask)->tgid); exit() "
|
||||
"}'"))
|
||||
'';
|
||||
})
|
||||
33
nixos/tests/breitbandmessung.nix
Normal file
33
nixos/tests/breitbandmessung.nix
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
import ./make-test-python.nix ({ lib, ... }: {
|
||||
name = "breitbandmessung";
|
||||
meta.maintainers = with lib.maintainers; [ b4dm4n ];
|
||||
|
||||
nodes.machine = { pkgs, ... }: {
|
||||
imports = [
|
||||
./common/user-account.nix
|
||||
./common/x11.nix
|
||||
];
|
||||
|
||||
# increase screen size to make the whole program visible
|
||||
virtualisation.resolution = { x = 1280; y = 1024; };
|
||||
|
||||
test-support.displayManager.auto.user = "alice";
|
||||
|
||||
environment.systemPackages = with pkgs; [ breitbandmessung ];
|
||||
environment.variables.XAUTHORITY = "/home/alice/.Xauthority";
|
||||
|
||||
# breitbandmessung is unfree
|
||||
nixpkgs.config.allowUnfreePredicate = pkg: builtins.elem (lib.getName pkg) [ "breitbandmessung" ];
|
||||
};
|
||||
|
||||
enableOCR = true;
|
||||
|
||||
testScript = ''
|
||||
machine.wait_for_x()
|
||||
machine.execute("su - alice -c breitbandmessung >&2 &")
|
||||
machine.wait_for_window("Breitbandmessung")
|
||||
machine.wait_for_text("Breitbandmessung")
|
||||
machine.wait_for_text("Datenschutz")
|
||||
machine.screenshot("breitbandmessung")
|
||||
'';
|
||||
})
|
||||
43
nixos/tests/brscan5.nix
Normal file
43
nixos/tests/brscan5.nix
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
# integration tests for brscan5 sane driver
|
||||
#
|
||||
|
||||
import ./make-test-python.nix ({ pkgs, ...} : {
|
||||
name = "brscan5";
|
||||
meta = with pkgs.lib.maintainers; {
|
||||
maintainers = [ mattchrist ];
|
||||
};
|
||||
|
||||
nodes.machine = { pkgs, ... }:
|
||||
{
|
||||
nixpkgs.config.allowUnfree = true;
|
||||
hardware.sane = {
|
||||
enable = true;
|
||||
brscan5 = {
|
||||
enable = true;
|
||||
netDevices = {
|
||||
"a" = { model="ADS-1200"; nodename="BRW0080927AFBCE"; };
|
||||
"b" = { model="ADS-1200"; ip="192.168.1.2"; };
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
testScript = ''
|
||||
import re
|
||||
# sane loads libsane-brother5.so.1 successfully, and scanimage doesn't die
|
||||
strace = machine.succeed('strace scanimage -L 2>&1').split("\n")
|
||||
regexp = 'openat\(.*libsane-brother5.so.1", O_RDONLY|O_CLOEXEC\) = \d\d*$'
|
||||
assert len([x for x in strace if re.match(regexp,x)]) > 0
|
||||
|
||||
# module creates a config
|
||||
cfg = machine.succeed('cat /etc/opt/brother/scanner/brscan5/brsanenetdevice.cfg')
|
||||
assert 'DEVICE=a , "ADS-1200" , 0x4f9:0x459 , NODENAME=BRW0080927AFBCE' in cfg
|
||||
assert 'DEVICE=b , "ADS-1200" , 0x4f9:0x459 , IP-ADDRESS=192.168.1.2' in cfg
|
||||
|
||||
# scanimage lists the two network scanners
|
||||
scanimage = machine.succeed("scanimage -L")
|
||||
print(scanimage)
|
||||
assert """device `brother5:net1;dev0' is a Brother b ADS-1200""" in scanimage
|
||||
assert """device `brother5:net1;dev1' is a Brother a ADS-1200""" in scanimage
|
||||
'';
|
||||
})
|
||||
37
nixos/tests/btrbk-no-timer.nix
Normal file
37
nixos/tests/btrbk-no-timer.nix
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
import ./make-test-python.nix ({ lib, pkgs, ... }:
|
||||
{
|
||||
name = "btrbk-no-timer";
|
||||
meta.maintainers = with lib.maintainers; [ oxalica ];
|
||||
|
||||
nodes.machine = { ... }: {
|
||||
environment.systemPackages = with pkgs; [ btrfs-progs ];
|
||||
services.btrbk.instances.local = {
|
||||
onCalendar = null;
|
||||
settings.volume."/mnt" = {
|
||||
snapshot_dir = "btrbk/local";
|
||||
subvolume = "to_backup";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
testScript = ''
|
||||
start_all()
|
||||
|
||||
# Create btrfs partition at /mnt
|
||||
machine.succeed("truncate --size=128M /data_fs")
|
||||
machine.succeed("mkfs.btrfs /data_fs")
|
||||
machine.succeed("mkdir /mnt")
|
||||
machine.succeed("mount /data_fs /mnt")
|
||||
machine.succeed("btrfs subvolume create /mnt/to_backup")
|
||||
machine.succeed("mkdir -p /mnt/btrbk/local")
|
||||
|
||||
# The service should not have any triggering timer.
|
||||
unit = machine.get_unit_info('btrbk-local.service')
|
||||
assert "TriggeredBy" not in unit
|
||||
|
||||
# Manually starting the service should still work.
|
||||
machine.succeed("echo foo > /mnt/to_backup/bar")
|
||||
machine.start_job("btrbk-local.service")
|
||||
machine.wait_until_succeeds("cat /mnt/btrbk/local/*/bar | grep foo")
|
||||
'';
|
||||
})
|
||||
110
nixos/tests/btrbk.nix
Normal file
110
nixos/tests/btrbk.nix
Normal file
|
|
@ -0,0 +1,110 @@
|
|||
import ./make-test-python.nix ({ pkgs, ... }:
|
||||
|
||||
let
|
||||
privateKey = ''
|
||||
-----BEGIN OPENSSH PRIVATE KEY-----
|
||||
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
|
||||
QyNTUxOQAAACBx8UB04Q6Q/fwDFjakHq904PYFzG9pU2TJ9KXpaPMcrwAAAJB+cF5HfnBe
|
||||
RwAAAAtzc2gtZWQyNTUxOQAAACBx8UB04Q6Q/fwDFjakHq904PYFzG9pU2TJ9KXpaPMcrw
|
||||
AAAEBN75NsJZSpt63faCuaD75Unko0JjlSDxMhYHAPJk2/xXHxQHThDpD9/AMWNqQer3Tg
|
||||
9gXMb2lTZMn0pelo8xyvAAAADXJzY2h1ZXR6QGt1cnQ=
|
||||
-----END OPENSSH PRIVATE KEY-----
|
||||
'';
|
||||
publicKey = ''
|
||||
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIHHxQHThDpD9/AMWNqQer3Tg9gXMb2lTZMn0pelo8xyv
|
||||
'';
|
||||
in
|
||||
{
|
||||
name = "btrbk";
|
||||
meta = with pkgs.lib; {
|
||||
maintainers = with maintainers; [ symphorien ];
|
||||
};
|
||||
|
||||
nodes = {
|
||||
archive = { ... }: {
|
||||
environment.systemPackages = with pkgs; [ btrfs-progs ];
|
||||
# note: this makes the privateKey world readable.
|
||||
# don't do it with real ssh keys.
|
||||
environment.etc."btrbk_key".text = privateKey;
|
||||
services.btrbk = {
|
||||
extraPackages = [ pkgs.lz4 ];
|
||||
instances = {
|
||||
remote = {
|
||||
onCalendar = "minutely";
|
||||
settings = {
|
||||
ssh_identity = "/etc/btrbk_key";
|
||||
ssh_user = "btrbk";
|
||||
stream_compress = "lz4";
|
||||
volume = {
|
||||
"ssh://main/mnt" = {
|
||||
target = "/mnt";
|
||||
snapshot_dir = "btrbk/remote";
|
||||
subvolume = "to_backup";
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
main = { ... }: {
|
||||
environment.systemPackages = with pkgs; [ btrfs-progs ];
|
||||
services.openssh = {
|
||||
enable = true;
|
||||
passwordAuthentication = false;
|
||||
kbdInteractiveAuthentication = false;
|
||||
};
|
||||
services.btrbk = {
|
||||
extraPackages = [ pkgs.lz4 ];
|
||||
sshAccess = [
|
||||
{
|
||||
key = publicKey;
|
||||
roles = [ "source" "send" "info" "delete" ];
|
||||
}
|
||||
];
|
||||
instances = {
|
||||
local = {
|
||||
onCalendar = "minutely";
|
||||
settings = {
|
||||
volume = {
|
||||
"/mnt" = {
|
||||
snapshot_dir = "btrbk/local";
|
||||
subvolume = "to_backup";
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
testScript = ''
|
||||
start_all()
|
||||
|
||||
# create btrfs partition at /mnt
|
||||
for machine in (archive, main):
|
||||
machine.succeed("dd if=/dev/zero of=/data_fs bs=120M count=1")
|
||||
machine.succeed("mkfs.btrfs /data_fs")
|
||||
machine.succeed("mkdir /mnt")
|
||||
machine.succeed("mount /data_fs /mnt")
|
||||
|
||||
# what to backup and where
|
||||
main.succeed("btrfs subvolume create /mnt/to_backup")
|
||||
main.succeed("mkdir -p /mnt/btrbk/{local,remote}")
|
||||
|
||||
# check that local snapshots work
|
||||
with subtest("local"):
|
||||
main.succeed("echo foo > /mnt/to_backup/bar")
|
||||
main.wait_until_succeeds("cat /mnt/btrbk/local/*/bar | grep foo")
|
||||
main.succeed("echo bar > /mnt/to_backup/bar")
|
||||
main.succeed("cat /mnt/btrbk/local/*/bar | grep foo")
|
||||
|
||||
# check that btrfs send/receive works and ssh access works
|
||||
with subtest("remote"):
|
||||
archive.wait_until_succeeds("cat /mnt/*/bar | grep bar")
|
||||
main.succeed("echo baz > /mnt/to_backup/bar")
|
||||
archive.succeed("cat /mnt/*/bar | grep bar")
|
||||
'';
|
||||
})
|
||||
113
nixos/tests/buildbot.nix
Normal file
113
nixos/tests/buildbot.nix
Normal file
|
|
@ -0,0 +1,113 @@
|
|||
# Test ensures buildbot master comes up correctly and workers can connect
|
||||
|
||||
{ system ? builtins.currentSystem,
|
||||
config ? {},
|
||||
pkgs ? import ../.. { inherit system config; }
|
||||
}:
|
||||
|
||||
import ./make-test-python.nix {
|
||||
name = "buildbot";
|
||||
|
||||
nodes = {
|
||||
bbmaster = { pkgs, ... }: {
|
||||
services.buildbot-master = {
|
||||
enable = true;
|
||||
|
||||
# NOTE: use fake repo due to no internet in hydra ci
|
||||
factorySteps = [
|
||||
"steps.Git(repourl='git://gitrepo/fakerepo.git', mode='incremental')"
|
||||
"steps.ShellCommand(command=['bash', 'fakerepo.sh'])"
|
||||
];
|
||||
changeSource = [
|
||||
"changes.GitPoller('git://gitrepo/fakerepo.git', workdir='gitpoller-workdir', branch='master', pollinterval=300)"
|
||||
];
|
||||
};
|
||||
networking.firewall.allowedTCPPorts = [ 8010 8011 9989 ];
|
||||
environment.systemPackages = with pkgs; [ git python3Packages.buildbot-full ];
|
||||
};
|
||||
|
||||
bbworker = { pkgs, ... }: {
|
||||
services.buildbot-worker = {
|
||||
enable = true;
|
||||
masterUrl = "bbmaster:9989";
|
||||
};
|
||||
environment.systemPackages = with pkgs; [ git python3Packages.buildbot-worker ];
|
||||
};
|
||||
|
||||
gitrepo = { pkgs, ... }: {
|
||||
services.openssh.enable = true;
|
||||
networking.firewall.allowedTCPPorts = [ 22 9418 ];
|
||||
environment.systemPackages = with pkgs; [ git ];
|
||||
systemd.services.git-daemon = {
|
||||
description = "Git daemon for the test";
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
after = [ "network.target" "sshd.service" ];
|
||||
|
||||
serviceConfig.Restart = "always";
|
||||
path = with pkgs; [ coreutils git openssh ];
|
||||
environment = { HOME = "/root"; };
|
||||
preStart = ''
|
||||
git config --global user.name 'Nobody Fakeuser'
|
||||
git config --global user.email 'nobody\@fakerepo.com'
|
||||
rm -rvf /srv/repos/fakerepo.git /tmp/fakerepo
|
||||
mkdir -pv /srv/repos/fakerepo ~/.ssh
|
||||
ssh-keyscan -H gitrepo > ~/.ssh/known_hosts
|
||||
cat ~/.ssh/known_hosts
|
||||
|
||||
mkdir -p /src/repos/fakerepo
|
||||
cd /srv/repos/fakerepo
|
||||
rm -rf *
|
||||
git init
|
||||
echo -e '#!/bin/sh\necho fakerepo' > fakerepo.sh
|
||||
cat fakerepo.sh
|
||||
touch .git/git-daemon-export-ok
|
||||
git add fakerepo.sh .git/git-daemon-export-ok
|
||||
git commit -m fakerepo
|
||||
'';
|
||||
script = ''
|
||||
git daemon --verbose --export-all --base-path=/srv/repos --reuseaddr
|
||||
'';
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
testScript = ''
|
||||
gitrepo.wait_for_unit("git-daemon.service")
|
||||
gitrepo.wait_for_unit("multi-user.target")
|
||||
|
||||
with subtest("Repo is accessible via git daemon"):
|
||||
bbmaster.wait_for_unit("network-online.target")
|
||||
bbmaster.succeed("rm -rfv /tmp/fakerepo")
|
||||
bbmaster.succeed("git clone git://gitrepo/fakerepo /tmp/fakerepo")
|
||||
|
||||
with subtest("Master service and worker successfully connect"):
|
||||
bbmaster.wait_for_unit("buildbot-master.service")
|
||||
bbmaster.wait_until_succeeds("curl --fail -s --head http://bbmaster:8010")
|
||||
bbworker.wait_for_unit("network-online.target")
|
||||
bbworker.succeed("nc -z bbmaster 8010")
|
||||
bbworker.succeed("nc -z bbmaster 9989")
|
||||
bbworker.wait_for_unit("buildbot-worker.service")
|
||||
|
||||
with subtest("Stop buildbot worker"):
|
||||
bbmaster.succeed("systemctl -l --no-pager status buildbot-master")
|
||||
bbmaster.succeed("systemctl stop buildbot-master")
|
||||
bbworker.fail("nc -z bbmaster 8010")
|
||||
bbworker.fail("nc -z bbmaster 9989")
|
||||
bbworker.succeed("systemctl -l --no-pager status buildbot-worker")
|
||||
bbworker.succeed("systemctl stop buildbot-worker")
|
||||
|
||||
with subtest("Buildbot daemon mode works"):
|
||||
bbmaster.succeed(
|
||||
"buildbot create-master /tmp",
|
||||
"mv -fv /tmp/master.cfg.sample /tmp/master.cfg",
|
||||
"sed -i 's/8010/8011/' /tmp/master.cfg",
|
||||
"buildbot start /tmp",
|
||||
"nc -z bbmaster 8011",
|
||||
)
|
||||
bbworker.wait_until_succeeds("curl --fail -s --head http://bbmaster:8011")
|
||||
bbmaster.wait_until_succeeds("buildbot stop /tmp")
|
||||
bbworker.fail("nc -z bbmaster 8011")
|
||||
'';
|
||||
|
||||
meta.maintainers = with pkgs.lib.maintainers; [ ];
|
||||
} {}
|
||||
31
nixos/tests/buildkite-agents.nix
Normal file
31
nixos/tests/buildkite-agents.nix
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
import ./make-test-python.nix ({ pkgs, ... }:
|
||||
|
||||
{
|
||||
name = "buildkite-agent";
|
||||
meta = with pkgs.lib.maintainers; {
|
||||
maintainers = [ flokli ];
|
||||
};
|
||||
|
||||
nodes.machine = { pkgs, ... }: {
|
||||
services.buildkite-agents = {
|
||||
one = {
|
||||
privateSshKeyPath = (import ./ssh-keys.nix pkgs).snakeOilPrivateKey;
|
||||
tokenPath = (pkgs.writeText "my-token" "5678");
|
||||
};
|
||||
two = {
|
||||
tokenPath = (pkgs.writeText "my-token" "1234");
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
testScript = ''
|
||||
start_all()
|
||||
# we can't wait on the unit to start up, as we obviously can't connect to buildkite,
|
||||
# but we can look whether files are set up correctly
|
||||
|
||||
machine.wait_for_file("/var/lib/buildkite-agent-one/buildkite-agent.cfg")
|
||||
machine.wait_for_file("/var/lib/buildkite-agent-one/.ssh/id_rsa")
|
||||
|
||||
machine.wait_for_file("/var/lib/buildkite-agent-two/buildkite-agent.cfg")
|
||||
'';
|
||||
})
|
||||
107
nixos/tests/caddy.nix
Normal file
107
nixos/tests/caddy.nix
Normal file
|
|
@ -0,0 +1,107 @@
|
|||
import ./make-test-python.nix ({ pkgs, ... }: {
|
||||
name = "caddy";
|
||||
meta = with pkgs.lib.maintainers; {
|
||||
maintainers = [ xfix Br1ght0ne ];
|
||||
};
|
||||
|
||||
nodes = {
|
||||
webserver = { pkgs, lib, ... }: {
|
||||
services.caddy.enable = true;
|
||||
services.caddy.extraConfig = ''
|
||||
http://localhost {
|
||||
encode gzip
|
||||
|
||||
file_server
|
||||
root * ${
|
||||
pkgs.runCommand "testdir" {} ''
|
||||
mkdir "$out"
|
||||
echo hello world > "$out/example.html"
|
||||
''
|
||||
}
|
||||
}
|
||||
'';
|
||||
|
||||
specialisation.etag.configuration = {
|
||||
services.caddy.extraConfig = lib.mkForce ''
|
||||
http://localhost {
|
||||
encode gzip
|
||||
|
||||
file_server
|
||||
root * ${
|
||||
pkgs.runCommand "testdir2" {} ''
|
||||
mkdir "$out"
|
||||
echo changed > "$out/example.html"
|
||||
''
|
||||
}
|
||||
}
|
||||
'';
|
||||
};
|
||||
|
||||
specialisation.config-reload.configuration = {
|
||||
services.caddy.extraConfig = ''
|
||||
http://localhost:8080 {
|
||||
}
|
||||
'';
|
||||
};
|
||||
specialisation.multiple-configs.configuration = {
|
||||
services.caddy.virtualHosts = {
|
||||
"http://localhost:8080" = { };
|
||||
"http://localhost:8081" = { };
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
testScript = { nodes, ... }:
|
||||
let
|
||||
etagSystem = "${nodes.webserver.config.system.build.toplevel}/specialisation/etag";
|
||||
justReloadSystem = "${nodes.webserver.config.system.build.toplevel}/specialisation/config-reload";
|
||||
multipleConfigs = "${nodes.webserver.config.system.build.toplevel}/specialisation/multiple-configs";
|
||||
in
|
||||
''
|
||||
url = "http://localhost/example.html"
|
||||
webserver.wait_for_unit("caddy")
|
||||
webserver.wait_for_open_port("80")
|
||||
|
||||
|
||||
def check_etag(url):
|
||||
etag = webserver.succeed(
|
||||
"curl --fail -v '{}' 2>&1 | sed -n -e \"s/^< [Ee][Tt][Aa][Gg]: *//p\"".format(
|
||||
url
|
||||
)
|
||||
)
|
||||
etag = etag.replace("\r\n", " ")
|
||||
http_code = webserver.succeed(
|
||||
"curl --fail --silent --show-error -o /dev/null -w \"%{{http_code}}\" --head -H 'If-None-Match: {}' {}".format(
|
||||
etag, url
|
||||
)
|
||||
)
|
||||
assert int(http_code) == 304, "HTTP code is {}, expected 304".format(http_code)
|
||||
return etag
|
||||
|
||||
|
||||
with subtest("check ETag if serving Nix store paths"):
|
||||
old_etag = check_etag(url)
|
||||
webserver.succeed(
|
||||
"${etagSystem}/bin/switch-to-configuration test >&2"
|
||||
)
|
||||
webserver.sleep(1)
|
||||
new_etag = check_etag(url)
|
||||
assert old_etag != new_etag, "Old ETag {} is the same as {}".format(
|
||||
old_etag, new_etag
|
||||
)
|
||||
|
||||
with subtest("config is reloaded on nixos-rebuild switch"):
|
||||
webserver.succeed(
|
||||
"${justReloadSystem}/bin/switch-to-configuration test >&2"
|
||||
)
|
||||
webserver.wait_for_open_port("8080")
|
||||
|
||||
with subtest("multiple configs are correctly merged"):
|
||||
webserver.succeed(
|
||||
"${multipleConfigs}/bin/switch-to-configuration test >&2"
|
||||
)
|
||||
webserver.wait_for_open_port("8080")
|
||||
webserver.wait_for_open_port("8081")
|
||||
'';
|
||||
})
|
||||
34
nixos/tests/cadvisor.nix
Normal file
34
nixos/tests/cadvisor.nix
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
import ./make-test-python.nix ({ pkgs, ... } : {
|
||||
name = "cadvisor";
|
||||
meta = with pkgs.lib.maintainers; {
|
||||
maintainers = [ offline ];
|
||||
};
|
||||
|
||||
nodes = {
|
||||
machine = { ... }: {
|
||||
services.cadvisor.enable = true;
|
||||
};
|
||||
|
||||
influxdb = { lib, ... }: with lib; {
|
||||
services.cadvisor.enable = true;
|
||||
services.cadvisor.storageDriver = "influxdb";
|
||||
services.influxdb.enable = true;
|
||||
};
|
||||
};
|
||||
|
||||
testScript = ''
|
||||
start_all()
|
||||
machine.wait_for_unit("cadvisor.service")
|
||||
machine.succeed("curl -f http://localhost:8080/containers/")
|
||||
|
||||
influxdb.wait_for_unit("influxdb.service")
|
||||
|
||||
# create influxdb database
|
||||
influxdb.succeed(
|
||||
'curl -f -XPOST http://localhost:8086/query --data-urlencode "q=CREATE DATABASE root"'
|
||||
)
|
||||
|
||||
influxdb.wait_for_unit("cadvisor.service")
|
||||
influxdb.succeed("curl -f http://localhost:8080/containers/")
|
||||
'';
|
||||
})
|
||||
36
nixos/tests/cage.nix
Normal file
36
nixos/tests/cage.nix
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
import ./make-test-python.nix ({ pkgs, ...} :
|
||||
|
||||
{
|
||||
name = "cage";
|
||||
meta = with pkgs.lib.maintainers; {
|
||||
maintainers = [ matthewbauer ];
|
||||
};
|
||||
|
||||
nodes.machine = { ... }:
|
||||
|
||||
{
|
||||
imports = [ ./common/user-account.nix ];
|
||||
services.cage = {
|
||||
enable = true;
|
||||
user = "alice";
|
||||
# Disable color and bold and use a larger font to make OCR easier:
|
||||
program = "${pkgs.xterm}/bin/xterm -cm -pc -fa Monospace -fs 24";
|
||||
};
|
||||
|
||||
# Need to switch to a different GPU driver than the default one (-vga std) so that Cage can launch:
|
||||
virtualisation.qemu.options = [ "-vga none -device virtio-gpu-pci" ];
|
||||
};
|
||||
|
||||
enableOCR = true;
|
||||
|
||||
testScript = { nodes, ... }: let
|
||||
user = nodes.machine.config.users.users.alice;
|
||||
in ''
|
||||
with subtest("Wait for cage to boot up"):
|
||||
start_all()
|
||||
machine.wait_for_file("/run/user/${toString user.uid}/wayland-0.lock")
|
||||
machine.wait_until_succeeds("pgrep xterm")
|
||||
machine.wait_for_text("alice@machine")
|
||||
machine.screenshot("screen")
|
||||
'';
|
||||
})
|
||||
64
nixos/tests/cagebreak.nix
Normal file
64
nixos/tests/cagebreak.nix
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
import ./make-test-python.nix ({ pkgs, lib, ...} :
|
||||
|
||||
let
|
||||
cagebreakConfigfile = pkgs.writeText "config" ''
|
||||
workspaces 1
|
||||
escape C-t
|
||||
bind t exec env DISPLAY=:0 ${pkgs.xterm}/bin/xterm -cm -pc
|
||||
'';
|
||||
in
|
||||
{
|
||||
name = "cagebreak";
|
||||
meta = with pkgs.lib.maintainers; {
|
||||
maintainers = [ berbiche ];
|
||||
};
|
||||
|
||||
nodes.machine = { config, ... }:
|
||||
let
|
||||
alice = config.users.users.alice;
|
||||
in {
|
||||
# Automatically login on tty1 as a normal user:
|
||||
imports = [ ./common/user-account.nix ];
|
||||
services.getty.autologinUser = "alice";
|
||||
programs.bash.loginShellInit = ''
|
||||
if [ "$(tty)" = "/dev/tty1" ]; then
|
||||
set -e
|
||||
|
||||
mkdir -p ~/.config/cagebreak
|
||||
cp -f ${cagebreakConfigfile} ~/.config/cagebreak/config
|
||||
|
||||
cagebreak
|
||||
fi
|
||||
'';
|
||||
|
||||
hardware.opengl.enable = true;
|
||||
programs.xwayland.enable = true;
|
||||
environment.systemPackages = [ pkgs.cagebreak pkgs.wayland-utils ];
|
||||
|
||||
# Need to switch to a different GPU driver than the default one (-vga std) so that Cagebreak can launch:
|
||||
virtualisation.qemu.options = [ "-vga none -device virtio-gpu-pci" ];
|
||||
};
|
||||
|
||||
enableOCR = true;
|
||||
|
||||
testScript = { nodes, ... }: let
|
||||
user = nodes.machine.config.users.users.alice;
|
||||
XDG_RUNTIME_DIR = "/run/user/${toString user.uid}";
|
||||
in ''
|
||||
start_all()
|
||||
machine.wait_for_unit("multi-user.target")
|
||||
machine.wait_for_file("${XDG_RUNTIME_DIR}/wayland-0")
|
||||
|
||||
with subtest("ensure wayland works with wayinfo from wallutils"):
|
||||
print(machine.succeed("env XDG_RUNTIME_DIR=${XDG_RUNTIME_DIR} wayland-info"))
|
||||
|
||||
# TODO: Fix the XWayland test (log the cagebreak output to debug):
|
||||
# with subtest("ensure xwayland works with xterm"):
|
||||
# machine.send_key("ctrl-t")
|
||||
# machine.send_key("t")
|
||||
# machine.wait_until_succeeds("pgrep xterm")
|
||||
# machine.wait_for_text("${user.name}@machine")
|
||||
# machine.screenshot("screen")
|
||||
# machine.send_key("ctrl-d")
|
||||
'';
|
||||
})
|
||||
43
nixos/tests/calibre-web.nix
Normal file
43
nixos/tests/calibre-web.nix
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
import ./make-test-python.nix (
|
||||
{ pkgs, lib, ... }:
|
||||
|
||||
let
|
||||
port = 3142;
|
||||
defaultPort = 8083;
|
||||
in
|
||||
with lib;
|
||||
{
|
||||
name = "calibre-web";
|
||||
meta.maintainers = with pkgs.lib.maintainers; [ pborzenkov ];
|
||||
|
||||
nodes = {
|
||||
customized = { pkgs, ... }: {
|
||||
services.calibre-web = {
|
||||
enable = true;
|
||||
listen.port = port;
|
||||
options = {
|
||||
calibreLibrary = "/tmp/books";
|
||||
reverseProxyAuth = {
|
||||
enable = true;
|
||||
header = "X-User";
|
||||
};
|
||||
};
|
||||
};
|
||||
environment.systemPackages = [ pkgs.calibre ];
|
||||
};
|
||||
};
|
||||
testScript = ''
|
||||
start_all()
|
||||
|
||||
customized.succeed(
|
||||
"mkdir /tmp/books && calibredb --library-path /tmp/books add -e --title test-book"
|
||||
)
|
||||
customized.succeed("systemctl restart calibre-web")
|
||||
customized.wait_for_unit("calibre-web.service")
|
||||
customized.wait_for_open_port(${toString port})
|
||||
customized.succeed(
|
||||
"curl --fail -H X-User:admin 'http://localhost:${toString port}' | grep test-book"
|
||||
)
|
||||
'';
|
||||
}
|
||||
)
|
||||
132
nixos/tests/cassandra.nix
Normal file
132
nixos/tests/cassandra.nix
Normal file
|
|
@ -0,0 +1,132 @@
|
|||
import ./make-test-python.nix ({ pkgs, lib, testPackage ? pkgs.cassandra, ... }:
|
||||
let
|
||||
clusterName = "NixOS Automated-Test Cluster";
|
||||
|
||||
testRemoteAuth = lib.versionAtLeast testPackage.version "3.11";
|
||||
jmxRoles = [{ username = "me"; password = "password"; }];
|
||||
jmxRolesFile = ./cassandra-jmx-roles;
|
||||
jmxAuthArgs = "-u ${(builtins.elemAt jmxRoles 0).username} -pw ${(builtins.elemAt jmxRoles 0).password}";
|
||||
jmxPort = 7200; # Non-standard port so it doesn't accidentally work
|
||||
jmxPortStr = toString jmxPort;
|
||||
|
||||
# Would usually be assigned to 512M.
|
||||
# Set it to a different value, so that we can check whether our config
|
||||
# actually changes it.
|
||||
numMaxHeapSize = "400";
|
||||
getHeapLimitCommand = ''
|
||||
nodetool info -p ${jmxPortStr} | grep "^Heap Memory" | awk '{print $NF}'
|
||||
'';
|
||||
checkHeapLimitCommand = pkgs.writeShellScript "check-heap-limit.sh" ''
|
||||
[ 1 -eq "$(echo "$(${getHeapLimitCommand}) < ${numMaxHeapSize}" | ${pkgs.bc}/bin/bc)" ]
|
||||
'';
|
||||
|
||||
cassandraCfg = ipAddress:
|
||||
{ enable = true;
|
||||
inherit clusterName;
|
||||
listenAddress = ipAddress;
|
||||
rpcAddress = ipAddress;
|
||||
seedAddresses = [ "192.168.1.1" ];
|
||||
package = testPackage;
|
||||
maxHeapSize = "${numMaxHeapSize}M";
|
||||
heapNewSize = "100M";
|
||||
inherit jmxPort;
|
||||
};
|
||||
nodeCfg = ipAddress: extra: {pkgs, config, ...}: rec {
|
||||
environment.systemPackages = [ testPackage ];
|
||||
networking = {
|
||||
firewall.allowedTCPPorts = [ 7000 9042 services.cassandra.jmxPort ];
|
||||
useDHCP = false;
|
||||
interfaces.eth1.ipv4.addresses = pkgs.lib.mkOverride 0 [
|
||||
{ address = ipAddress; prefixLength = 24; }
|
||||
];
|
||||
};
|
||||
services.cassandra = cassandraCfg ipAddress // extra;
|
||||
};
|
||||
in
|
||||
{
|
||||
name = "cassandra-${testPackage.version}";
|
||||
meta = {
|
||||
maintainers = with lib.maintainers; [ johnazoidberg ];
|
||||
};
|
||||
|
||||
nodes = {
|
||||
cass0 = nodeCfg "192.168.1.1" {};
|
||||
cass1 = nodeCfg "192.168.1.2" (lib.optionalAttrs testRemoteAuth { inherit jmxRoles; remoteJmx = true; });
|
||||
cass2 = nodeCfg "192.168.1.3" { jvmOpts = [ "-Dcassandra.replace_address=cass1" ]; };
|
||||
};
|
||||
|
||||
testScript = ''
|
||||
# Check configuration
|
||||
with subtest("Timers exist"):
|
||||
cass0.succeed("systemctl list-timers | grep cassandra-full-repair.timer")
|
||||
cass0.succeed("systemctl list-timers | grep cassandra-incremental-repair.timer")
|
||||
|
||||
with subtest("Can connect via cqlsh"):
|
||||
cass0.wait_for_unit("cassandra.service")
|
||||
cass0.wait_until_succeeds("nc -z cass0 9042")
|
||||
cass0.succeed("echo 'show version;' | cqlsh cass0")
|
||||
|
||||
with subtest("Nodetool is operational"):
|
||||
cass0.wait_for_unit("cassandra.service")
|
||||
cass0.wait_until_succeeds("nc -z localhost ${jmxPortStr}")
|
||||
cass0.succeed("nodetool status -p ${jmxPortStr} --resolve-ip | egrep '^UN[[:space:]]+cass0'")
|
||||
|
||||
with subtest("Cluster name was set"):
|
||||
cass0.wait_for_unit("cassandra.service")
|
||||
cass0.wait_until_succeeds("nc -z localhost ${jmxPortStr}")
|
||||
cass0.wait_until_succeeds(
|
||||
"nodetool describecluster -p ${jmxPortStr} | grep 'Name: ${clusterName}'"
|
||||
)
|
||||
|
||||
with subtest("Heap limit set correctly"):
|
||||
# Nodetool takes a while until it can display info
|
||||
cass0.wait_until_succeeds("nodetool info -p ${jmxPortStr}")
|
||||
cass0.succeed("${checkHeapLimitCommand}")
|
||||
|
||||
# Check cluster interaction
|
||||
with subtest("Bring up cluster"):
|
||||
cass1.wait_for_unit("cassandra.service")
|
||||
cass1.wait_until_succeeds(
|
||||
"nodetool -p ${jmxPortStr} ${jmxAuthArgs} status | egrep -c '^UN' | grep 2"
|
||||
)
|
||||
cass0.succeed("nodetool status -p ${jmxPortStr} --resolve-ip | egrep '^UN[[:space:]]+cass1'")
|
||||
'' + lib.optionalString testRemoteAuth ''
|
||||
with subtest("Remote authenticated jmx"):
|
||||
# Doesn't work if not enabled
|
||||
cass0.wait_until_succeeds("nc -z localhost ${jmxPortStr}")
|
||||
cass1.fail("nc -z 192.168.1.1 ${jmxPortStr}")
|
||||
cass1.fail("nodetool -p ${jmxPortStr} -h 192.168.1.1 status")
|
||||
|
||||
# Works if enabled
|
||||
cass1.wait_until_succeeds("nc -z localhost ${jmxPortStr}")
|
||||
cass0.succeed("nodetool -p ${jmxPortStr} -h 192.168.1.2 ${jmxAuthArgs} status")
|
||||
'' + ''
|
||||
with subtest("Break and fix node"):
|
||||
cass1.block()
|
||||
cass0.wait_until_succeeds(
|
||||
"nodetool status -p ${jmxPortStr} --resolve-ip | egrep -c '^DN[[:space:]]+cass1'"
|
||||
)
|
||||
cass0.succeed("nodetool status -p ${jmxPortStr} | egrep -c '^UN' | grep 1")
|
||||
cass1.unblock()
|
||||
cass1.wait_until_succeeds(
|
||||
"nodetool -p ${jmxPortStr} ${jmxAuthArgs} status | egrep -c '^UN' | grep 2"
|
||||
)
|
||||
cass0.succeed("nodetool status -p ${jmxPortStr} | egrep -c '^UN' | grep 2")
|
||||
|
||||
with subtest("Replace crashed node"):
|
||||
cass1.block() # .crash() waits until it's fully shutdown
|
||||
cass2.start()
|
||||
cass0.wait_until_fails(
|
||||
"nodetool status -p ${jmxPortStr} --resolve-ip | egrep '^UN[[:space:]]+cass1'"
|
||||
)
|
||||
|
||||
cass2.wait_for_unit("cassandra.service")
|
||||
cass0.wait_until_succeeds(
|
||||
"nodetool status -p ${jmxPortStr} --resolve-ip | egrep '^UN[[:space:]]+cass2'"
|
||||
)
|
||||
'';
|
||||
|
||||
passthru = {
|
||||
inherit testPackage;
|
||||
};
|
||||
})
|
||||
233
nixos/tests/ceph-multi-node.nix
Normal file
233
nixos/tests/ceph-multi-node.nix
Normal file
|
|
@ -0,0 +1,233 @@
|
|||
import ./make-test-python.nix ({pkgs, lib, ...}:
|
||||
|
||||
let
|
||||
cfg = {
|
||||
clusterId = "066ae264-2a5d-4729-8001-6ad265f50b03";
|
||||
monA = {
|
||||
name = "a";
|
||||
ip = "192.168.1.1";
|
||||
};
|
||||
osd0 = {
|
||||
name = "0";
|
||||
ip = "192.168.1.2";
|
||||
key = "AQBCEJNa3s8nHRAANvdsr93KqzBznuIWm2gOGg==";
|
||||
uuid = "55ba2294-3e24-478f-bee0-9dca4c231dd9";
|
||||
};
|
||||
osd1 = {
|
||||
name = "1";
|
||||
ip = "192.168.1.3";
|
||||
key = "AQBEEJNac00kExAAXEgy943BGyOpVH1LLlHafQ==";
|
||||
uuid = "5e97a838-85b6-43b0-8950-cb56d554d1e5";
|
||||
};
|
||||
osd2 = {
|
||||
name = "2";
|
||||
ip = "192.168.1.4";
|
||||
key = "AQAdyhZeIaUlARAAGRoidDAmS6Vkp546UFEf5w==";
|
||||
uuid = "ea999274-13d0-4dd5-9af9-ad25a324f72f";
|
||||
};
|
||||
};
|
||||
generateCephConfig = { daemonConfig }: {
|
||||
enable = true;
|
||||
global = {
|
||||
fsid = cfg.clusterId;
|
||||
monHost = cfg.monA.ip;
|
||||
monInitialMembers = cfg.monA.name;
|
||||
};
|
||||
} // daemonConfig;
|
||||
|
||||
generateHost = { pkgs, cephConfig, networkConfig, ... }: {
|
||||
virtualisation = {
|
||||
emptyDiskImages = [ 20480 ];
|
||||
vlans = [ 1 ];
|
||||
};
|
||||
|
||||
networking = networkConfig;
|
||||
|
||||
environment.systemPackages = with pkgs; [
|
||||
bash
|
||||
sudo
|
||||
ceph
|
||||
xfsprogs
|
||||
libressl.nc
|
||||
];
|
||||
|
||||
boot.kernelModules = [ "xfs" ];
|
||||
|
||||
services.ceph = cephConfig;
|
||||
};
|
||||
|
||||
networkMonA = {
|
||||
dhcpcd.enable = false;
|
||||
interfaces.eth1.ipv4.addresses = pkgs.lib.mkOverride 0 [
|
||||
{ address = cfg.monA.ip; prefixLength = 24; }
|
||||
];
|
||||
firewall = {
|
||||
allowedTCPPorts = [ 6789 3300 ];
|
||||
allowedTCPPortRanges = [ { from = 6800; to = 7300; } ];
|
||||
};
|
||||
};
|
||||
cephConfigMonA = generateCephConfig { daemonConfig = {
|
||||
mon = {
|
||||
enable = true;
|
||||
daemons = [ cfg.monA.name ];
|
||||
};
|
||||
mgr = {
|
||||
enable = true;
|
||||
daemons = [ cfg.monA.name ];
|
||||
};
|
||||
}; };
|
||||
|
||||
networkOsd = osd: {
|
||||
dhcpcd.enable = false;
|
||||
interfaces.eth1.ipv4.addresses = pkgs.lib.mkOverride 0 [
|
||||
{ address = osd.ip; prefixLength = 24; }
|
||||
];
|
||||
firewall = {
|
||||
allowedTCPPortRanges = [ { from = 6800; to = 7300; } ];
|
||||
};
|
||||
};
|
||||
|
||||
cephConfigOsd = osd: generateCephConfig { daemonConfig = {
|
||||
osd = {
|
||||
enable = true;
|
||||
daemons = [ osd.name ];
|
||||
};
|
||||
}; };
|
||||
|
||||
# Following deployment is based on the manual deployment described here:
|
||||
# https://docs.ceph.com/docs/master/install/manual-deployment/
|
||||
# For other ways to deploy a ceph cluster, look at the documentation at
|
||||
# https://docs.ceph.com/docs/master/
|
||||
testscript = { ... }: ''
|
||||
start_all()
|
||||
|
||||
monA.wait_for_unit("network.target")
|
||||
osd0.wait_for_unit("network.target")
|
||||
osd1.wait_for_unit("network.target")
|
||||
osd2.wait_for_unit("network.target")
|
||||
|
||||
# Bootstrap ceph-mon daemon
|
||||
monA.succeed(
|
||||
"sudo -u ceph ceph-authtool --create-keyring /tmp/ceph.mon.keyring --gen-key -n mon. --cap mon 'allow *'",
|
||||
"sudo -u ceph ceph-authtool --create-keyring /etc/ceph/ceph.client.admin.keyring --gen-key -n client.admin --cap mon 'allow *' --cap osd 'allow *' --cap mds 'allow *' --cap mgr 'allow *'",
|
||||
"sudo -u ceph ceph-authtool /tmp/ceph.mon.keyring --import-keyring /etc/ceph/ceph.client.admin.keyring",
|
||||
"monmaptool --create --add ${cfg.monA.name} ${cfg.monA.ip} --fsid ${cfg.clusterId} /tmp/monmap",
|
||||
"sudo -u ceph ceph-mon --mkfs -i ${cfg.monA.name} --monmap /tmp/monmap --keyring /tmp/ceph.mon.keyring",
|
||||
"sudo -u ceph mkdir -p /var/lib/ceph/mgr/ceph-${cfg.monA.name}/",
|
||||
"sudo -u ceph touch /var/lib/ceph/mon/ceph-${cfg.monA.name}/done",
|
||||
"systemctl start ceph-mon-${cfg.monA.name}",
|
||||
)
|
||||
monA.wait_for_unit("ceph-mon-${cfg.monA.name}")
|
||||
monA.succeed("ceph mon enable-msgr2")
|
||||
monA.succeed("ceph config set mon auth_allow_insecure_global_id_reclaim false")
|
||||
|
||||
# Can't check ceph status until a mon is up
|
||||
monA.succeed("ceph -s | grep 'mon: 1 daemons'")
|
||||
|
||||
# Start the ceph-mgr daemon, it has no deps and hardly any setup
|
||||
monA.succeed(
|
||||
"ceph auth get-or-create mgr.${cfg.monA.name} mon 'allow profile mgr' osd 'allow *' mds 'allow *' > /var/lib/ceph/mgr/ceph-${cfg.monA.name}/keyring",
|
||||
"systemctl start ceph-mgr-${cfg.monA.name}",
|
||||
)
|
||||
monA.wait_for_unit("ceph-mgr-a")
|
||||
monA.wait_until_succeeds("ceph -s | grep 'quorum ${cfg.monA.name}'")
|
||||
monA.wait_until_succeeds("ceph -s | grep 'mgr: ${cfg.monA.name}(active,'")
|
||||
|
||||
# Send the admin keyring to the OSD machines
|
||||
monA.succeed("cp /etc/ceph/ceph.client.admin.keyring /tmp/shared")
|
||||
osd0.succeed("cp /tmp/shared/ceph.client.admin.keyring /etc/ceph")
|
||||
osd1.succeed("cp /tmp/shared/ceph.client.admin.keyring /etc/ceph")
|
||||
osd2.succeed("cp /tmp/shared/ceph.client.admin.keyring /etc/ceph")
|
||||
|
||||
# Bootstrap OSDs
|
||||
osd0.succeed(
|
||||
"mkfs.xfs /dev/vdb",
|
||||
"mkdir -p /var/lib/ceph/osd/ceph-${cfg.osd0.name}",
|
||||
"mount /dev/vdb /var/lib/ceph/osd/ceph-${cfg.osd0.name}",
|
||||
"ceph-authtool --create-keyring /var/lib/ceph/osd/ceph-${cfg.osd0.name}/keyring --name osd.${cfg.osd0.name} --add-key ${cfg.osd0.key}",
|
||||
'echo \'{"cephx_secret": "${cfg.osd0.key}"}\' | ceph osd new ${cfg.osd0.uuid} -i -',
|
||||
)
|
||||
osd1.succeed(
|
||||
"mkfs.xfs /dev/vdb",
|
||||
"mkdir -p /var/lib/ceph/osd/ceph-${cfg.osd1.name}",
|
||||
"mount /dev/vdb /var/lib/ceph/osd/ceph-${cfg.osd1.name}",
|
||||
"ceph-authtool --create-keyring /var/lib/ceph/osd/ceph-${cfg.osd1.name}/keyring --name osd.${cfg.osd1.name} --add-key ${cfg.osd1.key}",
|
||||
'echo \'{"cephx_secret": "${cfg.osd1.key}"}\' | ceph osd new ${cfg.osd1.uuid} -i -',
|
||||
)
|
||||
osd2.succeed(
|
||||
"mkfs.xfs /dev/vdb",
|
||||
"mkdir -p /var/lib/ceph/osd/ceph-${cfg.osd2.name}",
|
||||
"mount /dev/vdb /var/lib/ceph/osd/ceph-${cfg.osd2.name}",
|
||||
"ceph-authtool --create-keyring /var/lib/ceph/osd/ceph-${cfg.osd2.name}/keyring --name osd.${cfg.osd2.name} --add-key ${cfg.osd2.key}",
|
||||
'echo \'{"cephx_secret": "${cfg.osd2.key}"}\' | ceph osd new ${cfg.osd2.uuid} -i -',
|
||||
)
|
||||
|
||||
# Initialize the OSDs with regular filestore
|
||||
osd0.succeed(
|
||||
"ceph-osd -i ${cfg.osd0.name} --mkfs --osd-uuid ${cfg.osd0.uuid}",
|
||||
"chown -R ceph:ceph /var/lib/ceph/osd",
|
||||
"systemctl start ceph-osd-${cfg.osd0.name}",
|
||||
)
|
||||
osd1.succeed(
|
||||
"ceph-osd -i ${cfg.osd1.name} --mkfs --osd-uuid ${cfg.osd1.uuid}",
|
||||
"chown -R ceph:ceph /var/lib/ceph/osd",
|
||||
"systemctl start ceph-osd-${cfg.osd1.name}",
|
||||
)
|
||||
osd2.succeed(
|
||||
"ceph-osd -i ${cfg.osd2.name} --mkfs --osd-uuid ${cfg.osd2.uuid}",
|
||||
"chown -R ceph:ceph /var/lib/ceph/osd",
|
||||
"systemctl start ceph-osd-${cfg.osd2.name}",
|
||||
)
|
||||
monA.wait_until_succeeds("ceph osd stat | grep -e '3 osds: 3 up[^,]*, 3 in'")
|
||||
monA.wait_until_succeeds("ceph -s | grep 'mgr: ${cfg.monA.name}(active,'")
|
||||
monA.wait_until_succeeds("ceph -s | grep 'HEALTH_OK'")
|
||||
|
||||
monA.succeed(
|
||||
"ceph osd pool create multi-node-test 32 32",
|
||||
"ceph osd pool ls | grep 'multi-node-test'",
|
||||
"ceph osd pool rename multi-node-test multi-node-other-test",
|
||||
"ceph osd pool ls | grep 'multi-node-other-test'",
|
||||
)
|
||||
monA.wait_until_succeeds("ceph -s | grep '2 pools, 33 pgs'")
|
||||
monA.succeed("ceph osd pool set multi-node-other-test size 2")
|
||||
monA.wait_until_succeeds("ceph -s | grep 'HEALTH_OK'")
|
||||
monA.wait_until_succeeds("ceph -s | grep '33 active+clean'")
|
||||
monA.fail(
|
||||
"ceph osd pool ls | grep 'multi-node-test'",
|
||||
"ceph osd pool delete multi-node-other-test multi-node-other-test --yes-i-really-really-mean-it",
|
||||
)
|
||||
|
||||
# Shut down ceph on all machines in a very unpolite way
|
||||
monA.crash()
|
||||
osd0.crash()
|
||||
osd1.crash()
|
||||
osd2.crash()
|
||||
|
||||
# Start it up
|
||||
osd0.start()
|
||||
osd1.start()
|
||||
osd2.start()
|
||||
monA.start()
|
||||
|
||||
# Ensure the cluster comes back up again
|
||||
monA.succeed("ceph -s | grep 'mon: 1 daemons'")
|
||||
monA.wait_until_succeeds("ceph -s | grep 'quorum ${cfg.monA.name}'")
|
||||
monA.wait_until_succeeds("ceph osd stat | grep -e '3 osds: 3 up[^,]*, 3 in'")
|
||||
monA.wait_until_succeeds("ceph -s | grep 'mgr: ${cfg.monA.name}(active,'")
|
||||
monA.wait_until_succeeds("ceph -s | grep 'HEALTH_OK'")
|
||||
'';
|
||||
in {
|
||||
name = "basic-multi-node-ceph-cluster";
|
||||
meta = with pkgs.lib.maintainers; {
|
||||
maintainers = [ lejonet ];
|
||||
};
|
||||
|
||||
nodes = {
|
||||
monA = generateHost { pkgs = pkgs; cephConfig = cephConfigMonA; networkConfig = networkMonA; };
|
||||
osd0 = generateHost { pkgs = pkgs; cephConfig = cephConfigOsd cfg.osd0; networkConfig = networkOsd cfg.osd0; };
|
||||
osd1 = generateHost { pkgs = pkgs; cephConfig = cephConfigOsd cfg.osd1; networkConfig = networkOsd cfg.osd1; };
|
||||
osd2 = generateHost { pkgs = pkgs; cephConfig = cephConfigOsd cfg.osd2; networkConfig = networkOsd cfg.osd2; };
|
||||
};
|
||||
|
||||
testScript = testscript;
|
||||
})
|
||||
196
nixos/tests/ceph-single-node-bluestore.nix
Normal file
196
nixos/tests/ceph-single-node-bluestore.nix
Normal file
|
|
@ -0,0 +1,196 @@
|
|||
import ./make-test-python.nix ({pkgs, lib, ...}:
|
||||
|
||||
let
|
||||
cfg = {
|
||||
clusterId = "066ae264-2a5d-4729-8001-6ad265f50b03";
|
||||
monA = {
|
||||
name = "a";
|
||||
ip = "192.168.1.1";
|
||||
};
|
||||
osd0 = {
|
||||
name = "0";
|
||||
key = "AQBCEJNa3s8nHRAANvdsr93KqzBznuIWm2gOGg==";
|
||||
uuid = "55ba2294-3e24-478f-bee0-9dca4c231dd9";
|
||||
};
|
||||
osd1 = {
|
||||
name = "1";
|
||||
key = "AQBEEJNac00kExAAXEgy943BGyOpVH1LLlHafQ==";
|
||||
uuid = "5e97a838-85b6-43b0-8950-cb56d554d1e5";
|
||||
};
|
||||
osd2 = {
|
||||
name = "2";
|
||||
key = "AQAdyhZeIaUlARAAGRoidDAmS6Vkp546UFEf5w==";
|
||||
uuid = "ea999274-13d0-4dd5-9af9-ad25a324f72f";
|
||||
};
|
||||
};
|
||||
generateCephConfig = { daemonConfig }: {
|
||||
enable = true;
|
||||
global = {
|
||||
fsid = cfg.clusterId;
|
||||
monHost = cfg.monA.ip;
|
||||
monInitialMembers = cfg.monA.name;
|
||||
};
|
||||
} // daemonConfig;
|
||||
|
||||
generateHost = { pkgs, cephConfig, networkConfig, ... }: {
|
||||
virtualisation = {
|
||||
emptyDiskImages = [ 20480 20480 20480 ];
|
||||
vlans = [ 1 ];
|
||||
};
|
||||
|
||||
networking = networkConfig;
|
||||
|
||||
environment.systemPackages = with pkgs; [
|
||||
bash
|
||||
sudo
|
||||
ceph
|
||||
xfsprogs
|
||||
];
|
||||
|
||||
boot.kernelModules = [ "xfs" ];
|
||||
|
||||
services.ceph = cephConfig;
|
||||
};
|
||||
|
||||
networkMonA = {
|
||||
dhcpcd.enable = false;
|
||||
interfaces.eth1.ipv4.addresses = pkgs.lib.mkOverride 0 [
|
||||
{ address = cfg.monA.ip; prefixLength = 24; }
|
||||
];
|
||||
};
|
||||
cephConfigMonA = generateCephConfig { daemonConfig = {
|
||||
mon = {
|
||||
enable = true;
|
||||
daemons = [ cfg.monA.name ];
|
||||
};
|
||||
mgr = {
|
||||
enable = true;
|
||||
daemons = [ cfg.monA.name ];
|
||||
};
|
||||
osd = {
|
||||
enable = true;
|
||||
daemons = [ cfg.osd0.name cfg.osd1.name cfg.osd2.name ];
|
||||
};
|
||||
}; };
|
||||
|
||||
# Following deployment is based on the manual deployment described here:
|
||||
# https://docs.ceph.com/docs/master/install/manual-deployment/
|
||||
# For other ways to deploy a ceph cluster, look at the documentation at
|
||||
# https://docs.ceph.com/docs/master/
|
||||
testscript = { ... }: ''
|
||||
start_all()
|
||||
|
||||
monA.wait_for_unit("network.target")
|
||||
|
||||
# Bootstrap ceph-mon daemon
|
||||
monA.succeed(
|
||||
"sudo -u ceph ceph-authtool --create-keyring /tmp/ceph.mon.keyring --gen-key -n mon. --cap mon 'allow *'",
|
||||
"sudo -u ceph ceph-authtool --create-keyring /etc/ceph/ceph.client.admin.keyring --gen-key -n client.admin --cap mon 'allow *' --cap osd 'allow *' --cap mds 'allow *' --cap mgr 'allow *'",
|
||||
"sudo -u ceph ceph-authtool /tmp/ceph.mon.keyring --import-keyring /etc/ceph/ceph.client.admin.keyring",
|
||||
"monmaptool --create --add ${cfg.monA.name} ${cfg.monA.ip} --fsid ${cfg.clusterId} /tmp/monmap",
|
||||
"sudo -u ceph ceph-mon --mkfs -i ${cfg.monA.name} --monmap /tmp/monmap --keyring /tmp/ceph.mon.keyring",
|
||||
"sudo -u ceph touch /var/lib/ceph/mon/ceph-${cfg.monA.name}/done",
|
||||
"systemctl start ceph-mon-${cfg.monA.name}",
|
||||
)
|
||||
monA.wait_for_unit("ceph-mon-${cfg.monA.name}")
|
||||
monA.succeed("ceph mon enable-msgr2")
|
||||
monA.succeed("ceph config set mon auth_allow_insecure_global_id_reclaim false")
|
||||
|
||||
# Can't check ceph status until a mon is up
|
||||
monA.succeed("ceph -s | grep 'mon: 1 daemons'")
|
||||
|
||||
# Start the ceph-mgr daemon, after copying in the keyring
|
||||
monA.succeed(
|
||||
"sudo -u ceph mkdir -p /var/lib/ceph/mgr/ceph-${cfg.monA.name}/",
|
||||
"ceph auth get-or-create mgr.${cfg.monA.name} mon 'allow profile mgr' osd 'allow *' mds 'allow *' > /var/lib/ceph/mgr/ceph-${cfg.monA.name}/keyring",
|
||||
"systemctl start ceph-mgr-${cfg.monA.name}",
|
||||
)
|
||||
monA.wait_for_unit("ceph-mgr-a")
|
||||
monA.wait_until_succeeds("ceph -s | grep 'quorum ${cfg.monA.name}'")
|
||||
monA.wait_until_succeeds("ceph -s | grep 'mgr: ${cfg.monA.name}(active,'")
|
||||
|
||||
# Bootstrap OSDs
|
||||
monA.succeed(
|
||||
"mkdir -p /var/lib/ceph/osd/ceph-${cfg.osd0.name}",
|
||||
"echo bluestore > /var/lib/ceph/osd/ceph-${cfg.osd0.name}/type",
|
||||
"ln -sf /dev/vdb /var/lib/ceph/osd/ceph-${cfg.osd0.name}/block",
|
||||
"mkdir -p /var/lib/ceph/osd/ceph-${cfg.osd1.name}",
|
||||
"echo bluestore > /var/lib/ceph/osd/ceph-${cfg.osd1.name}/type",
|
||||
"ln -sf /dev/vdc /var/lib/ceph/osd/ceph-${cfg.osd1.name}/block",
|
||||
"mkdir -p /var/lib/ceph/osd/ceph-${cfg.osd2.name}",
|
||||
"echo bluestore > /var/lib/ceph/osd/ceph-${cfg.osd2.name}/type",
|
||||
"ln -sf /dev/vdd /var/lib/ceph/osd/ceph-${cfg.osd2.name}/block",
|
||||
"ceph-authtool --create-keyring /var/lib/ceph/osd/ceph-${cfg.osd0.name}/keyring --name osd.${cfg.osd0.name} --add-key ${cfg.osd0.key}",
|
||||
"ceph-authtool --create-keyring /var/lib/ceph/osd/ceph-${cfg.osd1.name}/keyring --name osd.${cfg.osd1.name} --add-key ${cfg.osd1.key}",
|
||||
"ceph-authtool --create-keyring /var/lib/ceph/osd/ceph-${cfg.osd2.name}/keyring --name osd.${cfg.osd2.name} --add-key ${cfg.osd2.key}",
|
||||
'echo \'{"cephx_secret": "${cfg.osd0.key}"}\' | ceph osd new ${cfg.osd0.uuid} -i -',
|
||||
'echo \'{"cephx_secret": "${cfg.osd1.key}"}\' | ceph osd new ${cfg.osd1.uuid} -i -',
|
||||
'echo \'{"cephx_secret": "${cfg.osd2.key}"}\' | ceph osd new ${cfg.osd2.uuid} -i -',
|
||||
)
|
||||
|
||||
# Initialize the OSDs with regular filestore
|
||||
monA.succeed(
|
||||
"ceph-osd -i ${cfg.osd0.name} --mkfs --osd-uuid ${cfg.osd0.uuid}",
|
||||
"ceph-osd -i ${cfg.osd1.name} --mkfs --osd-uuid ${cfg.osd1.uuid}",
|
||||
"ceph-osd -i ${cfg.osd2.name} --mkfs --osd-uuid ${cfg.osd2.uuid}",
|
||||
"chown -R ceph:ceph /var/lib/ceph/osd",
|
||||
"systemctl start ceph-osd-${cfg.osd0.name}",
|
||||
"systemctl start ceph-osd-${cfg.osd1.name}",
|
||||
"systemctl start ceph-osd-${cfg.osd2.name}",
|
||||
)
|
||||
monA.wait_until_succeeds("ceph osd stat | grep -e '3 osds: 3 up[^,]*, 3 in'")
|
||||
monA.wait_until_succeeds("ceph -s | grep 'mgr: ${cfg.monA.name}(active,'")
|
||||
monA.wait_until_succeeds("ceph -s | grep 'HEALTH_OK'")
|
||||
|
||||
monA.succeed(
|
||||
"ceph osd pool create single-node-test 32 32",
|
||||
"ceph osd pool ls | grep 'single-node-test'",
|
||||
"ceph osd pool rename single-node-test single-node-other-test",
|
||||
"ceph osd pool ls | grep 'single-node-other-test'",
|
||||
)
|
||||
monA.wait_until_succeeds("ceph -s | grep '2 pools, 33 pgs'")
|
||||
monA.succeed(
|
||||
"ceph osd getcrushmap -o crush",
|
||||
"crushtool -d crush -o decrushed",
|
||||
"sed 's/step chooseleaf firstn 0 type host/step chooseleaf firstn 0 type osd/' decrushed > modcrush",
|
||||
"crushtool -c modcrush -o recrushed",
|
||||
"ceph osd setcrushmap -i recrushed",
|
||||
"ceph osd pool set single-node-other-test size 2",
|
||||
)
|
||||
monA.wait_until_succeeds("ceph -s | grep 'HEALTH_OK'")
|
||||
monA.wait_until_succeeds("ceph -s | grep '33 active+clean'")
|
||||
monA.fail(
|
||||
"ceph osd pool ls | grep 'multi-node-test'",
|
||||
"ceph osd pool delete single-node-other-test single-node-other-test --yes-i-really-really-mean-it",
|
||||
)
|
||||
|
||||
# Shut down ceph by stopping ceph.target.
|
||||
monA.succeed("systemctl stop ceph.target")
|
||||
|
||||
# Start it up
|
||||
monA.succeed("systemctl start ceph.target")
|
||||
monA.wait_for_unit("ceph-mon-${cfg.monA.name}")
|
||||
monA.wait_for_unit("ceph-mgr-${cfg.monA.name}")
|
||||
monA.wait_for_unit("ceph-osd-${cfg.osd0.name}")
|
||||
monA.wait_for_unit("ceph-osd-${cfg.osd1.name}")
|
||||
monA.wait_for_unit("ceph-osd-${cfg.osd2.name}")
|
||||
|
||||
# Ensure the cluster comes back up again
|
||||
monA.succeed("ceph -s | grep 'mon: 1 daemons'")
|
||||
monA.wait_until_succeeds("ceph -s | grep 'quorum ${cfg.monA.name}'")
|
||||
monA.wait_until_succeeds("ceph osd stat | grep -e '3 osds: 3 up[^,]*, 3 in'")
|
||||
monA.wait_until_succeeds("ceph -s | grep 'mgr: ${cfg.monA.name}(active,'")
|
||||
monA.wait_until_succeeds("ceph -s | grep 'HEALTH_OK'")
|
||||
'';
|
||||
in {
|
||||
name = "basic-single-node-ceph-cluster-bluestore";
|
||||
meta = with pkgs.lib.maintainers; {
|
||||
maintainers = [ lukegb ];
|
||||
};
|
||||
|
||||
nodes = {
|
||||
monA = generateHost { pkgs = pkgs; cephConfig = cephConfigMonA; networkConfig = networkMonA; };
|
||||
};
|
||||
|
||||
testScript = testscript;
|
||||
})
|
||||
196
nixos/tests/ceph-single-node.nix
Normal file
196
nixos/tests/ceph-single-node.nix
Normal file
|
|
@ -0,0 +1,196 @@
|
|||
import ./make-test-python.nix ({pkgs, lib, ...}:
|
||||
|
||||
let
|
||||
cfg = {
|
||||
clusterId = "066ae264-2a5d-4729-8001-6ad265f50b03";
|
||||
monA = {
|
||||
name = "a";
|
||||
ip = "192.168.1.1";
|
||||
};
|
||||
osd0 = {
|
||||
name = "0";
|
||||
key = "AQBCEJNa3s8nHRAANvdsr93KqzBznuIWm2gOGg==";
|
||||
uuid = "55ba2294-3e24-478f-bee0-9dca4c231dd9";
|
||||
};
|
||||
osd1 = {
|
||||
name = "1";
|
||||
key = "AQBEEJNac00kExAAXEgy943BGyOpVH1LLlHafQ==";
|
||||
uuid = "5e97a838-85b6-43b0-8950-cb56d554d1e5";
|
||||
};
|
||||
osd2 = {
|
||||
name = "2";
|
||||
key = "AQAdyhZeIaUlARAAGRoidDAmS6Vkp546UFEf5w==";
|
||||
uuid = "ea999274-13d0-4dd5-9af9-ad25a324f72f";
|
||||
};
|
||||
};
|
||||
generateCephConfig = { daemonConfig }: {
|
||||
enable = true;
|
||||
global = {
|
||||
fsid = cfg.clusterId;
|
||||
monHost = cfg.monA.ip;
|
||||
monInitialMembers = cfg.monA.name;
|
||||
};
|
||||
} // daemonConfig;
|
||||
|
||||
generateHost = { pkgs, cephConfig, networkConfig, ... }: {
|
||||
virtualisation = {
|
||||
emptyDiskImages = [ 20480 20480 20480 ];
|
||||
vlans = [ 1 ];
|
||||
};
|
||||
|
||||
networking = networkConfig;
|
||||
|
||||
environment.systemPackages = with pkgs; [
|
||||
bash
|
||||
sudo
|
||||
ceph
|
||||
xfsprogs
|
||||
];
|
||||
|
||||
boot.kernelModules = [ "xfs" ];
|
||||
|
||||
services.ceph = cephConfig;
|
||||
};
|
||||
|
||||
networkMonA = {
|
||||
dhcpcd.enable = false;
|
||||
interfaces.eth1.ipv4.addresses = pkgs.lib.mkOverride 0 [
|
||||
{ address = cfg.monA.ip; prefixLength = 24; }
|
||||
];
|
||||
};
|
||||
cephConfigMonA = generateCephConfig { daemonConfig = {
|
||||
mon = {
|
||||
enable = true;
|
||||
daemons = [ cfg.monA.name ];
|
||||
};
|
||||
mgr = {
|
||||
enable = true;
|
||||
daemons = [ cfg.monA.name ];
|
||||
};
|
||||
osd = {
|
||||
enable = true;
|
||||
daemons = [ cfg.osd0.name cfg.osd1.name cfg.osd2.name ];
|
||||
};
|
||||
}; };
|
||||
|
||||
# Following deployment is based on the manual deployment described here:
|
||||
# https://docs.ceph.com/docs/master/install/manual-deployment/
|
||||
# For other ways to deploy a ceph cluster, look at the documentation at
|
||||
# https://docs.ceph.com/docs/master/
|
||||
testscript = { ... }: ''
|
||||
start_all()
|
||||
|
||||
monA.wait_for_unit("network.target")
|
||||
|
||||
# Bootstrap ceph-mon daemon
|
||||
monA.succeed(
|
||||
"sudo -u ceph ceph-authtool --create-keyring /tmp/ceph.mon.keyring --gen-key -n mon. --cap mon 'allow *'",
|
||||
"sudo -u ceph ceph-authtool --create-keyring /etc/ceph/ceph.client.admin.keyring --gen-key -n client.admin --cap mon 'allow *' --cap osd 'allow *' --cap mds 'allow *' --cap mgr 'allow *'",
|
||||
"sudo -u ceph ceph-authtool /tmp/ceph.mon.keyring --import-keyring /etc/ceph/ceph.client.admin.keyring",
|
||||
"monmaptool --create --add ${cfg.monA.name} ${cfg.monA.ip} --fsid ${cfg.clusterId} /tmp/monmap",
|
||||
"sudo -u ceph ceph-mon --mkfs -i ${cfg.monA.name} --monmap /tmp/monmap --keyring /tmp/ceph.mon.keyring",
|
||||
"sudo -u ceph touch /var/lib/ceph/mon/ceph-${cfg.monA.name}/done",
|
||||
"systemctl start ceph-mon-${cfg.monA.name}",
|
||||
)
|
||||
monA.wait_for_unit("ceph-mon-${cfg.monA.name}")
|
||||
monA.succeed("ceph mon enable-msgr2")
|
||||
monA.succeed("ceph config set mon auth_allow_insecure_global_id_reclaim false")
|
||||
|
||||
# Can't check ceph status until a mon is up
|
||||
monA.succeed("ceph -s | grep 'mon: 1 daemons'")
|
||||
|
||||
# Start the ceph-mgr daemon, after copying in the keyring
|
||||
monA.succeed(
|
||||
"sudo -u ceph mkdir -p /var/lib/ceph/mgr/ceph-${cfg.monA.name}/",
|
||||
"ceph auth get-or-create mgr.${cfg.monA.name} mon 'allow profile mgr' osd 'allow *' mds 'allow *' > /var/lib/ceph/mgr/ceph-${cfg.monA.name}/keyring",
|
||||
"systemctl start ceph-mgr-${cfg.monA.name}",
|
||||
)
|
||||
monA.wait_for_unit("ceph-mgr-a")
|
||||
monA.wait_until_succeeds("ceph -s | grep 'quorum ${cfg.monA.name}'")
|
||||
monA.wait_until_succeeds("ceph -s | grep 'mgr: ${cfg.monA.name}(active,'")
|
||||
|
||||
# Bootstrap OSDs
|
||||
monA.succeed(
|
||||
"mkfs.xfs /dev/vdb",
|
||||
"mkfs.xfs /dev/vdc",
|
||||
"mkfs.xfs /dev/vdd",
|
||||
"mkdir -p /var/lib/ceph/osd/ceph-${cfg.osd0.name}",
|
||||
"mount /dev/vdb /var/lib/ceph/osd/ceph-${cfg.osd0.name}",
|
||||
"mkdir -p /var/lib/ceph/osd/ceph-${cfg.osd1.name}",
|
||||
"mount /dev/vdc /var/lib/ceph/osd/ceph-${cfg.osd1.name}",
|
||||
"mkdir -p /var/lib/ceph/osd/ceph-${cfg.osd2.name}",
|
||||
"mount /dev/vdd /var/lib/ceph/osd/ceph-${cfg.osd2.name}",
|
||||
"ceph-authtool --create-keyring /var/lib/ceph/osd/ceph-${cfg.osd0.name}/keyring --name osd.${cfg.osd0.name} --add-key ${cfg.osd0.key}",
|
||||
"ceph-authtool --create-keyring /var/lib/ceph/osd/ceph-${cfg.osd1.name}/keyring --name osd.${cfg.osd1.name} --add-key ${cfg.osd1.key}",
|
||||
"ceph-authtool --create-keyring /var/lib/ceph/osd/ceph-${cfg.osd2.name}/keyring --name osd.${cfg.osd2.name} --add-key ${cfg.osd2.key}",
|
||||
'echo \'{"cephx_secret": "${cfg.osd0.key}"}\' | ceph osd new ${cfg.osd0.uuid} -i -',
|
||||
'echo \'{"cephx_secret": "${cfg.osd1.key}"}\' | ceph osd new ${cfg.osd1.uuid} -i -',
|
||||
'echo \'{"cephx_secret": "${cfg.osd2.key}"}\' | ceph osd new ${cfg.osd2.uuid} -i -',
|
||||
)
|
||||
|
||||
# Initialize the OSDs with regular filestore
|
||||
monA.succeed(
|
||||
"ceph-osd -i ${cfg.osd0.name} --mkfs --osd-uuid ${cfg.osd0.uuid}",
|
||||
"ceph-osd -i ${cfg.osd1.name} --mkfs --osd-uuid ${cfg.osd1.uuid}",
|
||||
"ceph-osd -i ${cfg.osd2.name} --mkfs --osd-uuid ${cfg.osd2.uuid}",
|
||||
"chown -R ceph:ceph /var/lib/ceph/osd",
|
||||
"systemctl start ceph-osd-${cfg.osd0.name}",
|
||||
"systemctl start ceph-osd-${cfg.osd1.name}",
|
||||
"systemctl start ceph-osd-${cfg.osd2.name}",
|
||||
)
|
||||
monA.wait_until_succeeds("ceph osd stat | grep -e '3 osds: 3 up[^,]*, 3 in'")
|
||||
monA.wait_until_succeeds("ceph -s | grep 'mgr: ${cfg.monA.name}(active,'")
|
||||
monA.wait_until_succeeds("ceph -s | grep 'HEALTH_OK'")
|
||||
|
||||
monA.succeed(
|
||||
"ceph osd pool create single-node-test 32 32",
|
||||
"ceph osd pool ls | grep 'single-node-test'",
|
||||
"ceph osd pool rename single-node-test single-node-other-test",
|
||||
"ceph osd pool ls | grep 'single-node-other-test'",
|
||||
)
|
||||
monA.wait_until_succeeds("ceph -s | grep '2 pools, 33 pgs'")
|
||||
monA.succeed(
|
||||
"ceph osd getcrushmap -o crush",
|
||||
"crushtool -d crush -o decrushed",
|
||||
"sed 's/step chooseleaf firstn 0 type host/step chooseleaf firstn 0 type osd/' decrushed > modcrush",
|
||||
"crushtool -c modcrush -o recrushed",
|
||||
"ceph osd setcrushmap -i recrushed",
|
||||
"ceph osd pool set single-node-other-test size 2",
|
||||
)
|
||||
monA.wait_until_succeeds("ceph -s | grep 'HEALTH_OK'")
|
||||
monA.wait_until_succeeds("ceph -s | grep '33 active+clean'")
|
||||
monA.fail(
|
||||
"ceph osd pool ls | grep 'multi-node-test'",
|
||||
"ceph osd pool delete single-node-other-test single-node-other-test --yes-i-really-really-mean-it",
|
||||
)
|
||||
|
||||
# Shut down ceph by stopping ceph.target.
|
||||
monA.succeed("systemctl stop ceph.target")
|
||||
|
||||
# Start it up
|
||||
monA.succeed("systemctl start ceph.target")
|
||||
monA.wait_for_unit("ceph-mon-${cfg.monA.name}")
|
||||
monA.wait_for_unit("ceph-mgr-${cfg.monA.name}")
|
||||
monA.wait_for_unit("ceph-osd-${cfg.osd0.name}")
|
||||
monA.wait_for_unit("ceph-osd-${cfg.osd1.name}")
|
||||
monA.wait_for_unit("ceph-osd-${cfg.osd2.name}")
|
||||
|
||||
# Ensure the cluster comes back up again
|
||||
monA.succeed("ceph -s | grep 'mon: 1 daemons'")
|
||||
monA.wait_until_succeeds("ceph -s | grep 'quorum ${cfg.monA.name}'")
|
||||
monA.wait_until_succeeds("ceph osd stat | grep -e '3 osds: 3 up[^,]*, 3 in'")
|
||||
monA.wait_until_succeeds("ceph -s | grep 'mgr: ${cfg.monA.name}(active,'")
|
||||
monA.wait_until_succeeds("ceph -s | grep 'HEALTH_OK'")
|
||||
'';
|
||||
in {
|
||||
name = "basic-single-node-ceph-cluster";
|
||||
meta = with pkgs.lib.maintainers; {
|
||||
maintainers = [ lejonet johanot ];
|
||||
};
|
||||
|
||||
nodes = {
|
||||
monA = generateHost { pkgs = pkgs; cephConfig = cephConfigMonA; networkConfig = networkMonA; };
|
||||
};
|
||||
|
||||
testScript = testscript;
|
||||
})
|
||||
155
nixos/tests/certmgr.nix
Normal file
155
nixos/tests/certmgr.nix
Normal file
|
|
@ -0,0 +1,155 @@
|
|||
{ system ? builtins.currentSystem,
|
||||
config ? {},
|
||||
pkgs ? import ../.. { inherit system config; }
|
||||
}:
|
||||
|
||||
with import ../lib/testing-python.nix { inherit system pkgs; };
|
||||
let
|
||||
mkSpec = { host, service ? null, action }: {
|
||||
inherit action;
|
||||
authority = {
|
||||
file = {
|
||||
group = "nginx";
|
||||
owner = "nginx";
|
||||
path = "/var/ssl/${host}-ca.pem";
|
||||
};
|
||||
label = "www_ca";
|
||||
profile = "three-month";
|
||||
remote = "localhost:8888";
|
||||
};
|
||||
certificate = {
|
||||
group = "nginx";
|
||||
owner = "nginx";
|
||||
path = "/var/ssl/${host}-cert.pem";
|
||||
};
|
||||
private_key = {
|
||||
group = "nginx";
|
||||
mode = "0600";
|
||||
owner = "nginx";
|
||||
path = "/var/ssl/${host}-key.pem";
|
||||
};
|
||||
request = {
|
||||
CN = host;
|
||||
hosts = [ host "www.${host}" ];
|
||||
key = {
|
||||
algo = "rsa";
|
||||
size = 2048;
|
||||
};
|
||||
names = [
|
||||
{
|
||||
C = "US";
|
||||
L = "San Francisco";
|
||||
O = "Example, LLC";
|
||||
ST = "CA";
|
||||
}
|
||||
];
|
||||
};
|
||||
inherit service;
|
||||
};
|
||||
|
||||
mkCertmgrTest = { svcManager, specs, testScript }: makeTest {
|
||||
name = "certmgr-" + svcManager;
|
||||
nodes = {
|
||||
machine = { config, lib, pkgs, ... }: {
|
||||
networking.firewall.allowedTCPPorts = with config.services; [ cfssl.port certmgr.metricsPort ];
|
||||
networking.extraHosts = "127.0.0.1 imp.example.org decl.example.org";
|
||||
|
||||
services.cfssl.enable = true;
|
||||
systemd.services.cfssl.after = [ "cfssl-init.service" "networking.target" ];
|
||||
|
||||
systemd.tmpfiles.rules = [ "d /var/ssl 777 root root" ];
|
||||
|
||||
systemd.services.cfssl-init = {
|
||||
description = "Initialize the cfssl CA";
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
serviceConfig = {
|
||||
User = "cfssl";
|
||||
Type = "oneshot";
|
||||
WorkingDirectory = config.services.cfssl.dataDir;
|
||||
};
|
||||
script = ''
|
||||
${pkgs.cfssl}/bin/cfssl genkey -initca ${pkgs.writeText "ca.json" (builtins.toJSON {
|
||||
hosts = [ "ca.example.com" ];
|
||||
key = {
|
||||
algo = "rsa"; size = 4096; };
|
||||
names = [
|
||||
{
|
||||
C = "US";
|
||||
L = "San Francisco";
|
||||
O = "Internet Widgets, LLC";
|
||||
OU = "Certificate Authority";
|
||||
ST = "California";
|
||||
}
|
||||
];
|
||||
})} | ${pkgs.cfssl}/bin/cfssljson -bare ca
|
||||
'';
|
||||
};
|
||||
|
||||
services.nginx = {
|
||||
enable = true;
|
||||
virtualHosts = lib.mkMerge (map (host: {
|
||||
${host} = {
|
||||
sslCertificate = "/var/ssl/${host}-cert.pem";
|
||||
sslCertificateKey = "/var/ssl/${host}-key.pem";
|
||||
extraConfig = ''
|
||||
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
|
||||
'';
|
||||
onlySSL = true;
|
||||
serverName = host;
|
||||
root = pkgs.writeTextDir "index.html" "It works!";
|
||||
};
|
||||
}) [ "imp.example.org" "decl.example.org" ]);
|
||||
};
|
||||
|
||||
systemd.services.nginx.wantedBy = lib.mkForce [];
|
||||
|
||||
systemd.services.certmgr.after = [ "cfssl.service" ];
|
||||
services.certmgr = {
|
||||
enable = true;
|
||||
inherit svcManager;
|
||||
inherit specs;
|
||||
};
|
||||
|
||||
};
|
||||
};
|
||||
inherit testScript;
|
||||
};
|
||||
in
|
||||
{
|
||||
systemd = mkCertmgrTest {
|
||||
svcManager = "systemd";
|
||||
specs = {
|
||||
decl = mkSpec { host = "decl.example.org"; service = "nginx"; action ="restart"; };
|
||||
imp = toString (pkgs.writeText "test.json" (builtins.toJSON (
|
||||
mkSpec { host = "imp.example.org"; service = "nginx"; action = "restart"; }
|
||||
)));
|
||||
};
|
||||
testScript = ''
|
||||
machine.wait_for_unit("cfssl.service")
|
||||
machine.wait_until_succeeds("ls /var/ssl/decl.example.org-ca.pem")
|
||||
machine.wait_until_succeeds("ls /var/ssl/decl.example.org-key.pem")
|
||||
machine.wait_until_succeeds("ls /var/ssl/decl.example.org-cert.pem")
|
||||
machine.wait_until_succeeds("ls /var/ssl/imp.example.org-ca.pem")
|
||||
machine.wait_until_succeeds("ls /var/ssl/imp.example.org-key.pem")
|
||||
machine.wait_until_succeeds("ls /var/ssl/imp.example.org-cert.pem")
|
||||
machine.wait_for_unit("nginx.service")
|
||||
assert 1 < int(machine.succeed('journalctl -u nginx | grep "Starting Nginx" | wc -l'))
|
||||
machine.succeed("curl --cacert /var/ssl/imp.example.org-ca.pem https://imp.example.org")
|
||||
machine.succeed(
|
||||
"curl --cacert /var/ssl/decl.example.org-ca.pem https://decl.example.org"
|
||||
)
|
||||
'';
|
||||
};
|
||||
|
||||
command = mkCertmgrTest {
|
||||
svcManager = "command";
|
||||
specs = {
|
||||
test = mkSpec { host = "command.example.org"; action = "touch /tmp/command.executed"; };
|
||||
};
|
||||
testScript = ''
|
||||
machine.wait_for_unit("cfssl.service")
|
||||
machine.wait_until_succeeds("stat /tmp/command.executed")
|
||||
'';
|
||||
};
|
||||
|
||||
}
|
||||
67
nixos/tests/cfssl.nix
Normal file
67
nixos/tests/cfssl.nix
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
import ./make-test-python.nix ({ pkgs, ...} : {
|
||||
name = "cfssl";
|
||||
|
||||
nodes.machine = { config, lib, pkgs, ... }:
|
||||
{
|
||||
networking.firewall.allowedTCPPorts = [ config.services.cfssl.port ];
|
||||
|
||||
services.cfssl.enable = true;
|
||||
systemd.services.cfssl.after = [ "cfssl-init.service" ];
|
||||
|
||||
systemd.services.cfssl-init = {
|
||||
description = "Initialize the cfssl CA";
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
serviceConfig = {
|
||||
User = "cfssl";
|
||||
Type = "oneshot";
|
||||
WorkingDirectory = config.services.cfssl.dataDir;
|
||||
};
|
||||
script = with pkgs; ''
|
||||
${cfssl}/bin/cfssl genkey -initca ${pkgs.writeText "ca.json" (builtins.toJSON {
|
||||
hosts = [ "ca.example.com" ];
|
||||
key = {
|
||||
algo = "rsa"; size = 4096; };
|
||||
names = [
|
||||
{
|
||||
C = "US";
|
||||
L = "San Francisco";
|
||||
O = "Internet Widgets, LLC";
|
||||
OU = "Certificate Authority";
|
||||
ST = "California";
|
||||
}
|
||||
];
|
||||
})} | ${cfssl}/bin/cfssljson -bare ca
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
testScript =
|
||||
let
|
||||
cfsslrequest = with pkgs; writeScript "cfsslrequest" ''
|
||||
curl -f -X POST -H "Content-Type: application/json" -d @${csr} \
|
||||
http://localhost:8888/api/v1/cfssl/newkey | ${cfssl}/bin/cfssljson /tmp/certificate
|
||||
'';
|
||||
csr = pkgs.writeText "csr.json" (builtins.toJSON {
|
||||
CN = "www.example.com";
|
||||
hosts = [ "example.com" "www.example.com" ];
|
||||
key = {
|
||||
algo = "rsa";
|
||||
size = 2048;
|
||||
};
|
||||
names = [
|
||||
{
|
||||
C = "US";
|
||||
L = "San Francisco";
|
||||
O = "Example Company, LLC";
|
||||
OU = "Operations";
|
||||
ST = "California";
|
||||
}
|
||||
];
|
||||
});
|
||||
in
|
||||
''
|
||||
machine.wait_for_unit("cfssl.service")
|
||||
machine.wait_until_succeeds("${cfsslrequest}")
|
||||
machine.succeed("ls /tmp/certificate-key.pem")
|
||||
'';
|
||||
})
|
||||
43
nixos/tests/charliecloud.nix
Normal file
43
nixos/tests/charliecloud.nix
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
# This test checks charliecloud image construction and run
|
||||
|
||||
import ./make-test-python.nix ({ pkgs, ...} : let
|
||||
|
||||
dockerfile = pkgs.writeText "Dockerfile" ''
|
||||
FROM nix
|
||||
RUN mkdir /home /tmp
|
||||
RUN touch /etc/passwd /etc/group
|
||||
CMD ["true"]
|
||||
'';
|
||||
|
||||
in {
|
||||
name = "charliecloud";
|
||||
meta = with pkgs.lib.maintainers; {
|
||||
maintainers = [ bzizou ];
|
||||
};
|
||||
|
||||
nodes = {
|
||||
host = { ... }: {
|
||||
environment.systemPackages = [ pkgs.charliecloud ];
|
||||
virtualisation.docker.enable = true;
|
||||
users.users.alice = {
|
||||
isNormalUser = true;
|
||||
extraGroups = [ "docker" ];
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
testScript = ''
|
||||
host.start()
|
||||
host.wait_for_unit("docker.service")
|
||||
host.succeed(
|
||||
'su - alice -c "docker load --input=${pkgs.dockerTools.examples.nix}"'
|
||||
)
|
||||
host.succeed(
|
||||
"cp ${dockerfile} /home/alice/Dockerfile"
|
||||
)
|
||||
host.succeed('su - alice -c "ch-build -t hello ."')
|
||||
host.succeed('su - alice -c "ch-builder2tar hello /var/tmp"')
|
||||
host.succeed('su - alice -c "ch-tar2dir /var/tmp/hello.tar.gz /var/tmp"')
|
||||
host.succeed('su - alice -c "ch-run /var/tmp/hello -- echo Running_From_Container_OK"')
|
||||
'';
|
||||
})
|
||||
262
nixos/tests/chromium.nix
Normal file
262
nixos/tests/chromium.nix
Normal file
|
|
@ -0,0 +1,262 @@
|
|||
{ system ? builtins.currentSystem
|
||||
, config ? {}
|
||||
, pkgs ? import ../.. { inherit system config; }
|
||||
, channelMap ? { # Maps "channels" to packages
|
||||
stable = pkgs.chromium;
|
||||
beta = pkgs.chromiumBeta;
|
||||
dev = pkgs.chromiumDev;
|
||||
ungoogled = pkgs.ungoogled-chromium;
|
||||
chrome-stable = pkgs.google-chrome;
|
||||
chrome-beta = pkgs.google-chrome-beta;
|
||||
chrome-dev = pkgs.google-chrome-dev;
|
||||
}
|
||||
}:
|
||||
|
||||
with import ../lib/testing-python.nix { inherit system pkgs; };
|
||||
with pkgs.lib;
|
||||
|
||||
let
|
||||
user = "alice";
|
||||
|
||||
startupHTML = pkgs.writeText "chromium-startup.html" ''
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Chromium startup notifier</title>
|
||||
</head>
|
||||
<body onload="javascript:document.title='startup done'">
|
||||
<img src="file://${pkgs.fetchurl {
|
||||
url = "https://nixos.org/logo/nixos-hex.svg";
|
||||
sha256 = "07ymq6nw8kc22m7kzxjxldhiq8gzmc7f45kq2bvhbdm0w5s112s4";
|
||||
}}" />
|
||||
</body>
|
||||
</html>
|
||||
'';
|
||||
in
|
||||
|
||||
mapAttrs (channel: chromiumPkg: makeTest {
|
||||
name = "chromium-${channel}";
|
||||
meta = {
|
||||
maintainers = with maintainers; [ aszlig primeos ];
|
||||
# https://github.com/NixOS/hydra/issues/591#issuecomment-435125621
|
||||
inherit (chromiumPkg.meta) timeout;
|
||||
};
|
||||
|
||||
enableOCR = true;
|
||||
|
||||
nodes.machine = { ... }: {
|
||||
imports = [ ./common/user-account.nix ./common/x11.nix ];
|
||||
virtualisation.memorySize = 2047;
|
||||
test-support.displayManager.auto.user = user;
|
||||
environment = {
|
||||
systemPackages = [ chromiumPkg ];
|
||||
variables."XAUTHORITY" = "/home/alice/.Xauthority";
|
||||
};
|
||||
};
|
||||
|
||||
testScript = let
|
||||
xdo = name: text: let
|
||||
xdoScript = pkgs.writeText "${name}.xdo" text;
|
||||
in "${pkgs.xdotool}/bin/xdotool ${xdoScript}";
|
||||
in ''
|
||||
import shlex
|
||||
import re
|
||||
from contextlib import contextmanager
|
||||
|
||||
|
||||
# Run as user alice
|
||||
def ru(cmd):
|
||||
return "su - ${user} -c " + shlex.quote(cmd)
|
||||
|
||||
|
||||
def launch_browser():
|
||||
"""Launches the web browser with the correct options."""
|
||||
# Determine the name of the binary:
|
||||
pname = "${getName chromiumPkg.name}"
|
||||
if pname.find("chromium") != -1:
|
||||
binary = "chromium" # Same name for all channels and ungoogled-chromium
|
||||
elif pname == "google-chrome":
|
||||
binary = "google-chrome-stable"
|
||||
elif pname == "google-chrome-dev":
|
||||
binary = "google-chrome-unstable"
|
||||
else: # For google-chrome-beta and as fallback:
|
||||
binary = pname
|
||||
# Add optional CLI options:
|
||||
options = []
|
||||
major_version = "${versions.major (getVersion chromiumPkg.name)}"
|
||||
if major_version > "95" and not pname.startswith("google-chrome"):
|
||||
# Workaround to avoid a GPU crash:
|
||||
options.append("--use-gl=swiftshader")
|
||||
# Launch the process:
|
||||
options.append("file://${startupHTML}")
|
||||
machine.succeed(ru(f'ulimit -c unlimited; {binary} {shlex.join(options)} >&2 & disown'))
|
||||
if binary.startswith("google-chrome"):
|
||||
# Need to click away the first window:
|
||||
machine.wait_for_text("Make Google Chrome the default browser")
|
||||
machine.screenshot("google_chrome_default_browser_prompt")
|
||||
machine.send_key("ret")
|
||||
|
||||
|
||||
def create_new_win():
|
||||
"""Creates a new Chromium window."""
|
||||
with machine.nested("Creating a new Chromium window"):
|
||||
machine.wait_until_succeeds(
|
||||
ru(
|
||||
"${xdo "create_new_win-select_main_window" ''
|
||||
search --onlyvisible --name "startup done"
|
||||
windowfocus --sync
|
||||
windowactivate --sync
|
||||
''}"
|
||||
)
|
||||
)
|
||||
machine.send_key("ctrl-n")
|
||||
# Wait until the new window appears:
|
||||
machine.wait_until_succeeds(
|
||||
ru(
|
||||
"${xdo "create_new_win-wait_for_window" ''
|
||||
search --onlyvisible --name "New Tab"
|
||||
windowfocus --sync
|
||||
windowactivate --sync
|
||||
''}"
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def close_new_tab_win():
|
||||
"""Closes the Chromium window with the title "New Tab"."""
|
||||
machine.wait_until_succeeds(
|
||||
ru(
|
||||
"${xdo "close_new_tab_win-select_main_window" ''
|
||||
search --onlyvisible --name "New Tab"
|
||||
windowfocus --sync
|
||||
windowactivate --sync
|
||||
''}"
|
||||
)
|
||||
)
|
||||
machine.send_key("ctrl-w")
|
||||
# Wait until the closed window disappears:
|
||||
machine.wait_until_fails(
|
||||
ru(
|
||||
"${xdo "close_new_tab_win-wait_for_close" ''
|
||||
search --onlyvisible --name "New Tab"
|
||||
''}"
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@contextmanager
|
||||
def test_new_win(description, url, window_name):
|
||||
create_new_win()
|
||||
machine.wait_for_window("New Tab")
|
||||
machine.send_chars(f"{url}\n")
|
||||
machine.wait_for_window(window_name)
|
||||
machine.screenshot(description)
|
||||
machine.succeed(
|
||||
ru(
|
||||
"${xdo "copy-all" ''
|
||||
key --delay 1000 Ctrl+a Ctrl+c
|
||||
''}"
|
||||
)
|
||||
)
|
||||
clipboard = machine.succeed(
|
||||
ru("${pkgs.xclip}/bin/xclip -o")
|
||||
)
|
||||
print(f"{description} window content:\n{clipboard}")
|
||||
with machine.nested(description):
|
||||
yield clipboard
|
||||
# Close the newly created window:
|
||||
machine.send_key("ctrl-w")
|
||||
|
||||
|
||||
machine.wait_for_x()
|
||||
|
||||
launch_browser()
|
||||
|
||||
machine.wait_for_text("startup done")
|
||||
machine.wait_until_succeeds(
|
||||
ru(
|
||||
"${xdo "check-startup" ''
|
||||
search --sync --onlyvisible --name "startup done"
|
||||
# close first start help popup
|
||||
key -delay 1000 Escape
|
||||
windowfocus --sync
|
||||
windowactivate --sync
|
||||
''}"
|
||||
)
|
||||
)
|
||||
|
||||
create_new_win()
|
||||
# Optional: Wait for the new tab page to fully load before taking the screenshot:
|
||||
machine.wait_for_text("Web Store")
|
||||
machine.screenshot("empty_windows")
|
||||
close_new_tab_win()
|
||||
|
||||
machine.screenshot("startup_done")
|
||||
|
||||
with test_new_win("sandbox_info", "chrome://sandbox", "Sandbox Status") as clipboard:
|
||||
filters = [
|
||||
"layer 1 sandbox.*namespace",
|
||||
"pid namespaces.*yes",
|
||||
"network namespaces.*yes",
|
||||
"seccomp.*sandbox.*yes",
|
||||
"you are adequately sandboxed",
|
||||
]
|
||||
if not all(
|
||||
re.search(filter, clipboard, flags=re.DOTALL | re.IGNORECASE)
|
||||
for filter in filters
|
||||
):
|
||||
assert False, f"sandbox not working properly: {clipboard}"
|
||||
|
||||
machine.sleep(1)
|
||||
machine.succeed(
|
||||
ru(
|
||||
"${xdo "find-window-after-copy" ''
|
||||
search --onlyvisible --name "Sandbox Status"
|
||||
''}"
|
||||
)
|
||||
)
|
||||
|
||||
clipboard = machine.succeed(
|
||||
ru(
|
||||
"echo void | ${pkgs.xclip}/bin/xclip -i >&2"
|
||||
)
|
||||
)
|
||||
machine.succeed(
|
||||
ru(
|
||||
"${xdo "copy-sandbox-info" ''
|
||||
key --delay 1000 Ctrl+a Ctrl+c
|
||||
''}"
|
||||
)
|
||||
)
|
||||
|
||||
clipboard = machine.succeed(
|
||||
ru("${pkgs.xclip}/bin/xclip -o")
|
||||
)
|
||||
if not all(
|
||||
re.search(filter, clipboard, flags=re.DOTALL | re.IGNORECASE)
|
||||
for filter in filters
|
||||
):
|
||||
assert False, f"copying twice in a row does not work properly: {clipboard}"
|
||||
|
||||
machine.screenshot("after_copy_from_chromium")
|
||||
|
||||
|
||||
with test_new_win("gpu_info", "chrome://gpu", "chrome://gpu"):
|
||||
# To check the text rendering (catches regressions like #131074):
|
||||
machine.wait_for_text("Graphics Feature Status")
|
||||
|
||||
|
||||
with test_new_win("version_info", "chrome://version", "About Version") as clipboard:
|
||||
filters = [
|
||||
r"${chromiumPkg.version} \(Official Build",
|
||||
]
|
||||
if not all(
|
||||
re.search(filter, clipboard) for filter in filters
|
||||
):
|
||||
assert False, "Version info not correct."
|
||||
|
||||
|
||||
machine.shutdown()
|
||||
'';
|
||||
}) channelMap
|
||||
121
nixos/tests/cjdns.nix
Normal file
121
nixos/tests/cjdns.nix
Normal file
|
|
@ -0,0 +1,121 @@
|
|||
let
|
||||
carolKey = "2d2a338b46f8e4a8c462f0c385b481292a05f678e19a2b82755258cf0f0af7e2";
|
||||
carolPubKey = "n932l3pjvmhtxxcdrqq2qpw5zc58f01vvjx01h4dtd1bb0nnu2h0.k";
|
||||
carolPassword = "678287829ce4c67bc8b227e56d94422ee1b85fa11618157b2f591de6c6322b52";
|
||||
|
||||
basicConfig =
|
||||
{ ... }:
|
||||
{ services.cjdns.enable = true;
|
||||
|
||||
# Turning off DHCP isn't very realistic but makes
|
||||
# the sequence of address assignment less stochastic.
|
||||
networking.useDHCP = false;
|
||||
|
||||
# CJDNS output is incompatible with the XML log.
|
||||
systemd.services.cjdns.serviceConfig.StandardOutput = "null";
|
||||
};
|
||||
|
||||
in
|
||||
|
||||
import ./make-test-python.nix ({ pkgs, ...} : {
|
||||
name = "cjdns";
|
||||
meta = with pkgs.lib.maintainers; {
|
||||
maintainers = [ ehmry ];
|
||||
};
|
||||
|
||||
nodes = { # Alice finds peers over over ETHInterface.
|
||||
alice =
|
||||
{ ... }:
|
||||
{ imports = [ basicConfig ];
|
||||
|
||||
services.cjdns.ETHInterface.bind = "eth1";
|
||||
|
||||
services.httpd.enable = true;
|
||||
services.httpd.adminAddr = "foo@example.org";
|
||||
networking.firewall.allowedTCPPorts = [ 80 ];
|
||||
};
|
||||
|
||||
# Bob explicitly connects to Carol over UDPInterface.
|
||||
bob =
|
||||
{ ... }:
|
||||
|
||||
{ imports = [ basicConfig ];
|
||||
|
||||
networking.interfaces.eth1.ipv4.addresses = [
|
||||
{ address = "192.168.0.2"; prefixLength = 24; }
|
||||
];
|
||||
|
||||
services.cjdns =
|
||||
{ UDPInterface =
|
||||
{ bind = "0.0.0.0:1024";
|
||||
connectTo."192.168.0.1:1024" =
|
||||
{ password = carolPassword;
|
||||
publicKey = carolPubKey;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
# Carol listens on ETHInterface and UDPInterface,
|
||||
# but knows neither Alice or Bob.
|
||||
carol =
|
||||
{ ... }:
|
||||
{ imports = [ basicConfig ];
|
||||
|
||||
environment.etc."cjdns.keys".text = ''
|
||||
CJDNS_PRIVATE_KEY=${carolKey}
|
||||
CJDNS_ADMIN_PASSWORD=FOOBAR
|
||||
'';
|
||||
|
||||
networking.interfaces.eth1.ipv4.addresses = [
|
||||
{ address = "192.168.0.1"; prefixLength = 24; }
|
||||
];
|
||||
|
||||
services.cjdns =
|
||||
{ authorizedPasswords = [ carolPassword ];
|
||||
ETHInterface.bind = "eth1";
|
||||
UDPInterface.bind = "192.168.0.1:1024";
|
||||
};
|
||||
networking.firewall.allowedUDPPorts = [ 1024 ];
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
testScript =
|
||||
''
|
||||
import re
|
||||
|
||||
start_all()
|
||||
|
||||
alice.wait_for_unit("cjdns.service")
|
||||
bob.wait_for_unit("cjdns.service")
|
||||
carol.wait_for_unit("cjdns.service")
|
||||
|
||||
|
||||
def cjdns_ip(machine):
|
||||
res = machine.succeed("ip -o -6 addr show dev tun0")
|
||||
ip = re.split("\s+|/", res)[3]
|
||||
machine.log("has ip {}".format(ip))
|
||||
return ip
|
||||
|
||||
|
||||
alice_ip6 = cjdns_ip(alice)
|
||||
bob_ip6 = cjdns_ip(bob)
|
||||
carol_ip6 = cjdns_ip(carol)
|
||||
|
||||
# ping a few times each to let the routing table establish itself
|
||||
|
||||
alice.succeed("ping -c 4 {}".format(carol_ip6))
|
||||
bob.succeed("ping -c 4 {}".format(carol_ip6))
|
||||
|
||||
carol.succeed("ping -c 4 {}".format(alice_ip6))
|
||||
carol.succeed("ping -c 4 {}".format(bob_ip6))
|
||||
|
||||
alice.succeed("ping -c 4 {}".format(bob_ip6))
|
||||
bob.succeed("ping -c 4 {}".format(alice_ip6))
|
||||
|
||||
alice.wait_for_unit("httpd.service")
|
||||
|
||||
bob.succeed("curl --fail -g http://[{}]".format(alice_ip6))
|
||||
'';
|
||||
})
|
||||
32
nixos/tests/clickhouse.nix
Normal file
32
nixos/tests/clickhouse.nix
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
import ./make-test-python.nix ({ pkgs, ... }: {
|
||||
name = "clickhouse";
|
||||
meta.maintainers = with pkgs.lib.maintainers; [ ma27 ];
|
||||
|
||||
nodes.machine = {
|
||||
services.clickhouse.enable = true;
|
||||
virtualisation.memorySize = 4096;
|
||||
};
|
||||
|
||||
testScript =
|
||||
let
|
||||
# work around quote/substitution complexity by Nix, Perl, bash and SQL.
|
||||
tableDDL = pkgs.writeText "ddl.sql" "CREATE TABLE `demo` (`value` FixedString(10)) engine = MergeTree PARTITION BY value ORDER BY tuple();";
|
||||
insertQuery = pkgs.writeText "insert.sql" "INSERT INTO `demo` (`value`) VALUES ('foo');";
|
||||
selectQuery = pkgs.writeText "select.sql" "SELECT * from `demo`";
|
||||
in
|
||||
''
|
||||
machine.start()
|
||||
machine.wait_for_unit("clickhouse.service")
|
||||
machine.wait_for_open_port(9000)
|
||||
|
||||
machine.succeed(
|
||||
"cat ${tableDDL} | clickhouse-client"
|
||||
)
|
||||
machine.succeed(
|
||||
"cat ${insertQuery} | clickhouse-client"
|
||||
)
|
||||
machine.succeed(
|
||||
"cat ${selectQuery} | clickhouse-client | grep foo"
|
||||
)
|
||||
'';
|
||||
})
|
||||
109
nixos/tests/cloud-init.nix
Normal file
109
nixos/tests/cloud-init.nix
Normal file
|
|
@ -0,0 +1,109 @@
|
|||
{ system ? builtins.currentSystem,
|
||||
config ? {},
|
||||
pkgs ? import ../.. { inherit system config; }
|
||||
}:
|
||||
|
||||
with import ../lib/testing-python.nix { inherit system pkgs; };
|
||||
with pkgs.lib;
|
||||
|
||||
let
|
||||
inherit (import ./ssh-keys.nix pkgs)
|
||||
snakeOilPrivateKey snakeOilPublicKey;
|
||||
|
||||
metadataDrive = pkgs.stdenv.mkDerivation {
|
||||
name = "metadata";
|
||||
buildCommand = ''
|
||||
mkdir -p $out/iso
|
||||
|
||||
cat << EOF > $out/iso/user-data
|
||||
#cloud-config
|
||||
write_files:
|
||||
- content: |
|
||||
cloudinit
|
||||
path: /tmp/cloudinit-write-file
|
||||
|
||||
users:
|
||||
- default
|
||||
- name: nixos
|
||||
ssh_authorized_keys:
|
||||
- "${snakeOilPublicKey}"
|
||||
EOF
|
||||
|
||||
cat << EOF > $out/iso/meta-data
|
||||
instance-id: iid-local01
|
||||
local-hostname: "test"
|
||||
public-keys:
|
||||
- "${snakeOilPublicKey}"
|
||||
EOF
|
||||
|
||||
cat << EOF > $out/iso/network-config
|
||||
version: 1
|
||||
config:
|
||||
- type: physical
|
||||
name: eth0
|
||||
mac_address: '52:54:00:12:34:56'
|
||||
subnets:
|
||||
- type: static
|
||||
address: '12.34.56.78'
|
||||
netmask: '255.255.255.0'
|
||||
gateway: '12.34.56.9'
|
||||
- type: nameserver
|
||||
address:
|
||||
- '8.8.8.8'
|
||||
search:
|
||||
- 'example.com'
|
||||
EOF
|
||||
${pkgs.cdrkit}/bin/genisoimage -volid cidata -joliet -rock -o $out/metadata.iso $out/iso
|
||||
'';
|
||||
};
|
||||
in makeTest {
|
||||
name = "cloud-init";
|
||||
meta = with pkgs.lib.maintainers; {
|
||||
maintainers = [ lewo ];
|
||||
};
|
||||
nodes.machine = { ... }:
|
||||
{
|
||||
virtualisation.qemu.options = [ "-cdrom" "${metadataDrive}/metadata.iso" ];
|
||||
services.cloud-init = {
|
||||
enable = true;
|
||||
network.enable = true;
|
||||
};
|
||||
services.openssh.enable = true;
|
||||
networking.hostName = "";
|
||||
networking.useDHCP = false;
|
||||
};
|
||||
testScript = ''
|
||||
# To wait until cloud-init terminates its run
|
||||
unnamed.wait_for_unit("cloud-final.service")
|
||||
|
||||
unnamed.succeed("cat /tmp/cloudinit-write-file | grep -q 'cloudinit'")
|
||||
|
||||
# install snakeoil ssh key and provision .ssh/config file
|
||||
unnamed.succeed("mkdir -p ~/.ssh")
|
||||
unnamed.succeed(
|
||||
"cat ${snakeOilPrivateKey} > ~/.ssh/id_snakeoil"
|
||||
)
|
||||
unnamed.succeed("chmod 600 ~/.ssh/id_snakeoil")
|
||||
|
||||
unnamed.wait_for_unit("sshd.service")
|
||||
|
||||
# we should be able to log in as the root user, as well as the created nixos user
|
||||
unnamed.succeed(
|
||||
"ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -o IdentityFile=~/.ssh/id_snakeoil root@localhost 'true'"
|
||||
)
|
||||
unnamed.succeed(
|
||||
"ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -o IdentityFile=~/.ssh/id_snakeoil nixos@localhost 'true'"
|
||||
)
|
||||
|
||||
# test changing hostname via cloud-init worked
|
||||
assert (
|
||||
unnamed.succeed(
|
||||
"ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -o IdentityFile=~/.ssh/id_snakeoil nixos@localhost 'hostname'"
|
||||
).strip()
|
||||
== "test"
|
||||
)
|
||||
|
||||
assert "default via 12.34.56.9 dev eth0 proto static" in unnamed.succeed("ip route")
|
||||
assert "12.34.56.0/24 dev eth0 proto kernel scope link src 12.34.56.78" in unnamed.succeed("ip route")
|
||||
'';
|
||||
}
|
||||
75
nixos/tests/cntr.nix
Normal file
75
nixos/tests/cntr.nix
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
# Test for cntr tool
|
||||
{ system ? builtins.currentSystem, config ? { }
|
||||
, pkgs ? import ../.. { inherit system config; }, lib ? pkgs.lib }:
|
||||
|
||||
let
|
||||
inherit (import ../lib/testing-python.nix { inherit system pkgs; }) makeTest;
|
||||
|
||||
mkOCITest = backend:
|
||||
makeTest {
|
||||
name = "cntr-${backend}";
|
||||
|
||||
meta = { maintainers = with lib.maintainers; [ sorki mic92 ]; };
|
||||
|
||||
nodes = {
|
||||
${backend} = { pkgs, ... }: {
|
||||
environment.systemPackages = [ pkgs.cntr ];
|
||||
virtualisation.oci-containers = {
|
||||
inherit backend;
|
||||
containers.nginx = {
|
||||
image = "nginx-container";
|
||||
imageFile = pkgs.dockerTools.examples.nginx;
|
||||
ports = [ "8181:80" ];
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
testScript = ''
|
||||
start_all()
|
||||
${backend}.wait_for_unit("${backend}-nginx.service")
|
||||
${backend}.wait_for_open_port(8181)
|
||||
# For some reason, the cntr command hangs when run without the &.
|
||||
# As such, we have to do some messy things to ensure we check the exitcode and output in a race-condition-safe manner
|
||||
${backend}.execute(
|
||||
"(cntr attach -t ${backend} nginx sh -- -c 'curl localhost | grep Hello' > /tmp/result; echo $? > /tmp/exitcode; touch /tmp/done) &"
|
||||
)
|
||||
|
||||
${backend}.wait_for_file("/tmp/done")
|
||||
assert "0" == ${backend}.succeed("cat /tmp/exitcode").strip(), "non-zero exit code"
|
||||
assert "Hello" in ${backend}.succeed("cat /tmp/result"), "no greeting in output"
|
||||
'';
|
||||
};
|
||||
|
||||
mkContainersTest = makeTest {
|
||||
name = "cntr-containers";
|
||||
|
||||
meta = with pkgs.lib.maintainers; { maintainers = [ sorki mic92 ]; };
|
||||
|
||||
nodes.machine = { lib, ... }: {
|
||||
environment.systemPackages = [ pkgs.cntr ];
|
||||
containers.test = {
|
||||
autoStart = true;
|
||||
privateNetwork = true;
|
||||
hostAddress = "172.16.0.1";
|
||||
localAddress = "172.16.0.2";
|
||||
config = { };
|
||||
};
|
||||
};
|
||||
|
||||
testScript = ''
|
||||
machine.start()
|
||||
machine.wait_for_unit("container@test.service")
|
||||
# I haven't observed the same hanging behaviour in this version as in the OCI version which necessetates this messy invocation, but it's probably better to be safe than sorry and use it here as well
|
||||
machine.execute(
|
||||
"(cntr attach test sh -- -c 'ping -c5 172.16.0.1'; echo $? > /tmp/exitcode; touch /tmp/done) &"
|
||||
)
|
||||
|
||||
machine.wait_for_file("/tmp/done")
|
||||
assert "0" == machine.succeed("cat /tmp/exitcode").strip(), "non-zero exit code"
|
||||
'';
|
||||
};
|
||||
in {
|
||||
nixos-container = mkContainersTest;
|
||||
} // (lib.foldl' (attrs: backend: attrs // { ${backend} = mkOCITest backend; })
|
||||
{ } [ "docker" "podman" ])
|
||||
124
nixos/tests/cockroachdb.nix
Normal file
124
nixos/tests/cockroachdb.nix
Normal file
|
|
@ -0,0 +1,124 @@
|
|||
# This performs a full 'end-to-end' test of a multi-node CockroachDB cluster
|
||||
# using the built-in 'cockroach workload' command, to simulate a semi-realistic
|
||||
# test load. It generally takes anywhere from 3-5 minutes to run and 1-2GB of
|
||||
# RAM (though each of 3 workers gets 2GB allocated)
|
||||
#
|
||||
# CockroachDB requires synchronized system clocks within a small error window
|
||||
# (~500ms by default) on each node in order to maintain a multi-node cluster.
|
||||
# Cluster joins that are outside this window will fail, and nodes that skew
|
||||
# outside the window after joining will promptly get kicked out.
|
||||
#
|
||||
# To accomodate this, we use QEMU/virtio infrastructure and load the 'ptp_kvm'
|
||||
# driver inside a guest. This driver allows the host machine to pass its clock
|
||||
# through to the guest as a hardware clock that appears as a Precision Time
|
||||
# Protocol (PTP) Clock device, generally /dev/ptp0. PTP devices can be measured
|
||||
# and used as hardware reference clocks (similar to an on-board GPS clock) by
|
||||
# NTP software. In our case, we use Chrony to synchronize to the reference
|
||||
# clock.
|
||||
#
|
||||
# This test is currently NOT enabled as a continuously-checked NixOS test.
|
||||
# Ideally, this test would be run by Hydra and Borg on all relevant changes,
|
||||
# except:
|
||||
#
|
||||
# - Not every build machine is compatible with the ptp_kvm driver.
|
||||
# Virtualized EC2 instances, for example, do not support loading the ptp_kvm
|
||||
# driver into guests. However, bare metal builders (e.g. Packet) do seem to
|
||||
# work just fine. In practice, this means x86_64-linux builds would fail
|
||||
# randomly, depending on which build machine got the job. (This is probably
|
||||
# worth some investigation; I imagine it's based on ptp_kvm's usage of paravirt
|
||||
# support which may not be available in 'nested' environments.)
|
||||
#
|
||||
# - ptp_kvm is not supported on aarch64, otherwise it seems likely Cockroach
|
||||
# could be tested there, as well. This seems to be due to the usage of
|
||||
# the TSC in ptp_kvm, which isn't supported (easily) on AArch64. (And:
|
||||
# testing stuff, not just making sure it builds, is important to ensure
|
||||
# aarch64 support remains viable.)
|
||||
#
|
||||
# For future developers who are reading this message, are daring and would want
|
||||
# to fix this, some options are:
|
||||
#
|
||||
# - Just test a single node cluster instead (boring and less thorough).
|
||||
# - Move all CI to bare metal packet builders, and we can at least do x86_64-linux.
|
||||
# - Get virtualized clocking working in aarch64, somehow.
|
||||
# - Add a 4th node that acts as an NTP service and uses no PTP clocks for
|
||||
# references, at the client level. This bloats the node and memory
|
||||
# requirements, but would probably allow both aarch64/x86_64 to work.
|
||||
#
|
||||
|
||||
let
|
||||
|
||||
# Creates a node. If 'joinNode' parameter, a string containing an IP address,
|
||||
# is non-null, then the CockroachDB server will attempt to join/connect to
|
||||
# the cluster node specified at that address.
|
||||
makeNode = locality: myAddr: joinNode:
|
||||
{ nodes, pkgs, lib, config, ... }:
|
||||
|
||||
{
|
||||
# Bank/TPC-C benchmarks take some memory to complete
|
||||
virtualisation.memorySize = 2048;
|
||||
|
||||
# Install the KVM PTP "Virtualized Clock" driver. This allows a /dev/ptp0
|
||||
# device to appear as a reference clock, synchronized to the host clock.
|
||||
# Because CockroachDB *requires* a time-synchronization mechanism for
|
||||
# the system time in a cluster scenario, this is necessary to work.
|
||||
boot.kernelModules = [ "ptp_kvm" ];
|
||||
|
||||
# Enable and configure Chrony, using the given virtualized clock passed
|
||||
# through by KVM.
|
||||
services.chrony.enable = true;
|
||||
services.chrony.servers = lib.mkForce [ ];
|
||||
services.chrony.extraConfig = ''
|
||||
refclock PHC /dev/ptp0 poll 2 prefer require refid KVM
|
||||
makestep 0.1 3
|
||||
'';
|
||||
|
||||
# Enable CockroachDB. In order to ensure that Chrony has performed its
|
||||
# first synchronization at boot-time (which may take ~10 seconds) before
|
||||
# starting CockroachDB, we block the ExecStartPre directive using the
|
||||
# 'waitsync' command. This ensures Cockroach doesn't have its system time
|
||||
# leap forward out of nowhere during startup/execution.
|
||||
#
|
||||
# Note that the default threshold for NTP-based skew in CockroachDB is
|
||||
# ~500ms by default, so making sure it's started *after* accurate time
|
||||
# synchronization is extremely important.
|
||||
services.cockroachdb.enable = true;
|
||||
services.cockroachdb.insecure = true;
|
||||
services.cockroachdb.openPorts = true;
|
||||
services.cockroachdb.locality = locality;
|
||||
services.cockroachdb.listen.address = myAddr;
|
||||
services.cockroachdb.join = lib.mkIf (joinNode != null) joinNode;
|
||||
|
||||
systemd.services.chronyd.unitConfig.ConditionPathExists = "/dev/ptp0";
|
||||
|
||||
# Hold startup until Chrony has performed its first measurement (which
|
||||
# will probably result in a full timeskip, thanks to makestep)
|
||||
systemd.services.cockroachdb.preStart = ''
|
||||
${pkgs.chrony}/bin/chronyc waitsync
|
||||
'';
|
||||
};
|
||||
|
||||
in import ./make-test-python.nix ({ pkgs, ...} : {
|
||||
name = "cockroachdb";
|
||||
meta.maintainers = with pkgs.lib.maintainers;
|
||||
[ thoughtpolice ];
|
||||
|
||||
nodes = {
|
||||
node1 = makeNode "country=us,region=east,dc=1" "192.168.1.1" null;
|
||||
node2 = makeNode "country=us,region=west,dc=2b" "192.168.1.2" "192.168.1.1";
|
||||
node3 = makeNode "country=eu,region=west,dc=2" "192.168.1.3" "192.168.1.1";
|
||||
};
|
||||
|
||||
# NOTE: All the nodes must start in order and you must NOT use startAll, because
|
||||
# there's otherwise no way to guarantee that node1 will start before the others try
|
||||
# to join it.
|
||||
testScript = ''
|
||||
for node in node1, node2, node3:
|
||||
node.start()
|
||||
node.wait_for_unit("cockroachdb")
|
||||
node1.succeed(
|
||||
"cockroach sql --host=192.168.1.1 --insecure -e 'SHOW ALL CLUSTER SETTINGS' 2>&1",
|
||||
"cockroach workload init bank 'postgresql://root@192.168.1.1:26257?sslmode=disable'",
|
||||
"cockroach workload run bank --duration=1m 'postgresql://root@192.168.1.1:26257?sslmode=disable'",
|
||||
)
|
||||
'';
|
||||
})
|
||||
38
nixos/tests/collectd.nix
Normal file
38
nixos/tests/collectd.nix
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
import ./make-test-python.nix ({ pkgs, ... }: {
|
||||
name = "collectd";
|
||||
meta = { };
|
||||
|
||||
nodes.machine =
|
||||
{ pkgs, lib, ... }:
|
||||
|
||||
{
|
||||
services.collectd = {
|
||||
enable = true;
|
||||
extraConfig = lib.mkBefore ''
|
||||
Interval 30
|
||||
'';
|
||||
plugins = {
|
||||
rrdtool = ''
|
||||
DataDir "/var/lib/collectd/rrd"
|
||||
'';
|
||||
load = "";
|
||||
};
|
||||
};
|
||||
environment.systemPackages = [ pkgs.rrdtool ];
|
||||
};
|
||||
|
||||
testScript = ''
|
||||
machine.wait_for_unit("collectd.service")
|
||||
hostname = machine.succeed("hostname").strip()
|
||||
file = f"/var/lib/collectd/rrd/{hostname}/load/load.rrd"
|
||||
machine.wait_for_file(file);
|
||||
machine.succeed(f"rrdinfo {file} | logger")
|
||||
# check that this file contains a shortterm metric
|
||||
machine.succeed(f"rrdinfo {file} | grep -F 'ds[shortterm].min = '")
|
||||
# check that interval was set before the plugins
|
||||
machine.succeed(f"rrdinfo {file} | grep -F 'step = 30'")
|
||||
# check that there are frequent updates
|
||||
machine.succeed(f"cp {file} before")
|
||||
machine.wait_until_fails(f"cmp before {file}")
|
||||
'';
|
||||
})
|
||||
16
nixos/tests/common/acme/client/default.nix
Normal file
16
nixos/tests/common/acme/client/default.nix
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
{ lib, nodes, pkgs, ... }:
|
||||
let
|
||||
caCert = nodes.acme.config.test-support.acme.caCert;
|
||||
caDomain = nodes.acme.config.test-support.acme.caDomain;
|
||||
|
||||
in {
|
||||
security.acme = {
|
||||
acceptTerms = true;
|
||||
defaults = {
|
||||
server = "https://${caDomain}/dir";
|
||||
email = "hostmaster@example.test";
|
||||
};
|
||||
};
|
||||
|
||||
security.pki.certificateFiles = [ caCert ];
|
||||
}
|
||||
21
nixos/tests/common/acme/server/README.md
Normal file
21
nixos/tests/common/acme/server/README.md
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
# Fake Certificate Authority for ACME testing
|
||||
|
||||
This will set up a test node running [pebble](https://github.com/letsencrypt/pebble)
|
||||
to serve ACME certificate requests.
|
||||
|
||||
## "Snake oil" certs
|
||||
|
||||
The snake oil certs are hard coded into the repo for reasons explained [here](https://github.com/NixOS/nixpkgs/pull/91121#discussion_r505410235).
|
||||
The root of the issue is that Nix will hash the derivation based on the arguments
|
||||
to mkDerivation, not the output. [Minica](https://github.com/jsha/minica) will
|
||||
always generate a random certificate even if the arguments are unchanged. As a
|
||||
result, it's possible to end up in a situation where the cached and local
|
||||
generated certs mismatch and cause issues with testing.
|
||||
|
||||
To generate new certificates, run the following commands:
|
||||
|
||||
```bash
|
||||
nix-build generate-certs.nix
|
||||
cp result/* .
|
||||
rm result
|
||||
```
|
||||
19
nixos/tests/common/acme/server/acme.test.cert.pem
Normal file
19
nixos/tests/common/acme/server/acme.test.cert.pem
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
-----BEGIN CERTIFICATE-----
|
||||
MIIDLDCCAhSgAwIBAgIIRDAN3FHH//IwDQYJKoZIhvcNAQELBQAwIDEeMBwGA1UE
|
||||
AxMVbWluaWNhIHJvb3QgY2EgNzg3NDZmMB4XDTIwMTAyMTEzMjgzNloXDTIyMTEy
|
||||
MDEzMjgzNlowFDESMBAGA1UEAxMJYWNtZS50ZXN0MIIBIjANBgkqhkiG9w0BAQEF
|
||||
AAOCAQ8AMIIBCgKCAQEAo8XjMVUaljcaqQ5MFhfPuQgSwdyXEUbpSHz+5yPkE0h9
|
||||
Z4Xu5BJF1Oq7h5ggCtadVsIspiY6Jm6aWDOjlh4myzW5UNBNUG3OPEk50vmmHFeH
|
||||
pImHO/d8yb33QoF9VRcTZs4tuJYg7l9bSs4jNG72vYvv2YiGAcmjJcsmAZIfniCN
|
||||
Xf/LjIm+Cxykn+Vo3UuzO1w5/iuofdgWO/aZxMezmXUivlL3ih4cNzCJei8WlB/l
|
||||
EnHrkcy3ogRmmynP5zcz7vmGIJX2ji6dhCa4Got5B7eZK76o2QglhQXqPatG0AOY
|
||||
H+RfQfzKemqPG5om9MgJtwFtTOU1LoaiBw//jXKESQIDAQABo3YwdDAOBgNVHQ8B
|
||||
Af8EBAMCBaAwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMAwGA1UdEwEB
|
||||
/wQCMAAwHwYDVR0jBBgwFoAU+8IZlLV/Qp5CXqpXMLvtxWlxcJwwFAYDVR0RBA0w
|
||||
C4IJYWNtZS50ZXN0MA0GCSqGSIb3DQEBCwUAA4IBAQB0pe8I5/VDkB5VMgQB2GJV
|
||||
GKzyigfWbVez9uLmqMj9PPP/zzYKSYeq+91aMuOZrnH7NqBxSTwanULkmqAmhbJJ
|
||||
YkXw+FlFekf9FyxcuArzwzzNZDSGcjcdXpN8S2K1qkBd00iSJF9kU7pdZYCIKR20
|
||||
QirdBrELEfsJ3GU62a6N3a2YsrisZUvq5TbjGJDcytAtt+WG3gmV7RInLdFfPwbw
|
||||
bEHPCnx0uiV0nxLjd/aVT+RceVrFQVt4hR99jLoMlBitSKluZ1ljsrpIyroBhQT0
|
||||
pp/pVi6HJdijG0fsPrC325NEGAwcpotLUhczoeM/rffKJd54wLhDkfYxOyRZXivs
|
||||
-----END CERTIFICATE-----
|
||||
27
nixos/tests/common/acme/server/acme.test.key.pem
Normal file
27
nixos/tests/common/acme/server/acme.test.key.pem
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
-----BEGIN RSA PRIVATE KEY-----
|
||||
MIIEowIBAAKCAQEAo8XjMVUaljcaqQ5MFhfPuQgSwdyXEUbpSHz+5yPkE0h9Z4Xu
|
||||
5BJF1Oq7h5ggCtadVsIspiY6Jm6aWDOjlh4myzW5UNBNUG3OPEk50vmmHFeHpImH
|
||||
O/d8yb33QoF9VRcTZs4tuJYg7l9bSs4jNG72vYvv2YiGAcmjJcsmAZIfniCNXf/L
|
||||
jIm+Cxykn+Vo3UuzO1w5/iuofdgWO/aZxMezmXUivlL3ih4cNzCJei8WlB/lEnHr
|
||||
kcy3ogRmmynP5zcz7vmGIJX2ji6dhCa4Got5B7eZK76o2QglhQXqPatG0AOYH+Rf
|
||||
QfzKemqPG5om9MgJtwFtTOU1LoaiBw//jXKESQIDAQABAoIBADox/2FwVFo8ioS4
|
||||
R+Ex5OZjMAcjU6sX/516jTmlT05q2+UFerYgqB/YqXqtW/V9/brulN8VhmRRuRbO
|
||||
grq9TBu5o3hMDK0f18EkZB/MBnLbx594H033y6gEkPBZAyhRYtuNOEH3VwxdZhtW
|
||||
1Lu1EoiYSUqLcNMBy6+KWJ8GRaXyacMYBlj2lMHmyzkA/t1+2mwTGC3lT6zN0F5Y
|
||||
E5umXOxsn6Tb6q3KM9O5IvtmMMKpgj4HIHZLZ6j40nNgHwGRaAv4Sha/vx0DeBw3
|
||||
6VlNiTTPdShEkhESlM5/ocqTfI92VHJpM5gkqTYOWBi2aKIPfAopXoqoJdWl4pQ/
|
||||
NCFIu2ECgYEAzntNKIcQtf0ewe0/POo07SIFirvz6jVtYNMTzeQfL6CoEjYArJeu
|
||||
Vzc4wEQfA4ZFVerBb1/O6M449gI3zex1PH4AX0h8q8DSjrppK1Jt2TnpVh97k7Gg
|
||||
Tnat/M/yW3lWYkcMVJJ3AYurXLFTT1dYP0HvBwZN04yInrEcPNXKfmcCgYEAywyJ
|
||||
51d4AE94PrANathKqSI/gk8sP+L1gzylZCcUEAiGk/1r45iYB4HN2gvWbS+CvSdp
|
||||
F7ShlDWrTaNh2Bm1dgTjc4pWb4J+CPy/KN2sgLwIuM4+ZWIZmEDcio6khrM/gNqK
|
||||
aR7xUsvWsqU26O84woY/xR8IHjSNF7cFWE1H2c8CgYEAt6SSi2kVQ8dMg84uYE8t
|
||||
o3qO00U3OycpkOQqyQQLeKC62veMwfRl6swCfX4Y11mkcTXJtPTRYd2Ia8StPUkB
|
||||
PDwUuKoPt/JXUvoYb59wc7M+BIsbrdBdc2u6cw+/zfutCNuH6/AYSBeg4WAVaIuW
|
||||
wSwzG1xP+8cR+5IqOzEqWCECgYATweeVTCyQEyuHJghYMi2poXx+iIesu7/aAkex
|
||||
pB/Oo5W8xrb90XZRnK7UHbzCqRHWqAQQ23Gxgztk9ZXqui2vCzC6qGZauV7cLwPG
|
||||
zTMg36sVmHP314DYEM+k59ZYiQ6P0jQPoIQo407D2VGrfsOOIhQIcUmP7tsfyJ5L
|
||||
hlGMfwKBgGq4VNnnuX8I5kl03NpaKfG+M8jEHmVwtI9RkPTCCX9bMjeG0cDxqPTF
|
||||
TRkf3r8UWQTZ5QfAfAXYAOlZvmGhHjSembRbXMrMdi3rGsYRSrQL6n5NHnORUaMy
|
||||
FCWo4gyAnniry7tx9dVNgmHmbjEHuQnf8AC1r3dibRCjvJWUiQ8H
|
||||
-----END RSA PRIVATE KEY-----
|
||||
20
nixos/tests/common/acme/server/ca.cert.pem
Normal file
20
nixos/tests/common/acme/server/ca.cert.pem
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
-----BEGIN CERTIFICATE-----
|
||||
MIIDSzCCAjOgAwIBAgIIeHRvRrNvbGQwDQYJKoZIhvcNAQELBQAwIDEeMBwGA1UE
|
||||
AxMVbWluaWNhIHJvb3QgY2EgNzg3NDZmMCAXDTIwMTAyMTEzMjgzNloYDzIxMjAx
|
||||
MDIxMTMyODM2WjAgMR4wHAYDVQQDExVtaW5pY2Egcm9vdCBjYSA3ODc0NmYwggEi
|
||||
MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCrNTzVLDJOKtGYGLU98EEcLKps
|
||||
tXHCLC6G54LKbEcU80fn+ArX8qsPSHyhdXQkcYjq6Vh/EDJ1TctyRSnvAjwyG4Aa
|
||||
1Zy1QFc/JnjMjvzimCkUc9lQ+wkLwHSM/KGwR1cGjmtQ/EMClZTA0NwulJsXMKVz
|
||||
bd5asXbq/yJTQ5Ww25HtdNjwRQXTvB7r3IKcY+DsED9CvFvC9oG/ZhtZqZuyyRdC
|
||||
kFUrrv8WNUDkWSN+lMR6xMx8v0583IN6f11IhX0b+svK98G81B2eswBdkzvVyv9M
|
||||
unZBO0JuJG8sdM502KhWLmzBC1ZbvgUBF9BumDRpMFH4DCj7+qQ2taWeGyc7AgMB
|
||||
AAGjgYYwgYMwDgYDVR0PAQH/BAQDAgKEMB0GA1UdJQQWMBQGCCsGAQUFBwMBBggr
|
||||
BgEFBQcDAjASBgNVHRMBAf8ECDAGAQH/AgEAMB0GA1UdDgQWBBT7whmUtX9CnkJe
|
||||
qlcwu+3FaXFwnDAfBgNVHSMEGDAWgBT7whmUtX9CnkJeqlcwu+3FaXFwnDANBgkq
|
||||
hkiG9w0BAQsFAAOCAQEARMe1wKmF33GjEoLLw0oDDS4EdAv26BzCwtrlljsEtwQN
|
||||
95oSzUNd6o4Js7WCG2o543OX6cxzM+yju8TES3+vJKDgsbNMU0bWCv//tdrb0/G8
|
||||
OkU3Kfi5q4fOauZ1pqGv/pXdfYhZ5ieB/zwis3ykANe5JfB0XqwCb1Vd0C3UCIS2
|
||||
NPKngRwNSzphIsbzfvxGDkdM1enuGl5CVyDhrwTMqGaJGDSOv6U5jKFxKRvigqTN
|
||||
Ls9lPmT5NXYETduWLBR3yUIdH6kZXrcozZ02B9vjOB2Cv4RMDc+9eM30CLIWpf1I
|
||||
097e7JkhzxFhfC/bMMt3P1FeQc+fwH91wdBmNi7tQw==
|
||||
-----END CERTIFICATE-----
|
||||
27
nixos/tests/common/acme/server/ca.key.pem
Normal file
27
nixos/tests/common/acme/server/ca.key.pem
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
-----BEGIN RSA PRIVATE KEY-----
|
||||
MIIEowIBAAKCAQEAqzU81SwyTirRmBi1PfBBHCyqbLVxwiwuhueCymxHFPNH5/gK
|
||||
1/KrD0h8oXV0JHGI6ulYfxAydU3LckUp7wI8MhuAGtWctUBXPyZ4zI784pgpFHPZ
|
||||
UPsJC8B0jPyhsEdXBo5rUPxDApWUwNDcLpSbFzClc23eWrF26v8iU0OVsNuR7XTY
|
||||
8EUF07we69yCnGPg7BA/QrxbwvaBv2YbWambsskXQpBVK67/FjVA5FkjfpTEesTM
|
||||
fL9OfNyDen9dSIV9G/rLyvfBvNQdnrMAXZM71cr/TLp2QTtCbiRvLHTOdNioVi5s
|
||||
wQtWW74FARfQbpg0aTBR+Awo+/qkNrWlnhsnOwIDAQABAoIBAA3ykVkgd5ysmlSU
|
||||
trcsCnHcJaojgff6l3PACoSpG4VWaGY6a8+54julgRm6MtMBONFCX0ZCsImj484U
|
||||
Wl0xRmwil2YYPuL5MeJgJPktMObY1IfpBCw3tz3w2M3fiuCMf0d2dMGtO1xLiUnH
|
||||
+hgFXTkfamsj6ThkOrbcQBSebeRxbKM5hqyCaQoieV+0IJnyxUVq/apib8N50VsH
|
||||
SHd4oqLUuEZgg6N70+l5DpzedJUb4nrwS/KhUHUBgnoPItYBCiGPmrwLk7fUhPs6
|
||||
kTDqJDtc/xW/JbjmzhWEpVvtumcC/OEKULss7HLdeQqwVBrRQkznb0M9AnSra3d0
|
||||
X11/Y4ECgYEA3FC8SquLPFb2lHK4+YbJ4Ac6QVWeYFEHiZ0Rj+CmONmjcAvOGLPE
|
||||
SblRLm3Nbrkxbm8FF6/AfXa/rviAKEVPs5xqGfSDw/3n1uInPcmShiBCLwM/jHH5
|
||||
NeVG+R5mTg5zyQ/pQMLWRcs+Ail+ZAnZuoGpW3Cdc8OtCUYFQ7XB6nsCgYEAxvBJ
|
||||
zFxcTtsDzWbMWXejugQiUqJcEbKWwEfkRbf3J2rAVO2+EFr7LxdRfN2VwPiTQcWc
|
||||
LnN2QN+ouOjqBMTh3qm5oQY+TLLHy86k9g1k0gXWkMRQgP2ZdfWH1HyrwjLUgLe1
|
||||
VezFN7N1azgy6xFkInAAvuA4loxElZNvkGBgekECgYA/Xw26ILvNIGqO6qzgQXAh
|
||||
+5I7JsiGheg4IjDiBMlrQtbrLMoceuD0H9UFGNplhel9DXwWgxxIOncKejpK2x0A
|
||||
2fX+/0FDh+4+9hA5ipiV8gN3iGSoHkSDxy5yC9d7jlapt+TtFt4Rd1OfxZWwatDw
|
||||
/8jaH3t6yAcmyrhK8KYVrwKBgAE5KwsBqmOlvyE9N5Z5QN189wUREIXfVkP6bTHs
|
||||
jq2EX4hmKdwJ4y+H8i1VY31bSfSGlY5HkXuWpH/2lrHO0CDBZG3UDwADvWzIaYVF
|
||||
0c/kz0v2mRQh+xaZmus4lQnNrDbaalgL666LAPbW0qFVaws3KxoBYPe0BxvwWyhF
|
||||
H3LBAoGBAKRRNsq2pWQ8Gqxc0rVoH0FlexU9U2ci3lsLmgEB0A/o/kQkSyAxaRM+
|
||||
VdKp3sWfO8o8lX5CVQslCNBSjDTNcat3Co4NEBLg6Xv1yKN/WN1GhusnchP9szsP
|
||||
oU47gC89QhUyWSd6vvr2z2NG9C3cACxe4dhDSHQcE4nHSldzCKv2
|
||||
-----END RSA PRIVATE KEY-----
|
||||
141
nixos/tests/common/acme/server/default.nix
Normal file
141
nixos/tests/common/acme/server/default.nix
Normal file
|
|
@ -0,0 +1,141 @@
|
|||
# The certificate for the ACME service is exported as:
|
||||
#
|
||||
# config.test-support.acme.caCert
|
||||
#
|
||||
# This value can be used inside the configuration of other test nodes to inject
|
||||
# the test certificate into security.pki.certificateFiles or into package
|
||||
# overlays.
|
||||
#
|
||||
# Another value that's needed if you don't use a custom resolver (see below for
|
||||
# notes on that) is to add the acme node as a nameserver to every node
|
||||
# that needs to acquire certificates using ACME, because otherwise the API host
|
||||
# for acme.test can't be resolved.
|
||||
#
|
||||
# A configuration example of a full node setup using this would be this:
|
||||
#
|
||||
# {
|
||||
# acme = import ./common/acme/server;
|
||||
#
|
||||
# example = { nodes, ... }: {
|
||||
# networking.nameservers = [
|
||||
# nodes.acme.config.networking.primaryIPAddress
|
||||
# ];
|
||||
# security.pki.certificateFiles = [
|
||||
# nodes.acme.config.test-support.acme.caCert
|
||||
# ];
|
||||
# };
|
||||
# }
|
||||
#
|
||||
# By default, this module runs a local resolver, generated using resolver.nix
|
||||
# from the parent directory to automatically discover all zones in the network.
|
||||
#
|
||||
# If you do not want this and want to use your own resolver, you can just
|
||||
# override networking.nameservers like this:
|
||||
#
|
||||
# {
|
||||
# acme = { nodes, lib, ... }: {
|
||||
# imports = [ ./common/acme/server ];
|
||||
# networking.nameservers = lib.mkForce [
|
||||
# nodes.myresolver.config.networking.primaryIPAddress
|
||||
# ];
|
||||
# };
|
||||
#
|
||||
# myresolver = ...;
|
||||
# }
|
||||
#
|
||||
# Keep in mind, that currently only _one_ resolver is supported, if you have
|
||||
# more than one resolver in networking.nameservers only the first one will be
|
||||
# used.
|
||||
#
|
||||
# Also make sure that whenever you use a resolver from a different test node
|
||||
# that it has to be started _before_ the ACME service.
|
||||
{ config, pkgs, lib, ... }:
|
||||
let
|
||||
testCerts = import ./snakeoil-certs.nix;
|
||||
domain = testCerts.domain;
|
||||
|
||||
resolver = let
|
||||
message = "You need to define a resolver for the acme test module.";
|
||||
firstNS = lib.head config.networking.nameservers;
|
||||
in if config.networking.nameservers == [] then throw message else firstNS;
|
||||
|
||||
pebbleConf.pebble = {
|
||||
listenAddress = "0.0.0.0:443";
|
||||
managementListenAddress = "0.0.0.0:15000";
|
||||
# These certs and keys are used for the Web Front End (WFE)
|
||||
certificate = testCerts.${domain}.cert;
|
||||
privateKey = testCerts.${domain}.key;
|
||||
httpPort = 80;
|
||||
tlsPort = 443;
|
||||
ocspResponderURL = "http://${domain}:4002";
|
||||
strict = true;
|
||||
};
|
||||
|
||||
pebbleConfFile = pkgs.writeText "pebble.conf" (builtins.toJSON pebbleConf);
|
||||
|
||||
in {
|
||||
imports = [ ../../resolver.nix ];
|
||||
|
||||
options.test-support.acme = with lib; {
|
||||
caDomain = mkOption {
|
||||
type = types.str;
|
||||
readOnly = true;
|
||||
default = domain;
|
||||
description = ''
|
||||
A domain name to use with the <literal>nodes</literal> attribute to
|
||||
identify the CA server.
|
||||
'';
|
||||
};
|
||||
caCert = mkOption {
|
||||
type = types.path;
|
||||
readOnly = true;
|
||||
default = testCerts.ca.cert;
|
||||
description = ''
|
||||
A certificate file to use with the <literal>nodes</literal> attribute to
|
||||
inject the test CA certificate used in the ACME server into
|
||||
<option>security.pki.certificateFiles</option>.
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
config = {
|
||||
test-support = {
|
||||
resolver.enable = let
|
||||
isLocalResolver = config.networking.nameservers == [ "127.0.0.1" ];
|
||||
in lib.mkOverride 900 isLocalResolver;
|
||||
};
|
||||
|
||||
# This has priority 140, because modules/testing/test-instrumentation.nix
|
||||
# already overrides this with priority 150.
|
||||
networking.nameservers = lib.mkOverride 140 [ "127.0.0.1" ];
|
||||
networking.firewall.allowedTCPPorts = [ 80 443 15000 4002 ];
|
||||
|
||||
networking.extraHosts = ''
|
||||
127.0.0.1 ${domain}
|
||||
${config.networking.primaryIPAddress} ${domain}
|
||||
'';
|
||||
|
||||
systemd.services = {
|
||||
pebble = {
|
||||
enable = true;
|
||||
description = "Pebble ACME server";
|
||||
wantedBy = [ "network.target" ];
|
||||
environment = {
|
||||
# We're not testing lego, we're just testing our configuration.
|
||||
# No need to sleep.
|
||||
PEBBLE_VA_NOSLEEP = "1";
|
||||
};
|
||||
|
||||
serviceConfig = {
|
||||
RuntimeDirectory = "pebble";
|
||||
WorkingDirectory = "/run/pebble";
|
||||
|
||||
# Required to bind on privileged ports.
|
||||
AmbientCapabilities = [ "CAP_NET_BIND_SERVICE" ];
|
||||
|
||||
ExecStart = "${pkgs.pebble}/bin/pebble -config ${pebbleConfFile}";
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
29
nixos/tests/common/acme/server/generate-certs.nix
Normal file
29
nixos/tests/common/acme/server/generate-certs.nix
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
# Minica can provide a CA key and cert, plus a key
|
||||
# and cert for our fake CA server's Web Front End (WFE).
|
||||
{
|
||||
pkgs ? import <nixpkgs> {},
|
||||
minica ? pkgs.minica,
|
||||
mkDerivation ? pkgs.stdenv.mkDerivation
|
||||
}:
|
||||
let
|
||||
conf = import ./snakeoil-certs.nix;
|
||||
domain = conf.domain;
|
||||
in mkDerivation {
|
||||
name = "test-certs";
|
||||
buildInputs = [ minica ];
|
||||
phases = [ "buildPhase" "installPhase" ];
|
||||
|
||||
buildPhase = ''
|
||||
minica \
|
||||
--ca-key ca.key.pem \
|
||||
--ca-cert ca.cert.pem \
|
||||
--domains ${domain}
|
||||
'';
|
||||
|
||||
installPhase = ''
|
||||
mkdir -p $out
|
||||
mv ca.*.pem $out/
|
||||
mv ${domain}/key.pem $out/${domain}.key.pem
|
||||
mv ${domain}/cert.pem $out/${domain}.cert.pem
|
||||
'';
|
||||
}
|
||||
13
nixos/tests/common/acme/server/snakeoil-certs.nix
Normal file
13
nixos/tests/common/acme/server/snakeoil-certs.nix
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
let
|
||||
domain = "acme.test";
|
||||
in {
|
||||
inherit domain;
|
||||
ca = {
|
||||
cert = ./ca.cert.pem;
|
||||
key = ./ca.key.pem;
|
||||
};
|
||||
"${domain}" = {
|
||||
cert = ./. + "/${domain}.cert.pem";
|
||||
key = ./. + "/${domain}.key.pem";
|
||||
};
|
||||
}
|
||||
68
nixos/tests/common/auto.nix
Normal file
68
nixos/tests/common/auto.nix
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
{ config, lib, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
|
||||
dmcfg = config.services.xserver.displayManager;
|
||||
cfg = config.test-support.displayManager.auto;
|
||||
|
||||
in
|
||||
|
||||
{
|
||||
|
||||
###### interface
|
||||
|
||||
options = {
|
||||
|
||||
test-support.displayManager.auto = {
|
||||
|
||||
enable = mkOption {
|
||||
default = false;
|
||||
description = ''
|
||||
Whether to enable the fake "auto" display manager, which
|
||||
automatically logs in the user specified in the
|
||||
<option>user</option> option. This is mostly useful for
|
||||
automated tests.
|
||||
'';
|
||||
};
|
||||
|
||||
user = mkOption {
|
||||
default = "root";
|
||||
description = "The user account to login automatically.";
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
|
||||
###### implementation
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
|
||||
services.xserver.displayManager = {
|
||||
lightdm.enable = true;
|
||||
autoLogin = {
|
||||
enable = true;
|
||||
user = cfg.user;
|
||||
};
|
||||
};
|
||||
|
||||
# lightdm by default doesn't allow auto login for root, which is
|
||||
# required by some nixos tests. Override it here.
|
||||
security.pam.services.lightdm-autologin.text = lib.mkForce ''
|
||||
auth requisite pam_nologin.so
|
||||
auth required pam_succeed_if.so quiet
|
||||
auth required pam_permit.so
|
||||
|
||||
account include lightdm
|
||||
|
||||
password include lightdm
|
||||
|
||||
session include lightdm
|
||||
'';
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
66
nixos/tests/common/ec2.nix
Normal file
66
nixos/tests/common/ec2.nix
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
{ pkgs, makeTest }:
|
||||
|
||||
with pkgs.lib;
|
||||
|
||||
{
|
||||
makeEc2Test = { name, image, userData, script, hostname ? "ec2-instance", sshPublicKey ? null, meta ? {} }:
|
||||
let
|
||||
metaData = pkgs.stdenv.mkDerivation {
|
||||
name = "metadata";
|
||||
buildCommand = ''
|
||||
mkdir -p $out/1.0/meta-data
|
||||
ln -s ${pkgs.writeText "userData" userData} $out/1.0/user-data
|
||||
echo "${hostname}" > $out/1.0/meta-data/hostname
|
||||
echo "(unknown)" > $out/1.0/meta-data/ami-manifest-path
|
||||
'' + optionalString (sshPublicKey != null) ''
|
||||
mkdir -p $out/1.0/meta-data/public-keys/0
|
||||
ln -s ${pkgs.writeText "sshPublicKey" sshPublicKey} $out/1.0/meta-data/public-keys/0/openssh-key
|
||||
'';
|
||||
};
|
||||
in makeTest {
|
||||
name = "ec2-" + name;
|
||||
nodes = {};
|
||||
testScript = ''
|
||||
import os
|
||||
import subprocess
|
||||
import tempfile
|
||||
|
||||
image_dir = os.path.join(
|
||||
os.environ.get("TMPDIR", tempfile.gettempdir()), "tmp", "vm-state-machine"
|
||||
)
|
||||
os.makedirs(image_dir, mode=0o700, exist_ok=True)
|
||||
disk_image = os.path.join(image_dir, "machine.qcow2")
|
||||
subprocess.check_call(
|
||||
[
|
||||
"qemu-img",
|
||||
"create",
|
||||
"-f",
|
||||
"qcow2",
|
||||
"-o",
|
||||
"backing_file=${image}",
|
||||
disk_image,
|
||||
]
|
||||
)
|
||||
subprocess.check_call(["qemu-img", "resize", disk_image, "10G"])
|
||||
|
||||
# Note: we use net=169.0.0.0/8 rather than
|
||||
# net=169.254.0.0/16 to prevent dhcpcd from getting horribly
|
||||
# confused. (It would get a DHCP lease in the 169.254.*
|
||||
# range, which it would then configure and prompty delete
|
||||
# again when it deletes link-local addresses.) Ideally we'd
|
||||
# turn off the DHCP server, but qemu does not have an option
|
||||
# to do that.
|
||||
start_command = (
|
||||
"qemu-kvm -m 1024"
|
||||
+ " -device virtio-net-pci,netdev=vlan0"
|
||||
+ " -netdev 'user,id=vlan0,net=169.0.0.0/8,guestfwd=tcp:169.254.169.254:80-cmd:${pkgs.micro-httpd}/bin/micro_httpd ${metaData}'"
|
||||
+ f" -drive file={disk_image},if=virtio,werror=report"
|
||||
+ " $QEMU_OPTS"
|
||||
)
|
||||
|
||||
machine = create_machine({"startCommand": start_command})
|
||||
'' + script;
|
||||
|
||||
inherit meta;
|
||||
};
|
||||
}
|
||||
24
nixos/tests/common/lxd/config.yaml
Normal file
24
nixos/tests/common/lxd/config.yaml
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
storage_pools:
|
||||
- name: default
|
||||
driver: dir
|
||||
config:
|
||||
source: /var/lxd-pool
|
||||
|
||||
networks:
|
||||
- name: lxdbr0
|
||||
type: bridge
|
||||
config:
|
||||
ipv4.address: auto
|
||||
ipv6.address: none
|
||||
|
||||
profiles:
|
||||
- name: default
|
||||
devices:
|
||||
eth0:
|
||||
name: eth0
|
||||
network: lxdbr0
|
||||
type: nic
|
||||
root:
|
||||
path: /
|
||||
pool: default
|
||||
type: disk
|
||||
141
nixos/tests/common/resolver.nix
Normal file
141
nixos/tests/common/resolver.nix
Normal file
|
|
@ -0,0 +1,141 @@
|
|||
# This module automatically discovers zones in BIND and NSD NixOS
|
||||
# configurations and creates zones for all definitions of networking.extraHosts
|
||||
# (except those that point to 127.0.0.1 or ::1) within the current test network
|
||||
# and delegates these zones using a fake root zone served by a BIND recursive
|
||||
# name server.
|
||||
{ config, nodes, pkgs, lib, ... }:
|
||||
|
||||
{
|
||||
options.test-support.resolver.enable = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
default = true;
|
||||
internal = true;
|
||||
description = ''
|
||||
Whether to enable the resolver that automatically discovers zone in the
|
||||
test network.
|
||||
|
||||
This option is <literal>true</literal> by default, because the module
|
||||
defining this option needs to be explicitly imported.
|
||||
|
||||
The reason this option exists is for the
|
||||
<filename>nixos/tests/common/acme/server</filename> module, which
|
||||
needs that option to disable the resolver once the user has set its own
|
||||
resolver.
|
||||
'';
|
||||
};
|
||||
|
||||
config = lib.mkIf config.test-support.resolver.enable {
|
||||
networking.firewall.enable = false;
|
||||
services.bind.enable = true;
|
||||
services.bind.cacheNetworks = lib.mkForce [ "any" ];
|
||||
services.bind.forwarders = lib.mkForce [];
|
||||
services.bind.zones = lib.singleton {
|
||||
name = ".";
|
||||
file = let
|
||||
addDot = zone: zone + lib.optionalString (!lib.hasSuffix "." zone) ".";
|
||||
mkNsdZoneNames = zones: map addDot (lib.attrNames zones);
|
||||
mkBindZoneNames = zones: map (zone: addDot zone.name) zones;
|
||||
getZones = cfg: mkNsdZoneNames cfg.services.nsd.zones
|
||||
++ mkBindZoneNames cfg.services.bind.zones;
|
||||
|
||||
getZonesForNode = attrs: {
|
||||
ip = attrs.config.networking.primaryIPAddress;
|
||||
zones = lib.filter (zone: zone != ".") (getZones attrs.config);
|
||||
};
|
||||
|
||||
zoneInfo = lib.mapAttrsToList (lib.const getZonesForNode) nodes;
|
||||
|
||||
# A and AAAA resource records for all the definitions of
|
||||
# networking.extraHosts except those for 127.0.0.1 or ::1.
|
||||
#
|
||||
# The result is an attribute set with keys being the host name and the
|
||||
# values are either { ipv4 = ADDR; } or { ipv6 = ADDR; } where ADDR is
|
||||
# the IP address for the corresponding key.
|
||||
recordsFromExtraHosts = let
|
||||
getHostsForNode = lib.const (n: n.config.networking.extraHosts);
|
||||
allHostsList = lib.mapAttrsToList getHostsForNode nodes;
|
||||
allHosts = lib.concatStringsSep "\n" allHostsList;
|
||||
|
||||
reIp = "[a-fA-F0-9.:]+";
|
||||
reHost = "[a-zA-Z0-9.-]+";
|
||||
|
||||
matchAliases = str: let
|
||||
matched = builtins.match "[ \t]+(${reHost})(.*)" str;
|
||||
continue = lib.singleton (lib.head matched)
|
||||
++ matchAliases (lib.last matched);
|
||||
in if matched == null then [] else continue;
|
||||
|
||||
matchLine = str: let
|
||||
result = builtins.match "[ \t]*(${reIp})[ \t]+(${reHost})(.*)" str;
|
||||
in if result == null then null else {
|
||||
ipAddr = lib.head result;
|
||||
hosts = lib.singleton (lib.elemAt result 1)
|
||||
++ matchAliases (lib.last result);
|
||||
};
|
||||
|
||||
skipLine = str: let
|
||||
rest = builtins.match "[^\n]*\n(.*)" str;
|
||||
in if rest == null then "" else lib.head rest;
|
||||
|
||||
getEntries = str: acc: let
|
||||
result = matchLine str;
|
||||
next = getEntries (skipLine str);
|
||||
newEntry = acc ++ lib.singleton result;
|
||||
continue = if result == null then next acc else next newEntry;
|
||||
in if str == "" then acc else continue;
|
||||
|
||||
isIPv6 = str: builtins.match ".*:.*" str != null;
|
||||
loopbackIps = [ "127.0.0.1" "::1" ];
|
||||
filterLoopback = lib.filter (e: !lib.elem e.ipAddr loopbackIps);
|
||||
|
||||
allEntries = lib.concatMap (entry: map (host: {
|
||||
inherit host;
|
||||
${if isIPv6 entry.ipAddr then "ipv6" else "ipv4"} = entry.ipAddr;
|
||||
}) entry.hosts) (filterLoopback (getEntries (allHosts + "\n") []));
|
||||
|
||||
mkRecords = entry: let
|
||||
records = lib.optional (entry ? ipv6) "AAAA ${entry.ipv6}"
|
||||
++ lib.optional (entry ? ipv4) "A ${entry.ipv4}";
|
||||
mkRecord = typeAndData: "${entry.host}. IN ${typeAndData}";
|
||||
in lib.concatMapStringsSep "\n" mkRecord records;
|
||||
|
||||
in lib.concatMapStringsSep "\n" mkRecords allEntries;
|
||||
|
||||
# All of the zones that are subdomains of existing zones.
|
||||
# For example if there is only "example.com" the following zones would
|
||||
# be 'subZones':
|
||||
#
|
||||
# * foo.example.com.
|
||||
# * bar.example.com.
|
||||
#
|
||||
# While the following would *not* be 'subZones':
|
||||
#
|
||||
# * example.com.
|
||||
# * com.
|
||||
#
|
||||
subZones = let
|
||||
allZones = lib.concatMap (zi: zi.zones) zoneInfo;
|
||||
isSubZoneOf = z1: z2: lib.hasSuffix z2 z1 && z1 != z2;
|
||||
in lib.filter (z: lib.any (isSubZoneOf z) allZones) allZones;
|
||||
|
||||
# All the zones without 'subZones'.
|
||||
filteredZoneInfo = map (zi: zi // {
|
||||
zones = lib.filter (x: !lib.elem x subZones) zi.zones;
|
||||
}) zoneInfo;
|
||||
|
||||
in pkgs.writeText "fake-root.zone" ''
|
||||
$TTL 3600
|
||||
. IN SOA ns.fakedns. admin.fakedns. ( 1 3h 1h 1w 1d )
|
||||
ns.fakedns. IN A ${config.networking.primaryIPAddress}
|
||||
. IN NS ns.fakedns.
|
||||
${lib.concatImapStrings (num: { ip, zones }: ''
|
||||
ns${toString num}.fakedns. IN A ${ip}
|
||||
${lib.concatMapStrings (zone: ''
|
||||
${zone} IN NS ns${toString num}.fakedns.
|
||||
'') zones}
|
||||
'') (lib.filter (zi: zi.zones != []) filteredZoneInfo)}
|
||||
${recordsFromExtraHosts}
|
||||
'';
|
||||
};
|
||||
};
|
||||
}
|
||||
15
nixos/tests/common/user-account.nix
Normal file
15
nixos/tests/common/user-account.nix
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
{ ... }:
|
||||
|
||||
{ users.users.alice =
|
||||
{ isNormalUser = true;
|
||||
description = "Alice Foobar";
|
||||
password = "foobar";
|
||||
uid = 1000;
|
||||
};
|
||||
|
||||
users.users.bob =
|
||||
{ isNormalUser = true;
|
||||
description = "Bob Foobar";
|
||||
password = "foobar";
|
||||
};
|
||||
}
|
||||
13
nixos/tests/common/wayland-cage.nix
Normal file
13
nixos/tests/common/wayland-cage.nix
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
{ ... }:
|
||||
|
||||
{
|
||||
imports = [ ./user-account.nix ];
|
||||
services.cage = {
|
||||
enable = true;
|
||||
user = "alice";
|
||||
};
|
||||
|
||||
virtualisation = {
|
||||
qemu.options = [ "-vga virtio" ];
|
||||
};
|
||||
}
|
||||
27
nixos/tests/common/webroot/news-rss.xml
Normal file
27
nixos/tests/common/webroot/news-rss.xml
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<rss xmlns:blogChannel="http://backend.userland.com/blogChannelModule" version="2.0">
|
||||
<channel>
|
||||
<title>NixOS News</title><link>https://nixos.org</link>
|
||||
<description>News for NixOS, the purely functional Linux distribution.</description>
|
||||
<image>
|
||||
<title>NixOS</title>
|
||||
<url>https://nixos.org/logo/nixos-logo-only-hires.png</url><link>https://nixos.org/</link>
|
||||
</image>
|
||||
<item>
|
||||
<title>NixOS 18.09 released</title><link>https://nixos.org/news.html</link>
|
||||
<description>
|
||||
<a href="https://github.com/NixOS/nixos-artwork/blob/master/releases/18.09-jellyfish/jellyfish.png">
|
||||
<img class="inline" src="logo/nixos-logo-18.09-jellyfish-lores.png" alt="18.09 Jellyfish logo" with="100" height="87"/>
|
||||
</a>
|
||||
NixOS 18.09 “Jellyfish” has been released, the tenth stable release branch.
|
||||
See the <a href="/nixos/manual/release-notes.html#sec-release-18.09">release notes</a>
|
||||
for details. You can get NixOS 18.09 ISOs and VirtualBox appliances
|
||||
from the <a href="nixos/download.html">download page</a>.
|
||||
For information on how to upgrade from older release branches
|
||||
to 18.09, check out the
|
||||
<a href="/nixos/manual/index.html#sec-upgrading">manual section on upgrading</a>.
|
||||
</description>
|
||||
<pubDate>Sat Oct 06 2018 00:00:00 GMT</pubDate>
|
||||
</item>
|
||||
</channel>
|
||||
</rss>
|
||||
17
nixos/tests/common/x11.nix
Normal file
17
nixos/tests/common/x11.nix
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
{ lib, ... }:
|
||||
|
||||
{
|
||||
imports = [
|
||||
./auto.nix
|
||||
];
|
||||
|
||||
services.xserver.enable = true;
|
||||
|
||||
# Automatically log in.
|
||||
test-support.displayManager.auto.enable = true;
|
||||
|
||||
# Use IceWM as the window manager.
|
||||
# Don't use a desktop manager.
|
||||
services.xserver.displayManager.defaultSession = lib.mkDefault "none+icewm";
|
||||
services.xserver.windowManager.icewm.enable = true;
|
||||
}
|
||||
229
nixos/tests/consul.nix
Normal file
229
nixos/tests/consul.nix
Normal file
|
|
@ -0,0 +1,229 @@
|
|||
import ./make-test-python.nix ({pkgs, lib, ...}:
|
||||
|
||||
let
|
||||
# Settings for both servers and agents
|
||||
webUi = true;
|
||||
retry_interval = "1s";
|
||||
raft_multiplier = 1;
|
||||
|
||||
defaultExtraConfig = {
|
||||
inherit retry_interval;
|
||||
performance = {
|
||||
inherit raft_multiplier;
|
||||
};
|
||||
};
|
||||
|
||||
allConsensusServerHosts = [
|
||||
"192.168.1.1"
|
||||
"192.168.1.2"
|
||||
"192.168.1.3"
|
||||
];
|
||||
|
||||
allConsensusClientHosts = [
|
||||
"192.168.2.1"
|
||||
"192.168.2.2"
|
||||
];
|
||||
|
||||
firewallSettings = {
|
||||
# See https://www.consul.io/docs/install/ports.html
|
||||
allowedTCPPorts = [ 8301 8302 8600 8500 8300 ];
|
||||
allowedUDPPorts = [ 8301 8302 8600 ];
|
||||
};
|
||||
|
||||
client = index: { pkgs, ... }:
|
||||
let
|
||||
ip = builtins.elemAt allConsensusClientHosts index;
|
||||
in
|
||||
{
|
||||
environment.systemPackages = [ pkgs.consul ];
|
||||
|
||||
networking.interfaces.eth1.ipv4.addresses = pkgs.lib.mkOverride 0 [
|
||||
{ address = ip; prefixLength = 16; }
|
||||
];
|
||||
networking.firewall = firewallSettings;
|
||||
|
||||
services.consul = {
|
||||
enable = true;
|
||||
inherit webUi;
|
||||
extraConfig = defaultExtraConfig // {
|
||||
server = false;
|
||||
retry_join = allConsensusServerHosts;
|
||||
bind_addr = ip;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
server = index: { pkgs, ... }:
|
||||
let
|
||||
numConsensusServers = builtins.length allConsensusServerHosts;
|
||||
thisConsensusServerHost = builtins.elemAt allConsensusServerHosts index;
|
||||
ip = thisConsensusServerHost; # since we already use IPs to identify servers
|
||||
in
|
||||
{
|
||||
networking.interfaces.eth1.ipv4.addresses = pkgs.lib.mkOverride 0 [
|
||||
{ address = ip; prefixLength = 16; }
|
||||
];
|
||||
networking.firewall = firewallSettings;
|
||||
|
||||
services.consul =
|
||||
assert builtins.elem thisConsensusServerHost allConsensusServerHosts;
|
||||
{
|
||||
enable = true;
|
||||
inherit webUi;
|
||||
extraConfig = defaultExtraConfig // {
|
||||
server = true;
|
||||
bootstrap_expect = numConsensusServers;
|
||||
# Tell Consul that we never intend to drop below this many servers.
|
||||
# Ensures to not permanently lose consensus after temporary loss.
|
||||
# See https://github.com/hashicorp/consul/issues/8118#issuecomment-645330040
|
||||
autopilot.min_quorum = numConsensusServers;
|
||||
retry_join =
|
||||
# If there's only 1 node in the network, we allow self-join;
|
||||
# otherwise, the node must not try to join itself, and join only the other servers.
|
||||
# See https://github.com/hashicorp/consul/issues/2868
|
||||
if numConsensusServers == 1
|
||||
then allConsensusServerHosts
|
||||
else builtins.filter (h: h != thisConsensusServerHost) allConsensusServerHosts;
|
||||
bind_addr = ip;
|
||||
};
|
||||
};
|
||||
};
|
||||
in {
|
||||
name = "consul";
|
||||
|
||||
nodes = {
|
||||
server1 = server 0;
|
||||
server2 = server 1;
|
||||
server3 = server 2;
|
||||
|
||||
client1 = client 0;
|
||||
client2 = client 1;
|
||||
};
|
||||
|
||||
testScript = ''
|
||||
servers = [server1, server2, server3]
|
||||
machines = [server1, server2, server3, client1, client2]
|
||||
|
||||
for m in machines:
|
||||
m.wait_for_unit("consul.service")
|
||||
|
||||
|
||||
def wait_for_healthy_servers():
|
||||
# See https://github.com/hashicorp/consul/issues/8118#issuecomment-645330040
|
||||
# for why the `Voter` column of `list-peers` has that info.
|
||||
# TODO: The `grep true` relies on the fact that currently in
|
||||
# the output like
|
||||
# # consul operator raft list-peers
|
||||
# Node ID Address State Voter RaftProtocol
|
||||
# server3 ... 192.168.1.3:8300 leader true 3
|
||||
# server2 ... 192.168.1.2:8300 follower true 3
|
||||
# server1 ... 192.168.1.1:8300 follower false 3
|
||||
# `Voter`is the only boolean column.
|
||||
# Change this to the more reliable way to be defined by
|
||||
# https://github.com/hashicorp/consul/issues/8118
|
||||
# once that ticket is closed.
|
||||
for m in machines:
|
||||
m.wait_until_succeeds(
|
||||
"[ $(consul operator raft list-peers | grep true | wc -l) == 3 ]"
|
||||
)
|
||||
|
||||
|
||||
def wait_for_all_machines_alive():
|
||||
"""
|
||||
Note that Serf-"alive" does not mean "Raft"-healthy;
|
||||
see `wait_for_healthy_servers()` for that instead.
|
||||
"""
|
||||
for m in machines:
|
||||
m.wait_until_succeeds("[ $(consul members | grep -o alive | wc -l) == 5 ]")
|
||||
|
||||
|
||||
wait_for_healthy_servers()
|
||||
# Also wait for clients to be alive.
|
||||
wait_for_all_machines_alive()
|
||||
|
||||
client1.succeed("consul kv put testkey 42")
|
||||
client2.succeed("[ $(consul kv get testkey) == 42 ]")
|
||||
|
||||
|
||||
def rolling_reboot_test(proper_rolling_procedure=True):
|
||||
"""
|
||||
Tests that the cluster can tolearate failures of any single server,
|
||||
following the recommended rolling upgrade procedure from
|
||||
https://www.consul.io/docs/upgrading#standard-upgrades.
|
||||
|
||||
Optionally, `proper_rolling_procedure=False` can be given
|
||||
to wait only for each server to be back `Healthy`, not `Stable`
|
||||
in the Raft consensus, see Consul setting `ServerStabilizationTime` and
|
||||
https://github.com/hashicorp/consul/issues/8118#issuecomment-645330040.
|
||||
"""
|
||||
|
||||
for server in servers:
|
||||
server.crash()
|
||||
|
||||
# For each client, wait until they have connection again
|
||||
# using `kv get -recurse` before issuing commands.
|
||||
client1.wait_until_succeeds("consul kv get -recurse")
|
||||
client2.wait_until_succeeds("consul kv get -recurse")
|
||||
|
||||
# Do some consul actions while one server is down.
|
||||
client1.succeed("consul kv put testkey 43")
|
||||
client2.succeed("[ $(consul kv get testkey) == 43 ]")
|
||||
client2.succeed("consul kv delete testkey")
|
||||
|
||||
# Restart crashed machine.
|
||||
server.start()
|
||||
|
||||
if proper_rolling_procedure:
|
||||
# Wait for recovery.
|
||||
wait_for_healthy_servers()
|
||||
else:
|
||||
# NOT proper rolling upgrade procedure, see above.
|
||||
wait_for_all_machines_alive()
|
||||
|
||||
# Wait for client connections.
|
||||
client1.wait_until_succeeds("consul kv get -recurse")
|
||||
client2.wait_until_succeeds("consul kv get -recurse")
|
||||
|
||||
# Do some consul actions with server back up.
|
||||
client1.succeed("consul kv put testkey 44")
|
||||
client2.succeed("[ $(consul kv get testkey) == 44 ]")
|
||||
client2.succeed("consul kv delete testkey")
|
||||
|
||||
|
||||
def all_servers_crash_simultaneously_test():
|
||||
"""
|
||||
Tests that the cluster will eventually come back after all
|
||||
servers crash simultaneously.
|
||||
"""
|
||||
|
||||
for server in servers:
|
||||
server.crash()
|
||||
|
||||
for server in servers:
|
||||
server.start()
|
||||
|
||||
# Wait for recovery.
|
||||
wait_for_healthy_servers()
|
||||
|
||||
# Wait for client connections.
|
||||
client1.wait_until_succeeds("consul kv get -recurse")
|
||||
client2.wait_until_succeeds("consul kv get -recurse")
|
||||
|
||||
# Do some consul actions with servers back up.
|
||||
client1.succeed("consul kv put testkey 44")
|
||||
client2.succeed("[ $(consul kv get testkey) == 44 ]")
|
||||
client2.succeed("consul kv delete testkey")
|
||||
|
||||
|
||||
# Run the tests.
|
||||
|
||||
print("rolling_reboot_test()")
|
||||
rolling_reboot_test()
|
||||
|
||||
print("all_servers_crash_simultaneously_test()")
|
||||
all_servers_crash_simultaneously_test()
|
||||
|
||||
print("rolling_reboot_test(proper_rolling_procedure=False)")
|
||||
rolling_reboot_test(proper_rolling_procedure=False)
|
||||
'';
|
||||
})
|
||||
99
nixos/tests/containers-bridge.nix
Normal file
99
nixos/tests/containers-bridge.nix
Normal file
|
|
@ -0,0 +1,99 @@
|
|||
let
|
||||
hostIp = "192.168.0.1";
|
||||
containerIp = "192.168.0.100/24";
|
||||
hostIp6 = "fc00::1";
|
||||
containerIp6 = "fc00::2/7";
|
||||
in
|
||||
|
||||
import ./make-test-python.nix ({ pkgs, lib, ... }: {
|
||||
name = "containers-bridge";
|
||||
meta = {
|
||||
maintainers = with lib.maintainers; [ aristid aszlig eelco kampfschlaefer ];
|
||||
};
|
||||
|
||||
nodes.machine =
|
||||
{ pkgs, ... }:
|
||||
{ imports = [ ../modules/installer/cd-dvd/channel.nix ];
|
||||
virtualisation.writableStore = true;
|
||||
|
||||
networking.bridges = {
|
||||
br0 = {
|
||||
interfaces = [];
|
||||
};
|
||||
};
|
||||
networking.interfaces = {
|
||||
br0 = {
|
||||
ipv4.addresses = [{ address = hostIp; prefixLength = 24; }];
|
||||
ipv6.addresses = [{ address = hostIp6; prefixLength = 7; }];
|
||||
};
|
||||
};
|
||||
|
||||
containers.webserver =
|
||||
{
|
||||
autoStart = true;
|
||||
privateNetwork = true;
|
||||
hostBridge = "br0";
|
||||
localAddress = containerIp;
|
||||
localAddress6 = containerIp6;
|
||||
config =
|
||||
{ services.httpd.enable = true;
|
||||
services.httpd.adminAddr = "foo@example.org";
|
||||
networking.firewall.allowedTCPPorts = [ 80 ];
|
||||
};
|
||||
};
|
||||
|
||||
containers.web-noip =
|
||||
{
|
||||
autoStart = true;
|
||||
privateNetwork = true;
|
||||
hostBridge = "br0";
|
||||
config =
|
||||
{ services.httpd.enable = true;
|
||||
services.httpd.adminAddr = "foo@example.org";
|
||||
networking.firewall.allowedTCPPorts = [ 80 ];
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
virtualisation.additionalPaths = [ pkgs.stdenv ];
|
||||
};
|
||||
|
||||
testScript = ''
|
||||
machine.wait_for_unit("default.target")
|
||||
assert "webserver" in machine.succeed("nixos-container list")
|
||||
|
||||
with subtest("Start the webserver container"):
|
||||
assert "up" in machine.succeed("nixos-container status webserver")
|
||||
|
||||
with subtest("Bridges exist inside containers"):
|
||||
machine.succeed(
|
||||
"nixos-container run webserver -- ip link show eth0",
|
||||
"nixos-container run web-noip -- ip link show eth0",
|
||||
)
|
||||
|
||||
ip = "${containerIp}".split("/")[0]
|
||||
machine.succeed(f"ping -n -c 1 {ip}")
|
||||
machine.succeed(f"curl --fail http://{ip}/ > /dev/null")
|
||||
|
||||
ip6 = "${containerIp6}".split("/")[0]
|
||||
machine.succeed(f"ping -n -c 1 {ip6}")
|
||||
machine.succeed(f"curl --fail http://[{ip6}]/ > /dev/null")
|
||||
|
||||
with subtest(
|
||||
"nixos-container show-ip works in case of an ipv4 address "
|
||||
+ "with subnetmask in CIDR notation."
|
||||
):
|
||||
result = machine.succeed("nixos-container show-ip webserver").rstrip()
|
||||
assert result == ip
|
||||
|
||||
with subtest("Stop the container"):
|
||||
machine.succeed("nixos-container stop webserver")
|
||||
machine.fail(
|
||||
f"curl --fail --connect-timeout 2 http://{ip}/ > /dev/null",
|
||||
f"curl --fail --connect-timeout 2 http://[{ip6}]/ > /dev/null",
|
||||
)
|
||||
|
||||
# Destroying a declarative container should fail.
|
||||
machine.fail("nixos-container destroy webserver")
|
||||
'';
|
||||
})
|
||||
34
nixos/tests/containers-custom-pkgs.nix
Normal file
34
nixos/tests/containers-custom-pkgs.nix
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
import ./make-test-python.nix ({ pkgs, lib, ... }: let
|
||||
|
||||
customPkgs = pkgs.appendOverlays [ (self: super: {
|
||||
hello = super.hello.overrideAttrs (old: {
|
||||
name = "custom-hello";
|
||||
});
|
||||
}) ];
|
||||
|
||||
in {
|
||||
name = "containers-custom-pkgs";
|
||||
meta = {
|
||||
maintainers = with lib.maintainers; [ adisbladis earvstedt ];
|
||||
};
|
||||
|
||||
nodes.machine = { config, ... }: {
|
||||
assertions = let
|
||||
helloName = (builtins.head config.containers.test.config.system.extraDependencies).name;
|
||||
in [ {
|
||||
assertion = helloName == "custom-hello";
|
||||
message = "Unexpected value: ${helloName}";
|
||||
} ];
|
||||
|
||||
containers.test = {
|
||||
autoStart = true;
|
||||
config = { pkgs, config, ... }: {
|
||||
nixpkgs.pkgs = customPkgs;
|
||||
system.extraDependencies = [ pkgs.hello ];
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
# This test only consists of evaluating the test machine
|
||||
testScript = "pass";
|
||||
})
|
||||
54
nixos/tests/containers-ephemeral.nix
Normal file
54
nixos/tests/containers-ephemeral.nix
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
import ./make-test-python.nix ({ pkgs, lib, ... }: {
|
||||
name = "containers-ephemeral";
|
||||
meta = {
|
||||
maintainers = with lib.maintainers; [ patryk27 ];
|
||||
};
|
||||
|
||||
nodes.machine = { pkgs, ... }: {
|
||||
virtualisation.writableStore = true;
|
||||
|
||||
containers.webserver = {
|
||||
ephemeral = true;
|
||||
privateNetwork = true;
|
||||
hostAddress = "10.231.136.1";
|
||||
localAddress = "10.231.136.2";
|
||||
config = {
|
||||
services.nginx = {
|
||||
enable = true;
|
||||
virtualHosts.localhost = {
|
||||
root = pkgs.runCommand "localhost" {} ''
|
||||
mkdir "$out"
|
||||
echo hello world > "$out/index.html"
|
||||
'';
|
||||
};
|
||||
};
|
||||
networking.firewall.allowedTCPPorts = [ 80 ];
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
testScript = ''
|
||||
assert "webserver" in machine.succeed("nixos-container list")
|
||||
|
||||
machine.succeed("nixos-container start webserver")
|
||||
|
||||
with subtest("Container got its own root folder"):
|
||||
machine.succeed("ls /run/nixos-containers/webserver")
|
||||
|
||||
with subtest("Container persistent directory is not created"):
|
||||
machine.fail("ls /var/lib/nixos-containers/webserver")
|
||||
|
||||
# Since "start" returns after the container has reached
|
||||
# multi-user.target, we should now be able to access it.
|
||||
ip = machine.succeed("nixos-container show-ip webserver").rstrip()
|
||||
machine.succeed(f"ping -n -c1 {ip}")
|
||||
machine.succeed(f"curl --fail http://{ip}/ > /dev/null")
|
||||
|
||||
with subtest("Stop the container"):
|
||||
machine.succeed("nixos-container stop webserver")
|
||||
machine.fail(f"curl --fail --connect-timeout 2 http://{ip}/ > /dev/null")
|
||||
|
||||
with subtest("Container's root folder was removed"):
|
||||
machine.fail("ls /run/nixos-containers/webserver")
|
||||
'';
|
||||
})
|
||||
91
nixos/tests/containers-extra_veth.nix
Normal file
91
nixos/tests/containers-extra_veth.nix
Normal file
|
|
@ -0,0 +1,91 @@
|
|||
import ./make-test-python.nix ({ pkgs, lib, ... }: {
|
||||
name = "containers-extra_veth";
|
||||
meta = {
|
||||
maintainers = with lib.maintainers; [ kampfschlaefer ];
|
||||
};
|
||||
|
||||
nodes.machine =
|
||||
{ pkgs, ... }:
|
||||
{ imports = [ ../modules/installer/cd-dvd/channel.nix ];
|
||||
virtualisation.writableStore = true;
|
||||
virtualisation.vlans = [];
|
||||
|
||||
networking.useDHCP = false;
|
||||
networking.bridges = {
|
||||
br0 = {
|
||||
interfaces = [];
|
||||
};
|
||||
br1 = { interfaces = []; };
|
||||
};
|
||||
networking.interfaces = {
|
||||
br0 = {
|
||||
ipv4.addresses = [{ address = "192.168.0.1"; prefixLength = 24; }];
|
||||
ipv6.addresses = [{ address = "fc00::1"; prefixLength = 7; }];
|
||||
};
|
||||
br1 = {
|
||||
ipv4.addresses = [{ address = "192.168.1.1"; prefixLength = 24; }];
|
||||
};
|
||||
};
|
||||
|
||||
containers.webserver =
|
||||
{
|
||||
autoStart = true;
|
||||
privateNetwork = true;
|
||||
hostBridge = "br0";
|
||||
localAddress = "192.168.0.100/24";
|
||||
localAddress6 = "fc00::2/7";
|
||||
extraVeths = {
|
||||
veth1 = { hostBridge = "br1"; localAddress = "192.168.1.100/24"; };
|
||||
veth2 = { hostAddress = "192.168.2.1"; localAddress = "192.168.2.100"; };
|
||||
};
|
||||
config =
|
||||
{
|
||||
networking.firewall.allowedTCPPorts = [ 80 ];
|
||||
};
|
||||
};
|
||||
|
||||
virtualisation.additionalPaths = [ pkgs.stdenv ];
|
||||
};
|
||||
|
||||
testScript =
|
||||
''
|
||||
machine.wait_for_unit("default.target")
|
||||
assert "webserver" in machine.succeed("nixos-container list")
|
||||
|
||||
with subtest("Status of the webserver container is up"):
|
||||
assert "up" in machine.succeed("nixos-container status webserver")
|
||||
|
||||
with subtest("Ensure that the veths are inside the container"):
|
||||
assert "state UP" in machine.succeed(
|
||||
"nixos-container run webserver -- ip link show veth1"
|
||||
)
|
||||
assert "state UP" in machine.succeed(
|
||||
"nixos-container run webserver -- ip link show veth2"
|
||||
)
|
||||
|
||||
with subtest("Ensure the presence of the extra veths"):
|
||||
assert "state UP" in machine.succeed("ip link show veth1")
|
||||
assert "state UP" in machine.succeed("ip link show veth2")
|
||||
|
||||
with subtest("Ensure the veth1 is part of br1 on the host"):
|
||||
assert "master br1" in machine.succeed("ip link show veth1")
|
||||
|
||||
with subtest("Ping on main veth"):
|
||||
machine.succeed("ping -n -c 1 192.168.0.100")
|
||||
machine.succeed("ping -n -c 1 fc00::2")
|
||||
|
||||
with subtest("Ping on the first extra veth"):
|
||||
machine.succeed("ping -n -c 1 192.168.1.100 >&2")
|
||||
|
||||
with subtest("Ping on the second extra veth"):
|
||||
machine.succeed("ping -n -c 1 192.168.2.100 >&2")
|
||||
|
||||
with subtest("Container can be stopped"):
|
||||
machine.succeed("nixos-container stop webserver")
|
||||
machine.fail("ping -n -c 1 192.168.1.100 >&2")
|
||||
machine.fail("ping -n -c 1 192.168.2.100 >&2")
|
||||
|
||||
with subtest("Destroying a declarative container should fail"):
|
||||
machine.fail("nixos-container destroy webserver")
|
||||
'';
|
||||
})
|
||||
49
nixos/tests/containers-hosts.nix
Normal file
49
nixos/tests/containers-hosts.nix
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
import ./make-test-python.nix ({ pkgs, lib, ... }: {
|
||||
name = "containers-hosts";
|
||||
meta = {
|
||||
maintainers = with lib.maintainers; [ montag451 ];
|
||||
};
|
||||
|
||||
nodes.machine =
|
||||
{ lib, ... }:
|
||||
{
|
||||
virtualisation.vlans = [];
|
||||
|
||||
networking.bridges.br0.interfaces = [];
|
||||
networking.interfaces.br0.ipv4.addresses = [
|
||||
{ address = "10.11.0.254"; prefixLength = 24; }
|
||||
];
|
||||
|
||||
# Force /etc/hosts to be the only source for host name resolution
|
||||
environment.etc."nsswitch.conf".text = lib.mkForce ''
|
||||
hosts: files
|
||||
'';
|
||||
|
||||
containers.simple = {
|
||||
autoStart = true;
|
||||
privateNetwork = true;
|
||||
localAddress = "10.10.0.1";
|
||||
hostAddress = "10.10.0.254";
|
||||
|
||||
config = {};
|
||||
};
|
||||
|
||||
containers.netmask = {
|
||||
autoStart = true;
|
||||
privateNetwork = true;
|
||||
hostBridge = "br0";
|
||||
localAddress = "10.11.0.1/24";
|
||||
|
||||
config = {};
|
||||
};
|
||||
};
|
||||
|
||||
testScript = ''
|
||||
start_all()
|
||||
machine.wait_for_unit("default.target")
|
||||
|
||||
with subtest("Ping the containers using the entries added in /etc/hosts"):
|
||||
for host in "simple.containers", "netmask.containers":
|
||||
machine.succeed(f"ping -n -c 1 {host}")
|
||||
'';
|
||||
})
|
||||
167
nixos/tests/containers-imperative.nix
Normal file
167
nixos/tests/containers-imperative.nix
Normal file
|
|
@ -0,0 +1,167 @@
|
|||
import ./make-test-python.nix ({ pkgs, lib, ... }: {
|
||||
name = "containers-imperative";
|
||||
meta = {
|
||||
maintainers = with lib.maintainers; [ aristid aszlig eelco kampfschlaefer ];
|
||||
};
|
||||
|
||||
nodes.machine =
|
||||
{ config, pkgs, lib, ... }:
|
||||
{ imports = [ ../modules/installer/cd-dvd/channel.nix ];
|
||||
|
||||
# XXX: Sandbox setup fails while trying to hardlink files from the host's
|
||||
# store file system into the prepared chroot directory.
|
||||
nix.settings.sandbox = false;
|
||||
nix.settings.substituters = []; # don't try to access cache.nixos.org
|
||||
|
||||
virtualisation.writableStore = true;
|
||||
# Make sure we always have all the required dependencies for creating a
|
||||
# container available within the VM, because we don't have network access.
|
||||
virtualisation.additionalPaths = let
|
||||
emptyContainer = import ../lib/eval-config.nix {
|
||||
modules = lib.singleton {
|
||||
nixpkgs = { inherit (config.nixpkgs) localSystem; };
|
||||
|
||||
containers.foo.config = {
|
||||
system.stateVersion = "18.03";
|
||||
};
|
||||
};
|
||||
};
|
||||
in with pkgs; [
|
||||
stdenv stdenvNoCC emptyContainer.config.containers.foo.path
|
||||
libxslt desktop-file-utils texinfo docbook5 libxml2
|
||||
docbook_xsl_ns xorg.lndir documentation-highlighter
|
||||
];
|
||||
};
|
||||
|
||||
testScript = let
|
||||
tmpfilesContainerConfig = pkgs.writeText "container-config-tmpfiles" ''
|
||||
{
|
||||
systemd.tmpfiles.rules = [ "d /foo - - - - -" ];
|
||||
systemd.services.foo = {
|
||||
serviceConfig.Type = "oneshot";
|
||||
script = "ls -al /foo";
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
};
|
||||
}
|
||||
'';
|
||||
brokenCfg = pkgs.writeText "broken.nix" ''
|
||||
{
|
||||
assertions = [
|
||||
{ assertion = false;
|
||||
message = "I never evaluate";
|
||||
}
|
||||
];
|
||||
}
|
||||
'';
|
||||
in ''
|
||||
with subtest("Make sure we have a NixOS tree (required by ‘nixos-container create’)"):
|
||||
machine.succeed("PAGER=cat nix-env -qa -A nixos.hello >&2")
|
||||
|
||||
id1, id2 = None, None
|
||||
|
||||
with subtest("Create some containers imperatively"):
|
||||
id1 = machine.succeed("nixos-container create foo --ensure-unique-name").rstrip()
|
||||
machine.log(f"created container {id1}")
|
||||
|
||||
id2 = machine.succeed("nixos-container create foo --ensure-unique-name").rstrip()
|
||||
machine.log(f"created container {id2}")
|
||||
|
||||
assert id1 != id2
|
||||
|
||||
with subtest(f"Put the root of {id2} into a bind mount"):
|
||||
machine.succeed(
|
||||
f"mv /var/lib/nixos-containers/{id2} /id2-bindmount",
|
||||
f"mount --bind /id2-bindmount /var/lib/nixos-containers/{id1}",
|
||||
)
|
||||
|
||||
ip1 = machine.succeed(f"nixos-container show-ip {id1}").rstrip()
|
||||
ip2 = machine.succeed(f"nixos-container show-ip {id2}").rstrip()
|
||||
assert ip1 != ip2
|
||||
|
||||
with subtest(
|
||||
"Create a directory and a file we can later check if it still exists "
|
||||
+ "after destruction of the container"
|
||||
):
|
||||
machine.succeed("mkdir /nested-bindmount")
|
||||
machine.succeed("echo important data > /nested-bindmount/dummy")
|
||||
|
||||
with subtest(
|
||||
"Create a directory with a dummy file and bind-mount it into both containers."
|
||||
):
|
||||
for id in id1, id2:
|
||||
important_path = f"/var/lib/nixos-containers/{id}/very/important/data"
|
||||
machine.succeed(
|
||||
f"mkdir -p {important_path}",
|
||||
f"mount --bind /nested-bindmount {important_path}",
|
||||
)
|
||||
|
||||
with subtest("Start one of them"):
|
||||
machine.succeed(f"nixos-container start {id1}")
|
||||
|
||||
with subtest("Execute commands via the root shell"):
|
||||
assert "Linux" in machine.succeed(f"nixos-container run {id1} -- uname")
|
||||
|
||||
with subtest("Execute a nix command via the root shell. (regression test for #40355)"):
|
||||
machine.succeed(
|
||||
f"nixos-container run {id1} -- nix-instantiate -E "
|
||||
+ '\'derivation { name = "empty"; builder = "false"; system = "false"; }\' '
|
||||
)
|
||||
|
||||
with subtest("Stop and start (regression test for #4989)"):
|
||||
machine.succeed(f"nixos-container stop {id1}")
|
||||
machine.succeed(f"nixos-container start {id1}")
|
||||
|
||||
# clear serial backlog for next tests
|
||||
machine.succeed("logger eat console backlog 3ea46eb2-7f82-4f70-b810-3f00e3dd4c4d")
|
||||
machine.wait_for_console_text(
|
||||
"eat console backlog 3ea46eb2-7f82-4f70-b810-3f00e3dd4c4d"
|
||||
)
|
||||
|
||||
with subtest("Stop a container early"):
|
||||
machine.succeed(f"nixos-container stop {id1}")
|
||||
machine.succeed(f"nixos-container start {id1} >&2 &")
|
||||
machine.wait_for_console_text("Stage 2")
|
||||
machine.succeed(f"nixos-container stop {id1}")
|
||||
machine.wait_for_console_text(f"Container {id1} exited successfully")
|
||||
machine.succeed(f"nixos-container start {id1}")
|
||||
|
||||
with subtest("Stop a container without machined (regression test for #109695)"):
|
||||
machine.systemctl("stop systemd-machined")
|
||||
machine.succeed(f"nixos-container stop {id1}")
|
||||
machine.wait_for_console_text(f"Container {id1} has been shut down")
|
||||
machine.succeed(f"nixos-container start {id1}")
|
||||
|
||||
with subtest("tmpfiles are present"):
|
||||
machine.log("creating container tmpfiles")
|
||||
machine.succeed(
|
||||
"nixos-container create tmpfiles --config-file ${tmpfilesContainerConfig}"
|
||||
)
|
||||
machine.log("created, starting…")
|
||||
machine.succeed("nixos-container start tmpfiles")
|
||||
machine.log("done starting, investigating…")
|
||||
machine.succeed(
|
||||
"echo $(nixos-container run tmpfiles -- systemctl is-active foo.service) | grep -q active;"
|
||||
)
|
||||
machine.succeed("nixos-container destroy tmpfiles")
|
||||
|
||||
with subtest("Execute commands via the root shell"):
|
||||
assert "Linux" in machine.succeed(f"nixos-container run {id1} -- uname")
|
||||
|
||||
with subtest("Destroy the containers"):
|
||||
for id in id1, id2:
|
||||
machine.succeed(f"nixos-container destroy {id}")
|
||||
|
||||
with subtest("Check whether destruction of any container has killed important data"):
|
||||
machine.succeed("grep -qF 'important data' /nested-bindmount/dummy")
|
||||
|
||||
with subtest("Ensure that the container path is gone"):
|
||||
print(machine.succeed("ls -lsa /var/lib/nixos-containers"))
|
||||
machine.succeed(f"test ! -e /var/lib/nixos-containers/{id1}")
|
||||
|
||||
with subtest("Ensure that a failed container creation doesn'leave any state"):
|
||||
machine.fail(
|
||||
"nixos-container create b0rk --config-file ${brokenCfg}"
|
||||
)
|
||||
machine.succeed("test ! -e /var/lib/nixos-containers/b0rk")
|
||||
'';
|
||||
})
|
||||
74
nixos/tests/containers-ip.nix
Normal file
74
nixos/tests/containers-ip.nix
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
let
|
||||
webserverFor = hostAddress: localAddress: {
|
||||
inherit hostAddress localAddress;
|
||||
privateNetwork = true;
|
||||
config = {
|
||||
services.httpd = {
|
||||
enable = true;
|
||||
adminAddr = "foo@example.org";
|
||||
};
|
||||
networking.firewall.allowedTCPPorts = [ 80 ];
|
||||
};
|
||||
};
|
||||
|
||||
in import ./make-test-python.nix ({ pkgs, lib, ... }: {
|
||||
name = "containers-ipv4-ipv6";
|
||||
meta = {
|
||||
maintainers = with lib.maintainers; [ aristid aszlig eelco kampfschlaefer ];
|
||||
};
|
||||
|
||||
nodes.machine =
|
||||
{ pkgs, ... }: {
|
||||
imports = [ ../modules/installer/cd-dvd/channel.nix ];
|
||||
virtualisation = {
|
||||
writableStore = true;
|
||||
};
|
||||
|
||||
containers.webserver4 = webserverFor "10.231.136.1" "10.231.136.2";
|
||||
containers.webserver6 = webserverFor "fc00::2" "fc00::1";
|
||||
virtualisation.additionalPaths = [ pkgs.stdenv ];
|
||||
};
|
||||
|
||||
testScript = { nodes, ... }: ''
|
||||
import time
|
||||
|
||||
|
||||
def curl_host(ip):
|
||||
# put [] around ipv6 addresses for curl
|
||||
host = ip if ":" not in ip else f"[{ip}]"
|
||||
return f"curl --fail --connect-timeout 2 http://{host}/ > /dev/null"
|
||||
|
||||
|
||||
def get_ip(container):
|
||||
# need to distinguish because show-ip won't work for ipv6
|
||||
if container == "webserver4":
|
||||
ip = machine.succeed(f"nixos-container show-ip {container}").rstrip()
|
||||
assert ip == "${nodes.machine.config.containers.webserver4.localAddress}"
|
||||
return ip
|
||||
return "${nodes.machine.config.containers.webserver6.localAddress}"
|
||||
|
||||
|
||||
for container in "webserver4", "webserver6":
|
||||
assert container in machine.succeed("nixos-container list")
|
||||
|
||||
with subtest(f"Start container {container}"):
|
||||
machine.succeed(f"nixos-container start {container}")
|
||||
# wait 2s for container to start and network to be up
|
||||
time.sleep(2)
|
||||
|
||||
# Since "start" returns after the container has reached
|
||||
# multi-user.target, we should now be able to access it.
|
||||
|
||||
ip = get_ip(container)
|
||||
with subtest(f"{container} reacts to pings and HTTP requests"):
|
||||
machine.succeed(f"ping -n -c1 {ip}")
|
||||
machine.succeed(curl_host(ip))
|
||||
|
||||
with subtest(f"Stop container {container}"):
|
||||
machine.succeed(f"nixos-container stop {container}")
|
||||
machine.fail(curl_host(ip))
|
||||
|
||||
# Destroying a declarative container should fail.
|
||||
machine.fail(f"nixos-container destroy {container}")
|
||||
'';
|
||||
})
|
||||
82
nixos/tests/containers-macvlans.nix
Normal file
82
nixos/tests/containers-macvlans.nix
Normal file
|
|
@ -0,0 +1,82 @@
|
|||
let
|
||||
# containers IP on VLAN 1
|
||||
containerIp1 = "192.168.1.253";
|
||||
containerIp2 = "192.168.1.254";
|
||||
in
|
||||
|
||||
import ./make-test-python.nix ({ pkgs, lib, ... }: {
|
||||
name = "containers-macvlans";
|
||||
meta = {
|
||||
maintainers = with lib.maintainers; [ montag451 ];
|
||||
};
|
||||
|
||||
nodes = {
|
||||
|
||||
machine1 =
|
||||
{ lib, ... }:
|
||||
{
|
||||
virtualisation.vlans = [ 1 ];
|
||||
|
||||
# To be able to ping containers from the host, it is necessary
|
||||
# to create a macvlan on the host on the VLAN 1 network.
|
||||
networking.macvlans.mv-eth1-host = {
|
||||
interface = "eth1";
|
||||
mode = "bridge";
|
||||
};
|
||||
networking.interfaces.eth1.ipv4.addresses = lib.mkForce [];
|
||||
networking.interfaces.mv-eth1-host = {
|
||||
ipv4.addresses = [ { address = "192.168.1.1"; prefixLength = 24; } ];
|
||||
};
|
||||
|
||||
containers.test1 = {
|
||||
autoStart = true;
|
||||
macvlans = [ "eth1" ];
|
||||
|
||||
config = {
|
||||
networking.interfaces.mv-eth1 = {
|
||||
ipv4.addresses = [ { address = containerIp1; prefixLength = 24; } ];
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
containers.test2 = {
|
||||
autoStart = true;
|
||||
macvlans = [ "eth1" ];
|
||||
|
||||
config = {
|
||||
networking.interfaces.mv-eth1 = {
|
||||
ipv4.addresses = [ { address = containerIp2; prefixLength = 24; } ];
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
machine2 =
|
||||
{ ... }:
|
||||
{
|
||||
virtualisation.vlans = [ 1 ];
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
testScript = ''
|
||||
start_all()
|
||||
machine1.wait_for_unit("default.target")
|
||||
machine2.wait_for_unit("default.target")
|
||||
|
||||
with subtest(
|
||||
"Ping between containers to check that macvlans are created in bridge mode"
|
||||
):
|
||||
machine1.succeed("nixos-container run test1 -- ping -n -c 1 ${containerIp2}")
|
||||
|
||||
with subtest("Ping containers from the host (machine1)"):
|
||||
machine1.succeed("ping -n -c 1 ${containerIp1}")
|
||||
machine1.succeed("ping -n -c 1 ${containerIp2}")
|
||||
|
||||
with subtest(
|
||||
"Ping containers from the second machine to check that containers are reachable from the outside"
|
||||
):
|
||||
machine2.succeed("ping -n -c 1 ${containerIp1}")
|
||||
machine2.succeed("ping -n -c 1 ${containerIp2}")
|
||||
'';
|
||||
})
|
||||
37
nixos/tests/containers-names.nix
Normal file
37
nixos/tests/containers-names.nix
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
import ./make-test-python.nix ({ pkgs, lib, ... }: {
|
||||
name = "containers-names";
|
||||
meta = {
|
||||
maintainers = with lib.maintainers; [ patryk27 ];
|
||||
};
|
||||
|
||||
nodes.machine = { ... }: {
|
||||
# We're using the newest kernel, so that we can test containers with long names.
|
||||
# Please see https://github.com/NixOS/nixpkgs/issues/38509 for details.
|
||||
boot.kernelPackages = pkgs.linuxPackages_latest;
|
||||
|
||||
containers = let
|
||||
container = subnet: {
|
||||
autoStart = true;
|
||||
privateNetwork = true;
|
||||
hostAddress = "192.168.${subnet}.1";
|
||||
localAddress = "192.168.${subnet}.2";
|
||||
config = { };
|
||||
};
|
||||
|
||||
in {
|
||||
first = container "1";
|
||||
second = container "2";
|
||||
really-long-name = container "3";
|
||||
really-long-long-name-2 = container "4";
|
||||
};
|
||||
};
|
||||
|
||||
testScript = ''
|
||||
machine.wait_for_unit("default.target")
|
||||
|
||||
machine.succeed("ip link show | grep ve-first")
|
||||
machine.succeed("ip link show | grep ve-second")
|
||||
machine.succeed("ip link show | grep ve-really-lFYWO")
|
||||
machine.succeed("ip link show | grep ve-really-l3QgY")
|
||||
'';
|
||||
})
|
||||
30
nixos/tests/containers-nested.nix
Normal file
30
nixos/tests/containers-nested.nix
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
# Test for NixOS' container nesting.
|
||||
|
||||
import ./make-test-python.nix ({ pkgs, ... }: {
|
||||
name = "nested";
|
||||
|
||||
meta = with pkgs.lib.maintainers; { maintainers = [ sorki ]; };
|
||||
|
||||
nodes.machine = { lib, ... }:
|
||||
let
|
||||
makeNested = subConf: {
|
||||
containers.nested = {
|
||||
autoStart = true;
|
||||
privateNetwork = true;
|
||||
config = subConf;
|
||||
};
|
||||
};
|
||||
in makeNested (makeNested { });
|
||||
|
||||
testScript = ''
|
||||
machine.start()
|
||||
machine.wait_for_unit("container@nested.service")
|
||||
machine.succeed("systemd-run --pty --machine=nested -- machinectl list | grep nested")
|
||||
print(
|
||||
machine.succeed(
|
||||
"systemd-run --pty --machine=nested -- systemd-run --pty --machine=nested -- systemctl status"
|
||||
)
|
||||
)
|
||||
'';
|
||||
})
|
||||
|
||||
131
nixos/tests/containers-physical_interfaces.nix
Normal file
131
nixos/tests/containers-physical_interfaces.nix
Normal file
|
|
@ -0,0 +1,131 @@
|
|||
import ./make-test-python.nix ({ pkgs, lib, ... }: {
|
||||
name = "containers-physical_interfaces";
|
||||
meta = {
|
||||
maintainers = with lib.maintainers; [ kampfschlaefer ];
|
||||
};
|
||||
|
||||
nodes = {
|
||||
server = { ... }:
|
||||
{
|
||||
virtualisation.vlans = [ 1 ];
|
||||
|
||||
containers.server = {
|
||||
privateNetwork = true;
|
||||
interfaces = [ "eth1" ];
|
||||
|
||||
config = {
|
||||
networking.interfaces.eth1.ipv4.addresses = [
|
||||
{ address = "10.10.0.1"; prefixLength = 24; }
|
||||
];
|
||||
networking.firewall.enable = false;
|
||||
};
|
||||
};
|
||||
};
|
||||
bridged = { ... }: {
|
||||
virtualisation.vlans = [ 1 ];
|
||||
|
||||
containers.bridged = {
|
||||
privateNetwork = true;
|
||||
interfaces = [ "eth1" ];
|
||||
|
||||
config = {
|
||||
networking.bridges.br0.interfaces = [ "eth1" ];
|
||||
networking.interfaces.br0.ipv4.addresses = [
|
||||
{ address = "10.10.0.2"; prefixLength = 24; }
|
||||
];
|
||||
networking.firewall.enable = false;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
bonded = { ... }: {
|
||||
virtualisation.vlans = [ 1 ];
|
||||
|
||||
containers.bonded = {
|
||||
privateNetwork = true;
|
||||
interfaces = [ "eth1" ];
|
||||
|
||||
config = {
|
||||
networking.bonds.bond0 = {
|
||||
interfaces = [ "eth1" ];
|
||||
driverOptions.mode = "active-backup";
|
||||
};
|
||||
networking.interfaces.bond0.ipv4.addresses = [
|
||||
{ address = "10.10.0.3"; prefixLength = 24; }
|
||||
];
|
||||
networking.firewall.enable = false;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
bridgedbond = { ... }: {
|
||||
virtualisation.vlans = [ 1 ];
|
||||
|
||||
containers.bridgedbond = {
|
||||
privateNetwork = true;
|
||||
interfaces = [ "eth1" ];
|
||||
|
||||
config = {
|
||||
networking.bonds.bond0 = {
|
||||
interfaces = [ "eth1" ];
|
||||
driverOptions.mode = "active-backup";
|
||||
};
|
||||
networking.bridges.br0.interfaces = [ "bond0" ];
|
||||
networking.interfaces.br0.ipv4.addresses = [
|
||||
{ address = "10.10.0.4"; prefixLength = 24; }
|
||||
];
|
||||
networking.firewall.enable = false;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
testScript = ''
|
||||
start_all()
|
||||
|
||||
with subtest("Prepare server"):
|
||||
server.wait_for_unit("default.target")
|
||||
server.succeed("ip link show dev eth1 >&2")
|
||||
|
||||
with subtest("Simple physical interface is up"):
|
||||
server.succeed("nixos-container start server")
|
||||
server.wait_for_unit("container@server")
|
||||
server.succeed(
|
||||
"systemctl -M server list-dependencies network-addresses-eth1.service >&2"
|
||||
)
|
||||
|
||||
# The other tests will ping this container on its ip. Here we just check
|
||||
# that the device is present in the container.
|
||||
server.succeed("nixos-container run server -- ip a show dev eth1 >&2")
|
||||
|
||||
with subtest("Physical device in bridge in container can ping server"):
|
||||
bridged.wait_for_unit("default.target")
|
||||
bridged.succeed("nixos-container start bridged")
|
||||
bridged.wait_for_unit("container@bridged")
|
||||
bridged.succeed(
|
||||
"systemctl -M bridged list-dependencies network-addresses-br0.service >&2",
|
||||
"systemctl -M bridged status -n 30 -l network-addresses-br0.service",
|
||||
"nixos-container run bridged -- ping -w 10 -c 1 -n 10.10.0.1",
|
||||
)
|
||||
|
||||
with subtest("Physical device in bond in container can ping server"):
|
||||
bonded.wait_for_unit("default.target")
|
||||
bonded.succeed("nixos-container start bonded")
|
||||
bonded.wait_for_unit("container@bonded")
|
||||
bonded.succeed(
|
||||
"systemctl -M bonded list-dependencies network-addresses-bond0 >&2",
|
||||
"systemctl -M bonded status -n 30 -l network-addresses-bond0 >&2",
|
||||
"nixos-container run bonded -- ping -w 10 -c 1 -n 10.10.0.1",
|
||||
)
|
||||
|
||||
with subtest("Physical device in bond in bridge in container can ping server"):
|
||||
bridgedbond.wait_for_unit("default.target")
|
||||
bridgedbond.succeed("nixos-container start bridgedbond")
|
||||
bridgedbond.wait_for_unit("container@bridgedbond")
|
||||
bridgedbond.succeed(
|
||||
"systemctl -M bridgedbond list-dependencies network-addresses-br0.service >&2",
|
||||
"systemctl -M bridgedbond status -n 30 -l network-addresses-br0.service",
|
||||
"nixos-container run bridgedbond -- ping -w 10 -c 1 -n 10.10.0.1",
|
||||
)
|
||||
'';
|
||||
})
|
||||
59
nixos/tests/containers-portforward.nix
Normal file
59
nixos/tests/containers-portforward.nix
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
let
|
||||
hostIp = "192.168.0.1";
|
||||
hostPort = 10080;
|
||||
containerIp = "192.168.0.100";
|
||||
containerPort = 80;
|
||||
in
|
||||
|
||||
import ./make-test-python.nix ({ pkgs, lib, ... }: {
|
||||
name = "containers-portforward";
|
||||
meta = {
|
||||
maintainers = with lib.maintainers; [ aristid aszlig eelco kampfschlaefer ianwookim ];
|
||||
};
|
||||
|
||||
nodes.machine =
|
||||
{ pkgs, ... }:
|
||||
{ imports = [ ../modules/installer/cd-dvd/channel.nix ];
|
||||
virtualisation.writableStore = true;
|
||||
|
||||
containers.webserver =
|
||||
{ privateNetwork = true;
|
||||
hostAddress = hostIp;
|
||||
localAddress = containerIp;
|
||||
forwardPorts = [ { protocol = "tcp"; hostPort = hostPort; containerPort = containerPort; } ];
|
||||
config =
|
||||
{ services.httpd.enable = true;
|
||||
services.httpd.adminAddr = "foo@example.org";
|
||||
networking.firewall.allowedTCPPorts = [ 80 ];
|
||||
};
|
||||
};
|
||||
|
||||
virtualisation.additionalPaths = [ pkgs.stdenv ];
|
||||
};
|
||||
|
||||
testScript =
|
||||
''
|
||||
container_list = machine.succeed("nixos-container list")
|
||||
assert "webserver" in container_list
|
||||
|
||||
# Start the webserver container.
|
||||
machine.succeed("nixos-container start webserver")
|
||||
|
||||
# wait two seconds for the container to start and the network to be up
|
||||
machine.sleep(2)
|
||||
|
||||
# Since "start" returns after the container has reached
|
||||
# multi-user.target, we should now be able to access it.
|
||||
# ip = machine.succeed("nixos-container show-ip webserver").strip()
|
||||
machine.succeed("ping -n -c1 ${hostIp}")
|
||||
machine.succeed("curl --fail http://${hostIp}:${toString hostPort}/ > /dev/null")
|
||||
|
||||
# Stop the container.
|
||||
machine.succeed("nixos-container stop webserver")
|
||||
machine.fail("curl --fail --connect-timeout 2 http://${hostIp}:${toString hostPort}/ > /dev/null")
|
||||
|
||||
# Destroying a declarative container should fail.
|
||||
machine.fail("nixos-container destroy webserver")
|
||||
'';
|
||||
|
||||
})
|
||||
71
nixos/tests/containers-reloadable.nix
Normal file
71
nixos/tests/containers-reloadable.nix
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
import ./make-test-python.nix ({ pkgs, lib, ... }:
|
||||
let
|
||||
client_base = {
|
||||
containers.test1 = {
|
||||
autoStart = true;
|
||||
config = {
|
||||
environment.etc.check.text = "client_base";
|
||||
};
|
||||
};
|
||||
|
||||
# prevent make-test-python.nix to change IP
|
||||
networking.interfaces = {
|
||||
eth1.ipv4.addresses = lib.mkOverride 0 [ ];
|
||||
};
|
||||
};
|
||||
in {
|
||||
name = "containers-reloadable";
|
||||
meta = {
|
||||
maintainers = with lib.maintainers; [ danbst ];
|
||||
};
|
||||
|
||||
nodes = {
|
||||
client = { ... }: {
|
||||
imports = [ client_base ];
|
||||
};
|
||||
|
||||
client_c1 = { lib, ... }: {
|
||||
imports = [ client_base ];
|
||||
|
||||
containers.test1.config = {
|
||||
environment.etc.check.text = lib.mkForce "client_c1";
|
||||
services.httpd.enable = true;
|
||||
services.httpd.adminAddr = "nixos@example.com";
|
||||
};
|
||||
};
|
||||
client_c2 = { lib, ... }: {
|
||||
imports = [ client_base ];
|
||||
|
||||
containers.test1.config = {
|
||||
environment.etc.check.text = lib.mkForce "client_c2";
|
||||
services.nginx.enable = true;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
testScript = {nodes, ...}: let
|
||||
c1System = nodes.client_c1.config.system.build.toplevel;
|
||||
c2System = nodes.client_c2.config.system.build.toplevel;
|
||||
in ''
|
||||
client.start()
|
||||
client.wait_for_unit("default.target")
|
||||
|
||||
assert "client_base" in client.succeed("nixos-container run test1 cat /etc/check")
|
||||
|
||||
with subtest("httpd is available after activating config1"):
|
||||
client.succeed(
|
||||
"${c1System}/bin/switch-to-configuration test >&2",
|
||||
"[[ $(nixos-container run test1 cat /etc/check) == client_c1 ]] >&2",
|
||||
"systemctl status httpd -M test1 >&2",
|
||||
)
|
||||
|
||||
with subtest("httpd is not available any longer after switching to config2"):
|
||||
client.succeed(
|
||||
"${c2System}/bin/switch-to-configuration test >&2",
|
||||
"[[ $(nixos-container run test1 cat /etc/check) == client_c2 ]] >&2",
|
||||
"systemctl status nginx -M test1 >&2",
|
||||
)
|
||||
client.fail("systemctl status httpd -M test1 >&2")
|
||||
'';
|
||||
|
||||
})
|
||||
113
nixos/tests/containers-restart_networking.nix
Normal file
113
nixos/tests/containers-restart_networking.nix
Normal file
|
|
@ -0,0 +1,113 @@
|
|||
let
|
||||
client_base = {
|
||||
networking.firewall.enable = false;
|
||||
|
||||
containers.webserver = {
|
||||
autoStart = true;
|
||||
privateNetwork = true;
|
||||
hostBridge = "br0";
|
||||
config = {
|
||||
networking.firewall.enable = false;
|
||||
networking.interfaces.eth0.ipv4.addresses = [
|
||||
{ address = "192.168.1.122"; prefixLength = 24; }
|
||||
];
|
||||
};
|
||||
};
|
||||
};
|
||||
in import ./make-test-python.nix ({ pkgs, lib, ... }:
|
||||
{
|
||||
name = "containers-restart_networking";
|
||||
meta = {
|
||||
maintainers = with lib.maintainers; [ kampfschlaefer ];
|
||||
};
|
||||
|
||||
nodes = {
|
||||
client = { lib, ... }: client_base // {
|
||||
virtualisation.vlans = [ 1 ];
|
||||
|
||||
networking.bridges.br0 = {
|
||||
interfaces = [];
|
||||
rstp = false;
|
||||
};
|
||||
networking.interfaces = {
|
||||
eth1.ipv4.addresses = lib.mkOverride 0 [ ];
|
||||
br0.ipv4.addresses = [ { address = "192.168.1.1"; prefixLength = 24; } ];
|
||||
};
|
||||
|
||||
};
|
||||
client_eth1 = { lib, ... }: client_base // {
|
||||
networking.bridges.br0 = {
|
||||
interfaces = [ "eth1" ];
|
||||
rstp = false;
|
||||
};
|
||||
networking.interfaces = {
|
||||
eth1.ipv4.addresses = lib.mkOverride 0 [ ];
|
||||
br0.ipv4.addresses = [ { address = "192.168.1.2"; prefixLength = 24; } ];
|
||||
};
|
||||
};
|
||||
client_eth1_rstp = { lib, ... }: client_base // {
|
||||
networking.bridges.br0 = {
|
||||
interfaces = [ "eth1" ];
|
||||
rstp = true;
|
||||
};
|
||||
networking.interfaces = {
|
||||
eth1.ipv4.addresses = lib.mkOverride 0 [ ];
|
||||
br0.ipv4.addresses = [ { address = "192.168.1.2"; prefixLength = 24; } ];
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
testScript = {nodes, ...}: let
|
||||
originalSystem = nodes.client.config.system.build.toplevel;
|
||||
eth1_bridged = nodes.client_eth1.config.system.build.toplevel;
|
||||
eth1_rstp = nodes.client_eth1_rstp.config.system.build.toplevel;
|
||||
in ''
|
||||
client.start()
|
||||
|
||||
client.wait_for_unit("default.target")
|
||||
|
||||
with subtest("Initial configuration connectivity check"):
|
||||
client.succeed("ping 192.168.1.122 -c 1 -n >&2")
|
||||
client.succeed("nixos-container run webserver -- ping -c 1 -n 192.168.1.1 >&2")
|
||||
|
||||
client.fail("ip l show eth1 |grep 'master br0' >&2")
|
||||
client.fail("grep eth1 /run/br0.interfaces >&2")
|
||||
|
||||
with subtest("Bridged configuration without STP preserves connectivity"):
|
||||
client.succeed(
|
||||
"${eth1_bridged}/bin/switch-to-configuration test >&2"
|
||||
)
|
||||
|
||||
client.succeed(
|
||||
"ping 192.168.1.122 -c 1 -n >&2",
|
||||
"nixos-container run webserver -- ping -c 1 -n 192.168.1.2 >&2",
|
||||
"ip l show eth1 |grep 'master br0' >&2",
|
||||
"grep eth1 /run/br0.interfaces >&2",
|
||||
)
|
||||
|
||||
# activating rstp needs another service, therefore the bridge will restart and the container will lose its connectivity
|
||||
# with subtest("Bridged configuration with STP"):
|
||||
# client.succeed("${eth1_rstp}/bin/switch-to-configuration test >&2")
|
||||
# client.execute("ip -4 a >&2")
|
||||
# client.execute("ip l >&2")
|
||||
#
|
||||
# client.succeed(
|
||||
# "ping 192.168.1.122 -c 1 -n >&2",
|
||||
# "nixos-container run webserver -- ping -c 1 -n 192.168.1.2 >&2",
|
||||
# "ip l show eth1 |grep 'master br0' >&2",
|
||||
# "grep eth1 /run/br0.interfaces >&2",
|
||||
# )
|
||||
|
||||
with subtest("Reverting to initial configuration preserves connectivity"):
|
||||
client.succeed(
|
||||
"${originalSystem}/bin/switch-to-configuration test >&2"
|
||||
)
|
||||
|
||||
client.succeed("ping 192.168.1.122 -c 1 -n >&2")
|
||||
client.succeed("nixos-container run webserver -- ping -c 1 -n 192.168.1.1 >&2")
|
||||
|
||||
client.fail("ip l show eth1 |grep 'master br0' >&2")
|
||||
client.fail("grep eth1 /run/br0.interfaces >&2")
|
||||
'';
|
||||
|
||||
})
|
||||
90
nixos/tests/containers-tmpfs.nix
Normal file
90
nixos/tests/containers-tmpfs.nix
Normal file
|
|
@ -0,0 +1,90 @@
|
|||
import ./make-test-python.nix ({ pkgs, lib, ... }: {
|
||||
name = "containers-tmpfs";
|
||||
meta = {
|
||||
maintainers = with lib.maintainers; [ patryk27 ];
|
||||
};
|
||||
|
||||
nodes.machine =
|
||||
{ pkgs, ... }:
|
||||
{ imports = [ ../modules/installer/cd-dvd/channel.nix ];
|
||||
virtualisation.writableStore = true;
|
||||
|
||||
containers.tmpfs =
|
||||
{
|
||||
autoStart = true;
|
||||
tmpfs = [
|
||||
# Mount var as a tmpfs
|
||||
"/var"
|
||||
|
||||
# Add a nested mount inside a tmpfs
|
||||
"/var/log"
|
||||
|
||||
# Add a tmpfs on a path that does not exist
|
||||
"/some/random/path"
|
||||
];
|
||||
config = { };
|
||||
};
|
||||
|
||||
virtualisation.additionalPaths = [ pkgs.stdenv ];
|
||||
};
|
||||
|
||||
testScript = ''
|
||||
machine.wait_for_unit("default.target")
|
||||
assert "tmpfs" in machine.succeed("nixos-container list")
|
||||
|
||||
with subtest("tmpfs container is up"):
|
||||
assert "up" in machine.succeed("nixos-container status tmpfs")
|
||||
|
||||
|
||||
def tmpfs_cmd(command):
|
||||
return f"nixos-container run tmpfs -- {command} 2>/dev/null"
|
||||
|
||||
|
||||
with subtest("/var is mounted as a tmpfs"):
|
||||
machine.succeed(tmpfs_cmd("mountpoint -q /var"))
|
||||
|
||||
with subtest("/var/log is mounted as a tmpfs"):
|
||||
assert "What: tmpfs" in machine.succeed(
|
||||
tmpfs_cmd("systemctl status var-log.mount --no-pager")
|
||||
)
|
||||
machine.succeed(tmpfs_cmd("mountpoint -q /var/log"))
|
||||
|
||||
with subtest("/some/random/path is mounted as a tmpfs"):
|
||||
assert "What: tmpfs" in machine.succeed(
|
||||
tmpfs_cmd("systemctl status some-random-path.mount --no-pager")
|
||||
)
|
||||
machine.succeed(tmpfs_cmd("mountpoint -q /some/random/path"))
|
||||
|
||||
with subtest(
|
||||
"files created in the container in a non-tmpfs directory are visible on the host."
|
||||
):
|
||||
# This establishes legitimacy for the following tests
|
||||
machine.succeed(
|
||||
tmpfs_cmd("touch /root/test.file"),
|
||||
tmpfs_cmd("ls -l /root | grep -q test.file"),
|
||||
"test -e /var/lib/nixos-containers/tmpfs/root/test.file",
|
||||
)
|
||||
|
||||
with subtest(
|
||||
"/some/random/path is writable and that files created there are not "
|
||||
+ "in the hosts container dir but in the tmpfs"
|
||||
):
|
||||
machine.succeed(
|
||||
tmpfs_cmd("touch /some/random/path/test.file"),
|
||||
tmpfs_cmd("test -e /some/random/path/test.file"),
|
||||
)
|
||||
machine.fail("test -e /var/lib/nixos-containers/tmpfs/some/random/path/test.file")
|
||||
|
||||
with subtest(
|
||||
"files created in the hosts container dir in a path where a tmpfs "
|
||||
+ "file system has been mounted are not visible to the container as "
|
||||
+ "the do not exist in the tmpfs"
|
||||
):
|
||||
machine.succeed(
|
||||
"touch /var/lib/nixos-containers/tmpfs/var/test.file",
|
||||
"test -e /var/lib/nixos-containers/tmpfs/var/test.file",
|
||||
"ls -l /var/lib/nixos-containers/tmpfs/var/ | grep -q test.file 2>/dev/null",
|
||||
)
|
||||
machine.fail(tmpfs_cmd("ls -l /var | grep -q test.file"))
|
||||
'';
|
||||
})
|
||||
30
nixos/tests/convos.nix
Normal file
30
nixos/tests/convos.nix
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
import ./make-test-python.nix ({ lib, pkgs, ... }:
|
||||
|
||||
with lib;
|
||||
let
|
||||
port = 3333;
|
||||
in
|
||||
{
|
||||
name = "convos";
|
||||
meta = with pkgs.lib.maintainers; {
|
||||
maintainers = [ sgo ];
|
||||
};
|
||||
|
||||
nodes = {
|
||||
machine =
|
||||
{ pkgs, ... }:
|
||||
{
|
||||
services.convos = {
|
||||
enable = true;
|
||||
listenPort = port;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
testScript = ''
|
||||
machine.wait_for_unit("convos")
|
||||
machine.wait_for_open_port("${toString port}")
|
||||
machine.succeed("journalctl -u convos | grep -q 'Listening at.*${toString port}'")
|
||||
machine.succeed("curl -f http://localhost:${toString port}/")
|
||||
'';
|
||||
})
|
||||
89
nixos/tests/corerad.nix
Normal file
89
nixos/tests/corerad.nix
Normal file
|
|
@ -0,0 +1,89 @@
|
|||
import ./make-test-python.nix (
|
||||
{
|
||||
nodes = {
|
||||
router = {config, pkgs, ...}: {
|
||||
config = {
|
||||
# This machine simulates a router with IPv6 forwarding and a static IPv6 address.
|
||||
boot.kernel.sysctl = {
|
||||
"net.ipv6.conf.all.forwarding" = true;
|
||||
};
|
||||
networking.interfaces.eth1 = {
|
||||
ipv6.addresses = [ { address = "fd00:dead:beef:dead::1"; prefixLength = 64; } ];
|
||||
};
|
||||
services.corerad = {
|
||||
enable = true;
|
||||
# Serve router advertisements to the client machine with prefix information matching
|
||||
# any IPv6 /64 prefixes configured on this interface.
|
||||
#
|
||||
# This configuration is identical to the example in the CoreRAD NixOS module.
|
||||
settings = {
|
||||
interfaces = [
|
||||
{
|
||||
name = "eth0";
|
||||
monitor = true;
|
||||
}
|
||||
{
|
||||
name = "eth1";
|
||||
advertise = true;
|
||||
prefix = [{ prefix = "::/64"; }];
|
||||
}
|
||||
];
|
||||
debug = {
|
||||
address = "localhost:9430";
|
||||
prometheus = true;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
client = {config, pkgs, ...}: {
|
||||
# Use IPv6 SLAAC from router advertisements, and install rdisc6 so we can
|
||||
# trigger one immediately.
|
||||
config = {
|
||||
boot.kernel.sysctl = {
|
||||
"net.ipv6.conf.all.autoconf" = true;
|
||||
};
|
||||
environment.systemPackages = with pkgs; [
|
||||
ndisc6
|
||||
];
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
testScript = ''
|
||||
start_all()
|
||||
|
||||
with subtest("Wait for CoreRAD and network ready"):
|
||||
# Ensure networking is online and CoreRAD is ready.
|
||||
router.wait_for_unit("network-online.target")
|
||||
client.wait_for_unit("network-online.target")
|
||||
router.wait_for_unit("corerad.service")
|
||||
|
||||
# Ensure the client can reach the router.
|
||||
client.wait_until_succeeds("ping -c 1 fd00:dead:beef:dead::1")
|
||||
|
||||
with subtest("Verify SLAAC on client"):
|
||||
# Trigger a router solicitation and verify a SLAAC address is assigned from
|
||||
# the prefix configured on the router.
|
||||
client.wait_until_succeeds("rdisc6 -1 -r 10 eth1")
|
||||
client.wait_until_succeeds(
|
||||
"ip -6 addr show dev eth1 | grep -q 'fd00:dead:beef:dead:'"
|
||||
)
|
||||
|
||||
addrs = client.succeed("ip -6 addr show dev eth1")
|
||||
|
||||
assert (
|
||||
"fd00:dead:beef:dead:" in addrs
|
||||
), "SLAAC prefix was not found in client addresses after router advertisement"
|
||||
assert (
|
||||
"/64 scope global temporary" in addrs
|
||||
), "SLAAC temporary address was not configured on client after router advertisement"
|
||||
|
||||
with subtest("Verify HTTP debug server is configured"):
|
||||
out = router.succeed("curl -f localhost:9430/metrics")
|
||||
|
||||
assert (
|
||||
"corerad_build_info" in out
|
||||
), "Build info metric was not found in Prometheus output"
|
||||
'';
|
||||
})
|
||||
29
nixos/tests/coturn.nix
Normal file
29
nixos/tests/coturn.nix
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
import ./make-test-python.nix ({ ... }: {
|
||||
name = "coturn";
|
||||
nodes = {
|
||||
default = {
|
||||
services.coturn.enable = true;
|
||||
};
|
||||
secretsfile = {
|
||||
boot.postBootCommands = ''
|
||||
echo "some-very-secret-string" > /run/coturn-secret
|
||||
'';
|
||||
services.coturn = {
|
||||
enable = true;
|
||||
static-auth-secret-file = "/run/coturn-secret";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
testScript =
|
||||
''
|
||||
start_all()
|
||||
|
||||
with subtest("by default works without configuration"):
|
||||
default.wait_for_unit("coturn.service")
|
||||
|
||||
with subtest("works with static-auth-secret-file"):
|
||||
secretsfile.wait_for_unit("coturn.service")
|
||||
secretsfile.succeed("grep 'some-very-secret-string' /run/coturn/turnserver.cfg")
|
||||
'';
|
||||
})
|
||||
63
nixos/tests/couchdb.nix
Normal file
63
nixos/tests/couchdb.nix
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
let
|
||||
|
||||
makeNode = couchpkg: user: passwd:
|
||||
{ pkgs, ... } :
|
||||
|
||||
{ environment.systemPackages = with pkgs; [ jq ];
|
||||
services.couchdb.enable = true;
|
||||
services.couchdb.package = couchpkg;
|
||||
services.couchdb.adminUser = user;
|
||||
services.couchdb.adminPass = passwd;
|
||||
};
|
||||
testuser = "testadmin";
|
||||
testpass = "cowabunga";
|
||||
testlogin = "${testuser}:${testpass}@";
|
||||
|
||||
in import ./make-test-python.nix ({ pkgs, lib, ...}:
|
||||
|
||||
with lib;
|
||||
|
||||
{
|
||||
name = "couchdb";
|
||||
meta = with pkgs.lib.maintainers; {
|
||||
maintainers = [ fpletz ];
|
||||
};
|
||||
|
||||
nodes = {
|
||||
couchdb3 = makeNode pkgs.couchdb3 testuser testpass;
|
||||
};
|
||||
|
||||
testScript = let
|
||||
curlJqCheck = login: action: path: jqexpr: result:
|
||||
pkgs.writeScript "curl-jq-check-${action}-${path}.sh" ''
|
||||
RESULT=$(curl -X ${action} http://${login}127.0.0.1:5984/${path} | jq -r '${jqexpr}')
|
||||
echo $RESULT >&2
|
||||
if [ "$RESULT" != "${result}" ]; then
|
||||
exit 1
|
||||
fi
|
||||
'';
|
||||
in ''
|
||||
start_all()
|
||||
|
||||
couchdb3.wait_for_unit("couchdb.service")
|
||||
couchdb3.wait_until_succeeds(
|
||||
"${curlJqCheck testlogin "GET" "" ".couchdb" "Welcome"}"
|
||||
)
|
||||
couchdb3.wait_until_succeeds(
|
||||
"${curlJqCheck testlogin "GET" "_all_dbs" ". | length" "0"}"
|
||||
)
|
||||
couchdb3.succeed("${curlJqCheck testlogin "PUT" "foo" ".ok" "true"}")
|
||||
couchdb3.succeed(
|
||||
"${curlJqCheck testlogin "GET" "_all_dbs" ". | length" "1"}"
|
||||
)
|
||||
couchdb3.succeed(
|
||||
"${curlJqCheck testlogin "DELETE" "foo" ".ok" "true"}"
|
||||
)
|
||||
couchdb3.succeed(
|
||||
"${curlJqCheck testlogin "GET" "_all_dbs" ". | length" "0"}"
|
||||
)
|
||||
couchdb3.succeed(
|
||||
"${curlJqCheck testlogin "GET" "_node/couchdb@127.0.0.1" ".couchdb" "Welcome"}"
|
||||
)
|
||||
'';
|
||||
})
|
||||
19
nixos/tests/cri-o.nix
Normal file
19
nixos/tests/cri-o.nix
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
# This test runs CRI-O and verifies via critest
|
||||
import ./make-test-python.nix ({ pkgs, ... }: {
|
||||
name = "cri-o";
|
||||
meta.maintainers = with pkgs.lib.maintainers; teams.podman.members;
|
||||
|
||||
nodes = {
|
||||
crio = {
|
||||
virtualisation.cri-o.enable = true;
|
||||
};
|
||||
};
|
||||
|
||||
testScript = ''
|
||||
start_all()
|
||||
crio.wait_for_unit("crio.service")
|
||||
crio.succeed(
|
||||
"critest --ginkgo.focus='Runtime info' --runtime-endpoint unix:///var/run/crio/crio.sock"
|
||||
)
|
||||
'';
|
||||
})
|
||||
51
nixos/tests/croc.nix
Normal file
51
nixos/tests/croc.nix
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
import ./make-test-python.nix ({ pkgs, ... }:
|
||||
let
|
||||
client = { pkgs, ... }: {
|
||||
environment.systemPackages = [ pkgs.croc ];
|
||||
};
|
||||
pass = pkgs.writeText "pass" "PassRelay";
|
||||
in {
|
||||
name = "croc";
|
||||
meta = with pkgs.lib.maintainers; {
|
||||
maintainers = [ hax404 julm ];
|
||||
};
|
||||
|
||||
nodes = {
|
||||
relay = {
|
||||
services.croc = {
|
||||
enable = true;
|
||||
pass = pass;
|
||||
openFirewall = true;
|
||||
};
|
||||
};
|
||||
sender = client;
|
||||
receiver = client;
|
||||
};
|
||||
|
||||
testScript = ''
|
||||
start_all()
|
||||
|
||||
# wait until relay is up
|
||||
relay.wait_for_unit("croc")
|
||||
relay.wait_for_open_port(9009)
|
||||
relay.wait_for_open_port(9010)
|
||||
relay.wait_for_open_port(9011)
|
||||
relay.wait_for_open_port(9012)
|
||||
relay.wait_for_open_port(9013)
|
||||
|
||||
# generate testfiles and send them
|
||||
sender.wait_for_unit("multi-user.target")
|
||||
sender.execute("echo Hello World > testfile01.txt")
|
||||
sender.execute("echo Hello Earth > testfile02.txt")
|
||||
sender.execute(
|
||||
"croc --pass ${pass} --relay relay send --code topSecret testfile01.txt testfile02.txt >&2 &"
|
||||
)
|
||||
|
||||
# receive the testfiles and check them
|
||||
receiver.succeed(
|
||||
"croc --pass ${pass} --yes --relay relay topSecret"
|
||||
)
|
||||
assert "Hello World" in receiver.succeed("cat testfile01.txt")
|
||||
assert "Hello Earth" in receiver.succeed("cat testfile02.txt")
|
||||
'';
|
||||
})
|
||||
18
nixos/tests/cryptpad.nix
Normal file
18
nixos/tests/cryptpad.nix
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
import ./make-test-python.nix ({ lib, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
{
|
||||
name = "cryptpad";
|
||||
meta.maintainers = with maintainers; [ davhau ];
|
||||
|
||||
nodes.machine =
|
||||
{ pkgs, ... }:
|
||||
{ services.cryptpad.enable = true; };
|
||||
|
||||
testScript = ''
|
||||
machine.wait_for_unit("cryptpad.service")
|
||||
machine.wait_for_open_port("3000")
|
||||
machine.succeed("curl -L --fail http://localhost:3000/sheet")
|
||||
'';
|
||||
})
|
||||
195
nixos/tests/custom-ca.nix
Normal file
195
nixos/tests/custom-ca.nix
Normal file
|
|
@ -0,0 +1,195 @@
|
|||
# Checks that `security.pki` options are working in curl and the main browser
|
||||
# engines: Gecko (via Firefox), Chromium, QtWebEngine (via qutebrowser) and
|
||||
# WebKitGTK (via Midori). The test checks that certificates issued by a custom
|
||||
# trusted CA are accepted but those from an unknown CA are rejected.
|
||||
|
||||
{ system ? builtins.currentSystem,
|
||||
config ? {},
|
||||
pkgs ? import ../.. { inherit system config; }
|
||||
}:
|
||||
|
||||
with import ../lib/testing-python.nix { inherit system pkgs; };
|
||||
|
||||
let
|
||||
inherit (pkgs) lib;
|
||||
|
||||
makeCert = { caName, domain }: pkgs.runCommand "example-cert"
|
||||
{ buildInputs = [ pkgs.gnutls ]; }
|
||||
''
|
||||
mkdir $out
|
||||
|
||||
# CA cert template
|
||||
cat >ca.template <<EOF
|
||||
organization = "${caName}"
|
||||
cn = "${caName}"
|
||||
expiration_days = 365
|
||||
ca
|
||||
cert_signing_key
|
||||
crl_signing_key
|
||||
EOF
|
||||
|
||||
# server cert template
|
||||
cat >server.template <<EOF
|
||||
organization = "An example company"
|
||||
cn = "${domain}"
|
||||
expiration_days = 30
|
||||
dns_name = "${domain}"
|
||||
encryption_key
|
||||
signing_key
|
||||
EOF
|
||||
|
||||
# generate CA keypair
|
||||
certtool \
|
||||
--generate-privkey \
|
||||
--key-type rsa \
|
||||
--sec-param High \
|
||||
--outfile $out/ca.key
|
||||
certtool \
|
||||
--generate-self-signed \
|
||||
--load-privkey $out/ca.key \
|
||||
--template ca.template \
|
||||
--outfile $out/ca.crt
|
||||
|
||||
# generate server keypair
|
||||
certtool \
|
||||
--generate-privkey \
|
||||
--key-type rsa \
|
||||
--sec-param High \
|
||||
--outfile $out/server.key
|
||||
certtool \
|
||||
--generate-certificate \
|
||||
--load-privkey $out/server.key \
|
||||
--load-ca-privkey $out/ca.key \
|
||||
--load-ca-certificate $out/ca.crt \
|
||||
--template server.template \
|
||||
--outfile $out/server.crt
|
||||
'';
|
||||
|
||||
example-good-cert = makeCert
|
||||
{ caName = "Example good CA";
|
||||
domain = "good.example.com";
|
||||
};
|
||||
|
||||
example-bad-cert = makeCert
|
||||
{ caName = "Unknown CA";
|
||||
domain = "bad.example.com";
|
||||
};
|
||||
|
||||
webserverConfig =
|
||||
{ networking.hosts."127.0.0.1" = [ "good.example.com" "bad.example.com" ];
|
||||
security.pki.certificateFiles = [ "${example-good-cert}/ca.crt" ];
|
||||
|
||||
services.nginx.enable = true;
|
||||
services.nginx.virtualHosts."good.example.com" =
|
||||
{ onlySSL = true;
|
||||
sslCertificate = "${example-good-cert}/server.crt";
|
||||
sslCertificateKey = "${example-good-cert}/server.key";
|
||||
locations."/".extraConfig = ''
|
||||
add_header Content-Type text/plain;
|
||||
return 200 'It works!';
|
||||
'';
|
||||
};
|
||||
services.nginx.virtualHosts."bad.example.com" =
|
||||
{ onlySSL = true;
|
||||
sslCertificate = "${example-bad-cert}/server.crt";
|
||||
sslCertificateKey = "${example-bad-cert}/server.key";
|
||||
locations."/".extraConfig = ''
|
||||
add_header Content-Type text/plain;
|
||||
return 200 'It does not work!';
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
curlTest = makeTest {
|
||||
name = "custom-ca-curl";
|
||||
meta.maintainers = with lib.maintainers; [ rnhmjoj ];
|
||||
nodes.machine = { ... }: webserverConfig;
|
||||
testScript = ''
|
||||
with subtest("Good certificate is trusted in curl"):
|
||||
machine.wait_for_unit("nginx")
|
||||
machine.wait_for_open_port(443)
|
||||
machine.succeed("curl -fv https://good.example.com")
|
||||
|
||||
with subtest("Unknown CA is untrusted in curl"):
|
||||
machine.fail("curl -fv https://bad.example.com")
|
||||
'';
|
||||
};
|
||||
|
||||
mkBrowserTest = browser: testParams: makeTest {
|
||||
name = "custom-ca-${browser}";
|
||||
meta.maintainers = with lib.maintainers; [ rnhmjoj ];
|
||||
|
||||
enableOCR = true;
|
||||
|
||||
nodes.machine = { pkgs, ... }:
|
||||
{ imports =
|
||||
[ ./common/user-account.nix
|
||||
./common/x11.nix
|
||||
webserverConfig
|
||||
];
|
||||
|
||||
# chromium-based browsers refuse to run as root
|
||||
test-support.displayManager.auto.user = "alice";
|
||||
|
||||
# browsers may hang with the default memory
|
||||
virtualisation.memorySize = 600;
|
||||
|
||||
environment.systemPackages = [ pkgs.xdotool pkgs.${browser} ];
|
||||
};
|
||||
|
||||
testScript = ''
|
||||
from typing import Tuple
|
||||
def execute_as(user: str, cmd: str) -> Tuple[int, str]:
|
||||
"""
|
||||
Run a shell command as a specific user.
|
||||
"""
|
||||
return machine.execute(f"sudo -u {user} {cmd}")
|
||||
|
||||
|
||||
def wait_for_window_as(user: str, cls: str) -> None:
|
||||
"""
|
||||
Wait until a X11 window of a given user appears.
|
||||
"""
|
||||
|
||||
def window_is_visible(last_try: bool) -> bool:
|
||||
ret, stdout = execute_as(user, f"xdotool search --onlyvisible --class {cls}")
|
||||
if last_try:
|
||||
machine.log(f"Last chance to match {cls} on the window list")
|
||||
return ret == 0
|
||||
|
||||
with machine.nested("Waiting for a window to appear"):
|
||||
retry(window_is_visible)
|
||||
|
||||
|
||||
machine.start()
|
||||
machine.wait_for_x()
|
||||
|
||||
command = "${browser} ${testParams.args or ""}"
|
||||
with subtest("Good certificate is trusted in ${browser}"):
|
||||
execute_as(
|
||||
"alice", f"{command} https://good.example.com >&2 &"
|
||||
)
|
||||
wait_for_window_as("alice", "${browser}")
|
||||
machine.sleep(4)
|
||||
execute_as("alice", "xdotool key ctrl+r") # reload to be safe
|
||||
machine.wait_for_text("It works!")
|
||||
machine.screenshot("good${browser}")
|
||||
execute_as("alice", "xdotool key ctrl+w") # close tab
|
||||
|
||||
with subtest("Unknown CA is untrusted in ${browser}"):
|
||||
execute_as("alice", f"{command} https://bad.example.com >&2 &")
|
||||
machine.wait_for_text("${testParams.error}")
|
||||
machine.screenshot("bad${browser}")
|
||||
'';
|
||||
};
|
||||
|
||||
in
|
||||
|
||||
{
|
||||
curl = curlTest;
|
||||
} // pkgs.lib.mapAttrs mkBrowserTest {
|
||||
firefox = { error = "Security Risk"; };
|
||||
chromium = { error = "not private"; };
|
||||
qutebrowser = { args = "-T"; error = "Certificate error"; };
|
||||
midori = { error = "Security"; };
|
||||
}
|
||||
61
nixos/tests/deluge.nix
Normal file
61
nixos/tests/deluge.nix
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
import ./make-test-python.nix ({ pkgs, ...} : {
|
||||
name = "deluge";
|
||||
meta = with pkgs.lib.maintainers; {
|
||||
maintainers = [ flokli ];
|
||||
};
|
||||
|
||||
nodes = {
|
||||
simple = {
|
||||
services.deluge = {
|
||||
enable = true;
|
||||
package = pkgs.deluge-2_x;
|
||||
web = {
|
||||
enable = true;
|
||||
openFirewall = true;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
declarative = {
|
||||
services.deluge = {
|
||||
enable = true;
|
||||
package = pkgs.deluge-2_x;
|
||||
openFirewall = true;
|
||||
declarative = true;
|
||||
config = {
|
||||
allow_remote = true;
|
||||
download_location = "/var/lib/deluge/my-download";
|
||||
daemon_port = 58846;
|
||||
listen_ports = [ 6881 6889 ];
|
||||
};
|
||||
web = {
|
||||
enable = true;
|
||||
port = 3142;
|
||||
};
|
||||
authFile = pkgs.writeText "deluge-auth" ''
|
||||
localclient:a7bef72a890:10
|
||||
andrew:password:10
|
||||
user3:anotherpass:5
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
testScript = ''
|
||||
start_all()
|
||||
|
||||
simple.wait_for_unit("deluged")
|
||||
simple.wait_for_unit("delugeweb")
|
||||
simple.wait_for_open_port("8112")
|
||||
declarative.wait_for_unit("network.target")
|
||||
declarative.wait_until_succeeds("curl --fail http://simple:8112")
|
||||
|
||||
declarative.wait_for_unit("deluged")
|
||||
declarative.wait_for_unit("delugeweb")
|
||||
declarative.wait_until_succeeds("curl --fail http://declarative:3142")
|
||||
declarative.succeed(
|
||||
"deluge-console 'connect 127.0.0.1:58846 andrew password; help' | grep -q 'rm.*Remove a torrent'"
|
||||
)
|
||||
'';
|
||||
})
|
||||
101
nixos/tests/dendrite.nix
Normal file
101
nixos/tests/dendrite.nix
Normal file
|
|
@ -0,0 +1,101 @@
|
|||
import ./make-test-python.nix (
|
||||
{ pkgs, ... }:
|
||||
let
|
||||
homeserverUrl = "http://homeserver:8008";
|
||||
|
||||
private_key = pkgs.runCommand "matrix_key.pem" {
|
||||
buildInputs = [ pkgs.dendrite ];
|
||||
} "generate-keys --private-key $out";
|
||||
in
|
||||
{
|
||||
name = "dendrite";
|
||||
meta = with pkgs.lib; {
|
||||
maintainers = teams.matrix.members;
|
||||
};
|
||||
|
||||
nodes = {
|
||||
homeserver = { pkgs, ... }: {
|
||||
services.dendrite = {
|
||||
enable = true;
|
||||
loadCredential = [ "test_private_key:${private_key}" ];
|
||||
openRegistration = true;
|
||||
settings = {
|
||||
global.server_name = "test-dendrite-server.com";
|
||||
global.private_key = "$CREDENTIALS_DIRECTORY/test_private_key";
|
||||
client_api.registration_disabled = false;
|
||||
};
|
||||
};
|
||||
|
||||
networking.firewall.allowedTCPPorts = [ 8008 ];
|
||||
};
|
||||
|
||||
client = { pkgs, ... }: {
|
||||
environment.systemPackages = [
|
||||
(
|
||||
pkgs.writers.writePython3Bin "do_test"
|
||||
{ libraries = [ pkgs.python3Packages.matrix-nio ]; } ''
|
||||
import asyncio
|
||||
|
||||
from nio import AsyncClient
|
||||
|
||||
|
||||
async def main() -> None:
|
||||
# Connect to dendrite
|
||||
client = AsyncClient("http://homeserver:8008", "alice")
|
||||
|
||||
# Register as user alice
|
||||
response = await client.register("alice", "my-secret-password")
|
||||
|
||||
# Log in as user alice
|
||||
response = await client.login("my-secret-password")
|
||||
|
||||
# Create a new room
|
||||
response = await client.room_create(federate=False)
|
||||
room_id = response.room_id
|
||||
|
||||
# Join the room
|
||||
response = await client.join(room_id)
|
||||
|
||||
# Send a message to the room
|
||||
response = await client.room_send(
|
||||
room_id=room_id,
|
||||
message_type="m.room.message",
|
||||
content={
|
||||
"msgtype": "m.text",
|
||||
"body": "Hello world!"
|
||||
}
|
||||
)
|
||||
|
||||
# Sync responses
|
||||
response = await client.sync(timeout=30000)
|
||||
|
||||
# Check the message was received by dendrite
|
||||
last_message = response.rooms.join[room_id].timeline.events[-1].body
|
||||
assert last_message == "Hello world!"
|
||||
|
||||
# Leave the room
|
||||
response = await client.room_leave(room_id)
|
||||
|
||||
# Close the client
|
||||
await client.close()
|
||||
|
||||
asyncio.get_event_loop().run_until_complete(main())
|
||||
''
|
||||
)
|
||||
];
|
||||
};
|
||||
};
|
||||
|
||||
testScript = ''
|
||||
start_all()
|
||||
|
||||
with subtest("start the homeserver"):
|
||||
homeserver.wait_for_unit("dendrite.service")
|
||||
homeserver.wait_for_open_port(8008)
|
||||
|
||||
with subtest("ensure messages can be exchanged"):
|
||||
client.succeed("do_test")
|
||||
'';
|
||||
|
||||
}
|
||||
)
|
||||
78
nixos/tests/dex-oidc.nix
Normal file
78
nixos/tests/dex-oidc.nix
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
import ./make-test-python.nix ({ lib, ... }: {
|
||||
name = "dex-oidc";
|
||||
meta.maintainers = with lib.maintainers; [ Flakebi ];
|
||||
|
||||
nodes.machine = { pkgs, ... }: {
|
||||
environment.systemPackages = with pkgs; [ jq ];
|
||||
services.dex = {
|
||||
enable = true;
|
||||
settings = {
|
||||
issuer = "http://127.0.0.1:8080/dex";
|
||||
storage = {
|
||||
type = "postgres";
|
||||
config.host = "/var/run/postgresql";
|
||||
};
|
||||
web.http = "127.0.0.1:8080";
|
||||
oauth2.skipApprovalScreen = true;
|
||||
staticClients = [
|
||||
{
|
||||
id = "oidcclient";
|
||||
name = "Client";
|
||||
redirectURIs = [ "https://example.com/callback" ];
|
||||
secretFile = "/etc/dex/oidcclient";
|
||||
}
|
||||
];
|
||||
connectors = [
|
||||
{
|
||||
type = "mockPassword";
|
||||
id = "mock";
|
||||
name = "Example";
|
||||
config = {
|
||||
username = "admin";
|
||||
password = "password";
|
||||
};
|
||||
}
|
||||
];
|
||||
};
|
||||
};
|
||||
|
||||
# This should not be set from nix but through other means to not leak the secret.
|
||||
environment.etc."dex/oidcclient" = {
|
||||
mode = "0400";
|
||||
user = "dex";
|
||||
text = "oidcclientsecret";
|
||||
};
|
||||
|
||||
services.postgresql = {
|
||||
enable = true;
|
||||
ensureDatabases =[ "dex" ];
|
||||
ensureUsers = [
|
||||
{
|
||||
name = "dex";
|
||||
ensurePermissions = { "DATABASE dex" = "ALL PRIVILEGES"; };
|
||||
}
|
||||
];
|
||||
};
|
||||
};
|
||||
|
||||
testScript = ''
|
||||
with subtest("Web server gets ready"):
|
||||
machine.wait_for_unit("dex.service")
|
||||
# Wait until server accepts connections
|
||||
machine.wait_until_succeeds("curl -fs 'localhost:8080/dex/auth/mock?client_id=oidcclient&response_type=code&redirect_uri=https://example.com/callback&scope=openid'")
|
||||
|
||||
with subtest("Login"):
|
||||
state = machine.succeed("curl -fs 'localhost:8080/dex/auth/mock?client_id=oidcclient&response_type=code&redirect_uri=https://example.com/callback&scope=openid' | sed -n 's/.*state=\\(.*\\)\">.*/\\1/p'").strip()
|
||||
print(f"Got state {state}")
|
||||
machine.succeed(f"curl -fs 'localhost:8080/dex/auth/mock/login?back=&state={state}' -d 'login=admin&password=password'")
|
||||
code = machine.succeed(f"curl -fs localhost:8080/dex/approval?req={state} | sed -n 's/.*code=\\(.*\\)&.*/\\1/p'").strip()
|
||||
print(f"Got approval code {code}")
|
||||
bearer = machine.succeed(f"curl -fs localhost:8080/dex/token -u oidcclient:oidcclientsecret -d 'grant_type=authorization_code&redirect_uri=https://example.com/callback&code={code}' | jq .access_token -r").strip()
|
||||
print(f"Got access token {bearer}")
|
||||
|
||||
with subtest("Get userinfo"):
|
||||
assert '"sub"' in machine.succeed(
|
||||
f"curl -fs localhost:8080/dex/userinfo --oauth2-bearer {bearer}"
|
||||
)
|
||||
'';
|
||||
})
|
||||
142
nixos/tests/dhparams.nix
Normal file
142
nixos/tests/dhparams.nix
Normal file
|
|
@ -0,0 +1,142 @@
|
|||
let
|
||||
common = { pkgs, ... }: {
|
||||
security.dhparams.enable = true;
|
||||
environment.systemPackages = [ pkgs.openssl ];
|
||||
};
|
||||
|
||||
in import ./make-test-python.nix {
|
||||
name = "dhparams";
|
||||
|
||||
nodes.generation1 = { pkgs, config, ... }: {
|
||||
imports = [ common ];
|
||||
security.dhparams.params = {
|
||||
# Use low values here because we don't want the test to run for ages.
|
||||
foo.bits = 16;
|
||||
# Also use the old format to make sure the type is coerced in the right
|
||||
# way.
|
||||
bar = 17;
|
||||
};
|
||||
|
||||
systemd.services.foo = {
|
||||
description = "Check systemd Ordering";
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
unitConfig = {
|
||||
# This is to make sure that the dhparams generation of foo occurs
|
||||
# before this service so we need this service to start as early as
|
||||
# possible to provoke a race condition.
|
||||
DefaultDependencies = false;
|
||||
|
||||
# We check later whether the service has been started or not.
|
||||
ConditionPathExists = config.security.dhparams.params.foo.path;
|
||||
};
|
||||
serviceConfig.Type = "oneshot";
|
||||
serviceConfig.RemainAfterExit = true;
|
||||
# The reason we only provide an ExecStop here is to ensure that we don't
|
||||
# accidentally trigger an error because a file system is not yet ready
|
||||
# during very early startup (we might not even have the Nix store
|
||||
# available, for example if future changes in NixOS use systemd mount
|
||||
# units to do early file system initialisation).
|
||||
serviceConfig.ExecStop = "${pkgs.coreutils}/bin/true";
|
||||
};
|
||||
};
|
||||
|
||||
nodes.generation2 = {
|
||||
imports = [ common ];
|
||||
security.dhparams.params.foo.bits = 18;
|
||||
};
|
||||
|
||||
nodes.generation3 = common;
|
||||
|
||||
nodes.generation4 = {
|
||||
imports = [ common ];
|
||||
security.dhparams.stateful = false;
|
||||
security.dhparams.params.foo2.bits = 18;
|
||||
security.dhparams.params.bar2.bits = 19;
|
||||
};
|
||||
|
||||
nodes.generation5 = {
|
||||
imports = [ common ];
|
||||
security.dhparams.defaultBitSize = 30;
|
||||
security.dhparams.params.foo3 = {};
|
||||
security.dhparams.params.bar3 = {};
|
||||
};
|
||||
|
||||
testScript = { nodes, ... }: let
|
||||
getParamPath = gen: name: let
|
||||
node = "generation${toString gen}";
|
||||
in nodes.${node}.config.security.dhparams.params.${name}.path;
|
||||
|
||||
switchToGeneration = gen: let
|
||||
node = "generation${toString gen}";
|
||||
inherit (nodes.${node}.config.system.build) toplevel;
|
||||
switchCmd = "${toplevel}/bin/switch-to-configuration test";
|
||||
in ''
|
||||
with machine.nested("switch to generation ${toString gen}"):
|
||||
machine.succeed(
|
||||
"${switchCmd}"
|
||||
)
|
||||
machine = ${node}
|
||||
'';
|
||||
|
||||
in ''
|
||||
import re
|
||||
|
||||
|
||||
def assert_param_bits(path, bits):
|
||||
with machine.nested(f"check bit size of {path}"):
|
||||
output = machine.succeed(f"openssl dhparam -in {path} -text")
|
||||
pattern = re.compile(r"^\s*DH Parameters:\s+\((\d+)\s+bit\)\s*$", re.M)
|
||||
match = pattern.match(output)
|
||||
if match is None:
|
||||
raise Exception("bla")
|
||||
if match[1] != str(bits):
|
||||
raise Exception(f"bit size should be {bits} but it is {match[1]} instead.")
|
||||
|
||||
|
||||
machine = generation1
|
||||
|
||||
machine.wait_for_unit("multi-user.target")
|
||||
|
||||
with subtest("verify startup order"):
|
||||
machine.succeed("systemctl is-active foo.service")
|
||||
|
||||
with subtest("check bit sizes of dhparam files"):
|
||||
assert_param_bits("${getParamPath 1 "foo"}", 16)
|
||||
assert_param_bits("${getParamPath 1 "bar"}", 17)
|
||||
|
||||
${switchToGeneration 2}
|
||||
|
||||
with subtest("check whether bit size has changed"):
|
||||
assert_param_bits("${getParamPath 2 "foo"}", 18)
|
||||
|
||||
with subtest("ensure that dhparams file for 'bar' was deleted"):
|
||||
machine.fail("test -e ${getParamPath 1 "bar"}")
|
||||
|
||||
${switchToGeneration 3}
|
||||
|
||||
with subtest("ensure that 'security.dhparams.path' has been deleted"):
|
||||
machine.fail("test -e ${nodes.generation3.config.security.dhparams.path}")
|
||||
|
||||
${switchToGeneration 4}
|
||||
|
||||
with subtest("check bit sizes dhparam files"):
|
||||
assert_param_bits(
|
||||
"${getParamPath 4 "foo2"}", 18
|
||||
)
|
||||
assert_param_bits(
|
||||
"${getParamPath 4 "bar2"}", 19
|
||||
)
|
||||
|
||||
with subtest("check whether dhparam files are in the Nix store"):
|
||||
machine.succeed(
|
||||
"expr match ${getParamPath 4 "foo2"} ${builtins.storeDir}",
|
||||
"expr match ${getParamPath 4 "bar2"} ${builtins.storeDir}",
|
||||
)
|
||||
|
||||
${switchToGeneration 5}
|
||||
|
||||
with subtest("check whether defaultBitSize works as intended"):
|
||||
assert_param_bits("${getParamPath 5 "foo3"}", 30)
|
||||
assert_param_bits("${getParamPath 5 "bar3"}", 30)
|
||||
'';
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue