···118Hence [garbage collection](#sec-nix-gc) will remove that file and you
119will wind up with a broken symlink in your systemd configuration, which
120in turn will not make the service / timer start on login.
000000000000000000000000000000
···118Hence [garbage collection](#sec-nix-gc) will remove that file and you
119will wind up with a broken symlink in your systemd configuration, which
120in turn will not make the service / timer start on login.
121+122+## Template units {#sect-nixos-systemd-template-units}
123+124+systemd supports templated units where a base unit can be started multiple
125+times with a different parameter. The syntax to accomplish this is
126+`service-name@instance-name.service`. Units get the instance name passed to
127+them (see `systemd.unit(5)`). NixOS has support for these kinds of units and
128+for template-specific overrides. A service needs to be defined twice, once
129+for the base unit and once for the instance. All instances must include
130+`overrideStrategy = "asDropin"` for the change detection to work. This
131+example illustrates this:
132+```nix
133+{
134+ systemd.services = {
135+ "base-unit@".serviceConfig = {
136+ ExecStart = "...";
137+ User = "...";
138+ };
139+ "base-unit@instance-a" = {
140+ overrideStrategy = "asDropin"; # needed for templates to work
141+ wantedBy = [ "multi-user.target" ]; # causes NixOS to manage the instance
142+ };
143+ "base-unit@instance-b" = {
144+ overrideStrategy = "asDropin"; # needed for templates to work
145+ wantedBy = [ "multi-user.target" ]; # causes NixOS to manage the instance
146+ serviceConfig.User = "root"; # also override something for this specific instance
147+ };
148+ };
149+}
150+```
···253# If a directory with the same basename ending in .d exists next to the unit file, it will be
254# assumed to contain override files which will be parsed as well and handled properly.
255sub parse_unit {
256- my ($unit_path) = @_;
257258 # Parse the main unit and all overrides
259 my %unit_data;
260 # Replace \ with \\ so glob() still works with units that have a \ in them
261 # Valid characters in unit names are ASCII letters, digits, ":", "-", "_", ".", and "\"
0262 $unit_path =~ s/\\/\\\\/gmsx;
263- foreach (glob("${unit_path}{,.d/*.conf}")) {
0264 parse_systemd_ini(\%unit_data, "$_")
000000265 }
266 return %unit_data;
267}
···423# Called when a unit exists in both the old systemd and the new system and the units
424# differ. This figures out of what units are to be stopped, restarted, reloaded, started, and skipped.
425sub handle_modified_unit { ## no critic(Subroutines::ProhibitManyArgs, Subroutines::ProhibitExcessComplexity)
426- my ($unit, $base_name, $new_unit_file, $new_unit_info, $active_cur, $units_to_stop, $units_to_start, $units_to_reload, $units_to_restart, $units_to_skip) = @_;
427428 if ($unit eq "sysinit.target" || $unit eq "basic.target" || $unit eq "multi-user.target" || $unit eq "graphical.target" || $unit =~ /\.path$/msx || $unit =~ /\.slice$/msx) {
429 # Do nothing. These cannot be restarted directly.
···442 # Revert of the attempt: https://github.com/NixOS/nixpkgs/pull/147609
443 # More details: https://github.com/NixOS/nixpkgs/issues/74899#issuecomment-981142430
444 } else {
445- my %new_unit_info = $new_unit_info ? %{$new_unit_info} : parse_unit($new_unit_file);
446 if (parse_systemd_bool(\%new_unit_info, "Service", "X-ReloadIfChanged", 0) and not $units_to_restart->{$unit} and not $units_to_stop->{$unit}) {
447 $units_to_reload->{$unit} = 1;
448 record_unit($reload_list_file, $unit);
···538539my $active_cur = get_active_units();
540while (my ($unit, $state) = each(%{$active_cur})) {
000541 my $base_unit = $unit;
542-543- my $cur_unit_file = "/etc/systemd/system/$base_unit";
544- my $new_unit_file = "$toplevel/etc/systemd/system/$base_unit";
545546 # Detect template instances.
547 if (!-e $cur_unit_file && !-e $new_unit_file && $unit =~ /^(.*)@[^\.]*\.(.*)$/msx) {
548 $base_unit = "$1\@.$2";
549- $cur_unit_file = "/etc/systemd/system/$base_unit";
550- $new_unit_file = "$toplevel/etc/systemd/system/$base_unit";
551 }
552553 my $base_name = $base_unit;
554 $base_name =~ s/\.[[:lower:]]*$//msx;
555556- if (-e $cur_unit_file && ($state->{state} eq "active" || $state->{state} eq "activating")) {
557- if (! -e $new_unit_file || abs_path($new_unit_file) eq "/dev/null") {
558- my %cur_unit_info = parse_unit($cur_unit_file);
559 if (parse_systemd_bool(\%cur_unit_info, "Unit", "X-StopOnRemoval", 1)) {
560 $units_to_stop{$unit} = 1;
561 }
562 }
563564 elsif ($unit =~ /\.target$/msx) {
565- my %new_unit_info = parse_unit($new_unit_file);
566567 # Cause all active target units to be restarted below.
568 # This should start most changed units we stop here as
···596 }
597598 else {
599- my %cur_unit_info = parse_unit($cur_unit_file);
600- my %new_unit_info = parse_unit($new_unit_file);
601 my $diff = compare_units(\%cur_unit_info, \%new_unit_info);
602 if ($diff == 1) {
603- handle_modified_unit($unit, $base_name, $new_unit_file, \%new_unit_info, $active_cur, \%units_to_stop, \%units_to_start, \%units_to_reload, \%units_to_restart, \%units_to_skip);
604 } elsif ($diff == 2 and not $units_to_restart{$unit}) {
605 $units_to_reload{$unit} = 1;
606 record_unit($reload_list_file, $unit);
···710 # Handle the activation script requesting the restart or reload of a unit.
711 foreach (split(/\n/msx, read_file($dry_restart_by_activation_file, err_mode => "quiet") // "")) {
712 my $unit = $_;
0713 my $base_unit = $unit;
714- my $new_unit_file = "$toplevel/etc/systemd/system/$base_unit";
715716 # Detect template instances.
717 if (!-e $new_unit_file && $unit =~ /^(.*)@[^\.]*\.(.*)$/msx) {
718 $base_unit = "$1\@.$2";
719- $new_unit_file = "$toplevel/etc/systemd/system/$base_unit";
720 }
721722 my $base_name = $base_unit;
···728 next;
729 }
730731- handle_modified_unit($unit, $base_name, $new_unit_file, undef, $active_cur, \%units_to_restart, \%units_to_restart, \%units_to_reload, \%units_to_restart, \%units_to_skip);
732 }
733 unlink($dry_restart_by_activation_file);
734···782# Handle the activation script requesting the restart or reload of a unit.
783foreach (split(/\n/msx, read_file($restart_by_activation_file, err_mode => "quiet") // "")) {
784 my $unit = $_;
0785 my $base_unit = $unit;
786- my $new_unit_file = "$toplevel/etc/systemd/system/$base_unit";
787788 # Detect template instances.
789 if (!-e $new_unit_file && $unit =~ /^(.*)@[^\.]*\.(.*)$/msx) {
790 $base_unit = "$1\@.$2";
791- $new_unit_file = "$toplevel/etc/systemd/system/$base_unit";
792 }
793794 my $base_name = $base_unit;
···801 next;
802 }
803804- handle_modified_unit($unit, $base_name, $new_unit_file, undef, $active_cur, \%units_to_restart, \%units_to_restart, \%units_to_reload, \%units_to_restart, \%units_to_skip);
805}
806# We can remove the file now because it has been propagated to the other restart/reload files
807unlink($restart_by_activation_file);
···859 for my $unit (keys(%units_to_reload)) {
860 if (!unit_is_active($unit)) {
861 # Figure out if we need to start the unit
862- my %unit_info = parse_unit("$toplevel/etc/systemd/system/$unit");
863 if (!(parse_systemd_bool(\%unit_info, "Unit", "RefuseManualStart", 0) || parse_systemd_bool(\%unit_info, "Unit", "X-OnlyManualStart", 0))) {
864 $units_to_start{$unit} = 1;
865 record_unit($start_list_file, $unit);
···253# If a directory with the same basename ending in .d exists next to the unit file, it will be
254# assumed to contain override files which will be parsed as well and handled properly.
255sub parse_unit {
256+ my ($unit_path, $base_unit_path) = @_;
257258 # Parse the main unit and all overrides
259 my %unit_data;
260 # Replace \ with \\ so glob() still works with units that have a \ in them
261 # Valid characters in unit names are ASCII letters, digits, ":", "-", "_", ".", and "\"
262+ $base_unit_path =~ s/\\/\\\\/gmsx;
263 $unit_path =~ s/\\/\\\\/gmsx;
264+265+ foreach (glob("${base_unit_path}{,.d/*.conf}")) {
266 parse_systemd_ini(\%unit_data, "$_")
267+ }
268+ # Handle drop-in template-unit instance overrides
269+ if ($unit_path ne $base_unit_path) {
270+ foreach (glob("${unit_path}.d/*.conf")) {
271+ parse_systemd_ini(\%unit_data, "$_")
272+ }
273 }
274 return %unit_data;
275}
···431# Called when a unit exists in both the old systemd and the new system and the units
432# differ. This figures out of what units are to be stopped, restarted, reloaded, started, and skipped.
433sub handle_modified_unit { ## no critic(Subroutines::ProhibitManyArgs, Subroutines::ProhibitExcessComplexity)
434+ my ($unit, $base_name, $new_unit_file, $new_base_unit_file, $new_unit_info, $active_cur, $units_to_stop, $units_to_start, $units_to_reload, $units_to_restart, $units_to_skip) = @_;
435436 if ($unit eq "sysinit.target" || $unit eq "basic.target" || $unit eq "multi-user.target" || $unit eq "graphical.target" || $unit =~ /\.path$/msx || $unit =~ /\.slice$/msx) {
437 # Do nothing. These cannot be restarted directly.
···450 # Revert of the attempt: https://github.com/NixOS/nixpkgs/pull/147609
451 # More details: https://github.com/NixOS/nixpkgs/issues/74899#issuecomment-981142430
452 } else {
453+ my %new_unit_info = $new_unit_info ? %{$new_unit_info} : parse_unit($new_unit_file, $new_base_unit_file);
454 if (parse_systemd_bool(\%new_unit_info, "Service", "X-ReloadIfChanged", 0) and not $units_to_restart->{$unit} and not $units_to_stop->{$unit}) {
455 $units_to_reload->{$unit} = 1;
456 record_unit($reload_list_file, $unit);
···546547my $active_cur = get_active_units();
548while (my ($unit, $state) = each(%{$active_cur})) {
549+ my $cur_unit_file = "/etc/systemd/system/$unit";
550+ my $new_unit_file = "$toplevel/etc/systemd/system/$unit";
551+552 my $base_unit = $unit;
553+ my $cur_base_unit_file = $cur_unit_file;
554+ my $new_base_unit_file = $new_unit_file;
0555556 # Detect template instances.
557 if (!-e $cur_unit_file && !-e $new_unit_file && $unit =~ /^(.*)@[^\.]*\.(.*)$/msx) {
558 $base_unit = "$1\@.$2";
559+ $cur_base_unit_file = "/etc/systemd/system/$base_unit";
560+ $new_base_unit_file = "$toplevel/etc/systemd/system/$base_unit";
561 }
562563 my $base_name = $base_unit;
564 $base_name =~ s/\.[[:lower:]]*$//msx;
565566+ if (-e $cur_base_unit_file && ($state->{state} eq "active" || $state->{state} eq "activating")) {
567+ if (! -e $new_base_unit_file || abs_path($new_base_unit_file) eq "/dev/null") {
568+ my %cur_unit_info = parse_unit($cur_unit_file, $cur_base_unit_file);
569 if (parse_systemd_bool(\%cur_unit_info, "Unit", "X-StopOnRemoval", 1)) {
570 $units_to_stop{$unit} = 1;
571 }
572 }
573574 elsif ($unit =~ /\.target$/msx) {
575+ my %new_unit_info = parse_unit($new_unit_file, $new_base_unit_file);
576577 # Cause all active target units to be restarted below.
578 # This should start most changed units we stop here as
···606 }
607608 else {
609+ my %cur_unit_info = parse_unit($cur_unit_file, $cur_base_unit_file);
610+ my %new_unit_info = parse_unit($new_unit_file, $new_base_unit_file);
611 my $diff = compare_units(\%cur_unit_info, \%new_unit_info);
612 if ($diff == 1) {
613+ handle_modified_unit($unit, $base_name, $new_unit_file, $new_base_unit_file, \%new_unit_info, $active_cur, \%units_to_stop, \%units_to_start, \%units_to_reload, \%units_to_restart, \%units_to_skip);
614 } elsif ($diff == 2 and not $units_to_restart{$unit}) {
615 $units_to_reload{$unit} = 1;
616 record_unit($reload_list_file, $unit);
···720 # Handle the activation script requesting the restart or reload of a unit.
721 foreach (split(/\n/msx, read_file($dry_restart_by_activation_file, err_mode => "quiet") // "")) {
722 my $unit = $_;
723+ my $new_unit_file = "$toplevel/etc/systemd/system/$unit";
724 my $base_unit = $unit;
725+ my $new_base_unit_file = $new_unit_file;
726727 # Detect template instances.
728 if (!-e $new_unit_file && $unit =~ /^(.*)@[^\.]*\.(.*)$/msx) {
729 $base_unit = "$1\@.$2";
730+ $new_base_unit_file = "$toplevel/etc/systemd/system/$base_unit";
731 }
732733 my $base_name = $base_unit;
···739 next;
740 }
741742+ handle_modified_unit($unit, $base_name, $new_unit_file, $new_base_unit_file, undef, $active_cur, \%units_to_restart, \%units_to_restart, \%units_to_reload, \%units_to_restart, \%units_to_skip);
743 }
744 unlink($dry_restart_by_activation_file);
745···793# Handle the activation script requesting the restart or reload of a unit.
794foreach (split(/\n/msx, read_file($restart_by_activation_file, err_mode => "quiet") // "")) {
795 my $unit = $_;
796+ my $new_unit_file = "$toplevel/etc/systemd/system/$unit";
797 my $base_unit = $unit;
798+ my $new_base_unit_file = $new_unit_file;
799800 # Detect template instances.
801 if (!-e $new_unit_file && $unit =~ /^(.*)@[^\.]*\.(.*)$/msx) {
802 $base_unit = "$1\@.$2";
803+ $new_base_unit_file = "$toplevel/etc/systemd/system/$base_unit";
804 }
805806 my $base_name = $base_unit;
···813 next;
814 }
815816+ handle_modified_unit($unit, $base_name, $new_unit_file, $new_base_unit_file, undef, $active_cur, \%units_to_restart, \%units_to_restart, \%units_to_reload, \%units_to_restart, \%units_to_skip);
817}
818# We can remove the file now because it has been propagated to the other restart/reload files
819unlink($restart_by_activation_file);
···871 for my $unit (keys(%units_to_reload)) {
872 if (!unit_is_active($unit)) {
873 # Figure out if we need to start the unit
874+ my %unit_info = parse_unit("$toplevel/etc/systemd/system/$unit", "$toplevel/etc/systemd/system/$unit");
875 if (!(parse_systemd_bool(\%unit_info, "Unit", "RefuseManualStart", 0) || parse_systemd_bool(\%unit_info, "Unit", "X-OnlyManualStart", 0))) {
876 $units_to_start{$unit} = 1;
877 record_unit($start_list_file, $unit);
+153-9
nixos/tests/switch-test.nix
···1# Test configuration switching.
23-import ./make-test-python.nix ({ pkgs, ...} : let
45 # Simple service that can either be socket-activated or that will
6 # listen on port 1234 if not socket-activated.
···279 systemd.services.test-service.unitConfig.RefuseManualStart = true;
280 };
2810000000000000000000000282 restart-and-reload-by-activation-script.configuration = {
283 systemd.services = rec {
284 simple-service = {
···290 ExecReload = "${pkgs.coreutils}/bin/true";
291 };
292 };
00293294 simple-restart-service = simple-service // {
295 stopIfChanged = false;
296 };
00297298 simple-reload-service = simple-service // {
299 reloadIfChanged = true;
300 };
00301302 no-restart-service = simple-service // {
303 restartIfChanged = false;
304 };
00305306 reload-triggers = simple-service // {
307 wantedBy = [ "multi-user.target" ];
308 };
00000309310 reload-triggers-and-restart-by-as = simple-service;
00311312 reload-triggers-and-restart = simple-service // {
313 stopIfChanged = false; # easier to check for this
314 wantedBy = [ "multi-user.target" ];
315 };
000000316 };
317318 system.activationScripts.restart-and-reload-test = {
···332 simple-reload-service.service
333 no-restart-service.service
334 reload-triggers-and-restart-by-as.service
00000335 EOF
336337 cat <<EOF >> "$g"
338 reload-triggers.service
339 reload-triggers-and-restart-by-as.service
340 reload-triggers-and-restart.service
000341 EOF
342 '';
343 };
···346 restart-and-reload-by-activation-script-modified.configuration = {
347 imports = [ restart-and-reload-by-activation-script.configuration ];
348 systemd.services.reload-triggers-and-restart.serviceConfig.X-Modified = "test";
0000349 };
350351 simple-socket.configuration = {
···507 set -o pipefail
508 exec env -i "$@" | tee /dev/stderr
509 '';
0000510 in /* python */ ''
511 def switch_to_specialisation(system, name, action="test", fail=False):
512 if name == "":
···733 assert_contains(out, "\nstarting the following units: required-service.service\n")
734 assert_lacks(out, "the following new units were started:")
7350000000000736 with subtest("failing units"):
737 # Let the simple service fail
738 switch_to_specialisation("${machine}", "simpleServiceModified")
···896 assert_lacks(out, "NOT restarting the following changed units:")
897 assert_lacks(out, "reloading the following units:")
898 assert_lacks(out, "restarting the following units:")
899- assert_contains(out, "\nstarting the following units: no-restart-service.service, reload-triggers-and-restart-by-as.service, simple-reload-service.service, simple-restart-service.service, simple-service.service\n")
900- assert_contains(out, "the following new units were started: no-restart-service.service, reload-triggers-and-restart-by-as.service, reload-triggers-and-restart.service, reload-triggers.service, simple-reload-service.service, simple-restart-service.service, simple-service.service\n")
000000000000000000000000000000000901 # Switch to the same system where the example services get restarted
902 # and reloaded by the activation script
903 out = switch_to_specialisation("${machine}", "restart-and-reload-by-activation-script")
904 assert_lacks(out, "stopping the following units:")
905 assert_lacks(out, "NOT restarting the following changed units:")
906- assert_contains(out, "reloading the following units: reload-triggers-and-restart.service, reload-triggers.service, simple-reload-service.service\n")
907- assert_contains(out, "restarting the following units: reload-triggers-and-restart-by-as.service, simple-restart-service.service, simple-service.service\n")
00000000000000908 assert_lacks(out, "\nstarting the following units:")
909 assert_lacks(out, "the following new units were started:")
910 # Switch to the same system and see if the service gets restarted when it's modified
···912 out = switch_to_specialisation("${machine}", "restart-and-reload-by-activation-script-modified")
913 assert_lacks(out, "stopping the following units:")
914 assert_lacks(out, "NOT restarting the following changed units:")
915- assert_contains(out, "reloading the following units: reload-triggers.service, simple-reload-service.service\n")
916- assert_contains(out, "restarting the following units: reload-triggers-and-restart-by-as.service, reload-triggers-and-restart.service, simple-restart-service.service, simple-service.service\n")
00000000000000917 assert_lacks(out, "\nstarting the following units:")
918 assert_lacks(out, "the following new units were started:")
919 # The same, but in dry mode
920 out = switch_to_specialisation("${machine}", "restart-and-reload-by-activation-script", action="dry-activate")
921 assert_lacks(out, "would stop the following units:")
922 assert_lacks(out, "would NOT stop the following changed units:")
923- assert_contains(out, "would reload the following units: reload-triggers.service, simple-reload-service.service\n")
924- assert_contains(out, "would restart the following units: reload-triggers-and-restart-by-as.service, reload-triggers-and-restart.service, simple-restart-service.service, simple-service.service\n")
00000000000000925 assert_lacks(out, "\nwould start the following units:")
926927 with subtest("socket-activated services"):
···1# Test configuration switching.
23+import ./make-test-python.nix ({ lib, pkgs, ...} : let
45 # Simple service that can either be socket-activated or that will
6 # listen on port 1234 if not socket-activated.
···279 systemd.services.test-service.unitConfig.RefuseManualStart = true;
280 };
281282+ unitWithTemplate.configuration = {
283+ systemd.services."instantiated@".serviceConfig = {
284+ Type = "oneshot";
285+ RemainAfterExit = true;
286+ ExecStart = "${pkgs.coreutils}/bin/true";
287+ ExecReload = "${pkgs.coreutils}/bin/true";
288+ };
289+ systemd.services."instantiated@one" = {
290+ wantedBy = [ "multi-user.target" ];
291+ overrideStrategy = "asDropin";
292+ };
293+ systemd.services."instantiated@two" = {
294+ wantedBy = [ "multi-user.target" ];
295+ overrideStrategy = "asDropin";
296+ };
297+ };
298+299+ unitWithTemplateModified.configuration = {
300+ imports = [ unitWithTemplate.configuration ];
301+ systemd.services."instantiated@".serviceConfig.X-Test = "test";
302+ };
303+304 restart-and-reload-by-activation-script.configuration = {
305 systemd.services = rec {
306 simple-service = {
···312 ExecReload = "${pkgs.coreutils}/bin/true";
313 };
314 };
315+ "templated-simple-service@" = simple-service;
316+ "templated-simple-service@instance".overrideStrategy = "asDropin";
317318 simple-restart-service = simple-service // {
319 stopIfChanged = false;
320 };
321+ "templated-simple-restart-service@" = simple-restart-service;
322+ "templated-simple-restart-service@instance".overrideStrategy = "asDropin";
323324 simple-reload-service = simple-service // {
325 reloadIfChanged = true;
326 };
327+ "templated-simple-reload-service@" = simple-reload-service;
328+ "templated-simple-reload-service@instance".overrideStrategy = "asDropin";
329330 no-restart-service = simple-service // {
331 restartIfChanged = false;
332 };
333+ "templated-no-restart-service@" = no-restart-service;
334+ "templated-no-restart-service@instance".overrideStrategy = "asDropin";
335336 reload-triggers = simple-service // {
337 wantedBy = [ "multi-user.target" ];
338 };
339+ "templated-reload-triggers@" = simple-service;
340+ "templated-reload-triggers@instance" = {
341+ overrideStrategy = "asDropin";
342+ wantedBy = [ "multi-user.target" ];
343+ };
344345 reload-triggers-and-restart-by-as = simple-service;
346+ "templated-reload-triggers-and-restart-by-as@" = reload-triggers-and-restart-by-as;
347+ "templated-reload-triggers-and-restart-by-as@instance".overrideStrategy = "asDropin";
348349 reload-triggers-and-restart = simple-service // {
350 stopIfChanged = false; # easier to check for this
351 wantedBy = [ "multi-user.target" ];
352 };
353+ "templated-reload-triggers-and-restart@" = simple-service;
354+ "templated-reload-triggers-and-restart@instance" = {
355+ overrideStrategy = "asDropin";
356+ stopIfChanged = false; # easier to check for this
357+ wantedBy = [ "multi-user.target" ];
358+ };
359 };
360361 system.activationScripts.restart-and-reload-test = {
···375 simple-reload-service.service
376 no-restart-service.service
377 reload-triggers-and-restart-by-as.service
378+ templated-simple-service@instance.service
379+ templated-simple-restart-service@instance.service
380+ templated-simple-reload-service@instance.service
381+ templated-no-restart-service@instance.service
382+ templated-reload-triggers-and-restart-by-as@instance.service
383 EOF
384385 cat <<EOF >> "$g"
386 reload-triggers.service
387 reload-triggers-and-restart-by-as.service
388 reload-triggers-and-restart.service
389+ templated-reload-triggers@instance.service
390+ templated-reload-triggers-and-restart-by-as@instance.service
391+ templated-reload-triggers-and-restart@instance.service
392 EOF
393 '';
394 };
···397 restart-and-reload-by-activation-script-modified.configuration = {
398 imports = [ restart-and-reload-by-activation-script.configuration ];
399 systemd.services.reload-triggers-and-restart.serviceConfig.X-Modified = "test";
400+ systemd.services."templated-reload-triggers-and-restart@instance" = {
401+ overrideStrategy = "asDropin";
402+ serviceConfig.X-Modified = "test";
403+ };
404 };
405406 simple-socket.configuration = {
···562 set -o pipefail
563 exec env -i "$@" | tee /dev/stderr
564 '';
565+566+ # Returns a comma separated representation of the given list in sorted
567+ # order, that matches the output format of switch-to-configuration.pl
568+ sortedUnits = xs: lib.concatStringsSep ", " (builtins.sort builtins.lessThan xs);
569 in /* python */ ''
570 def switch_to_specialisation(system, name, action="test", fail=False):
571 if name == "":
···792 assert_contains(out, "\nstarting the following units: required-service.service\n")
793 assert_lacks(out, "the following new units were started:")
794795+ # Ensure templated units are restarted when the base unit changes
796+ switch_to_specialisation("${machine}", "unitWithTemplate")
797+ out = switch_to_specialisation("${machine}", "unitWithTemplateModified")
798+ assert_contains(out, "stopping the following units: instantiated@one.service, instantiated@two.service\n")
799+ assert_lacks(out, "NOT restarting the following changed units:")
800+ assert_lacks(out, "reloading the following units:")
801+ assert_lacks(out, "\nrestarting the following units:")
802+ assert_contains(out, "\nstarting the following units: instantiated@one.service, instantiated@two.service\n")
803+ assert_lacks(out, "the following new units were started:")
804+805 with subtest("failing units"):
806 # Let the simple service fail
807 switch_to_specialisation("${machine}", "simpleServiceModified")
···965 assert_lacks(out, "NOT restarting the following changed units:")
966 assert_lacks(out, "reloading the following units:")
967 assert_lacks(out, "restarting the following units:")
968+ assert_contains(out, "\nstarting the following units: ${sortedUnits [
969+ "no-restart-service.service"
970+ "reload-triggers-and-restart-by-as.service"
971+ "simple-reload-service.service"
972+ "simple-restart-service.service"
973+ "simple-service.service"
974+ "templated-no-restart-service@instance.service"
975+ "templated-reload-triggers-and-restart-by-as@instance.service"
976+ "templated-simple-reload-service@instance.service"
977+ "templated-simple-restart-service@instance.service"
978+ "templated-simple-service@instance.service"
979+ ]}\n")
980+ assert_contains(out, "the following new units were started: ${sortedUnits [
981+ "no-restart-service.service"
982+ "reload-triggers-and-restart-by-as.service"
983+ "reload-triggers-and-restart.service"
984+ "reload-triggers.service"
985+ "simple-reload-service.service"
986+ "simple-restart-service.service"
987+ "simple-service.service"
988+ "system-templated\\\\x2dno\\\\x2drestart\\\\x2dservice.slice"
989+ "system-templated\\\\x2dreload\\\\x2dtriggers.slice"
990+ "system-templated\\\\x2dreload\\\\x2dtriggers\\\\x2dand\\\\x2drestart.slice"
991+ "system-templated\\\\x2dreload\\\\x2dtriggers\\\\x2dand\\\\x2drestart\\\\x2dby\\\\x2das.slice"
992+ "system-templated\\\\x2dsimple\\\\x2dreload\\\\x2dservice.slice"
993+ "system-templated\\\\x2dsimple\\\\x2drestart\\\\x2dservice.slice"
994+ "system-templated\\\\x2dsimple\\\\x2dservice.slice"
995+ "templated-no-restart-service@instance.service"
996+ "templated-reload-triggers-and-restart-by-as@instance.service"
997+ "templated-reload-triggers-and-restart@instance.service"
998+ "templated-reload-triggers@instance.service"
999+ "templated-simple-reload-service@instance.service"
1000+ "templated-simple-restart-service@instance.service"
1001+ "templated-simple-service@instance.service"
1002+ ]}\n")
1003 # Switch to the same system where the example services get restarted
1004 # and reloaded by the activation script
1005 out = switch_to_specialisation("${machine}", "restart-and-reload-by-activation-script")
1006 assert_lacks(out, "stopping the following units:")
1007 assert_lacks(out, "NOT restarting the following changed units:")
1008+ assert_contains(out, "reloading the following units: ${sortedUnits [
1009+ "reload-triggers-and-restart.service"
1010+ "reload-triggers.service"
1011+ "simple-reload-service.service"
1012+ "templated-reload-triggers-and-restart@instance.service"
1013+ "templated-reload-triggers@instance.service"
1014+ "templated-simple-reload-service@instance.service"
1015+ ]}\n")
1016+ assert_contains(out, "restarting the following units: ${sortedUnits [
1017+ "reload-triggers-and-restart-by-as.service"
1018+ "simple-restart-service.service"
1019+ "simple-service.service"
1020+ "templated-reload-triggers-and-restart-by-as@instance.service"
1021+ "templated-simple-restart-service@instance.service"
1022+ "templated-simple-service@instance.service"
1023+ ]}\n")
1024 assert_lacks(out, "\nstarting the following units:")
1025 assert_lacks(out, "the following new units were started:")
1026 # Switch to the same system and see if the service gets restarted when it's modified
···1028 out = switch_to_specialisation("${machine}", "restart-and-reload-by-activation-script-modified")
1029 assert_lacks(out, "stopping the following units:")
1030 assert_lacks(out, "NOT restarting the following changed units:")
1031+ assert_contains(out, "reloading the following units: ${sortedUnits [
1032+ "reload-triggers.service"
1033+ "simple-reload-service.service"
1034+ "templated-reload-triggers@instance.service"
1035+ "templated-simple-reload-service@instance.service"
1036+ ]}\n")
1037+ assert_contains(out, "restarting the following units: ${sortedUnits [
1038+ "reload-triggers-and-restart-by-as.service"
1039+ "reload-triggers-and-restart.service"
1040+ "simple-restart-service.service"
1041+ "simple-service.service"
1042+ "templated-reload-triggers-and-restart-by-as@instance.service"
1043+ "templated-reload-triggers-and-restart@instance.service"
1044+ "templated-simple-restart-service@instance.service"
1045+ "templated-simple-service@instance.service"
1046+ ]}\n")
1047 assert_lacks(out, "\nstarting the following units:")
1048 assert_lacks(out, "the following new units were started:")
1049 # The same, but in dry mode
1050 out = switch_to_specialisation("${machine}", "restart-and-reload-by-activation-script", action="dry-activate")
1051 assert_lacks(out, "would stop the following units:")
1052 assert_lacks(out, "would NOT stop the following changed units:")
1053+ assert_contains(out, "would reload the following units: ${sortedUnits [
1054+ "reload-triggers.service"
1055+ "simple-reload-service.service"
1056+ "templated-reload-triggers@instance.service"
1057+ "templated-simple-reload-service@instance.service"
1058+ ]}\n")
1059+ assert_contains(out, "would restart the following units: ${sortedUnits [
1060+ "reload-triggers-and-restart-by-as.service"
1061+ "reload-triggers-and-restart.service"
1062+ "simple-restart-service.service"
1063+ "simple-service.service"
1064+ "templated-reload-triggers-and-restart-by-as@instance.service"
1065+ "templated-reload-triggers-and-restart@instance.service"
1066+ "templated-simple-restart-service@instance.service"
1067+ "templated-simple-service@instance.service"
1068+ ]}\n")
1069 assert_lacks(out, "\nwould start the following units:")
10701071 with subtest("socket-activated services"):