diff --git a/nixos/doc/manual/from_md/release-notes/rl-2111.section.xml b/nixos/doc/manual/from_md/release-notes/rl-2111.section.xml index f0e39471eceee..eb620a3e3edc2 100644 --- a/nixos/doc/manual/from_md/release-notes/rl-2111.section.xml +++ b/nixos/doc/manual/from_md/release-notes/rl-2111.section.xml @@ -1860,15 +1860,6 @@ Superuser created successfully. encapsulation. - - - Changing systemd .socket units now restarts - them and stops the service that is activated by them. - Additionally, services with - stopOnChange = false don’t break anymore - when they are socket-activated. - - The virtualisation.libvirtd module has been diff --git a/nixos/doc/manual/release-notes/rl-2111.section.md b/nixos/doc/manual/release-notes/rl-2111.section.md index 3bdda82c026f4..c015147990388 100644 --- a/nixos/doc/manual/release-notes/rl-2111.section.md +++ b/nixos/doc/manual/release-notes/rl-2111.section.md @@ -520,8 +520,6 @@ In addition to numerous new and upgraded packages, this release has the followin - `networking.sits` now supports Foo-over-UDP encapsulation. -- Changing systemd `.socket` units now restarts them and stops the service that is activated by them. Additionally, services with `stopOnChange = false` don't break anymore when they are socket-activated. - - The `virtualisation.libvirtd` module has been refactored and updated with new options: - `virtualisation.libvirtd.qemu*` options (e.g.: `virtualisation.libvirtd.qemuRunAsRoot`) were moved to [`virtualisation.libvirtd.qemu`](options.html#opt-virtualisation.libvirtd.qemu) submodule, - software TPM1/TPM2 support (e.g.: Windows 11 guests) ([`virtualisation.libvirtd.qemu.swtpm`](options.html#opt-virtualisation.libvirtd.qemu.swtpm)), diff --git a/nixos/modules/system/activation/switch-to-configuration.pl b/nixos/modules/system/activation/switch-to-configuration.pl index e105502cf3a48..053496441d81c 100644 --- a/nixos/modules/system/activation/switch-to-configuration.pl +++ b/nixos/modules/system/activation/switch-to-configuration.pl @@ -11,6 +11,7 @@ my $out = "@out@"; +# FIXME: maybe we should use /proc/1/exe to get the current systemd. my $curSystemd = abs_path("/run/current-system/sw/bin"); # To be robust against interruption, record what units need to be started etc. @@ -18,16 +19,13 @@ my $restartListFile = "/run/nixos/restart-list"; my $reloadListFile = "/run/nixos/reload-list"; -# Parse restart/reload requests by the activation script. -# Activation scripts may write newline-separated units to this -# file and switch-to-configuration will handle them. While -# `stopIfChanged = true` is ignored, switch-to-configuration will -# handle `restartIfChanged = false` and `reloadIfChanged = true`. -# This also works for socket-activated units. +# Parse restart/reload requests by the activation script my $restartByActivationFile = "/run/nixos/activation-restart-list"; +my $reloadByActivationFile = "/run/nixos/activation-reload-list"; my $dryRestartByActivationFile = "/run/nixos/dry-activation-restart-list"; +my $dryReloadByActivationFile = "/run/nixos/dry-activation-reload-list"; -make_path("/run/nixos", { mode => oct(755) }); +make_path("/run/nixos", { mode => 0755 }); my $action = shift @ARGV; @@ -149,92 +147,6 @@ sub fingerprintUnit { return abs_path($s) . (-f "${s}.d/overrides.conf" ? " " . abs_path "${s}.d/overrides.conf" : ""); } -sub handleModifiedUnit { - my ($unit, $baseName, $newUnitFile, $activePrev, $unitsToStop, $unitsToStart, $unitsToReload, $unitsToRestart, $unitsToSkip) = @_; - - if ($unit eq "sysinit.target" || $unit eq "basic.target" || $unit eq "multi-user.target" || $unit eq "graphical.target" || $unit =~ /\.slice$/ || $unit =~ /\.path$/) { - # Do nothing. These cannot be restarted directly. - # Slices and Paths don't have to be restarted since - # properties (resource limits and inotify watches) - # seem to get applied on daemon-reload. - } elsif ($unit =~ /\.mount$/) { - # Reload the changed mount unit to force a remount. - $unitsToReload->{$unit} = 1; - recordUnit($reloadListFile, $unit); - } else { - my $unitInfo = parseUnit($newUnitFile); - if (boolIsTrue($unitInfo->{'X-ReloadIfChanged'} // "no")) { - $unitsToReload->{$unit} = 1; - recordUnit($reloadListFile, $unit); - } - elsif (!boolIsTrue($unitInfo->{'X-RestartIfChanged'} // "yes") || boolIsTrue($unitInfo->{'RefuseManualStop'} // "no") || boolIsTrue($unitInfo->{'X-OnlyManualStart'} // "no")) { - $unitsToSkip->{$unit} = 1; - } else { - # If this unit is socket-activated, then stop it instead - # of restarting it to make sure the new version of it is - # socket-activated. - my $socketActivated = 0; - if ($unit =~ /\.service$/) { - my @sockets = split / /, ($unitInfo->{Sockets} // ""); - if (scalar @sockets == 0) { - @sockets = ("$baseName.socket"); - } - foreach my $socket (@sockets) { - if (-e "$out/etc/systemd/system/$socket") { - $socketActivated = 1; - $unitsToStop->{$unit} = 1; - # If the socket was not running previously, - # start it now. - if (not defined $activePrev->{$socket}) { - $unitsToStart->{$socket} = 1; - } - } - } - } - - # Don't do the rest of this for socket-activated units - # because we handled these above where we stop the unit. - # Since only services can be socket-activated, the - # following condition always evaluates to `true` for - # non-service units. - if ($socketActivated) { - return; - } - - # If we are restarting a socket, also stop the corresponding - # service. This is required because restarting a socket - # when the service is already activated fails. - if ($unit =~ /\.socket$/) { - my $service = $unitInfo->{Service} // ""; - if ($service eq "") { - $service = "$baseName.service"; - } - if (defined $activePrev->{$service}) { - $unitsToStop->{$service} = 1; - } - $unitsToRestart->{$unit} = 1; - recordUnit($restartListFile, $unit); - } else { - # Always restart non-services instead of stopping and starting them - # because it doesn't make sense to stop them with a config from - # the old evaluation. - if (!boolIsTrue($unitInfo->{'X-StopIfChanged'} // "yes") || $unit !~ /\.service$/) { - # This unit should be restarted instead of - # stopped and started. - $unitsToRestart->{$unit} = 1; - recordUnit($restartListFile, $unit); - } else { - # We write to a file to ensure that the - # service gets restarted if we're interrupted. - $unitsToStart->{$unit} = 1; - recordUnit($startListFile, $unit); - $unitsToStop->{$unit} = 1; - } - } - } - } -} - # Figure out what units need to be stopped, started, restarted or reloaded. my (%unitsToStop, %unitsToSkip, %unitsToStart, %unitsToRestart, %unitsToReload); @@ -307,7 +219,65 @@ sub handleModifiedUnit { } elsif (fingerprintUnit($prevUnitFile) ne fingerprintUnit($newUnitFile)) { - handleModifiedUnit($unit, $baseName, $newUnitFile, $activePrev, \%unitsToStop, \%unitsToStart, \%unitsToReload, \%unitsToRestart, %unitsToSkip); + if ($unit eq "sysinit.target" || $unit eq "basic.target" || $unit eq "multi-user.target" || $unit eq "graphical.target") { + # Do nothing. These cannot be restarted directly. + } elsif ($unit =~ /\.mount$/) { + # Reload the changed mount unit to force a remount. + $unitsToReload{$unit} = 1; + recordUnit($reloadListFile, $unit); + } elsif ($unit =~ /\.socket$/ || $unit =~ /\.path$/ || $unit =~ /\.slice$/) { + # FIXME: do something? + } else { + my $unitInfo = parseUnit($newUnitFile); + if (boolIsTrue($unitInfo->{'X-ReloadIfChanged'} // "no")) { + $unitsToReload{$unit} = 1; + recordUnit($reloadListFile, $unit); + } + elsif (!boolIsTrue($unitInfo->{'X-RestartIfChanged'} // "yes") || boolIsTrue($unitInfo->{'RefuseManualStop'} // "no") || boolIsTrue($unitInfo->{'X-OnlyManualStart'} // "no")) { + $unitsToSkip{$unit} = 1; + } else { + if (!boolIsTrue($unitInfo->{'X-StopIfChanged'} // "yes")) { + # This unit should be restarted instead of + # stopped and started. + $unitsToRestart{$unit} = 1; + recordUnit($restartListFile, $unit); + } else { + # If this unit is socket-activated, then stop the + # socket unit(s) as well, and restart the + # socket(s) instead of the service. + my $socketActivated = 0; + if ($unit =~ /\.service$/) { + my @sockets = split / /, ($unitInfo->{Sockets} // ""); + if (scalar @sockets == 0) { + @sockets = ("$baseName.socket"); + } + foreach my $socket (@sockets) { + if (defined $activePrev->{$socket}) { + $unitsToStop{$socket} = 1; + # Only restart sockets that actually + # exist in new configuration: + if (-e "$out/etc/systemd/system/$socket") { + $unitsToStart{$socket} = 1; + recordUnit($startListFile, $socket); + $socketActivated = 1; + } + } + } + } + + # If the unit is not socket-activated, record + # that this unit needs to be started below. + # We write this to a file to ensure that the + # service gets restarted if we're interrupted. + if (!$socketActivated) { + $unitsToStart{$unit} = 1; + recordUnit($startListFile, $unit); + } + + $unitsToStop{$unit} = 1; + } + } + } } } } @@ -392,6 +362,8 @@ sub filterUnits { } my @unitsToStopFiltered = filterUnits(\%unitsToStop); +my @unitsToStartFiltered = filterUnits(\%unitsToStart); + # Show dry-run actions. if ($action eq "dry-activate") { @@ -403,44 +375,21 @@ sub filterUnits { print STDERR "would activate the configuration...\n"; system("$out/dry-activate", "$out"); - # Handle the activation script requesting the restart or reload of a unit. - my %unitsToAlsoStop; - my %unitsToAlsoSkip; - foreach (split('\n', read_file($dryRestartByActivationFile, err_mode => 'quiet') // "")) { - my $unit = $_; - my $baseUnit = $unit; - my $newUnitFile = "$out/etc/systemd/system/$baseUnit"; - - # Detect template instances. - if (!-e $newUnitFile && $unit =~ /^(.*)@[^\.]*\.(.*)$/) { - $baseUnit = "$1\@.$2"; - $newUnitFile = "$out/etc/systemd/system/$baseUnit"; - } - - my $baseName = $baseUnit; - $baseName =~ s/\.[a-z]*$//; + $unitsToRestart{$_} = 1 foreach + split('\n', read_file($dryRestartByActivationFile, err_mode => 'quiet') // ""); - handleModifiedUnit($unit, $baseName, $newUnitFile, $activePrev, \%unitsToAlsoStop, \%unitsToStart, \%unitsToReload, \%unitsToRestart, %unitsToAlsoSkip); - } - unlink($dryRestartByActivationFile); - - my @unitsToAlsoStopFiltered = filterUnits(\%unitsToAlsoStop); - if (scalar(keys %unitsToAlsoStop) > 0) { - print STDERR "would stop the following units as well: ", join(", ", @unitsToAlsoStopFiltered), "\n" - if scalar @unitsToAlsoStopFiltered; - } - - print STDERR "would NOT restart the following changed units as well: ", join(", ", sort(keys %unitsToAlsoSkip)), "\n" - if scalar(keys %unitsToAlsoSkip) > 0; + $unitsToReload{$_} = 1 foreach + split('\n', read_file($dryReloadByActivationFile, err_mode => 'quiet') // ""); print STDERR "would restart systemd\n" if $restartSystemd; - print STDERR "would reload the following units: ", join(", ", sort(keys %unitsToReload)), "\n" - if scalar(keys %unitsToReload) > 0; print STDERR "would restart the following units: ", join(", ", sort(keys %unitsToRestart)), "\n" if scalar(keys %unitsToRestart) > 0; - my @unitsToStartFiltered = filterUnits(\%unitsToStart); print STDERR "would start the following units: ", join(", ", @unitsToStartFiltered), "\n" if scalar @unitsToStartFiltered; + print STDERR "would reload the following units: ", join(", ", sort(keys %unitsToReload)), "\n" + if scalar(keys %unitsToReload) > 0; + unlink($dryRestartByActivationFile); + unlink($dryReloadByActivationFile); exit 0; } @@ -451,7 +400,7 @@ sub filterUnits { print STDERR "stopping the following units: ", join(", ", @unitsToStopFiltered), "\n" if scalar @unitsToStopFiltered; # Use current version of systemctl binary before daemon is reexeced. - system("$curSystemd/systemctl", "stop", "--", sort(keys %unitsToStop)); + system("$curSystemd/systemctl", "stop", "--", sort(keys %unitsToStop)); # FIXME: ignore errors? } print STDERR "NOT restarting the following changed units: ", join(", ", sort(keys %unitsToSkip)), "\n" @@ -465,38 +414,12 @@ sub filterUnits { # Handle the activation script requesting the restart or reload of a unit. # We can only restart and reload (not stop/start) because the units to be -# stopped are already stopped before the activation script is run. We do however -# make an exception for services that are socket-activated and that have to be stopped -# instead of being restarted. -my %unitsToAlsoStop; -my %unitsToAlsoSkip; -foreach (split('\n', read_file($restartByActivationFile, err_mode => 'quiet') // "")) { - my $unit = $_; - my $baseUnit = $unit; - my $newUnitFile = "$out/etc/systemd/system/$baseUnit"; - - # Detect template instances. - if (!-e $newUnitFile && $unit =~ /^(.*)@[^\.]*\.(.*)$/) { - $baseUnit = "$1\@.$2"; - $newUnitFile = "$out/etc/systemd/system/$baseUnit"; - } - - my $baseName = $baseUnit; - $baseName =~ s/\.[a-z]*$//; - - handleModifiedUnit($unit, $baseName, $newUnitFile, $activePrev, \%unitsToAlsoStop, \%unitsToStart, \%unitsToReload, \%unitsToRestart, %unitsToAlsoSkip); -} -unlink($restartByActivationFile); - -my @unitsToAlsoStopFiltered = filterUnits(\%unitsToAlsoStop); -if (scalar(keys %unitsToAlsoStop) > 0) { - print STDERR "stopping the following units as well: ", join(", ", @unitsToAlsoStopFiltered), "\n" - if scalar @unitsToAlsoStopFiltered; - system("$curSystemd/systemctl", "stop", "--", sort(keys %unitsToAlsoStop)); -} +# stopped are already stopped before the activation script is run. +$unitsToRestart{$_} = 1 foreach + split('\n', read_file($restartByActivationFile, err_mode => 'quiet') // ""); -print STDERR "NOT restarting the following changed units as well: ", join(", ", sort(keys %unitsToAlsoSkip)), "\n" - if scalar(keys %unitsToAlsoSkip) > 0; +$unitsToReload{$_} = 1 foreach + split('\n', read_file($reloadByActivationFile, err_mode => 'quiet') // ""); # Restart systemd if necessary. Note that this is done using the # current version of systemd, just in case the new one has trouble @@ -537,40 +460,14 @@ sub filterUnits { print STDERR "reloading the following units: ", join(", ", sort(keys %unitsToReload)), "\n"; system("@systemd@/bin/systemctl", "reload", "--", sort(keys %unitsToReload)) == 0 or $res = 4; unlink($reloadListFile); + unlink($reloadByActivationFile); } # Restart changed services (those that have to be restarted rather # than stopped and started). if (scalar(keys %unitsToRestart) > 0) { print STDERR "restarting the following units: ", join(", ", sort(keys %unitsToRestart)), "\n"; - - # We split the units to be restarted into sockets and non-sockets. - # This is because restarting sockets may fail which is not bad by - # itself but which will prevent changes on the sockets. We usually - # restart the socket and stop the service before that. Restarting - # the socket will fail however when the service was re-activated - # in the meantime. There is no proper way to prevent that from happening. - my @unitsWithErrorHandling = grep { $_ !~ /\.socket$/ } sort(keys %unitsToRestart); - my @unitsWithoutErrorHandling = grep { $_ =~ /\.socket$/ } sort(keys %unitsToRestart); - - if (scalar(@unitsWithErrorHandling) > 0) { - system("@systemd@/bin/systemctl", "restart", "--", @unitsWithErrorHandling) == 0 or $res = 4; - } - if (scalar(@unitsWithoutErrorHandling) > 0) { - # Don't print warnings from systemctl - no warnings 'once'; - open(OLDERR, ">&", \*STDERR); - close(STDERR); - - my $ret = system("@systemd@/bin/systemctl", "restart", "--", @unitsWithoutErrorHandling); - - # Print stderr again - open(STDERR, ">&OLDERR"); - - if ($ret ne 0) { - print STDERR "warning: some sockets failed to restart. Please check your journal (journalctl -eb) and act accordingly.\n"; - } - } + system("@systemd@/bin/systemctl", "restart", "--", sort(keys %unitsToRestart)) == 0 or $res = 4; unlink($restartListFile); unlink($restartByActivationFile); } @@ -581,7 +478,6 @@ sub filterUnits { # that are symlinks to other units. We shouldn't start both at the # same time because we'll get a "Failed to add path to set" error from # systemd. -my @unitsToStartFiltered = filterUnits(\%unitsToStart); print STDERR "starting the following units: ", join(", ", @unitsToStartFiltered), "\n" if scalar @unitsToStartFiltered; system("@systemd@/bin/systemctl", "start", "--", sort(keys %unitsToStart)) == 0 or $res = 4; @@ -589,7 +485,7 @@ sub filterUnits { # Print failed and new units. -my (@failed, @new); +my (@failed, @new, @restarting); my $activeNew = getActiveUnits; while (my ($unit, $state) = each %{$activeNew}) { if ($state->{state} eq "failed") { @@ -605,9 +501,7 @@ sub filterUnits { push @failed, $unit; } } - # Ignore scopes since they are not managed by this script but rather - # created and managed by third-party services via the systemd dbus API. - elsif ($state->{state} ne "failed" && !defined $activePrev->{$unit} && $unit !~ /\.scope$/) { + elsif ($state->{state} ne "failed" && !defined $activePrev->{$unit}) { push @new, $unit; } } diff --git a/nixos/modules/system/activation/top-level.nix b/nixos/modules/system/activation/top-level.nix index 68da910d29cc8..026fd1791d33f 100644 --- a/nixos/modules/system/activation/top-level.nix +++ b/nixos/modules/system/activation/top-level.nix @@ -84,13 +84,6 @@ let export localeArchive="${config.i18n.glibcLocales}/lib/locale/locale-archive" substituteAll ${./switch-to-configuration.pl} $out/bin/switch-to-configuration chmod +x $out/bin/switch-to-configuration - ${optionalString (pkgs.stdenv.hostPlatform == pkgs.stdenv.buildPlatform) '' - if ! output=$($perl/bin/perl -c $out/bin/switch-to-configuration 2>&1); then - echo "switch-to-configuration syntax is not valid:" - echo "$output" - exit 1 - fi - ''} echo -n "${toString config.system.extraDependencies}" > $out/extra-dependencies diff --git a/nixos/tests/switch-test.nix b/nixos/tests/switch-test.nix index 7ea07a390b808..78adf7ffa7da5 100644 --- a/nixos/tests/switch-test.nix +++ b/nixos/tests/switch-test.nix @@ -7,224 +7,15 @@ import ./make-test-python.nix ({ pkgs, ...} : { }; nodes = { - machine = { config, pkgs, lib, ... }: { - environment.systemPackages = [ pkgs.socat ]; # for the socket activation stuff + machine = { ... }: { users.mutableUsers = false; - - specialisation = { - # A system with a simple socket-activated unit - simple-socket.configuration = { - systemd.services.socket-activated.serviceConfig = { - ExecStart = pkgs.writeScript "socket-test.py" /* python */ '' - #!${pkgs.python3}/bin/python3 - - from socketserver import TCPServer, StreamRequestHandler - import socket - - class Handler(StreamRequestHandler): - def handle(self): - self.wfile.write("hello".encode("utf-8")) - - class Server(TCPServer): - def __init__(self, server_address, handler_cls): - # Invoke base but omit bind/listen steps (performed by systemd activation!) - TCPServer.__init__( - self, server_address, handler_cls, bind_and_activate=False) - # Override socket - self.socket = socket.fromfd(3, self.address_family, self.socket_type) - - if __name__ == "__main__": - server = Server(("localhost", 1234), Handler) - server.serve_forever() - ''; - }; - systemd.sockets.socket-activated = { - wantedBy = [ "sockets.target" ]; - listenStreams = [ "/run/test.sock" ]; - socketConfig.SocketMode = lib.mkDefault "0777"; - }; - }; - - # The same system but the socket is modified - modified-socket.configuration = { - imports = [ config.specialisation.simple-socket.configuration ]; - systemd.sockets.socket-activated.socketConfig.SocketMode = "0666"; - }; - - # The same system but the service is modified - modified-service.configuration = { - imports = [ config.specialisation.simple-socket.configuration ]; - systemd.services.socket-activated.serviceConfig.X-Test = "test"; - }; - - # The same system but both service and socket are modified - modified-service-and-socket.configuration = { - imports = [ config.specialisation.simple-socket.configuration ]; - systemd.services.socket-activated.serviceConfig.X-Test = "some_value"; - systemd.sockets.socket-activated.socketConfig.SocketMode = "0444"; - }; - - # A system with a socket-activated service and some simple services - service-and-socket.configuration = { - imports = [ config.specialisation.simple-socket.configuration ]; - systemd.services.simple-service = { - wantedBy = [ "multi-user.target" ]; - serviceConfig = { - Type = "oneshot"; - RemainAfterExit = true; - ExecStart = "${pkgs.coreutils}/bin/true"; - }; - }; - - systemd.services.simple-restart-service = { - stopIfChanged = false; - wantedBy = [ "multi-user.target" ]; - serviceConfig = { - Type = "oneshot"; - RemainAfterExit = true; - ExecStart = "${pkgs.coreutils}/bin/true"; - }; - }; - - systemd.services.simple-reload-service = { - reloadIfChanged = true; - wantedBy = [ "multi-user.target" ]; - serviceConfig = { - Type = "oneshot"; - RemainAfterExit = true; - ExecStart = "${pkgs.coreutils}/bin/true"; - ExecReload = "${pkgs.coreutils}/bin/true"; - }; - }; - - systemd.services.no-restart-service = { - restartIfChanged = false; - wantedBy = [ "multi-user.target" ]; - serviceConfig = { - Type = "oneshot"; - RemainAfterExit = true; - ExecStart = "${pkgs.coreutils}/bin/true"; - }; - }; - }; - - # The same system but with an activation script that restarts all services - restart-and-reload-by-activation-script.configuration = { - imports = [ config.specialisation.service-and-socket.configuration ]; - system.activationScripts.restart-and-reload-test = { - supportsDryActivation = true; - deps = []; - text = '' - if [ "$NIXOS_ACTION" = dry-activate ]; then - f=/run/nixos/dry-activation-restart-list - else - f=/run/nixos/activation-restart-list - fi - cat <> "$f" - simple-service.service - simple-restart-service.service - simple-reload-service.service - no-restart-service.service - socket-activated.service - EOF - ''; - }; - }; - - # A system with a timer - with-timer.configuration = { - systemd.timers.test-timer = { - wantedBy = [ "timers.target" ]; - timerConfig.OnCalendar = "@1395716396"; # chosen by fair dice roll - }; - systemd.services.test-timer = { - serviceConfig = { - Type = "oneshot"; - ExecStart = "${pkgs.coreutils}/bin/true"; - }; - }; - }; - - # The same system but with another time - with-timer-modified.configuration = { - imports = [ config.specialisation.with-timer.configuration ]; - systemd.timers.test-timer.timerConfig.OnCalendar = lib.mkForce "Fri 2012-11-23 16:00:00"; - }; - - # A system with a systemd mount - with-mount.configuration = { - systemd.mounts = [ - { - description = "Testmount"; - what = "tmpfs"; - type = "tmpfs"; - where = "/testmount"; - options = "size=1M"; - wantedBy = [ "local-fs.target" ]; - } - ]; - }; - - # The same system but with another time - with-mount-modified.configuration = { - systemd.mounts = [ - { - description = "Testmount"; - what = "tmpfs"; - type = "tmpfs"; - where = "/testmount"; - options = "size=10M"; - wantedBy = [ "local-fs.target" ]; - } - ]; - }; - - # A system with a path unit - with-path.configuration = { - systemd.paths.test-watch = { - wantedBy = [ "paths.target" ]; - pathConfig.PathExists = "/testpath"; - }; - systemd.services.test-watch = { - serviceConfig = { - Type = "oneshot"; - ExecStart = "${pkgs.coreutils}/bin/touch /testpath-modified"; - }; - }; - }; - - # The same system but watching another file - with-path-modified.configuration = { - imports = [ config.specialisation.with-path.configuration ]; - systemd.paths.test-watch.pathConfig.PathExists = lib.mkForce "/testpath2"; - }; - - # A system with a slice - with-slice.configuration = { - systemd.slices.testslice.sliceConfig.MemoryMax = "1"; # don't allow memory allocation - systemd.services.testservice = { - serviceConfig = { - Type = "oneshot"; - RemainAfterExit = true; - ExecStart = "${pkgs.coreutils}/bin/true"; - Slice = "testslice.slice"; - }; - }; - }; - - # The same system but the slice allows to allocate memory - with-slice-non-crashing.configuration = { - imports = [ config.specialisation.with-slice.configuration ]; - systemd.slices.testslice.sliceConfig.MemoryMax = lib.mkForce null; - }; - }; }; other = { ... }: { users.mutableUsers = true; }; }; - testScript = { nodes, ... }: let + testScript = {nodes, ...}: let originalSystem = nodes.machine.config.system.build.toplevel; otherSystem = nodes.other.config.system.build.toplevel; @@ -236,183 +27,12 @@ import ./make-test-python.nix ({ pkgs, ...} : { set -o pipefail exec env -i "$@" | tee /dev/stderr ''; - in /* python */ '' - def switch_to_specialisation(name, action="test"): - out = machine.succeed(f"${originalSystem}/specialisation/{name}/bin/switch-to-configuration {action} 2>&1") - assert_lacks(out, "switch-to-configuration line") # Perl warnings - return out - - def assert_contains(haystack, needle): - if needle not in haystack: - print("The haystack that will cause the following exception is:") - print("---") - print(haystack) - print("---") - raise Exception(f"Expected string '{needle}' was not found") - - def assert_lacks(haystack, needle): - if needle in haystack: - print("The haystack that will cause the following exception is:") - print("---") - print(haystack, end="") - print("---") - raise Exception(f"Unexpected string '{needle}' was found") - - + in '' machine.succeed( "${stderrRunner} ${originalSystem}/bin/switch-to-configuration test" ) machine.succeed( "${stderrRunner} ${otherSystem}/bin/switch-to-configuration test" ) - - with subtest("systemd sockets"): - machine.succeed("${originalSystem}/bin/switch-to-configuration test") - - # Simple socket is created - out = switch_to_specialisation("simple-socket") - assert_lacks(out, "stopping the following units:") - # not checking for reload because dbus gets reloaded - assert_lacks(out, "restarting the following units:") - assert_lacks(out, "\nstarting the following units:") - assert_contains(out, "the following new units were started: socket-activated.socket\n") - assert_lacks(out, "as well:") - machine.succeed("[ $(stat -c%a /run/test.sock) = 777 ]") - - # Changing the socket restarts it - out = switch_to_specialisation("modified-socket") - assert_lacks(out, "stopping the following units:") - #assert_lacks(out, "reloading the following units:") - assert_contains(out, "restarting the following units: socket-activated.socket\n") - assert_lacks(out, "\nstarting the following units:") - assert_lacks(out, "the following new units were started:") - assert_lacks(out, "as well:") - machine.succeed("[ $(stat -c%a /run/test.sock) = 666 ]") # change was applied - - # The unit is properly activated when the socket is accessed - if machine.succeed("socat - UNIX-CONNECT:/run/test.sock") != "hello": - raise Exception("Socket was not properly activated") - - # Changing the socket restarts it and ignores the active service - out = switch_to_specialisation("simple-socket") - assert_contains(out, "stopping the following units: socket-activated.service\n") - assert_lacks(out, "reloading the following units:") - assert_contains(out, "restarting the following units: socket-activated.socket\n") - assert_lacks(out, "\nstarting the following units:") - assert_lacks(out, "the following new units were started:") - assert_lacks(out, "as well:") - machine.succeed("[ $(stat -c%a /run/test.sock) = 777 ]") # change was applied - - # Changing the service does nothing when the service is not active - out = switch_to_specialisation("modified-service") - assert_lacks(out, "stopping the following units:") - assert_lacks(out, "reloading the following units:") - assert_lacks(out, "restarting the following units:") - assert_lacks(out, "\nstarting the following units:") - assert_lacks(out, "the following new units were started:") - assert_lacks(out, "as well:") - - # Activating the service and modifying it stops it but leaves the socket untouched - machine.succeed("socat - UNIX-CONNECT:/run/test.sock") - out = switch_to_specialisation("simple-socket") - assert_contains(out, "stopping the following units: socket-activated.service\n") - assert_lacks(out, "reloading the following units:") - assert_lacks(out, "restarting the following units:") - assert_lacks(out, "\nstarting the following units:") - assert_lacks(out, "the following new units were started:") - assert_lacks(out, "as well:") - - # Activating the service and both the service and the socket stops the service and restarts the socket - machine.succeed("socat - UNIX-CONNECT:/run/test.sock") - out = switch_to_specialisation("modified-service-and-socket") - assert_contains(out, "stopping the following units: socket-activated.service\n") - assert_lacks(out, "reloading the following units:") - assert_contains(out, "restarting the following units: socket-activated.socket\n") - assert_lacks(out, "\nstarting the following units:") - assert_lacks(out, "the following new units were started:") - assert_lacks(out, "as well:") - - with subtest("restart and reload by activation file"): - out = switch_to_specialisation("service-and-socket") - # Switch to a system where the example services get restarted - # by the activation script - out = switch_to_specialisation("restart-and-reload-by-activation-script") - assert_lacks(out, "stopping the following units:") - assert_contains(out, "stopping the following units as well: simple-service.service, socket-activated.service\n") - assert_contains(out, "reloading the following units: simple-reload-service.service\n") - assert_contains(out, "restarting the following units: simple-restart-service.service\n") - assert_contains(out, "\nstarting the following units: simple-service.service") - - # The same, but in dry mode - switch_to_specialisation("service-and-socket") - out = switch_to_specialisation("restart-and-reload-by-activation-script", action="dry-activate") - assert_lacks(out, "would stop the following units:") - assert_contains(out, "would stop the following units as well: simple-service.service, socket-activated.service\n") - assert_contains(out, "would reload the following units: simple-reload-service.service\n") - assert_contains(out, "would restart the following units: simple-restart-service.service\n") - assert_contains(out, "\nwould start the following units: simple-service.service") - - with subtest("mounts"): - switch_to_specialisation("with-mount") - out = machine.succeed("mount | grep 'on /testmount'") - assert_contains(out, "size=1024k") - - out = switch_to_specialisation("with-mount-modified") - assert_lacks(out, "stopping the following units:") - assert_contains(out, "reloading the following units: testmount.mount\n") - assert_lacks(out, "restarting the following units:") - assert_lacks(out, "\nstarting the following units:") - assert_lacks(out, "the following new units were started:") - assert_lacks(out, "as well:") - # It changed - out = machine.succeed("mount | grep 'on /testmount'") - assert_contains(out, "size=10240k") - - with subtest("timers"): - switch_to_specialisation("with-timer") - out = machine.succeed("systemctl show test-timer.timer") - assert_contains(out, "OnCalendar=2014-03-25 02:59:56 UTC") - - out = switch_to_specialisation("with-timer-modified") - assert_lacks(out, "stopping the following units:") - assert_lacks(out, "reloading the following units:") - assert_contains(out, "restarting the following units: test-timer.timer\n") - assert_lacks(out, "\nstarting the following units:") - assert_lacks(out, "the following new units were started:") - assert_lacks(out, "as well:") - # It changed - out = machine.succeed("systemctl show test-timer.timer") - assert_contains(out, "OnCalendar=Fri 2012-11-23 16:00:00") - - with subtest("paths"): - switch_to_specialisation("with-path") - machine.fail("test -f /testpath-modified") - - # touch the file, unit should be triggered - machine.succeed("touch /testpath") - machine.wait_until_succeeds("test -f /testpath-modified") - - machine.succeed("rm /testpath") - machine.succeed("rm /testpath-modified") - switch_to_specialisation("with-path-modified") - - machine.succeed("touch /testpath") - machine.fail("test -f /testpath-modified") - machine.succeed("touch /testpath2") - machine.wait_until_succeeds("test -f /testpath-modified") - - # This test ensures that changes to slice configuration get applied. - # We test this by having a slice that allows no memory allocation at - # all and starting a service within it. If the service crashes, the slice - # is applied and if we modify the slice to allow memory allocation, the - # service should successfully start. - with subtest("slices"): - machine.succeed("echo 0 > /proc/sys/vm/panic_on_oom") # allow OOMing - out = switch_to_specialisation("with-slice") - machine.fail("systemctl start testservice.service") - out = switch_to_specialisation("with-slice-non-crashing") - machine.succeed("systemctl start testservice.service") - machine.succeed("echo 1 > /proc/sys/vm/panic_on_oom") # disallow OOMing - ''; })