about summary refs log tree commit diff
path: root/nixos/tests
diff options
context:
space:
mode:
Diffstat (limited to 'nixos/tests')
-rw-r--r--nixos/tests/acme.nix4
-rw-r--r--nixos/tests/atd.nix36
-rw-r--r--nixos/tests/boot-stage1.nix5
-rw-r--r--nixos/tests/common/letsencrypt.nix4
-rw-r--r--nixos/tests/containers-bridge.nix6
-rw-r--r--nixos/tests/couchdb.nix56
-rw-r--r--nixos/tests/dovecot.nix64
-rw-r--r--nixos/tests/gitlab.nix54
-rw-r--r--nixos/tests/gitolite.nix139
-rw-r--r--nixos/tests/gnome3-gdm.nix2
-rw-r--r--nixos/tests/grafana.nix25
-rw-r--r--nixos/tests/graphite.nix32
-rw-r--r--nixos/tests/hardened.nix29
-rw-r--r--nixos/tests/initrd-network-ssh.nix74
-rw-r--r--nixos/tests/installer.nix107
-rw-r--r--nixos/tests/ipfs.nix6
-rw-r--r--nixos/tests/kafka_0_10.nix48
-rw-r--r--nixos/tests/kafka_0_11.nix48
-rw-r--r--nixos/tests/kafka_0_9.nix48
-rw-r--r--nixos/tests/kafka_1_0.nix48
-rw-r--r--nixos/tests/kernel-params.nix24
-rw-r--r--nixos/tests/krb5/default.nix5
-rw-r--r--nixos/tests/krb5/deprecated-config.nix48
-rw-r--r--nixos/tests/krb5/example-config.nix106
-rw-r--r--nixos/tests/kubernetes.nix409
-rw-r--r--nixos/tests/kubernetes/base.nix113
-rw-r--r--nixos/tests/kubernetes/certs.nix185
-rw-r--r--nixos/tests/kubernetes/default.nix7
-rw-r--r--nixos/tests/kubernetes/dns.nix127
-rw-r--r--nixos/tests/kubernetes/e2e.nix40
-rw-r--r--nixos/tests/kubernetes/kubernetes-common.nix59
-rw-r--r--nixos/tests/kubernetes/rbac.nix137
-rw-r--r--nixos/tests/mesos.nix10
-rw-r--r--nixos/tests/minio.nix4
-rw-r--r--nixos/tests/misc.nix15
-rw-r--r--nixos/tests/mutable-users.nix39
-rw-r--r--nixos/tests/mysql-backup.nix42
-rw-r--r--nixos/tests/networking.nix4
-rw-r--r--nixos/tests/nghttpx.nix61
-rw-r--r--nixos/tests/pgjwt.nix51
-rw-r--r--nixos/tests/pgmanage.nix39
-rw-r--r--nixos/tests/php-pcre.nix44
-rw-r--r--nixos/tests/postgis.nix10
-rw-r--r--nixos/tests/prometheus.nix3
-rw-r--r--nixos/tests/radicale.nix91
-rw-r--r--nixos/tests/run-in-machine.nix17
-rw-r--r--nixos/tests/switch-test.nix25
-rw-r--r--nixos/tests/sysctl.nix25
-rw-r--r--nixos/tests/testdb.sql1
-rw-r--r--nixos/tests/virtualbox.nix12
-rw-r--r--nixos/tests/zfs.nix85
-rw-r--r--nixos/tests/zookeeper.nix28
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");
+  '';
+})