at 22.05-pre 18 kB view raw
1# Test configuration switching. 2 3import ./make-test-python.nix ({ pkgs, ...} : { 4 name = "switch-test"; 5 meta = with pkgs.lib.maintainers; { 6 maintainers = [ gleber ]; 7 }; 8 9 nodes = { 10 machine = { config, pkgs, lib, ... }: { 11 environment.systemPackages = [ pkgs.socat ]; # for the socket activation stuff 12 users.mutableUsers = false; 13 14 specialisation = { 15 # A system with a simple socket-activated unit 16 simple-socket.configuration = { 17 systemd.services.socket-activated.serviceConfig = { 18 ExecStart = pkgs.writeScript "socket-test.py" /* python */ '' 19 #!${pkgs.python3}/bin/python3 20 21 from socketserver import TCPServer, StreamRequestHandler 22 import socket 23 24 class Handler(StreamRequestHandler): 25 def handle(self): 26 self.wfile.write("hello".encode("utf-8")) 27 28 class Server(TCPServer): 29 def __init__(self, server_address, handler_cls): 30 # Invoke base but omit bind/listen steps (performed by systemd activation!) 31 TCPServer.__init__( 32 self, server_address, handler_cls, bind_and_activate=False) 33 # Override socket 34 self.socket = socket.fromfd(3, self.address_family, self.socket_type) 35 36 if __name__ == "__main__": 37 server = Server(("localhost", 1234), Handler) 38 server.serve_forever() 39 ''; 40 }; 41 systemd.sockets.socket-activated = { 42 wantedBy = [ "sockets.target" ]; 43 listenStreams = [ "/run/test.sock" ]; 44 socketConfig.SocketMode = lib.mkDefault "0777"; 45 }; 46 }; 47 48 # The same system but the socket is modified 49 modified-socket.configuration = { 50 imports = [ config.specialisation.simple-socket.configuration ]; 51 systemd.sockets.socket-activated.socketConfig.SocketMode = "0666"; 52 }; 53 54 # The same system but the service is modified 55 modified-service.configuration = { 56 imports = [ config.specialisation.simple-socket.configuration ]; 57 systemd.services.socket-activated.serviceConfig.X-Test = "test"; 58 }; 59 60 # The same system but both service and socket are modified 61 modified-service-and-socket.configuration = { 62 imports = [ config.specialisation.simple-socket.configuration ]; 63 systemd.services.socket-activated.serviceConfig.X-Test = "some_value"; 64 systemd.sockets.socket-activated.socketConfig.SocketMode = "0444"; 65 }; 66 67 # A system with a socket-activated service and some simple services 68 service-and-socket.configuration = { 69 imports = [ config.specialisation.simple-socket.configuration ]; 70 systemd.services.simple-service = { 71 wantedBy = [ "multi-user.target" ]; 72 serviceConfig = { 73 Type = "oneshot"; 74 RemainAfterExit = true; 75 ExecStart = "${pkgs.coreutils}/bin/true"; 76 }; 77 }; 78 79 systemd.services.simple-restart-service = { 80 stopIfChanged = false; 81 wantedBy = [ "multi-user.target" ]; 82 serviceConfig = { 83 Type = "oneshot"; 84 RemainAfterExit = true; 85 ExecStart = "${pkgs.coreutils}/bin/true"; 86 }; 87 }; 88 89 systemd.services.simple-reload-service = { 90 reloadIfChanged = true; 91 wantedBy = [ "multi-user.target" ]; 92 serviceConfig = { 93 Type = "oneshot"; 94 RemainAfterExit = true; 95 ExecStart = "${pkgs.coreutils}/bin/true"; 96 ExecReload = "${pkgs.coreutils}/bin/true"; 97 }; 98 }; 99 100 systemd.services.no-restart-service = { 101 restartIfChanged = false; 102 wantedBy = [ "multi-user.target" ]; 103 serviceConfig = { 104 Type = "oneshot"; 105 RemainAfterExit = true; 106 ExecStart = "${pkgs.coreutils}/bin/true"; 107 }; 108 }; 109 }; 110 111 # The same system but with an activation script that restarts all services 112 restart-and-reload-by-activation-script.configuration = { 113 imports = [ config.specialisation.service-and-socket.configuration ]; 114 system.activationScripts.restart-and-reload-test = { 115 supportsDryActivation = true; 116 deps = []; 117 text = '' 118 if [ "$NIXOS_ACTION" = dry-activate ]; then 119 f=/run/nixos/dry-activation-restart-list 120 else 121 f=/run/nixos/activation-restart-list 122 fi 123 cat <<EOF >> "$f" 124 simple-service.service 125 simple-restart-service.service 126 simple-reload-service.service 127 no-restart-service.service 128 socket-activated.service 129 EOF 130 ''; 131 }; 132 }; 133 134 # A system with a timer 135 with-timer.configuration = { 136 systemd.timers.test-timer = { 137 wantedBy = [ "timers.target" ]; 138 timerConfig.OnCalendar = "@1395716396"; # chosen by fair dice roll 139 }; 140 systemd.services.test-timer = { 141 serviceConfig = { 142 Type = "oneshot"; 143 ExecStart = "${pkgs.coreutils}/bin/true"; 144 }; 145 }; 146 }; 147 148 # The same system but with another time 149 with-timer-modified.configuration = { 150 imports = [ config.specialisation.with-timer.configuration ]; 151 systemd.timers.test-timer.timerConfig.OnCalendar = lib.mkForce "Fri 2012-11-23 16:00:00"; 152 }; 153 154 # A system with a systemd mount 155 with-mount.configuration = { 156 systemd.mounts = [ 157 { 158 description = "Testmount"; 159 what = "tmpfs"; 160 type = "tmpfs"; 161 where = "/testmount"; 162 options = "size=1M"; 163 wantedBy = [ "local-fs.target" ]; 164 } 165 ]; 166 }; 167 168 # The same system but with another time 169 with-mount-modified.configuration = { 170 systemd.mounts = [ 171 { 172 description = "Testmount"; 173 what = "tmpfs"; 174 type = "tmpfs"; 175 where = "/testmount"; 176 options = "size=10M"; 177 wantedBy = [ "local-fs.target" ]; 178 } 179 ]; 180 }; 181 182 # A system with a path unit 183 with-path.configuration = { 184 systemd.paths.test-watch = { 185 wantedBy = [ "paths.target" ]; 186 pathConfig.PathExists = "/testpath"; 187 }; 188 systemd.services.test-watch = { 189 serviceConfig = { 190 Type = "oneshot"; 191 ExecStart = "${pkgs.coreutils}/bin/touch /testpath-modified"; 192 }; 193 }; 194 }; 195 196 # The same system but watching another file 197 with-path-modified.configuration = { 198 imports = [ config.specialisation.with-path.configuration ]; 199 systemd.paths.test-watch.pathConfig.PathExists = lib.mkForce "/testpath2"; 200 }; 201 202 # A system with a slice 203 with-slice.configuration = { 204 systemd.slices.testslice.sliceConfig.MemoryMax = "1"; # don't allow memory allocation 205 systemd.services.testservice = { 206 serviceConfig = { 207 Type = "oneshot"; 208 RemainAfterExit = true; 209 ExecStart = "${pkgs.coreutils}/bin/true"; 210 Slice = "testslice.slice"; 211 }; 212 }; 213 }; 214 215 # The same system but the slice allows to allocate memory 216 with-slice-non-crashing.configuration = { 217 imports = [ config.specialisation.with-slice.configuration ]; 218 systemd.slices.testslice.sliceConfig.MemoryMax = lib.mkForce null; 219 }; 220 }; 221 }; 222 other = { ... }: { 223 users.mutableUsers = true; 224 }; 225 }; 226 227 testScript = { nodes, ... }: let 228 originalSystem = nodes.machine.config.system.build.toplevel; 229 otherSystem = nodes.other.config.system.build.toplevel; 230 231 # Ensures failures pass through using pipefail, otherwise failing to 232 # switch-to-configuration is hidden by the success of `tee`. 233 stderrRunner = pkgs.writeScript "stderr-runner" '' 234 #! ${pkgs.runtimeShell} 235 set -e 236 set -o pipefail 237 exec env -i "$@" | tee /dev/stderr 238 ''; 239 in /* python */ '' 240 def switch_to_specialisation(name, action="test"): 241 out = machine.succeed(f"${originalSystem}/specialisation/{name}/bin/switch-to-configuration {action} 2>&1") 242 assert_lacks(out, "switch-to-configuration line") # Perl warnings 243 return out 244 245 def assert_contains(haystack, needle): 246 if needle not in haystack: 247 print("The haystack that will cause the following exception is:") 248 print("---") 249 print(haystack) 250 print("---") 251 raise Exception(f"Expected string '{needle}' was not found") 252 253 def assert_lacks(haystack, needle): 254 if needle in haystack: 255 print("The haystack that will cause the following exception is:") 256 print("---") 257 print(haystack, end="") 258 print("---") 259 raise Exception(f"Unexpected string '{needle}' was found") 260 261 262 machine.succeed( 263 "${stderrRunner} ${originalSystem}/bin/switch-to-configuration test" 264 ) 265 machine.succeed( 266 "${stderrRunner} ${otherSystem}/bin/switch-to-configuration test" 267 ) 268 269 with subtest("systemd sockets"): 270 machine.succeed("${originalSystem}/bin/switch-to-configuration test") 271 272 # Simple socket is created 273 out = switch_to_specialisation("simple-socket") 274 assert_lacks(out, "stopping the following units:") 275 # not checking for reload because dbus gets reloaded 276 assert_lacks(out, "restarting the following units:") 277 assert_lacks(out, "\nstarting the following units:") 278 assert_contains(out, "the following new units were started: socket-activated.socket\n") 279 assert_lacks(out, "as well:") 280 machine.succeed("[ $(stat -c%a /run/test.sock) = 777 ]") 281 282 # Changing the socket restarts it 283 out = switch_to_specialisation("modified-socket") 284 assert_lacks(out, "stopping the following units:") 285 #assert_lacks(out, "reloading the following units:") 286 assert_contains(out, "restarting the following units: socket-activated.socket\n") 287 assert_lacks(out, "\nstarting the following units:") 288 assert_lacks(out, "the following new units were started:") 289 assert_lacks(out, "as well:") 290 machine.succeed("[ $(stat -c%a /run/test.sock) = 666 ]") # change was applied 291 292 # The unit is properly activated when the socket is accessed 293 if machine.succeed("socat - UNIX-CONNECT:/run/test.sock") != "hello": 294 raise Exception("Socket was not properly activated") 295 296 # Changing the socket restarts it and ignores the active service 297 out = switch_to_specialisation("simple-socket") 298 assert_contains(out, "stopping the following units: socket-activated.service\n") 299 assert_lacks(out, "reloading the following units:") 300 assert_contains(out, "restarting the following units: socket-activated.socket\n") 301 assert_lacks(out, "\nstarting the following units:") 302 assert_lacks(out, "the following new units were started:") 303 assert_lacks(out, "as well:") 304 machine.succeed("[ $(stat -c%a /run/test.sock) = 777 ]") # change was applied 305 306 # Changing the service does nothing when the service is not active 307 out = switch_to_specialisation("modified-service") 308 assert_lacks(out, "stopping the following units:") 309 assert_lacks(out, "reloading the following units:") 310 assert_lacks(out, "restarting the following units:") 311 assert_lacks(out, "\nstarting the following units:") 312 assert_lacks(out, "the following new units were started:") 313 assert_lacks(out, "as well:") 314 315 # Activating the service and modifying it stops it but leaves the socket untouched 316 machine.succeed("socat - UNIX-CONNECT:/run/test.sock") 317 out = switch_to_specialisation("simple-socket") 318 assert_contains(out, "stopping the following units: socket-activated.service\n") 319 assert_lacks(out, "reloading the following units:") 320 assert_lacks(out, "restarting the following units:") 321 assert_lacks(out, "\nstarting the following units:") 322 assert_lacks(out, "the following new units were started:") 323 assert_lacks(out, "as well:") 324 325 # Activating the service and both the service and the socket stops the service and restarts the socket 326 machine.succeed("socat - UNIX-CONNECT:/run/test.sock") 327 out = switch_to_specialisation("modified-service-and-socket") 328 assert_contains(out, "stopping the following units: socket-activated.service\n") 329 assert_lacks(out, "reloading the following units:") 330 assert_contains(out, "restarting the following units: socket-activated.socket\n") 331 assert_lacks(out, "\nstarting the following units:") 332 assert_lacks(out, "the following new units were started:") 333 assert_lacks(out, "as well:") 334 335 with subtest("restart and reload by activation file"): 336 out = switch_to_specialisation("service-and-socket") 337 # Switch to a system where the example services get restarted 338 # by the activation script 339 out = switch_to_specialisation("restart-and-reload-by-activation-script") 340 assert_lacks(out, "stopping the following units:") 341 assert_contains(out, "stopping the following units as well: simple-service.service, socket-activated.service\n") 342 assert_contains(out, "reloading the following units: simple-reload-service.service\n") 343 assert_contains(out, "restarting the following units: simple-restart-service.service\n") 344 assert_contains(out, "\nstarting the following units: simple-service.service") 345 346 # The same, but in dry mode 347 switch_to_specialisation("service-and-socket") 348 out = switch_to_specialisation("restart-and-reload-by-activation-script", action="dry-activate") 349 assert_lacks(out, "would stop the following units:") 350 assert_contains(out, "would stop the following units as well: simple-service.service, socket-activated.service\n") 351 assert_contains(out, "would reload the following units: simple-reload-service.service\n") 352 assert_contains(out, "would restart the following units: simple-restart-service.service\n") 353 assert_contains(out, "\nwould start the following units: simple-service.service") 354 355 with subtest("mounts"): 356 switch_to_specialisation("with-mount") 357 out = machine.succeed("mount | grep 'on /testmount'") 358 assert_contains(out, "size=1024k") 359 360 out = switch_to_specialisation("with-mount-modified") 361 assert_lacks(out, "stopping the following units:") 362 assert_contains(out, "reloading the following units: testmount.mount\n") 363 assert_lacks(out, "restarting the following units:") 364 assert_lacks(out, "\nstarting the following units:") 365 assert_lacks(out, "the following new units were started:") 366 assert_lacks(out, "as well:") 367 # It changed 368 out = machine.succeed("mount | grep 'on /testmount'") 369 assert_contains(out, "size=10240k") 370 371 with subtest("timers"): 372 switch_to_specialisation("with-timer") 373 out = machine.succeed("systemctl show test-timer.timer") 374 assert_contains(out, "OnCalendar=2014-03-25 02:59:56 UTC") 375 376 out = switch_to_specialisation("with-timer-modified") 377 assert_lacks(out, "stopping the following units:") 378 assert_lacks(out, "reloading the following units:") 379 assert_contains(out, "restarting the following units: test-timer.timer\n") 380 assert_lacks(out, "\nstarting the following units:") 381 assert_lacks(out, "the following new units were started:") 382 assert_lacks(out, "as well:") 383 # It changed 384 out = machine.succeed("systemctl show test-timer.timer") 385 assert_contains(out, "OnCalendar=Fri 2012-11-23 16:00:00") 386 387 with subtest("paths"): 388 switch_to_specialisation("with-path") 389 machine.fail("test -f /testpath-modified") 390 391 # touch the file, unit should be triggered 392 machine.succeed("touch /testpath") 393 machine.wait_until_succeeds("test -f /testpath-modified") 394 395 machine.succeed("rm /testpath") 396 machine.succeed("rm /testpath-modified") 397 switch_to_specialisation("with-path-modified") 398 399 machine.succeed("touch /testpath") 400 machine.fail("test -f /testpath-modified") 401 machine.succeed("touch /testpath2") 402 machine.wait_until_succeeds("test -f /testpath-modified") 403 404 # This test ensures that changes to slice configuration get applied. 405 # We test this by having a slice that allows no memory allocation at 406 # all and starting a service within it. If the service crashes, the slice 407 # is applied and if we modify the slice to allow memory allocation, the 408 # service should successfully start. 409 with subtest("slices"): 410 machine.succeed("echo 0 > /proc/sys/vm/panic_on_oom") # allow OOMing 411 out = switch_to_specialisation("with-slice") 412 machine.fail("systemctl start testservice.service") 413 out = switch_to_specialisation("with-slice-non-crashing") 414 machine.succeed("systemctl start testservice.service") 415 machine.succeed("echo 1 > /proc/sys/vm/panic_on_oom") # disallow OOMing 416 417 ''; 418})