at master 8.4 kB view raw
1# Run with: 2# cd nixpkgs 3# nix-build -A nixosTests.modular-service-etc 4 5# This tests the NixOS modular service integration to make sure `etc` entries 6# are generated correctly for `configData` files. 7{ lib, ... }: 8{ 9 _class = "nixosTest"; 10 name = "modular-service-etc"; 11 12 nodes = { 13 server = 14 { pkgs, ... }: 15 let 16 # Normally the package services.default attribute combines this, but we 17 # don't have that, because this is not a production service. Should it be? 18 python-http-server = { 19 imports = [ ./python-http-server.nix ]; 20 python-http-server.package = pkgs.python3; 21 }; 22 in 23 { 24 system.services.webserver = { 25 # The python web server is simple enough that it doesn't need a reload signal. 26 # Other services may need to receive a signal in order to re-read what's in `configData`. 27 imports = [ python-http-server ]; 28 python-http-server = { 29 port = 8080; 30 }; 31 32 configData = { 33 "webroot" = { 34 source = pkgs.runCommand "webroot" { } '' 35 mkdir -p $out 36 cat > $out/index.html << 'EOF' 37 <!DOCTYPE html> 38 <html> 39 <head><title>Python Web Server</title></head> 40 <body> 41 <h1>Welcome to the Python Web Server</h1> 42 <p>Serving from port 8080</p> 43 </body> 44 </html> 45 EOF 46 ''; 47 }; 48 }; 49 50 # Add a sub-service 51 services.api = { 52 imports = [ python-http-server ]; 53 python-http-server = { 54 port = 8081; 55 }; 56 configData = { 57 "webroot" = { 58 source = pkgs.runCommand "api-webroot" { } '' 59 mkdir -p $out 60 cat > $out/index.html << 'EOF' 61 <!DOCTYPE html> 62 <html> 63 <head><title>API Sub-service</title></head> 64 <body> 65 <h1>API Sub-service</h1> 66 <p>This is a sub-service running on port 8081</p> 67 </body> 68 </html> 69 EOF 70 cat > $out/status.json << 'EOF' 71 {"status": "ok", "service": "api", "port": 8081} 72 EOF 73 ''; 74 }; 75 }; 76 }; 77 }; 78 79 networking.firewall.allowedTCPPorts = [ 80 8080 81 8081 82 ]; 83 84 specialisation.updated.configuration = { 85 system.services.webserver = { 86 configData = { 87 "webroot" = { 88 source = lib.mkForce ( 89 pkgs.runCommand "webroot-updated" { } '' 90 mkdir -p $out 91 cat > $out/index.html << 'EOF' 92 <!DOCTYPE html> 93 <html> 94 <head><title>Updated Python Web Server</title></head> 95 <body> 96 <h1>Updated content via specialisation</h1> 97 <p>This content was changed without restarting the service</p> 98 </body> 99 </html> 100 EOF 101 '' 102 ); 103 }; 104 }; 105 106 services.api = { 107 configData = { 108 "webroot" = { 109 source = lib.mkForce ( 110 pkgs.runCommand "api-webroot-updated" { } '' 111 mkdir -p $out 112 cat > $out/index.html << 'EOF' 113 <!DOCTYPE html> 114 <html> 115 <head><title>Updated API Sub-service</title></head> 116 <body> 117 <h1>Updated API Sub-service</h1> 118 <p>This sub-service content was also updated</p> 119 </body> 120 </html> 121 EOF 122 cat > $out/status.json << 'EOF' 123 {"status": "updated", "service": "api", "port": 8081, "version": "2.0"} 124 EOF 125 '' 126 ); 127 }; 128 }; 129 }; 130 }; 131 }; 132 }; 133 134 client = 135 { pkgs, ... }: 136 { 137 environment.systemPackages = [ pkgs.curl ]; 138 }; 139 }; 140 141 testScript = '' 142 start_all() 143 144 server.wait_for_unit("multi-user.target") 145 client.wait_for_unit("multi-user.target") 146 147 # Wait for the web servers to start 148 server.wait_for_unit("webserver.service") 149 server.wait_for_open_port(8080) 150 server.wait_for_unit("webserver-api.service") 151 server.wait_for_open_port(8081) 152 153 # Check that the configData directories were created with unique paths 154 server.succeed("test -d /etc/system-services/webserver/webroot") 155 server.succeed("test -f /etc/system-services/webserver/webroot/index.html") 156 server.succeed("test -d /etc/system-services/webserver-api/webroot") 157 server.succeed("test -f /etc/system-services/webserver-api/webroot/index.html") 158 server.succeed("test -f /etc/system-services/webserver-api/webroot/status.json") 159 160 # Check that the main web server is serving the configData content 161 client.succeed("curl -f http://server:8080/index.html | grep 'Welcome to the Python Web Server'") 162 client.succeed("curl -f http://server:8080/index.html | grep 'Serving from port 8080'") 163 164 # Check that the sub-service is serving its own configData content 165 client.succeed("curl -f http://server:8081/index.html | grep 'API Sub-service'") 166 client.succeed("curl -f http://server:8081/index.html | grep 'This is a sub-service running on port 8081'") 167 client.succeed("curl -f http://server:8081/status.json | grep '\"service\": \"api\"'") 168 169 # Record PIDs before switching to verify services aren't restarted 170 webserver_pid = server.succeed("systemctl show webserver.service --property=MainPID --value").strip() 171 api_pid = server.succeed("systemctl show webserver-api.service --property=MainPID --value").strip() 172 173 print(f"Before switch - webserver PID: {webserver_pid}, api PID: {api_pid}") 174 175 # Switch to the specialisation with updated content 176 # Capture both stdout and stderr, and show stderr in real-time for debugging 177 switch_output = server.succeed("/run/current-system/specialisation/updated/bin/switch-to-configuration test 2>&1 | tee /dev/stderr") 178 print(f"Switch output (stdout+stderr): {switch_output}") 179 180 # Verify services are not mentioned in the switch output (indicating they weren't touched) 181 assert "webserver.service" not in switch_output, f"webserver.service was mentioned in switch output: {switch_output}" 182 assert "webserver-api.service" not in switch_output, f"webserver-api.service was mentioned in switch output: {switch_output}" 183 184 # Verify the content was updated without restarting the services 185 server.succeed("systemctl is-active webserver.service") 186 server.succeed("systemctl is-active webserver-api.service") 187 188 # Verify PIDs are the same (services weren't restarted) 189 webserver_pid_after = server.succeed("systemctl show webserver.service --property=MainPID --value").strip() 190 api_pid_after = server.succeed("systemctl show webserver-api.service --property=MainPID --value").strip() 191 192 print(f"After switch - webserver PID: {webserver_pid_after}, api PID: {api_pid_after}") 193 194 assert webserver_pid == webserver_pid_after, f"webserver.service was restarted: PID changed from {webserver_pid} to {webserver_pid_after}" 195 assert api_pid == api_pid_after, f"webserver-api.service was restarted: PID changed from {api_pid} to {api_pid_after}" 196 197 # Check main service updated content 198 client.succeed("curl -f http://server:8080/index.html | grep 'Updated content via specialisation'") 199 client.succeed("curl -f http://server:8080/index.html | grep 'This content was changed without restarting the service'") 200 201 # Check sub-service updated content 202 client.succeed("curl -f http://server:8081/index.html | grep 'Updated API Sub-service'") 203 client.succeed("curl -f http://server:8081/index.html | grep 'This sub-service content was also updated'") 204 client.succeed("curl -f http://server:8081/status.json | grep '\"version\": \"2.0\"'") 205 ''; 206 207 meta.maintainers = with lib.maintainers; [ roberth ]; 208}