Merge pull request #232230 from oddlama/fix-activation-template-unit-specializations

Fix detection of changed template unit specializations in switch-to-configuration.pl

authored by

Janne Heß and committed by
GitHub
bc9b484d 1d482993

+218 -32
+30
nixos/doc/manual/administration/service-mgmt.chapter.md
··· 118 Hence [garbage collection](#sec-nix-gc) will remove that file and you 119 will wind up with a broken symlink in your systemd configuration, which 120 in turn will not make the service / timer start on login.
··· 118 Hence [garbage collection](#sec-nix-gc) will remove that file and you 119 will wind up with a broken symlink in your systemd configuration, which 120 in 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 + ```
+35 -23
nixos/modules/system/activation/switch-to-configuration.pl
··· 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. 255 sub parse_unit { 256 - my ($unit_path) = @_; 257 258 # 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 $unit_path =~ s/\\/\\\\/gmsx; 263 - foreach (glob("${unit_path}{,.d/*.conf}")) { 264 parse_systemd_ini(\%unit_data, "$_") 265 } 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. 425 sub 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) = @_; 427 428 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); ··· 538 539 my $active_cur = get_active_units(); 540 while (my ($unit, $state) = each(%{$active_cur})) { 541 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"; 545 546 # 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 } 552 553 my $base_name = $base_unit; 554 $base_name =~ s/\.[[:lower:]]*$//msx; 555 556 - 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 } 563 564 elsif ($unit =~ /\.target$/msx) { 565 - my %new_unit_info = parse_unit($new_unit_file); 566 567 # Cause all active target units to be restarted below. 568 # This should start most changed units we stop here as ··· 596 } 597 598 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 = $_; 713 my $base_unit = $unit; 714 - my $new_unit_file = "$toplevel/etc/systemd/system/$base_unit"; 715 716 # 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 } 721 722 my $base_name = $base_unit; ··· 728 next; 729 } 730 731 - 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. 783 foreach (split(/\n/msx, read_file($restart_by_activation_file, err_mode => "quiet") // "")) { 784 my $unit = $_; 785 my $base_unit = $unit; 786 - my $new_unit_file = "$toplevel/etc/systemd/system/$base_unit"; 787 788 # 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 } 793 794 my $base_name = $base_unit; ··· 801 next; 802 } 803 804 - 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 807 unlink($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. 255 sub parse_unit { 256 + my ($unit_path, $base_unit_path) = @_; 257 258 # 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. 433 sub 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) = @_; 435 436 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); ··· 546 547 my $active_cur = get_active_units(); 548 while (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; 555 556 # 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 } 562 563 my $base_name = $base_unit; 564 $base_name =~ s/\.[[:lower:]]*$//msx; 565 566 + 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 } 573 574 elsif ($unit =~ /\.target$/msx) { 575 + my %new_unit_info = parse_unit($new_unit_file, $new_base_unit_file); 576 577 # Cause all active target units to be restarted below. 578 # This should start most changed units we stop here as ··· 606 } 607 608 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; 726 727 # 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 } 732 733 my $base_name = $base_unit; ··· 739 next; 740 } 741 742 + 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. 794 foreach (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; 799 800 # 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 } 805 806 my $base_name = $base_unit; ··· 813 next; 814 } 815 816 + 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 819 unlink($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. 2 3 - import ./make-test-python.nix ({ pkgs, ...} : let 4 5 # 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 }; 281 282 restart-and-reload-by-activation-script.configuration = { 283 systemd.services = rec { 284 simple-service = { ··· 290 ExecReload = "${pkgs.coreutils}/bin/true"; 291 }; 292 }; 293 294 simple-restart-service = simple-service // { 295 stopIfChanged = false; 296 }; 297 298 simple-reload-service = simple-service // { 299 reloadIfChanged = true; 300 }; 301 302 no-restart-service = simple-service // { 303 restartIfChanged = false; 304 }; 305 306 reload-triggers = simple-service // { 307 wantedBy = [ "multi-user.target" ]; 308 }; 309 310 reload-triggers-and-restart-by-as = simple-service; 311 312 reload-triggers-and-restart = simple-service // { 313 stopIfChanged = false; # easier to check for this 314 wantedBy = [ "multi-user.target" ]; 315 }; 316 }; 317 318 system.activationScripts.restart-and-reload-test = { ··· 332 simple-reload-service.service 333 no-restart-service.service 334 reload-triggers-and-restart-by-as.service 335 EOF 336 337 cat <<EOF >> "$g" 338 reload-triggers.service 339 reload-triggers-and-restart-by-as.service 340 reload-triggers-and-restart.service 341 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"; 349 }; 350 351 simple-socket.configuration = { ··· 507 set -o pipefail 508 exec env -i "$@" | tee /dev/stderr 509 ''; 510 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:") 735 736 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") 901 # 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") 908 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") 917 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") 925 assert_lacks(out, "\nwould start the following units:") 926 927 with subtest("socket-activated services"):
··· 1 # Test configuration switching. 2 3 + import ./make-test-python.nix ({ lib, pkgs, ...} : let 4 5 # 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 }; 281 282 + 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"; 317 318 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"; 323 324 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"; 329 330 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"; 335 336 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 + }; 344 345 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"; 348 349 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 }; 360 361 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 384 385 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 }; 405 406 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:") 794 795 + # 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:") 1070 1071 with subtest("socket-activated services"):