From cfad5e3403de1037ee5b8fd27b814f29fbd64790 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Janne=20He=C3=9F?= Date: Sun, 10 Oct 2021 17:42:23 +0200 Subject: [PATCH] nixos/switch-to-configuration: Improve socket support This commit changes a lot more that you'd expect but it also adds a lot of new testing code so nothing breaks in the future. The main change is that sockets are now restarted when they change. The main reason for the large amount of changes is the ability of activation scripts to restart/reload units. This also works for socket-activated units now, and honors reloadIfChanged and restartIfChanged. The two changes don't really work without each other so they are done in the one large commit. The test should show what works now and ensure it will continue to do so in the future. --- .../from_md/release-notes/rl-2111.section.xml | 9 + .../manual/release-notes/rl-2111.section.md | 2 + .../activation/switch-to-configuration.pl | 241 ++++++++++++------ nixos/tests/switch-test.nix | 235 ++++++++++++++++- 4 files changed, 408 insertions(+), 79 deletions(-) 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 023f0f57a9b2..d3046b88bfec 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 @@ -1554,6 +1554,15 @@ 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. + + diff --git a/nixos/doc/manual/release-notes/rl-2111.section.md b/nixos/doc/manual/release-notes/rl-2111.section.md index 658f3b59da67..e84ca7ed8cc9 100644 --- a/nixos/doc/manual/release-notes/rl-2111.section.md +++ b/nixos/doc/manual/release-notes/rl-2111.section.md @@ -449,3 +449,5 @@ In addition to numerous new and upgraded packages, this release has the followin - The `networking` module has a new `networking.fooOverUDP` option to configure Foo-over-UDP encapsulations. - `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. diff --git a/nixos/modules/system/activation/switch-to-configuration.pl b/nixos/modules/system/activation/switch-to-configuration.pl index 027e050511fb..ced49773d1ed 100644 --- a/nixos/modules/system/activation/switch-to-configuration.pl +++ b/nixos/modules/system/activation/switch-to-configuration.pl @@ -19,11 +19,14 @@ my $startListFile = "/run/nixos/start-list"; my $restartListFile = "/run/nixos/restart-list"; my $reloadListFile = "/run/nixos/reload-list"; -# Parse restart/reload requests by the activation script +# 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. 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) }); @@ -147,6 +150,87 @@ 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") { + # 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); + } elsif ($unit =~ /\.slice$/ || $unit =~ /\.path$/) { + # 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 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 (defined $activePrev->{$socket}) { + # Only restart sockets that actually + # exist in new configuration + if (-e "$out/etc/systemd/system/$socket") { + $socketActivated = 1; + $unitsToStop->{$unit} = 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) { + # 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 { + if (!boolIsTrue($unitInfo->{'X-StopIfChanged'} // "yes")) { + # 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); @@ -219,65 +303,7 @@ while (my ($unit, $state) = each %{$activePrev}) { } elsif (fingerprintUnit($prevUnitFile) ne fingerprintUnit($newUnitFile)) { - 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; - } - } - } + handleModifiedUnit($unit, $baseName, $newUnitFile, $activePrev, \%unitsToStop, \%unitsToStart, \%unitsToReload, \%unitsToRestart, %unitsToSkip); } } } @@ -362,8 +388,6 @@ sub filterUnits { } my @unitsToStopFiltered = filterUnits(\%unitsToStop); -my @unitsToStartFiltered = filterUnits(\%unitsToStart); - # Show dry-run actions. if ($action eq "dry-activate") { @@ -375,21 +399,45 @@ if ($action eq "dry-activate") { print STDERR "would activate the configuration...\n"; system("$out/dry-activate", "$out"); - $unitsToRestart{$_} = 1 foreach - split('\n', read_file($dryRestartByActivationFile, err_mode => 'quiet') // ""); + # 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"; - $unitsToReload{$_} = 1 foreach - split('\n', read_file($dryReloadByActivationFile, err_mode => 'quiet') // ""); + # 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($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 "NOT restarting the following changed units as well: ", join(", ", sort(keys %unitsToAlsoSkip)), "\n" + if scalar(keys %unitsToAlsoSkip) > 0; print STDERR "would restart systemd\n" if $restartSystemd; 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; } @@ -414,12 +462,38 @@ system("$out/activate", "$out") == 0 or $res = 2; # 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. -$unitsToRestart{$_} = 1 foreach - split('\n', read_file($restartByActivationFile, err_mode => 'quiet') // ""); +# 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"; -$unitsToReload{$_} = 1 foreach - split('\n', read_file($reloadByActivationFile, err_mode => 'quiet') // ""); + # 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)); +} + +print STDERR "NOT restarting the following changed units as well: ", join(", ", sort(keys %unitsToAlsoSkip)), "\n" + if scalar(keys %unitsToAlsoSkip) > 0; # Restart systemd if necessary. Note that this is done using the # current version of systemd, just in case the new one has trouble @@ -460,14 +534,28 @@ if (scalar(keys %unitsToReload) > 0) { 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"; - system("@systemd@/bin/systemctl", "restart", "--", sort(keys %unitsToRestart)) == 0 or $res = 4; + + # 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) { + system("@systemd@/bin/systemctl", "restart", "--", @unitsWithoutErrorHandling); + } unlink($restartListFile); unlink($restartByActivationFile); } @@ -478,6 +566,7 @@ if (scalar(keys %unitsToRestart) > 0) { # 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; diff --git a/nixos/tests/switch-test.nix b/nixos/tests/switch-test.nix index 78adf7ffa7da..76a9ef6f624e 100644 --- a/nixos/tests/switch-test.nix +++ b/nixos/tests/switch-test.nix @@ -7,15 +7,135 @@ import ./make-test-python.nix ({ pkgs, ...} : { }; nodes = { - machine = { ... }: { + machine = { config, pkgs, lib, ... }: { + environment.systemPackages = [ pkgs.socat ]; # for the socket activation stuff 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"; + }; + }; + }; + 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 + ''; + }; + }; + }; }; other = { ... }: { users.mutableUsers = true; }; }; - testScript = {nodes, ...}: let + testScript = { nodes, ... }: let originalSystem = nodes.machine.config.system.build.toplevel; otherSystem = nodes.other.config.system.build.toplevel; @@ -27,12 +147,121 @@ import ./make-test-python.nix ({ pkgs, ...} : { set -o pipefail exec env -i "$@" | tee /dev/stderr ''; - in '' + 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") + + 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") + ''; })