modular services: Add configData option for etc-like files
This commit is contained in:
@@ -8,6 +8,8 @@ See the [Modular Services chapter] in the manual [[source]](../../doc/manual/dev
|
|||||||
|
|
||||||
# Design decision log
|
# Design decision log
|
||||||
|
|
||||||
|
## Initial design
|
||||||
|
|
||||||
- `system.services.<name>`. Alternatives considered
|
- `system.services.<name>`. Alternatives considered
|
||||||
- `systemServices`: similar to does not allow importing a composition of services into `system`. Not sure if that's a good idea in the first place, but I've kept the possibility open.
|
- `systemServices`: similar to does not allow importing a composition of services into `system`. Not sure if that's a good idea in the first place, but I've kept the possibility open.
|
||||||
- `services.abstract`: used in https://github.com/NixOS/nixpkgs/pull/267111, but too weird. Service modules should fit naturally into the configuration system.
|
- `services.abstract`: used in https://github.com/NixOS/nixpkgs/pull/267111, but too weird. Service modules should fit naturally into the configuration system.
|
||||||
@@ -26,3 +28,28 @@ See the [Modular Services chapter] in the manual [[source]](../../doc/manual/dev
|
|||||||
2. `systemd/system` configures SystemD _system units_.
|
2. `systemd/system` configures SystemD _system units_.
|
||||||
- This reserves `modules/service` for actual service modules, at least until those are lifted out of NixOS, potentially
|
- This reserves `modules/service` for actual service modules, at least until those are lifted out of NixOS, potentially
|
||||||
|
|
||||||
|
## Configuration Data (`configData`) Design
|
||||||
|
|
||||||
|
Without a mechanism for adding files, all configuration had to go through `process.*`, requiring process restarts even when those would have been avoidable.
|
||||||
|
Many services implement automatic reloading or reloading on e.g. `SIGUSR1`, but those mechanisms need files to read. `configData` provides such files.
|
||||||
|
|
||||||
|
### Naming and Terminology
|
||||||
|
|
||||||
|
- **`configData` instead of `environment.etc`**: The name `configData` is service manager agnostic. While systemd system services can use `/etc`, other service managers may expose configuration data differently (e.g., different directory, relative paths).
|
||||||
|
|
||||||
|
- **`path` attribute**: Each `configData` entry automatically gets a `path` attribute set by the service manager implementation, allowing services to reference the location of their configuration files. These paths themselves are not subject to change from generation to generation; only their contents are.
|
||||||
|
|
||||||
|
- **`name` attribute**: In `environment.etc` this would be `target` but that's confusing, especially for symlinks, as it's not the symlink's target.
|
||||||
|
|
||||||
|
### Service Manager Integration
|
||||||
|
|
||||||
|
- **Portable base**: The `configData` interface is declared in `portable/config-data.nix`, making it available to all service manager implementations.
|
||||||
|
|
||||||
|
- **Systemd integration**: The systemd implementation (`systemd/system.nix`) maps `configData` entries to `environment.etc` entries under `/etc/system-services/`.
|
||||||
|
|
||||||
|
- **Path computation**: `systemd/config-data-path.nix` recursively computes unique paths for services and sub-services (e.g., `/etc/system-services/webserver/` vs `/etc/system-services/webserver-api/`).
|
||||||
|
Fun fact: for the module system it is a completely normal module, despite its recursive definition.
|
||||||
|
If we parameterize `/etc/system-services`, it will have to become an `importApply` style module nonetheless (function returning module).
|
||||||
|
|
||||||
|
- **Simple attribute structure**: Unlike `environment.etc`, `configData` uses a simpler structure with just `enable`, `name`, `text`, `source`, and `path` attributes. Complex ownership options were omitted for simplicity and portability.
|
||||||
|
Per-service user creation is still TBD.
|
||||||
|
|||||||
65
nixos/modules/system/service/portable/config-data-item.nix
Normal file
65
nixos/modules/system/service/portable/config-data-item.nix
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
# Tests in: ../../../../tests/modular-service-etc/test.nix
|
||||||
|
# This file is a function that returns a module.
|
||||||
|
pkgs:
|
||||||
|
{
|
||||||
|
lib,
|
||||||
|
name,
|
||||||
|
config,
|
||||||
|
options,
|
||||||
|
...
|
||||||
|
}:
|
||||||
|
let
|
||||||
|
inherit (lib) mkOption types;
|
||||||
|
in
|
||||||
|
{
|
||||||
|
options = {
|
||||||
|
enable = mkOption {
|
||||||
|
type = types.bool;
|
||||||
|
default = true;
|
||||||
|
description = ''
|
||||||
|
Whether this configuration file should be generated.
|
||||||
|
This option allows specific configuration files to be disabled.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
name = mkOption {
|
||||||
|
type = types.str;
|
||||||
|
description = ''
|
||||||
|
Name of the configuration file (relative to the service's configuration directory). Defaults to the attribute name.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
path = mkOption {
|
||||||
|
type = types.str;
|
||||||
|
readOnly = true;
|
||||||
|
description = ''
|
||||||
|
The actual path where this configuration file will be available.
|
||||||
|
This is determined by the service manager implementation.
|
||||||
|
|
||||||
|
On NixOS it is an absolute path.
|
||||||
|
Other service managers may provide a relative path, in order to be unprivileged and/or relocatable.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
text = mkOption {
|
||||||
|
default = null;
|
||||||
|
type = types.nullOr types.lines;
|
||||||
|
description = "Text content of the configuration file.";
|
||||||
|
};
|
||||||
|
|
||||||
|
source = mkOption {
|
||||||
|
type = types.path;
|
||||||
|
description = "Path of the source file.";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
config = {
|
||||||
|
name = lib.mkDefault name;
|
||||||
|
source = lib.mkIf (config.text != null) (
|
||||||
|
let
|
||||||
|
name' = "service-configdata-" + lib.replaceStrings [ "/" ] [ "-" ] name;
|
||||||
|
in
|
||||||
|
lib.mkDerivedConfig options.text (pkgs.writeText name')
|
||||||
|
);
|
||||||
|
};
|
||||||
|
}
|
||||||
44
nixos/modules/system/service/portable/config-data.nix
Normal file
44
nixos/modules/system/service/portable/config-data.nix
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
# Tests in: ../../../../tests/modular-service-etc/test.nix
|
||||||
|
# Configuration data support for portable services
|
||||||
|
# This module provides configData for services, enabling configuration reloading
|
||||||
|
# without terminating and restarting the service process.
|
||||||
|
{
|
||||||
|
lib,
|
||||||
|
pkgs,
|
||||||
|
...
|
||||||
|
}:
|
||||||
|
let
|
||||||
|
inherit (lib) mkOption types;
|
||||||
|
inherit (lib.modules) importApply;
|
||||||
|
in
|
||||||
|
{
|
||||||
|
options = {
|
||||||
|
configData = mkOption {
|
||||||
|
default = { };
|
||||||
|
example = lib.literalExpression ''
|
||||||
|
{
|
||||||
|
"server.conf" = {
|
||||||
|
text = '''
|
||||||
|
port = 8080
|
||||||
|
workers = 4
|
||||||
|
''';
|
||||||
|
};
|
||||||
|
"ssl/cert.pem" = {
|
||||||
|
source = ./cert.pem;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
'';
|
||||||
|
description = ''
|
||||||
|
Configuration data files for the service
|
||||||
|
|
||||||
|
These files are made available to the service and can be updated without restarting the service process, enabling configuration reloading.
|
||||||
|
The service manager implementation determines how these files are exposed to the service (e.g., via a specific directory path).
|
||||||
|
This path is available in the `path` sub-option for each `configData.<name>` entry.
|
||||||
|
|
||||||
|
This is particularly useful for services that support configuration reloading via signals (e.g., SIGHUP) or which pick up changes automatically, so that no downtime is required in order to reload the service.
|
||||||
|
'';
|
||||||
|
|
||||||
|
type = types.lazyAttrsOf (types.submodule (importApply ./config-data-item.nix pkgs));
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -11,6 +11,7 @@ in
|
|||||||
_class = "service";
|
_class = "service";
|
||||||
imports = [
|
imports = [
|
||||||
../../../misc/assertions.nix
|
../../../misc/assertions.nix
|
||||||
|
./config-data.nix
|
||||||
];
|
];
|
||||||
options = {
|
options = {
|
||||||
services = mkOption {
|
services = mkOption {
|
||||||
|
|||||||
39
nixos/modules/system/service/systemd/config-data-path.nix
Normal file
39
nixos/modules/system/service/systemd/config-data-path.nix
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
# Tests in: ../../tests/modular-service-etc/test.nix
|
||||||
|
# This module sets the path for configData entries in systemd services
|
||||||
|
let
|
||||||
|
setPathsModule =
|
||||||
|
prefix:
|
||||||
|
{ lib, name, ... }:
|
||||||
|
let
|
||||||
|
inherit (lib) mkOption types;
|
||||||
|
servicePrefix = "${prefix}${name}";
|
||||||
|
in
|
||||||
|
{
|
||||||
|
_class = "service";
|
||||||
|
options = {
|
||||||
|
# Extend portable configData option
|
||||||
|
configData = mkOption {
|
||||||
|
type = types.lazyAttrsOf (
|
||||||
|
types.submodule (
|
||||||
|
{ config, ... }:
|
||||||
|
{
|
||||||
|
config = {
|
||||||
|
path = lib.mkDefault "/etc/system-services/${servicePrefix}/${config.name}";
|
||||||
|
};
|
||||||
|
}
|
||||||
|
)
|
||||||
|
);
|
||||||
|
};
|
||||||
|
services = mkOption {
|
||||||
|
type = types.attrsOf (
|
||||||
|
types.submoduleWith {
|
||||||
|
modules = [
|
||||||
|
(setPathsModule "${servicePrefix}-")
|
||||||
|
];
|
||||||
|
}
|
||||||
|
);
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
in
|
||||||
|
setPathsModule ""
|
||||||
@@ -26,6 +26,27 @@ let
|
|||||||
else
|
else
|
||||||
"${before}-${after}";
|
"${before}-${after}";
|
||||||
|
|
||||||
|
makeNixosEtcFiles =
|
||||||
|
prefix: service:
|
||||||
|
let
|
||||||
|
# Convert configData entries to environment.etc entries
|
||||||
|
serviceConfigData = lib.mapAttrs' (name: cfg: {
|
||||||
|
name =
|
||||||
|
# cfg.path is read only and prefixed with unique service name; see ./config-data-path.nix
|
||||||
|
assert lib.hasPrefix "/etc/system-services" cfg.path;
|
||||||
|
lib.removePrefix "/etc/" cfg.path;
|
||||||
|
value = {
|
||||||
|
inherit (cfg) enable source;
|
||||||
|
};
|
||||||
|
}) (service.configData or { });
|
||||||
|
|
||||||
|
# Recursively process sub-services
|
||||||
|
subServiceConfigData = concatMapAttrs (
|
||||||
|
subServiceName: subService: makeNixosEtcFiles (dash prefix subServiceName) subService
|
||||||
|
) service.services;
|
||||||
|
in
|
||||||
|
serviceConfigData // subServiceConfigData;
|
||||||
|
|
||||||
makeUnits =
|
makeUnits =
|
||||||
unitType: prefix: service:
|
unitType: prefix: service:
|
||||||
concatMapAttrs (unitName: unitModule: {
|
concatMapAttrs (unitName: unitModule: {
|
||||||
@@ -51,6 +72,7 @@ in
|
|||||||
class = "service";
|
class = "service";
|
||||||
modules = [
|
modules = [
|
||||||
./service.nix
|
./service.nix
|
||||||
|
./config-data-path.nix
|
||||||
|
|
||||||
# TODO: Consider removing pkgs. Service modules can provide their own
|
# TODO: Consider removing pkgs. Service modules can provide their own
|
||||||
# dependencies.
|
# dependencies.
|
||||||
@@ -102,5 +124,9 @@ in
|
|||||||
systemd.sockets = concatMapAttrs (
|
systemd.sockets = concatMapAttrs (
|
||||||
serviceName: topLevelService: makeUnits "sockets" serviceName topLevelService
|
serviceName: topLevelService: makeUnits "sockets" serviceName topLevelService
|
||||||
) config.system.services;
|
) config.system.services;
|
||||||
|
|
||||||
|
environment.etc = concatMapAttrs (
|
||||||
|
serviceName: topLevelService: makeNixosEtcFiles serviceName topLevelService
|
||||||
|
) config.system.services;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -918,6 +918,7 @@ in
|
|||||||
modularService = pkgs.callPackage ../modules/system/service/systemd/test.nix {
|
modularService = pkgs.callPackage ../modules/system/service/systemd/test.nix {
|
||||||
inherit evalSystem;
|
inherit evalSystem;
|
||||||
};
|
};
|
||||||
|
modular-service-etc = runTest ./modular-service-etc/test.nix;
|
||||||
molly-brown = runTest ./molly-brown.nix;
|
molly-brown = runTest ./molly-brown.nix;
|
||||||
mollysocket = runTest ./mollysocket.nix;
|
mollysocket = runTest ./mollysocket.nix;
|
||||||
monado = runTest ./monado.nix;
|
monado = runTest ./monado.nix;
|
||||||
|
|||||||
67
nixos/tests/modular-service-etc/python-http-server.nix
Normal file
67
nixos/tests/modular-service-etc/python-http-server.nix
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
# Tests in: ./test.nix
|
||||||
|
# This module provides a basic web server based on the python built-in http.server package.
|
||||||
|
{
|
||||||
|
config,
|
||||||
|
lib,
|
||||||
|
pkgs,
|
||||||
|
...
|
||||||
|
}:
|
||||||
|
let
|
||||||
|
inherit (lib) mkOption types;
|
||||||
|
in
|
||||||
|
{
|
||||||
|
_class = "service";
|
||||||
|
|
||||||
|
options = {
|
||||||
|
python-http-server = {
|
||||||
|
package = mkOption {
|
||||||
|
type = types.package;
|
||||||
|
default = pkgs.python3;
|
||||||
|
description = "Python package to use for the web server";
|
||||||
|
};
|
||||||
|
|
||||||
|
port = mkOption {
|
||||||
|
type = types.port;
|
||||||
|
default = 8000;
|
||||||
|
description = "Port to listen on";
|
||||||
|
};
|
||||||
|
|
||||||
|
directory = mkOption {
|
||||||
|
type = types.str;
|
||||||
|
default = config.configData."webroot".path;
|
||||||
|
defaultText = lib.literalExpression ''config.configData."webroot".path'';
|
||||||
|
description = "Directory to serve files from";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
config = {
|
||||||
|
process.argv = [
|
||||||
|
"${lib.getExe config.python-http-server.package}"
|
||||||
|
"-m"
|
||||||
|
"http.server"
|
||||||
|
"${toString config.python-http-server.port}"
|
||||||
|
"--directory"
|
||||||
|
config.python-http-server.directory
|
||||||
|
];
|
||||||
|
|
||||||
|
configData = {
|
||||||
|
# This should probably just be {} if we were to put this module in production.
|
||||||
|
"webroot" = lib.mkDefault {
|
||||||
|
source = pkgs.runCommand "default-webroot" { } ''
|
||||||
|
mkdir -p $out
|
||||||
|
cat > $out/index.html << 'EOF'
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head><title>Python Web Server</title></head>
|
||||||
|
<body>
|
||||||
|
<h1>Welcome to the Python Web Server</h1>
|
||||||
|
<p>Serving from port ${toString config.python-http-server.port}</p>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
EOF
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
181
nixos/tests/modular-service-etc/test.nix
Normal file
181
nixos/tests/modular-service-etc/test.nix
Normal file
@@ -0,0 +1,181 @@
|
|||||||
|
# Run with:
|
||||||
|
# cd nixpkgs
|
||||||
|
# nix-build -A nixosTests.modular-service-etc
|
||||||
|
|
||||||
|
# This tests the NixOS modular service integration to make sure `etc` entries
|
||||||
|
# are generated correctly for `configData` files.
|
||||||
|
{ lib, ... }:
|
||||||
|
{
|
||||||
|
_class = "nixosTest";
|
||||||
|
name = "modular-service-etc";
|
||||||
|
|
||||||
|
nodes = {
|
||||||
|
server =
|
||||||
|
{ pkgs, ... }:
|
||||||
|
{
|
||||||
|
system.services.webserver = {
|
||||||
|
# The python web server is simple enough that it doesn't need a reload signal.
|
||||||
|
# Other services may need to receive a signal in order to re-read what's in `configData`.
|
||||||
|
imports = [ ./python-http-server.nix ];
|
||||||
|
python-http-server = {
|
||||||
|
port = 8080;
|
||||||
|
};
|
||||||
|
|
||||||
|
# Add a sub-service
|
||||||
|
services.api = {
|
||||||
|
imports = [ ./python-http-server.nix ];
|
||||||
|
python-http-server = {
|
||||||
|
port = 8081;
|
||||||
|
};
|
||||||
|
configData = {
|
||||||
|
"webroot" = {
|
||||||
|
source = pkgs.runCommand "api-webroot" { } ''
|
||||||
|
mkdir -p $out
|
||||||
|
cat > $out/index.html << 'EOF'
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head><title>API Sub-service</title></head>
|
||||||
|
<body>
|
||||||
|
<h1>API Sub-service</h1>
|
||||||
|
<p>This is a sub-service running on port 8081</p>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
EOF
|
||||||
|
cat > $out/status.json << 'EOF'
|
||||||
|
{"status": "ok", "service": "api", "port": 8081}
|
||||||
|
EOF
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
networking.firewall.allowedTCPPorts = [
|
||||||
|
8080
|
||||||
|
8081
|
||||||
|
];
|
||||||
|
|
||||||
|
specialisation.updated.configuration = {
|
||||||
|
system.services.webserver = {
|
||||||
|
configData = {
|
||||||
|
"webroot" = {
|
||||||
|
source = lib.mkForce (
|
||||||
|
pkgs.runCommand "webroot-updated" { } ''
|
||||||
|
mkdir -p $out
|
||||||
|
cat > $out/index.html << 'EOF'
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head><title>Updated Python Web Server</title></head>
|
||||||
|
<body>
|
||||||
|
<h1>Updated content via specialisation</h1>
|
||||||
|
<p>This content was changed without restarting the service</p>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
EOF
|
||||||
|
''
|
||||||
|
);
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
services.api = {
|
||||||
|
configData = {
|
||||||
|
"webroot" = {
|
||||||
|
source = lib.mkForce (
|
||||||
|
pkgs.runCommand "api-webroot-updated" { } ''
|
||||||
|
mkdir -p $out
|
||||||
|
cat > $out/index.html << 'EOF'
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head><title>Updated API Sub-service</title></head>
|
||||||
|
<body>
|
||||||
|
<h1>Updated API Sub-service</h1>
|
||||||
|
<p>This sub-service content was also updated</p>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
EOF
|
||||||
|
cat > $out/status.json << 'EOF'
|
||||||
|
{"status": "updated", "service": "api", "port": 8081, "version": "2.0"}
|
||||||
|
EOF
|
||||||
|
''
|
||||||
|
);
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
client =
|
||||||
|
{ pkgs, ... }:
|
||||||
|
{
|
||||||
|
environment.systemPackages = [ pkgs.curl ];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
testScript = ''
|
||||||
|
start_all()
|
||||||
|
|
||||||
|
server.wait_for_unit("multi-user.target")
|
||||||
|
client.wait_for_unit("multi-user.target")
|
||||||
|
|
||||||
|
# Wait for the web servers to start
|
||||||
|
server.wait_for_unit("webserver.service")
|
||||||
|
server.wait_for_open_port(8080)
|
||||||
|
server.wait_for_unit("webserver-api.service")
|
||||||
|
server.wait_for_open_port(8081)
|
||||||
|
|
||||||
|
# Check that the configData directories were created with unique paths
|
||||||
|
server.succeed("test -d /etc/system-services/webserver/webroot")
|
||||||
|
server.succeed("test -f /etc/system-services/webserver/webroot/index.html")
|
||||||
|
server.succeed("test -d /etc/system-services/webserver-api/webroot")
|
||||||
|
server.succeed("test -f /etc/system-services/webserver-api/webroot/index.html")
|
||||||
|
server.succeed("test -f /etc/system-services/webserver-api/webroot/status.json")
|
||||||
|
|
||||||
|
# Check that the main web server is serving the configData content
|
||||||
|
client.succeed("curl -f http://server:8080/index.html | grep 'Welcome to the Python Web Server'")
|
||||||
|
client.succeed("curl -f http://server:8080/index.html | grep 'Serving from port 8080'")
|
||||||
|
|
||||||
|
# Check that the sub-service is serving its own configData content
|
||||||
|
client.succeed("curl -f http://server:8081/index.html | grep 'API Sub-service'")
|
||||||
|
client.succeed("curl -f http://server:8081/index.html | grep 'This is a sub-service running on port 8081'")
|
||||||
|
client.succeed("curl -f http://server:8081/status.json | grep '\"service\": \"api\"'")
|
||||||
|
|
||||||
|
# Record PIDs before switching to verify services aren't restarted
|
||||||
|
webserver_pid = server.succeed("systemctl show webserver.service --property=MainPID --value").strip()
|
||||||
|
api_pid = server.succeed("systemctl show webserver-api.service --property=MainPID --value").strip()
|
||||||
|
|
||||||
|
print(f"Before switch - webserver PID: {webserver_pid}, api PID: {api_pid}")
|
||||||
|
|
||||||
|
# Switch to the specialisation with updated content
|
||||||
|
switch_output = server.succeed("/run/current-system/specialisation/updated/bin/switch-to-configuration test")
|
||||||
|
print(f"Switch output: {switch_output}")
|
||||||
|
|
||||||
|
# Verify services are not mentioned in the switch output (indicating they weren't touched)
|
||||||
|
assert "webserver.service" not in switch_output, f"webserver.service was mentioned in switch output: {switch_output}"
|
||||||
|
assert "webserver-api.service" not in switch_output, f"webserver-api.service was mentioned in switch output: {switch_output}"
|
||||||
|
|
||||||
|
# Verify the content was updated without restarting the services
|
||||||
|
server.succeed("systemctl is-active webserver.service")
|
||||||
|
server.succeed("systemctl is-active webserver-api.service")
|
||||||
|
|
||||||
|
# Verify PIDs are the same (services weren't restarted)
|
||||||
|
webserver_pid_after = server.succeed("systemctl show webserver.service --property=MainPID --value").strip()
|
||||||
|
api_pid_after = server.succeed("systemctl show webserver-api.service --property=MainPID --value").strip()
|
||||||
|
|
||||||
|
print(f"After switch - webserver PID: {webserver_pid_after}, api PID: {api_pid_after}")
|
||||||
|
|
||||||
|
assert webserver_pid == webserver_pid_after, f"webserver.service was restarted: PID changed from {webserver_pid} to {webserver_pid_after}"
|
||||||
|
assert api_pid == api_pid_after, f"webserver-api.service was restarted: PID changed from {api_pid} to {api_pid_after}"
|
||||||
|
|
||||||
|
# Check main service updated content
|
||||||
|
client.succeed("curl -f http://server:8080/index.html | grep 'Updated content via specialisation'")
|
||||||
|
client.succeed("curl -f http://server:8080/index.html | grep 'This content was changed without restarting the service'")
|
||||||
|
|
||||||
|
# Check sub-service updated content
|
||||||
|
client.succeed("curl -f http://server:8081/index.html | grep 'Updated API Sub-service'")
|
||||||
|
client.succeed("curl -f http://server:8081/index.html | grep 'This sub-service content was also updated'")
|
||||||
|
client.succeed("curl -f http://server:8081/status.json | grep '\"version\": \"2.0\"'")
|
||||||
|
'';
|
||||||
|
|
||||||
|
meta.maintainers = with lib.maintainers; [ roberth ];
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user