diff options
Diffstat (limited to 'nixos/tests')
52 files changed, 2124 insertions, 577 deletions
diff --git a/nixos/tests/acme.nix b/nixos/tests/acme.nix index f44524a1d6a8..d7452744e17b 100644 --- a/nixos/tests/acme.nix +++ b/nixos/tests/acme.nix @@ -14,8 +14,8 @@ let pythonPackages = (super.python.override { packageOverrides = lib.const (pysuper: { - certifi = pysuper.certifi.overrideDerivation (drv: { - postPatch = (drv.postPatch or "") + '' + certifi = pysuper.certifi.overridePythonAttrs (attrs: { + postPatch = (attrs.postPatch or "") + '' cat "${self.cacert}/etc/ssl/certs/ca-bundle.crt" \ > certifi/cacert.pem ''; diff --git a/nixos/tests/atd.nix b/nixos/tests/atd.nix new file mode 100644 index 000000000000..c2c0a716e0de --- /dev/null +++ b/nixos/tests/atd.nix @@ -0,0 +1,36 @@ +import ./make-test.nix ({ pkgs, lib, ... }: + +{ + name = "atd"; + meta = with pkgs.stdenv.lib.maintainers; { + maintainers = [ bjornfor ]; + }; + + machine = + { config, pkgs, ... }: + { services.atd.enable = true; + users.extraUsers.alice = { isNormalUser = true; }; + }; + + # "at" has a resolution of 1 minute + testScript = '' + startAll; + + $machine->fail("test -f ~root/at-1"); + $machine->fail("test -f ~root/batch-1"); + $machine->fail("test -f ~alice/at-1"); + $machine->fail("test -f ~alice/batch-1"); + + $machine->succeed("echo 'touch ~root/at-1' | at now+1min"); + $machine->succeed("echo 'touch ~root/batch-1' | batch"); + $machine->succeed("su - alice -c \"echo 'touch at-1' | at now+1min\""); + $machine->succeed("su - alice -c \"echo 'touch batch-1' | batch\""); + + $machine->succeed("sleep 1.5m"); + + $machine->succeed("test -f ~root/at-1"); + $machine->succeed("test -f ~root/batch-1"); + $machine->succeed("test -f ~alice/at-1"); + $machine->succeed("test -f ~alice/batch-1"); + ''; +}) diff --git a/nixos/tests/boot-stage1.nix b/nixos/tests/boot-stage1.nix index 50186525cf39..eeaca9f50edc 100644 --- a/nixos/tests/boot-stage1.nix +++ b/nixos/tests/boot-stage1.nix @@ -21,11 +21,16 @@ import ./make-test.nix ({ pkgs, ... }: { # 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 struct task_struct *canaryTask; diff --git a/nixos/tests/common/letsencrypt.nix b/nixos/tests/common/letsencrypt.nix index 5a2a266d4da5..9b53d9d61a16 100644 --- a/nixos/tests/common/letsencrypt.nix +++ b/nixos/tests/common/letsencrypt.nix @@ -228,12 +228,12 @@ let # Retrieved via: # curl -s -I https://acme-v01.api.letsencrypt.org/terms \ # | sed -ne 's/^[Ll]ocation: *//p' - tosUrl = "https://letsencrypt.org/documents/LE-SA-v1.1.1-August-1-2016.pdf"; + tosUrl = "https://letsencrypt.org/documents/2017.11.15-LE-SA-v1.2.pdf"; tosPath = builtins.head (builtins.match "https?://[^/]+(.*)" tosUrl); tosFile = pkgs.fetchurl { url = tosUrl; - sha256 = "08b2gacdz23mzji2pjr1pwnk82a84rzvr36isif7mmi9kydl6wv3"; + sha256 = "0yvyckqzj0b1xi61sypcha82nanizzlm8yqy828h2jbza7cxi26c"; }; resolver = let diff --git a/nixos/tests/containers-bridge.nix b/nixos/tests/containers-bridge.nix index 598abd22e61b..b8d4759684cc 100644 --- a/nixos/tests/containers-bridge.nix +++ b/nixos/tests/containers-bridge.nix @@ -69,6 +69,12 @@ import ./make-test.nix ({ pkgs, ...} : { $machine->succeed("ping -n -c 1 $ip6"); $machine->succeed("curl --fail http://[$ip6]/ > /dev/null"); + # Check that nixos-container show-ip works in case of an ipv4 address with + # subnetmask in CIDR notation. + my $result = $machine->succeed("nixos-container show-ip webserver"); + chomp $result; + $result eq $ip or die; + # Stop the container. $machine->succeed("nixos-container stop webserver"); $machine->fail("curl --fail --connect-timeout 2 http://$ip/ > /dev/null"); diff --git a/nixos/tests/couchdb.nix b/nixos/tests/couchdb.nix new file mode 100644 index 000000000000..a3f675236bc6 --- /dev/null +++ b/nixos/tests/couchdb.nix @@ -0,0 +1,56 @@ +import ./make-test.nix ({ pkgs, lib, ...}: + +with lib; + +{ + name = "couchdb"; + meta = with pkgs.stdenv.lib.maintainers; { + maintainers = [ fpletz ]; + }; + + nodes = { + couchdb1 = + { pkgs, config, ... }: + + { environment.systemPackages = with pkgs; [ jq ]; + services.couchdb.enable = true; + }; + + couchdb2 = + { pkgs, config, ... }: + + { environment.systemPackages = with pkgs; [ jq ]; + services.couchdb.enable = true; + services.couchdb.package = pkgs.couchdb2; + }; + }; + + testScript = let + curlJqCheck = action: path: jqexpr: result: + pkgs.writeScript "curl-jq-check-${action}-${path}.sh" '' + RESULT=$(curl -X ${action} http://127.0.0.1:5984/${path} | jq -r '${jqexpr}') + echo $RESULT >&2 + if [ "$RESULT" != "${result}" ]; then + exit 1 + fi + ''; + in '' + startAll; + + $couchdb1->waitForUnit("couchdb.service"); + $couchdb1->waitUntilSucceeds("${curlJqCheck "GET" "" ".couchdb" "Welcome"}"); + $couchdb1->waitUntilSucceeds("${curlJqCheck "GET" "_all_dbs" ". | length" "2"}"); + $couchdb1->succeed("${curlJqCheck "PUT" "foo" ".ok" "true"}"); + $couchdb1->succeed("${curlJqCheck "GET" "_all_dbs" ". | length" "3"}"); + $couchdb1->succeed("${curlJqCheck "DELETE" "foo" ".ok" "true"}"); + $couchdb1->succeed("${curlJqCheck "GET" "_all_dbs" ". | length" "2"}"); + + $couchdb2->waitForUnit("couchdb.service"); + $couchdb2->waitUntilSucceeds("${curlJqCheck "GET" "" ".couchdb" "Welcome"}"); + $couchdb2->waitUntilSucceeds("${curlJqCheck "GET" "_all_dbs" ". | length" "0"}"); + $couchdb2->succeed("${curlJqCheck "PUT" "foo" ".ok" "true"}"); + $couchdb2->succeed("${curlJqCheck "GET" "_all_dbs" ". | length" "1"}"); + $couchdb2->succeed("${curlJqCheck "DELETE" "foo" ".ok" "true"}"); + $couchdb2->succeed("${curlJqCheck "GET" "_all_dbs" ". | length" "0"}"); + ''; +}) diff --git a/nixos/tests/dovecot.nix b/nixos/tests/dovecot.nix new file mode 100644 index 000000000000..3814855ed8e7 --- /dev/null +++ b/nixos/tests/dovecot.nix @@ -0,0 +1,64 @@ +import ./make-test.nix { + name = "dovecot"; + + machine = { pkgs, ... }: { + imports = [ common/user-account.nix ]; + services.postfix.enable = true; + services.dovecot2.enable = true; + services.dovecot2.protocols = [ "imap" "pop3" ]; + environment.systemPackages = let + sendTestMail = pkgs.writeScriptBin "send-testmail" '' + #!${pkgs.stdenv.shell} + exec sendmail -vt <<MAIL + From: root@localhost + To: alice@localhost + Subject: Very important! + + Hello world! + MAIL + ''; + + testImap = pkgs.writeScriptBin "test-imap" '' + #!${pkgs.python3.interpreter} + import imaplib + + with imaplib.IMAP4('localhost') as imap: + imap.login('alice', 'foobar') + imap.select() + status, refs = imap.search(None, 'ALL') + assert status == 'OK' + assert len(refs) == 1 + status, msg = imap.fetch(refs[0], 'BODY[TEXT]') + assert status == 'OK' + assert msg[0][1].strip() == b'Hello world!' + ''; + + testPop = pkgs.writeScriptBin "test-pop" '' + #!${pkgs.python3.interpreter} + import poplib + + pop = poplib.POP3('localhost') + try: + pop.user('alice') + pop.pass_('foobar') + assert len(pop.list()[1]) == 1 + status, fullmail, size = pop.retr(1) + assert status.startswith(b'+OK ') + body = b"".join(fullmail[fullmail.index(b""):]).strip() + assert body == b'Hello world!' + finally: + pop.quit() + ''; + + in [ sendTestMail testImap testPop ]; + }; + + testScript = '' + $machine->waitForUnit('postfix.service'); + $machine->waitForUnit('dovecot2.service'); + $machine->succeed('send-testmail'); + $machine->waitUntilFails('[ "$(postqueue -p)" != "Mail queue is empty" ]'); + $machine->succeed('test-imap'); + $machine->succeed('test-pop'); + ''; +} diff --git a/nixos/tests/gitlab.nix b/nixos/tests/gitlab.nix index 357911046960..7268636b62ad 100644 --- a/nixos/tests/gitlab.nix +++ b/nixos/tests/gitlab.nix @@ -9,9 +9,57 @@ import ./make-test.nix ({ pkgs, ...} : { nodes = { gitlab = { config, pkgs, ... }: { virtualisation.memorySize = 768; - services.gitlab.enable = true; - services.gitlab.databasePassword = "gitlab"; + + services.nginx = { + enable = true; + virtualHosts = { + "localhost" = { + locations."/".proxyPass = "http://unix:/run/gitlab/gitlab-workhorse.socket"; + }; + }; + }; + systemd.services.gitlab.serviceConfig.TimeoutStartSec = "10min"; + services.gitlab = { + enable = true; + databasePassword = "dbPassword"; + secrets = { + secret = "secret"; + otp = "otpsecret"; + db = "dbsecret"; + + # nix-shell -p openssl --run "openssl genrsa 2048" + jws = '' + -----BEGIN RSA PRIVATE KEY----- + MIIEpAIBAAKCAQEA13/qEio76OWUtWO0WIz9lWnsTWOU8Esv4sQHDq9PCEFsLt21 + PAXrlWhLjjWcxGfsrDwnh7YErGHYL62BMSxMdFJolaknlQK/O/V8UETDe45VoHM+ + Znk270RfUcfYFgiihnXUZXVmL0om9TsQSk646wCcjCY9LxtxUyKNhvT7KjgYw2aX + z34aw7M+Js3T2p1TjZPSC82GtmtKkJEKFMi5EjprLTDE7EdcUzr9Xuw+kQ+gRm9k + 7FE+JQqSoprwE3Q0v2OAn3UhLMgg0gNFRnsc5l6IAshDzV+H22RPqKKlJjVjjfPY + 0TQSvYLVApigHbDPH0BoCXfjFfQazbbP3OUHrwIDAQABAoIBAQCMU+tkcMQaYIV5 + qLdjgkwO467QpivyXcOM8wF1eosIYTHFQvIlZ+WEoSmyLQ8shlADyBgls01Pw1c3 + lNAv6RzQEmmwKzpvOh61OKH+0whIiOMRXHoh2IUBQZCgfHYlwvGyhUAN4WjtGmhM + AG4XNTQNM5S9Xpkw97nP3Qwz+YskbbkrfqtCEVy9ro+4nhbjqPsuO3adbnkva4zR + cyurRhrHgHU6LPjn5NHnHH4qw2faY2oAsL8pmpkTbO5IqWDvOcbjNfjVPgVoq26O + bbaa1qs4nmc80qQgMjRPJef535xyf3eLsSlDvpf6O8sPrJzVR1zaqEqixpQCZDac + +kRiSBrhAoGBAOwHiq0PuyJh6VzBu7ybqX6+gF/wA4Jkwzx6mbfaBgurvU1aospp + kisIonAkxSbxllZMnjbkShZEdATYKeT9o5NEhnU4YnHfc5bJZbiWOZAzYGLcY7g8 + vDQ31pBItyY4pFgPbSpNlbUvUsoPVJ45RasRADDTNCzMzdjFQQXst2V9AoGBAOm7 + sSpzYfFPLEAhieAkuhtbsX58Boo46djiKVfzGftfp6F9aHTOfzGORU5jrZ16mSbS + qkkC6BEFrATX2051dzzXC89fWoJYALrsffE5I3KlKXsCAWSnCP1MMxOfH+Ls61Mr + 7pK/LKfvJt53mUH4jIdbmmFUDwbg18oBEH+x9PmbAoGAS/+JqXu9N67rIxDGUE6W + 3tacI0f2+U9Uhe67/DTZaXyc8YFTlXU0uWKIWy+bw5RaYeM9tlL/f/f+m2i25KK+ + vrZ7zNag7CWU5GJovGyykDnauTpZaYM03mN0VPT08/uc/zXIYqyknbhlIeaZynCK + fDB3LUF0NVCknz20WCIGU0kCgYEAkxY0ZXx61Dp4pFr2wwEZxQGs7uXpz64FKyEX + 12r6nMATY4Lh6y/Px0W6w5vis8lk+5Ny6cNUevHQ0LNuJS+yu6ywl+1vrbrnqroM + f3LvpcPeGLSoX8jl1VDQi7aFgG6LoKly1xJLbdsH4NPutB9PgBbbTghx9GgmI88L + rPA2M6UCgYBOmkYJocNgxg6B1/n4Tb9fN1Q/XuJrFDE6NxVUoke+IIyMPRH7FC3m + VMYzu+b7zTVJjaBb1cmJemxl/xajziWDofJYPefhdbOVU7HXtmJFY0IG3pVxU1zW + 3bmDj5QAtCUDpuuNa6GEIT0YR4+D/V7o3DmlZ0tVIwKJmVJoQ2f5dw== + -----END RSA PRIVATE KEY----- + ''; + }; + }; }; }; @@ -19,6 +67,6 @@ import ./make-test.nix ({ pkgs, ...} : { $gitlab->start(); $gitlab->waitForUnit("gitlab.service"); $gitlab->waitForUnit("gitlab-sidekiq.service"); - $gitlab->waitUntilSucceeds("curl http://localhost:8080/users/sign_in"); + $gitlab->waitUntilSucceeds("curl http://localhost:80/users/sign_in"); ''; }) diff --git a/nixos/tests/gitolite.nix b/nixos/tests/gitolite.nix new file mode 100644 index 000000000000..4b4e081acc5f --- /dev/null +++ b/nixos/tests/gitolite.nix @@ -0,0 +1,139 @@ +import ./make-test.nix ({ pkgs, ...}: + +let + adminPrivateKey = pkgs.writeText "id_ed25519" '' + -----BEGIN OPENSSH PRIVATE KEY----- + b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW + QyNTUxOQAAACDu7qxYQAPdAU6RrhB3llk2N1v4PTwcVzcX1oX265uC3gAAAJBJiYxDSYmM + QwAAAAtzc2gtZWQyNTUxOQAAACDu7qxYQAPdAU6RrhB3llk2N1v4PTwcVzcX1oX265uC3g + AAAEDE1W6vMwSEUcF1r7Hyypm/+sCOoDmKZgPxi3WOa1mD2u7urFhAA90BTpGuEHeWWTY3 + W/g9PBxXNxfWhfbrm4LeAAAACGJmb0BtaW5pAQIDBAU= + -----END OPENSSH PRIVATE KEY----- + ''; + + adminPublicKey = '' + ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIO7urFhAA90BTpGuEHeWWTY3W/g9PBxXNxfWhfbrm4Le root@client + ''; + + alicePrivateKey = pkgs.writeText "id_ed25519" '' + -----BEGIN OPENSSH PRIVATE KEY----- + b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW + QyNTUxOQAAACBbeWvHh/AWGWI6EIc1xlSihyXtacNQ9KeztlW/VUy8wQAAAJAwVQ5VMFUO + VQAAAAtzc2gtZWQyNTUxOQAAACBbeWvHh/AWGWI6EIc1xlSihyXtacNQ9KeztlW/VUy8wQ + AAAEB7lbfkkdkJoE+4TKHPdPQWBKLSx+J54Eg8DaTr+3KoSlt5a8eH8BYZYjoQhzXGVKKH + Je1pw1D0p7O2Vb9VTLzBAAAACGJmb0BtaW5pAQIDBAU= + -----END OPENSSH PRIVATE KEY----- + ''; + + alicePublicKey = pkgs.writeText "id_ed25519.pub" '' + ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIFt5a8eH8BYZYjoQhzXGVKKHJe1pw1D0p7O2Vb9VTLzB alice@client + ''; + + bobPrivateKey = pkgs.writeText "id_ed25519" '' + -----BEGIN OPENSSH PRIVATE KEY----- + b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW + QyNTUxOQAAACCWTaJ1D9Xjxy6759FvQ9oXTes1lmWBciXPkEeqTikBMAAAAJDQBmNV0AZj + VQAAAAtzc2gtZWQyNTUxOQAAACCWTaJ1D9Xjxy6759FvQ9oXTes1lmWBciXPkEeqTikBMA + AAAEDM1IYYFUwk/IVxauha9kuR6bbRtT3gZ6ZA0GLb9txb/pZNonUP1ePHLrvn0W9D2hdN + 6zWWZYFyJc+QR6pOKQEwAAAACGJmb0BtaW5pAQIDBAU= + -----END OPENSSH PRIVATE KEY----- + ''; + + bobPublicKey = pkgs.writeText "id_ed25519.pub" '' + ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIJZNonUP1ePHLrvn0W9D2hdN6zWWZYFyJc+QR6pOKQEw bob@client + ''; + + gitoliteAdminConfSnippet = '' + repo alice-project + RW+ = alice + ''; +in +{ + name = "gitolite"; + + meta = with pkgs.stdenv.lib.maintainers; { + maintainers = [ bjornfor ]; + }; + + nodes = { + + server = + { config, pkgs, lib, ... }: + { + services.gitolite = { + enable = true; + adminPubkey = adminPublicKey; + }; + services.openssh.enable = true; + }; + + client = + { config, pkgs, lib, ... }: + { + environment.systemPackages = [ pkgs.git ]; + programs.ssh.extraConfig = '' + Host * + UserKnownHostsFile /dev/null + StrictHostKeyChecking no + # there's nobody around that can input password + PreferredAuthentications publickey + ''; + users.extraUsers.alice = { isNormalUser = true; }; + users.extraUsers.bob = { isNormalUser = true; }; + }; + + }; + + testScript = '' + startAll; + + subtest "can setup ssh keys on system", sub { + $client->mustSucceed("mkdir -p ~root/.ssh"); + $client->mustSucceed("cp ${adminPrivateKey} ~root/.ssh/id_ed25519"); + $client->mustSucceed("chmod 600 ~root/.ssh/id_ed25519"); + + $client->mustSucceed("sudo -u alice mkdir -p ~alice/.ssh"); + $client->mustSucceed("sudo -u alice cp ${alicePrivateKey} ~alice/.ssh/id_ed25519"); + $client->mustSucceed("sudo -u alice chmod 600 ~alice/.ssh/id_ed25519"); + + $client->mustSucceed("sudo -u bob mkdir -p ~bob/.ssh"); + $client->mustSucceed("sudo -u bob cp ${bobPrivateKey} ~bob/.ssh/id_ed25519"); + $client->mustSucceed("sudo -u bob chmod 600 ~bob/.ssh/id_ed25519"); + }; + + subtest "gitolite server starts", sub { + $server->waitForUnit("gitolite-init.service"); + $server->waitForUnit("sshd.service"); + $client->mustSucceed('ssh gitolite@server info'); + }; + + subtest "admin can clone and configure gitolite-admin.git", sub { + $client->mustSucceed('git clone gitolite@server:gitolite-admin.git'); + $client->mustSucceed("git config --global user.name 'System Administrator'"); + $client->mustSucceed("git config --global user.email root\@domain.example"); + $client->mustSucceed("cp ${alicePublicKey} gitolite-admin/keydir/alice.pub"); + $client->mustSucceed("cp ${bobPublicKey} gitolite-admin/keydir/bob.pub"); + $client->mustSucceed('(cd gitolite-admin && git add . && git commit -m "Add keys for alice, bob" && git push)'); + $client->mustSucceed("printf '${gitoliteAdminConfSnippet}' >> gitolite-admin/conf/gitolite.conf"); + $client->mustSucceed('(cd gitolite-admin && git add . && git commit -m "Add repo for alice" && git push)'); + }; + + subtest "non-admins cannot clone gitolite-admin.git", sub { + $client->mustFail('sudo -i -u alice git clone gitolite@server:gitolite-admin.git'); + $client->mustFail('sudo -i -u bob git clone gitolite@server:gitolite-admin.git'); + }; + + subtest "non-admins can clone testing.git", sub { + $client->mustSucceed('sudo -i -u alice git clone gitolite@server:testing.git'); + $client->mustSucceed('sudo -i -u bob git clone gitolite@server:testing.git'); + }; + + subtest "alice can clone alice-project.git", sub { + $client->mustSucceed('sudo -i -u alice git clone gitolite@server:alice-project.git'); + }; + + subtest "bob cannot clone alice-project.git", sub { + $client->mustFail('sudo -i -u bob git clone gitolite@server:alice-project.git'); + }; + ''; +}) diff --git a/nixos/tests/gnome3-gdm.nix b/nixos/tests/gnome3-gdm.nix index 2c9c745021af..4b459e93e1be 100644 --- a/nixos/tests/gnome3-gdm.nix +++ b/nixos/tests/gnome3-gdm.nix @@ -21,7 +21,7 @@ import ./make-test.nix ({ pkgs, ...} : { }; services.xserver.desktopManager.gnome3.enable = true; - virtualisation.memorySize = 512; + virtualisation.memorySize = 1024; }; testScript = diff --git a/nixos/tests/grafana.nix b/nixos/tests/grafana.nix new file mode 100644 index 000000000000..16b8181498a6 --- /dev/null +++ b/nixos/tests/grafana.nix @@ -0,0 +1,25 @@ +import ./make-test.nix ({ lib, ... }: +{ + name = "grafana"; + + meta = with lib.maintainers; { + maintainers = [ willibutz ]; + }; + + machine = { config, pkgs, ... }: { + services.grafana = { + enable = true; + addr = "localhost"; + analytics.reporting.enable = false; + domain = "localhost"; + security.adminUser = "testusername"; + }; + }; + + testScript = '' + $machine->start; + $machine->waitForUnit("grafana.service"); + $machine->waitForOpenPort(3000); + $machine->succeed("curl -sS http://127.0.0.1:3000/"); + ''; +}) diff --git a/nixos/tests/graphite.nix b/nixos/tests/graphite.nix new file mode 100644 index 000000000000..a22ef224580a --- /dev/null +++ b/nixos/tests/graphite.nix @@ -0,0 +1,32 @@ +import ./make-test.nix ({ pkgs, ...} : +{ + name = "graphite"; + nodes = { + one = + { config, pkgs, ... }: { + time.timeZone = "UTC"; + services.graphite = { + web.enable = true; + api = { + enable = true; + port = 8082; + }; + carbon.enableCache = true; + seyren.enable = true; + pager.enable = true; + }; + }; + }; + + testScript = '' + startAll; + $one->waitForUnit("default.target"); + $one->requireActiveUnit("graphiteWeb.service"); + $one->requireActiveUnit("graphiteApi.service"); + $one->requireActiveUnit("graphitePager.service"); + $one->requireActiveUnit("carbonCache.service"); + $one->requireActiveUnit("seyren.service"); + $one->succeed("echo \"foo 1 `date +%s`\" | nc -q0 localhost 2003"); + $one->waitUntilSucceeds("curl 'http://localhost:8080/metrics/find/?query=foo&format=treejson' --silent | grep foo") + ''; +}) diff --git a/nixos/tests/hardened.nix b/nixos/tests/hardened.nix index 1d9a9043e03a..cb33b69e7199 100644 --- a/nixos/tests/hardened.nix +++ b/nixos/tests/hardened.nix @@ -10,6 +10,17 @@ import ./make-test.nix ({ pkgs, ...} : { { users.users.alice = { isNormalUser = true; extraGroups = [ "proc" ]; }; users.users.sybil = { isNormalUser = true; group = "wheel"; }; imports = [ ../modules/profiles/hardened.nix ]; + virtualisation.emptyDiskImages = [ 4096 ]; + boot.initrd.postDeviceCommands = '' + ${pkgs.dosfstools}/bin/mkfs.vfat -n EFISYS /dev/vdb + ''; + fileSystems = lib.mkVMOverride { + "/efi" = { + device = "/dev/disk/by-label/EFISYS"; + fsType = "vfat"; + options = [ "noauto" ]; + }; + }; }; testScript = @@ -32,5 +43,23 @@ import ./make-test.nix ({ pkgs, ...} : { subtest "userns", sub { $machine->fail("unshare --user"); }; + + # Test dmesg restriction + subtest "dmesg", sub { + $machine->fail("su -l alice -c dmesg"); + }; + + # Test access to kcore + subtest "kcore", sub { + $machine->fail("cat /proc/kcore"); + }; + + # Test deferred mount + subtest "mount", sub { + $machine->fail("mountpoint -q /efi"); # was deferred + $machine->execute("mkdir -p /efi"); + $machine->succeed("mount /dev/disk/by-label/EFISYS /efi"); + $machine->succeed("mountpoint -q /efi"); # now mounted + }; ''; }) diff --git a/nixos/tests/initrd-network-ssh.nix b/nixos/tests/initrd-network-ssh.nix new file mode 100644 index 000000000000..596610493921 --- /dev/null +++ b/nixos/tests/initrd-network-ssh.nix @@ -0,0 +1,74 @@ +import ./make-test.nix ({ pkgs, lib, ... }: + +let + keys = pkgs.runCommand "gen-keys" { + outputs = [ "out" "dbPub" "dbPriv" "sshPub" "sshPriv" ]; + buildInputs = with pkgs; [ dropbear openssh ]; + } + '' + touch $out + dropbearkey -t rsa -f $dbPriv -s 4096 | sed -n 2p > $dbPub + ssh-keygen -q -t rsa -b 4096 -N "" -f client + mv client $sshPriv + mv client.pub $sshPub + ''; + +in { + name = "initrd-network-ssh"; + meta = with lib.maintainers; { + maintainers = [ willibutz ]; + }; + + nodes = with lib; rec { + server = + { config, pkgs, ... }: + { + boot.kernelParams = [ + "ip=${ + (head config.networking.interfaces.eth1.ip4).address + }:::255.255.255.0::eth1:none" + ]; + boot.initrd.network = { + enable = true; + ssh = { + enable = true; + authorizedKeys = [ "${readFile keys.sshPub}" ]; + port = 22; + hostRSAKey = keys.dbPriv; + }; + }; + boot.initrd.preLVMCommands = '' + while true; do + if [ -f fnord ]; then + poweroff + fi + sleep 1 + done + ''; + }; + + client = + { config, pkgs, ... }: + { + environment.etc.knownHosts = { + text = concatStrings [ + "server," + "${toString (head (splitString " " ( + toString (elemAt (splitString "\n" config.networking.extraHosts) 2) + )))} " + "${readFile keys.dbPub}" + ]; + }; + }; + }; + + testScript = '' + startAll; + $client->waitForUnit("network.target"); + $client->copyFileFromHost("${keys.sshPriv}","/etc/sshKey"); + $client->succeed("chmod 0600 /etc/sshKey"); + $client->waitUntilSucceeds("ping -c 1 server"); + $client->succeed("ssh -i /etc/sshKey -o UserKnownHostsFile=/etc/knownHosts server 'touch /fnord'"); + $client->shutdown; + ''; +}) diff --git a/nixos/tests/installer.nix b/nixos/tests/installer.nix index 670e4ce1aeaf..8c04b606e481 100644 --- a/nixos/tests/installer.nix +++ b/nixos/tests/installer.nix @@ -7,7 +7,7 @@ with pkgs.lib; let # The configuration to install. - makeConfig = { bootLoader, grubVersion, grubDevice, grubIdentifier + makeConfig = { bootLoader, grubVersion, grubDevice, grubIdentifier, grubUseEfi , extraConfig, forceGrubReinstallCount ? 0 }: pkgs.writeText "configuration.nix" '' @@ -26,9 +26,16 @@ let ${optionalString (grubVersion == 1) '' boot.loader.grub.splashImage = null; ''} - boot.loader.grub.device = "${grubDevice}"; + boot.loader.grub.extraConfig = "serial; terminal_output.serial"; - boot.loader.grub.fsIdentifier = "${grubIdentifier}"; + ${if grubUseEfi then '' + boot.loader.grub.device = "nodev"; + boot.loader.grub.efiSupport = true; + boot.loader.grub.efiInstallAsRemovable = true; # XXX: needed for OVMF? + '' else '' + boot.loader.grub.device = "${grubDevice}"; + boot.loader.grub.fsIdentifier = "${grubIdentifier}"; + ''} boot.loader.grub.configurationLimit = 100 + ${toString forceGrubReinstallCount}; ''} @@ -57,16 +64,17 @@ let # disk, and then reboot from the hard disk. It's parameterized with # a test script fragment `createPartitions', which must create # partitions and filesystems. - testScriptFun = { bootLoader, createPartitions, grubVersion, grubDevice + testScriptFun = { bootLoader, createPartitions, grubVersion, grubDevice, grubUseEfi , grubIdentifier, preBootCommands, extraConfig }: let iface = if grubVersion == 1 then "ide" else "virtio"; + isEfi = bootLoader == "systemd-boot" || (bootLoader == "grub" && grubUseEfi); qemuFlags = (if system == "x86_64-linux" then "-m 768 " else "-m 512 ") + (optionalString (system == "x86_64-linux") "-cpu kvm64 "); hdFlags = ''hda => "vm-state-machine/machine.qcow2", hdaInterface => "${iface}", '' - + optionalString (bootLoader == "systemd-boot") ''bios => "${pkgs.OVMF.fd}/FV/OVMF.fd", ''; + + optionalString isEfi ''bios => "${pkgs.OVMF.fd}/FV/OVMF.fd", ''; in '' $machine->start; @@ -89,7 +97,7 @@ let $machine->succeed("cat /mnt/etc/nixos/hardware-configuration.nix >&2"); $machine->copyFileFromHost( - "${ makeConfig { inherit bootLoader grubVersion grubDevice grubIdentifier extraConfig; } }", + "${ makeConfig { inherit bootLoader grubVersion grubDevice grubIdentifier grubUseEfi extraConfig; } }", "/mnt/etc/nixos/configuration.nix"); # Perform the installation. @@ -140,7 +148,7 @@ let # We need to a writable nix-store on next boot. $machine->copyFileFromHost( - "${ makeConfig { inherit bootLoader grubVersion grubDevice grubIdentifier extraConfig; forceGrubReinstallCount = 1; } }", + "${ makeConfig { inherit bootLoader grubVersion grubDevice grubIdentifier grubUseEfi extraConfig; forceGrubReinstallCount = 1; } }", "/etc/nixos/configuration.nix"); # Check whether nixos-rebuild works. @@ -158,7 +166,7 @@ let ${preBootCommands} $machine->waitForUnit("multi-user.target"); $machine->copyFileFromHost( - "${ makeConfig { inherit bootLoader grubVersion grubDevice grubIdentifier extraConfig; forceGrubReinstallCount = 2; } }", + "${ makeConfig { inherit bootLoader grubVersion grubDevice grubIdentifier grubUseEfi extraConfig; forceGrubReinstallCount = 2; } }", "/etc/nixos/configuration.nix"); $machine->succeed("nixos-rebuild boot >&2"); $machine->shutdown; @@ -176,7 +184,7 @@ let { createPartitions, preBootCommands ? "", extraConfig ? "" , extraInstallerConfig ? {} , bootLoader ? "grub" # either "grub" or "systemd-boot" - , grubVersion ? 2, grubDevice ? "/dev/vda", grubIdentifier ? "uuid" + , grubVersion ? 2, grubDevice ? "/dev/vda", grubIdentifier ? "uuid", grubUseEfi ? false , enableOCR ? false, meta ? {} }: makeTest { @@ -244,7 +252,7 @@ let testScript = testScriptFun { inherit bootLoader createPartitions preBootCommands - grubVersion grubDevice grubIdentifier extraConfig; + grubVersion grubDevice grubIdentifier grubUseEfi extraConfig; }; }; @@ -260,9 +268,9 @@ in { { createPartitions = '' $machine->succeed( - "parted /dev/vda mklabel msdos", - "parted /dev/vda -- mkpart primary linux-swap 1M 1024M", - "parted /dev/vda -- mkpart primary ext2 1024M -1s", + "parted --script /dev/vda mklabel msdos", + "parted --script /dev/vda -- mkpart primary linux-swap 1M 1024M", + "parted --script /dev/vda -- mkpart primary ext2 1024M -1s", "udevadm settle", "mkswap /dev/vda1 -L swap", "swapon -L swap", @@ -277,6 +285,28 @@ in { { createPartitions = '' $machine->succeed( + "parted --script /dev/vda mklabel gpt", + "parted --script /dev/vda -- mkpart ESP fat32 1M 50MiB", # /boot + "parted --script /dev/vda -- set 1 boot on", + "parted --script /dev/vda -- mkpart primary linux-swap 50MiB 1024MiB", + "parted --script /dev/vda -- mkpart primary ext2 1024MiB -1MiB", # / + "udevadm settle", + "mkswap /dev/vda2 -L swap", + "swapon -L swap", + "mkfs.ext3 -L nixos /dev/vda3", + "mount LABEL=nixos /mnt", + "mkfs.vfat -n BOOT /dev/vda1", + "mkdir -p /mnt/boot", + "mount LABEL=BOOT /mnt/boot", + ); + ''; + bootLoader = "systemd-boot"; + }; + + simpleUefiGrub = makeInstallerTest "simpleUefiGrub" + { createPartitions = + '' + $machine->succeed( "parted /dev/vda mklabel gpt", "parted -s /dev/vda -- mkpart ESP fat32 1M 50MiB", # /boot "parted -s /dev/vda -- set 1 boot on", @@ -292,7 +322,8 @@ in { "mount LABEL=BOOT /mnt/boot", ); ''; - bootLoader = "systemd-boot"; + bootLoader = "grub"; + grubUseEfi = true; }; # Same as the previous, but now with a separate /boot partition. @@ -300,10 +331,10 @@ in { { createPartitions = '' $machine->succeed( - "parted /dev/vda mklabel msdos", - "parted /dev/vda -- mkpart primary ext2 1M 50MB", # /boot - "parted /dev/vda -- mkpart primary linux-swap 50MB 1024M", - "parted /dev/vda -- mkpart primary ext2 1024M -1s", # / + "parted --script /dev/vda mklabel msdos", + "parted --script /dev/vda -- mkpart primary ext2 1M 50MB", # /boot + "parted --script /dev/vda -- mkpart primary linux-swap 50MB 1024M", + "parted --script /dev/vda -- mkpart primary ext2 1024M -1s", # / "udevadm settle", "mkswap /dev/vda2 -L swap", "swapon -L swap", @@ -321,10 +352,10 @@ in { { createPartitions = '' $machine->succeed( - "parted /dev/vda mklabel msdos", - "parted /dev/vda -- mkpart primary ext2 1M 50MB", # /boot - "parted /dev/vda -- mkpart primary linux-swap 50MB 1024M", - "parted /dev/vda -- mkpart primary ext2 1024M -1s", # / + "parted --script /dev/vda mklabel msdos", + "parted --script /dev/vda -- mkpart primary ext2 1M 50MB", # /boot + "parted --script /dev/vda -- mkpart primary linux-swap 50MB 1024M", + "parted --script /dev/vda -- mkpart primary ext2 1024M -1s", # / "udevadm settle", "mkswap /dev/vda2 -L swap", "swapon -L swap", @@ -357,9 +388,9 @@ in { createPartitions = '' $machine->succeed( - "parted /dev/vda mklabel msdos", - "parted /dev/vda -- mkpart primary linux-swap 1M 1024M", - "parted /dev/vda -- mkpart primary 1024M -1s", + "parted --script /dev/vda mklabel msdos", + "parted --script /dev/vda -- mkpart primary linux-swap 1M 1024M", + "parted --script /dev/vda -- mkpart primary 1024M -1s", "udevadm settle", "mkswap /dev/vda1 -L swap", @@ -380,11 +411,11 @@ in { { createPartitions = '' $machine->succeed( - "parted /dev/vda mklabel msdos", - "parted /dev/vda -- mkpart primary 1M 2048M", # PV1 - "parted /dev/vda -- set 1 lvm on", - "parted /dev/vda -- mkpart primary 2048M -1s", # PV2 - "parted /dev/vda -- set 2 lvm on", + "parted --script /dev/vda mklabel msdos", + "parted --script /dev/vda -- mkpart primary 1M 2048M", # PV1 + "parted --script /dev/vda -- set 1 lvm on", + "parted --script /dev/vda -- mkpart primary 2048M -1s", # PV2 + "parted --script /dev/vda -- set 2 lvm on", "udevadm settle", "pvcreate /dev/vda1 /dev/vda2", "vgcreate MyVolGroup /dev/vda1 /dev/vda2", @@ -402,10 +433,10 @@ in { luksroot = makeInstallerTest "luksroot" { createPartitions = '' $machine->succeed( - "parted /dev/vda mklabel msdos", - "parted /dev/vda -- mkpart primary ext2 1M 50MB", # /boot - "parted /dev/vda -- mkpart primary linux-swap 50M 1024M", - "parted /dev/vda -- mkpart primary 1024M -1s", # LUKS + "parted --script /dev/vda mklabel msdos", + "parted --script /dev/vda -- mkpart primary ext2 1M 50MB", # /boot + "parted --script /dev/vda -- mkpart primary linux-swap 50M 1024M", + "parted --script /dev/vda -- mkpart primary 1024M -1s", # LUKS "udevadm settle", "mkswap /dev/vda2 -L swap", "swapon -L swap", @@ -475,7 +506,7 @@ in { { createPartitions = '' $machine->succeed( - "parted /dev/vda --" + "parted --script /dev/vda --" . " mklabel msdos" . " mkpart primary ext2 1M 100MB" # /boot . " mkpart extended 100M -1s" @@ -510,9 +541,9 @@ in { { createPartitions = '' $machine->succeed( - "parted /dev/sda mklabel msdos", - "parted /dev/sda -- mkpart primary linux-swap 1M 1024M", - "parted /dev/sda -- mkpart primary ext2 1024M -1s", + "parted --script /dev/sda mklabel msdos", + "parted --script /dev/sda -- mkpart primary linux-swap 1M 1024M", + "parted --script /dev/sda -- mkpart primary ext2 1024M -1s", "udevadm settle", "mkswap /dev/sda1 -L swap", "swapon -L swap", diff --git a/nixos/tests/ipfs.nix b/nixos/tests/ipfs.nix index a93f8f175c55..c6bc61545245 100644 --- a/nixos/tests/ipfs.nix +++ b/nixos/tests/ipfs.nix @@ -23,8 +23,7 @@ import ./make-test.nix ({ pkgs, ...} : { services.ipfs = { enable = true; defaultMode = "norouting"; - # not yet. See #28621 - #autoMount = true; + autoMount = true; }; networking.firewall.allowedTCPPorts = [ 4001 ]; }; @@ -51,7 +50,6 @@ import ./make-test.nix ({ pkgs, ...} : { $getter->mustSucceed("ipfs --api /ip4/127.0.0.1/tcp/5001 swarm connect /ip4/$addrIp/tcp/4001/ipfs/$addrId"); $getter->mustSucceed("[ -n \"\$(ipfs --api /ip4/127.0.0.1/tcp/5001 cat /ipfs/$ipfsHash | grep fnord)\" ]"); - # not yet. See #28621 - # $getter->mustSucceed("[ -n \"$(cat /ipfs/$ipfsHash | grep fnord)\" ]"); + $getter->mustSucceed("[ -n \"$(cat /ipfs/$ipfsHash | grep fnord)\" ]"); ''; }) diff --git a/nixos/tests/kafka_0_10.nix b/nixos/tests/kafka_0_10.nix new file mode 100644 index 000000000000..6e7820f64bc4 --- /dev/null +++ b/nixos/tests/kafka_0_10.nix @@ -0,0 +1,48 @@ +import ./make-test.nix ({ pkgs, lib, ... } : +let + kafkaPackage = pkgs.apacheKafka_0_10; +in { + name = "kafka_0_10"; + meta = with pkgs.stdenv.lib.maintainers; { + maintainers = [ nequissimus ]; + }; + + nodes = { + zookeeper1 = { config, ... }: { + services.zookeeper = { + enable = true; + }; + + networking.firewall.allowedTCPPorts = [ 2181 ]; + }; + kafka = { config, ... }: { + services.apache-kafka = { + enable = true; + extraProperties = '' + offsets.topic.replication.factor = 1 + ''; + package = kafkaPackage; + zookeeper = "zookeeper1:2181"; + }; + + networking.firewall.allowedTCPPorts = [ 9092 ]; + virtualisation.memorySize = 2048; + }; + }; + + testScript = '' + startAll; + + $zookeeper1->waitForUnit("zookeeper"); + $zookeeper1->waitForUnit("network.target"); + $zookeeper1->waitForOpenPort(2181); + + $kafka->waitForUnit("apache-kafka"); + $kafka->waitForUnit("network.target"); + $kafka->waitForOpenPort(9092); + + $kafka->waitUntilSucceeds("${kafkaPackage}/bin/kafka-topics.sh --create --zookeeper zookeeper1:2181 --partitions 1 --replication-factor 1 --topic testtopic"); + $kafka->mustSucceed("echo 'test 1' | ${kafkaPackage}/bin/kafka-console-producer.sh --broker-list localhost:9092 --topic testtopic"); + $kafka->mustSucceed("${kafkaPackage}/bin/kafka-console-consumer.sh --bootstrap-server localhost:9092 --topic testtopic --from-beginning --max-messages 1 | grep 'test 1'"); + ''; +}) diff --git a/nixos/tests/kafka_0_11.nix b/nixos/tests/kafka_0_11.nix new file mode 100644 index 000000000000..39f9c36bb229 --- /dev/null +++ b/nixos/tests/kafka_0_11.nix @@ -0,0 +1,48 @@ +import ./make-test.nix ({ pkgs, lib, ... } : +let + kafkaPackage = pkgs.apacheKafka_0_11; +in { + name = "kafka_0_11"; + meta = with pkgs.stdenv.lib.maintainers; { + maintainers = [ nequissimus ]; + }; + + nodes = { + zookeeper1 = { config, ... }: { + services.zookeeper = { + enable = true; + }; + + networking.firewall.allowedTCPPorts = [ 2181 ]; + }; + kafka = { config, ... }: { + services.apache-kafka = { + enable = true; + extraProperties = '' + offsets.topic.replication.factor = 1 + ''; + package = kafkaPackage; + zookeeper = "zookeeper1:2181"; + }; + + networking.firewall.allowedTCPPorts = [ 9092 ]; + virtualisation.memorySize = 2048; + }; + }; + + testScript = '' + startAll; + + $zookeeper1->waitForUnit("zookeeper"); + $zookeeper1->waitForUnit("network.target"); + $zookeeper1->waitForOpenPort(2181); + + $kafka->waitForUnit("apache-kafka"); + $kafka->waitForUnit("network.target"); + $kafka->waitForOpenPort(9092); + + $kafka->waitUntilSucceeds("${kafkaPackage}/bin/kafka-topics.sh --create --zookeeper zookeeper1:2181 --partitions 1 --replication-factor 1 --topic testtopic"); + $kafka->mustSucceed("echo 'test 1' | ${kafkaPackage}/bin/kafka-console-producer.sh --broker-list localhost:9092 --topic testtopic"); + $kafka->mustSucceed("${kafkaPackage}/bin/kafka-console-consumer.sh --bootstrap-server localhost:9092 --topic testtopic --from-beginning --max-messages 1 | grep 'test 1'"); + ''; +}) diff --git a/nixos/tests/kafka_0_9.nix b/nixos/tests/kafka_0_9.nix new file mode 100644 index 000000000000..fee82aba2bda --- /dev/null +++ b/nixos/tests/kafka_0_9.nix @@ -0,0 +1,48 @@ +import ./make-test.nix ({ pkgs, lib, ... } : +let + kafkaPackage = pkgs.apacheKafka_0_9; +in { + name = "kafka_0_9"; + meta = with pkgs.stdenv.lib.maintainers; { + maintainers = [ nequissimus ]; + }; + + nodes = { + zookeeper1 = { config, ... }: { + services.zookeeper = { + enable = true; + }; + + networking.firewall.allowedTCPPorts = [ 2181 ]; + }; + kafka = { config, ... }: { + services.apache-kafka = { + enable = true; + extraProperties = '' + offsets.topic.replication.factor = 1 + ''; + package = kafkaPackage; + zookeeper = "zookeeper1:2181"; + }; + + networking.firewall.allowedTCPPorts = [ 9092 ]; + virtualisation.memorySize = 2048; + }; + }; + + testScript = '' + startAll; + + $zookeeper1->waitForUnit("zookeeper"); + $zookeeper1->waitForUnit("network.target"); + $zookeeper1->waitForOpenPort(2181); + + $kafka->waitForUnit("apache-kafka"); + $kafka->waitForUnit("network.target"); + $kafka->waitForOpenPort(9092); + + $kafka->waitUntilSucceeds("${kafkaPackage}/bin/kafka-topics.sh --create --zookeeper zookeeper1:2181 --partitions 1 --replication-factor 1 --topic testtopic"); + $kafka->mustSucceed("echo 'test 1' | ${kafkaPackage}/bin/kafka-console-producer.sh --broker-list localhost:9092 --topic testtopic"); + $kafka->mustSucceed("${kafkaPackage}/bin/kafka-console-consumer.sh --zookeeper zookeeper1:2181 --topic testtopic --from-beginning --max-messages 1 | grep 'test 1'"); + ''; +}) diff --git a/nixos/tests/kafka_1_0.nix b/nixos/tests/kafka_1_0.nix new file mode 100644 index 000000000000..936840dbcfdc --- /dev/null +++ b/nixos/tests/kafka_1_0.nix @@ -0,0 +1,48 @@ +import ./make-test.nix ({ pkgs, lib, ... } : +let + kafkaPackage = pkgs.apacheKafka_1_0; +in { + name = "kafka_1_0"; + meta = with pkgs.stdenv.lib.maintainers; { + maintainers = [ nequissimus ]; + }; + + nodes = { + zookeeper1 = { config, ... }: { + services.zookeeper = { + enable = true; + }; + + networking.firewall.allowedTCPPorts = [ 2181 ]; + }; + kafka = { config, ... }: { + services.apache-kafka = { + enable = true; + extraProperties = '' + offsets.topic.replication.factor = 1 + ''; + package = kafkaPackage; + zookeeper = "zookeeper1:2181"; + }; + + networking.firewall.allowedTCPPorts = [ 9092 ]; + virtualisation.memorySize = 2048; + }; + }; + + testScript = '' + startAll; + + $zookeeper1->waitForUnit("zookeeper"); + $zookeeper1->waitForUnit("network.target"); + $zookeeper1->waitForOpenPort(2181); + + $kafka->waitForUnit("apache-kafka"); + $kafka->waitForUnit("network.target"); + $kafka->waitForOpenPort(9092); + + $kafka->waitUntilSucceeds("${kafkaPackage}/bin/kafka-topics.sh --create --zookeeper zookeeper1:2181 --partitions 1 --replication-factor 1 --topic testtopic"); + $kafka->mustSucceed("echo 'test 1' | ${kafkaPackage}/bin/kafka-console-producer.sh --broker-list localhost:9092 --topic testtopic"); + $kafka->mustSucceed("${kafkaPackage}/bin/kafka-console-consumer.sh --bootstrap-server localhost:9092 --topic testtopic --from-beginning --max-messages 1 | grep 'test 1'"); + ''; +}) diff --git a/nixos/tests/kernel-params.nix b/nixos/tests/kernel-params.nix deleted file mode 100644 index 14a393356911..000000000000 --- a/nixos/tests/kernel-params.nix +++ /dev/null @@ -1,24 +0,0 @@ -import ./make-test.nix ({ pkgs, ...} : { - name = "kernel-params"; - meta = with pkgs.stdenv.lib.maintainers; { - maintainers = [ nequissimus ]; - }; - - machine = { config, lib, pkgs, ... }: - { - boot.kernelPackages = pkgs.linuxPackages; - boot.kernelParams = [ - "nohibernate" - "page_poison=1" - "vsyscall=none" - ]; - }; - - testScript = - '' - $machine->fail("cat /proc/cmdline | grep page_poison=0"); - $machine->succeed("cat /proc/cmdline | grep nohibernate"); - $machine->succeed("cat /proc/cmdline | grep page_poison=1"); - $machine->succeed("cat /proc/cmdline | grep vsyscall=none"); - ''; -}) diff --git a/nixos/tests/krb5/default.nix b/nixos/tests/krb5/default.nix new file mode 100644 index 000000000000..dd5b2f37202e --- /dev/null +++ b/nixos/tests/krb5/default.nix @@ -0,0 +1,5 @@ +{ system ? builtins.currentSystem }: +{ + example-config = import ./example-config.nix { inherit system; }; + deprecated-config = import ./deprecated-config.nix { inherit system; }; +} diff --git a/nixos/tests/krb5/deprecated-config.nix b/nixos/tests/krb5/deprecated-config.nix new file mode 100644 index 000000000000..980b3e762dc6 --- /dev/null +++ b/nixos/tests/krb5/deprecated-config.nix @@ -0,0 +1,48 @@ +# Verifies that the configuration suggested in deprecated example values +# will result in the expected output. + +import ../make-test.nix ({ pkgs, ...} : { + name = "krb5-with-deprecated-config"; + meta = with pkgs.stdenv.lib.maintainers; { + maintainers = [ eqyiel ]; + }; + + machine = + { config, pkgs, ... }: { + krb5 = { + enable = true; + defaultRealm = "ATHENA.MIT.EDU"; + domainRealm = "athena.mit.edu"; + kdc = "kerberos.mit.edu"; + kerberosAdminServer = "kerberos.mit.edu"; + }; + }; + + testScript = + let snapshot = pkgs.writeText "krb5-with-deprecated-config.conf" '' + [libdefaults] + default_realm = ATHENA.MIT.EDU + + [realms] + ATHENA.MIT.EDU = { + admin_server = kerberos.mit.edu + kdc = kerberos.mit.edu + } + + [domain_realm] + .athena.mit.edu = ATHENA.MIT.EDU + athena.mit.edu = ATHENA.MIT.EDU + + [capaths] + + + [appdefaults] + + + [plugins] + + ''; + in '' + $machine->succeed("diff /etc/krb5.conf ${snapshot}"); + ''; +}) diff --git a/nixos/tests/krb5/example-config.nix b/nixos/tests/krb5/example-config.nix new file mode 100644 index 000000000000..d5328720931e --- /dev/null +++ b/nixos/tests/krb5/example-config.nix @@ -0,0 +1,106 @@ +# Verifies that the configuration suggested in (non-deprecated) example values +# will result in the expected output. + +import ../make-test.nix ({ pkgs, ...} : { + name = "krb5-with-example-config"; + meta = with pkgs.stdenv.lib.maintainers; { + maintainers = [ eqyiel ]; + }; + + machine = + { config, pkgs, ... }: { + krb5 = { + enable = true; + kerberos = pkgs.krb5Full; + libdefaults = { + default_realm = "ATHENA.MIT.EDU"; + }; + realms = { + "ATHENA.MIT.EDU" = { + admin_server = "athena.mit.edu"; + kdc = "athena.mit.edu"; + }; + }; + domain_realm = { + "example.com" = "EXAMPLE.COM"; + ".example.com" = "EXAMPLE.COM"; + }; + capaths = { + "ATHENA.MIT.EDU" = { + "EXAMPLE.COM" = "."; + }; + "EXAMPLE.COM" = { + "ATHENA.MIT.EDU" = "."; + }; + }; + appdefaults = { + pam = { + debug = false; + ticket_lifetime = 36000; + renew_lifetime = 36000; + max_timeout = 30; + timeout_shift = 2; + initial_timeout = 1; + }; + }; + plugins = { + ccselect = { + disable = "k5identity"; + }; + }; + extraConfig = '' + [logging] + kdc = SYSLOG:NOTICE + admin_server = SYSLOG:NOTICE + default = SYSLOG:NOTICE + ''; + }; + }; + + testScript = + let snapshot = pkgs.writeText "krb5-with-example-config.conf" '' + [libdefaults] + default_realm = ATHENA.MIT.EDU + + [realms] + ATHENA.MIT.EDU = { + admin_server = athena.mit.edu + kdc = athena.mit.edu + } + + [domain_realm] + .example.com = EXAMPLE.COM + example.com = EXAMPLE.COM + + [capaths] + ATHENA.MIT.EDU = { + EXAMPLE.COM = . + } + EXAMPLE.COM = { + ATHENA.MIT.EDU = . + } + + [appdefaults] + pam = { + debug = false + initial_timeout = 1 + max_timeout = 30 + renew_lifetime = 36000 + ticket_lifetime = 36000 + timeout_shift = 2 + } + + [plugins] + ccselect = { + disable = k5identity + } + + [logging] + kdc = SYSLOG:NOTICE + admin_server = SYSLOG:NOTICE + default = SYSLOG:NOTICE + ''; + in '' + $machine->succeed("diff /etc/krb5.conf ${snapshot}"); + ''; +}) diff --git a/nixos/tests/kubernetes.nix b/nixos/tests/kubernetes.nix deleted file mode 100644 index dcd25e211971..000000000000 --- a/nixos/tests/kubernetes.nix +++ /dev/null @@ -1,409 +0,0 @@ -{ system ? builtins.currentSystem }: - -with import ../lib/testing.nix { inherit system; }; -with import ../lib/qemu-flags.nix; -with pkgs.lib; - -let - redisPod = pkgs.writeText "redis-master-pod.json" (builtins.toJSON { - kind = "Pod"; - apiVersion = "v1"; - metadata.name = "redis"; - metadata.labels.name = "redis"; - spec.containers = [{ - name = "redis"; - image = "redis"; - args = ["--bind" "0.0.0.0"]; - imagePullPolicy = "Never"; - ports = [{ - name = "redis-server"; - containerPort = 6379; - }]; - }]; - }); - - redisService = pkgs.writeText "redis-service.json" (builtins.toJSON { - kind = "Service"; - apiVersion = "v1"; - metadata.name = "redis"; - spec = { - ports = [{port = 6379; targetPort = 6379;}]; - selector = {name = "redis";}; - }; - }); - - redisImage = pkgs.dockerTools.buildImage { - name = "redis"; - tag = "latest"; - contents = pkgs.redis; - config.Entrypoint = "/bin/redis-server"; - }; - - testSimplePod = '' - $kubernetes->execute("docker load < ${redisImage}"); - $kubernetes->waitUntilSucceeds("kubectl create -f ${redisPod}"); - $kubernetes->succeed("kubectl create -f ${redisService}"); - $kubernetes->waitUntilSucceeds("kubectl get pod redis | grep Running"); - $kubernetes->succeed("nc -z \$\(dig \@10.10.0.1 redis.default.svc.cluster.local +short\) 6379"); - ''; -in { - # This test runs kubernetes on a single node - trivial = makeTest { - name = "kubernetes-trivial"; - - nodes = { - kubernetes = - { config, pkgs, lib, nodes, ... }: - { - virtualisation.memorySize = 768; - virtualisation.diskSize = 2048; - - programs.bash.enableCompletion = true; - environment.systemPackages = with pkgs; [ netcat bind ]; - - services.kubernetes.roles = ["master" "node"]; - virtualisation.docker.extraOptions = "--iptables=false --ip-masq=false -b cbr0"; - - networking.bridges.cbr0.interfaces = []; - networking.interfaces.cbr0 = {}; - }; - }; - - testScript = '' - startAll; - - $kubernetes->waitUntilSucceeds("kubectl get nodes | grep kubernetes | grep Ready"); - - ${testSimplePod} - ''; - }; - - cluster = let - runWithOpenSSL = file: cmd: pkgs.runCommand file { - buildInputs = [ pkgs.openssl ]; - } cmd; - - ca_key = runWithOpenSSL "ca-key.pem" "openssl genrsa -out $out 2048"; - ca_pem = runWithOpenSSL "ca.pem" '' - openssl req \ - -x509 -new -nodes -key ${ca_key} \ - -days 10000 -out $out -subj "/CN=etcd-ca" - ''; - etcd_key = runWithOpenSSL "etcd-key.pem" "openssl genrsa -out $out 2048"; - etcd_csr = runWithOpenSSL "etcd.csr" '' - openssl req \ - -new -key ${etcd_key} \ - -out $out -subj "/CN=etcd" \ - -config ${openssl_cnf} - ''; - etcd_cert = runWithOpenSSL "etcd.pem" '' - openssl x509 \ - -req -in ${etcd_csr} \ - -CA ${ca_pem} -CAkey ${ca_key} \ - -CAcreateserial -out $out \ - -days 365 -extensions v3_req \ - -extfile ${openssl_cnf} - ''; - - etcd_client_key = runWithOpenSSL "etcd-client-key.pem" - "openssl genrsa -out $out 2048"; - - etcd_client_csr = runWithOpenSSL "etcd-client-key.pem" '' - openssl req \ - -new -key ${etcd_client_key} \ - -out $out -subj "/CN=etcd-client" \ - -config ${client_openssl_cnf} - ''; - - etcd_client_cert = runWithOpenSSL "etcd-client.crt" '' - openssl x509 \ - -req -in ${etcd_client_csr} \ - -CA ${ca_pem} -CAkey ${ca_key} -CAcreateserial \ - -out $out -days 365 -extensions v3_req \ - -extfile ${client_openssl_cnf} - ''; - - apiserver_key = runWithOpenSSL "apiserver-key.pem" "openssl genrsa -out $out 2048"; - - apiserver_csr = runWithOpenSSL "apiserver.csr" '' - openssl req \ - -new -key ${apiserver_key} \ - -out $out -subj "/CN=kube-apiserver" \ - -config ${apiserver_cnf} - ''; - - apiserver_cert = runWithOpenSSL "apiserver.pem" '' - openssl x509 \ - -req -in ${apiserver_csr} \ - -CA ${ca_pem} -CAkey ${ca_key} -CAcreateserial \ - -out $out -days 365 -extensions v3_req \ - -extfile ${apiserver_cnf} - ''; - - worker_key = runWithOpenSSL "worker-key.pem" "openssl genrsa -out $out 2048"; - - worker_csr = runWithOpenSSL "worker.csr" '' - openssl req \ - -new -key ${worker_key} \ - -out $out -subj "/CN=kube-worker" \ - -config ${worker_cnf} - ''; - - worker_cert = runWithOpenSSL "worker.pem" '' - openssl x509 \ - -req -in ${worker_csr} \ - -CA ${ca_pem} -CAkey ${ca_key} -CAcreateserial \ - -out $out -days 365 -extensions v3_req \ - -extfile ${worker_cnf} - ''; - - openssl_cnf = pkgs.writeText "openssl.cnf" '' - [req] - req_extensions = v3_req - distinguished_name = req_distinguished_name - [req_distinguished_name] - [ v3_req ] - basicConstraints = CA:FALSE - keyUsage = digitalSignature, keyEncipherment - extendedKeyUsage = serverAuth - subjectAltName = @alt_names - [alt_names] - DNS.1 = etcd1 - DNS.2 = etcd2 - DNS.3 = etcd3 - IP.1 = 127.0.0.1 - ''; - - client_openssl_cnf = pkgs.writeText "client-openssl.cnf" '' - [req] - req_extensions = v3_req - distinguished_name = req_distinguished_name - [req_distinguished_name] - [ v3_req ] - basicConstraints = CA:FALSE - keyUsage = digitalSignature, keyEncipherment - extendedKeyUsage = clientAuth - ''; - - apiserver_cnf = pkgs.writeText "apiserver-openssl.cnf" '' - [req] - req_extensions = v3_req - distinguished_name = req_distinguished_name - [req_distinguished_name] - [ v3_req ] - basicConstraints = CA:FALSE - keyUsage = nonRepudiation, digitalSignature, keyEncipherment - subjectAltName = @alt_names - [alt_names] - DNS.1 = kubernetes - DNS.2 = kubernetes.default - DNS.3 = kubernetes.default.svc - DNS.4 = kubernetes.default.svc.cluster.local - IP.1 = 10.10.10.1 - ''; - - worker_cnf = pkgs.writeText "worker-openssl.cnf" '' - [req] - req_extensions = v3_req - distinguished_name = req_distinguished_name - [req_distinguished_name] - [ v3_req ] - basicConstraints = CA:FALSE - keyUsage = nonRepudiation, digitalSignature, keyEncipherment - subjectAltName = @alt_names - [alt_names] - DNS.1 = kubeWorker1 - DNS.2 = kubeWorker2 - ''; - - etcdNodeConfig = { - virtualisation.memorySize = 128; - - services = { - etcd = { - enable = true; - keyFile = etcd_key; - certFile = etcd_cert; - trustedCaFile = ca_pem; - peerClientCertAuth = true; - listenClientUrls = ["https://0.0.0.0:2379"]; - listenPeerUrls = ["https://0.0.0.0:2380"]; - }; - }; - - environment.variables = { - ETCDCTL_CERT_FILE = "${etcd_client_cert}"; - ETCDCTL_KEY_FILE = "${etcd_client_key}"; - ETCDCTL_CA_FILE = "${ca_pem}"; - ETCDCTL_PEERS = "https://127.0.0.1:2379"; - }; - - networking.firewall.allowedTCPPorts = [ 2379 2380 ]; - }; - - kubeConfig = { - virtualisation.diskSize = 2048; - programs.bash.enableCompletion = true; - - services.flannel = { - enable = true; - network = "10.10.0.0/16"; - iface = "eth1"; - etcd = { - endpoints = ["https://etcd1:2379" "https://etcd2:2379" "https://etcd3:2379"]; - keyFile = etcd_client_key; - certFile = etcd_client_cert; - caFile = ca_pem; - }; - }; - - # vxlan - networking.firewall.allowedUDPPorts = [ 8472 ]; - - systemd.services.docker.after = ["flannel.service"]; - systemd.services.docker.serviceConfig.EnvironmentFile = "/run/flannel/subnet.env"; - virtualisation.docker.extraOptions = "--iptables=false --ip-masq=false --bip $FLANNEL_SUBNET"; - - services.kubernetes.verbose = true; - services.kubernetes.etcd = { - servers = ["https://etcd1:2379" "https://etcd2:2379" "https://etcd3:2379"]; - keyFile = etcd_client_key; - certFile = etcd_client_cert; - caFile = ca_pem; - }; - - environment.systemPackages = [ pkgs.bind pkgs.tcpdump pkgs.utillinux ]; - }; - - kubeMasterConfig = {pkgs, ...}: { - require = [kubeConfig]; - - # kube apiserver - networking.firewall.allowedTCPPorts = [ 443 ]; - - virtualisation.memorySize = 512; - - services.kubernetes = { - roles = ["master"]; - scheduler.leaderElect = true; - controllerManager.leaderElect = true; - - apiserver = { - publicAddress = "0.0.0.0"; - advertiseAddress = "192.168.1.8"; - tlsKeyFile = apiserver_key; - tlsCertFile = apiserver_cert; - clientCaFile = ca_pem; - kubeletClientCaFile = ca_pem; - kubeletClientKeyFile = worker_key; - kubeletClientCertFile = worker_cert; - }; - }; - }; - - kubeWorkerConfig = { pkgs, ... }: { - require = [kubeConfig]; - - virtualisation.memorySize = 512; - - # kubelet - networking.firewall.allowedTCPPorts = [ 10250 ]; - - services.kubernetes = { - roles = ["node"]; - kubeconfig = { - server = "https://kubernetes:443"; - caFile = ca_pem; - certFile = worker_cert; - keyFile = worker_key; - }; - kubelet = { - tlsKeyFile = worker_key; - tlsCertFile = worker_cert; - }; - }; - }; - in makeTest { - name = "kubernetes-cluster"; - - nodes = { - etcd1 = { config, pkgs, nodes, ... }: { - require = [etcdNodeConfig]; - services.etcd = { - advertiseClientUrls = ["https://etcd1:2379"]; - initialCluster = ["etcd1=https://etcd1:2380" "etcd2=https://etcd2:2380" "etcd3=https://etcd3:2380"]; - initialAdvertisePeerUrls = ["https://etcd1:2380"]; - }; - }; - - etcd2 = { config, pkgs, ... }: { - require = [etcdNodeConfig]; - services.etcd = { - advertiseClientUrls = ["https://etcd2:2379"]; - initialCluster = ["etcd1=https://etcd1:2380" "etcd2=https://etcd2:2380" "etcd3=https://etcd3:2380"]; - initialAdvertisePeerUrls = ["https://etcd2:2380"]; - }; - }; - - etcd3 = { config, pkgs, ... }: { - require = [etcdNodeConfig]; - services.etcd = { - advertiseClientUrls = ["https://etcd3:2379"]; - initialCluster = ["etcd1=https://etcd1:2380" "etcd2=https://etcd2:2380" "etcd3=https://etcd3:2380"]; - initialAdvertisePeerUrls = ["https://etcd3:2380"]; - }; - }; - - kubeMaster1 = { config, pkgs, lib, nodes, ... }: { - require = [kubeMasterConfig]; - }; - - kubeMaster2 = { config, pkgs, lib, nodes, ... }: { - require = [kubeMasterConfig]; - }; - - # Kubernetes TCP load balancer - kubernetes = { config, pkgs, ... }: { - # kubernetes - networking.firewall.allowedTCPPorts = [ 443 ]; - - services.haproxy.enable = true; - services.haproxy.config = '' - global - log 127.0.0.1 local0 notice - user haproxy - group haproxy - - defaults - log global - retries 2 - timeout connect 3000 - timeout server 5000 - timeout client 5000 - - listen kubernetes - bind 0.0.0.0:443 - mode tcp - option ssl-hello-chk - balance roundrobin - server kube-master-1 kubeMaster1:443 check - server kube-master-2 kubeMaster2:443 check - ''; - }; - - kubeWorker1 = { config, pkgs, lib, nodes, ... }: { - require = [kubeWorkerConfig]; - }; - - kubeWorker2 = { config, pkgs, lib, nodes, ... }: { - require = [kubeWorkerConfig]; - }; - }; - - testScript = '' - startAll; - - ${testSimplePod} - ''; - }; -} diff --git a/nixos/tests/kubernetes/base.nix b/nixos/tests/kubernetes/base.nix new file mode 100644 index 000000000000..acf2e0250819 --- /dev/null +++ b/nixos/tests/kubernetes/base.nix @@ -0,0 +1,113 @@ +{ system ? builtins.currentSystem }: + +with import ../../lib/testing.nix { inherit system; }; +with import ../../lib/qemu-flags.nix; +with pkgs.lib; + +let + mkKubernetesBaseTest = + { name, domain ? "my.zyx", test, machines + , pkgs ? import <nixpkgs> { inherit system; } + , certs ? import ./certs.nix { inherit pkgs; externalDomain = domain; } + , extraConfiguration ? null }: + let + masterName = head (filter (machineName: any (role: role == "master") machines.${machineName}.roles) (attrNames machines)); + master = machines.${masterName}; + extraHosts = '' + ${master.ip} etcd.${domain} + ${master.ip} api.${domain} + ${concatMapStringsSep "\n" (machineName: "${machines.${machineName}.ip} ${machineName}.${domain}") (attrNames machines)} + ''; + in makeTest { + inherit name; + + nodes = mapAttrs (machineName: machine: + { config, pkgs, lib, nodes, ... }: + mkMerge [ + { + virtualisation.memorySize = mkDefault 768; + virtualisation.diskSize = mkDefault 4096; + networking = { + inherit domain extraHosts; + primaryIPAddress = mkForce machine.ip; + + firewall = { + allowedTCPPorts = [ + 10250 # kubelet + ]; + trustedInterfaces = ["docker0"]; + + extraCommands = concatMapStrings (node: '' + iptables -A INPUT -s ${node.config.networking.primaryIPAddress} -j ACCEPT + '') (attrValues nodes); + }; + }; + programs.bash.enableCompletion = true; + environment.variables = { + ETCDCTL_CERT_FILE = "${certs.worker}/etcd-client.pem"; + ETCDCTL_KEY_FILE = "${certs.worker}/etcd-client-key.pem"; + ETCDCTL_CA_FILE = "${certs.worker}/ca.pem"; + ETCDCTL_PEERS = "https://etcd.${domain}:2379"; + }; + services.flannel.iface = "eth1"; + services.kubernetes.apiserver.advertiseAddress = master.ip; + } + (optionalAttrs (any (role: role == "master") machine.roles) { + networking.firewall.allowedTCPPorts = [ + 2379 2380 # etcd + 443 # kubernetes apiserver + ]; + services.etcd = { + enable = true; + certFile = "${certs.master}/etcd.pem"; + keyFile = "${certs.master}/etcd-key.pem"; + trustedCaFile = "${certs.master}/ca.pem"; + peerClientCertAuth = true; + listenClientUrls = ["https://0.0.0.0:2379"]; + listenPeerUrls = ["https://0.0.0.0:2380"]; + advertiseClientUrls = ["https://etcd.${config.networking.domain}:2379"]; + initialCluster = ["${masterName}=https://etcd.${config.networking.domain}:2380"]; + initialAdvertisePeerUrls = ["https://etcd.${config.networking.domain}:2380"]; + }; + }) + (import ./kubernetes-common.nix { inherit (machine) roles; inherit pkgs config certs; }) + (optionalAttrs (machine ? "extraConfiguration") (machine.extraConfiguration { inherit config pkgs lib nodes; })) + (optionalAttrs (extraConfiguration != null) (extraConfiguration { inherit config pkgs lib nodes; })) + ] + ) machines; + + testScript = '' + startAll; + + ${test} + ''; + }; + + mkKubernetesMultiNodeTest = attrs: mkKubernetesBaseTest ({ + machines = { + machine1 = { + roles = ["master"]; + ip = "192.168.1.1"; + }; + machine2 = { + roles = ["node"]; + ip = "192.168.1.2"; + }; + }; + } // attrs // { + name = "kubernetes-${attrs.name}-multinode"; + }); + + mkKubernetesSingleNodeTest = attrs: mkKubernetesBaseTest ({ + machines = { + machine1 = { + roles = ["master" "node"]; + ip = "192.168.1.1"; + }; + }; + } // attrs // { + name = "kubernetes-${attrs.name}-singlenode"; + }); +in { + inherit mkKubernetesBaseTest mkKubernetesSingleNodeTest mkKubernetesMultiNodeTest; +} diff --git a/nixos/tests/kubernetes/certs.nix b/nixos/tests/kubernetes/certs.nix new file mode 100644 index 000000000000..f108e35b98cd --- /dev/null +++ b/nixos/tests/kubernetes/certs.nix @@ -0,0 +1,185 @@ +{ + pkgs ? import <nixpkgs> {}, + internalDomain ? "cloud.yourdomain.net", + externalDomain ? "myawesomecluster.cluster.yourdomain.net", + serviceClusterIp ? "10.0.0.1" +}: +let + runWithCFSSL = name: cmd: + builtins.fromJSON (builtins.readFile ( + pkgs.runCommand "${name}-cfss.json" { + buildInputs = [ pkgs.cfssl ]; + } "cfssl ${cmd} > $out" + )); + + writeCFSSL = content: + pkgs.runCommand content.name { + buildInputs = [ pkgs.cfssl ]; + } '' + mkdir -p $out + cd $out + cat ${writeFile content} | cfssljson -bare ${content.name} + ''; + + noCSR = content: pkgs.lib.filterAttrs (n: v: n != "csr") content; + noKey = content: pkgs.lib.filterAttrs (n: v: n != "key") content; + + writeFile = content: pkgs.writeText "content" ( + if pkgs.lib.isAttrs content then builtins.toJSON content + else toString content + ); + + createServingCertKey = { ca, cn, hosts? [], size ? 2048, name ? cn }: + noCSR ( + (runWithCFSSL name "gencert -ca=${writeFile ca.cert} -ca-key=${writeFile ca.key} -profile=server -config=${writeFile ca.config} ${writeFile { + CN = cn; + hosts = hosts; + key = { algo = "rsa"; inherit size; }; + }}") // { inherit name; } + ); + + createClientCertKey = { ca, cn, groups ? [], size ? 2048, name ? cn }: + noCSR ( + (runWithCFSSL name "gencert -ca=${writeFile ca.cert} -ca-key=${writeFile ca.key} -profile=client -config=${writeFile ca.config} ${writeFile { + CN = cn; + names = map (group: {O = group;}) groups; + hosts = [""]; + key = { algo = "rsa"; inherit size; }; + }}") // { inherit name; } + ); + + createSigningCertKey = { C ? "xx", ST ? "x", L ? "x", O ? "x", OU ? "x", CN ? "ca", emailAddress ? "x", expiry ? "43800h", size ? 2048, name ? CN }: + (noCSR (runWithCFSSL CN "genkey -initca ${writeFile { + key = { algo = "rsa"; inherit size; }; + names = [{ inherit C ST L O OU CN emailAddress; }]; + }}")) // { + inherit name; + config.signing = { + default.expiry = expiry; + profiles = { + server = { + inherit expiry; + usages = [ + "signing" + "key encipherment" + "server auth" + ]; + }; + client = { + inherit expiry; + usages = [ + "signing" + "key encipherment" + "client auth" + ]; + }; + peer = { + inherit expiry; + usages = [ + "signing" + "key encipherment" + "server auth" + "client auth" + ]; + }; + }; + }; + }; + + ca = createSigningCertKey {}; + + kube-apiserver = createServingCertKey { + inherit ca; + cn = "kube-apiserver"; + hosts = ["kubernetes.default" "kubernetes.default.svc" "localhost" "api.${externalDomain}" serviceClusterIp]; + }; + + kubelet = createServingCertKey { + inherit ca; + cn = "kubelet"; + hosts = ["*.${externalDomain}"]; + }; + + service-accounts = createServingCertKey { + inherit ca; + cn = "kube-service-accounts"; + }; + + etcd = createServingCertKey { + inherit ca; + cn = "etcd"; + hosts = ["etcd.${externalDomain}"]; + }; + + etcd-client = createClientCertKey { + inherit ca; + cn = "etcd-client"; + }; + + kubelet-client = createClientCertKey { + inherit ca; + cn = "kubelet-client"; + groups = ["system:masters"]; + }; + + apiserver-client = { + kubelet = createClientCertKey { + inherit ca; + cn = "apiserver-client-kubelet"; + groups = ["system:nodes"]; + }; + + kube-proxy = createClientCertKey { + inherit ca; + name = "apiserver-client-kube-proxy"; + cn = "system:kube-proxy"; + groups = ["system:kube-proxy" "system:nodes"]; + }; + + kube-controller-manager = createClientCertKey { + inherit ca; + name = "apiserver-client-kube-controller-manager"; + cn = "system:kube-controller-manager"; + groups = ["system:masters"]; + }; + + kube-scheduler = createClientCertKey { + inherit ca; + name = "apiserver-client-kube-scheduler"; + cn = "system:kube-scheduler"; + groups = ["system:kube-scheduler"]; + }; + + admin = createClientCertKey { + inherit ca; + cn = "admin"; + groups = ["system:masters"]; + }; + }; +in { + master = pkgs.buildEnv { + name = "master-keys"; + paths = [ + (writeCFSSL (noKey ca)) + (writeCFSSL kube-apiserver) + (writeCFSSL kubelet-client) + (writeCFSSL apiserver-client.kube-controller-manager) + (writeCFSSL apiserver-client.kube-scheduler) + (writeCFSSL service-accounts) + (writeCFSSL etcd) + ]; + }; + + worker = pkgs.buildEnv { + name = "worker-keys"; + paths = [ + (writeCFSSL (noKey ca)) + (writeCFSSL kubelet) + (writeCFSSL apiserver-client.kubelet) + (writeCFSSL apiserver-client.kube-proxy) + (writeCFSSL etcd-client) + ]; + }; + + admin = writeCFSSL apiserver-client.admin; +} diff --git a/nixos/tests/kubernetes/default.nix b/nixos/tests/kubernetes/default.nix new file mode 100644 index 000000000000..a801759bf582 --- /dev/null +++ b/nixos/tests/kubernetes/default.nix @@ -0,0 +1,7 @@ +{ system ? builtins.currentSystem }: +{ + dns = import ./dns.nix { inherit system; }; + # e2e = import ./e2e.nix { inherit system; }; # TODO: make it pass + # the following test(s) can be removed when e2e is working: + rbac = import ./rbac.nix { inherit system; }; +} diff --git a/nixos/tests/kubernetes/dns.nix b/nixos/tests/kubernetes/dns.nix new file mode 100644 index 000000000000..74d98dabec8d --- /dev/null +++ b/nixos/tests/kubernetes/dns.nix @@ -0,0 +1,127 @@ +{ system ? builtins.currentSystem, pkgs ? import <nixpkgs> { inherit system; } }: +with import ./base.nix { inherit system; }; +let + domain = "my.zyx"; + + certs = import ./certs.nix { externalDomain = domain; }; + + redisPod = pkgs.writeText "redis-pod.json" (builtins.toJSON { + kind = "Pod"; + apiVersion = "v1"; + metadata.name = "redis"; + metadata.labels.name = "redis"; + spec.containers = [{ + name = "redis"; + image = "redis"; + args = ["--bind" "0.0.0.0"]; + imagePullPolicy = "Never"; + ports = [{ + name = "redis-server"; + containerPort = 6379; + }]; + }]; + }); + + redisService = pkgs.writeText "redis-service.json" (builtins.toJSON { + kind = "Service"; + apiVersion = "v1"; + metadata.name = "redis"; + spec = { + ports = [{port = 6379; targetPort = 6379;}]; + selector = {name = "redis";}; + }; + }); + + redisImage = pkgs.dockerTools.buildImage { + name = "redis"; + tag = "latest"; + contents = [ pkgs.redis pkgs.bind.host ]; + config.Entrypoint = "/bin/redis-server"; + }; + + probePod = pkgs.writeText "probe-pod.json" (builtins.toJSON { + kind = "Pod"; + apiVersion = "v1"; + metadata.name = "probe"; + metadata.labels.name = "probe"; + spec.containers = [{ + name = "probe"; + image = "probe"; + args = [ "-f" ]; + tty = true; + imagePullPolicy = "Never"; + }]; + }); + + probeImage = pkgs.dockerTools.buildImage { + name = "probe"; + tag = "latest"; + contents = [ pkgs.bind.host pkgs.busybox ]; + config.Entrypoint = "/bin/tail"; + }; + + extraConfiguration = { config, pkgs, lib, nodes, ... }: { + environment.systemPackages = [ pkgs.bind.host ]; + # virtualisation.docker.extraOptions = "--dns=${config.services.kubernetes.addons.dns.clusterIp}"; + services.dnsmasq.enable = true; + services.dnsmasq.servers = [ + "/cluster.local/${config.services.kubernetes.addons.dns.clusterIp}#53" + ]; + }; + + base = { + name = "dns"; + inherit domain certs extraConfiguration; + }; + + singleNodeTest = { + test = '' + # prepare machine1 for test + $machine1->waitUntilSucceeds("kubectl get node machine1.${domain} | grep -w Ready"); + $machine1->execute("docker load < ${redisImage}"); + $machine1->waitUntilSucceeds("kubectl create -f ${redisPod}"); + $machine1->waitUntilSucceeds("kubectl create -f ${redisService}"); + $machine1->execute("docker load < ${probeImage}"); + $machine1->waitUntilSucceeds("kubectl create -f ${probePod}"); + + # check if pods are running + $machine1->waitUntilSucceeds("kubectl get pod redis | grep Running"); + $machine1->waitUntilSucceeds("kubectl get pod probe | grep Running"); + $machine1->waitUntilSucceeds("kubectl get pods -n kube-system | grep 'kube-dns.*3/3'"); + + # check dns on host (dnsmasq) + $machine1->succeed("host redis.default.svc.cluster.local"); + + # check dns inside the container + $machine1->succeed("kubectl exec -ti probe -- /bin/host redis.default.svc.cluster.local"); + ''; + }; + + multiNodeTest = { + test = '' + # prepare machines for test + $machine1->waitUntilSucceeds("kubectl get node machine1.${domain} | grep -w Ready"); + $machine1->waitUntilSucceeds("kubectl get node machine2.${domain} | grep -w Ready"); + $machine2->execute("docker load < ${redisImage}"); + $machine1->waitUntilSucceeds("kubectl create -f ${redisPod}"); + $machine1->waitUntilSucceeds("kubectl create -f ${redisService}"); + $machine2->execute("docker load < ${probeImage}"); + $machine1->waitUntilSucceeds("kubectl create -f ${probePod}"); + + # check if pods are running + $machine1->waitUntilSucceeds("kubectl get pod redis | grep Running"); + $machine1->waitUntilSucceeds("kubectl get pod probe | grep Running"); + $machine1->waitUntilSucceeds("kubectl get pods -n kube-system | grep 'kube-dns.*3/3'"); + + # check dns on hosts (dnsmasq) + $machine1->succeed("host redis.default.svc.cluster.local"); + $machine2->succeed("host redis.default.svc.cluster.local"); + + # check dns inside the container + $machine1->succeed("kubectl exec -ti probe -- /bin/host redis.default.svc.cluster.local"); + ''; + }; +in { + singlenode = mkKubernetesSingleNodeTest (base // singleNodeTest); + multinode = mkKubernetesMultiNodeTest (base // multiNodeTest); +} diff --git a/nixos/tests/kubernetes/e2e.nix b/nixos/tests/kubernetes/e2e.nix new file mode 100644 index 000000000000..d9d7ba9bb2cc --- /dev/null +++ b/nixos/tests/kubernetes/e2e.nix @@ -0,0 +1,40 @@ +{ system ? builtins.currentSystem, pkgs ? import <nixpkgs> { inherit system; } }: +with import ./base.nix { inherit system; }; +let + domain = "my.zyx"; + certs = import ./certs.nix { externalDomain = domain; }; + kubeconfig = pkgs.writeText "kubeconfig.json" (builtins.toJSON { + apiVersion = "v1"; + kind = "Config"; + clusters = [{ + name = "local"; + cluster.certificate-authority = "${certs.master}/ca.pem"; + cluster.server = "https://api.${domain}"; + }]; + users = [{ + name = "kubelet"; + user = { + client-certificate = "${certs.admin}/admin.pem"; + client-key = "${certs.admin}/admin-key.pem"; + }; + }]; + contexts = [{ + context = { + cluster = "local"; + user = "kubelet"; + }; + current-context = "kubelet-context"; + }]; + }); + + base = { + name = "e2e"; + inherit domain certs; + test = '' + $machine1->succeed("e2e.test -kubeconfig ${kubeconfig} -provider local -ginkgo.focus '\\[Conformance\\]' -ginkgo.skip '\\[Flaky\\]|\\[Serial\\]'"); + ''; + }; +in { + singlenode = mkKubernetesSingleNodeTest base; + multinode = mkKubernetesMultiNodeTest base; +} diff --git a/nixos/tests/kubernetes/kubernetes-common.nix b/nixos/tests/kubernetes/kubernetes-common.nix new file mode 100644 index 000000000000..00a5c9aba4e3 --- /dev/null +++ b/nixos/tests/kubernetes/kubernetes-common.nix @@ -0,0 +1,59 @@ +{ roles, config, pkgs, certs }: +with pkgs.lib; +let + base = { + inherit roles; + featureGates = ["AllAlpha"]; + flannel.enable = true; + addons.dashboard.enable = true; + verbose = true; + + caFile = "${certs.master}/ca.pem"; + apiserver = { + tlsCertFile = "${certs.master}/kube-apiserver.pem"; + tlsKeyFile = "${certs.master}/kube-apiserver-key.pem"; + kubeletClientCertFile = "${certs.master}/kubelet-client.pem"; + kubeletClientKeyFile = "${certs.master}/kubelet-client-key.pem"; + serviceAccountKeyFile = "${certs.master}/kube-service-accounts.pem"; + }; + etcd = { + servers = ["https://etcd.${config.networking.domain}:2379"]; + certFile = "${certs.worker}/etcd-client.pem"; + keyFile = "${certs.worker}/etcd-client-key.pem"; + }; + kubeconfig = { + server = "https://api.${config.networking.domain}"; + }; + kubelet = { + tlsCertFile = "${certs.worker}/kubelet.pem"; + tlsKeyFile = "${certs.worker}/kubelet-key.pem"; + hostname = "${config.networking.hostName}.${config.networking.domain}"; + kubeconfig = { + certFile = "${certs.worker}/apiserver-client-kubelet.pem"; + keyFile = "${certs.worker}/apiserver-client-kubelet-key.pem"; + }; + }; + controllerManager = { + serviceAccountKeyFile = "${certs.master}/kube-service-accounts-key.pem"; + kubeconfig = { + certFile = "${certs.master}/apiserver-client-kube-controller-manager.pem"; + keyFile = "${certs.master}/apiserver-client-kube-controller-manager-key.pem"; + }; + }; + scheduler = { + kubeconfig = { + certFile = "${certs.master}/apiserver-client-kube-scheduler.pem"; + keyFile = "${certs.master}/apiserver-client-kube-scheduler-key.pem"; + }; + }; + proxy = { + kubeconfig = { + certFile = "${certs.worker}/apiserver-client-kube-proxy.pem"; + keyFile = "${certs.worker}//apiserver-client-kube-proxy-key.pem"; + }; + }; + }; + +in { + services.kubernetes = base; +} diff --git a/nixos/tests/kubernetes/rbac.nix b/nixos/tests/kubernetes/rbac.nix new file mode 100644 index 000000000000..1966fed3a5fb --- /dev/null +++ b/nixos/tests/kubernetes/rbac.nix @@ -0,0 +1,137 @@ +{ system ? builtins.currentSystem, pkgs ? import <nixpkgs> { inherit system; } }: +with import ./base.nix { inherit system; }; +let + + roServiceAccount = pkgs.writeText "ro-service-account.json" (builtins.toJSON { + kind = "ServiceAccount"; + apiVersion = "v1"; + metadata = { + name = "read-only"; + namespace = "default"; + }; + }); + + roRoleBinding = pkgs.writeText "ro-role-binding.json" (builtins.toJSON { + apiVersion = "rbac.authorization.k8s.io/v1beta1"; + kind = "RoleBinding"; + metadata = { + name = "read-pods"; + namespace = "default"; + }; + roleRef = { + apiGroup = "rbac.authorization.k8s.io"; + kind = "Role"; + name = "pod-reader"; + }; + subjects = [{ + kind = "ServiceAccount"; + name = "read-only"; + namespace = "default"; + }]; + }); + + roRole = pkgs.writeText "ro-role.json" (builtins.toJSON { + apiVersion = "rbac.authorization.k8s.io/v1beta1"; + kind = "Role"; + metadata = { + name = "pod-reader"; + namespace = "default"; + }; + rules = [{ + apiGroups = [""]; + resources = ["pods"]; + verbs = ["get" "list" "watch"]; + }]; + }); + + kubectlPod = pkgs.writeText "kubectl-pod.json" (builtins.toJSON { + kind = "Pod"; + apiVersion = "v1"; + metadata.name = "kubectl"; + metadata.namespace = "default"; + metadata.labels.name = "kubectl"; + spec.serviceAccountName = "read-only"; + spec.containers = [{ + name = "kubectl"; + image = "kubectl:latest"; + command = ["/bin/tail" "-f"]; + imagePullPolicy = "Never"; + tty = true; + }]; + }); + + kubectlPod2 = pkgs.writeTextDir "kubectl-pod-2.json" (builtins.toJSON { + kind = "Pod"; + apiVersion = "v1"; + metadata.name = "kubectl-2"; + metadata.namespace = "default"; + metadata.labels.name = "kubectl-2"; + spec.serviceAccountName = "read-only"; + spec.containers = [{ + name = "kubectl-2"; + image = "kubectl:latest"; + command = ["/bin/tail" "-f"]; + imagePullPolicy = "Never"; + tty = true; + }]; + }); + + kubectl = pkgs.runCommand "copy-kubectl" { buildInputs = [ pkgs.kubernetes ]; } '' + mkdir -p $out/bin + cp ${pkgs.kubernetes}/bin/kubectl $out/bin/kubectl + ''; + + kubectlImage = pkgs.dockerTools.buildImage { + name = "kubectl"; + tag = "latest"; + contents = [ kubectl pkgs.busybox kubectlPod2 ]; + config.Entrypoint = "/bin/sh"; + }; + + base = { + name = "rbac"; + }; + + singlenode = base // { + test = '' + $machine1->waitUntilSucceeds("kubectl get node machine1.my.zyx | grep -w Ready"); + + $machine1->execute("docker load < ${kubectlImage}"); + + $machine1->waitUntilSucceeds("kubectl apply -f ${roServiceAccount}"); + $machine1->waitUntilSucceeds("kubectl apply -f ${roRole}"); + $machine1->waitUntilSucceeds("kubectl apply -f ${roRoleBinding}"); + $machine1->waitUntilSucceeds("kubectl create -f ${kubectlPod}"); + + $machine1->waitUntilSucceeds("kubectl get pod kubectl | grep Running"); + + $machine1->succeed("kubectl exec -ti kubectl -- kubectl get pods"); + $machine1->fail("kubectl exec -ti kubectl -- kubectl create -f /kubectl-pod-2.json"); + $machine1->fail("kubectl exec -ti kubectl -- kubectl delete pods -l name=kubectl"); + ''; + }; + + multinode = base // { + test = '' + $machine1->waitUntilSucceeds("kubectl get node machine1.my.zyx | grep -w Ready"); + $machine1->waitUntilSucceeds("kubectl get node machine2.my.zyx | grep -w Ready"); + + $machine2->execute("docker load < ${kubectlImage}"); + + $machine1->waitUntilSucceeds("kubectl apply -f ${roServiceAccount}"); + $machine1->waitUntilSucceeds("kubectl apply -f ${roRole}"); + $machine1->waitUntilSucceeds("kubectl apply -f ${roRoleBinding}"); + $machine1->waitUntilSucceeds("kubectl create -f ${kubectlPod}"); + + $machine1->waitUntilSucceeds("kubectl get pod kubectl | grep Running"); + + $machine1->succeed("kubectl exec -ti kubectl -- kubectl get pods"); + $machine1->fail("kubectl exec -ti kubectl -- kubectl create -f /kubectl-pod-2.json"); + $machine1->fail("kubectl exec -ti kubectl -- kubectl delete pods -l name=kubectl"); + ''; + }; + +in { + singlenode = mkKubernetesSingleNodeTest singlenode; + multinode = mkKubernetesMultiNodeTest multinode; +} diff --git a/nixos/tests/mesos.nix b/nixos/tests/mesos.nix index 6e9af126f032..34671df047c8 100644 --- a/nixos/tests/mesos.nix +++ b/nixos/tests/mesos.nix @@ -56,9 +56,7 @@ import ./make-test.nix ({ pkgs, ...} : rec { src = ./mesos_test.py; phases = [ "installPhase" "fixupPhase" ]; installPhase = '' - mkdir $out - cp $src $out/mesos_test.py - chmod +x $out/mesos_test.py + install -Dvm 0755 $src $out/bin/mesos_test.py echo "done" > test.result tar czf $out/test.tar.gz test.result @@ -74,18 +72,18 @@ import ./make-test.nix ({ pkgs, ...} : rec { $master->waitForOpenPort(5050); $slave->waitForOpenPort(5051); - # is slave registred? + # is slave registered? $master->waitUntilSucceeds("curl -s --fail http://master:5050/master/slaves". " | grep -q \"\\\"hostname\\\":\\\"slave\\\"\""); - # try to run docker image + # try to run docker image $master->succeed("${pkgs.mesos}/bin/mesos-execute --master=master:5050". " --resources=\"cpus:0.1;mem:32\" --name=simple-docker". " --containerizer=mesos --docker_image=echo:latest". " --shell=true --command=\"echo done\" | grep -q TASK_FINISHED"); # simple command with .tar.gz uri - $master->succeed("${testFramework}/mesos_test.py master ". + $master->succeed("${testFramework}/bin/mesos_test.py master ". "${testFramework}/test.tar.gz"); ''; }) diff --git a/nixos/tests/minio.nix b/nixos/tests/minio.nix index a349265b2f57..07a292a9baa5 100644 --- a/nixos/tests/minio.nix +++ b/nixos/tests/minio.nix @@ -12,6 +12,9 @@ import ./make-test.nix ({ pkgs, ...} : { secretKey = "V7f1CwQqAcwo80UEIJEjc5gVQUSSx5ohQ9GSrr12"; }; environment.systemPackages = [ pkgs.minio-client ]; + + # Minio requires at least 1GiB of free disk space to run. + virtualisation.diskSize = 4 * 1024; }; }; @@ -20,7 +23,6 @@ import ./make-test.nix ({ pkgs, ...} : { startAll; $machine->waitForUnit("minio.service"); $machine->waitForOpenPort(9000); - $machine->succeed("curl --fail http://localhost:9000/minio/index.html"); # Create a test bucket on the server $machine->succeed("mc config host add minio http://localhost:9000 BKIKJAA5BMMU2RHO6IBB V7f1CwQqAcwo80UEIJEjc5gVQUSSx5ohQ9GSrr12 S3v4"); diff --git a/nixos/tests/misc.nix b/nixos/tests/misc.nix index 1b24551009c9..79290861cb0b 100644 --- a/nixos/tests/misc.nix +++ b/nixos/tests/misc.nix @@ -25,6 +25,8 @@ import ./make-test.nix ({ pkgs, ...} : { }; users.users.sybil = { isNormalUser = true; group = "wheel"; }; security.sudo = { enable = true; wheelNeedsPassword = false; }; + boot.kernel.sysctl."vm.swappiness" = 1; + boot.kernelParams = [ "vsyscall=emulate" ]; }; testScript = @@ -117,5 +119,18 @@ import ./make-test.nix ({ pkgs, ...} : { subtest "sudo", sub { $machine->succeed("su - sybil -c 'sudo true'"); }; + + # Test sysctl + subtest "sysctl", sub { + $machine->waitForUnit("systemd-sysctl.service"); + $machine->succeed('[ `sysctl -ne vm.swappiness` = 1 ]'); + $machine->execute('sysctl vm.swappiness=60'); + $machine->succeed('[ `sysctl -ne vm.swappiness` = 60 ]'); + }; + + # Test boot parameters + subtest "bootparam", sub { + $machine->succeed('grep -Fq vsyscall=emulate /proc/cmdline'); + }; ''; }) diff --git a/nixos/tests/mutable-users.nix b/nixos/tests/mutable-users.nix new file mode 100644 index 000000000000..4f11a4b83669 --- /dev/null +++ b/nixos/tests/mutable-users.nix @@ -0,0 +1,39 @@ +# Mutable users tests. + +import ./make-test.nix ({ pkgs, ...} : { + name = "mutable-users"; + meta = with pkgs.stdenv.lib.maintainers; { + maintainers = [ gleber ]; + }; + + nodes = { + machine = { config, lib, pkgs, ... }: { + users.mutableUsers = false; + }; + mutable = { config, lib, pkgs, ... }: { + users.mutableUsers = true; + }; + }; + + testScript = {nodes, ...}: let + immutableSystem = nodes.machine.config.system.build.toplevel; + mutableSystem = nodes.mutable.config.system.build.toplevel; + in '' + $machine->start(); + $machine->waitForUnit("default.target"); + + # Machine starts in immutable mode. Add a user and test if reactivating + # configuration removes the user. + $machine->fail("cat /etc/passwd | grep ^foobar:"); + $machine->succeed("sudo useradd foobar"); + $machine->succeed("cat /etc/passwd | grep ^foobar:"); + $machine->succeed("${immutableSystem}/bin/switch-to-configuration test"); + $machine->fail("cat /etc/passwd | grep ^foobar:"); + + # In immutable mode passwd is not wrapped, while in mutable mode it is + # wrapped. + $machine->succeed('which passwd | grep /run/current-system/'); + $machine->succeed("${mutableSystem}/bin/switch-to-configuration test"); + $machine->succeed('which passwd | grep /run/wrappers/'); + ''; +}) diff --git a/nixos/tests/mysql-backup.nix b/nixos/tests/mysql-backup.nix new file mode 100644 index 000000000000..f5bcc460cba7 --- /dev/null +++ b/nixos/tests/mysql-backup.nix @@ -0,0 +1,42 @@ +# Test whether mysqlBackup option works +import ./make-test.nix ({ pkgs, ... } : { + name = "mysql-backup"; + meta = with pkgs.stdenv.lib.maintainers; { + maintainers = [ rvl ]; + }; + + nodes = { + master = { config, pkgs, ... }: { + services.mysql = { + enable = true; + initialDatabases = [ { name = "testdb"; schema = ./testdb.sql; } ]; + package = pkgs.mysql; + }; + + services.mysqlBackup = { + enable = true; + databases = [ "doesnotexist" "testdb" ]; + }; + }; + }; + + testScript = + '' startAll; + + # Need to have mysql started so that it can be populated with data. + $master->waitForUnit("mysql.service"); + + # Wait for testdb to be populated. + $master->sleep(10); + + # Do a backup and wait for it to finish. + $master->startJob("mysql-backup.service"); + $master->waitForJob("mysql-backup.service"); + + # Check that data appears in backup + $master->succeed("${pkgs.gzip}/bin/zcat /var/backup/mysql/testdb.gz | grep hello"); + + # Check that a failed backup is logged + $master->succeed("journalctl -u mysql-backup.service | grep 'fail.*doesnotexist' > /dev/null"); + ''; +}) diff --git a/nixos/tests/networking.nix b/nixos/tests/networking.nix index 6a7e628d8ef1..7708775f73f3 100644 --- a/nixos/tests/networking.nix +++ b/nixos/tests/networking.nix @@ -105,7 +105,7 @@ let startAll; $client->waitForUnit("network.target"); - $router->waitForUnit("network.target"); + $router->waitForUnit("network-online.target"); # Make sure dhcpcd is not started $client->fail("systemctl status dhcpcd.service"); @@ -157,7 +157,7 @@ let startAll; $client->waitForUnit("network.target"); - $router->waitForUnit("network.target"); + $router->waitForUnit("network-online.target"); # Wait until we have an ip address on each interface $client->waitUntilSucceeds("ip addr show dev eth1 | grep -q '192.168.1'"); diff --git a/nixos/tests/nghttpx.nix b/nixos/tests/nghttpx.nix new file mode 100644 index 000000000000..433562b97191 --- /dev/null +++ b/nixos/tests/nghttpx.nix @@ -0,0 +1,61 @@ +let + nginxRoot = "/var/run/nginx"; +in + import ./make-test.nix ({...}: { + name = "nghttpx"; + nodes = { + webserver = { + networking.firewall.allowedTCPPorts = [ 80 ]; + systemd.services.nginx = { + preStart = '' + mkdir -p ${nginxRoot} + echo "Hello world!" > ${nginxRoot}/hello-world.txt + ''; + }; + + services.nginx = { + enable = true; + virtualHosts."server" = { + locations."/".root = nginxRoot; + }; + }; + }; + + proxy = { + networking.firewall.allowedTCPPorts = [ 80 ]; + services.nghttpx = { + enable = true; + frontends = [ + { server = { + host = "*"; + port = 80; + }; + + params = { + tls = "no-tls"; + }; + } + ]; + backends = [ + { server = { + host = "webserver"; + port = 80; + }; + patterns = [ "/" ]; + params.proto = "http/1.1"; + } + ]; + }; + }; + + client = {}; + }; + + testScript = '' + startAll; + + $webserver->waitForOpenPort("80"); + $proxy->waitForOpenPort("80"); + $client->waitUntilSucceeds("curl -s --fail http://proxy/hello-world.txt"); + ''; + }) diff --git a/nixos/tests/pgjwt.nix b/nixos/tests/pgjwt.nix index 2cf2963ae316..d186c42a2a98 100644 --- a/nixos/tests/pgjwt.nix +++ b/nixos/tests/pgjwt.nix @@ -1,42 +1,37 @@ -import ./make-test.nix ({ pkgs, ...} : +import ./make-test.nix ({ pkgs, lib, ...}: let - test = pkgs.writeText "test.sql" '' - CREATE EXTENSION pgcrypto; - CREATE EXTENSION pgjwt; - select sign('{"sub":"1234567890","name":"John Doe","admin":true}', 'secret'); - select * from verify('eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ', 'secret'); + test = with pkgs; runCommand "patch-test" { + nativeBuildInputs = [ pgjwt ]; + } + '' + sed -e '12 i CREATE EXTENSION pgcrypto;\nCREATE EXTENSION pgtap;\nSET search_path TO tap,public;' ${pgjwt.src}/test.sql > $out; ''; in -{ +with pkgs; { name = "pgjwt"; - meta = with pkgs.stdenv.lib.maintainers; { - maintainers = [ spinus ]; + meta = with lib.maintainers; { + maintainers = [ spinus willibutz ]; }; nodes = { - master = - { pkgs, config, ... }: - - { - services.postgresql = let mypg = pkgs.postgresql95; in { - enable = true; - package = mypg; - extraPlugins =[pkgs.pgjwt]; - initialScript = pkgs.writeText "postgresql-init.sql" - '' - CREATE ROLE postgres WITH superuser login createdb; - ''; - }; + master = { pkgs, config, ... }: + { + services.postgresql = { + enable = true; + extraPlugins = [ pgjwt pgtap ]; }; + }; }; - testScript = '' + testScript = { nodes, ... }: + let + sqlSU = "${nodes.master.config.services.postgresql.superUser}"; + pgProve = "${pkgs.perlPackages.TAPParserSourceHandlerpgTAP}"; + in + '' startAll; $master->waitForUnit("postgresql"); - $master->succeed("timeout 10 bash -c 'while ! psql postgres -c \"SELECT 1;\";do sleep 1;done;'"); - $master->succeed("cat ${test} | psql postgres"); - # I can't make original test working :[ - # $master->succeed("${pkgs.perlPackages.TAPParserSourceHandlerpgTAP}/bin/pg_prove -d postgres ${pkgs.pgjwt.src}/test.sql"); - + $master->copyFileFromHost("${test}","/tmp/test.sql"); + $master->succeed("${pkgs.sudo}/bin/sudo -u ${sqlSU} PGOPTIONS=--search_path=tap,public ${pgProve}/bin/pg_prove -d postgres -v -f /tmp/test.sql"); ''; }) diff --git a/nixos/tests/pgmanage.nix b/nixos/tests/pgmanage.nix new file mode 100644 index 000000000000..110cbd5c5b40 --- /dev/null +++ b/nixos/tests/pgmanage.nix @@ -0,0 +1,39 @@ +import ./make-test.nix ({ pkgs, ... } : +let + role = "test"; + password = "secret"; + conn = "local"; +in +{ + name = "pgmanage"; + meta = with pkgs.stdenv.lib.maintainers; { + maintainers = [ basvandijk ]; + }; + nodes = { + one = { config, pkgs, ... }: { + services = { + postgresql = { + enable = true; + initialScript = pkgs.writeText "pg-init-script" '' + CREATE ROLE ${role} SUPERUSER LOGIN PASSWORD '${password}'; + ''; + }; + pgmanage = { + enable = true; + connections = { + "${conn}" = "hostaddr=127.0.0.1 port=${toString config.services.postgresql.port} dbname=postgres"; + }; + }; + }; + }; + }; + + testScript = '' + startAll; + $one->waitForUnit("default.target"); + $one->requireActiveUnit("pgmanage.service"); + + # Test if we can log in. + $one->waitUntilSucceeds("curl 'http://localhost:8080/pgmanage/auth' --data 'action=login&connname=${conn}&username=${role}&password=${password}' --fail"); + ''; +}) diff --git a/nixos/tests/php-pcre.nix b/nixos/tests/php-pcre.nix new file mode 100644 index 000000000000..f618a39a2293 --- /dev/null +++ b/nixos/tests/php-pcre.nix @@ -0,0 +1,44 @@ + +let testString = "can-use-subgroups"; in + +import ./make-test.nix ({ pkgs, ...}: { + name = "php-httpd-pcre-jit-test"; + machine = { config, lib, pkgs, ... }: { + time.timeZone = "UTC"; + services.httpd = { + enable = true; + adminAddr = "please@dont.contact"; + extraSubservices = lib.singleton { + function = f: { + enablePHP = true; + phpOptions = "pcre.jit = true"; + + extraConfig = + let + testRoot = pkgs.writeText "index.php" + '' + <?php + preg_match('/(${testString})/', '${testString}', $result); + var_dump($result); + ?> + ''; + in + '' + Alias / ${testRoot}/ + + <Directory ${testRoot}> + Require all granted + </Directory> + ''; + }; + }; + }; + }; + testScript = { nodes, ... }: + '' + $machine->waitForUnit('httpd.service'); + # Ensure php evaluation by matching on the var_dump syntax + $machine->succeed('curl -vvv -s http://127.0.0.1:80/index.php \ + | grep "string(${toString (builtins.stringLength testString)}) \"${testString}\""'); + ''; +}) diff --git a/nixos/tests/postgis.nix b/nixos/tests/postgis.nix index 1dba5c363c09..f6ce3fe38ed3 100644 --- a/nixos/tests/postgis.nix +++ b/nixos/tests/postgis.nix @@ -9,15 +9,11 @@ import ./make-test.nix ({ pkgs, ...} : { { pkgs, config, ... }: { - services.postgresql = let mypg = pkgs.postgresql95; in { + services.postgresql = let mypg = pkgs.postgresql100; in { enable = true; package = mypg; - extraPlugins = [ (pkgs.postgis.override { postgresql = mypg; }).v_2_2_1 ]; - initialScript = pkgs.writeText "postgresql-init.sql" - '' - CREATE ROLE postgres WITH superuser login createdb; - ''; - }; + extraPlugins = [ (pkgs.postgis.override { postgresql = mypg; }).v_2_4_0 ]; + }; }; }; diff --git a/nixos/tests/prometheus.nix b/nixos/tests/prometheus.nix index ade097597bb8..374fb2d634b4 100644 --- a/nixos/tests/prometheus.nix +++ b/nixos/tests/prometheus.nix @@ -5,9 +5,6 @@ import ./make-test.nix { one = { config, pkgs, ... }: { services.prometheus = { enable = true; - globalConfig = { - labels = { foo = "bar"; }; - }; scrapeConfigs = [{ job_name = "prometheus"; static_configs = [{ diff --git a/nixos/tests/radicale.nix b/nixos/tests/radicale.nix index bec86d2cb284..f694fc75ef77 100644 --- a/nixos/tests/radicale.nix +++ b/nixos/tests/radicale.nix @@ -2,12 +2,8 @@ let user = "someuser"; password = "some_password"; port = builtins.toString 5232; -in - import ./make-test.nix ({ pkgs, lib, ... }: { - name = "radicale"; - meta.maintainers = with lib.maintainers; [ aneeshusa infinisil ]; - machine = { + common = { pkgs, ... }: { services.radicale = { enable = true; config = '' @@ -29,11 +25,82 @@ in ${pkgs.apacheHttpd}/bin/htpasswd -bcB "$out" ${user} ${password} ''; }; - - # This tests whether the web interface is accessible to an authenticated user - testScript = '' - $machine->waitForUnit('radicale.service'); - $machine->waitForOpenPort(${port}); - $machine->succeed('curl --fail http://${user}:${password}@localhost:${port}/.web/'); - ''; + +in + + import ./make-test.nix ({ pkgs, lib, ... }@args: { + name = "radicale"; + meta.maintainers = with lib.maintainers; [ aneeshusa infinisil ]; + + nodes = rec { + radicale = radicale1; # Make the test script read more nicely + radicale1 = lib.recursiveUpdate (common args) { + nixpkgs.overlays = [ + (self: super: { + radicale1 = super.radicale1.overrideAttrs (oldAttrs: { + propagatedBuildInputs = with self.pythonPackages; + (oldAttrs.propagatedBuildInputs or []) ++ [ passlib ]; + }); + }) + ]; + system.stateVersion = "17.03"; + }; + radicale1_export = lib.recursiveUpdate radicale1 { + services.radicale.extraArgs = [ + "--export-storage" "/tmp/collections-new" + ]; + }; + radicale2_verify = lib.recursiveUpdate radicale2 { + services.radicale.extraArgs = [ "--verify-storage" ]; + }; + radicale2 = lib.recursiveUpdate (common args) { + system.stateVersion = "17.09"; + }; + }; + + # This tests whether the web interface is accessible to an authenticated user + testScript = { nodes }: let + switchToConfig = nodeName: let + newSystem = nodes.${nodeName}.config.system.build.toplevel; + in "${newSystem}/bin/switch-to-configuration test"; + in '' + # Check Radicale 1 functionality + $radicale->succeed('${switchToConfig "radicale1"} >&2'); + $radicale->waitForUnit('radicale.service'); + $radicale->waitForOpenPort(${port}); + $radicale->succeed('curl --fail http://${user}:${password}@localhost:${port}/someuser/calendar.ics/'); + + # Export data in Radicale 2 format + $radicale->succeed('systemctl stop radicale'); + $radicale->succeed('ls -al /tmp/collections'); + $radicale->fail('ls -al /tmp/collections-new'); + # Radicale exits immediately after exporting storage + $radicale->succeed('${switchToConfig "radicale1_export"} >&2'); + $radicale->waitUntilFails('systemctl status radicale'); + $radicale->succeed('ls -al /tmp/collections'); + $radicale->succeed('ls -al /tmp/collections-new'); + + # Verify data in Radicale 2 format + $radicale->succeed('rm -r /tmp/collections/${user}'); + $radicale->succeed('mv /tmp/collections-new/collection-root /tmp/collections'); + $radicale->succeed('${switchToConfig "radicale2_verify"} >&2'); + $radicale->waitUntilFails('systemctl status radicale'); + my ($retcode, $logs) = $radicale->execute('journalctl -u radicale -n 5'); + if ($retcode != 0 || index($logs, 'Verifying storage') == -1) { + die "Radicale 2 didn't verify storage" + } + if (index($logs, 'failed') != -1 || index($logs, 'exception') != -1) { + die "storage verification failed" + } + + # Check Radicale 2 functionality + $radicale->succeed('${switchToConfig "radicale2"} >&2'); + $radicale->waitForUnit('radicale.service'); + $radicale->waitForOpenPort(${port}); + my ($retcode, $output) = $radicale->execute('curl --fail http://${user}:${password}@localhost:${port}/someuser/calendar.ics/'); + if ($retcode != 0 || index($output, 'VCALENDAR') == -1) { + die "Could not read calendar from Radicale 2" + } + $radicale->succeed('curl --fail http://${user}:${password}@localhost:${port}/.web/'); + ''; }) diff --git a/nixos/tests/run-in-machine.nix b/nixos/tests/run-in-machine.nix index d1102f8d4073..a6dfece44a92 100644 --- a/nixos/tests/run-in-machine.nix +++ b/nixos/tests/run-in-machine.nix @@ -2,7 +2,16 @@ with import ../lib/testing.nix { inherit system; }; -runInMachine { - drv = pkgs.hello; - machine = { config, pkgs, ... }: { /* services.sshd.enable = true; */ }; -} +let + output = runInMachine { + drv = pkgs.hello; + machine = { config, pkgs, ... }: { /* services.sshd.enable = true; */ }; + }; +in pkgs.runCommand "verify-output" { inherit output; } '' + if [ ! -e "$output/bin/hello" ]; then + echo "Derivation built using runInMachine produced incorrect output:" >&2 + ls -laR "$output" >&2 + exit 1 + fi + "$output/bin/hello" > "$out" +'' diff --git a/nixos/tests/switch-test.nix b/nixos/tests/switch-test.nix new file mode 100644 index 000000000000..46f2563af8d9 --- /dev/null +++ b/nixos/tests/switch-test.nix @@ -0,0 +1,25 @@ +# Test configuration switching. + +import ./make-test.nix ({ pkgs, ...} : { + name = "switch-test"; + meta = with pkgs.stdenv.lib.maintainers; { + maintainers = [ gleber ]; + }; + + nodes = { + machine = { config, lib, pkgs, ... }: { + users.mutableUsers = false; + }; + other = { config, lib, pkgs, ... }: { + users.mutableUsers = true; + }; + }; + + testScript = {nodes, ...}: let + originalSystem = nodes.machine.config.system.build.toplevel; + otherSystem = nodes.other.config.system.build.toplevel; + in '' + $machine->succeed("env -i ${originalSystem}/bin/switch-to-configuration test | tee /dev/stderr"); + $machine->succeed("env -i ${otherSystem}/bin/switch-to-configuration test | tee /dev/stderr"); + ''; +}) diff --git a/nixos/tests/sysctl.nix b/nixos/tests/sysctl.nix deleted file mode 100644 index d7220cabb22c..000000000000 --- a/nixos/tests/sysctl.nix +++ /dev/null @@ -1,25 +0,0 @@ -import ./make-test.nix ({ pkgs, ...} : { - name = "sysctl"; - meta = with pkgs.stdenv.lib.maintainers; { - maintainers = [ nequissimus ]; - }; - - machine = { config, lib, pkgs, ... }: - { - boot.kernelPackages = pkgs.linuxPackages; - boot.kernel.sysctl = { - "kernel.dmesg_restrict" = true; # Restrict dmesg access - "net.core.bpf_jit_enable" = false; # Turn off bpf JIT - "user.max_user_namespaces" = 0; # Disable user namespaces - "vm.swappiness" = 2; # Low swap usage - }; - }; - - testScript = - '' - $machine->succeed("sysctl kernel.dmesg_restrict | grep 'kernel.dmesg_restrict = 1'"); - $machine->succeed("sysctl net.core.bpf_jit_enable | grep 'net.core.bpf_jit_enable = 0'"); - $machine->succeed("sysctl user.max_user_namespaces | grep 'user.max_user_namespaces = 0'"); - $machine->succeed("sysctl vm.swappiness | grep 'vm.swappiness = 2'"); - ''; -}) diff --git a/nixos/tests/testdb.sql b/nixos/tests/testdb.sql index 4fb28fea3df9..3c68c49ae82c 100644 --- a/nixos/tests/testdb.sql +++ b/nixos/tests/testdb.sql @@ -8,3 +8,4 @@ insert into tests values (1, 'a'); insert into tests values (2, 'b'); insert into tests values (3, 'c'); insert into tests values (4, 'd'); +insert into tests values (5, 'hello'); diff --git a/nixos/tests/virtualbox.nix b/nixos/tests/virtualbox.nix index 4f7cb176d96f..c519d7dae8be 100644 --- a/nixos/tests/virtualbox.nix +++ b/nixos/tests/virtualbox.nix @@ -107,8 +107,8 @@ let buildInputs = [ pkgs.utillinux pkgs.perl ]; } '' - ${pkgs.parted}/sbin/parted /dev/vda mklabel msdos - ${pkgs.parted}/sbin/parted /dev/vda -- mkpart primary ext2 1M -1s + ${pkgs.parted}/sbin/parted --script /dev/vda mklabel msdos + ${pkgs.parted}/sbin/parted --script /dev/vda -- mkpart primary ext2 1M -1s . /sys/class/block/vda1/uevent mknod /dev/vda1 b $MAJOR $MINOR @@ -461,11 +461,11 @@ in mapAttrs mkVBoxTest { my $test1IP = waitForIP_test1 1; my $test2IP = waitForIP_test2 1; - $machine->succeed("echo '$test2IP' | nc '$test1IP' 1234"); - $machine->succeed("echo '$test1IP' | nc '$test2IP' 1234"); + $machine->succeed("echo '$test2IP' | nc -N '$test1IP' 1234"); + $machine->succeed("echo '$test1IP' | nc -N '$test2IP' 1234"); - $machine->waitUntilSucceeds("nc '$test1IP' 5678 >&2"); - $machine->waitUntilSucceeds("nc '$test2IP' 5678 >&2"); + $machine->waitUntilSucceeds("nc -N '$test1IP' 5678 < /dev/null >&2"); + $machine->waitUntilSucceeds("nc -N '$test2IP' 5678 < /dev/null >&2"); shutdownVM_test1; shutdownVM_test2; diff --git a/nixos/tests/zfs.nix b/nixos/tests/zfs.nix new file mode 100644 index 000000000000..a6908024de9b --- /dev/null +++ b/nixos/tests/zfs.nix @@ -0,0 +1,85 @@ +{ system ? builtins.currentSystem }: + +with import ../lib/testing.nix { inherit system; }; + +let + + makeTest = import ./make-test.nix; + + makeZfsTest = name: + { kernelPackage ? pkgs.linuxPackages_latest + , enableUnstable ? false + , extraTest ? "" + }: + makeTest { + name = "zfs-" + name; + meta = with pkgs.stdenv.lib.maintainers; { + maintainers = [ adisbladis ]; + }; + + machine = { config, lib, pkgs, ... }: + { + virtualisation.emptyDiskImages = [ 4096 ]; + networking.hostId = "deadbeef"; + boot.kernelPackages = kernelPackage; + boot.supportedFilesystems = [ "zfs" ]; + boot.zfs.enableUnstable = enableUnstable; + + environment.systemPackages = with pkgs; [ + parted + ]; + }; + + testScript = '' + $machine->succeed("modprobe zfs"); + $machine->succeed("zpool status"); + + $machine->succeed("ls /dev"); + + $machine->succeed( + "mkdir /tmp/mnt", + + "udevadm settle", + "parted --script /dev/vdb mklabel msdos", + "parted --script /dev/vdb -- mkpart primary 1024M -1s", + "udevadm settle", + + "zpool create rpool /dev/vdb1", + "zfs create -o mountpoint=legacy rpool/root", + "mount -t zfs rpool/root /tmp/mnt", + "udevadm settle", + + "umount /tmp/mnt", + "zpool destroy rpool", + "udevadm settle" + + ); + + '' + extraTest; + + }; + + +in { + + stable = makeZfsTest "stable" { }; + + unstable = makeZfsTest "unstable" { + enableUnstable = true; + extraTest = '' + $machine->succeed( + "echo password | zpool create -o altroot='/tmp/mnt' -O encryption=aes-256-gcm -O keyformat=passphrase rpool /dev/vdb1", + "zfs create -o mountpoint=legacy rpool/root", + "mount -t zfs rpool/root /tmp/mnt", + "udevadm settle", + + "umount /tmp/mnt", + "zpool destroy rpool", + "udevadm settle" + ); + ''; + }; + + installer = (import ./installer.nix { }).zfsroot; + +} diff --git a/nixos/tests/zookeeper.nix b/nixos/tests/zookeeper.nix new file mode 100644 index 000000000000..d247654adade --- /dev/null +++ b/nixos/tests/zookeeper.nix @@ -0,0 +1,28 @@ +import ./make-test.nix ({ pkgs, ...} : { + name = "zookeeper"; + meta = with pkgs.stdenv.lib.maintainers; { + maintainers = [ nequissimus ]; + }; + + nodes = { + server = { pkgs, config, ... }: { + services.zookeeper = { + enable = true; + }; + + networking.firewall.allowedTCPPorts = [ 2181 ]; + }; + }; + + testScript = '' + startAll; + + $server->waitForUnit("zookeeper"); + $server->waitForUnit("network.target"); + $server->waitForOpenPort(2181); + + $server->waitUntilSucceeds("${pkgs.zookeeper}/bin/zkCli.sh -server localhost:2181 create /foo bar"); + $server->waitUntilSucceeds("${pkgs.zookeeper}/bin/zkCli.sh -server localhost:2181 set /foo hello"); + $server->waitUntilSucceeds("${pkgs.zookeeper}/bin/zkCli.sh -server localhost:2181 get /foo | grep hello"); + ''; +}) |