about summary refs log tree commit diff
path: root/nixpkgs/nixos/modules/testing
diff options
context:
space:
mode:
Diffstat (limited to 'nixpkgs/nixos/modules/testing')
-rw-r--r--nixpkgs/nixos/modules/testing/service-runner.nix127
-rw-r--r--nixpkgs/nixos/modules/testing/test-instrumentation.nix155
2 files changed, 282 insertions, 0 deletions
diff --git a/nixpkgs/nixos/modules/testing/service-runner.nix b/nixpkgs/nixos/modules/testing/service-runner.nix
new file mode 100644
index 000000000000..bdb35f128a73
--- /dev/null
+++ b/nixpkgs/nixos/modules/testing/service-runner.nix
@@ -0,0 +1,127 @@
+{ lib, pkgs, ... }:
+
+with lib;
+
+let
+
+  makeScript = name: service: pkgs.writeScript "${name}-runner"
+    ''
+      #! ${pkgs.perl.withPackages (p: [ p.FileSlurp ])}/bin/perl -w
+
+      use File::Slurp;
+
+      sub run {
+          my ($cmd) = @_;
+          my @args = ();
+          while ($cmd =~ /([^ \t\n']+)|(\'([^'])\')\s*/g) {
+            push @args, $1;
+          }
+          my $prog;
+          if (substr($args[0], 0, 1) eq "@") {
+              $prog = substr($args[0], 1);
+              shift @args;
+          } else {
+              $prog = $args[0];
+          }
+          my $pid = fork;
+          if ($pid == 0) {
+              setpgrp; # don't receive SIGINT etc. from terminal
+              exec { $prog } @args;
+              die "failed to exec $prog\n";
+          } elsif (!defined $pid) {
+              die "failed to fork: $!\n";
+          }
+          return $pid;
+      };
+
+      sub run_wait {
+          my ($cmd) = @_;
+          my $pid = run $cmd;
+          die if waitpid($pid, 0) != $pid;
+          return $?;
+      };
+
+      # Set the environment.  FIXME: escaping.
+      foreach my $key (keys %ENV) {
+          next if $key eq 'LOCALE_ARCHIVE';
+          delete $ENV{$key};
+      }
+      ${concatStrings (mapAttrsToList (n: v: ''
+        $ENV{'${n}'} = '${v}';
+      '') service.environment)}
+
+      # Run the ExecStartPre program.  FIXME: this could be a list.
+      my $preStart = <<END_CMD;
+      ${concatStringsSep "\n" (service.serviceConfig.ExecStartPre or [])}
+      END_CMD
+      if (defined $preStart && $preStart ne "\n") {
+          print STDERR "running ExecStartPre: $preStart\n";
+          my $res = run_wait $preStart;
+          die "$0: ExecStartPre failed with status $res\n" if $res;
+      };
+
+      # Run the ExecStart program.
+      my $cmd = <<END_CMD;
+      ${service.serviceConfig.ExecStart}
+      END_CMD
+
+      print STDERR "running ExecStart: $cmd\n";
+      my $mainPid = run $cmd;
+      $ENV{'MAINPID'} = $mainPid;
+
+      # Catch SIGINT, propagate to the main program.
+      sub intHandler {
+          print STDERR "got SIGINT, stopping service...\n";
+          kill 'INT', $mainPid;
+      };
+      $SIG{'INT'} = \&intHandler;
+      $SIG{'QUIT'} = \&intHandler;
+
+      # Run the ExecStartPost program.
+      my $postStart = <<END_CMD;
+      ${concatStringsSep "\n" (service.serviceConfig.ExecStartPost or [])}
+      END_CMD
+      if (defined $postStart && $postStart ne "\n") {
+          print STDERR "running ExecStartPost: $postStart\n";
+          my $res = run_wait $postStart;
+          die "$0: ExecStartPost failed with status $res\n" if $res;
+      }
+
+      # Wait for the main program to exit.
+      die if waitpid($mainPid, 0) != $mainPid;
+      my $mainRes = $?;
+
+      # Run the ExecStopPost program.
+      my $postStop = <<END_CMD;
+      ${service.serviceConfig.ExecStopPost or ""}
+      END_CMD
+      if (defined $postStop && $postStop ne "\n") {
+          print STDERR "running ExecStopPost: $postStop\n";
+          my $res = run_wait $postStop;
+          die "$0: ExecStopPost failed with status $res\n" if $res;
+      }
+
+      exit($mainRes & 127 ? 255 : $mainRes << 8);
+    '';
+
+  opts = { config, name, ... }: {
+    options.runner = mkOption {
+    internal = true;
+    description = lib.mdDoc ''
+        A script that runs the service outside of systemd,
+        useful for testing or for using NixOS services outside
+        of NixOS.
+    '';
+    };
+    config.runner = makeScript name config;
+  };
+
+in
+
+{
+  options = {
+    systemd.services = mkOption {
+      type = with types; attrsOf (submodule opts);
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/testing/test-instrumentation.nix b/nixpkgs/nixos/modules/testing/test-instrumentation.nix
new file mode 100644
index 000000000000..c91e54f5a4d7
--- /dev/null
+++ b/nixpkgs/nixos/modules/testing/test-instrumentation.nix
@@ -0,0 +1,155 @@
+# This module allows the test driver to connect to the virtual machine
+# via a root shell attached to port 514.
+
+{ options, config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  qemu-common = import ../../lib/qemu-common.nix { inherit lib pkgs; };
+in
+
+{
+
+  config = {
+
+    systemd.services.backdoor =
+      { wantedBy = [ "multi-user.target" ];
+        requires = [ "dev-hvc0.device" "dev-${qemu-common.qemuSerialDevice}.device" ];
+        after = [ "dev-hvc0.device" "dev-${qemu-common.qemuSerialDevice}.device" ];
+        script =
+          ''
+            export USER=root
+            export HOME=/root
+            export DISPLAY=:0.0
+
+            source /etc/profile
+
+            # Don't use a pager when executing backdoor
+            # actions. Because we use a tty, commands like systemctl
+            # or nix-store get confused into thinking they're running
+            # interactively.
+            export PAGER=
+
+            cd /tmp
+            exec < /dev/hvc0 > /dev/hvc0
+            while ! exec 2> /dev/${qemu-common.qemuSerialDevice}; do sleep 0.1; done
+            echo "connecting to host..." >&2
+            stty -F /dev/hvc0 raw -echo # prevent nl -> cr/nl conversion
+            # The following line is essential since it signals to
+            # the test driver that the shell is ready.
+            # See: the connect method in the Machine class.
+            echo "Spawning backdoor root shell..."
+            # Passing the terminal device makes bash run non-interactively.
+            # Otherwise we get errors on the terminal because bash tries to
+            # setup things like job control.
+            # Note: calling bash explicitly here instead of sh makes sure that
+            # we can also run non-NixOS guests during tests.
+            PS1= exec /usr/bin/env bash --norc /dev/hvc0
+          '';
+        serviceConfig.KillSignal = "SIGHUP";
+      };
+
+    # Prevent agetty from being instantiated on the serial device, since it
+    # interferes with the backdoor (writes to it will randomly fail
+    # with EIO).  Likewise for hvc0.
+    systemd.services."serial-getty@${qemu-common.qemuSerialDevice}".enable = false;
+    systemd.services."serial-getty@hvc0".enable = false;
+
+    # Only set these settings when the options exist. Some tests (e.g. those
+    # that do not specify any nodes, or an empty attr set as nodes) will not
+    # have the QEMU module loaded and thuse these options can't and should not
+    # be set.
+    virtualisation = lib.optionalAttrs (options ? virtualisation.qemu) {
+      qemu = {
+        # Only use a serial console, no TTY.
+        # NOTE: optionalAttrs
+        #       test-instrumentation.nix appears to be used without qemu-vm.nix, so
+        #       we avoid defining consoles if not possible.
+        # TODO: refactor such that test-instrumentation can import qemu-vm
+        #       or declare virtualisation.qemu.console option in a module that's always imported
+        consoles = [ qemu-common.qemuSerialDevice ];
+        package  = lib.mkDefault pkgs.qemu_test;
+      };
+    };
+
+    boot.kernel.sysctl = {
+      "kernel.hung_task_timeout_secs" = 600;
+      # Panic on out-of-memory conditions rather than letting the
+      # OOM killer randomly get rid of processes, since this leads
+      # to failures that are hard to diagnose.
+      "vm.panic_on_oom" = lib.mkDefault 2;
+    };
+
+    boot.kernelParams = [
+      "console=${qemu-common.qemuSerialDevice}"
+      # Panic if an error occurs in stage 1 (rather than waiting for
+      # user intervention).
+      "panic=1" "boot.panic_on_fail"
+      # Using acpi_pm as a clock source causes the guest clock to
+      # slow down under high host load.  This is usually a bad
+      # thing, but for VM tests it should provide a bit more
+      # determinism (e.g. if the VM runs at lower speed, then
+      # timeouts in the VM should also be delayed).
+      "clock=acpi_pm"
+    ];
+
+    # `xwininfo' is used by the test driver to query open windows.
+    environment.systemPackages = [ pkgs.xorg.xwininfo ];
+
+    # Log everything to the serial console.
+    services.journald.extraConfig =
+      ''
+        ForwardToConsole=yes
+        MaxLevelConsole=debug
+      '';
+
+    boot.initrd.systemd.contents."/etc/systemd/journald.conf".text = ''
+      [Journal]
+      ForwardToConsole=yes
+      MaxLevelConsole=debug
+    '';
+
+    systemd.extraConfig = ''
+      # Don't clobber the console with duplicate systemd messages.
+      ShowStatus=no
+      # Allow very slow start
+      DefaultTimeoutStartSec=300
+      DefaultDeviceTimeoutSec=300
+    '';
+    systemd.user.extraConfig = ''
+      # Allow very slow start
+      DefaultTimeoutStartSec=300
+      DefaultDeviceTimeoutSec=300
+    '';
+
+    boot.initrd.systemd.extraConfig = config.systemd.extraConfig;
+
+    boot.consoleLogLevel = 7;
+
+    # Prevent tests from accessing the Internet.
+    networking.defaultGateway = mkOverride 150 null;
+    networking.nameservers = mkOverride 150 [ ];
+
+    system.requiredKernelConfig = with config.lib.kernelConfig; [
+      (isYes "SERIAL_8250_CONSOLE")
+      (isYes "SERIAL_8250")
+      (isEnabled "VIRTIO_CONSOLE")
+    ];
+
+    networking.usePredictableInterfaceNames = false;
+
+    # Make it easy to log in as root when running the test interactively.
+    users.users.root.initialHashedPassword = mkOverride 150 "";
+
+    services.xserver.displayManager.job.logToJournal = true;
+
+    # Make sure we use the Guest Agent from the QEMU package for testing
+    # to reduce the closure size required for the tests.
+    services.qemuGuest.package = pkgs.qemu_test.ga;
+
+    # Squelch warning about unset system.stateVersion
+    system.stateVersion = lib.mkDefault lib.trivial.release;
+  };
+
+}