From d1c1335908813fa659a02aa61222672b03b9239f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Janne=20He=C3=9F?= Date: Wed, 13 Sep 2023 16:42:19 +0200 Subject: [PATCH 1/6] nixos/switch-to-configuration: Test more action things --- nixos/modules/system/activation/switch-to-configuration.pl | 2 +- nixos/tests/switch-test.nix | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/nixos/modules/system/activation/switch-to-configuration.pl b/nixos/modules/system/activation/switch-to-configuration.pl index 8bd450d7343b..95308d5da946 100755 --- a/nixos/modules/system/activation/switch-to-configuration.pl +++ b/nixos/modules/system/activation/switch-to-configuration.pl @@ -74,7 +74,7 @@ if ("@localeArchive@" ne "") { if (!defined($action) || ($action ne "switch" && $action ne "boot" && $action ne "test" && $action ne "dry-activate")) { print STDERR <<"EOF"; -Usage: $0 [switch|boot|test] +Usage: $0 [switch|boot|test|dry-activate] switch: make the configuration the boot default and activate now boot: make the configuration the boot default diff --git a/nixos/tests/switch-test.nix b/nixos/tests/switch-test.nix index 529a20864206..7be0b4c7d3cc 100644 --- a/nixos/tests/switch-test.nix +++ b/nixos/tests/switch-test.nix @@ -643,6 +643,11 @@ in { # test and dry-activate actions are tested further down below + # invalid action fails the script + switch_to_specialisation("${machine}", "", action="broken-action", fail=True) + # no action fails the script + "Usage:" in machine.fail("${machine}/bin/switch-to-configuration") + with subtest("services"): switch_to_specialisation("${machine}", "") # Nothing happens when nothing is changed From 211e2d738bbb9b19ce873203e8bc72c832072e15 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Janne=20He=C3=9F?= Date: Wed, 13 Sep 2023 16:56:01 +0200 Subject: [PATCH 2/6] nixos/switchTest: Also test init interface version --- nixos/tests/switch-test.nix | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/nixos/tests/switch-test.nix b/nixos/tests/switch-test.nix index 7be0b4c7d3cc..ef50f93eee86 100644 --- a/nixos/tests/switch-test.nix +++ b/nixos/tests/switch-test.nix @@ -58,6 +58,10 @@ in { ''); specialisation = rec { + brokenInitInterface.configuration.config.system.extraSystemBuilderCmds = '' + echo "systemd 0" > $out/init-interface-version + ''; + simpleService.configuration = { systemd.services.test = { wantedBy = [ "multi-user.target" ]; @@ -646,7 +650,11 @@ in { # invalid action fails the script switch_to_specialisation("${machine}", "", action="broken-action", fail=True) # no action fails the script - "Usage:" in machine.fail("${machine}/bin/switch-to-configuration") + assert "Usage:" in machine.fail("${machine}/bin/switch-to-configuration 2>&1") + + with subtest("init interface version"): + # Do not try to switch to an invalid init interface version + assert "incompatible" in switch_to_specialisation("${machine}", "brokenInitInterface", fail=True) with subtest("services"): switch_to_specialisation("${machine}", "") From e0717ce857afab4a72a1d2b1e69d91e482b3370d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Janne=20He=C3=9F?= Date: Wed, 13 Sep 2023 17:04:54 +0200 Subject: [PATCH 3/6] nixos/switchTest: Also test systemd restarts --- nixos/tests/switch-test.nix | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/nixos/tests/switch-test.nix b/nixos/tests/switch-test.nix index ef50f93eee86..1ecf00d158cc 100644 --- a/nixos/tests/switch-test.nix +++ b/nixos/tests/switch-test.nix @@ -62,6 +62,10 @@ in { echo "systemd 0" > $out/init-interface-version ''; + modifiedSystemConf.configuration.systemd.extraConfig = '' + # Hello world! + ''; + simpleService.configuration = { systemd.services.test = { wantedBy = [ "multi-user.target" ]; @@ -656,6 +660,11 @@ in { # Do not try to switch to an invalid init interface version assert "incompatible" in switch_to_specialisation("${machine}", "brokenInitInterface", fail=True) + with subtest("systemd restarts"): + # systemd is restarted when its system.conf changes + out = switch_to_specialisation("${machine}", "modifiedSystemConf") + assert_contains(out, "restarting systemd...") + with subtest("services"): switch_to_specialisation("${machine}", "") # Nothing happens when nothing is changed From c3e6412260dd30f82b715f976fff834fbeddae2f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Janne=20He=C3=9F?= Date: Wed, 13 Sep 2023 17:11:32 +0200 Subject: [PATCH 4/6] nixos/switchTest: Also test restarting from aborted switches --- nixos/tests/switch-test.nix | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/nixos/tests/switch-test.nix b/nixos/tests/switch-test.nix index 1ecf00d158cc..c0bf8f2518d1 100644 --- a/nixos/tests/switch-test.nix +++ b/nixos/tests/switch-test.nix @@ -665,6 +665,13 @@ in { out = switch_to_specialisation("${machine}", "modifiedSystemConf") assert_contains(out, "restarting systemd...") + with subtest("continuing from an aborted switch"): + # An aborted switch will write into a file what it tried to start + # and a second switch should continue from this + machine.succeed("echo dbus.service > /run/nixos/start-list") + out = switch_to_specialisation("${machine}", "modifiedSystemConf") + assert_contains(out, "starting the following units: dbus.service\n") + with subtest("services"): switch_to_specialisation("${machine}", "") # Nothing happens when nothing is changed From 85c1c30fd9ab350ad254736d0950d07639b9f6e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Janne=20He=C3=9F?= Date: Wed, 13 Sep 2023 18:22:12 +0200 Subject: [PATCH 5/6] nixos/switch-to-configuration: Never unmount / or /nix Also adds a huge test for fstab handling --- ...-happens-during-a-system-switch.chapter.md | 5 +- .../activation/switch-to-configuration.pl | 18 +++-- nixos/tests/switch-test.nix | 69 +++++++++++++++++++ 3 files changed, 86 insertions(+), 6 deletions(-) diff --git a/nixos/doc/manual/development/what-happens-during-a-system-switch.chapter.md b/nixos/doc/manual/development/what-happens-during-a-system-switch.chapter.md index 9cbec729803a..5d6d67f1aa92 100644 --- a/nixos/doc/manual/development/what-happens-during-a-system-switch.chapter.md +++ b/nixos/doc/manual/development/what-happens-during-a-system-switch.chapter.md @@ -21,8 +21,9 @@ If the action is `switch` or `test`, the currently running system is inspected and the actions to switch to the new system are calculated. This process takes two data sources into account: `/etc/fstab` and the current systemd status. Mounts and swaps are read from `/etc/fstab` and the corresponding actions are -generated. If a new mount is added, for example, the proper `.mount` unit is -marked to be started. The current systemd state is inspected, the difference +generated. If the options of a mount are modified, for example, the proper `.mount` +unit is reloaded (or restarted if anything else changed and it's neither the root +mount or the nix store). The current systemd state is inspected, the difference between the current system and the desired configuration is calculated and actions are generated to get to this state. There are a lot of nuances that can be controlled by the units which are explained here. diff --git a/nixos/modules/system/activation/switch-to-configuration.pl b/nixos/modules/system/activation/switch-to-configuration.pl index 95308d5da946..e05f89bb0fb4 100755 --- a/nixos/modules/system/activation/switch-to-configuration.pl +++ b/nixos/modules/system/activation/switch-to-configuration.pl @@ -661,10 +661,20 @@ foreach my $mount_point (keys(%{$cur_fss})) { # Filesystem entry disappeared, so unmount it. $units_to_stop{$unit} = 1; } elsif ($cur->{fsType} ne $new->{fsType} || $cur->{device} ne $new->{device}) { - # Filesystem type or device changed, so unmount and mount it. - $units_to_stop{$unit} = 1; - $units_to_start{$unit} = 1; - record_unit($start_list_file, $unit); + if ($mount_point eq '/' or $mount_point eq '/nix') { + if ($cur->{options} ne $new->{options}) { + # Mount options changed, so remount it. + $units_to_reload{$unit} = 1; + record_unit($reload_list_file, $unit); + } else { + # Don't unmount / or /nix if the device changed + $units_to_skip{$unit} = 1; + } + } else { + # Filesystem type or device changed, so unmount and mount it. + $units_to_restart{$unit} = 1; + record_unit($restart_list_file, $unit); + } } elsif ($cur->{options} ne $new->{options}) { # Mount options changes, so remount it. $units_to_reload{$unit} = 1; diff --git a/nixos/tests/switch-test.nix b/nixos/tests/switch-test.nix index c0bf8f2518d1..5e9b38d2913a 100644 --- a/nixos/tests/switch-test.nix +++ b/nixos/tests/switch-test.nix @@ -66,6 +66,25 @@ in { # Hello world! ''; + addedMount.configuration.virtualisation.fileSystems."/test" = { + device = "tmpfs"; + fsType = "tmpfs"; + }; + + addedMountOptsModified.configuration = { + imports = [ addedMount.configuration ]; + virtualisation.fileSystems."/test".options = [ "x-test" ]; + }; + + addedMountDevModified.configuration = { + imports = [ addedMountOptsModified.configuration ]; + virtualisation.fileSystems."/test".device = lib.mkForce "ramfs"; + }; + + storeMountModified.configuration = { + virtualisation.fileSystems."/".device = lib.mkForce "auto"; + }; + simpleService.configuration = { systemd.services.test = { wantedBy = [ "multi-user.target" ]; @@ -672,6 +691,56 @@ in { out = switch_to_specialisation("${machine}", "modifiedSystemConf") assert_contains(out, "starting the following units: dbus.service\n") + with subtest("fstab mounts"): + switch_to_specialisation("${machine}", "") + # add a mountpoint + out = switch_to_specialisation("${machine}", "addedMount") + assert_lacks(out, "stopping the following units:") + assert_lacks(out, "NOT restarting the following changed units:") + assert_lacks(out, "\nrestarting the following units:") + assert_lacks(out, "\nstarting the following units:") + assert_contains(out, "the following new units were started: test.mount\n") + # modify the mountpoint's options + out = switch_to_specialisation("${machine}", "addedMountOptsModified") + assert_lacks(out, "stopping the following units:") + assert_lacks(out, "NOT restarting the following changed units:") + assert_contains(out, "reloading the following units: test.mount\n") + assert_lacks(out, "\nrestarting the following units:") + assert_lacks(out, "\nstarting the following units:") + assert_lacks(out, "the following new units were started:") + # modify the device + out = switch_to_specialisation("${machine}", "addedMountDevModified") + assert_lacks(out, "stopping the following units:") + assert_lacks(out, "NOT restarting the following changed units:") + assert_lacks(out, "reloading the following units:") + assert_contains(out, "\nrestarting the following units: test.mount\n") + assert_lacks(out, "\nstarting the following units:") + assert_lacks(out, "the following new units were started:") + # modify both + out = switch_to_specialisation("${machine}", "addedMount") + assert_lacks(out, "stopping the following units:") + assert_lacks(out, "NOT restarting the following changed units:") + assert_lacks(out, "reloading the following units:") + assert_contains(out, "\nrestarting the following units: test.mount\n") + assert_lacks(out, "\nstarting the following units:") + assert_lacks(out, "the following new units were started:") + # remove the mount + out = switch_to_specialisation("${machine}", "") + assert_contains(out, "stopping the following units: test.mount\n") + assert_lacks(out, "NOT restarting the following changed units:") + assert_contains(out, "reloading the following units: dbus.service\n") + assert_lacks(out, "\nrestarting the following units:") + assert_lacks(out, "\nstarting the following units:") + assert_lacks(out, "the following new units were started:") + # change something about the / mount + out = switch_to_specialisation("${machine}", "storeMountModified") + assert_lacks(out, "stopping the following units:") + assert_contains(out, "NOT restarting the following changed units: -.mount") + assert_contains(out, "reloading the following units: dbus.service\n") + assert_lacks(out, "\nrestarting the following units:") + assert_lacks(out, "\nstarting the following units:") + assert_lacks(out, "the following new units were started:") + with subtest("services"): switch_to_specialisation("${machine}", "") # Nothing happens when nothing is changed From 358347e8b651ae24413c2abb7640c8c8e1786a1f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Janne=20He=C3=9F?= Date: Wed, 13 Sep 2023 19:14:08 +0200 Subject: [PATCH 6/6] nixos/switchTest: Also test swap devices --- nixos/tests/switch-test.nix | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/nixos/tests/switch-test.nix b/nixos/tests/switch-test.nix index 5e9b38d2913a..5ffdf180d5e3 100644 --- a/nixos/tests/switch-test.nix +++ b/nixos/tests/switch-test.nix @@ -85,6 +85,10 @@ in { virtualisation.fileSystems."/".device = lib.mkForce "auto"; }; + swap.configuration.swapDevices = lib.mkVMOverride [ + { device = "/swapfile"; size = 1; } + ]; + simpleService.configuration = { systemd.services.test = { wantedBy = [ "multi-user.target" ]; @@ -741,6 +745,26 @@ in { assert_lacks(out, "\nstarting the following units:") assert_lacks(out, "the following new units were started:") + with subtest("swaps"): + switch_to_specialisation("${machine}", "") + # add a swap + out = switch_to_specialisation("${machine}", "swap") + assert_lacks(out, "stopping the following units:") + assert_lacks(out, "NOT restarting the following changed units:") + assert_contains(out, "reloading the following units: dbus.service\n") + assert_lacks(out, "\nrestarting the following units:") + assert_lacks(out, "\nstarting the following units:") + assert_contains(out, "the following new units were started: swapfile.swap") + # remove it + out = switch_to_specialisation("${machine}", "") + assert_contains(out, "stopping swap device: /swapfile") + assert_lacks(out, "stopping the following units:") + assert_lacks(out, "NOT restarting the following changed units:") + assert_contains(out, "reloading the following units: dbus.service\n") + assert_lacks(out, "\nrestarting the following units:") + assert_lacks(out, "\nstarting the following units:") + assert_lacks(out, "the following new units were started:") + with subtest("services"): switch_to_specialisation("${machine}", "") # Nothing happens when nothing is changed