Skip to content

Commit

Permalink
Merge pull request #171 from boinkor-net/better-tests
Browse files Browse the repository at this point in the history
Improve tests, add oci-sidecar functionality tests
  • Loading branch information
antifuchs authored Oct 25, 2024
2 parents a750709 + 20c8049 commit af34ed5
Show file tree
Hide file tree
Showing 9 changed files with 388 additions and 198 deletions.
3 changes: 3 additions & 0 deletions nixos/default.nix
Original file line number Diff line number Diff line change
Expand Up @@ -423,6 +423,9 @@ in {
extraOptions = [
"--network=container:${sidecar.forContainer}"
];
environment = lib.optionalAttrs (sidecar.service.loginServerUrl != null) {
TS_URL = sidecar.service.loginServerUrl;
};
cmd =
["-stateDir=/state" "-authkeyPath=${credentialsDir}/authKey"]
++ (serviceArgs {
Expand Down
18 changes: 18 additions & 0 deletions nixos/tests/cmdline/basic.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
pkgs,
nixos-lib,
nixosModule,
validatorPackage,
}: let
helper = import ./../helpers/cmdline_validation.nix {
inherit pkgs nixos-lib nixosModule validatorPackage;
};
in
helper {
testConfig = {
services.tsnsrv.services.basic.toURL = "http://127.0.0.1:3000";
};
testScript = ''
machine.wait_for_unit("tsnsrv-basic")
'';
}
12 changes: 12 additions & 0 deletions nixos/tests/cmdline/default.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# Tests that ensure that generated commandline arguments are correct
# (they use the tsnsrvCmdLineValidator package output, which has all
# functionality stubbed out).
{
pkgs,
nixos-lib,
nixosModule,
validatorPackage,
}: {
basic = import ./basic.nix {inherit pkgs nixos-lib nixosModule validatorPackage;};
with-custom-certs = import ./with-custom-certs.nix {inherit pkgs nixos-lib nixosModule validatorPackage;};
}
22 changes: 22 additions & 0 deletions nixos/tests/cmdline/with-custom-certs.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
{
pkgs,
nixos-lib,
nixosModule,
validatorPackage,
}: let
helper = import ./../helpers/cmdline_validation.nix {
inherit pkgs nixos-lib nixosModule validatorPackage;
};
in
helper {
testConfig = {
services.tsnsrv.services.custom = {
toURL = "http://127.0.0.1:3000";
certificateFile = "/tmp/cert.pem";
certificateKey = "/tmp/key.pem";
};
};
testScript = ''
machine.wait_for_unit("tsnsrv-custom")
'';
}
9 changes: 9 additions & 0 deletions nixos/tests/e2e/default.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
pkgs,
nixos-lib,
nixosModule,
...
}: {
systemd = import ./systemd.nix {inherit pkgs nixos-lib nixosModule;};
oci = import ./oci.nix {inherit pkgs nixos-lib nixosModule;};
}
141 changes: 141 additions & 0 deletions nixos/tests/e2e/oci.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
{
pkgs,
nixos-lib,
nixosModule,
}: let
stunPort = 3478;
in
nixos-lib.runTest {
name = "tsnsrv-nixos";
hostPkgs = pkgs;

nodes.headscale = {
environment.systemPackages = [pkgs.headscale];
services.headscale = {
enable = true;
address = "[::]";
settings = {
ip_prefixes = ["100.64.0.0/10"];
derp.server = {
enabled = true;
region_id = 999;
stun_listen_addr = "0.0.0.0:${toString stunPort}";
};
server_url = "http://headscale:8080";
};
};
networking.firewall = {
allowedTCPPorts = [8080 443];
allowedUDPPorts = [stunPort];
};
};

nodes.machine = {
config,
pkgs,
lib,
...
}: {
imports = [
nixosModule
];

environment.systemPackages = [pkgs.tailscale];
virtualisation.cores = 4;
virtualisation.memorySize = 1024;
services.tailscale.enable = true;
systemd.services.tailscaled.serviceConfig.Environment = ["TS_NO_LOGS_NO_SUPPORT=true"];

services.tsnsrv = {
enable = true;
defaults.tsnetVerbose = true;
defaults.loginServerUrl = "http://headscale:8080";
defaults.authKeyPath = "/run/ts-authkey";
};
virtualisation.oci-sidecars.tsnsrv = {
enable = true;
containers.web-server-tsnsrv = {
name = "web-server";
forContainer = "web-server";
service = {
timeout = "10s";
listenAddr = ":80";
plaintext = true;
toURL = "http://127.0.0.1:3000";
};
};
};
virtualisation.oci-containers = let
htmlRoot = pkgs.writeTextDir "index.html" "It works!";
in {
backend = "podman";
containers.web-server = {
image = "web-server:latest";
imageFile = pkgs.dockerTools.buildImage {
name = "web-server";
tag = "latest";
created = "now";
copyToRoot = pkgs.buildEnv {
name = "image-root";
paths = [pkgs.static-web-server htmlRoot];
pathsToLink = ["/bin"];
};
config.Cmd = ["/bin/static-web-server" "--port" "3000" "--root" htmlRoot];
};
};
};
networking.firewall.trustedInterfaces = ["podman0"];

# Delay starting the container machinery until we have an authkey:
systemd.services.podman-web-server.serviceConfig.ConditionPathExists = "/run/ts-authkey";

# Serve DNS to the podman containers, otherwise they have no idea who headscale is:
virtualisation.podman.defaultNetwork.settings.dns_enabled = true;
services.resolved = {
enable = true;
};
};

testScript = ''
import time
import json
headscale.start()
machine.start()
headscale.wait_for_unit("headscale.service", timeout=30)
headscale.succeed("headscale users create machine")
authkey = headscale.succeed("headscale preauthkeys create --reusable -e 24h -u machine")
with open("authkey", "w") as k:
k.write(authkey)
machine.copy_from_host("authkey", "/run/ts-authkey")
machine.wait_for_unit("tailscaled.service", timeout=30)
machine.succeed('tailscale up --login-server=http://headscale:8080 --auth-key="$(cat /run/ts-authkey)"')
@polling_condition
def tsnsrv_running():
machine.succeed("systemctl is-active podman-web-server-tsnsrv")
def wait_for_tsnsrv_registered():
"Poll until tsnsrv appears in the list of hosts, then return its IP."
while True:
output = json.loads(headscale.succeed("headscale nodes list -o json-line"))
basic_entry = [elt["ip_addresses"][0] for elt in output if elt["given_name"] == "web-server"]
if len(basic_entry) == 1:
return basic_entry[0]
time.sleep(1)
def test_script_e2e():
headscale.wait_until_succeeds("headscale nodes list -o json-line")
machine.wait_for_unit("podman-web-server-tsnsrv", timeout=30)
with tsnsrv_running:
# We don't have magic DNS in this setup, so let's figure out the IP from the node list:
tsnsrv_ip = wait_for_tsnsrv_registered()
print(f"tsnsrv seems up, with IP {tsnsrv_ip}")
machine.wait_until_succeeds(f"tailscale ping {tsnsrv_ip}", timeout=30)
print(machine.succeed(f"curl -f http://{tsnsrv_ip}"))
test_script_e2e()
'';
}
137 changes: 137 additions & 0 deletions nixos/tests/e2e/systemd.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
{
pkgs,
nixos-lib,
nixosModule,
}: let
stunPort = 3478;
in
nixos-lib.runTest {
name = "tsnsrv-nixos";
hostPkgs = pkgs;

defaults.services.tsnsrv.enable = true;
defaults.services.tsnsrv.defaults.tsnetVerbose = true;

# defaults.services.tsnsrv.defaults.package = config.packages.tsnsrvCmdLineValidator;

nodes.machine = {
config,
pkgs,
lib,
...
}: {
imports = [
nixosModule
];

environment.systemPackages = [
pkgs.headscale
pkgs.tailscale
(pkgs.writeShellApplication {
name = "tailscale-up-for-tests";
text = ''
systemctl start --wait [email protected]
tailscale up \
--login-server=${config.services.headscale.settings.server_url} \
--auth-key="$(cat /var/lib/headscale-authkeys/tailscaled.preauth-key)"
'';
})
];
virtualisation.cores = 4;
virtualisation.memorySize = 1024;
services.headscale = {
enable = true;
settings = {
ip_prefixes = ["100.64.0.0/10"];
derp.server = {
enabled = true;
region_id = 999;
stun_listen_addr = "0.0.0.0:${toString stunPort}";
};
};
};
services.tailscale.enable = true;
systemd.services.tailscaled.serviceConfig.Environment = ["TS_NO_LOGS_NO_SUPPORT=true"];
networking.firewall = {
allowedTCPPorts = [80 443];
allowedUDPPorts = [stunPort];
};

systemd.services."generate-tsnsrv-authkey@" = {
description = "Generate headscale authkey for %i";
serviceConfig.ExecStart = let
startScript = pkgs.writeShellApplication {
name = "generate-tsnsrv-authkey";
runtimeInputs = [pkgs.headscale pkgs.jq];
text = ''
set -x
headscale users create "$1"
headscale preauthkeys create --reusable -e 24h -u "$1" > "$STATE_DIRECTORY"/"$1".preauth-key
echo generated "$STATE_DIRECTORY"/"$1".preauth-key
cat "$STATE_DIRECTORY"/"$1".preauth-key
'';
};
in "${lib.getExe startScript} %i";
wants = ["headscale.service"];
after = ["headscale.service"];
serviceConfig.Type = "oneshot";
serviceConfig.StateDirectory = "headscale-authkeys";
serviceConfig.Group = "tsnsrv";
unitConfig.Requires = ["headscale.service"];
};

systemd.services.tsnsrv-basic = {
wants = ["[email protected]"];
after = ["[email protected]"];
unitConfig.Requires = ["[email protected]"];
};
services.static-web-server = {
enable = true;
listen = "127.0.0.1:3000";
root = pkgs.writeTextDir "index.html" "It works!";
};
services.tsnsrv = {
defaults.loginServerUrl = config.services.headscale.settings.server_url;
defaults.authKeyPath = "/var/lib/headscale-authkeys/basic.preauth-key";
services.basic = {
timeout = "10s";
listenAddr = ":80";
plaintext = true; # HTTPS requires certs
toURL = "http://127.0.0.1:3000";
};
};
};

testScript = ''
machine.start()
machine.wait_for_unit("tailscaled.service", timeout=30)
machine.succeed("tailscale-up-for-tests", timeout=30)
import time
import json
@polling_condition
def tsnsrv_running():
machine.succeed("systemctl is-active tsnsrv-basic")
def wait_for_tsnsrv_registered():
"Poll until tsnsrv appears in the list of hosts, then return its IP."
while True:
output = json.loads(machine.succeed("headscale nodes list -o json-line"))
basic_entry = [elt["ip_addresses"][0] for elt in output if elt["given_name"] == "basic"]
if len(basic_entry) == 1:
return basic_entry[0]
time.sleep(1)
def test_script_e2e():
machine.wait_until_succeeds("headscale nodes list -o json-line")
machine.wait_for_unit("tsnsrv-basic", timeout=30)
with tsnsrv_running:
# We don't have magic DNS in this setup, so let's figure out the IP from the node list:
tsnsrv_ip = wait_for_tsnsrv_registered()
print(f"tsnsrv seems up, with IP {tsnsrv_ip}")
machine.wait_until_succeeds(f"tailscale ping {tsnsrv_ip}", timeout=30)
print(machine.succeed(f"curl -f http://{tsnsrv_ip}"))
test_script_e2e()
'';
}
Loading

0 comments on commit af34ed5

Please sign in to comment.