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")
+
'';
})