From 47b8c8b12951af8355df2a1ca51432ec99c71116 Mon Sep 17 00:00:00 2001 From: Paul Kroeher Date: Mon, 3 Aug 2026 15:41:53 +0200 Subject: [PATCH 01/83] nix: add option to disable destructive database setup Signed-off-by: Paul Kroeher On-behalf-of: SAP paul.kroeher@sap.com --- modules/controller/openstack-controller.nix | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/modules/controller/openstack-controller.nix b/modules/controller/openstack-controller.nix index 3861fe7..09a0561 100644 --- a/modules/controller/openstack-controller.nix +++ b/modules/controller/openstack-controller.nix @@ -9,6 +9,7 @@ }: { config, + lib, pkgs, ... }: @@ -36,9 +37,19 @@ in (import ./cinder.nix { inherit cinder; }) # only cinder management component ]; + options.openstack.manual_setup = lib.mkOption { + type = lib.types.bool; + default = false; + description = '' + Whether to manage OpenStack setup manually. When enabled, automatic + database setup, OpenStack service bootstrapping, and Cinder storage + initialization are disabled. Setup must be performed separately. + ''; + }; + config = { - systemd.services.database-setup = { + systemd.services.database-setup = lib.mkIf (!config.openstack.manual_setup) { description = "OpenStack Database setup"; after = [ "mysql.service" From 3058bbd376e772b4e9bab1a488bfa4697092590a Mon Sep 17 00:00:00 2001 From: Paul Kroeher Date: Mon, 3 Aug 2026 16:56:42 +0200 Subject: [PATCH 02/83] nix: add self build openstack-client to system packages Signed-off-by: Paul Kroeher On-behalf-of: SAP paul.kroeher@sap.com --- modules/controller/openstack-controller.nix | 5 +++++ modules/default.nix | 1 + 2 files changed, 6 insertions(+) diff --git a/modules/controller/openstack-controller.nix b/modules/controller/openstack-controller.nix index 09a0561..0fe21fe 100644 --- a/modules/controller/openstack-controller.nix +++ b/modules/controller/openstack-controller.nix @@ -6,6 +6,7 @@ placement, horizon, cinder, + python-openstackclient, }: { config, @@ -49,6 +50,10 @@ in config = { + environment.systemPackages = [ + python-openstackclient + ]; + systemd.services.database-setup = lib.mkIf (!config.openstack.manual_setup) { description = "OpenStack Database setup"; after = [ diff --git a/modules/default.nix b/modules/default.nix index 179ed5d..ef09b5d 100644 --- a/modules/default.nix +++ b/modules/default.nix @@ -8,6 +8,7 @@ glance horizon cinder + python-openstackclient ; placement = openstackPkgs.openstack-placement; }; From 1854ad6c8ebd6cf4124b0c5a065d4f6de378603c Mon Sep 17 00:00:00 2001 From: Paul Kroeher Date: Tue, 4 Aug 2026 08:39:59 +0200 Subject: [PATCH 03/83] nix: move database setup into a separate script Signed-off-by: Paul Kroeher On-behalf-of: SAP paul.kroeher@sap.com --- modules/controller/openstack-controller.nix | 124 ++++++++++++-------- 1 file changed, 78 insertions(+), 46 deletions(-) diff --git a/modules/controller/openstack-controller.nix b/modules/controller/openstack-controller.nix index 0fe21fe..64df2e9 100644 --- a/modules/controller/openstack-controller.nix +++ b/modules/controller/openstack-controller.nix @@ -24,6 +24,80 @@ let OS_AUTH_URL = "http://controller:5000/v3"; OS_IDENTITY_API_VERSION = "3"; }; + adminEnvScript = pkgs.writeShellScript "openstack-admin-env" ( + lib.concatStringsSep "\n" ( + lib.mapAttrsToList (name: value: "export ${name}=${lib.escapeShellArg value}") adminEnv + ) + ); + + # Keep manual provisioning commands and systemd units on the same store scripts. + setupCommand = + name: script: + pkgs.writeShellApplication { + inherit name; + runtimeInputs = [ + pkgs.coreutils + pkgs.systemd + ]; + text = '' + if (( EUID != 0 )); then + echo "Run this command as root." >&2 + exit 1 + fi + # shellcheck source=/dev/null + source ${adminEnvScript} + exec ${script} "$@" + ''; + }; + + databaseSetupScript = pkgs.writeShellScript "database-setup.sh" '' + export PATH=${lib.makeBinPath [ pkgs.mariadb ]}:$PATH + + # Keystone + mysql -N -e "drop database keystone;" || true + mysql -N -e "create database keystone;" || true + mysql -N -e "GRANT ALL PRIVILEGES ON keystone.* TO 'keystone'@'localhost' IDENTIFIED BY 'keystone';" + mysql -N -e "GRANT ALL PRIVILEGES ON keystone.* TO 'keystone'@'%' IDENTIFIED BY 'keystone';" + + # Glance + mysql -N -e "drop database glance;" || true + mysql -N -e "create database glance;" || true + mysql -N -e "GRANT ALL PRIVILEGES ON glance.* TO 'glance'@'localhost' IDENTIFIED BY 'glance';" + mysql -N -e "GRANT ALL PRIVILEGES ON glance.* TO 'glance'@'%' IDENTIFIED BY 'glance';" + + # Cinder + mysql -N -e "drop database cinder;" || true + mysql -N -e "create database cinder;" || true + mysql -N -e "GRANT ALL PRIVILEGES ON cinder.* TO 'cinder'@'localhost' IDENTIFIED BY 'cinder';" + mysql -N -e "GRANT ALL PRIVILEGES ON cinder.* TO 'cinder'@'%' IDENTIFIED BY 'cinder';" + + # Placement + mysql -N -e "drop database placement;" || true + mysql -N -e "create database placement;" || true + mysql -N -e "GRANT ALL PRIVILEGES ON placement.* TO 'placement'@'localhost' IDENTIFIED BY 'placement';" + mysql -N -e "GRANT ALL PRIVILEGES ON placement.* TO 'placement'@'%' IDENTIFIED BY 'placement';" + + # Nova + mysql -N -e "drop database nova_api;" || true + mysql -N -e "drop database nova;" || true + mysql -N -e "drop database nova_cell0;" || true + mysql -N -e "create database nova_api;" || true + mysql -N -e "create database nova;" || true + mysql -N -e "create database nova_cell0;" || true + + mysql -N -e "GRANT ALL PRIVILEGES ON nova_api.* TO 'nova'@'localhost' IDENTIFIED BY 'nova';" + mysql -N -e "GRANT ALL PRIVILEGES ON nova_api.* TO 'nova'@'%' IDENTIFIED BY 'nova';" + mysql -N -e "GRANT ALL PRIVILEGES ON nova.* TO 'nova'@'localhost' IDENTIFIED BY 'nova';" + mysql -N -e "GRANT ALL PRIVILEGES ON nova.* TO 'nova'@'%' IDENTIFIED BY 'nova';" + mysql -N -e "GRANT ALL PRIVILEGES ON nova_cell0.* TO 'nova'@'localhost' IDENTIFIED BY 'nova';" + mysql -N -e "GRANT ALL PRIVILEGES ON nova_cell0.* TO 'nova'@'%' IDENTIFIED BY 'nova';" + + # Neutron + mysql -N -e "drop database neutron;" || true + mysql -N -e "create database neutron;" || true + mysql -N -e "GRANT ALL PRIVILEGES ON neutron.* TO 'neutron'@'localhost' IDENTIFIED BY 'neutron';" + mysql -N -e "GRANT ALL PRIVILEGES ON neutron.* TO 'neutron'@'%' IDENTIFIED BY 'neutron';" + ''; in { imports = [ @@ -51,6 +125,7 @@ in config = { environment.systemPackages = [ + (setupCommand "openstack-database-setup" databaseSetupScript) python-openstackclient ]; @@ -63,54 +138,11 @@ in ]; wantedBy = [ "multi-user.target" ]; path = [ pkgs.mariadb ]; + restartIfChanged = false; serviceConfig = { + RemainAfterExit = true; Type = "oneshot"; - ExecStart = pkgs.writeShellScript "database-setup.sh" '' - # Keystone - mysql -N -e "drop database keystone;" || true - mysql -N -e "create database keystone;" || true - mysql -N -e "GRANT ALL PRIVILEGES ON keystone.* TO 'keystone'@'localhost' IDENTIFIED BY 'keystone';" - mysql -N -e "GRANT ALL PRIVILEGES ON keystone.* TO 'keystone'@'%' IDENTIFIED BY 'keystone';" - - # Glance - mysql -N -e "drop database glance;" || true - mysql -N -e "create database glance;" || true - mysql -N -e "GRANT ALL PRIVILEGES ON glance.* TO 'glance'@'localhost' IDENTIFIED BY 'glance';" - mysql -N -e "GRANT ALL PRIVILEGES ON glance.* TO 'glance'@'%' IDENTIFIED BY 'glance';" - - # Cinder - mysql -N -e "drop database cinder;" || true - mysql -N -e "create database cinder;" || true - mysql -N -e "GRANT ALL PRIVILEGES ON cinder.* TO 'cinder'@'localhost' IDENTIFIED BY 'cinder';" - mysql -N -e "GRANT ALL PRIVILEGES ON cinder.* TO 'cinder'@'%' IDENTIFIED BY 'cinder';" - - # Placement - mysql -N -e "drop database placement;" || true - mysql -N -e "create database placement;" || true - mysql -N -e "GRANT ALL PRIVILEGES ON placement.* TO 'placement'@'localhost' IDENTIFIED BY 'placement';" - mysql -N -e "GRANT ALL PRIVILEGES ON placement.* TO 'placement'@'%' IDENTIFIED BY 'placement';" - - # Nova - mysql -N -e "drop database nova_api;" || true - mysql -N -e "drop database nova;" || true - mysql -N -e "drop database nova_cell0;" || true - mysql -N -e "create database nova_api;" || true - mysql -N -e "create database nova;" || true - mysql -N -e "create database nova_cell0;" || true - - mysql -N -e "GRANT ALL PRIVILEGES ON nova_api.* TO 'nova'@'localhost' IDENTIFIED BY 'nova';" - mysql -N -e "GRANT ALL PRIVILEGES ON nova_api.* TO 'nova'@'%' IDENTIFIED BY 'nova';" - mysql -N -e "GRANT ALL PRIVILEGES ON nova.* TO 'nova'@'localhost' IDENTIFIED BY 'nova';" - mysql -N -e "GRANT ALL PRIVILEGES ON nova.* TO 'nova'@'%' IDENTIFIED BY 'nova';" - mysql -N -e "GRANT ALL PRIVILEGES ON nova_cell0.* TO 'nova'@'localhost' IDENTIFIED BY 'nova';" - mysql -N -e "GRANT ALL PRIVILEGES ON nova_cell0.* TO 'nova'@'%' IDENTIFIED BY 'nova';" - - # Neutron - mysql -N -e "drop database neutron;" || true - mysql -N -e "create database neutron;" || true - mysql -N -e "GRANT ALL PRIVILEGES ON neutron.* TO 'neutron'@'localhost' IDENTIFIED BY 'neutron';" - mysql -N -e "GRANT ALL PRIVILEGES ON neutron.* TO 'neutron'@'%' IDENTIFIED BY 'neutron';" - ''; + ExecStart = "${databaseSetupScript}"; }; }; From 202396d71608b3e04de375318da6109bf42aeaa6 Mon Sep 17 00:00:00 2001 From: Paul Kroeher Date: Tue, 4 Aug 2026 09:18:05 +0200 Subject: [PATCH 04/83] nix: move keystone setup into a separate script Signed-off-by: Paul Kroeher On-behalf-of: SAP paul.kroeher@sap.com --- modules/controller/openstack-controller.nix | 62 +++++++++++++++------ 1 file changed, 44 insertions(+), 18 deletions(-) diff --git a/modules/controller/openstack-controller.nix b/modules/controller/openstack-controller.nix index 64df2e9..d381ba8 100644 --- a/modules/controller/openstack-controller.nix +++ b/modules/controller/openstack-controller.nix @@ -24,6 +24,7 @@ let OS_AUTH_URL = "http://controller:5000/v3"; OS_IDENTITY_API_VERSION = "3"; }; + adminEnvScript = pkgs.writeShellScript "openstack-admin-env" ( lib.concatStringsSep "\n" ( lib.mapAttrsToList (name: value: "export ${name}=${lib.escapeShellArg value}") adminEnv @@ -98,6 +99,42 @@ let mysql -N -e "GRANT ALL PRIVILEGES ON neutron.* TO 'neutron'@'localhost' IDENTIFIED BY 'neutron';" mysql -N -e "GRANT ALL PRIVILEGES ON neutron.* TO 'neutron'@'%' IDENTIFIED BY 'neutron';" ''; + + keystonePreStartScript = pkgs.writeShellScript "keystone-all-pre-start.sh" '' + export PATH=${ + lib.makeBinPath [ + keystone + pkgs.coreutils + ] + }:$PATH + + # Initialise the database + keystone-manage --config-file ${config.keystone.config} db_sync + # Set up the keystone's PKI infrastructure + keystone-manage --config-file ${config.keystone.config} fernet_setup --keystone-user keystone --keystone-group keystone + keystone-manage --config-file ${config.keystone.config} credential_setup --keystone-user keystone --keystone-group keystone + chown -R keystone:keystone /etc/keystone + chown -R keystone:keystone /var/log/keystone + ''; + + keystoneStartScript = pkgs.writeShellScript "keystone-all.sh" '' + export PATH=${ + lib.makeBinPath [ + keystone + pkgs.openstackclient + pkgs.util-linux + ] + }:$PATH + + exec runuser --user keystone --preserve-environment -- ${pkgs.runtimeShell} <<'EOF' + set -euxo pipefail + keystone-manage --config-file ${config.keystone.config} bootstrap \ + --bootstrap-password admin\ + --bootstrap-region-id RegionOne + openstack project create --domain default --description "Service Project" service + EOF + ''; + in { imports = [ @@ -126,6 +163,8 @@ in environment.systemPackages = [ (setupCommand "openstack-database-setup" databaseSetupScript) + (setupCommand "openstack-keystone-pre-setup" keystonePreStartScript) + (setupCommand "openstack-keystone-setup" keystoneStartScript) python-openstackclient ]; @@ -146,7 +185,7 @@ in }; }; - systemd.services.keystone-all = { + systemd.services.keystone-all = lib.mkIf (!config.openstack.manual_setup) { description = "OpenStack Keystone Daemon"; after = [ "database-setup.service" ]; path = [ @@ -155,27 +194,14 @@ in ]; environment = adminEnv; wantedBy = [ "multi-user.target" ]; - preStart = '' - # Initialise the database - keystone-manage --config-file ${config.keystone.config} db_sync - # Set up the keystone's PKI infrastructure - keystone-manage --config-file ${config.keystone.config} fernet_setup --keystone-user keystone --keystone-group keystone - keystone-manage --config-file ${config.keystone.config} credential_setup --keystone-user keystone --keystone-group keystone - chown -R keystone:keystone /etc/keystone - chown -R keystone:keystone /var/log/keystone - ''; + restartIfChanged = false; serviceConfig = { - PermissionsStartOnly = true; User = "keystone"; Group = "keystone"; + RemainAfterExit = true; Type = "oneshot"; - ExecStart = pkgs.writeShellScript "keystone-all.sh" '' - set -euxo pipefail - keystone-manage --config-file ${config.keystone.config} bootstrap \ - --bootstrap-password admin\ - --bootstrap-region-id RegionOne - openstack project create --domain default --description "Service Project" service - ''; + ExecStartPre = "+${keystonePreStartScript}"; + ExecStart = "+${keystoneStartScript}"; }; }; From 99773bbb1cfd0aa4c2b8f403369a31bfb581808f Mon Sep 17 00:00:00 2001 From: Paul Kroeher Date: Tue, 4 Aug 2026 09:21:36 +0200 Subject: [PATCH 05/83] nix: move glance setup into a separate script Signed-off-by: Paul Kroeher On-behalf-of: SAP paul.kroeher@sap.com --- modules/controller/openstack-controller.nix | 31 +++++++++++++++------ 1 file changed, 23 insertions(+), 8 deletions(-) diff --git a/modules/controller/openstack-controller.nix b/modules/controller/openstack-controller.nix index d381ba8..33b84c0 100644 --- a/modules/controller/openstack-controller.nix +++ b/modules/controller/openstack-controller.nix @@ -135,6 +135,24 @@ let EOF ''; + glanceStartScript = pkgs.writeShellScript "glance.sh" '' + export PATH=${ + lib.makeBinPath [ + glance + pkgs.openstackclient + pkgs.util-linux + ] + }:$PATH + + exec runuser --user glance --preserve-environment -- ${pkgs.runtimeShell} <<'EOF' + set -euxo pipefail + openstack user create --domain default --password glance glance + openstack role add --project service --user glance admin + openstack role add --user glance --user-domain default --system all reader + glance-manage --config-file ${config.glance.config} db_sync + EOF + ''; + in { imports = [ @@ -165,6 +183,7 @@ in (setupCommand "openstack-database-setup" databaseSetupScript) (setupCommand "openstack-keystone-pre-setup" keystonePreStartScript) (setupCommand "openstack-keystone-setup" keystoneStartScript) + (setupCommand "openstack-glance-setup" glanceStartScript) python-openstackclient ]; @@ -205,7 +224,7 @@ in }; }; - systemd.services.glance = { + systemd.services.glance = lib.mkIf (!config.openstack.manual_setup) { description = "OpenStack Glance setup"; after = [ "keystone-all.service" ]; wantedBy = [ "multi-user.target" ]; @@ -214,17 +233,13 @@ in pkgs.openstackclient glance ]; + restartIfChanged = false; serviceConfig = { + RemainAfterExit = true; Type = "oneshot"; User = "glance"; Group = "glance"; - ExecStart = pkgs.writeShellScript "glance.sh" '' - set -euxo pipefail - openstack user create --domain default --password glance glance - openstack role add --project service --user glance admin - openstack role add --user glance --user-domain default --system all reader - glance-manage --config-file ${config.glance.config} db_sync - ''; + ExecStart = "+${glanceStartScript}"; }; }; From 37173fc0d433f528db45d6dd0471c321ed71bc48 Mon Sep 17 00:00:00 2001 From: Paul Kroeher Date: Tue, 4 Aug 2026 09:57:40 +0200 Subject: [PATCH 06/83] nix: move cinder setup into a separate script Signed-off-by: Paul Kroeher On-behalf-of: SAP paul.kroeher@sap.com --- modules/controller/openstack-controller.nix | 31 +++++++++++++++------ 1 file changed, 23 insertions(+), 8 deletions(-) diff --git a/modules/controller/openstack-controller.nix b/modules/controller/openstack-controller.nix index 33b84c0..12c6d74 100644 --- a/modules/controller/openstack-controller.nix +++ b/modules/controller/openstack-controller.nix @@ -153,6 +153,24 @@ let EOF ''; + cinderStartScript = pkgs.writeShellScript "cinder.sh" '' + export PATH=${ + lib.makeBinPath [ + cinder + pkgs.openstackclient + pkgs.util-linux + ] + }:$PATH + + exec runuser --user cinder --preserve-environment -- ${pkgs.runtimeShell} <<'EOF' + set -euxo pipefail + openstack user create --domain default --password cinder cinder || true + openstack role add --project service --user cinder admin || true + openstack role add --user cinder --user-domain default --system all reader || true + cinder-manage --config-file ${config.cinder.config} db sync + EOF + ''; + in { imports = [ @@ -184,6 +202,7 @@ in (setupCommand "openstack-keystone-pre-setup" keystonePreStartScript) (setupCommand "openstack-keystone-setup" keystoneStartScript) (setupCommand "openstack-glance-setup" glanceStartScript) + (setupCommand "openstack-cinder-setup" cinderStartScript) python-openstackclient ]; @@ -243,7 +262,7 @@ in }; }; - systemd.services.cinder = { + systemd.services.cinder = lib.mkIf (!config.openstack.manual_setup) { description = "OpenStack Cinder setup"; after = [ "keystone-all.service" ]; wantedBy = [ "multi-user.target" ]; @@ -252,17 +271,13 @@ in pkgs.openstackclient cinder ]; + restartIfChanged = false; serviceConfig = { + RemainAfterExit = true; Type = "oneshot"; User = "cinder"; Group = "cinder"; - ExecStart = pkgs.writeShellScript "cinder.sh" '' - set -euxo pipefail - openstack user create --domain default --password cinder cinder || true - openstack role add --project service --user cinder admin || true - openstack role add --user cinder --user-domain default --system all reader || true - cinder-manage --config-file ${config.cinder.config} db sync - ''; + ExecStart = "+${cinderStartScript}"; }; }; From 48823759d59afe595ffb6d239659a00634579270 Mon Sep 17 00:00:00 2001 From: Paul Kroeher Date: Tue, 4 Aug 2026 09:59:22 +0200 Subject: [PATCH 07/83] nix: move placement setup into a separate script Signed-off-by: Paul Kroeher On-behalf-of: SAP paul.kroeher@sap.com --- modules/controller/openstack-controller.nix | 29 ++++++++++++++++----- 1 file changed, 22 insertions(+), 7 deletions(-) diff --git a/modules/controller/openstack-controller.nix b/modules/controller/openstack-controller.nix index 12c6d74..ca8e824 100644 --- a/modules/controller/openstack-controller.nix +++ b/modules/controller/openstack-controller.nix @@ -171,6 +171,23 @@ let EOF ''; + placementStartScript = pkgs.writeShellScript "placement.sh" '' + export PATH=${ + lib.makeBinPath [ + placement + pkgs.openstackclient + pkgs.util-linux + ] + }:$PATH + + exec runuser --user placement --preserve-environment -- ${pkgs.runtimeShell} <<'EOF' + set -euxo pipefail + openstack user create --domain default --password placement placement + openstack role add --project service --user placement admin + placement-manage --config-file ${config.placement.config} db sync + EOF + ''; + in { imports = [ @@ -203,6 +220,7 @@ in (setupCommand "openstack-keystone-setup" keystoneStartScript) (setupCommand "openstack-glance-setup" glanceStartScript) (setupCommand "openstack-cinder-setup" cinderStartScript) + (setupCommand "openstack-placement-setup" placementStartScript) python-openstackclient ]; @@ -284,7 +302,7 @@ in # Placement service can be tested by executing # curl http://controller:8778 # and receive some json with version info as result. - systemd.services.placement = { + systemd.services.placement = lib.mkIf (!config.openstack.manual_setup) { description = "OpenStack Placement setup"; after = [ "glance.service" ]; requiredBy = [ "multi-user.target" ]; @@ -293,16 +311,13 @@ in pkgs.openstackclient placement ]; + restartIfChanged = false; serviceConfig = { + RemainAfterExit = true; Type = "oneshot"; User = "placement"; Group = "placement"; - ExecStart = pkgs.writeShellScript "placement.sh" '' - set -euxo pipefail - openstack user create --domain default --password placement placement - openstack role add --project service --user placement admin - placement-manage --config-file ${config.placement.config} db sync - ''; + ExecStart = "+${placementStartScript}"; }; }; From b7e5d5c957f4c5a1d33c26ea9320f9f64b8a1189 Mon Sep 17 00:00:00 2001 From: Paul Kroeher Date: Tue, 4 Aug 2026 10:01:11 +0200 Subject: [PATCH 08/83] nix: move nova setup into a separate script Signed-off-by: Paul Kroeher On-behalf-of: SAP paul.kroeher@sap.com --- modules/controller/openstack-controller.nix | 35 +++++++++++++++------ 1 file changed, 25 insertions(+), 10 deletions(-) diff --git a/modules/controller/openstack-controller.nix b/modules/controller/openstack-controller.nix index ca8e824..d83add9 100644 --- a/modules/controller/openstack-controller.nix +++ b/modules/controller/openstack-controller.nix @@ -188,6 +188,26 @@ let EOF ''; + novaStartScript = pkgs.writeShellScript "nova.sh" '' + export PATH=${ + lib.makeBinPath [ + nova + pkgs.openstackclient + pkgs.util-linux + ] + }:$PATH + + exec runuser --user nova --preserve-environment -- ${pkgs.runtimeShell} <<'EOF' + set -euxo pipefail + openstack user create --domain default --password nova nova + openstack role add --project service --user nova admin + nova-manage --config-file ${config.nova.config} api_db sync + nova-manage --config-file ${config.nova.config} cell_v2 map_cell0 + nova-manage --config-file ${config.nova.config} cell_v2 create_cell --name=cell1 --verbose + nova-manage --config-file ${config.nova.config} db sync + EOF + ''; + in { imports = [ @@ -221,6 +241,7 @@ in (setupCommand "openstack-glance-setup" glanceStartScript) (setupCommand "openstack-cinder-setup" cinderStartScript) (setupCommand "openstack-placement-setup" placementStartScript) + (setupCommand "openstack-nova-setup" novaStartScript) python-openstackclient ]; @@ -321,7 +342,7 @@ in }; }; - systemd.services.nova = { + systemd.services.nova = lib.mkIf (!config.openstack.manual_setup) { description = "OpenStack Nova setup"; after = [ "neutron.service" ]; wantedBy = [ "multi-user.target" ]; @@ -330,19 +351,13 @@ in pkgs.openstackclient nova ]; + restartIfChanged = false; serviceConfig = { + RemainAfterExit = true; Type = "oneshot"; User = "nova"; Group = "nova"; - ExecStart = pkgs.writeShellScript "nova.sh" '' - set -euxo pipefail - openstack user create --domain default --password nova nova - openstack role add --project service --user nova admin - nova-manage --config-file ${config.nova.config} api_db sync - nova-manage --config-file ${config.nova.config} cell_v2 map_cell0 - nova-manage --config-file ${config.nova.config} cell_v2 create_cell --name=cell1 --verbose - nova-manage --config-file ${config.nova.config} db sync - ''; + ExecStart = "+${novaStartScript}"; }; }; From 2a693a66cc1179d12b0ce672864cf8938baee369 Mon Sep 17 00:00:00 2001 From: Paul Kroeher Date: Tue, 4 Aug 2026 10:03:01 +0200 Subject: [PATCH 09/83] nix: move neutron setup into a separate script Signed-off-by: Paul Kroeher On-behalf-of: SAP paul.kroeher@sap.com --- modules/controller/openstack-controller.nix | 27 ++++++++++++++++----- 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/modules/controller/openstack-controller.nix b/modules/controller/openstack-controller.nix index d83add9..f7334e5 100644 --- a/modules/controller/openstack-controller.nix +++ b/modules/controller/openstack-controller.nix @@ -208,6 +208,23 @@ let EOF ''; + neutronStartScript = pkgs.writeShellScript "neutron.sh" '' + export PATH=${ + lib.makeBinPath [ + neutron + pkgs.openstackclient + pkgs.util-linux + ] + }:$PATH + + exec runuser --user neutron --preserve-environment -- ${pkgs.runtimeShell} <<'EOF' + set -euxo pipefail + openstack user create --domain default --password neutron neutron + openstack role add --project service --user neutron admin + neutron-db-manage --config-file ${config.neutron.config} --config-file ${config.neutron.ml2Config} upgrade head + EOF + ''; + in { imports = [ @@ -242,6 +259,7 @@ in (setupCommand "openstack-cinder-setup" cinderStartScript) (setupCommand "openstack-placement-setup" placementStartScript) (setupCommand "openstack-nova-setup" novaStartScript) + (setupCommand "openstack-neutron-setup" neutronStartScript) python-openstackclient ]; @@ -370,16 +388,13 @@ in pkgs.openstackclient neutron ]; + restartIfChanged = false; serviceConfig = { + RemainAfterExit = true; Type = "oneshot"; User = "neutron"; Group = "neutron"; - ExecStart = pkgs.writeShellScript "neutron.sh" '' - set -euxo pipefail - openstack user create --domain default --password neutron neutron - openstack role add --project service --user neutron admin - neutron-db-manage --config-file ${config.neutron.config} --config-file ${config.neutron.ml2Config} upgrade head - ''; + ExecStart = "+${neutronStartScript}"; }; }; }; From 38b8f514db71ba4b79d2b7787616b234999fd5b5 Mon Sep 17 00:00:00 2001 From: Paul Kroeher Date: Tue, 4 Aug 2026 15:57:32 +0200 Subject: [PATCH 10/83] nix: rename generated scripts Signed-off-by: Paul Kroeher On-behalf-of: SAP paul.kroeher@sap.com --- modules/controller/openstack-controller.nix | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/modules/controller/openstack-controller.nix b/modules/controller/openstack-controller.nix index f7334e5..9918ebc 100644 --- a/modules/controller/openstack-controller.nix +++ b/modules/controller/openstack-controller.nix @@ -98,6 +98,8 @@ let mysql -N -e "create database neutron;" || true mysql -N -e "GRANT ALL PRIVILEGES ON neutron.* TO 'neutron'@'localhost' IDENTIFIED BY 'neutron';" mysql -N -e "GRANT ALL PRIVILEGES ON neutron.* TO 'neutron'@'%' IDENTIFIED BY 'neutron';" + + mysql -N -e "FLUSH PRIVILEGES;" ''; keystonePreStartScript = pkgs.writeShellScript "keystone-all-pre-start.sh" '' @@ -129,9 +131,9 @@ let exec runuser --user keystone --preserve-environment -- ${pkgs.runtimeShell} <<'EOF' set -euxo pipefail keystone-manage --config-file ${config.keystone.config} bootstrap \ - --bootstrap-password admin\ + --bootstrap-password admin \ --bootstrap-region-id RegionOne - openstack project create --domain default --description "Service Project" service + openstack project create --domain default --description "Service Project" service EOF ''; From 4dc51723cb54cc1e5d0f20af8f9998108bf64db6 Mon Sep 17 00:00:00 2001 From: Paul Kroeher Date: Wed, 5 Aug 2026 11:54:18 +0200 Subject: [PATCH 11/83] nix: split database script into create and cleanup Signed-off-by: Paul Kroeher On-behalf-of: SAP paul.kroeher@sap.com --- modules/controller/openstack-controller.nix | 76 ++++++++++++--------- 1 file changed, 42 insertions(+), 34 deletions(-) diff --git a/modules/controller/openstack-controller.nix b/modules/controller/openstack-controller.nix index 9918ebc..eb88a9e 100644 --- a/modules/controller/openstack-controller.nix +++ b/modules/controller/openstack-controller.nix @@ -31,6 +31,18 @@ let ) ); + databaseCleanupScript = pkgs.writeShellScript "database-cleanup.sh" '' + export PATH=${lib.makeBinPath [ pkgs.mariadb ]}:$PATH + mysql -N -e "drop database keystone;" || true + mysql -N -e "drop database glance;" || true + mysql -N -e "drop database cinder;" || true + mysql -N -e "drop database placement;" || true + mysql -N -e "drop database nova_api;" || true + mysql -N -e "drop database nova;" || true + mysql -N -e "drop database nova_cell0;" || true + mysql -N -e "drop database neutron;" || true + ''; + # Keep manual provisioning commands and systemd units on the same store scripts. setupCommand = name: script: @@ -55,49 +67,44 @@ let export PATH=${lib.makeBinPath [ pkgs.mariadb ]}:$PATH # Keystone - mysql -N -e "drop database keystone;" || true - mysql -N -e "create database keystone;" || true - mysql -N -e "GRANT ALL PRIVILEGES ON keystone.* TO 'keystone'@'localhost' IDENTIFIED BY 'keystone';" - mysql -N -e "GRANT ALL PRIVILEGES ON keystone.* TO 'keystone'@'%' IDENTIFIED BY 'keystone';" + mysql -N -e "CREATE DATABASE IF NOT EXISTS keystone;" + mysql -N -e "CREATE USER IF NOT EXISTS 'keystone'@'%' IDENTIFIED BY 'keystone';" + mysql -N -e "ALTER USER 'keystone'@'%' IDENTIFIED BY 'keystone';" + mysql -N -e "GRANT ALL PRIVILEGES ON keystone.* TO 'keystone'@'%';" # Glance - mysql -N -e "drop database glance;" || true - mysql -N -e "create database glance;" || true - mysql -N -e "GRANT ALL PRIVILEGES ON glance.* TO 'glance'@'localhost' IDENTIFIED BY 'glance';" - mysql -N -e "GRANT ALL PRIVILEGES ON glance.* TO 'glance'@'%' IDENTIFIED BY 'glance';" + mysql -N -e "CREATE DATABASE IF NOT EXISTS glance;" + mysql -N -e "CREATE USER IF NOT EXISTS 'glance'@'%' IDENTIFIED BY 'glance';" + mysql -N -e "ALTER USER 'glance'@'%' IDENTIFIED BY 'glance';" + mysql -N -e "GRANT ALL PRIVILEGES ON glance.* TO 'glance'@'%';" # Cinder - mysql -N -e "drop database cinder;" || true - mysql -N -e "create database cinder;" || true - mysql -N -e "GRANT ALL PRIVILEGES ON cinder.* TO 'cinder'@'localhost' IDENTIFIED BY 'cinder';" - mysql -N -e "GRANT ALL PRIVILEGES ON cinder.* TO 'cinder'@'%' IDENTIFIED BY 'cinder';" + mysql -N -e "CREATE DATABASE IF NOT EXISTS cinder;" + mysql -N -e "CREATE USER IF NOT EXISTS 'cinder'@'%' IDENTIFIED BY 'cinder';" + mysql -N -e "ALTER USER 'cinder'@'%' IDENTIFIED BY 'cinder';" + mysql -N -e "GRANT ALL PRIVILEGES ON cinder.* TO 'cinder'@'%';" # Placement - mysql -N -e "drop database placement;" || true - mysql -N -e "create database placement;" || true - mysql -N -e "GRANT ALL PRIVILEGES ON placement.* TO 'placement'@'localhost' IDENTIFIED BY 'placement';" - mysql -N -e "GRANT ALL PRIVILEGES ON placement.* TO 'placement'@'%' IDENTIFIED BY 'placement';" + mysql -N -e "CREATE DATABASE IF NOT EXISTS placement;" + mysql -N -e "CREATE USER IF NOT EXISTS 'placement'@'%' IDENTIFIED BY 'placement';" + mysql -N -e "ALTER USER 'placement'@'%' IDENTIFIED BY 'placement';" + mysql -N -e "GRANT ALL PRIVILEGES ON placement.* TO 'placement'@'%';" # Nova - mysql -N -e "drop database nova_api;" || true - mysql -N -e "drop database nova;" || true - mysql -N -e "drop database nova_cell0;" || true - mysql -N -e "create database nova_api;" || true - mysql -N -e "create database nova;" || true - mysql -N -e "create database nova_cell0;" || true - - mysql -N -e "GRANT ALL PRIVILEGES ON nova_api.* TO 'nova'@'localhost' IDENTIFIED BY 'nova';" - mysql -N -e "GRANT ALL PRIVILEGES ON nova_api.* TO 'nova'@'%' IDENTIFIED BY 'nova';" - mysql -N -e "GRANT ALL PRIVILEGES ON nova.* TO 'nova'@'localhost' IDENTIFIED BY 'nova';" - mysql -N -e "GRANT ALL PRIVILEGES ON nova.* TO 'nova'@'%' IDENTIFIED BY 'nova';" - mysql -N -e "GRANT ALL PRIVILEGES ON nova_cell0.* TO 'nova'@'localhost' IDENTIFIED BY 'nova';" - mysql -N -e "GRANT ALL PRIVILEGES ON nova_cell0.* TO 'nova'@'%' IDENTIFIED BY 'nova';" + mysql -N -e "CREATE DATABASE IF NOT EXISTS nova_api;" + mysql -N -e "CREATE DATABASE IF NOT EXISTS nova;" + mysql -N -e "CREATE DATABASE IF NOT EXISTS nova_cell0;" + mysql -N -e "CREATE USER IF NOT EXISTS 'nova'@'%' IDENTIFIED BY 'nova';" + mysql -N -e "ALTER USER 'nova'@'%' IDENTIFIED BY 'nova';" + mysql -N -e "GRANT ALL PRIVILEGES ON nova_api.* TO 'nova'@'%';" + mysql -N -e "GRANT ALL PRIVILEGES ON nova.* TO 'nova'@'%';" + mysql -N -e "GRANT ALL PRIVILEGES ON nova_cell0.* TO 'nova'@'%';" # Neutron - mysql -N -e "drop database neutron;" || true - mysql -N -e "create database neutron;" || true - mysql -N -e "GRANT ALL PRIVILEGES ON neutron.* TO 'neutron'@'localhost' IDENTIFIED BY 'neutron';" - mysql -N -e "GRANT ALL PRIVILEGES ON neutron.* TO 'neutron'@'%' IDENTIFIED BY 'neutron';" + mysql -N -e "CREATE DATABASE IF NOT EXISTS neutron;" + mysql -N -e "CREATE USER IF NOT EXISTS 'neutron'@'%' IDENTIFIED BY 'neutron';" + mysql -N -e "ALTER USER 'neutron'@'%' IDENTIFIED BY 'neutron';" + mysql -N -e "GRANT ALL PRIVILEGES ON neutron.* TO 'neutron'@'%';" mysql -N -e "FLUSH PRIVILEGES;" ''; @@ -254,6 +261,7 @@ in config = { environment.systemPackages = [ + (setupCommand "openstack-database-cleanup" databaseCleanupScript) (setupCommand "openstack-database-setup" databaseSetupScript) (setupCommand "openstack-keystone-pre-setup" keystonePreStartScript) (setupCommand "openstack-keystone-setup" keystoneStartScript) @@ -381,7 +389,7 @@ in }; }; - systemd.services.neutron = { + systemd.services.neutron = lib.mkIf (!config.openstack.manual_setup) { description = "OpenStack Neutron setup"; after = [ "placement.service" ]; wantedBy = [ "multi-user.target" ]; From ea898693794b0b1a5cace004fcb52f5018420b0f Mon Sep 17 00:00:00 2001 From: Paul Kroeher Date: Wed, 5 Aug 2026 15:07:25 +0200 Subject: [PATCH 12/83] nix: disable services but create all configurations and systemd units Signed-off-by: Paul Kroeher On-behalf-of: SAP paul.kroeher@sap.com --- modules/controller/cinder.nix | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/modules/controller/cinder.nix b/modules/controller/cinder.nix index 55b31a7..0dd4f2b 100644 --- a/modules/controller/cinder.nix +++ b/modules/controller/cinder.nix @@ -57,7 +57,7 @@ in ''; }; }; - config = mkIf cfg.enable { + config = { users.extraUsers.cinder = { group = "cinder"; @@ -121,6 +121,7 @@ in .cinder-wsgi-wrapped --port 8776 ''; }; + enable = cfg.enable; }; systemd.services.cinder-scheduler = { @@ -140,6 +141,7 @@ in .cinder-scheduler-wrapped ''; }; + enable = cfg.enable; }; }; } From f4948978f30bb191edb6561fc739e8e4a252bd5f Mon Sep 17 00:00:00 2001 From: Paul Kroeher Date: Wed, 5 Aug 2026 15:07:36 +0200 Subject: [PATCH 13/83] nix: disable services but create all configurations and systemd units Signed-off-by: Paul Kroeher On-behalf-of: SAP paul.kroeher@sap.com --- modules/controller/neutron.nix | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/modules/controller/neutron.nix b/modules/controller/neutron.nix index 448d38f..0e02d48 100644 --- a/modules/controller/neutron.nix +++ b/modules/controller/neutron.nix @@ -172,7 +172,7 @@ in ''; }; }; - config = mkIf cfg.enable { + config = { users.extraUsers.neutron = { group = "neutron"; @@ -252,6 +252,7 @@ in serviceConfig = { ExecStart = "${neutron}/bin/neutron-metadata-agent --config-file=${cfg.config}"; }; + enable = cfg.enable; }; virtualisation.vswitch = { @@ -291,6 +292,7 @@ in ${neutron}/bin/neutron-openvswitch-agent --config-file=${cfg.config} --config-file=${cfg.openvswitchConfig} ''; }; + enable = cfg.enable; }; systemd.services.neutron-server = { @@ -321,6 +323,7 @@ in LimitNOFILE = 65535; TimeoutStopSec = 15; }; + enable = cfg.enable; }; systemd.services.neutron-dhcp-agent = { @@ -352,6 +355,7 @@ in LimitNOFILE = 65535; TimeoutStopSec = 15; }; + enable = cfg.enable; }; }; From 87aa4990606e84d39e4e71263596e4367efc63bd Mon Sep 17 00:00:00 2001 From: Paul Kroeher Date: Wed, 5 Aug 2026 15:14:38 +0200 Subject: [PATCH 14/83] nix: disable services but create all configurations and systemd units Signed-off-by: Paul Kroeher On-behalf-of: SAP paul.kroeher@sap.com --- modules/controller/glance.nix | 1 + modules/controller/nova.nix | 8 +++++++- modules/controller/placement.nix | 3 ++- 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/modules/controller/glance.nix b/modules/controller/glance.nix index ffabe08..34a8212 100644 --- a/modules/controller/glance.nix +++ b/modules/controller/glance.nix @@ -131,6 +131,7 @@ in glance-api --config-file=${cfg.config} --config-file=/etc/glance/glance-api-paste.ini ''; }; + enable = cfg.enable; }; services.uwsgi = { diff --git a/modules/controller/nova.nix b/modules/controller/nova.nix index 1ea834b..12e6bda 100644 --- a/modules/controller/nova.nix +++ b/modules/controller/nova.nix @@ -108,7 +108,7 @@ in ''; }; }; - config = mkIf cfg.enable { + config = { users.extraUsers.nova = { group = "nova"; @@ -184,6 +184,7 @@ in nova-api --config-file ${cfg.config} ''; }; + enable = cfg.enable; }; systemd.services.nova-conductor = { @@ -216,6 +217,7 @@ in LimitNOFILE = 65535; TimeoutStopSec = 15; }; + enable = cfg.enable; }; systemd.services.nova-scheduler = { @@ -248,6 +250,7 @@ in LimitNOFILE = 65535; TimeoutStopSec = 15; }; + enable = cfg.enable; }; systemd.services.nova-host-discovery = { @@ -269,6 +272,7 @@ in nova-manage cell_v2 discover_hosts --verbose ''; }; + enable = cfg.enable; }; systemd.services.nova-novncproxy = { @@ -301,6 +305,7 @@ in LimitNOFILE = 65535; TimeoutStopSec = 15; }; + enable = cfg.enable; }; systemd.services.nova-serialproxy = { @@ -326,6 +331,7 @@ in LimitNOFILE = 65535; TimeoutStopSec = 15; }; + enable = cfg.enable; }; }; } diff --git a/modules/controller/placement.nix b/modules/controller/placement.nix index 98be12c..d4ed670 100644 --- a/modules/controller/placement.nix +++ b/modules/controller/placement.nix @@ -41,7 +41,7 @@ in ''; }; }; - config = mkIf cfg.enable { + config = { users.extraUsers.placement = { group = "placement"; @@ -84,6 +84,7 @@ in placement-api --port 8778 ''; }; + enable = cfg.enable; }; }; } From 65b688a19ad20036d7013433cf0828e6d555add9 Mon Sep 17 00:00:00 2001 From: Paul Kroeher Date: Thu, 6 Aug 2026 09:18:55 +0200 Subject: [PATCH 15/83] nix: rename mysql command and stop services before setup its database Signed-off-by: Paul Kroeher On-behalf-of: SAP paul.kroeher@sap.com --- modules/controller/openstack-controller.nix | 111 +++++++++++++------- 1 file changed, 74 insertions(+), 37 deletions(-) diff --git a/modules/controller/openstack-controller.nix b/modules/controller/openstack-controller.nix index eb88a9e..113c9f5 100644 --- a/modules/controller/openstack-controller.nix +++ b/modules/controller/openstack-controller.nix @@ -33,14 +33,14 @@ let databaseCleanupScript = pkgs.writeShellScript "database-cleanup.sh" '' export PATH=${lib.makeBinPath [ pkgs.mariadb ]}:$PATH - mysql -N -e "drop database keystone;" || true - mysql -N -e "drop database glance;" || true - mysql -N -e "drop database cinder;" || true - mysql -N -e "drop database placement;" || true - mysql -N -e "drop database nova_api;" || true - mysql -N -e "drop database nova;" || true - mysql -N -e "drop database nova_cell0;" || true - mysql -N -e "drop database neutron;" || true + mariadb -N -e "drop database keystone;" || true + mariadb -N -e "drop database glance;" || true + mariadb -N -e "drop database cinder;" || true + mariadb -N -e "drop database placement;" || true + mariadb -N -e "drop database nova_api;" || true + mariadb -N -e "drop database nova;" || true + mariadb -N -e "drop database nova_cell0;" || true + mariadb -N -e "drop database neutron;" || true ''; # Keep manual provisioning commands and systemd units on the same store scripts. @@ -67,46 +67,46 @@ let export PATH=${lib.makeBinPath [ pkgs.mariadb ]}:$PATH # Keystone - mysql -N -e "CREATE DATABASE IF NOT EXISTS keystone;" - mysql -N -e "CREATE USER IF NOT EXISTS 'keystone'@'%' IDENTIFIED BY 'keystone';" - mysql -N -e "ALTER USER 'keystone'@'%' IDENTIFIED BY 'keystone';" - mysql -N -e "GRANT ALL PRIVILEGES ON keystone.* TO 'keystone'@'%';" + mariadb -N -e "CREATE DATABASE IF NOT EXISTS keystone;" + mariadb -N -e "CREATE USER IF NOT EXISTS 'keystone'@'%' IDENTIFIED BY 'keystone';" + mariadb -N -e "ALTER USER 'keystone'@'%' IDENTIFIED BY 'keystone';" + mariadb -N -e "GRANT ALL PRIVILEGES ON keystone.* TO 'keystone'@'%';" # Glance - mysql -N -e "CREATE DATABASE IF NOT EXISTS glance;" - mysql -N -e "CREATE USER IF NOT EXISTS 'glance'@'%' IDENTIFIED BY 'glance';" - mysql -N -e "ALTER USER 'glance'@'%' IDENTIFIED BY 'glance';" - mysql -N -e "GRANT ALL PRIVILEGES ON glance.* TO 'glance'@'%';" + mariadb -N -e "CREATE DATABASE IF NOT EXISTS glance;" + mariadb -N -e "CREATE USER IF NOT EXISTS 'glance'@'%' IDENTIFIED BY 'glance';" + mariadb -N -e "ALTER USER 'glance'@'%' IDENTIFIED BY 'glance';" + mariadb -N -e "GRANT ALL PRIVILEGES ON glance.* TO 'glance'@'%';" # Cinder - mysql -N -e "CREATE DATABASE IF NOT EXISTS cinder;" - mysql -N -e "CREATE USER IF NOT EXISTS 'cinder'@'%' IDENTIFIED BY 'cinder';" - mysql -N -e "ALTER USER 'cinder'@'%' IDENTIFIED BY 'cinder';" - mysql -N -e "GRANT ALL PRIVILEGES ON cinder.* TO 'cinder'@'%';" + mariadb -N -e "CREATE DATABASE IF NOT EXISTS cinder;" + mariadb -N -e "CREATE USER IF NOT EXISTS 'cinder'@'%' IDENTIFIED BY 'cinder';" + mariadb -N -e "ALTER USER 'cinder'@'%' IDENTIFIED BY 'cinder';" + mariadb -N -e "GRANT ALL PRIVILEGES ON cinder.* TO 'cinder'@'%';" # Placement - mysql -N -e "CREATE DATABASE IF NOT EXISTS placement;" - mysql -N -e "CREATE USER IF NOT EXISTS 'placement'@'%' IDENTIFIED BY 'placement';" - mysql -N -e "ALTER USER 'placement'@'%' IDENTIFIED BY 'placement';" - mysql -N -e "GRANT ALL PRIVILEGES ON placement.* TO 'placement'@'%';" + mariadb -N -e "CREATE DATABASE IF NOT EXISTS placement;" + mariadb -N -e "CREATE USER IF NOT EXISTS 'placement'@'%' IDENTIFIED BY 'placement';" + mariadb -N -e "ALTER USER 'placement'@'%' IDENTIFIED BY 'placement';" + mariadb -N -e "GRANT ALL PRIVILEGES ON placement.* TO 'placement'@'%';" # Nova - mysql -N -e "CREATE DATABASE IF NOT EXISTS nova_api;" - mysql -N -e "CREATE DATABASE IF NOT EXISTS nova;" - mysql -N -e "CREATE DATABASE IF NOT EXISTS nova_cell0;" - mysql -N -e "CREATE USER IF NOT EXISTS 'nova'@'%' IDENTIFIED BY 'nova';" - mysql -N -e "ALTER USER 'nova'@'%' IDENTIFIED BY 'nova';" - mysql -N -e "GRANT ALL PRIVILEGES ON nova_api.* TO 'nova'@'%';" - mysql -N -e "GRANT ALL PRIVILEGES ON nova.* TO 'nova'@'%';" - mysql -N -e "GRANT ALL PRIVILEGES ON nova_cell0.* TO 'nova'@'%';" + mariadb -N -e "CREATE DATABASE IF NOT EXISTS nova_api;" + mariadb -N -e "CREATE DATABASE IF NOT EXISTS nova;" + mariadb -N -e "CREATE DATABASE IF NOT EXISTS nova_cell0;" + mariadb -N -e "CREATE USER IF NOT EXISTS 'nova'@'%' IDENTIFIED BY 'nova';" + mariadb -N -e "ALTER USER 'nova'@'%' IDENTIFIED BY 'nova';" + mariadb -N -e "GRANT ALL PRIVILEGES ON nova_api.* TO 'nova'@'%';" + mariadb -N -e "GRANT ALL PRIVILEGES ON nova.* TO 'nova'@'%';" + mariadb -N -e "GRANT ALL PRIVILEGES ON nova_cell0.* TO 'nova'@'%';" # Neutron - mysql -N -e "CREATE DATABASE IF NOT EXISTS neutron;" - mysql -N -e "CREATE USER IF NOT EXISTS 'neutron'@'%' IDENTIFIED BY 'neutron';" - mysql -N -e "ALTER USER 'neutron'@'%' IDENTIFIED BY 'neutron';" - mysql -N -e "GRANT ALL PRIVILEGES ON neutron.* TO 'neutron'@'%';" + mariadb -N -e "CREATE DATABASE IF NOT EXISTS neutron;" + mariadb -N -e "CREATE USER IF NOT EXISTS 'neutron'@'%' IDENTIFIED BY 'neutron';" + mariadb -N -e "ALTER USER 'neutron'@'%' IDENTIFIED BY 'neutron';" + mariadb -N -e "GRANT ALL PRIVILEGES ON neutron.* TO 'neutron'@'%';" - mysql -N -e "FLUSH PRIVILEGES;" + mariadb -N -e "FLUSH PRIVILEGES;" ''; keystonePreStartScript = pkgs.writeShellScript "keystone-all-pre-start.sh" '' @@ -117,6 +117,8 @@ let ] }:$PATH + set -euxo pipefail + # Initialise the database keystone-manage --config-file ${config.keystone.config} db_sync # Set up the keystone's PKI infrastructure @@ -124,6 +126,8 @@ let keystone-manage --config-file ${config.keystone.config} credential_setup --keystone-user keystone --keystone-group keystone chown -R keystone:keystone /etc/keystone chown -R keystone:keystone /var/log/keystone + + systemctl restart uwsgi.service ''; keystoneStartScript = pkgs.writeShellScript "keystone-all.sh" '' @@ -153,6 +157,8 @@ let ] }:$PATH + systemctl stop glance-api.service + exec runuser --user glance --preserve-environment -- ${pkgs.runtimeShell} <<'EOF' set -euxo pipefail openstack user create --domain default --password glance glance @@ -160,6 +166,8 @@ let openstack role add --user glance --user-domain default --system all reader glance-manage --config-file ${config.glance.config} db_sync EOF + + systemctl start glance-api.service ''; cinderStartScript = pkgs.writeShellScript "cinder.sh" '' @@ -171,6 +179,8 @@ let ] }:$PATH + systemctl stop cinder-api.service cinder-scheduler.service + exec runuser --user cinder --preserve-environment -- ${pkgs.runtimeShell} <<'EOF' set -euxo pipefail openstack user create --domain default --password cinder cinder || true @@ -178,6 +188,8 @@ let openstack role add --user cinder --user-domain default --system all reader || true cinder-manage --config-file ${config.cinder.config} db sync EOF + + systemctl start cinder-api.service cinder-scheduler.service ''; placementStartScript = pkgs.writeShellScript "placement.sh" '' @@ -189,12 +201,16 @@ let ] }:$PATH + systemctl stop placement-api.service + exec runuser --user placement --preserve-environment -- ${pkgs.runtimeShell} <<'EOF' set -euxo pipefail openstack user create --domain default --password placement placement openstack role add --project service --user placement admin placement-manage --config-file ${config.placement.config} db sync EOF + + systemctl start placement-api.service ''; novaStartScript = pkgs.writeShellScript "nova.sh" '' @@ -206,6 +222,12 @@ let ] }:$PATH + systemctl stop nova-conductor.service \ + nova-novncproxy.service \ + nova-scheduler.service \ + nova-serialproxy.service \ + nova-api.service + exec runuser --user nova --preserve-environment -- ${pkgs.runtimeShell} <<'EOF' set -euxo pipefail openstack user create --domain default --password nova nova @@ -215,6 +237,12 @@ let nova-manage --config-file ${config.nova.config} cell_v2 create_cell --name=cell1 --verbose nova-manage --config-file ${config.nova.config} db sync EOF + + systemctl start nova-conductor.service \ + nova-novncproxy.service \ + nova-scheduler.service \ + nova-serialproxy.service \ + nova-api.service ''; neutronStartScript = pkgs.writeShellScript "neutron.sh" '' @@ -226,12 +254,21 @@ let ] }:$PATH + systemctl stop neutron-dhcp-agent.service \ + neutron-metadata-agent.service \ + neutron-openvswitch-agent.service + exec runuser --user neutron --preserve-environment -- ${pkgs.runtimeShell} <<'EOF' set -euxo pipefail openstack user create --domain default --password neutron neutron openstack role add --project service --user neutron admin neutron-db-manage --config-file ${config.neutron.config} --config-file ${config.neutron.ml2Config} upgrade head EOF + + systemctl start neutron-dhcp-agent.service \ + neutron-metadata-agent.service \ + neutron-openvswitch-agent.service + ''; in From 74530a0ab719adaef14247a9573d4886c204ea1d Mon Sep 17 00:00:00 2001 From: Paul Kroeher Date: Thu, 6 Aug 2026 09:32:03 +0200 Subject: [PATCH 16/83] nix: disable services but create all configurations and systemd units Signed-off-by: Paul Kroeher On-behalf-of: SAP paul.kroeher@sap.com --- modules/controller/glance.nix | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/controller/glance.nix b/modules/controller/glance.nix index 34a8212..a8c446a 100644 --- a/modules/controller/glance.nix +++ b/modules/controller/glance.nix @@ -62,7 +62,7 @@ in ''; }; }; - config = mkIf cfg.enable { + config = { users.extraUsers.glance = { group = "glance"; From dbfd93604069607a4d2569b5e81df0fa4f706064 Mon Sep 17 00:00:00 2001 From: Paul Kroeher Date: Thu, 6 Aug 2026 09:32:33 +0200 Subject: [PATCH 17/83] nix: fix mysql database permissions Signed-off-by: Paul Kroeher On-behalf-of: SAP paul.kroeher@sap.com --- modules/controller/openstack-controller.nix | 3 +++ 1 file changed, 3 insertions(+) diff --git a/modules/controller/openstack-controller.nix b/modules/controller/openstack-controller.nix index 113c9f5..a180306 100644 --- a/modules/controller/openstack-controller.nix +++ b/modules/controller/openstack-controller.nix @@ -106,6 +106,9 @@ let mariadb -N -e "ALTER USER 'neutron'@'%' IDENTIFIED BY 'neutron';" mariadb -N -e "GRANT ALL PRIVILEGES ON neutron.* TO 'neutron'@'%';" + # fix mariadb permissions + mariadb -N -e "delete from mysql.user where user = ''';" + mariadb -N -e "FLUSH PRIVILEGES;" ''; From 2b1a97b8009440e5b1a05bcf56d43259e8cd1812 Mon Sep 17 00:00:00 2001 From: Paul Kroeher Date: Thu, 6 Aug 2026 15:56:14 +0200 Subject: [PATCH 18/83] nix: keystone configuration Change enable logic of keystone from generation of configuration to running real service Signed-off-by: Paul Kroeher On-behalf-of: SAP paul.kroeher@sap.com --- modules/controller/keystone.nix | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/controller/keystone.nix b/modules/controller/keystone.nix index 9315577..305fca7 100644 --- a/modules/controller/keystone.nix +++ b/modules/controller/keystone.nix @@ -73,7 +73,7 @@ in }; }; - config = mkIf cfg.enable { + config = { users.extraUsers.keystone = { group = "keystone"; @@ -134,7 +134,7 @@ in ]; instance.type = "emperor"; - instance.vassals.keystone = { + instance.vassals.keystone = mkIf cfg.enable { type = "normal"; http11-socket = "127.0.0.1:5001"; buffer-size = 65535; From e95d94f168de587edebd784460a35e6b68e7978d Mon Sep 17 00:00:00 2001 From: Paul Kroeher Date: Thu, 6 Aug 2026 15:57:23 +0200 Subject: [PATCH 19/83] nix: change enabled logic of glance-api service Signed-off-by: Paul Kroeher On-behalf-of: SAP paul.kroeher@sap.com --- modules/controller/glance.nix | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/controller/glance.nix b/modules/controller/glance.nix index a8c446a..9b2a487 100644 --- a/modules/controller/glance.nix +++ b/modules/controller/glance.nix @@ -135,7 +135,7 @@ in }; services.uwsgi = { - instance.vassals.glance = { + instance.vassals.glance = mkIf cfg.enable { socket-timeout = 10; http-auto-chunked = true; http-chunked-input = true; From f50b357e01e1f5c887182c8f718d3a95bc89aa85 Mon Sep 17 00:00:00 2001 From: Paul Kroeher Date: Thu, 6 Aug 2026 15:58:26 +0200 Subject: [PATCH 20/83] nix: change cinder-api deployment to a uwsgi vassal Signed-off-by: Paul Kroeher On-behalf-of: SAP paul.kroeher@sap.com --- modules/controller/cinder.nix | 39 +++++++++++---------- modules/controller/openstack-controller.nix | 4 +-- 2 files changed, 23 insertions(+), 20 deletions(-) diff --git a/modules/controller/cinder.nix b/modules/controller/cinder.nix index 0dd4f2b..e5adc14 100644 --- a/modules/controller/cinder.nix +++ b/modules/controller/cinder.nix @@ -98,30 +98,33 @@ in }; "/etc/cinder/cinder.conf" = { L = { - argument = "${cinderConf}"; + argument = "${cfg.config}"; }; }; }; }; - systemd.services.cinder-api = { - description = "OpenStack Cinder API Daemon"; - after = [ - "cinder.service" - "rabbitmq.service" - "mysql.service" - "network.target" - ]; - path = [ cinder ]; - wantedBy = [ "multi-user.target" ]; - serviceConfig = { - User = "cinder"; - Group = "cinder"; - ExecStart = pkgs.writeShellScript "cinder-api.sh" '' - .cinder-wsgi-wrapped --port 8776 - ''; + services.uwsgi = { + instance.vassals.cinder-api = mkIf cfg.enable { + type = "normal"; + http-socket = "127.0.0.1:8776"; + wsgi-file = "${cinder}/bin/.cinder-wsgi-wrapped"; + pyargv = "--config-file ${cfg.config}"; + env = [ "PATH=$PATH:/run/current-system/sw/bin" ]; + + master = true; + processes = 4; + enable-threads = true; + thunder-lock = true; + lazy-apps = true; + die-on-term = true; + vacuum = true; + need-app = true; + buffer-size = 65535; + + immediate-uid = "cinder"; + immediate-gid = "cinder"; }; - enable = cfg.enable; }; systemd.services.cinder-scheduler = { diff --git a/modules/controller/openstack-controller.nix b/modules/controller/openstack-controller.nix index a180306..8d9fdfd 100644 --- a/modules/controller/openstack-controller.nix +++ b/modules/controller/openstack-controller.nix @@ -182,7 +182,7 @@ let ] }:$PATH - systemctl stop cinder-api.service cinder-scheduler.service + systemctl stop cinder-scheduler.service exec runuser --user cinder --preserve-environment -- ${pkgs.runtimeShell} <<'EOF' set -euxo pipefail @@ -192,7 +192,7 @@ let cinder-manage --config-file ${config.cinder.config} db sync EOF - systemctl start cinder-api.service cinder-scheduler.service + systemctl start cinder-scheduler.service ''; placementStartScript = pkgs.writeShellScript "placement.sh" '' From eb4cf302f02e88f82252a259eaf767d050097cc0 Mon Sep 17 00:00:00 2001 From: Paul Kroeher Date: Fri, 7 Aug 2026 10:30:21 +0200 Subject: [PATCH 21/83] nix: fix cinder setup in CI/CD context Signed-off-by: Paul Kroeher On-behalf-of: SAP paul.kroeher@sap.com --- modules/controller/cinder.nix | 24 ++++++- modules/controller/openstack-controller.nix | 69 ++++++--------------- 2 files changed, 41 insertions(+), 52 deletions(-) diff --git a/modules/controller/cinder.nix b/modules/controller/cinder.nix index e5adc14..c146e3e 100644 --- a/modules/controller/cinder.nix +++ b/modules/controller/cinder.nix @@ -104,7 +104,29 @@ in }; }; - services.uwsgi = { + # create systemd service only if running in non production mode (CI/CD setup) + systemd.services.cinder-api = lib.mkIf (!config.openstack.manual_setup) { + description = "OpenStack Cinder API Daemon"; + after = [ + "cinder.service" + "rabbitmq.service" + "mysql.service" + "network.target" + ]; + path = [ cinder ]; + wantedBy = [ "multi-user.target" ]; + serviceConfig = { + User = "cinder"; + Group = "cinder"; + ExecStart = pkgs.writeShellScript "cinder-api.sh" '' + .cinder-wsgi-wrapped --port 8776 + ''; + }; + enable = cfg.enable; + }; + + # create uwsgi vassal configuration only in production setup + services.uwsgi = lib.mkIf (config.openstack.manual_setup) { instance.vassals.cinder-api = mkIf cfg.enable { type = "normal"; http-socket = "127.0.0.1:8776"; diff --git a/modules/controller/openstack-controller.nix b/modules/controller/openstack-controller.nix index 8d9fdfd..0cccf8c 100644 --- a/modules/controller/openstack-controller.nix +++ b/modules/controller/openstack-controller.nix @@ -142,7 +142,7 @@ let ] }:$PATH - exec runuser --user keystone --preserve-environment -- ${pkgs.runtimeShell} <<'EOF' + runuser --user keystone --preserve-environment -- ${pkgs.runtimeShell} <<'EOF' set -euxo pipefail keystone-manage --config-file ${config.keystone.config} bootstrap \ --bootstrap-password admin \ @@ -160,17 +160,13 @@ let ] }:$PATH - systemctl stop glance-api.service - - exec runuser --user glance --preserve-environment -- ${pkgs.runtimeShell} <<'EOF' + runuser --user glance --preserve-environment -- ${pkgs.runtimeShell} <<'EOF' set -euxo pipefail openstack user create --domain default --password glance glance openstack role add --project service --user glance admin openstack role add --user glance --user-domain default --system all reader glance-manage --config-file ${config.glance.config} db_sync EOF - - systemctl start glance-api.service ''; cinderStartScript = pkgs.writeShellScript "cinder.sh" '' @@ -182,17 +178,13 @@ let ] }:$PATH - systemctl stop cinder-scheduler.service - - exec runuser --user cinder --preserve-environment -- ${pkgs.runtimeShell} <<'EOF' + runuser --user cinder --preserve-environment -- ${pkgs.runtimeShell} <<'EOF' set -euxo pipefail openstack user create --domain default --password cinder cinder || true openstack role add --project service --user cinder admin || true openstack role add --user cinder --user-domain default --system all reader || true cinder-manage --config-file ${config.cinder.config} db sync EOF - - systemctl start cinder-scheduler.service ''; placementStartScript = pkgs.writeShellScript "placement.sh" '' @@ -204,16 +196,12 @@ let ] }:$PATH - systemctl stop placement-api.service - - exec runuser --user placement --preserve-environment -- ${pkgs.runtimeShell} <<'EOF' + runuser --user placement --preserve-environment -- ${pkgs.runtimeShell} <<'EOF' set -euxo pipefail openstack user create --domain default --password placement placement openstack role add --project service --user placement admin placement-manage --config-file ${config.placement.config} db sync EOF - - systemctl start placement-api.service ''; novaStartScript = pkgs.writeShellScript "nova.sh" '' @@ -225,13 +213,7 @@ let ] }:$PATH - systemctl stop nova-conductor.service \ - nova-novncproxy.service \ - nova-scheduler.service \ - nova-serialproxy.service \ - nova-api.service - - exec runuser --user nova --preserve-environment -- ${pkgs.runtimeShell} <<'EOF' + runuser --user nova --preserve-environment -- ${pkgs.runtimeShell} <<'EOF' set -euxo pipefail openstack user create --domain default --password nova nova openstack role add --project service --user nova admin @@ -240,12 +222,6 @@ let nova-manage --config-file ${config.nova.config} cell_v2 create_cell --name=cell1 --verbose nova-manage --config-file ${config.nova.config} db sync EOF - - systemctl start nova-conductor.service \ - nova-novncproxy.service \ - nova-scheduler.service \ - nova-serialproxy.service \ - nova-api.service ''; neutronStartScript = pkgs.writeShellScript "neutron.sh" '' @@ -257,21 +233,12 @@ let ] }:$PATH - systemctl stop neutron-dhcp-agent.service \ - neutron-metadata-agent.service \ - neutron-openvswitch-agent.service - - exec runuser --user neutron --preserve-environment -- ${pkgs.runtimeShell} <<'EOF' + runuser --user neutron --preserve-environment -- ${pkgs.runtimeShell} <<'EOF' set -euxo pipefail openstack user create --domain default --password neutron neutron openstack role add --project service --user neutron admin neutron-db-manage --config-file ${config.neutron.config} --config-file ${config.neutron.ml2Config} upgrade head EOF - - systemctl start neutron-dhcp-agent.service \ - neutron-metadata-agent.service \ - neutron-openvswitch-agent.service - ''; in @@ -341,8 +308,8 @@ in wantedBy = [ "multi-user.target" ]; restartIfChanged = false; serviceConfig = { - User = "keystone"; - Group = "keystone"; + User = "root"; + Group = "root"; RemainAfterExit = true; Type = "oneshot"; ExecStartPre = "+${keystonePreStartScript}"; @@ -363,8 +330,8 @@ in serviceConfig = { RemainAfterExit = true; Type = "oneshot"; - User = "glance"; - Group = "glance"; + User = "root"; + Group = "root"; ExecStart = "+${glanceStartScript}"; }; }; @@ -382,8 +349,8 @@ in serviceConfig = { RemainAfterExit = true; Type = "oneshot"; - User = "cinder"; - Group = "cinder"; + User = "root"; + Group = "root"; ExecStart = "+${cinderStartScript}"; }; }; @@ -404,8 +371,8 @@ in serviceConfig = { RemainAfterExit = true; Type = "oneshot"; - User = "placement"; - Group = "placement"; + User = "root"; + Group = "root"; ExecStart = "+${placementStartScript}"; }; }; @@ -423,8 +390,8 @@ in serviceConfig = { RemainAfterExit = true; Type = "oneshot"; - User = "nova"; - Group = "nova"; + User = "root"; + Group = "root"; ExecStart = "+${novaStartScript}"; }; }; @@ -442,8 +409,8 @@ in serviceConfig = { RemainAfterExit = true; Type = "oneshot"; - User = "neutron"; - Group = "neutron"; + User = "root"; + Group = "root"; ExecStart = "+${neutronStartScript}"; }; }; From 22ba8f22e03366258c412d63f7499bd5283ea20a Mon Sep 17 00:00:00 2001 From: Paul Kroeher Date: Fri, 7 Aug 2026 10:44:54 +0200 Subject: [PATCH 22/83] nix: change placement-api deployment to a uwsgi vassal Signed-off-by: Paul Kroeher On-behalf-of: SAP paul.kroeher@sap.com --- modules/controller/placement.nix | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/modules/controller/placement.nix b/modules/controller/placement.nix index d4ed670..cbd0113 100644 --- a/modules/controller/placement.nix +++ b/modules/controller/placement.nix @@ -62,7 +62,8 @@ in }; }; - systemd.services.placement-api = { + # create systemd service only if running in non production mode (CI/CD setup) + systemd.services.placement-api = lib.mkIf (!config.openstack.manual_setup) { description = "OpenStack Placement API Daemon"; after = [ "placement.service" @@ -86,5 +87,28 @@ in }; enable = cfg.enable; }; + + # create uwsgi vassal configuration only in production setup + services.uwsgi = lib.mkIf (config.openstack.manual_setup) { + instance.vassals.placement-api = mkIf cfg.enable { + type = "normal"; + http-socket = "127.0.0.1:8778"; + wsgi-file = "${placement}/bin/.placement-api-wrapped"; + pyargv = "--config-file ${cfg.config}"; + + master = true; + processes = 4; + enable-threads = true; + thunder-lock = true; + lazy-apps = true; + die-on-term = true; + vacuum = true; + need-app = true; + buffer-size = 65535; + + immediate-uid = "placement"; + immediate-gid = "placement"; + }; + }; }; } From 40eb410f3b6db2b2f6ef46d47aaae48663a12f60 Mon Sep 17 00:00:00 2001 From: Paul Kroeher Date: Fri, 7 Aug 2026 13:51:23 +0200 Subject: [PATCH 23/83] nix: nix cinder listen addresses Signed-off-by: Paul Kroeher On-behalf-of: SAP paul.kroeher@sap.com --- modules/controller/cinder.nix | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/controller/cinder.nix b/modules/controller/cinder.nix index c146e3e..23eeaa4 100644 --- a/modules/controller/cinder.nix +++ b/modules/controller/cinder.nix @@ -129,7 +129,7 @@ in services.uwsgi = lib.mkIf (config.openstack.manual_setup) { instance.vassals.cinder-api = mkIf cfg.enable { type = "normal"; - http-socket = "127.0.0.1:8776"; + http-socket = "0.0.0.0:8776"; wsgi-file = "${cinder}/bin/.cinder-wsgi-wrapped"; pyargv = "--config-file ${cfg.config}"; env = [ "PATH=$PATH:/run/current-system/sw/bin" ]; From 26c760c1ebd116b71047495a28272eaa2c602e08 Mon Sep 17 00:00:00 2001 From: Paul Kroeher Date: Fri, 7 Aug 2026 15:09:36 +0200 Subject: [PATCH 24/83] nix: move global option into separate file Signed-off-by: Paul Kroeher On-behalf-of: SAP paul.kroeher@sap.com --- modules/controller/openstack-controller.nix | 11 +---------- modules/generic/global-options.nix | 16 ++++++++++++++++ 2 files changed, 17 insertions(+), 10 deletions(-) create mode 100644 modules/generic/global-options.nix diff --git a/modules/controller/openstack-controller.nix b/modules/controller/openstack-controller.nix index 0cccf8c..5a3ff3c 100644 --- a/modules/controller/openstack-controller.nix +++ b/modules/controller/openstack-controller.nix @@ -244,6 +244,7 @@ let in { imports = [ + ../generic/global-options.nix ./generic.nix ../generic/controller-host-entry.nix (import ./keystone.nix { inherit keystone; }) @@ -255,16 +256,6 @@ in (import ./cinder.nix { inherit cinder; }) # only cinder management component ]; - options.openstack.manual_setup = lib.mkOption { - type = lib.types.bool; - default = false; - description = '' - Whether to manage OpenStack setup manually. When enabled, automatic - database setup, OpenStack service bootstrapping, and Cinder storage - initialization are disabled. Setup must be performed separately. - ''; - }; - config = { environment.systemPackages = [ diff --git a/modules/generic/global-options.nix b/modules/generic/global-options.nix new file mode 100644 index 0000000..6c97261 --- /dev/null +++ b/modules/generic/global-options.nix @@ -0,0 +1,16 @@ +{ lib, config, ... }: +with lib; +{ + options.openstack.manual_setup = lib.mkOption { + type = lib.types.bool; + default = false; + description = '' + Whether to manage OpenStack setup manually. When enabled, automatic + database setup, OpenStack service bootstrapping, and Cinder storage + initialization are disabled. Setup must be performed separately. + + This option is meant to allow persistent setups of openstack-nix as + physical deployments. + ''; + }; +} From 5050f22453eee12abbb968a116544eb9f706e506 Mon Sep 17 00:00:00 2001 From: Paul Kroeher Date: Fri, 7 Aug 2026 15:12:42 +0200 Subject: [PATCH 25/83] nix: production setup for cinder-storage-node Signed-off-by: Paul Kroeher On-behalf-of: SAP paul.kroeher@sap.com --- modules/storage/cinder-storage-node.nix | 36 ++++++++++++++++++++----- 1 file changed, 29 insertions(+), 7 deletions(-) diff --git a/modules/storage/cinder-storage-node.nix b/modules/storage/cinder-storage-node.nix index a6f7ca2..b610bfc 100644 --- a/modules/storage/cinder-storage-node.nix +++ b/modules/storage/cinder-storage-node.nix @@ -121,12 +121,27 @@ let cinderTgtConf = pkgs.writeText "cinder.conf" '' include /var/lib/cinder/volumes/* ''; + + cinderDefaultNFSexports = pkgs.writeText "exports" '' + /exports 10.0.0.0/24(rw,no_root_squash,insecure) + ''; in { imports = [ + ../generic/global-options.nix ../generic/controller-host-entry.nix ]; + options.openstack = { + storageIP = mkOption { + type = types.str; + default = "10.0.0.20"; + description = '' + IP address of the storage node. + ''; + }; + }; + options.cinder-storage-node = { enable = mkEnableOption "Enable OpenStack Cinder storage node." // { default = true; @@ -157,9 +172,15 @@ in Possible options: [ lvm | nfs ] ''; }; + exports = mkOption { + default = cinderDefaultNFSexports; + description = '' + The nfs-server /etc/exports file. + ''; + }; }; - config = mkIf cfg.enable { + config = { users.extraUsers.cinder = { group = "cinder"; isSystemUser = true; @@ -227,7 +248,7 @@ in group = "cinder"; mode = "0644"; argument = '' - 10.0.0.20:/exports + ${config.openstack.storageIP}:/exports ''; }; }; @@ -237,7 +258,7 @@ in # start iSCSI target daemon # we expose LVM block storage as iSCSI to compute hosts systemd.services.tgtd = { - enable = if (cfg.backend == "lvm") then true else false; + enable = if (cfg.backend == "lvm" && cfg.enable) then true else false; description = "iSCSI target framework daemon"; wantedBy = [ "multi-user.target" ]; after = [ @@ -270,11 +291,10 @@ in }; services.nfs.server.enable = if (cfg.backend == "lvm") then false else true; - services.nfs.server.exports = '' - /exports 10.0.0.0/24(rw,no_root_squash,insecure) - ''; + services.nfs.server.exports = builtins.readFile cfg.exports; - systemd.services.cinder-volume-group-setup = { + # run this service only in CI/CD setups + systemd.services.cinder-volume-group-setup = lib.mkIf (!config.openstack.manual_setup) { description = "OpenStack Cinder volume group setup"; wantedBy = [ "multi-user.target" ]; path = with pkgs; [ @@ -305,6 +325,7 @@ in exportfs -rv ''; }; + enable = cfg.enable; }; # It seems regardless of what we do, the cinder-volume service does not @@ -370,6 +391,7 @@ in Restart = "on-failure"; RestartSec = 20; }; + enable = cfg.enable; }; }; } From 64b2deadc3a198902e6ca6dc65a694cd8f3528c4 Mon Sep 17 00:00:00 2001 From: Paul Kroeher Date: Fri, 7 Aug 2026 16:55:05 +0200 Subject: [PATCH 26/83] nix: production setup for compute-node Signed-off-by: Paul Kroeher On-behalf-of: SAP paul.kroeher@sap.com --- modules/compute/compute.nix | 1 + modules/compute/neutron.nix | 3 ++- modules/compute/nova.nix | 7 ++++--- modules/generic/global-options.nix | 9 +++++++++ modules/testing/default.nix | 4 +++- 5 files changed, 19 insertions(+), 5 deletions(-) diff --git a/modules/compute/compute.nix b/modules/compute/compute.nix index a085916..1786cbb 100644 --- a/modules/compute/compute.nix +++ b/modules/compute/compute.nix @@ -2,6 +2,7 @@ { ... }: { imports = [ + ../generic/global-options.nix ../generic/controller-host-entry.nix (import ./neutron.nix { inherit neutron; }) (import ./nova.nix { inherit nova; }) diff --git a/modules/compute/neutron.nix b/modules/compute/neutron.nix index f9327bc..4a931fd 100644 --- a/modules/compute/neutron.nix +++ b/modules/compute/neutron.nix @@ -78,7 +78,7 @@ in }; }; - config = mkIf cfg.enable { + config = { users.extraUsers.neutron = { group = "neutron"; isSystemUser = true; @@ -158,6 +158,7 @@ in ${neutron}/bin/neutron-openvswitch-agent --config-file=${cfg.config} --config-file=${cfg.openvswitchConfig} ''; }; + enable = cfg.enable; }; }; } diff --git a/modules/compute/nova.nix b/modules/compute/nova.nix index 9d0b0d1..b61bc32 100644 --- a/modules/compute/nova.nix +++ b/modules/compute/nova.nix @@ -25,7 +25,7 @@ let state_path = /var/lib/nova rootwrap_config = ${rootwrapConf} compute_driver = libvirt.LibvirtDriver - my_ip = 10.0.0.39 + my_ip = ${config.openstack.computeIP} transport_url = rabbit://openstack:openstack@controller [api] @@ -146,7 +146,7 @@ in }; }; - config = mkIf cfg.enable { + config = { users.extraUsers.nova = { group = "nova"; isSystemUser = true; @@ -194,7 +194,7 @@ in }; services.openiscsi = { - enable = true; + enable = cfg.enable; name = "iqn.iscsi.${config.networking.hostName}"; }; @@ -228,6 +228,7 @@ in ${cfg.novaPackage}/bin/nova-compute --config-file=${cfg.config} ''; }; + enable = cfg.enable; }; }; } diff --git a/modules/generic/global-options.nix b/modules/generic/global-options.nix index 6c97261..d8e90e4 100644 --- a/modules/generic/global-options.nix +++ b/modules/generic/global-options.nix @@ -13,4 +13,13 @@ with lib; physical deployments. ''; }; + + options.openstack.computeIP = lib.mkOption { + type = types.str; + description = '' + IP address of the compute node, used as Nova's my_ip and as the address + reached by the serial-console and VNC proxies. This address must already + be configured on the host; this option does not configure an interface. + ''; + }; } diff --git a/modules/testing/default.nix b/modules/testing/default.nix index 7eca5c5..25820fa 100644 --- a/modules/testing/default.nix +++ b/modules/testing/default.nix @@ -301,13 +301,15 @@ in }; testCompute = - { ... }: + { lib, ... }: { imports = [ common portForwardingCompute ]; + openstack.computeIP = lib.mkDefault "10.0.0.39"; + # this is an example how to enable / disable all port forwardings or change port numbers # openstack-testing.enable = true; # enable / disable all port forwardings # openstack-testing.sshHostPort = 3022; From 3c6eef4e13c2db83e75b6563c9ac015e0f99d9a7 Mon Sep 17 00:00:00 2001 From: Paul Kroeher Date: Mon, 10 Aug 2026 15:12:07 +0200 Subject: [PATCH 27/83] nix: prod setup neutron-openvswitch-agent Create a reboot capable version of neutron-openvswitch-agent service Signed-off-by: Paul Kroeher On-behalf-of: SAP paul.kroeher@sap.com --- modules/compute/neutron.nix | 34 ++++++++++++++++++++++++++++++---- 1 file changed, 30 insertions(+), 4 deletions(-) diff --git a/modules/compute/neutron.nix b/modules/compute/neutron.nix index 4a931fd..8d21c83 100644 --- a/modules/compute/neutron.nix +++ b/modules/compute/neutron.nix @@ -50,6 +50,28 @@ let filterPath = "/etc/neutron/rootwrap.d"; inherit utils_env; }; + + openvswitchPrepareScript = pkgs.writeShellScript "openvswitch-setup.sh" '' + export PATH=${ + lib.makeBinPath [ + pkgs.openvswitch + ] + }:$PATH + + ovs-vsctl br-exists br-provider >/dev/null + status=$? + + if [[ $status -eq 0 ]]; then + echo "br-provider already exists. No further setup tasks." + else + echo "br-provider didn't exist already. Proceed with basic setup." + set -euxo pipefail + ovs-vsctl add-br br-provider + ovs-vsctl add-port br-provider ${cfg.providerInterface} + fi + + ''; + in { options.neutron = { @@ -68,6 +90,13 @@ in The Neutron OpenVSwitch config. ''; }; + openvswitchPrepare = mkOption { + default = openvswitchPrepareScript; + description = '' + Default OpenVswitch prepare script of systemd unit: neutron-openvswitch-agent.service + This prepare script creates the basic openvswitch setup. + ''; + }; providerInterface = mkOption { default = "eth2"; type = types.str; @@ -150,10 +179,7 @@ in conntrack-tools ]; serviceConfig = { - ExecStartPre = pkgs.writeShellScript "neutron-openvswitch-agent-pre.sh" '' - ${pkgs.openvswitch}/bin/ovs-vsctl add-br br-provider - ${pkgs.openvswitch}/bin/ovs-vsctl add-port br-provider ${cfg.providerInterface} - ''; + ExecStartPre = "${cfg.openvswitchPrepare}"; ExecStart = pkgs.writeShellScript "neutron-openvswitch-agent.sh" '' ${neutron}/bin/neutron-openvswitch-agent --config-file=${cfg.config} --config-file=${cfg.openvswitchConfig} ''; From 88b06bc5dde7554a523c2137bd43b3b7567a37a6 Mon Sep 17 00:00:00 2001 From: Paul Kroeher Date: Tue, 11 Aug 2026 11:33:50 +0200 Subject: [PATCH 28/83] nix: fix listen address placement service Signed-off-by: Paul Kroeher On-behalf-of: SAP paul.kroeher@sap.com --- modules/controller/placement.nix | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/controller/placement.nix b/modules/controller/placement.nix index cbd0113..d5e5447 100644 --- a/modules/controller/placement.nix +++ b/modules/controller/placement.nix @@ -92,7 +92,7 @@ in services.uwsgi = lib.mkIf (config.openstack.manual_setup) { instance.vassals.placement-api = mkIf cfg.enable { type = "normal"; - http-socket = "127.0.0.1:8778"; + http-socket = "0.0.0.0:8778"; wsgi-file = "${placement}/bin/.placement-api-wrapped"; pyargv = "--config-file ${cfg.config}"; From eef6f914e88aa9b977f289388d7ffe4eea4a432b Mon Sep 17 00:00:00 2001 From: Paul Kroeher Date: Tue, 11 Aug 2026 16:36:29 +0200 Subject: [PATCH 29/83] nix: fix glance persistent directories after reboot Signed-off-by: Paul Kroeher On-behalf-of: SAP paul.kroeher@sap.com --- modules/controller/glance.nix | 36 ++++++++++----------- modules/controller/openstack-controller.nix | 9 ++++++ 2 files changed, 27 insertions(+), 18 deletions(-) diff --git a/modules/controller/glance.nix b/modules/controller/glance.nix index 9b2a487..d767273 100644 --- a/modules/controller/glance.nix +++ b/modules/controller/glance.nix @@ -75,42 +75,42 @@ in systemd.tmpfiles.settings = { "10-glance" = { + "/etc/glance/glance-api.conf" = { + L = { + argument = "${cfg.config}"; + }; + }; + "/etc/glance/glance-api-paste.ini" = { + L = { + argument = "${glance}/etc/glance/glance-api-paste.ini"; + }; + }; + "/etc/glance/schema-image.json" = { + L = { + argument = "${glance}/etc/glance/schema-image.json"; + }; + }; "/var/lib/glance/" = { - D = { + d = { user = "glance"; group = "glance"; mode = "0755"; }; }; "/var/log/glance/" = { - D = { + d = { user = "glance"; group = "glance"; mode = "0755"; }; }; "/var/lib/glance/images" = { - D = { + d = { user = "glance"; group = "glance"; mode = "0755"; }; }; - "/etc/glance/glance-api.conf" = { - L = { - argument = "${cfg.config}"; - }; - }; - "/etc/glance/glance-api-paste.ini" = { - L = { - argument = "${glance}/etc/glance/glance-api-paste.ini"; - }; - }; - "/etc/glance/schema-image.json" = { - L = { - argument = "${glance}/etc/glance/schema-image.json"; - }; - }; }; }; diff --git a/modules/controller/openstack-controller.nix b/modules/controller/openstack-controller.nix index 5a3ff3c..77b9c52 100644 --- a/modules/controller/openstack-controller.nix +++ b/modules/controller/openstack-controller.nix @@ -160,6 +160,15 @@ let ] }:$PATH + mkdir -p /var/lib/glance + mkdir -p /var/lib/glance/images + chown -R glance /var/lib/glance + chgrp -R glance /var/lib/glance + + mkdir -p /var/log/glance + chown glance /var/log/glance + chgrp glance /var/log/glance + runuser --user glance --preserve-environment -- ${pkgs.runtimeShell} <<'EOF' set -euxo pipefail openstack user create --domain default --password glance glance From 2505c5e174158a4ac7e32d67bc059185e50f26eb Mon Sep 17 00:00:00 2001 From: Paul Kroeher Date: Tue, 11 Aug 2026 16:37:08 +0200 Subject: [PATCH 30/83] nix: add check script for quick controller status Signed-off-by: Paul Kroeher On-behalf-of: SAP paul.kroeher@sap.com --- modules/controller/openstack-controller.nix | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/modules/controller/openstack-controller.nix b/modules/controller/openstack-controller.nix index 77b9c52..c4df3b5 100644 --- a/modules/controller/openstack-controller.nix +++ b/modules/controller/openstack-controller.nix @@ -250,6 +250,24 @@ let EOF ''; + checkControllerScript = pkgs.writeShellScript "check-controller.sh" '' + export PATH=${ + lib.makeBinPath [ + pkgs.openstackclient + ] + }:$PATH + + systemctl status neutron-server.service + systemctl status glance-api.server + systemctl status uwsgi.service + systemctl status cinder-scheduler.service + systemctl status nova-api.service + systemctl status nova-scheduler.service + systemctl status nova-conductor.service + systemctl status nova-novncproxy.service + systemctl status nova-serialproxy.service + ''; + in { imports = [ @@ -277,6 +295,7 @@ in (setupCommand "openstack-placement-setup" placementStartScript) (setupCommand "openstack-nova-setup" novaStartScript) (setupCommand "openstack-neutron-setup" neutronStartScript) + (setupCommand "openstack-check-controller" checkControllerScript) python-openstackclient ]; From dd55a4d5dbc11d2b2aad0eb2c6ef28f85a2505ff Mon Sep 17 00:00:00 2001 From: Paul Kroeher Date: Wed, 12 Aug 2026 14:09:57 +0200 Subject: [PATCH 31/83] nix: fix cinder persistent directories after reboot Signed-off-by: Paul Kroeher On-behalf-of: SAP paul.kroeher@sap.com --- modules/storage/cinder-storage-node.nix | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/modules/storage/cinder-storage-node.nix b/modules/storage/cinder-storage-node.nix index b610bfc..7409f5f 100644 --- a/modules/storage/cinder-storage-node.nix +++ b/modules/storage/cinder-storage-node.nix @@ -198,28 +198,28 @@ in systemd.tmpfiles.settings = { "20-cinder" = { "/var/lib/cinder/" = { - D = { + d = { user = "cinder"; group = "cinder"; mode = "0755"; }; }; "/var/lib/cinder/volumes" = { - D = { + d = { user = "cinder"; group = "cinder"; mode = "0755"; }; }; "/var/log/cinder/" = { - D = { + d = { user = "cinder"; group = "cinder"; mode = "0755"; }; }; "/etc/cinder/cinder.conf" = { - L = { + "L+" = { argument = "${cfg.config}"; }; }; @@ -229,12 +229,12 @@ in # LVM configuration files { "/etc/tgt/conf.d/cinder.conf" = { - L = { + "L+" = { argument = "${cinderTgtConf}"; }; }; "/etc/tgt/targets.conf" = { - L = { + "L+" = { argument = "${pkgs.tgt}/etc/tgt/targets.conf"; }; }; From 5e3b995884792754fbeb0de61703df25e59361d8 Mon Sep 17 00:00:00 2001 From: Paul Kroeher Date: Wed, 12 Aug 2026 15:26:32 +0200 Subject: [PATCH 32/83] nix: add cinder volume setup script Signed-off-by: Paul Kroeher On-behalf-of: SAP paul.kroeher@sap.com --- modules/storage/cinder-storage-node.nix | 51 ++++++++++++++++++++++++- 1 file changed, 49 insertions(+), 2 deletions(-) diff --git a/modules/storage/cinder-storage-node.nix b/modules/storage/cinder-storage-node.nix index 7409f5f..4e42a41 100644 --- a/modules/storage/cinder-storage-node.nix +++ b/modules/storage/cinder-storage-node.nix @@ -125,6 +125,48 @@ let cinderDefaultNFSexports = pkgs.writeText "exports" '' /exports 10.0.0.0/24(rw,no_root_squash,insecure) ''; + + cinderVolumeSetupScript = pkgs.writeShellScript "cinder-volume-setup.sh" '' + export PATH=${ + lib.makeBinPath [ + pkgs.util-linux + ] + }:$PATH + + if [ -e /exports/.cinder-volume-setup-done-dont-delete-me ]; then + echo "cinder volume setup already done. Check content of this script." + fi + + mkdir /exports + mkfs.ext4 -F -m 0 -L cinder /dev/vdb + mount /dev/vdb /exports + exportfs -rv + rm -rf /exports/lost+found + chown cinder /exports + chgrp cinder /exports + + systemctl restart cinder-volume.service + touch /exports/.cinder-volume-setup-done-dont-delete-me + ''; + + cinderVolumeSetupCommand = pkgs.writeShellApplication { + name = "openstack-cinder-volume-setup"; + runtimeInputs = with pkgs; [ + coreutils + systemd + util-linux + nfs-utils + e2fsprogs + ]; + text = '' + if (( EUID != 0 )); then + echo "Run this command as root." >&2 + exit 1 + fi + exec ${cinderVolumeSetupScript} "$@" + ''; + }; + in { imports = [ @@ -181,6 +223,7 @@ in }; config = { + users.extraUsers.cinder = { group = "cinder"; isSystemUser = true; @@ -333,7 +376,10 @@ in # workaround, add it as a systemPackage. # Update: still does not work -.- - environment.systemPackages = + environment.systemPackages = [ + cinderVolumeSetupCommand + ] + ++ ( if (cfg.backend == "lvm") then with pkgs; [ @@ -346,7 +392,8 @@ in qemu nfs-utils e2fsprogs - ]; + ] + ); systemd.services.cinder-volume = { description = "OpenStack Cinder Volume"; From 995f64e15ac5ea170bff2047d57c7638e777463a Mon Sep 17 00:00:00 2001 From: Paul Kroeher Date: Wed, 12 Aug 2026 16:24:36 +0200 Subject: [PATCH 33/83] nix: fix nova-compute persistent directories after reboot Signed-off-by: Paul Kroeher On-behalf-of: SAP paul.kroeher@sap.com --- modules/compute/nova.nix | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/modules/compute/nova.nix b/modules/compute/nova.nix index b61bc32..28fa697 100644 --- a/modules/compute/nova.nix +++ b/modules/compute/nova.nix @@ -162,28 +162,28 @@ in systemd.tmpfiles.settings = { "10-nova" = { "/var/log/nova" = { - D = { + d = { group = "nova"; mode = "0755"; user = "nova"; }; }; "/var/lock/nova" = { - D = { + d = { group = "nova"; mode = "0755"; user = "nova"; }; }; "/var/lib/nova" = { - D = { + d = { group = "nova"; mode = "0755"; user = "nova"; }; }; "/var/lib/nova/instances" = { - D = { + d = { group = "nova"; mode = "0755"; user = "nova"; From 6f03db63cca904e3e55e16d02dfa9bad66d93b99 Mon Sep 17 00:00:00 2001 From: Paul Kroeher Date: Thu, 13 Aug 2026 16:52:22 +0200 Subject: [PATCH 34/83] nix: add environment config option to cinder Signed-off-by: Paul Kroeher On-behalf-of: SAP paul.kroeher@sap.com --- modules/controller/cinder.nix | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/modules/controller/cinder.nix b/modules/controller/cinder.nix index 23eeaa4..5c90c26 100644 --- a/modules/controller/cinder.nix +++ b/modules/controller/cinder.nix @@ -56,6 +56,24 @@ in The OpenStack Cinder package to use. ''; }; + envCinderApi = mkOption { + type = types.listOf types.str; + default = [ + "PYTHONWARNINGS=ignore::DeprecationWarning" + "PATH=$PATH:/run/current-system/sw/bin" + ]; + description = '' + Environment variables passed to the cinder-api uWSGI vassal. + ''; + }; + envCinderScheduler = mkOption { + default = { + PYTHONWARNINGS = "ignore::DeprecationWarning"; + }; + description = '' + Environment variables passed to the cinder-scheduler systemd unit. + ''; + }; }; config = { @@ -132,7 +150,7 @@ in http-socket = "0.0.0.0:8776"; wsgi-file = "${cinder}/bin/.cinder-wsgi-wrapped"; pyargv = "--config-file ${cfg.config}"; - env = [ "PATH=$PATH:/run/current-system/sw/bin" ]; + env = cfg.envCinderApi; master = true; processes = 4; @@ -167,6 +185,7 @@ in ''; }; enable = cfg.enable; + environment = cfg.envCinderScheduler; }; }; } From c6ba45a4dce52e2b31807a861866c7ec6ccc3c4a Mon Sep 17 00:00:00 2001 From: Paul Kroeher Date: Thu, 13 Aug 2026 16:52:36 +0200 Subject: [PATCH 35/83] nix: add environment config option to glance Signed-off-by: Paul Kroeher On-behalf-of: SAP paul.kroeher@sap.com --- modules/controller/glance.nix | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/modules/controller/glance.nix b/modules/controller/glance.nix index d767273..5bff0d9 100644 --- a/modules/controller/glance.nix +++ b/modules/controller/glance.nix @@ -61,6 +61,15 @@ in The Glance config. ''; }; + env = mkOption { + type = types.listOf types.str; + default = [ + "PYTHONWARNINGS=ignore::DeprecationWarning" + ]; + description = '' + Environment variables passed to the Keystone uWSGI vassal. + ''; + }; }; config = { @@ -155,6 +164,7 @@ in immediate-uid = "glance"; immediate-gid = "glance"; wsgi-file = "${glance}/bin/.glance-wsgi-api-wrapped"; + env = cfg.env; }; }; }; From d304248115b6d081a137f16aa22082d0f6dc5b41 Mon Sep 17 00:00:00 2001 From: Paul Kroeher Date: Thu, 13 Aug 2026 16:52:46 +0200 Subject: [PATCH 36/83] nix: add environment config option to horizon Signed-off-by: Paul Kroeher On-behalf-of: SAP paul.kroeher@sap.com --- modules/controller/horizon.nix | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/modules/controller/horizon.nix b/modules/controller/horizon.nix index 90b0898..9fc5d3e 100644 --- a/modules/controller/horizon.nix +++ b/modules/controller/horizon.nix @@ -30,6 +30,15 @@ in description = "The Horizon Package to use"; type = types.package; }; + env = mkOption { + type = types.listOf types.str; + default = [ + "PYTHONWARNINGS=ignore::DeprecationWarning" + ]; + description = '' + Environment variables passed to the horizon uWSGI vassal. + ''; + }; }; config = { @@ -89,6 +98,7 @@ in thunder-lock = true; lazy-apps = true; chdir = "/var/lib/openstack_dashboard"; + env = cfg.env; }; }; From 73d3ae1a2f083ce11bb0d822cb4a1b250c28baff Mon Sep 17 00:00:00 2001 From: Paul Kroeher Date: Thu, 13 Aug 2026 16:53:01 +0200 Subject: [PATCH 37/83] nix: add environment config option to keystone Signed-off-by: Paul Kroeher On-behalf-of: SAP paul.kroeher@sap.com --- modules/controller/keystone.nix | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/modules/controller/keystone.nix b/modules/controller/keystone.nix index 305fca7..9818893 100644 --- a/modules/controller/keystone.nix +++ b/modules/controller/keystone.nix @@ -71,6 +71,15 @@ in The Keystone config. ''; }; + env = mkOption { + type = types.listOf types.str; + default = [ + "PYTHONWARNINGS=ignore::DeprecationWarning" + ]; + description = '' + Environment variables passed to the Keystone uWSGI vassal. + ''; + }; }; config = { @@ -148,6 +157,7 @@ in threads = 4; thunder-lock = true; lazy-apps = true; + env = cfg.env; }; }; }; From ec863d5b60be09a511e724051cab96a9e0f906d1 Mon Sep 17 00:00:00 2001 From: Paul Kroeher Date: Thu, 13 Aug 2026 16:53:15 +0200 Subject: [PATCH 38/83] nix: add environment config option to placement Signed-off-by: Paul Kroeher On-behalf-of: SAP paul.kroeher@sap.com --- modules/controller/placement.nix | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/modules/controller/placement.nix b/modules/controller/placement.nix index d5e5447..95b54b5 100644 --- a/modules/controller/placement.nix +++ b/modules/controller/placement.nix @@ -40,6 +40,15 @@ in The Placement config. ''; }; + env = mkOption { + type = types.listOf types.str; + default = [ + "PYTHONWARNINGS=ignore::DeprecationWarning" + ]; + description = '' + Environment variables passed to the placement uWSGI vassal. + ''; + }; }; config = { @@ -108,6 +117,7 @@ in immediate-uid = "placement"; immediate-gid = "placement"; + env = cfg.env; }; }; }; From c4547ce7beca24f3fde97e49819ff8ab4e58a712 Mon Sep 17 00:00:00 2001 From: Paul Kroeher Date: Mon, 17 Aug 2026 11:23:28 +0200 Subject: [PATCH 39/83] nix: fix neutron configuration symlinks After a configuration update the generated symlinks into the nix store should be updated by systemd-tmpfiles unit. Signed-off-by: Paul Kroeher On-behalf-of: SAP paul.kroeher@sap.com --- modules/controller/neutron.nix | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/modules/controller/neutron.nix b/modules/controller/neutron.nix index 0e02d48..b159919 100644 --- a/modules/controller/neutron.nix +++ b/modules/controller/neutron.nix @@ -193,27 +193,27 @@ in }; }; "/etc/neutron/neutron.conf" = { - L = { + "L+" = { argument = "${cfg.config}"; }; }; "/etc/neutron/plugins/ml2/ml2_conf.ini" = { - L = { + "L+" = { argument = "${cfg.ml2Config}"; }; }; "/etc/neutron/plugins/ml2/openvswitch_agent.ini" = { - L = { + "L+" = { argument = "${cfg.openvswitchConfig}"; }; }; "/etc/neutron/dhcp_agent.ini" = { - L = { + "L+" = { argument = "${cfg.dhcpAgentConfig}"; }; }; "/etc/neutron/api-paste.ini" = { - L = { + "L+" = { argument = "${neutron}/etc/neutron/api-paste.ini"; }; }; From d4a8847dbb46fe0839c9e3e5adf01b2c392f250a Mon Sep 17 00:00:00 2001 From: Paul Kroeher Date: Mon, 17 Aug 2026 16:37:38 +0200 Subject: [PATCH 40/83] nix: explicitly enable uplink interfaces Signed-off-by: Paul Kroeher On-behalf-of: SAP paul.kroeher@sap.com --- modules/compute/neutron.nix | 3 +++ modules/controller/neutron.nix | 2 ++ 2 files changed, 5 insertions(+) diff --git a/modules/compute/neutron.nix b/modules/compute/neutron.nix index 8d21c83..329dc7d 100644 --- a/modules/compute/neutron.nix +++ b/modules/compute/neutron.nix @@ -70,6 +70,9 @@ let ovs-vsctl add-port br-provider ${cfg.providerInterface} fi + # enable uplink provider interface + ip link set dev ${cfg.providerInterface} up + ''; in diff --git a/modules/controller/neutron.nix b/modules/controller/neutron.nix index b159919..a757f4a 100644 --- a/modules/controller/neutron.nix +++ b/modules/controller/neutron.nix @@ -287,6 +287,8 @@ in ExecStartPre = pkgs.writeShellScript "pre.sh" '' ${pkgs.openvswitch}/bin/ovs-vsctl add-br br-provider || true ${pkgs.openvswitch}/bin/ovs-vsctl add-port br-provider ${cfg.providerInterface} || true + # enable uplink provider interface + ip link set dev ${cfg.providerInterface} up ''; ExecStart = pkgs.writeShellScript "neutron-openvswitch.sh" '' ${neutron}/bin/neutron-openvswitch-agent --config-file=${cfg.config} --config-file=${cfg.openvswitchConfig} From ced054651a22b4db2623737eb245999830d87ba0 Mon Sep 17 00:00:00 2001 From: Paul Kroeher Date: Mon, 24 Aug 2026 14:06:01 +0200 Subject: [PATCH 41/83] nix: update file content after configuration update Signed-off-by: Paul Kroeher On-behalf-of: SAP paul.kroeher@sap.com --- modules/storage/cinder-storage-node.nix | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/storage/cinder-storage-node.nix b/modules/storage/cinder-storage-node.nix index 4e42a41..0b666fa 100644 --- a/modules/storage/cinder-storage-node.nix +++ b/modules/storage/cinder-storage-node.nix @@ -286,7 +286,7 @@ in # NFS configuration files { "/etc/cinder/nfs_shares" = { - f = { + "f+" = { user = "cinder"; group = "cinder"; mode = "0644"; From d33cc39c28f190d692e92a0677ccc922748beef8 Mon Sep 17 00:00:00 2001 From: Paul Kroeher Date: Wed, 26 Aug 2026 15:22:59 +0200 Subject: [PATCH 42/83] nix: add package designate Signed-off-by: Paul Kroeher On-behalf-of: SAP paul.kroeher@sap.com --- flake.nix | 1 + modules/controller/keystone.nix | 7 +- packages/default.nix | 42 ++++++++ packages/designate.nix | 151 ++++++++++++++++++++++++++++ packages/infoblox-client.nix | 45 +++++++++ packages/python-openstackclient.nix | 2 + 6 files changed, 247 insertions(+), 1 deletion(-) create mode 100644 packages/designate.nix create mode 100644 packages/infoblox-client.nix diff --git a/flake.nix b/flake.nix index 149e67a..7b18cf3 100644 --- a/flake.nix +++ b/flake.nix @@ -65,6 +65,7 @@ python3Packages writeText lib + libredirect ; }; diff --git a/modules/controller/keystone.nix b/modules/controller/keystone.nix index 9818893..937507b 100644 --- a/modules/controller/keystone.nix +++ b/modules/controller/keystone.nix @@ -44,6 +44,11 @@ let catalog.RegionOne.volumev3.adminURL = http://controller:8776/v3 catalog.RegionOne.volumev3.internalURL = http://controller:8776/v3 catalog.RegionOne.volumev3.name = Cinder Service + + catalog.RegionOne.dns.publicURL = http://controller:9001/ + catalog.RegionOne.dns.adminURL = http://controller:9001/ + catalog.RegionOne.dns.internalURL = http://controller:9001/ + catalog.RegionOne.dns.name = DNS Service ''; keystoneConf = pkgs.writeText "keystone.conf" '' @@ -112,7 +117,7 @@ in # Certain executables e.g. keystone-wsgi-public expect the config file # at a default location. "/etc/keystone/keystone.conf" = { - L = { + "L+" = { argument = "${cfg.config}"; }; }; diff --git a/packages/default.nix b/packages/default.nix index 573dcf2..329b0db 100644 --- a/packages/default.nix +++ b/packages/default.nix @@ -3,6 +3,7 @@ python3Packages, writeText, lib, + libredirect, }: let # In the past, the packages was not ready for the latest python interpreter. @@ -69,6 +70,38 @@ let tooz ; }; + designate = callPackage ./designate.nix { + inherit + futurist + libredirect + infoblox-client + keystoneauth1 + keystonemiddleware + openstacksdk + oslo-concurrency + oslo-config + oslo-context + oslo-db + oslo-i18n + oslo-log + oslo-messaging + oslo-middleware + oslo-policy + oslo-reports + oslo-rootwrap + oslo-serialization + oslo-service + oslo-upgradecheck + oslo-utils + oslo-versionedobjects + osprofiler + oslotest + python3Packages + python-designateclient + sqlalchemy + tooz + ; + }; django = callPackage ./django.nix { inherit python3Packages; }; django-appconf = callPackage ./django-appconf.nix { inherit @@ -220,6 +253,14 @@ let xstatic-tv4 ; }; + infoblox-client = callPackage ./infoblox-client.nix { + inherit + python3Packages + oslo-log + oslo-serialization + + ; + }; jsonpath-rw-ext = callPackage ./jsonpath-rw-ext.nix { inherit python3Packages; }; keystone = callPackage ./keystone.nix { inherit @@ -793,6 +834,7 @@ let osc-lib oslo-i18n python-cinderclient + python-designateclient python-keystoneclient python3Packages ; diff --git a/packages/designate.nix b/packages/designate.nix new file mode 100644 index 0000000..e34e2d5 --- /dev/null +++ b/packages/designate.nix @@ -0,0 +1,151 @@ +{ + fetchPypi, + libredirect, + futurist, + infoblox-client, + keystoneauth1, + keystonemiddleware, + openstacksdk, + oslo-concurrency, + oslo-config, + oslo-context, + oslo-db, + oslo-i18n, + oslo-log, + oslo-messaging, + oslo-middleware, + oslo-policy, + oslo-reports, + oslo-rootwrap, + oslo-serialization, + oslo-service, + oslo-upgradecheck, + oslo-utils, + oslo-versionedobjects, + oslotest, + osprofiler, + python-designateclient, + python3Packages, + sqlalchemy, + tooz, + writeScript, + lib, +}: +let + inherit (python3Packages) + alembic + dnspython + eventlet + flask + greenlet + jinja2 + jsonschema + paste + pastedeploy + pbr + pecan + pymysql + python-memcached + requests + requests-mock + stestr + stevedore + tenacity + testresources + testscenarios + webob + webtest + ; + + testExcludes = [ + "designate.tests.unit.backend.test_infoblox.*" + ]; + + excludeListFile = writeScript "test_excludes" (lib.concatStringsSep "\n" testExcludes); + +in +python3Packages.buildPythonPackage (rec { + pname = "designate"; + version = "19.1.0"; + pyproject = true; + build-system = [ + python3Packages.pbr + python3Packages.setuptools + ]; + + nativeBuildInputs = [ + pbr + ]; + + propagatedBuildInputs = [ + (alembic.override { inherit sqlalchemy; }) + dnspython + eventlet + flask + futurist + greenlet + infoblox-client + jinja2 + jsonschema + keystoneauth1 + keystonemiddleware + openstacksdk + oslo-concurrency + oslo-config + oslo-context + oslo-db + oslo-i18n + oslo-log + oslo-messaging + oslo-middleware + oslo-policy + oslo-reports + oslo-rootwrap + oslo-serialization + oslo-service + oslo-upgradecheck + oslo-utils + oslo-versionedobjects + oslotest + osprofiler + paste + pastedeploy + pbr + pecan + pymysql + python-designateclient + python-memcached + requests + sqlalchemy + stevedore + tenacity + tooz + webob + ]; + + nativeCheckInputs = [ + libredirect.hook + stestr + ]; + + checkInputs = [ + oslotest + requests-mock + testresources + testscenarios + webtest + ]; + + checkPhase = '' + runHook preCheck + echo "nameserver 127.0.0.1" > resolv.conf + export NIX_REDIRECTS="/etc/resolv.conf=$(realpath resolv.conf)" + stestr run --exclude-list ${excludeListFile} + runHook postCheck + ''; + + src = fetchPypi { + inherit pname version; + sha256 = "sha256-kqZJRM9WX8nXxEi+8KuBotwkIk8kVlpvDgp+3czDSeE="; + }; +}) diff --git a/packages/infoblox-client.nix b/packages/infoblox-client.nix new file mode 100644 index 0000000..0790c2c --- /dev/null +++ b/packages/infoblox-client.nix @@ -0,0 +1,45 @@ +{ + fetchPypi, + python3Packages, + oslo-log, + oslo-serialization, +}: +let + inherit (python3Packages) + stestr + requests + urllib3 + six + ; +in +python3Packages.buildPythonPackage (rec { + pname = "infoblox-client"; + version = "0.6.2"; + pyproject = true; + build-system = [ + python3Packages.setuptools + ]; + + nativeBuildInputs = [ + ]; + + propagatedBuildInputs = [ + oslo-log + oslo-serialization + requests + urllib3 + six + ]; + + nativeCheckInputs = [ + stestr + ]; + + checkInputs = [ + ]; + + src = fetchPypi { + inherit pname version; + sha256 = "sha256-0hkgof6LLAwp2rYOl8rgLTSFYeQauik7Bi4rzhmszVE="; + }; +}) diff --git a/packages/python-openstackclient.nix b/packages/python-openstackclient.nix index 2997578..90bc4cb 100644 --- a/packages/python-openstackclient.nix +++ b/packages/python-openstackclient.nix @@ -3,6 +3,7 @@ osc-lib, oslo-i18n, python-cinderclient, + python-designateclient, python-keystoneclient, python3Packages, }: @@ -50,6 +51,7 @@ python3Packages.buildPythonPackage rec { oslo-i18n pbr python-cinderclient + python-designateclient python-keystoneclient requests stevedore From a969459b624de87e7a983f78624c49f9c420006a Mon Sep 17 00:00:00 2001 From: Paul Kroeher Date: Thu, 27 Aug 2026 15:22:38 +0200 Subject: [PATCH 43/83] nix: add module designate with external knot dns server backend Signed-off-by: Paul Kroeher On-behalf-of: SAP paul.kroeher@sap.com --- README.md | 11 + modules/controller/designate.nix | 243 ++++++++++++++++++++ modules/controller/openstack-controller.nix | 52 +++++ modules/default.nix | 1 + modules/knot-designate.nix | 105 +++++++++ packages/designate-knot3-backend.py | 112 +++++++++ packages/designate.nix | 12 + 7 files changed, 536 insertions(+) create mode 100644 modules/controller/designate.nix create mode 100644 modules/knot-designate.nix create mode 100644 packages/designate-knot3-backend.py diff --git a/README.md b/README.md index f27368a..a63ff35 100644 --- a/README.md +++ b/README.md @@ -63,6 +63,17 @@ openstack-nix.nixosModules There are modules to setup the OpenStack controller, an OpenStack compute node and modules simplifying the creation of new NixOS tests. +#### Designate DNS backend + +Enabling `designate.enable` requires a separately configured BIND server. +The Designate module manages the OpenStack DNS services and their pool +configuration; the deployment owns BIND, its RNDC credentials, and DNS access. + +The default pool expects BIND on the controller, serving DNS on port 53 and +accepting RNDC on `127.0.0.1:953`. Provision the matching RNDC key at +`/etc/bind/rndc.key` on the controller. Keep the key outside the Nix store; +Designate invokes RNDC through its privileged rootwrap helper. + ### NixOS tests The NixOS tests are located under the `tests` attribute. To execute a NixOS test diff --git a/modules/controller/designate.nix b/modules/controller/designate.nix new file mode 100644 index 0000000..9e8b572 --- /dev/null +++ b/modules/controller/designate.nix @@ -0,0 +1,243 @@ +{ designate }: +{ + config, + lib, + pkgs, + ... +}: + +let + cfg = config.designate; + + designateConf = pkgs.writeText "designate.conf" '' + [DEFAULT] + transport_url = rabbit://openstack:openstack@controller + auth_strategy = keystone + log_dir = /var/log/designate + root_helper = "/run/wrappers/bin/sudo ${designate}/bin/designate-rootwrap ${rootwrapConf}" + + [storage:sqlalchemy] + connection = mysql+pymysql://designate:designate@controller/designate + + [service:api] + listen = 0.0.0.0:9001 + api_base_uri = http://controller:9001/ + api_paste_config = ${designate}/etc/designate/api-paste.ini + auth_strategy = keystone + enable_api_v2 = true + enable_api_admin = true + enable_host_header = true + enabled_extensions_admin = quotas,reports + + [service:mdns] + listen = 0.0.0.0:5354 + + [service:worker] + threads = 20 + + [keystone_authtoken] + www_authenticate_uri = http://controller:5000 + auth_url = http://controller:5000 + memcached_servers = controller:11211 + auth_type = password + project_domain_name = Default + user_domain_name = Default + project_name = service + username = designate + password = designate + + [oslo_concurrency] + lock_path = /var/lib/designate/tmp + ''; + + # BIND and its RNDC key must be provisioned by the deployment. This module + # only configures Designate's connection to that independently managed server. + designatePools = pkgs.writeText "pools.yaml" '' + - name: default + description: Designate bind backend pool + attributes: {} + + ns_records: + - hostname: osdns.openstack.test. + priority: 2 + - hostname: controller.openstack.test. + priority: 1 + + # List out the nameservers for this pool. These are the actual BIND servers. + # We use these to verify changes have propagated to all nameservers. + nameservers: + # controller bind + - host: ${config.openstack.controllerIP} + port: 53 + # dedicated dns server knot (replace it later) + - host: ${cfg.knot.address} + port: 53 + + # List out the targets for this pool. For BIND, most often, there will be one + # entry for each BIND server. + targets: + - type: bind9 + description: local bind server on controller + + # List out the designate-mdns servers from which BIND servers should + # request zone transfers (AXFRs) from. + masters: + # IP address of controller + - host: ${config.openstack.controllerIP} + port: 5354 + + # BIND Configuration options + # in our use case localhost == controller + options: + host: 127.0.0.1 + port: 53 + rndc_host: 127.0.0.1 + rndc_port: 953 + rndc_key_file: /etc/bind/rndc.key + # set relative path so rootwrap works + rndc_bin_path: rndc + + also_notifies: + - host: ${cfg.knot.address} + port: 53 + ''; + + service = command: { + after = [ + "rabbitmq.service" + "mysql.service" + "network.target" + ] + ++ lib.optional (!config.openstack.manual_setup) "designate.service"; + requires = lib.optional (!config.openstack.manual_setup) "designate.service"; + wantedBy = [ "multi-user.target" ]; + path = [ designate ]; + restartTriggers = [ cfg.config ]; + serviceConfig = { + User = "designate"; + Group = "designate"; + ExecStart = "${designate}/bin/${command} --config-file=${cfg.config}"; + Restart = "on-failure"; + }; + }; + + designate_env = pkgs.python3.buildEnv.override { + extraLibs = [ designate ]; + }; + utils_env = pkgs.buildEnv { + name = "utils"; + paths = with pkgs; [ + designate_env + bind + ]; + }; + + rootwrapConf = pkgs.callPackage ../../lib/rootwrap-conf.nix { + package = designate_env; + filterPath = "/etc/designate/rootwrap.d"; + inherit utils_env; + }; + +in +{ + options.designate = { + enable = lib.mkEnableOption "OpenStack Designate with an externally configured BIND backend" // { + default = false; + }; + + config = lib.mkOption { + type = lib.types.path; + default = designateConf; + description = "Designate configuration file."; + }; + + pools = lib.mkOption { + type = lib.types.path; + default = designatePools; + description = '' + Designate pools.yaml. The deployment must configure the authoritative + BIND server and provision its RNDC key separately; enabling Designate + does not configure BIND. The default pool expects BIND on the controller, + with DNS on port 53, RNDC on 127.0.0.1:953, and its key available locally + at /etc/bind/rndc.key. It also expects an external Knot secondary at + designate.knot.address. Override this file for a different DNS topology. + ''; + }; + + knot = { + address = lib.mkOption { + type = lib.types.str; + default = "192.168.200.23"; + description = "IP address of the external Knot DNS server."; + }; + }; + }; + + config = lib.mkIf cfg.enable { + users.users.designate = { + group = "designate"; + isSystemUser = true; + }; + users.groups.designate = { }; + + security.sudo.enable = true; + security.sudo.extraConfig = '' + designate ALL = (root) NOPASSWD: ${designate}/bin/designate-rootwrap ${rootwrapConf} * + ''; + + systemd.tmpfiles.settings."10-designate" = { + "/etc/designate/designate.conf"."L+".argument = "${cfg.config}"; + "/etc/designate/pools.yaml"."L+".argument = "${cfg.pools}"; + "/var/lib/designate".d = { + user = "designate"; + group = "designate"; + mode = "0755"; + }; + "/var/lib/designate/.ssh".d = { + user = "designate"; + group = "designate"; + mode = "0755"; + }; + "/var/lib/designate/tmp".D = { + user = "designate"; + group = "designate"; + mode = "0755"; + }; + "/var/log/designate".d = { + user = "designate"; + group = "designate"; + mode = "0755"; + }; + }; + + systemd.services.designate-api = service "designate-api"; + systemd.services.designate-central = service "designate-central"; + systemd.services.designate-mdns = service "designate-mdns"; + systemd.services.designate-producer = service "designate-producer"; + systemd.services.designate-worker = lib.recursiveUpdate (service "designate-worker") { + path = [ + designate + pkgs.openssh + pkgs.sudo + ]; + }; + + networking.firewall.allowedTCPPorts = [ 5354 ]; + networking.firewall.allowedUDPPorts = [ 5354 ]; + + systemd.services.designate-pool-update = { + description = "Update the OpenStack Designate pools"; + after = [ "designate-central.service" ]; + requires = [ "designate-central.service" ]; + wantedBy = [ "multi-user.target" ]; + path = [ designate ]; + restartTriggers = [ cfg.pools ]; + serviceConfig = { + Type = "oneshot"; + User = "designate"; + Group = "designate"; + ExecStart = "${designate}/bin/designate-manage --config-file=${cfg.config} pool update --file=${cfg.pools}"; + }; + }; + }; +} diff --git a/modules/controller/openstack-controller.nix b/modules/controller/openstack-controller.nix index c4df3b5..756cf26 100644 --- a/modules/controller/openstack-controller.nix +++ b/modules/controller/openstack-controller.nix @@ -7,6 +7,7 @@ horizon, cinder, python-openstackclient, + designate, }: { config, @@ -41,6 +42,7 @@ let mariadb -N -e "drop database nova;" || true mariadb -N -e "drop database nova_cell0;" || true mariadb -N -e "drop database neutron;" || true + mariadb -N -e "drop database designate;" || true ''; # Keep manual provisioning commands and systemd units on the same store scripts. @@ -106,6 +108,12 @@ let mariadb -N -e "ALTER USER 'neutron'@'%' IDENTIFIED BY 'neutron';" mariadb -N -e "GRANT ALL PRIVILEGES ON neutron.* TO 'neutron'@'%';" + # Designate + mariadb -N -e "CREATE DATABASE IF NOT EXISTS designate CHARACTER SET utf8 COLLATE utf8_general_ci;" + mariadb -N -e "CREATE USER IF NOT EXISTS 'designate'@'%' IDENTIFIED BY 'designate';" + mariadb -N -e "ALTER USER 'designate'@'%' IDENTIFIED BY 'designate';" + mariadb -N -e "GRANT ALL PRIVILEGES ON designate.* TO 'designate'@'%';" + # fix mariadb permissions mariadb -N -e "delete from mysql.user where user = ''';" @@ -250,6 +258,25 @@ let EOF ''; + designateStartScript = pkgs.writeShellScript "designate.sh" '' + export PATH=${ + lib.makeBinPath [ + designate + pkgs.openstackclient + pkgs.util-linux + ] + }:$PATH + + set -euxo pipefail + + runuser --user designate -- \ + ${designate}/bin/designate-manage --config-file ${config.designate.config} database sync + + openstack user show designate >/dev/null 2>&1 || \ + openstack user create --domain default --password designate designate + openstack role add --project service --user designate admin + ''; + checkControllerScript = pkgs.writeShellScript "check-controller.sh" '' export PATH=${ lib.makeBinPath [ @@ -281,6 +308,7 @@ in (import ./neutron.nix { inherit neutron; }) (import ./horizon.nix { inherit horizon; }) (import ./cinder.nix { inherit cinder; }) # only cinder management component + (import ./designate.nix { inherit designate; }) ]; config = { @@ -295,6 +323,7 @@ in (setupCommand "openstack-placement-setup" placementStartScript) (setupCommand "openstack-nova-setup" novaStartScript) (setupCommand "openstack-neutron-setup" neutronStartScript) + (setupCommand "openstack-designate-setup" designateStartScript) (setupCommand "openstack-check-controller" checkControllerScript) python-openstackclient ]; @@ -433,5 +462,28 @@ in ExecStart = "+${neutronStartScript}"; }; }; + + systemd.services.designate = + lib.mkIf (config.designate.enable && !config.openstack.manual_setup) + { + description = "OpenStack Designate setup"; + after = [ "keystone-all.service" ]; + wantedBy = [ "multi-user.target" ]; + environment = adminEnv; + path = [ + pkgs.gnugrep + pkgs.openstackclient + pkgs.util-linux + designate + ]; + restartIfChanged = false; + serviceConfig = { + RemainAfterExit = true; + Type = "oneshot"; + User = "root"; + Group = "root"; + ExecStart = "+${designateStartScript}"; + }; + }; }; } diff --git a/modules/default.nix b/modules/default.nix index ef09b5d..6103cb6 100644 --- a/modules/default.nix +++ b/modules/default.nix @@ -9,6 +9,7 @@ horizon cinder python-openstackclient + designate ; placement = openstackPkgs.openstack-placement; }; diff --git a/modules/knot-designate.nix b/modules/knot-designate.nix new file mode 100644 index 0000000..9f8db58 --- /dev/null +++ b/modules/knot-designate.nix @@ -0,0 +1,105 @@ +{ + config, + lib, + pkgs, + ... +}: + +let + cfg = config.services.knot-designate; +in +{ + options.services.knot-designate = { + enable = lib.mkEnableOption "an external Knot DNS secondary for OpenStack Designate"; + + mdnsAddress = lib.mkOption { + type = lib.types.str; + example = "10.0.0.39"; + description = "Address of the Designate MiniDNS server used for IXFR/AXFR."; + }; + + mdnsPort = lib.mkOption { + type = lib.types.port; + default = 5354; + description = "Port of the Designate MiniDNS server."; + }; + + sshUser = lib.mkOption { + type = lib.types.str; + default = "designate-knot"; + description = "Account through which Designate runs knotc."; + }; + + sshPort = lib.mkOption { + type = lib.types.port; + default = 22; + description = "SSH port on which Designate manages Knot."; + }; + + sshAuthorizedKeys = lib.mkOption { + type = lib.types.listOf lib.types.str; + default = [ ]; + description = "SSH public keys allowed to manage Knot for Designate."; + }; + }; + + config = lib.mkIf cfg.enable { + services.knot = { + enable = true; + settings = { + server.listen = [ + "0.0.0.0@53" + "::@53" + ]; + remote.designate-mdns.address = "${cfg.mdnsAddress}@${toString cfg.mdnsPort}"; + acl.designate-mdns = { + address = [ cfg.mdnsAddress ]; + action = [ "notify" ]; + }; + template.designate = { + master = "designate-mdns"; + acl = "designate-mdns"; + storage = "/var/lib/knot/zones"; + semantic-checks = true; + }; + }; + }; + + # Designate adds and removes zones dynamically. The configuration database + # preserves those entries while +nopurge refreshes the declarative base + # configuration on restart. + systemd.services.knot = { + preStart = '' + ${pkgs.knot-dns}/bin/knotc \ + --confdb=/var/lib/knot/confdb \ + conf-import /etc/knot/knot.conf +nopurge + ''; + serviceConfig = { + ExecStart = lib.mkForce "${pkgs.knot-dns}/bin/knotd --confdb=/var/lib/knot/confdb --socket=/run/knot/knot.sock"; + UMask = lib.mkForce "0007"; + }; + }; + + users.groups.${cfg.sshUser} = { }; + users.users.${cfg.sshUser} = { + isSystemUser = true; + group = cfg.sshUser; + extraGroups = [ "knot" ]; + home = "/var/lib/${cfg.sshUser}"; + createHome = true; + shell = pkgs.bashInteractive; + openssh.authorizedKeys.keys = cfg.sshAuthorizedKeys; + }; + + services.openssh = { + enable = true; + ports = [ cfg.sshPort ]; + }; + + networking.firewall.allowedTCPPorts = [ + 53 + cfg.sshPort + ]; + networking.firewall.allowedUDPPorts = [ 53 ]; + }; +} diff --git a/packages/designate-knot3-backend.py b/packages/designate-knot3-backend.py new file mode 100644 index 0000000..1e05aff --- /dev/null +++ b/packages/designate-knot3-backend.py @@ -0,0 +1,112 @@ +# Copyright 2026 CobaltCore contributors +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 + +"""Remote Knot DNS 3 backend for Designate. + +Knot is configured as a secondary for Designate MiniDNS. This backend only +manages Knot's dynamic zone configuration over SSH; record data is transferred +directly from MiniDNS to Knot using IXFR/AXFR. +""" + +import subprocess + +from oslo_concurrency import lockutils +from oslo_log import log as logging + +from designate.backend import base +from designate import exceptions +from designate import utils + + +LOG = logging.getLogger(__name__) + + +class Knot3Backend(base.Backend): + __plugin_name__ = 'knot3' + __backend_status__ = 'untested' + + def __init__(self, target): + super().__init__(target) + + self.ssh = self.options.get('ssh_bin_path', 'ssh') + self.ssh_host = self.options.get('ssh_host', self.host) + self.ssh_port = int(self.options.get('ssh_port', 22)) + self.ssh_user = self.options.get('ssh_user', 'designate-knot') + self.ssh_identity = self.options.get('ssh_identity_file') + self.ssh_known_hosts = self.options.get( + 'ssh_known_hosts_file', '/etc/ssh/ssh_known_hosts') + self.knotc = self.options.get('knotc_bin_path', 'knotc') + self.confdb = self.options.get( + 'confdb_path', '/var/lib/knot/confdb') + self.socket = self.options.get( + 'control_socket', '/run/knot/knot.sock') + self.template = self.options.get('template', 'designate') + + def _execute(self, *args): + command = [ + self.ssh, + '-o', 'BatchMode=yes', + '-p', str(self.ssh_port), + ] + if self.ssh_identity: + command.extend(['-i', self.ssh_identity]) + if self.ssh_known_hosts: + command.extend([ + '-o', 'UserKnownHostsFile=%s' % self.ssh_known_hosts]) + command.extend([ + '--', + '%s@%s' % (self.ssh_user, self.ssh_host), + self.knotc, + # '--confdb=%s' % self.confdb, + '--socket=%s' % self.socket, + ]) + command.extend(args) + + try: + return utils.execute(*command, timeout=self.timeout, run_as_root=False) + except (utils.processutils.ProcessExecutionError, + subprocess.TimeoutExpired) as error: + raise exceptions.Backend(error) + + @lockutils.synchronized('designate-knot3', external=True) + def _change_config(self, action, zone_name): + """Change one zone in Knot's persistent configuration database.""" + self._execute('conf-begin') + try: + item = 'zone[%s]' % zone_name.rstrip('.') + self._execute(action, item) + if action == 'conf-set': + self._execute( + 'conf-set', '%s.template' % item, self.template) + self._execute('conf-commit') + except Exception: + try: + self._execute('conf-abort') + except exceptions.Backend: + LOG.exception('Unable to abort the Knot configuration change') + raise + + def create_zone(self, context, zone): + LOG.debug('Creating zone %s in Knot', zone.name) + self._change_config('conf-set', zone.name) + + def update_zone(self, context, zone): + LOG.debug('Refreshing zone %s in Knot', zone.name) + self._execute('zone-refresh', zone.name.rstrip('.')) + + def delete_zone(self, context, zone, zone_params=None): + LOG.debug('Deleting zone %s from Knot', zone.name) + self._change_config('conf-unset', zone.name) + + # Once the configuration entry is gone, remove the secondary's copy. + # A failed purge does not mean that the zone is still being served. + try: + self._execute( + '--force', 'zone-purge', zone.name.rstrip('.'), '+orphan') + except exceptions.Backend: + LOG.warning('Unable to purge data for Knot zone %s', zone.name) diff --git a/packages/designate.nix b/packages/designate.nix index e34e2d5..022409d 100644 --- a/packages/designate.nix +++ b/packages/designate.nix @@ -77,6 +77,18 @@ python3Packages.buildPythonPackage (rec { pbr ]; + postPatch = '' + cp ${./designate-knot3-backend.py} designate/backend/impl_knot3.py + + substituteInPlace etc/designate/rootwrap.d/bind9.filters \ + --replace-fail "/usr/sbin/rndc" "rndc" + + substituteInPlace setup.cfg \ + --replace-fail \ + "infoblox = designate.backend.impl_infoblox:InfobloxBackend" \ + $'infoblox = designate.backend.impl_infoblox:InfobloxBackend\n\tknot3 = designate.backend.impl_knot3:Knot3Backend' + ''; + propagatedBuildInputs = [ (alembic.override { inherit sqlalchemy; }) dnspython From 300aff21042675913b24bb4e513b9f41a664c3ee Mon Sep 17 00:00:00 2001 From: Paul Kroeher Date: Fri, 28 Aug 2026 14:30:20 +0200 Subject: [PATCH 44/83] nix: add e2fsprogs to nova-compute systemd unit Signed-off-by: Paul Kroeher On-behalf-of: SAP paul.kroeher@sap.com --- modules/compute/nova.nix | 1 + 1 file changed, 1 insertion(+) diff --git a/modules/compute/nova.nix b/modules/compute/nova.nix index 28fa697..cc7250d 100644 --- a/modules/compute/nova.nix +++ b/modules/compute/nova.nix @@ -220,6 +220,7 @@ in lvm2 openiscsi nfs-utils + e2fsprogs ] ++ cfg.extraPkgs; environment.PYTHONPATH = "${nova_env}/${pkgs.python3.sitePackages}"; From 002256a64cf80de8558e609aab8e6811996d6569 Mon Sep 17 00:00:00 2001 From: Paul Kroeher Date: Fri, 28 Aug 2026 15:05:31 +0200 Subject: [PATCH 45/83] nix: fix neutron runtime directory setup on controller Runtime direcoties shouldn't be recreated upon the next reboot. The content should be persist. Signed-off-by: Paul Kroeher On-behalf-of: SAP paul.kroeher@sap.com --- modules/controller/neutron.nix | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/modules/controller/neutron.nix b/modules/controller/neutron.nix index a757f4a..7fd1fef 100644 --- a/modules/controller/neutron.nix +++ b/modules/controller/neutron.nix @@ -186,7 +186,7 @@ in systemd.tmpfiles.settings = { "10-neutron" = { "/var/log/neutron" = { - D = { + d = { group = "neutron"; mode = "0755"; user = "neutron"; @@ -218,21 +218,21 @@ in }; }; "/var/lock/neutron" = { - D = { + d = { group = "neutron"; mode = "0755"; user = "neutron"; }; }; "/var/lib/neutron" = { - D = { + d = { group = "neutron"; mode = "0755"; user = "neutron"; }; }; "/var/lib/neutron/dhcp" = { - D = { + d = { group = "neutron"; mode = "0755"; user = "neutron"; From 243a3c0493017884960664bca3336ed0ddd49810 Mon Sep 17 00:00:00 2001 From: Paul Kroeher Date: Mon, 7 Sep 2026 16:20:51 +0200 Subject: [PATCH 46/83] nix: storage module - make path of exported filesystem configurable Signed-off-by: Paul Kroeher On-behalf-of: SAP paul.kroeher@sap.com --- modules/storage/cinder-storage-node.nix | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/modules/storage/cinder-storage-node.nix b/modules/storage/cinder-storage-node.nix index 0b666fa..27dcb2f 100644 --- a/modules/storage/cinder-storage-node.nix +++ b/modules/storage/cinder-storage-node.nix @@ -182,6 +182,13 @@ in IP address of the storage node. ''; }; + storagePath = mkOption { + type = types.str; + default = "/exports"; + description = '' + Exported filesystem path on the storage node. + ''; + }; }; options.cinder-storage-node = { @@ -291,7 +298,7 @@ in group = "cinder"; mode = "0644"; argument = '' - ${config.openstack.storageIP}:/exports + ${config.openstack.storageIP}:${config.openstack.storagePath} ''; }; }; From 7eb455ae69a41e3fa9795dbe02c59d42099bffa0 Mon Sep 17 00:00:00 2001 From: Paul Kroeher Date: Mon, 7 Sep 2026 16:47:19 +0200 Subject: [PATCH 47/83] nix: storage module - add new configuration option: controllerHostname Signed-off-by: Paul Kroeher On-behalf-of: SAP paul.kroeher@sap.com --- modules/storage/cinder-storage-node.nix | 31 +++++++++++++++---------- 1 file changed, 19 insertions(+), 12 deletions(-) diff --git a/modules/storage/cinder-storage-node.nix b/modules/storage/cinder-storage-node.nix index 27dcb2f..c601120 100644 --- a/modules/storage/cinder-storage-node.nix +++ b/modules/storage/cinder-storage-node.nix @@ -35,14 +35,14 @@ let cinderConfLvm = pkgs.writeText "cinder.conf" '' [DEFAULT] - transport_url = rabbit://openstack:openstack@controller + transport_url = rabbit://openstack:openstack@${cfg.controllerHostname} auth_strategy = keystone my_ip = 10.0.0.20 enabled_backends = lvm volumes_dir = /var/lib/cinder/volumes state_path = /var/lib/cinder rootwrap_config = ${rootwrapConf} - glance_api_servers = http://controller:9292 + glance_api_servers = http://${cfg.controllerHostname}:9292 verify_glance_signatures = disabled log_dir = /var/log/cinder iscsi_ip_address = $my_ip @@ -50,12 +50,12 @@ let iscsi_target_prefix = iqn.2010-10.org.openstack: [database] - connection = mysql+pymysql://cinder:cinder@controller/cinder + connection = mysql+pymysql://cinder:cinder@${cfg.controllerHostname}/cinder [keystone_authtoken] - www_authenticate_uri = http://controller:5000 - auth_url = http://controller:5000 - memcached_servers = controller:11211 + www_authenticate_uri = http://${cfg.controllerHostname}:5000 + auth_url = http://${cfg.controllerHostname}:5000 + memcached_servers = ${cfg.controllerHostname}:11211 auth_type = password project_domain_name = default user_domain_name = default @@ -82,24 +82,24 @@ let cinderConfNfs = pkgs.writeText "cinder.conf" '' [DEFAULT] - transport_url = rabbit://openstack:openstack@controller + transport_url = rabbit://openstack:openstack@${cfg.controllerHostname} auth_strategy = keystone my_ip = 10.0.0.20 enabled_backends = nfs volumes_dir = /var/lib/cinder/volumes state_path = /var/lib/cinder rootwrap_config = ${rootwrapConf} - glance_api_servers = http://controller:9292 + glance_api_servers = http://${cfg.controllerHostname}:9292 verify_glance_signatures = disabled log_dir = /var/log/cinder [database] - connection = mysql+pymysql://cinder:cinder@controller/cinder + connection = mysql+pymysql://cinder:cinder@${cfg.controllerHostname}/cinder [keystone_authtoken] - www_authenticate_uri = http://controller:5000 - auth_url = http://controller:5000 - memcached_servers = controller:11211 + www_authenticate_uri = http://${cfg.controllerHostname}:5000 + auth_url = http://${cfg.controllerHostname}:5000 + memcached_servers = ${cfg.controllerHostname}:11211 auth_type = password project_domain_name = default user_domain_name = default @@ -195,6 +195,13 @@ in enable = mkEnableOption "Enable OpenStack Cinder storage node." // { default = true; }; + controllerHostname = mkOption { + type = types.str; + default = "controller"; + description = '' + Hostname of the OpenStack controller. + ''; + }; config = mkOption { default = if (cfg.backend == "lvm") then cinderConfLvm else cinderConfNfs; description = '' From d8e3dd013217b111502d61b4532abae345c8f0d7 Mon Sep 17 00:00:00 2001 From: Paul Kroeher Date: Mon, 7 Sep 2026 16:52:05 +0200 Subject: [PATCH 48/83] nix: storage module - move configuration option into global namespace Signed-off-by: Paul Kroeher On-behalf-of: SAP paul.kroeher@sap.com --- modules/generic/global-options.nix | 8 +++++++ modules/storage/cinder-storage-node.nix | 31 ++++++++++--------------- 2 files changed, 20 insertions(+), 19 deletions(-) diff --git a/modules/generic/global-options.nix b/modules/generic/global-options.nix index d8e90e4..9b20f2d 100644 --- a/modules/generic/global-options.nix +++ b/modules/generic/global-options.nix @@ -1,6 +1,14 @@ { lib, config, ... }: with lib; { + options.openstack.controllerHostname = lib.mkOption { + type = types.str; + default = "controller"; + description = '' + Hostname of the OpenStack controller. + ''; + }; + options.openstack.manual_setup = lib.mkOption { type = lib.types.bool; default = false; diff --git a/modules/storage/cinder-storage-node.nix b/modules/storage/cinder-storage-node.nix index c601120..dfd823f 100644 --- a/modules/storage/cinder-storage-node.nix +++ b/modules/storage/cinder-storage-node.nix @@ -35,14 +35,14 @@ let cinderConfLvm = pkgs.writeText "cinder.conf" '' [DEFAULT] - transport_url = rabbit://openstack:openstack@${cfg.controllerHostname} + transport_url = rabbit://openstack:openstack@${config.openstack.controllerHostname} auth_strategy = keystone my_ip = 10.0.0.20 enabled_backends = lvm volumes_dir = /var/lib/cinder/volumes state_path = /var/lib/cinder rootwrap_config = ${rootwrapConf} - glance_api_servers = http://${cfg.controllerHostname}:9292 + glance_api_servers = http://${config.openstack.controllerHostname}:9292 verify_glance_signatures = disabled log_dir = /var/log/cinder iscsi_ip_address = $my_ip @@ -50,12 +50,12 @@ let iscsi_target_prefix = iqn.2010-10.org.openstack: [database] - connection = mysql+pymysql://cinder:cinder@${cfg.controllerHostname}/cinder + connection = mysql+pymysql://cinder:cinder@${config.openstack.controllerHostname}/cinder [keystone_authtoken] - www_authenticate_uri = http://${cfg.controllerHostname}:5000 - auth_url = http://${cfg.controllerHostname}:5000 - memcached_servers = ${cfg.controllerHostname}:11211 + www_authenticate_uri = http://${config.openstack.controllerHostname}:5000 + auth_url = http://${config.openstack.controllerHostname}:5000 + memcached_servers = ${config.openstack.controllerHostname}:11211 auth_type = password project_domain_name = default user_domain_name = default @@ -82,24 +82,24 @@ let cinderConfNfs = pkgs.writeText "cinder.conf" '' [DEFAULT] - transport_url = rabbit://openstack:openstack@${cfg.controllerHostname} + transport_url = rabbit://openstack:openstack@${config.openstack.controllerHostname} auth_strategy = keystone my_ip = 10.0.0.20 enabled_backends = nfs volumes_dir = /var/lib/cinder/volumes state_path = /var/lib/cinder rootwrap_config = ${rootwrapConf} - glance_api_servers = http://${cfg.controllerHostname}:9292 + glance_api_servers = http://${config.openstack.controllerHostname}:9292 verify_glance_signatures = disabled log_dir = /var/log/cinder [database] - connection = mysql+pymysql://cinder:cinder@${cfg.controllerHostname}/cinder + connection = mysql+pymysql://cinder:cinder@${config.openstack.controllerHostname}/cinder [keystone_authtoken] - www_authenticate_uri = http://${cfg.controllerHostname}:5000 - auth_url = http://${cfg.controllerHostname}:5000 - memcached_servers = ${cfg.controllerHostname}:11211 + www_authenticate_uri = http://${config.openstack.controllerHostname}:5000 + auth_url = http://${config.openstack.controllerHostname}:5000 + memcached_servers = ${config.openstack.controllerHostname}:11211 auth_type = password project_domain_name = default user_domain_name = default @@ -195,13 +195,6 @@ in enable = mkEnableOption "Enable OpenStack Cinder storage node." // { default = true; }; - controllerHostname = mkOption { - type = types.str; - default = "controller"; - description = '' - Hostname of the OpenStack controller. - ''; - }; config = mkOption { default = if (cfg.backend == "lvm") then cinderConfLvm else cinderConfNfs; description = '' From a0b8f55f36ed5023c367399ff763188351b1f3f8 Mon Sep 17 00:00:00 2001 From: Paul Kroeher Date: Mon, 7 Sep 2026 17:01:01 +0200 Subject: [PATCH 49/83] nix: compute module - use new configuration option: controllerHostname Signed-off-by: Paul Kroeher On-behalf-of: SAP paul.kroeher@sap.com --- modules/compute/neutron.nix | 2 +- modules/compute/nova.nix | 16 ++++++++-------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/modules/compute/neutron.nix b/modules/compute/neutron.nix index 329dc7d..8eeb800 100644 --- a/modules/compute/neutron.nix +++ b/modules/compute/neutron.nix @@ -25,7 +25,7 @@ let debug = false # File name for the paste.deploy config for api service (string value) api_paste_config = ${neutron}/etc/neutron/api-paste.ini - transport_url = rabbit://openstack:openstack@controller + transport_url = rabbit://openstack:openstack@${config.openstack.controllerHostname} log_dir = /var/log/neutron [agent] diff --git a/modules/compute/nova.nix b/modules/compute/nova.nix index cc7250d..6c1b048 100644 --- a/modules/compute/nova.nix +++ b/modules/compute/nova.nix @@ -26,7 +26,7 @@ let rootwrap_config = ${rootwrapConf} compute_driver = libvirt.LibvirtDriver my_ip = ${config.openstack.computeIP} - transport_url = rabbit://openstack:openstack@controller + transport_url = rabbit://openstack:openstack@${config.openstack.controllerHostname} [api] auth_strategy = keystone @@ -38,12 +38,12 @@ let connection = sqlite:////var/lib/nova/nova.sqlite [glance] - api_servers = http://controller:9292 + api_servers = http://${config.openstack.controllerHostname}:9292 [keystone_authtoken] - www_authenticate_uri = http://controller:5000/ - auth_url = http://controller:5000/ - memcached_servers = controller:11211 + www_authenticate_uri = http://${config.openstack.controllerHostname}:5000/ + auth_url = http://${config.openstack.controllerHostname}:5000/ + memcached_servers = ${config.openstack.controllerHostname}:11211 auth_type = password project_domain_name = Default user_domain_name = Default @@ -55,7 +55,7 @@ let virt_type = kvm [neutron] - auth_url = http://controller:5000 + auth_url = http://${config.openstack.controllerHostname}:5000 auth_type = password project_domain_name = Default user_domain_name = Default @@ -76,13 +76,13 @@ let project_name = service auth_type = password user_domain_name = Default - auth_url = http://controller:5000/v3 + auth_url = http://${config.openstack.controllerHostname}:5000/v3 username = placement password = placement [service_user] send_service_user_token = true - auth_url = http://controller:5000/ + auth_url = http://${config.openstack.controllerHostname}:5000/ auth_strategy = keystone auth_type = password project_domain_name = Default From fe992097676e6ab391e03907449d0e5bc2bbd3dc Mon Sep 17 00:00:00 2001 From: Paul Kroeher Date: Mon, 7 Sep 2026 17:08:36 +0200 Subject: [PATCH 50/83] nix: controller module use new configuration option: controllerHostname Signed-off-by: Paul Kroeher On-behalf-of: SAP paul.kroeher@sap.com --- modules/controller/cinder.nix | 12 +++--- modules/controller/designate.nix | 12 +++--- modules/controller/glance.nix | 10 ++--- modules/controller/keystone.nix | 46 ++++++++++----------- modules/controller/neutron.nix | 14 +++---- modules/controller/nova.nix | 22 +++++----- modules/controller/openstack-controller.nix | 4 +- modules/controller/placement.nix | 6 +-- modules/generic/controller-host-entry.nix | 1 + 9 files changed, 64 insertions(+), 63 deletions(-) diff --git a/modules/controller/cinder.nix b/modules/controller/cinder.nix index 5c90c26..fd5f365 100644 --- a/modules/controller/cinder.nix +++ b/modules/controller/cinder.nix @@ -13,18 +13,18 @@ let cinderConf = pkgs.writeText "cinder-api.conf" '' [DEFAULT] - transport_url = rabbit://openstack:openstack@controller + transport_url = rabbit://openstack:openstack@${config.openstack.controllerHostname} auth_strategy = keystone - my_ip = controller + my_ip = ${config.openstack.controllerIP} verify_glance_signatures = disabled [database] - connection = mysql+pymysql://cinder:cinder@controller/cinder + connection = mysql+pymysql://cinder:cinder@${config.openstack.controllerHostname}/cinder [keystone_authtoken] - www_authenticate_uri = http://controller:5000 - auth_url = http://controller:5000 - memcached_servers = controller:11211 + www_authenticate_uri = http://${config.openstack.controllerHostname}:5000 + auth_url = http://${config.openstack.controllerHostname}:5000 + memcached_servers = ${config.openstack.controllerHostname}:11211 auth_type = password project_domain_name = default user_domain_name = default diff --git a/modules/controller/designate.nix b/modules/controller/designate.nix index 9e8b572..21a4195 100644 --- a/modules/controller/designate.nix +++ b/modules/controller/designate.nix @@ -11,17 +11,17 @@ let designateConf = pkgs.writeText "designate.conf" '' [DEFAULT] - transport_url = rabbit://openstack:openstack@controller + transport_url = rabbit://openstack:openstack@${config.openstack.controllerHostname} auth_strategy = keystone log_dir = /var/log/designate root_helper = "/run/wrappers/bin/sudo ${designate}/bin/designate-rootwrap ${rootwrapConf}" [storage:sqlalchemy] - connection = mysql+pymysql://designate:designate@controller/designate + connection = mysql+pymysql://designate:designate@${config.openstack.controllerHostname}/designate [service:api] listen = 0.0.0.0:9001 - api_base_uri = http://controller:9001/ + api_base_uri = http://${config.openstack.controllerHostname}:9001/ api_paste_config = ${designate}/etc/designate/api-paste.ini auth_strategy = keystone enable_api_v2 = true @@ -36,9 +36,9 @@ let threads = 20 [keystone_authtoken] - www_authenticate_uri = http://controller:5000 - auth_url = http://controller:5000 - memcached_servers = controller:11211 + www_authenticate_uri = http://${config.openstack.controllerHostname}:5000 + auth_url = http://${config.openstack.controllerHostname}:5000 + memcached_servers = ${config.openstack.controllerHostname}:11211 auth_type = password project_domain_name = Default user_domain_name = Default diff --git a/modules/controller/glance.nix b/modules/controller/glance.nix index 5bff0d9..cde00ad 100644 --- a/modules/controller/glance.nix +++ b/modules/controller/glance.nix @@ -17,12 +17,12 @@ let log_dir = /var/log/glance [database] - connection = mysql+pymysql://glance:glance@controller/glance + connection = mysql+pymysql://glance:glance@${config.openstack.controllerHostname}/glance [keystone_authtoken] - www_authenticate_uri = http://controller:5000 - auth_url = http://controller:5000 - memcached_servers = controller:11211 + www_authenticate_uri = http://${config.openstack.controllerHostname}:5000 + auth_url = http://${config.openstack.controllerHostname}:5000 + memcached_servers = ${config.openstack.controllerHostname}:11211 auth_type = password project_domain_name = Default user_domain_name = Default @@ -40,7 +40,7 @@ let filesystem_store_datadir = /var/lib/glance/images/ [oslo_limit] - auth_url = http://controller:5000 + auth_url = http://${config.openstack.controllerHostname}:5000 auth_type = password user_domain_id = default username = glance diff --git a/modules/controller/keystone.nix b/modules/controller/keystone.nix index 937507b..b9f5617 100644 --- a/modules/controller/keystone.nix +++ b/modules/controller/keystone.nix @@ -14,40 +14,40 @@ let serviceEndPointTemplateConf = pkgs.writeText "default_catalog.templates" '' # config for templated.Catalog, using camelCase because I don't want to do # translations for keystone compat - catalog.RegionOne.identity.publicURL = http://controller:5000/v3 - catalog.RegionOne.identity.adminURL = http://controller:5000/v3 - catalog.RegionOne.identity.internalURL = http://controller:5000/v3 + catalog.RegionOne.identity.publicURL = http://${config.openstack.controllerHostname}:5000/v3 + catalog.RegionOne.identity.adminURL = http://${config.openstack.controllerHostname}:5000/v3 + catalog.RegionOne.identity.internalURL = http://${config.openstack.controllerHostname}:5000/v3 catalog.RegionOne.identity.name = Identity Service # fake compute service for now to help novaclient tests work - catalog.RegionOne.compute.publicURL = http://controller:8774/v2.1 - catalog.RegionOne.compute.adminURL = http://controller:8774/v2.1 - catalog.RegionOne.compute.internalURL = http://controller:8774/v2.1 + catalog.RegionOne.compute.publicURL = http://${config.openstack.controllerHostname}:8774/v2.1 + catalog.RegionOne.compute.adminURL = http://${config.openstack.controllerHostname}:8774/v2.1 + catalog.RegionOne.compute.internalURL = http://${config.openstack.controllerHostname}:8774/v2.1 catalog.RegionOne.compute.name = Compute Service V2.1 - catalog.RegionOne.image.publicURL = http://controller:9292 - catalog.RegionOne.image.adminURL = http://controller:9292 - catalog.RegionOne.image.internalURL = http://controller:9292 + catalog.RegionOne.image.publicURL = http://${config.openstack.controllerHostname}:9292 + catalog.RegionOne.image.adminURL = http://${config.openstack.controllerHostname}:9292 + catalog.RegionOne.image.internalURL = http://${config.openstack.controllerHostname}:9292 catalog.RegionOne.image.name = Image Service - catalog.RegionOne.network.publicURL = http://controller:9696 - catalog.RegionOne.network.adminURL = http://controller:9696 - catalog.RegionOne.network.internalURL = http://controller:9696 + catalog.RegionOne.network.publicURL = http://${config.openstack.controllerHostname}:9696 + catalog.RegionOne.network.adminURL = http://${config.openstack.controllerHostname}:9696 + catalog.RegionOne.network.internalURL = http://${config.openstack.controllerHostname}:9696 catalog.RegionOne.network.name = Network Service - catalog.RegionOne.placement.publicURL = http://controller:8778 - catalog.RegionOne.placement.adminURL = http://controller:8778 - catalog.RegionOne.placement.internalURL = http://controller:8778 + catalog.RegionOne.placement.publicURL = http://${config.openstack.controllerHostname}:8778 + catalog.RegionOne.placement.adminURL = http://${config.openstack.controllerHostname}:8778 + catalog.RegionOne.placement.internalURL = http://${config.openstack.controllerHostname}:8778 catalog.RegionOne.placement.name = Placement Service - catalog.RegionOne.volumev3.publicURL = http://controller:8776/v3 - catalog.RegionOne.volumev3.adminURL = http://controller:8776/v3 - catalog.RegionOne.volumev3.internalURL = http://controller:8776/v3 + catalog.RegionOne.volumev3.publicURL = http://${config.openstack.controllerHostname}:8776/v3 + catalog.RegionOne.volumev3.adminURL = http://${config.openstack.controllerHostname}:8776/v3 + catalog.RegionOne.volumev3.internalURL = http://${config.openstack.controllerHostname}:8776/v3 catalog.RegionOne.volumev3.name = Cinder Service - catalog.RegionOne.dns.publicURL = http://controller:9001/ - catalog.RegionOne.dns.adminURL = http://controller:9001/ - catalog.RegionOne.dns.internalURL = http://controller:9001/ + catalog.RegionOne.dns.publicURL = http://${config.openstack.controllerHostname}:9001/ + catalog.RegionOne.dns.adminURL = http://${config.openstack.controllerHostname}:9001/ + catalog.RegionOne.dns.internalURL = http://${config.openstack.controllerHostname}:9001/ catalog.RegionOne.dns.name = DNS Service ''; @@ -55,7 +55,7 @@ let [DEFAULT] log_dir = /var/log/keystone [database] - connection = mysql+pymysql://keystone:keystone@controller/keystone + connection = mysql+pymysql://keystone:keystone@${config.openstack.controllerHostname}/keystone [token] provider = fernet @@ -127,7 +127,7 @@ in services.nginx = { enable = true; virtualHosts = { - controller = { + ${config.openstack.controllerHostname} = { locations."/".proxyPass = "http://127.0.0.1:5001/"; listen = [ { diff --git a/modules/controller/neutron.nix b/modules/controller/neutron.nix index 7fd1fef..07cb810 100644 --- a/modules/controller/neutron.nix +++ b/modules/controller/neutron.nix @@ -25,24 +25,24 @@ let # neutron.conf is used as configuration file for neutron-metadata-agent as well neutronConf = pkgs.writeText "neutron.conf" '' [database] - connection = mysql+pymysql://neutron:neutron@controller/neutron + connection = mysql+pymysql://neutron:neutron@${config.openstack.controllerHostname}/neutron [DEFAULT] core_plugin = ml2 service_plugins = api_paste_config = ${neutron}/etc/neutron/api-paste.ini - transport_url = rabbit://openstack:openstack@controller + transport_url = rabbit://openstack:openstack@${config.openstack.controllerHostname} auth_strategy = keystone notify_nova_on_port_status_changes = true notify_nova_on_port_data_changes = true log_dir = /var/log/neutron - nova_metadata_host = controller + nova_metadata_host = ${config.openstack.controllerHostname} metadata_proxy_shared_secret = neutron_metadata_secret [keystone_authtoken] - www_authenticate_uri = http://controller:5000 - auth_url = http://controller:5000 - memcached_servers = controller:11211 + www_authenticate_uri = http://${config.openstack.controllerHostname}:5000 + auth_url = http://${config.openstack.controllerHostname}:5000 + memcached_servers = ${config.openstack.controllerHostname}:11211 auth_type = password project_domain_name = Default user_domain_name = Default @@ -57,7 +57,7 @@ let service_token_roles = admin [nova] - auth_url = http://controller:5000 + auth_url = http://${config.openstack.controllerHostname}:5000 auth_type = password project_domain_name = Default user_domain_name = Default diff --git a/modules/controller/nova.nix b/modules/controller/nova.nix index 12e6bda..818d26e 100644 --- a/modules/controller/nova.nix +++ b/modules/controller/nova.nix @@ -13,14 +13,14 @@ let novaConf = pkgs.writeText "nova.conf" '' [api_database] - connection = mysql+pymysql://nova:nova@controller/nova_api + connection = mysql+pymysql://nova:nova@${config.openstack.controllerHostname}/nova_api [database] - connection = mysql+pymysql://nova:nova@controller/nova + connection = mysql+pymysql://nova:nova@${config.openstack.controllerHostname}/nova [DEFAULT] - transport_url = rabbit://openstack:openstack@controller:5672/ - my_ip = 10.0.0.11 + transport_url = rabbit://openstack:openstack@${config.openstack.controllerHostname}:5672/ + my_ip = ${config.openstack.controllerIP} log_dir = /var/log/nova lock_path = /var/lock/nova state_path = /var/lib/nova @@ -29,9 +29,9 @@ let auth_strategy = keystone [keystone_authtoken] - www_authenticate_uri = http://controller:5000/ - auth_url = http://controller:5000/ - memcached_servers = controller:11211 + www_authenticate_uri = http://${config.openstack.controllerHostname}:5000/ + auth_url = http://${config.openstack.controllerHostname}:5000/ + memcached_servers = ${config.openstack.controllerHostname}:11211 auth_type = password project_domain_name = Default user_domain_name = Default @@ -41,7 +41,7 @@ let [service_user] send_service_user_token = true - auth_url = http://controller:5000/ + auth_url = http://${config.openstack.controllerHostname}:5000/ auth_strategy = keystone auth_type = password project_domain_name = Default @@ -59,7 +59,7 @@ let server_proxyclient_address = $my_ip [glance] - api_servers = http://controller:9292 + api_servers = http://${config.openstack.controllerHostname}:9292 [oslo_concurrency] lock_path = /var/lib/nova/tmp @@ -69,7 +69,7 @@ let project_name = service auth_type = password user_domain_name = Default - auth_url = http://controller:5000/v3 + auth_url = http://${config.openstack.controllerHostname}:5000/v3 username = placement password = placement @@ -77,7 +77,7 @@ let discover_hosts_in_cells_interval = 300 [neutron] - auth_url = http://controller:5000 + auth_url = http://${config.openstack.controllerHostname}:5000 auth_type = password project_domain_name = Default user_domain_name = Default diff --git a/modules/controller/openstack-controller.nix b/modules/controller/openstack-controller.nix index 756cf26..6798ec5 100644 --- a/modules/controller/openstack-controller.nix +++ b/modules/controller/openstack-controller.nix @@ -22,7 +22,7 @@ let OS_PROJECT_NAME = "admin"; OS_USER_DOMAIN_NAME = "Default"; OS_PROJECT_DOMAIN_NAME = "Default"; - OS_AUTH_URL = "http://controller:5000/v3"; + OS_AUTH_URL = "http://${config.openstack.controllerHostname}:5000/v3"; OS_IDENTITY_API_VERSION = "3"; }; @@ -404,7 +404,7 @@ in }; # Placement service can be tested by executing - # curl http://controller:8778 + # curl http://${config.openstack.controllerHostname}:8778 # and receive some json with version info as result. systemd.services.placement = lib.mkIf (!config.openstack.manual_setup) { description = "OpenStack Placement setup"; diff --git a/modules/controller/placement.nix b/modules/controller/placement.nix index 95b54b5..3b2dc67 100644 --- a/modules/controller/placement.nix +++ b/modules/controller/placement.nix @@ -16,8 +16,8 @@ let auth_strategy = keystone [keystone_authtoken] - www_authenticate_uri = http://controller:5000 - auth_url = http://controller:5000 + www_authenticate_uri = http://${config.openstack.controllerHostname}:5000 + auth_url = http://${config.openstack.controllerHostname}:5000 auth_type = password project_domain_name = Default user_domain_name = Default @@ -26,7 +26,7 @@ let password = placement [placement_database] - connection = mysql+pymysql://placement:placement@controller/placement + connection = mysql+pymysql://placement:placement@${config.openstack.controllerHostname}/placement ''; in { diff --git a/modules/generic/controller-host-entry.nix b/modules/generic/controller-host-entry.nix index 1a80a62..5321766 100644 --- a/modules/generic/controller-host-entry.nix +++ b/modules/generic/controller-host-entry.nix @@ -4,6 +4,7 @@ with lib; options.openstack = { controllerIP = mkOption { type = types.str; + default = "10.0.0.11"; description = '' IP address of the controller. Will be used to make a /etc/hosts entry to make the controller available via "controller". From 3aeff3028baf41b6d3760b35f2e91bcbcd7c8948 Mon Sep 17 00:00:00 2001 From: Paul Kroeher Date: Tue, 8 Sep 2026 11:51:15 +0200 Subject: [PATCH 51/83] nix: fix systemd-tmpfiles settings for a production deployment Signed-off-by: Paul Kroeher On-behalf-of: SAP paul.kroeher@sap.com --- modules/controller/cinder.nix | 6 +++--- modules/controller/glance.nix | 6 +++--- modules/controller/nova.nix | 6 +++--- modules/controller/placement.nix | 2 +- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/modules/controller/cinder.nix b/modules/controller/cinder.nix index fd5f365..9567afd 100644 --- a/modules/controller/cinder.nix +++ b/modules/controller/cinder.nix @@ -89,21 +89,21 @@ in systemd.tmpfiles.settings = { "10-cinder" = { "/var/lib/cinder/" = { - D = { + d = { user = "cinder"; group = "cinder"; mode = "0755"; }; }; "/var/lib/cinder/volumes" = { - D = { + d = { user = "cinder"; group = "cinder"; mode = "0755"; }; }; "/var/log/cinder/" = { - D = { + d = { user = "cinder"; group = "cinder"; mode = "0755"; diff --git a/modules/controller/glance.nix b/modules/controller/glance.nix index cde00ad..8a9755e 100644 --- a/modules/controller/glance.nix +++ b/modules/controller/glance.nix @@ -85,17 +85,17 @@ in systemd.tmpfiles.settings = { "10-glance" = { "/etc/glance/glance-api.conf" = { - L = { + "L+" = { argument = "${cfg.config}"; }; }; "/etc/glance/glance-api-paste.ini" = { - L = { + "L+" = { argument = "${glance}/etc/glance/glance-api-paste.ini"; }; }; "/etc/glance/schema-image.json" = { - L = { + "L+" = { argument = "${glance}/etc/glance/schema-image.json"; }; }; diff --git a/modules/controller/nova.nix b/modules/controller/nova.nix index 818d26e..756d511 100644 --- a/modules/controller/nova.nix +++ b/modules/controller/nova.nix @@ -122,12 +122,12 @@ in systemd.tmpfiles.settings = { "10-nova" = { "/etc/nova/nova.conf" = { - L = { + "L+" = { argument = "${cfg.config}"; }; }; "/etc/nova/api-paste.ini" = { - L = { + "L+" = { argument = "${cfg.novaPackage}/etc/nova/api-paste.ini"; }; }; @@ -153,7 +153,7 @@ in }; }; "/usr/share/novnc" = { - L = { + "L+" = { argument = "${pkgs.novnc}/share/webapps/novnc"; }; }; diff --git a/modules/controller/placement.nix b/modules/controller/placement.nix index 3b2dc67..62c499d 100644 --- a/modules/controller/placement.nix +++ b/modules/controller/placement.nix @@ -64,7 +64,7 @@ in systemd.tmpfiles.settings = { "10-placement" = { "/etc/placement/placement.conf" = { - L = { + "L+" = { argument = "${cfg.config}"; }; }; From b13400a357a98b0b3cc7abc6821f018cd09f9786 Mon Sep 17 00:00:00 2001 From: Paul Kroeher Date: Tue, 8 Sep 2026 13:55:46 +0200 Subject: [PATCH 52/83] nix: fix generated configuration file name Signed-off-by: Paul Kroeher On-behalf-of: SAP paul.kroeher@sap.com --- modules/controller/cinder.nix | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/controller/cinder.nix b/modules/controller/cinder.nix index 9567afd..4029050 100644 --- a/modules/controller/cinder.nix +++ b/modules/controller/cinder.nix @@ -11,7 +11,7 @@ with lib; let cfg = config.cinder; - cinderConf = pkgs.writeText "cinder-api.conf" '' + cinderConf = pkgs.writeText "cinder.conf" '' [DEFAULT] transport_url = rabbit://openstack:openstack@${config.openstack.controllerHostname} auth_strategy = keystone From f6e3e33d7eddd7539fdf0092289a65d953bd1fe0 Mon Sep 17 00:00:00 2001 From: Paul Kroeher Date: Tue, 8 Sep 2026 14:12:42 +0200 Subject: [PATCH 53/83] nix: use configuration as my_ip value Signed-off-by: Paul Kroeher On-behalf-of: SAP paul.kroeher@sap.com --- modules/storage/cinder-storage-node.nix | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/storage/cinder-storage-node.nix b/modules/storage/cinder-storage-node.nix index dfd823f..1b504b2 100644 --- a/modules/storage/cinder-storage-node.nix +++ b/modules/storage/cinder-storage-node.nix @@ -37,7 +37,7 @@ let [DEFAULT] transport_url = rabbit://openstack:openstack@${config.openstack.controllerHostname} auth_strategy = keystone - my_ip = 10.0.0.20 + my_ip = ${config.openstack.storageIP} enabled_backends = lvm volumes_dir = /var/lib/cinder/volumes state_path = /var/lib/cinder @@ -84,7 +84,7 @@ let [DEFAULT] transport_url = rabbit://openstack:openstack@${config.openstack.controllerHostname} auth_strategy = keystone - my_ip = 10.0.0.20 + my_ip = ${config.openstack.storageIP} enabled_backends = nfs volumes_dir = /var/lib/cinder/volumes state_path = /var/lib/cinder From 1c45913948988ed365b9a641f3a7d5a9979b22d9 Mon Sep 17 00:00:00 2001 From: Paul Kroeher Date: Tue, 8 Sep 2026 14:14:18 +0200 Subject: [PATCH 54/83] nix: storage module - settings deploy some settings only if this module is not deployed alongside the controller module on the same host. Signed-off-by: Paul Kroeher On-behalf-of: SAP paul.kroeher@sap.com --- modules/storage/cinder-storage-node.nix | 59 ++++++++++++++----------- 1 file changed, 32 insertions(+), 27 deletions(-) diff --git a/modules/storage/cinder-storage-node.nix b/modules/storage/cinder-storage-node.nix index 1b504b2..c2185cb 100644 --- a/modules/storage/cinder-storage-node.nix +++ b/modules/storage/cinder-storage-node.nix @@ -245,35 +245,40 @@ in cinder ALL = (root) NOPASSWD: ${cinder_env}/bin/cinder-rootwrap ${rootwrapConf} * ''; + # set this attributes only if this storage module is deployed to a different host than the controller + # or: don't set this attributes if this storage module is deployed alongside the controller module systemd.tmpfiles.settings = { - "20-cinder" = { - "/var/lib/cinder/" = { - d = { - user = "cinder"; - group = "cinder"; - mode = "0755"; - }; - }; - "/var/lib/cinder/volumes" = { - d = { - user = "cinder"; - group = "cinder"; - mode = "0755"; - }; - }; - "/var/log/cinder/" = { - d = { - user = "cinder"; - group = "cinder"; - mode = "0755"; - }; - }; - "/etc/cinder/cinder.conf" = { - "L+" = { - argument = "${cfg.config}"; + "20-cinder" = + lib.mkIf + (!(config ? cinder && builtins.isBool config.cinder.enable && config.cinder.enable == true)) + { + "/var/lib/cinder/" = { + d = { + user = "cinder"; + group = "cinder"; + mode = "0755"; + }; + }; + "/var/lib/cinder/volumes" = { + d = { + user = "cinder"; + group = "cinder"; + mode = "0755"; + }; + }; + "/var/log/cinder/" = { + d = { + user = "cinder"; + group = "cinder"; + mode = "0755"; + }; + }; + "/etc/cinder/cinder.conf" = { + "L+" = { + argument = "${cfg.config}"; + }; + }; }; - }; - }; "20-cinder-backend" = if (cfg.backend == "lvm") then # LVM configuration files From 99a90601b11713179fb18274cabf15a898b68204 Mon Sep 17 00:00:00 2001 From: Paul Kroeher Date: Tue, 8 Sep 2026 14:53:49 +0200 Subject: [PATCH 55/83] nix: storage module - move rootwrap configuration into a config option Signed-off-by: Paul Kroeher On-behalf-of: SAP paul.kroeher@sap.com --- modules/storage/cinder-storage-node.nix | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/modules/storage/cinder-storage-node.nix b/modules/storage/cinder-storage-node.nix index c2185cb..eea4d7f 100644 --- a/modules/storage/cinder-storage-node.nix +++ b/modules/storage/cinder-storage-node.nix @@ -41,7 +41,7 @@ let enabled_backends = lvm volumes_dir = /var/lib/cinder/volumes state_path = /var/lib/cinder - rootwrap_config = ${rootwrapConf} + rootwrap_config = /etc/cinder/rootwrap.conf glance_api_servers = http://${config.openstack.controllerHostname}:9292 verify_glance_signatures = disabled log_dir = /var/log/cinder @@ -88,7 +88,7 @@ let enabled_backends = nfs volumes_dir = /var/lib/cinder/volumes state_path = /var/lib/cinder - rootwrap_config = ${rootwrapConf} + rootwrap_config = /etc/cinder/rootwrap.conf glance_api_servers = http://${config.openstack.controllerHostname}:9292 verify_glance_signatures = disabled log_dir = /var/log/cinder @@ -227,6 +227,12 @@ in The nfs-server /etc/exports file. ''; }; + rootwrapConf = mkOption { + default = rootwrapConf; + description = '' + Cinder root wrap configuration file. + ''; + }; }; config = { @@ -243,6 +249,7 @@ in security.sudo.enable = true; security.sudo.extraConfig = '' cinder ALL = (root) NOPASSWD: ${cinder_env}/bin/cinder-rootwrap ${rootwrapConf} * + cinder ALL = (root) NOPASSWD: ${cinder_env}/bin/cinder-rootwrap /etc/cinder/rootwrap.conf * ''; # set this attributes only if this storage module is deployed to a different host than the controller @@ -308,6 +315,13 @@ in }; }; }; + "20-cinder-root-wrap" = { + "/etc/cinder/rootwrap.conf" = { + "L+" = { + argument = "${cfg.rootwrapConf}"; + }; + }; + }; }; # start iSCSI target daemon From 22c67c5c013e078cb021cf5b5b38516b3fd03d65 Mon Sep 17 00:00:00 2001 From: Paul Kroeher Date: Wed, 9 Sep 2026 16:42:57 +0200 Subject: [PATCH 56/83] nix: add option to specify live migration ip address Signed-off-by: Paul Kroeher On-behalf-of: SAP paul.kroeher@sap.com --- modules/generic/global-options.nix | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/modules/generic/global-options.nix b/modules/generic/global-options.nix index 9b20f2d..f66b7db 100644 --- a/modules/generic/global-options.nix +++ b/modules/generic/global-options.nix @@ -30,4 +30,12 @@ with lib; be configured on the host; this option does not configure an interface. ''; }; + + options.openstack.live_migration_inbound_addr = lib.mkOption { + default = "10.100.100.1"; + type = types.str; + description = '' + My own ip address of the migration network. Usually our internal 100G link. + ''; + }; } From 9912c8a1c6a7c34ffd0ab53dbd192f685a013319 Mon Sep 17 00:00:00 2001 From: Paul Kroeher Date: Tue, 15 Sep 2026 09:49:23 +0200 Subject: [PATCH 57/83] nix: more failure resistant script Signed-off-by: Paul Kroeher On-behalf-of: SAP paul.kroeher@sap.com --- modules/compute/neutron.nix | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/modules/compute/neutron.nix b/modules/compute/neutron.nix index 8eeb800..323a851 100644 --- a/modules/compute/neutron.nix +++ b/modules/compute/neutron.nix @@ -67,12 +67,11 @@ let echo "br-provider didn't exist already. Proceed with basic setup." set -euxo pipefail ovs-vsctl add-br br-provider - ovs-vsctl add-port br-provider ${cfg.providerInterface} fi + ovs-vsctl --may-exist add-port br-provider ${cfg.providerInterface} # enable uplink provider interface ip link set dev ${cfg.providerInterface} up - ''; in From 0d3599c3452c7cf07c6302b98d27ab0ccdbcbba8 Mon Sep 17 00:00:00 2001 From: Paul Kroeher Date: Tue, 15 Sep 2026 09:50:01 +0200 Subject: [PATCH 58/83] nix: fix typo and more failsafe setup script Signed-off-by: Paul Kroeher On-behalf-of: SAP paul.kroeher@sap.com --- modules/controller/openstack-controller.nix | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/modules/controller/openstack-controller.nix b/modules/controller/openstack-controller.nix index 6798ec5..1ae1021 100644 --- a/modules/controller/openstack-controller.nix +++ b/modules/controller/openstack-controller.nix @@ -67,6 +67,7 @@ let databaseSetupScript = pkgs.writeShellScript "database-setup.sh" '' export PATH=${lib.makeBinPath [ pkgs.mariadb ]}:$PATH + set -euxo pipefail # Keystone mariadb -N -e "CREATE DATABASE IF NOT EXISTS keystone;" @@ -285,7 +286,7 @@ let }:$PATH systemctl status neutron-server.service - systemctl status glance-api.server + systemctl status glance-api.service systemctl status uwsgi.service systemctl status cinder-scheduler.service systemctl status nova-api.service From c8a3b8e702eb77370978edacd3ab4fcb289e897c Mon Sep 17 00:00:00 2001 From: Paul Kroeher Date: Tue, 15 Sep 2026 09:51:43 +0200 Subject: [PATCH 59/83] nix: standardize nfs configuration Use config option storagePath in CI/CD setup script and add guard for nfs-server. Signed-off-by: Paul Kroeher On-behalf-of: SAP paul.kroeher@sap.com --- modules/storage/cinder-storage-node.nix | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/modules/storage/cinder-storage-node.nix b/modules/storage/cinder-storage-node.nix index eea4d7f..40adb8c 100644 --- a/modules/storage/cinder-storage-node.nix +++ b/modules/storage/cinder-storage-node.nix @@ -123,7 +123,7 @@ let ''; cinderDefaultNFSexports = pkgs.writeText "exports" '' - /exports 10.0.0.0/24(rw,no_root_squash,insecure) + ${config.openstack.storagePath} 10.0.0.0/24(rw,no_root_squash,insecure) ''; cinderVolumeSetupScript = pkgs.writeShellScript "cinder-volume-setup.sh" '' @@ -133,20 +133,22 @@ let ] }:$PATH - if [ -e /exports/.cinder-volume-setup-done-dont-delete-me ]; then + set -euxo pipefail + + if [ -e ${config.openstack.storagePath}/.cinder-volume-setup-done-dont-delete-me ]; then echo "cinder volume setup already done. Check content of this script." fi - mkdir /exports + mkdir ${config.openstack.storagePath} mkfs.ext4 -F -m 0 -L cinder /dev/vdb - mount /dev/vdb /exports + mount /dev/vdb ${config.openstack.storagePath} exportfs -rv - rm -rf /exports/lost+found - chown cinder /exports - chgrp cinder /exports + rm -rf ${config.openstack.storagePath}/lost+found + chown cinder ${config.openstack.storagePath} + chgrp cinder ${config.openstack.storagePath} systemctl restart cinder-volume.service - touch /exports/.cinder-volume-setup-done-dont-delete-me + touch ${config.openstack.storagePath}/.cinder-volume-setup-done-dont-delete-me ''; cinderVolumeSetupCommand = pkgs.writeShellApplication { @@ -359,7 +361,7 @@ in }; }; - services.nfs.server.enable = if (cfg.backend == "lvm") then false else true; + services.nfs.server.enable = cfg.enable && cfg.backend != "lvm"; services.nfs.server.exports = builtins.readFile cfg.exports; # run this service only in CI/CD setups From a1ad0f6b9473ba0c49fcd87a14f9d8c26264e801 Mon Sep 17 00:00:00 2001 From: Paul Kroeher Date: Tue, 15 Sep 2026 14:42:52 +0200 Subject: [PATCH 60/83] nix: fix linting Signed-off-by: Paul Kroeher On-behalf-of: SAP paul.kroeher@sap.com --- modules/controller/openstack-controller.nix | 42 ++++++++++----------- modules/generic/global-options.nix | 2 +- packages/default.nix | 3 +- 3 files changed, 22 insertions(+), 25 deletions(-) diff --git a/modules/controller/openstack-controller.nix b/modules/controller/openstack-controller.nix index 1ae1021..48ad0c7 100644 --- a/modules/controller/openstack-controller.nix +++ b/modules/controller/openstack-controller.nix @@ -464,27 +464,25 @@ in }; }; - systemd.services.designate = - lib.mkIf (config.designate.enable && !config.openstack.manual_setup) - { - description = "OpenStack Designate setup"; - after = [ "keystone-all.service" ]; - wantedBy = [ "multi-user.target" ]; - environment = adminEnv; - path = [ - pkgs.gnugrep - pkgs.openstackclient - pkgs.util-linux - designate - ]; - restartIfChanged = false; - serviceConfig = { - RemainAfterExit = true; - Type = "oneshot"; - User = "root"; - Group = "root"; - ExecStart = "+${designateStartScript}"; - }; - }; + systemd.services.designate = lib.mkIf (config.designate.enable && !config.openstack.manual_setup) { + description = "OpenStack Designate setup"; + after = [ "keystone-all.service" ]; + wantedBy = [ "multi-user.target" ]; + environment = adminEnv; + path = [ + pkgs.gnugrep + pkgs.openstackclient + pkgs.util-linux + designate + ]; + restartIfChanged = false; + serviceConfig = { + RemainAfterExit = true; + Type = "oneshot"; + User = "root"; + Group = "root"; + ExecStart = "+${designateStartScript}"; + }; + }; }; } diff --git a/modules/generic/global-options.nix b/modules/generic/global-options.nix index f66b7db..f5aa3db 100644 --- a/modules/generic/global-options.nix +++ b/modules/generic/global-options.nix @@ -1,4 +1,4 @@ -{ lib, config, ... }: +{ lib, ... }: with lib; { options.openstack.controllerHostname = lib.mkOption { diff --git a/packages/default.nix b/packages/default.nix index 329b0db..681add3 100644 --- a/packages/default.nix +++ b/packages/default.nix @@ -258,8 +258,7 @@ let python3Packages oslo-log oslo-serialization - - ; + ; }; jsonpath-rw-ext = callPackage ./jsonpath-rw-ext.nix { inherit python3Packages; }; keystone = callPackage ./keystone.nix { From 34156ceba030ac2a71960cd4b61e2f66f21e740d Mon Sep 17 00:00:00 2001 From: Paul Kroeher Date: Thu, 17 Sep 2026 12:39:54 +0200 Subject: [PATCH 61/83] nix: enable caching in keystone Signed-off-by: Paul Kroeher On-behalf-of: SAP paul.kroeher@sap.com --- modules/controller/keystone.nix | 9 +++++++-- packages/keystone.nix | 4 +++- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/modules/controller/keystone.nix b/modules/controller/keystone.nix index b9f5617..2d533d8 100644 --- a/modules/controller/keystone.nix +++ b/modules/controller/keystone.nix @@ -57,6 +57,11 @@ let [database] connection = mysql+pymysql://keystone:keystone@${config.openstack.controllerHostname}/keystone + [cache] + enabled = true + backend = dogpile.cache.memcached + memcache_servers = 127.0.0.1:11211 + [token] provider = fernet @@ -101,14 +106,14 @@ in systemd.tmpfiles.settings = { "10-keystone" = { "/var/lib/keystone/" = { - D = { + d = { user = "keystone"; group = "keystone"; mode = "0755"; }; }; "/var/log/keystone/" = { - D = { + d = { user = "keystone"; group = "keystone"; mode = "0755"; diff --git a/packages/keystone.nix b/packages/keystone.nix index a6f2f93..147d1b2 100644 --- a/packages/keystone.nix +++ b/packages/keystone.nix @@ -26,6 +26,7 @@ let freezegun hacking jsonschema + ldap ldappool lxml oauthlib @@ -35,7 +36,7 @@ let pycodestyle pymysql pysaml2 - ldap + python-memcached requests stestr tempest @@ -82,6 +83,7 @@ python3Packages.buildPythonPackage (rec { pymysql pysaml2 python-keystoneclient + python-memcached sqlalchemy webob ]; From 579dd9a58600a090776bb7d7ccd7bfa2e6276af8 Mon Sep 17 00:00:00 2001 From: Paul Kroeher Date: Thu, 17 Sep 2026 14:36:49 +0200 Subject: [PATCH 62/83] nix: fix keystone urls in all services Signed-off-by: Paul Kroeher On-behalf-of: SAP paul.kroeher@sap.com --- modules/compute/nova.nix | 8 ++++---- modules/controller/cinder.nix | 4 ++-- modules/controller/designate.nix | 4 ++-- modules/controller/glance.nix | 6 +++--- modules/controller/neutron.nix | 6 +++--- modules/controller/nova.nix | 8 ++++---- modules/controller/placement.nix | 4 ++-- modules/storage/cinder-storage-node.nix | 8 ++++---- 8 files changed, 24 insertions(+), 24 deletions(-) diff --git a/modules/compute/nova.nix b/modules/compute/nova.nix index 6c1b048..1c0b75f 100644 --- a/modules/compute/nova.nix +++ b/modules/compute/nova.nix @@ -41,8 +41,8 @@ let api_servers = http://${config.openstack.controllerHostname}:9292 [keystone_authtoken] - www_authenticate_uri = http://${config.openstack.controllerHostname}:5000/ - auth_url = http://${config.openstack.controllerHostname}:5000/ + www_authenticate_uri = http://${config.openstack.controllerHostname}:5000/v3 + auth_url = http://${config.openstack.controllerHostname}:5000/v3 memcached_servers = ${config.openstack.controllerHostname}:11211 auth_type = password project_domain_name = Default @@ -55,7 +55,7 @@ let virt_type = kvm [neutron] - auth_url = http://${config.openstack.controllerHostname}:5000 + auth_url = http://${config.openstack.controllerHostname}:5000/v3 auth_type = password project_domain_name = Default user_domain_name = Default @@ -82,7 +82,7 @@ let [service_user] send_service_user_token = true - auth_url = http://${config.openstack.controllerHostname}:5000/ + auth_url = http://${config.openstack.controllerHostname}:5000/v3 auth_strategy = keystone auth_type = password project_domain_name = Default diff --git a/modules/controller/cinder.nix b/modules/controller/cinder.nix index 4029050..391342f 100644 --- a/modules/controller/cinder.nix +++ b/modules/controller/cinder.nix @@ -22,8 +22,8 @@ let connection = mysql+pymysql://cinder:cinder@${config.openstack.controllerHostname}/cinder [keystone_authtoken] - www_authenticate_uri = http://${config.openstack.controllerHostname}:5000 - auth_url = http://${config.openstack.controllerHostname}:5000 + www_authenticate_uri = http://${config.openstack.controllerHostname}:5000/v3 + auth_url = http://${config.openstack.controllerHostname}:5000/v3 memcached_servers = ${config.openstack.controllerHostname}:11211 auth_type = password project_domain_name = default diff --git a/modules/controller/designate.nix b/modules/controller/designate.nix index 21a4195..9638a9b 100644 --- a/modules/controller/designate.nix +++ b/modules/controller/designate.nix @@ -36,8 +36,8 @@ let threads = 20 [keystone_authtoken] - www_authenticate_uri = http://${config.openstack.controllerHostname}:5000 - auth_url = http://${config.openstack.controllerHostname}:5000 + www_authenticate_uri = http://${config.openstack.controllerHostname}:5000/v3 + auth_url = http://${config.openstack.controllerHostname}:5000/v3 memcached_servers = ${config.openstack.controllerHostname}:11211 auth_type = password project_domain_name = Default diff --git a/modules/controller/glance.nix b/modules/controller/glance.nix index 8a9755e..3453806 100644 --- a/modules/controller/glance.nix +++ b/modules/controller/glance.nix @@ -20,8 +20,8 @@ let connection = mysql+pymysql://glance:glance@${config.openstack.controllerHostname}/glance [keystone_authtoken] - www_authenticate_uri = http://${config.openstack.controllerHostname}:5000 - auth_url = http://${config.openstack.controllerHostname}:5000 + www_authenticate_uri = http://${config.openstack.controllerHostname}:5000/v3 + auth_url = http://${config.openstack.controllerHostname}:5000/v3 memcached_servers = ${config.openstack.controllerHostname}:11211 auth_type = password project_domain_name = Default @@ -40,7 +40,7 @@ let filesystem_store_datadir = /var/lib/glance/images/ [oslo_limit] - auth_url = http://${config.openstack.controllerHostname}:5000 + auth_url = http://${config.openstack.controllerHostname}:5000/v3 auth_type = password user_domain_id = default username = glance diff --git a/modules/controller/neutron.nix b/modules/controller/neutron.nix index 07cb810..4a1d9a6 100644 --- a/modules/controller/neutron.nix +++ b/modules/controller/neutron.nix @@ -40,8 +40,8 @@ let metadata_proxy_shared_secret = neutron_metadata_secret [keystone_authtoken] - www_authenticate_uri = http://${config.openstack.controllerHostname}:5000 - auth_url = http://${config.openstack.controllerHostname}:5000 + www_authenticate_uri = http://${config.openstack.controllerHostname}:5000/v3 + auth_url = http://${config.openstack.controllerHostname}:5000/v3 memcached_servers = ${config.openstack.controllerHostname}:11211 auth_type = password project_domain_name = Default @@ -57,7 +57,7 @@ let service_token_roles = admin [nova] - auth_url = http://${config.openstack.controllerHostname}:5000 + auth_url = http://${config.openstack.controllerHostname}:5000/v3 auth_type = password project_domain_name = Default user_domain_name = Default diff --git a/modules/controller/nova.nix b/modules/controller/nova.nix index 756d511..d22744f 100644 --- a/modules/controller/nova.nix +++ b/modules/controller/nova.nix @@ -29,8 +29,8 @@ let auth_strategy = keystone [keystone_authtoken] - www_authenticate_uri = http://${config.openstack.controllerHostname}:5000/ - auth_url = http://${config.openstack.controllerHostname}:5000/ + www_authenticate_uri = http://${config.openstack.controllerHostname}:5000/v3 + auth_url = http://${config.openstack.controllerHostname}:5000/v3 memcached_servers = ${config.openstack.controllerHostname}:11211 auth_type = password project_domain_name = Default @@ -41,7 +41,7 @@ let [service_user] send_service_user_token = true - auth_url = http://${config.openstack.controllerHostname}:5000/ + auth_url = http://${config.openstack.controllerHostname}:5000/v3 auth_strategy = keystone auth_type = password project_domain_name = Default @@ -77,7 +77,7 @@ let discover_hosts_in_cells_interval = 300 [neutron] - auth_url = http://${config.openstack.controllerHostname}:5000 + auth_url = http://${config.openstack.controllerHostname}:5000/v3 auth_type = password project_domain_name = Default user_domain_name = Default diff --git a/modules/controller/placement.nix b/modules/controller/placement.nix index 62c499d..0278fba 100644 --- a/modules/controller/placement.nix +++ b/modules/controller/placement.nix @@ -16,8 +16,8 @@ let auth_strategy = keystone [keystone_authtoken] - www_authenticate_uri = http://${config.openstack.controllerHostname}:5000 - auth_url = http://${config.openstack.controllerHostname}:5000 + www_authenticate_uri = http://${config.openstack.controllerHostname}:5000/v3 + auth_url = http://${config.openstack.controllerHostname}:5000/v3 auth_type = password project_domain_name = Default user_domain_name = Default diff --git a/modules/storage/cinder-storage-node.nix b/modules/storage/cinder-storage-node.nix index 40adb8c..0093a04 100644 --- a/modules/storage/cinder-storage-node.nix +++ b/modules/storage/cinder-storage-node.nix @@ -53,8 +53,8 @@ let connection = mysql+pymysql://cinder:cinder@${config.openstack.controllerHostname}/cinder [keystone_authtoken] - www_authenticate_uri = http://${config.openstack.controllerHostname}:5000 - auth_url = http://${config.openstack.controllerHostname}:5000 + www_authenticate_uri = http://${config.openstack.controllerHostname}:5000/v3 + auth_url = http://${config.openstack.controllerHostname}:5000/v3 memcached_servers = ${config.openstack.controllerHostname}:11211 auth_type = password project_domain_name = default @@ -97,8 +97,8 @@ let connection = mysql+pymysql://cinder:cinder@${config.openstack.controllerHostname}/cinder [keystone_authtoken] - www_authenticate_uri = http://${config.openstack.controllerHostname}:5000 - auth_url = http://${config.openstack.controllerHostname}:5000 + www_authenticate_uri = http://${config.openstack.controllerHostname}:5000/v3 + auth_url = http://${config.openstack.controllerHostname}:5000/v3 memcached_servers = ${config.openstack.controllerHostname}:11211 auth_type = password project_domain_name = default From dfc1d08a343aa0e4a362880e77fbf738190833ae Mon Sep 17 00:00:00 2001 From: Paul Kroeher Date: Thu, 17 Sep 2026 16:53:55 +0200 Subject: [PATCH 63/83] nix: set mariadb max_connections to 500 Signed-off-by: Paul Kroeher On-behalf-of: SAP paul.kroeher@sap.com --- modules/controller/generic.nix | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/modules/controller/generic.nix b/modules/controller/generic.nix index 615f8ba..a73156f 100644 --- a/modules/controller/generic.nix +++ b/modules/controller/generic.nix @@ -6,6 +6,11 @@ { services.mysql.enable = true; services.mysql.package = lib.mkDefault pkgs.mariadb; + services.mysql.settings = { + mysqld = { + max_connections = 500; + }; + }; services.rabbitmq = { enable = true; From a288ec067794d5914be57218e9feb840cc43d147 Mon Sep 17 00:00:00 2001 From: Paul Kroeher Date: Fri, 18 Sep 2026 08:56:35 +0200 Subject: [PATCH 64/83] nix: removed knot3 backend Signed-off-by: Paul Kroeher On-behalf-of: SAP paul.kroeher@sap.com --- modules/knot-designate.nix | 105 -------------------------- packages/designate-knot3-backend.py | 112 ---------------------------- packages/designate.nix | 7 -- 3 files changed, 224 deletions(-) delete mode 100644 modules/knot-designate.nix delete mode 100644 packages/designate-knot3-backend.py diff --git a/modules/knot-designate.nix b/modules/knot-designate.nix deleted file mode 100644 index 9f8db58..0000000 --- a/modules/knot-designate.nix +++ /dev/null @@ -1,105 +0,0 @@ -{ - config, - lib, - pkgs, - ... -}: - -let - cfg = config.services.knot-designate; -in -{ - options.services.knot-designate = { - enable = lib.mkEnableOption "an external Knot DNS secondary for OpenStack Designate"; - - mdnsAddress = lib.mkOption { - type = lib.types.str; - example = "10.0.0.39"; - description = "Address of the Designate MiniDNS server used for IXFR/AXFR."; - }; - - mdnsPort = lib.mkOption { - type = lib.types.port; - default = 5354; - description = "Port of the Designate MiniDNS server."; - }; - - sshUser = lib.mkOption { - type = lib.types.str; - default = "designate-knot"; - description = "Account through which Designate runs knotc."; - }; - - sshPort = lib.mkOption { - type = lib.types.port; - default = 22; - description = "SSH port on which Designate manages Knot."; - }; - - sshAuthorizedKeys = lib.mkOption { - type = lib.types.listOf lib.types.str; - default = [ ]; - description = "SSH public keys allowed to manage Knot for Designate."; - }; - }; - - config = lib.mkIf cfg.enable { - services.knot = { - enable = true; - settings = { - server.listen = [ - "0.0.0.0@53" - "::@53" - ]; - remote.designate-mdns.address = "${cfg.mdnsAddress}@${toString cfg.mdnsPort}"; - acl.designate-mdns = { - address = [ cfg.mdnsAddress ]; - action = [ "notify" ]; - }; - template.designate = { - master = "designate-mdns"; - acl = "designate-mdns"; - storage = "/var/lib/knot/zones"; - semantic-checks = true; - }; - }; - }; - - # Designate adds and removes zones dynamically. The configuration database - # preserves those entries while +nopurge refreshes the declarative base - # configuration on restart. - systemd.services.knot = { - preStart = '' - ${pkgs.knot-dns}/bin/knotc \ - --confdb=/var/lib/knot/confdb \ - conf-import /etc/knot/knot.conf +nopurge - ''; - serviceConfig = { - ExecStart = lib.mkForce "${pkgs.knot-dns}/bin/knotd --confdb=/var/lib/knot/confdb --socket=/run/knot/knot.sock"; - UMask = lib.mkForce "0007"; - }; - }; - - users.groups.${cfg.sshUser} = { }; - users.users.${cfg.sshUser} = { - isSystemUser = true; - group = cfg.sshUser; - extraGroups = [ "knot" ]; - home = "/var/lib/${cfg.sshUser}"; - createHome = true; - shell = pkgs.bashInteractive; - openssh.authorizedKeys.keys = cfg.sshAuthorizedKeys; - }; - - services.openssh = { - enable = true; - ports = [ cfg.sshPort ]; - }; - - networking.firewall.allowedTCPPorts = [ - 53 - cfg.sshPort - ]; - networking.firewall.allowedUDPPorts = [ 53 ]; - }; -} diff --git a/packages/designate-knot3-backend.py b/packages/designate-knot3-backend.py deleted file mode 100644 index 1e05aff..0000000 --- a/packages/designate-knot3-backend.py +++ /dev/null @@ -1,112 +0,0 @@ -# Copyright 2026 CobaltCore contributors -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 - -"""Remote Knot DNS 3 backend for Designate. - -Knot is configured as a secondary for Designate MiniDNS. This backend only -manages Knot's dynamic zone configuration over SSH; record data is transferred -directly from MiniDNS to Knot using IXFR/AXFR. -""" - -import subprocess - -from oslo_concurrency import lockutils -from oslo_log import log as logging - -from designate.backend import base -from designate import exceptions -from designate import utils - - -LOG = logging.getLogger(__name__) - - -class Knot3Backend(base.Backend): - __plugin_name__ = 'knot3' - __backend_status__ = 'untested' - - def __init__(self, target): - super().__init__(target) - - self.ssh = self.options.get('ssh_bin_path', 'ssh') - self.ssh_host = self.options.get('ssh_host', self.host) - self.ssh_port = int(self.options.get('ssh_port', 22)) - self.ssh_user = self.options.get('ssh_user', 'designate-knot') - self.ssh_identity = self.options.get('ssh_identity_file') - self.ssh_known_hosts = self.options.get( - 'ssh_known_hosts_file', '/etc/ssh/ssh_known_hosts') - self.knotc = self.options.get('knotc_bin_path', 'knotc') - self.confdb = self.options.get( - 'confdb_path', '/var/lib/knot/confdb') - self.socket = self.options.get( - 'control_socket', '/run/knot/knot.sock') - self.template = self.options.get('template', 'designate') - - def _execute(self, *args): - command = [ - self.ssh, - '-o', 'BatchMode=yes', - '-p', str(self.ssh_port), - ] - if self.ssh_identity: - command.extend(['-i', self.ssh_identity]) - if self.ssh_known_hosts: - command.extend([ - '-o', 'UserKnownHostsFile=%s' % self.ssh_known_hosts]) - command.extend([ - '--', - '%s@%s' % (self.ssh_user, self.ssh_host), - self.knotc, - # '--confdb=%s' % self.confdb, - '--socket=%s' % self.socket, - ]) - command.extend(args) - - try: - return utils.execute(*command, timeout=self.timeout, run_as_root=False) - except (utils.processutils.ProcessExecutionError, - subprocess.TimeoutExpired) as error: - raise exceptions.Backend(error) - - @lockutils.synchronized('designate-knot3', external=True) - def _change_config(self, action, zone_name): - """Change one zone in Knot's persistent configuration database.""" - self._execute('conf-begin') - try: - item = 'zone[%s]' % zone_name.rstrip('.') - self._execute(action, item) - if action == 'conf-set': - self._execute( - 'conf-set', '%s.template' % item, self.template) - self._execute('conf-commit') - except Exception: - try: - self._execute('conf-abort') - except exceptions.Backend: - LOG.exception('Unable to abort the Knot configuration change') - raise - - def create_zone(self, context, zone): - LOG.debug('Creating zone %s in Knot', zone.name) - self._change_config('conf-set', zone.name) - - def update_zone(self, context, zone): - LOG.debug('Refreshing zone %s in Knot', zone.name) - self._execute('zone-refresh', zone.name.rstrip('.')) - - def delete_zone(self, context, zone, zone_params=None): - LOG.debug('Deleting zone %s from Knot', zone.name) - self._change_config('conf-unset', zone.name) - - # Once the configuration entry is gone, remove the secondary's copy. - # A failed purge does not mean that the zone is still being served. - try: - self._execute( - '--force', 'zone-purge', zone.name.rstrip('.'), '+orphan') - except exceptions.Backend: - LOG.warning('Unable to purge data for Knot zone %s', zone.name) diff --git a/packages/designate.nix b/packages/designate.nix index 022409d..5a81872 100644 --- a/packages/designate.nix +++ b/packages/designate.nix @@ -78,15 +78,8 @@ python3Packages.buildPythonPackage (rec { ]; postPatch = '' - cp ${./designate-knot3-backend.py} designate/backend/impl_knot3.py - substituteInPlace etc/designate/rootwrap.d/bind9.filters \ --replace-fail "/usr/sbin/rndc" "rndc" - - substituteInPlace setup.cfg \ - --replace-fail \ - "infoblox = designate.backend.impl_infoblox:InfobloxBackend" \ - $'infoblox = designate.backend.impl_infoblox:InfobloxBackend\n\tknot3 = designate.backend.impl_knot3:Knot3Backend' ''; propagatedBuildInputs = [ From 9675dc1d62174f830fd1624c507fb6c8097468b7 Mon Sep 17 00:00:00 2001 From: Paul Kroeher Date: Fri, 18 Sep 2026 08:57:52 +0200 Subject: [PATCH 65/83] nix: run database cleanup in CI/CD Signed-off-by: Paul Kroeher On-behalf-of: SAP paul.kroeher@sap.com --- modules/controller/openstack-controller.nix | 1 + 1 file changed, 1 insertion(+) diff --git a/modules/controller/openstack-controller.nix b/modules/controller/openstack-controller.nix index 48ad0c7..7fedf32 100644 --- a/modules/controller/openstack-controller.nix +++ b/modules/controller/openstack-controller.nix @@ -342,6 +342,7 @@ in serviceConfig = { RemainAfterExit = true; Type = "oneshot"; + ExecStartPre = "${databaseCleanupScript}"; ExecStart = "${databaseSetupScript}"; }; }; From 25442a1493a506581a04e5e4e7438973cb14f481 Mon Sep 17 00:00:00 2001 From: Paul Kroeher Date: Fri, 18 Sep 2026 09:00:40 +0200 Subject: [PATCH 66/83] nix: bugifxes storage node Signed-off-by: Paul Kroeher On-behalf-of: SAP paul.kroeher@sap.com --- modules/storage/cinder-storage-node.nix | 2 ++ 1 file changed, 2 insertions(+) diff --git a/modules/storage/cinder-storage-node.nix b/modules/storage/cinder-storage-node.nix index 0093a04..26ff819 100644 --- a/modules/storage/cinder-storage-node.nix +++ b/modules/storage/cinder-storage-node.nix @@ -130,6 +130,7 @@ let export PATH=${ lib.makeBinPath [ pkgs.util-linux + pkgs.nfs-utils ] }:$PATH @@ -137,6 +138,7 @@ let if [ -e ${config.openstack.storagePath}/.cinder-volume-setup-done-dont-delete-me ]; then echo "cinder volume setup already done. Check content of this script." + exit 0 fi mkdir ${config.openstack.storagePath} From 1e2fda97cc2bc1b4bd7732af01db6bf2a1b6bc65 Mon Sep 17 00:00:00 2001 From: Paul Kroeher Date: Fri, 18 Sep 2026 09:54:55 +0200 Subject: [PATCH 67/83] nix: add openstack packages into system path Signed-off-by: Paul Kroeher On-behalf-of: SAP paul.kroeher@sap.com --- modules/controller/openstack-controller.nix | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/modules/controller/openstack-controller.nix b/modules/controller/openstack-controller.nix index 7fedf32..ca3879a 100644 --- a/modules/controller/openstack-controller.nix +++ b/modules/controller/openstack-controller.nix @@ -327,6 +327,13 @@ in (setupCommand "openstack-designate-setup" designateStartScript) (setupCommand "openstack-check-controller" checkControllerScript) python-openstackclient + cinder + designate + glance + keystone + neutron + nova + placement ]; systemd.services.database-setup = lib.mkIf (!config.openstack.manual_setup) { From 5c9a65e6e0101cae3313c601a3feedc6fa2889b9 Mon Sep 17 00:00:00 2001 From: Paul Kroeher Date: Wed, 23 Sep 2026 11:43:57 +0200 Subject: [PATCH 68/83] nix: fix neutron-openvswitch-agent systemd unit dependency neutron-openvswitch-agent should start after network setup is completed by systemd-netword.service This fixes a runtime race condition during the reboot. Signed-off-by: Paul Kroeher On-behalf-of: SAP paul.kroeher@sap.com --- modules/compute/neutron.nix | 1 + 1 file changed, 1 insertion(+) diff --git a/modules/compute/neutron.nix b/modules/compute/neutron.nix index 323a851..0f6c122 100644 --- a/modules/compute/neutron.nix +++ b/modules/compute/neutron.nix @@ -167,6 +167,7 @@ in after = [ "network.target" "ovsdb.service" + "systemd-networkd.service" ]; wantedBy = [ "multi-user.target" ]; path = with pkgs; [ From 49db3090fd5696c311ecc47bcfa6e488f65e312f Mon Sep 17 00:00:00 2001 From: Stefan Kober Date: Wed, 14 Oct 2026 11:16:06 +0200 Subject: [PATCH 69/83] nix: scope OpenStack options to their owning modules Declare computeIP in the compute module and share controllerIP through the global options module. Keep the controller test address in the testing module and require deployments to supply their controller address. Remove the unused live_migration_inbound_addr option. Signed-off-by: Stefan Kober On-behalf-of: SAP stefan.kober@sap.com --- modules/compute/compute.nix | 11 +++++++++- modules/generic/controller-host-entry.nix | 14 ++----------- modules/generic/global-options.nix | 25 ++++++++--------------- modules/testing/default.nix | 2 +- 4 files changed, 21 insertions(+), 31 deletions(-) diff --git a/modules/compute/compute.nix b/modules/compute/compute.nix index 1786cbb..02a9868 100644 --- a/modules/compute/compute.nix +++ b/modules/compute/compute.nix @@ -1,5 +1,5 @@ { neutron, nova }: -{ ... }: +{ lib, ... }: { imports = [ ../generic/global-options.nix @@ -7,4 +7,13 @@ (import ./neutron.nix { inherit neutron; }) (import ./nova.nix { inherit nova; }) ]; + + options.openstack.computeIP = lib.mkOption { + type = lib.types.str; + description = '' + IP address of the compute node, used as Nova's my_ip and as the address + reached by the serial-console and VNC proxies. This address must already + be configured on the host; this option does not configure an interface. + ''; + }; } diff --git a/modules/generic/controller-host-entry.nix b/modules/generic/controller-host-entry.nix index 5321766..4205262 100644 --- a/modules/generic/controller-host-entry.nix +++ b/modules/generic/controller-host-entry.nix @@ -1,16 +1,6 @@ -{ lib, config, ... }: -with lib; +{ config, ... }: { - options.openstack = { - controllerIP = mkOption { - type = types.str; - default = "10.0.0.11"; - description = '' - IP address of the controller. Will be used to make a /etc/hosts entry - to make the controller available via "controller". - ''; - }; - }; + imports = [ ./global-options.nix ]; config = { networking.extraHosts = '' diff --git a/modules/generic/global-options.nix b/modules/generic/global-options.nix index f5aa3db..d3aedb3 100644 --- a/modules/generic/global-options.nix +++ b/modules/generic/global-options.nix @@ -9,6 +9,14 @@ with lib; ''; }; + options.openstack.controllerIP = lib.mkOption { + type = types.str; + description = '' + IP address of the controller, used by controller services and for the + controller and controller.local entries in /etc/hosts. + ''; + }; + options.openstack.manual_setup = lib.mkOption { type = lib.types.bool; default = false; @@ -21,21 +29,4 @@ with lib; physical deployments. ''; }; - - options.openstack.computeIP = lib.mkOption { - type = types.str; - description = '' - IP address of the compute node, used as Nova's my_ip and as the address - reached by the serial-console and VNC proxies. This address must already - be configured on the host; this option does not configure an interface. - ''; - }; - - options.openstack.live_migration_inbound_addr = lib.mkOption { - default = "10.100.100.1"; - type = types.str; - description = '' - My own ip address of the migration network. Usually our internal 100G link. - ''; - }; } diff --git a/modules/testing/default.nix b/modules/testing/default.nix index 25820fa..9400515 100644 --- a/modules/testing/default.nix +++ b/modules/testing/default.nix @@ -24,7 +24,7 @@ let services.getty.autologinUser = "root"; - openstack.controllerIP = "10.0.0.11"; + openstack.controllerIP = lib.mkDefault "10.0.0.11"; networking = { useDHCP = false; From c2cacde74396227572d2c32eb4a50353bb49acc4 Mon Sep 17 00:00:00 2001 From: Stefan Kober Date: Wed, 14 Oct 2026 13:12:23 +0200 Subject: [PATCH 70/83] nix: make manual Cinder setup device configurable Signed-off-by: Stefan Kober On-behalf-of: SAP stefan.kober@sap.com --- modules/storage/cinder-storage-node.nix | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/modules/storage/cinder-storage-node.nix b/modules/storage/cinder-storage-node.nix index 26ff819..282b48c 100644 --- a/modules/storage/cinder-storage-node.nix +++ b/modules/storage/cinder-storage-node.nix @@ -126,6 +126,8 @@ let ${config.openstack.storagePath} 10.0.0.0/24(rw,no_root_squash,insecure) ''; + # Run explicitly via openstack-cinder-volume-setup during manual deployment + # to format and mount the NFS storage disk and restart Cinder. cinderVolumeSetupScript = pkgs.writeShellScript "cinder-volume-setup.sh" '' export PATH=${ lib.makeBinPath [ @@ -142,8 +144,8 @@ let fi mkdir ${config.openstack.storagePath} - mkfs.ext4 -F -m 0 -L cinder /dev/vdb - mount /dev/vdb ${config.openstack.storagePath} + mkfs.ext4 -F -m 0 -L cinder ${lib.escapeShellArg cfg.setupDevice} + mount ${lib.escapeShellArg cfg.setupDevice} ${config.openstack.storagePath} exportfs -rv rm -rf ${config.openstack.storagePath}/lost+found chown cinder ${config.openstack.storagePath} @@ -199,6 +201,17 @@ in enable = mkEnableOption "Enable OpenStack Cinder storage node." // { default = true; }; + setupDevice = mkOption { + type = types.str; + default = "/dev/vdb"; + example = "/dev/disk/by-id/your-storage-disk"; + description = '' + Block device formatted and mounted by openstack-cinder-volume-setup + during manual deployment of the NFS backend. Running that command + creates an ext4 filesystem on this device, destroying existing data. + This option does not affect automatic test storage initialization. + ''; + }; config = mkOption { default = if (cfg.backend == "lvm") then cinderConfLvm else cinderConfNfs; description = '' From fe688b08a948f6dd742c8ab2954334aa1a303341 Mon Sep 17 00:00:00 2001 From: Stefan Kober Date: Wed, 14 Oct 2026 15:07:33 +0200 Subject: [PATCH 71/83] test: properly wait for volume creation The basic test was slightly flaky because we have not waited for the volume creation or the volume attachment to be successfull properly. Signed-off-by: Stefan Kober On-behalf-of: SAP stefan.kober@sap.com --- tests/openstack-default-setup.nix | 35 ++++++++++++++----------------- 1 file changed, 16 insertions(+), 19 deletions(-) diff --git a/tests/openstack-default-setup.nix b/tests/openstack-default-setup.nix index c12a288..4edff97 100644 --- a/tests/openstack-default-setup.nix +++ b/tests/openstack-default-setup.nix @@ -137,25 +137,22 @@ pkgs.testers.nixosTest { # namespace dedicated for the VM to ping it. assert retry_until_succeed(controllerVM, f"ip netns exec {net_ns} ping -c 1 {vm_ip}", 120) - # create volume with 4GB - controllerVM.execute("openstack volume create --size 4 test_vol") - # attach volume to VM - controllerVM.execute("openstack server add volume test_vm test_vol") - - _status, output = controllerVM.execute("openstack volume show test_vol") - print(f"openstack volume show test_vol: {output}") - time.sleep(3) - _status, output = controllerVM.execute("openstack volume show test_vol") - print(f"openstack volume show test_vol: {output}") - time.sleep(3) - _status, output = controllerVM.execute("openstack volume show test_vol") - print(f"openstack volume show test_vol: {output}") - time.sleep(3) - _status, output = controllerVM.execute("openstack volume show test_vol") - print(f"openstack volume show test_vol: {output}") - - # wait until volume is attached - assert retry_until_succeed(controllerVM, "openstack volume show test_vol -f value -c status | grep 'in-use'", 120) + # Volume creation is asynchronous; wait until it is ready to attach. + controllerVM.succeed("openstack volume create --size 4 test_vol") + assert retry_until_succeed( + controllerVM, + "openstack volume show test_vol -f value -c status | grep -Fx available", + 120, + ), "Volume test_vol did not become available" + + controllerVM.succeed("openstack server add volume test_vm test_vol") + + # Attachment is asynchronous too. + assert retry_until_succeed( + controllerVM, + "openstack volume show test_vol -f value -c status | grep -Fx in-use", + 120, + ), "Volume test_vol did not become attached" # add ssh host key to known_hosts retry_until_succeed(controllerVM, f"ip netns exec {net_ns} ssh-keyscan {vm_ip} > ~/.ssh/known_hosts", 60) From bf54c5656a7d97c8811ab0783da19f81464a3c89 Mon Sep 17 00:00:00 2001 From: Stefan Kober Date: Wed, 14 Oct 2026 15:21:22 +0200 Subject: [PATCH 72/83] tests: work around ssh config file permission Signed-off-by: Stefan Kober On-behalf-of: SAP stefan.kober@sap.com --- modules/testing/default.nix | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/modules/testing/default.nix b/modules/testing/default.nix index 9400515..b9ef69d 100644 --- a/modules/testing/default.nix +++ b/modules/testing/default.nix @@ -26,6 +26,11 @@ let openstack.controllerIP = lib.mkDefault "10.0.0.11"; + # The shared host Nix store can be owned by a non-root UID. Root's SSH + # client then rejects systemd's included proxy config on ownership + # checks. These tests only need TCP SSH, not systemd's socket proxy. + programs.ssh.systemd-ssh-proxy.enable = false; + networking = { useDHCP = false; networkmanager.enable = false; From e4f2956764878fc72f18bab6a60fcec6afde3c98 Mon Sep 17 00:00:00 2001 From: Stefan Kober Date: Wed, 14 Oct 2026 16:01:46 +0200 Subject: [PATCH 73/83] controller: user defined controller name resolves via hosts Signed-off-by: Stefan Kober On-behalf-of: SAP stefan.kober@sap.com --- modules/generic/controller-host-entry.nix | 12 ++++++++++-- modules/generic/global-options.nix | 3 ++- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/modules/generic/controller-host-entry.nix b/modules/generic/controller-host-entry.nix index 4205262..0a996f1 100644 --- a/modules/generic/controller-host-entry.nix +++ b/modules/generic/controller-host-entry.nix @@ -1,10 +1,18 @@ -{ config, ... }: +{ config, lib, ... }: { imports = [ ./global-options.nix ]; config = { networking.extraHosts = '' - ${config.openstack.controllerIP} controller controller.local + ${config.openstack.controllerIP} ${ + lib.concatStringsSep " " ( + lib.unique [ + config.openstack.controllerHostname + "controller" + "controller.local" + ] + ) + } ''; }; } diff --git a/modules/generic/global-options.nix b/modules/generic/global-options.nix index d3aedb3..3c2f044 100644 --- a/modules/generic/global-options.nix +++ b/modules/generic/global-options.nix @@ -13,7 +13,8 @@ with lib; type = types.str; description = '' IP address of the controller, used by controller services and for the - controller and controller.local entries in /etc/hosts. + configured controller hostname in /etc/hosts. The controller and + controller.local aliases also resolve to this address. ''; }; From e50c326022c7e0fda2887d6167c65583c5d73174 Mon Sep 17 00:00:00 2001 From: Stefan Kober Date: Wed, 14 Oct 2026 16:04:18 +0200 Subject: [PATCH 74/83] cinder: use configured storage path Signed-off-by: Stefan Kober On-behalf-of: SAP stefan.kober@sap.com --- modules/storage/cinder-storage-node.nix | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/storage/cinder-storage-node.nix b/modules/storage/cinder-storage-node.nix index 282b48c..2703c8f 100644 --- a/modules/storage/cinder-storage-node.nix +++ b/modules/storage/cinder-storage-node.nix @@ -405,9 +405,9 @@ in set -euxo pipefail # create a filesystem and mount and export it - mkdir /exports + mkdir ${lib.escapeShellArg config.openstack.storagePath} mkfs.ext4 -F -m 0 /dev/vdb - mount /dev/vdb /exports + mount /dev/vdb ${lib.escapeShellArg config.openstack.storagePath} exportfs -rv ''; }; From 94249b2d3c07654ab2fbe6f7783b1ce0ac5c1943 Mon Sep 17 00:00:00 2001 From: Stefan Kober Date: Fri, 16 Oct 2026 10:10:18 +0200 Subject: [PATCH 75/83] cinder: fix cinder-schduler invocation Signed-off-by: Stefan Kober On-behalf-of: SAP stefan.kober@sap.com --- modules/controller/cinder.nix | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/controller/cinder.nix b/modules/controller/cinder.nix index 391342f..2c5eb71 100644 --- a/modules/controller/cinder.nix +++ b/modules/controller/cinder.nix @@ -181,7 +181,7 @@ in User = "cinder"; Group = "cinder"; ExecStart = pkgs.writeShellScript "cinder-scheduler.sh" '' - .cinder-scheduler-wrapped + .cinder-scheduler-wrapped --config-file ${cfg.config} ''; }; enable = cfg.enable; From cde7d72892074b0cee5466bfe9ce37e621e58dc8 Mon Sep 17 00:00:00 2001 From: Stefan Kober Date: Fri, 16 Oct 2026 10:58:20 +0200 Subject: [PATCH 76/83] neutron: fix systemd dependency We now wait for the provider interface to be ready before starting certain newton services. Previously, the ovs newton service errored when it was started too early because the explicit dependency was missing. Signed-off-by: Stefan Kober On-behalf-of: SAP stefan.kober@sap.com --- modules/compute/neutron.nix | 9 +++++++++ modules/controller/neutron.nix | 9 +++++++++ 2 files changed, 18 insertions(+) diff --git a/modules/compute/neutron.nix b/modules/compute/neutron.nix index 0f6c122..0e93af0 100644 --- a/modules/compute/neutron.nix +++ b/modules/compute/neutron.nix @@ -3,6 +3,7 @@ config, lib, pkgs, + utils, ... }: @@ -10,6 +11,7 @@ with lib; let cfg = config.neutron; + providerDevice = "${utils.escapeSystemdPath "/sys/subsystem/net/devices/${cfg.providerInterface}"}.device"; neutron_env = pkgs.python3.buildEnv.override { extraLibs = [ neutron ]; }; @@ -167,8 +169,15 @@ in after = [ "network.target" "ovsdb.service" + "ovs-vswitchd.service" + providerDevice "systemd-networkd.service" ]; + # Bridge setup needs both the switching daemon and the provider NIC. + requires = [ + "ovs-vswitchd.service" + providerDevice + ]; wantedBy = [ "multi-user.target" ]; path = with pkgs; [ sudo diff --git a/modules/controller/neutron.nix b/modules/controller/neutron.nix index 4a1d9a6..b3c9a5b 100644 --- a/modules/controller/neutron.nix +++ b/modules/controller/neutron.nix @@ -3,6 +3,7 @@ config, lib, pkgs, + utils, ... }: @@ -10,6 +11,7 @@ with lib; let cfg = config.neutron; + providerDevice = "${utils.escapeSystemdPath "/sys/subsystem/net/devices/${cfg.providerInterface}"}.device"; # For live migration to work, neutron-server must allow certain actions. policyConf = pkgs.writeText "policy.yaml" '' @@ -270,8 +272,15 @@ in after = [ "network.target" "ovsdb.service" + "ovs-vswitchd.service" + providerDevice "neutron.service" ]; + # Bridge setup needs both the switching daemon and the provider NIC. + requires = [ + "ovs-vswitchd.service" + providerDevice + ]; wantedBy = [ "multi-user.target" ]; path = with pkgs; [ sudo From fe8a5cfc02cb667a914021b40aad7d611ec6e4ad Mon Sep 17 00:00:00 2001 From: Stefan Kober Date: Mon, 19 Oct 2026 09:50:56 +0200 Subject: [PATCH 77/83] nova: optional inbound_addr setting for migration We allow optionally configuring the inbound_addr for live migration in the nova config via a nixos option. Setting this, the user is able to specify a specific interface to transfer the data when live migrating. This can significantly speed up migration. Signed-off-by: Stefan Kober On-behalf-of: SAP stefan.kober@sap.com --- modules/compute/nova.nix | 3 +++ modules/generic/global-options.nix | 10 ++++++++++ 2 files changed, 13 insertions(+) diff --git a/modules/compute/nova.nix b/modules/compute/nova.nix index 1c0b75f..d43fb43 100644 --- a/modules/compute/nova.nix +++ b/modules/compute/nova.nix @@ -53,6 +53,9 @@ let [libvirt] virt_type = kvm + ${optionalString (config.openstack.live_migration_inbound_addr != null) '' + live_migration_inbound_addr = ${config.openstack.live_migration_inbound_addr} + ''} [neutron] auth_url = http://${config.openstack.controllerHostname}:5000/v3 diff --git a/modules/generic/global-options.nix b/modules/generic/global-options.nix index 3c2f044..ed09dc0 100644 --- a/modules/generic/global-options.nix +++ b/modules/generic/global-options.nix @@ -30,4 +30,14 @@ with lib; physical deployments. ''; }; + + options.openstack.live_migration_inbound_addr = lib.mkOption { + default = null; + type = types.nullOr types.str; + description = '' + Address of this compute node on the live migration network. When set, + configures Nova's libvirt live_migration_inbound_addr. When null, the + setting is omitted and Nova uses its default. + ''; + }; } From 401d8080ca9e2af93c9e660cc12820974f534b23 Mon Sep 17 00:00:00 2001 From: Stefan Kober Date: Wed, 21 Oct 2026 10:42:54 +0200 Subject: [PATCH 78/83] cinder: unify cinder-api We use the uwsgi vassal mechanism for the cinder-api for both, the nixos testing and the manual setup. Signed-off-by: Stefan Kober On-behalf-of: SAP stefan.kober@sap.com --- modules/controller/cinder.nix | 77 ++++++++++----------- modules/controller/generic.nix | 5 ++ modules/controller/openstack-controller.nix | 2 + 3 files changed, 44 insertions(+), 40 deletions(-) diff --git a/modules/controller/cinder.nix b/modules/controller/cinder.nix index 2c5eb71..5bd3c4c 100644 --- a/modules/controller/cinder.nix +++ b/modules/controller/cinder.nix @@ -122,48 +122,45 @@ in }; }; - # create systemd service only if running in non production mode (CI/CD setup) - systemd.services.cinder-api = lib.mkIf (!config.openstack.manual_setup) { - description = "OpenStack Cinder API Daemon"; - after = [ - "cinder.service" - "rabbitmq.service" - "mysql.service" - "network.target" - ]; - path = [ cinder ]; - wantedBy = [ "multi-user.target" ]; - serviceConfig = { - User = "cinder"; - Group = "cinder"; - ExecStart = pkgs.writeShellScript "cinder-api.sh" '' - .cinder-wsgi-wrapped --port 8776 - ''; - }; - enable = cfg.enable; - }; + services.uwsgi = { + instance.vassals = optionalAttrs cfg.enable { + cinder-api = { + type = "normal"; + http-socket = "0.0.0.0:8776"; + wsgi-file = "${cinder}/bin/.cinder-wsgi-wrapped"; + pyargv = "--config-file ${cfg.config}"; + env = cfg.envCinderApi; - # create uwsgi vassal configuration only in production setup - services.uwsgi = lib.mkIf (config.openstack.manual_setup) { - instance.vassals.cinder-api = mkIf cfg.enable { - type = "normal"; - http-socket = "0.0.0.0:8776"; - wsgi-file = "${cinder}/bin/.cinder-wsgi-wrapped"; - pyargv = "--config-file ${cfg.config}"; - env = cfg.envCinderApi; + master = true; + processes = 4; + enable-threads = true; + thunder-lock = true; + lazy-apps = true; + die-on-term = true; + vacuum = true; + need-app = true; + buffer-size = 65535; - master = true; - processes = 4; - enable-threads = true; - thunder-lock = true; - lazy-apps = true; - die-on-term = true; - vacuum = true; - need-app = true; - buffer-size = 65535; - - immediate-uid = "cinder"; - immediate-gid = "cinder"; + immediate-uid = "cinder"; + immediate-gid = "cinder"; + } + // optionalAttrs (!config.openstack.manual_setup) { + # Keystone must be available in the same emperor before automatic + # Cinder setup can finish, so only this vassal waits for the schema. + # As there is no uwsgi mechanism to serialize the vassals, we use a + # marker file created in the systemd service setup of cinder to wait + # for first. + exec-asap = pkgs.writeShellScript "cinder-api-wait-for-setup" '' + for ((attempt = 0; attempt < 300; attempt++)); do + if test -f /run/cinder-setup/ready; then + exit 0 + fi + ${pkgs.coreutils}/bin/sleep 1 + done + echo "Timed out waiting for Cinder database setup" >&2 + exit 1 + ''; + }; }; }; diff --git a/modules/controller/generic.nix b/modules/controller/generic.nix index a73156f..e6787c4 100644 --- a/modules/controller/generic.nix +++ b/modules/controller/generic.nix @@ -49,4 +49,9 @@ ]; instance.type = "emperor"; }; + + # Match ExecStop's SIGINT when systemd stops the remaining processes too. + # SIGQUIT can dump core for vassals still starting and their helper commands + # during the uWSGI restart performed by Keystone setup. + systemd.services.uwsgi.serviceConfig.KillSignal = lib.mkForce "SIGINT"; } diff --git a/modules/controller/openstack-controller.nix b/modules/controller/openstack-controller.nix index ca3879a..7c1d7e0 100644 --- a/modules/controller/openstack-controller.nix +++ b/modules/controller/openstack-controller.nix @@ -409,6 +409,8 @@ in User = "root"; Group = "root"; ExecStart = "+${cinderStartScript}"; + RuntimeDirectory = "cinder-setup"; + ExecStartPost = "${pkgs.coreutils}/bin/touch /run/cinder-setup/ready"; }; }; From d394417cefbd30348f184539b7737e9d87ba3fcd Mon Sep 17 00:00:00 2001 From: Stefan Kober Date: Wed, 21 Oct 2026 11:45:22 +0200 Subject: [PATCH 79/83] nix: exec placement via uwsgi Insteaf of using uwsgi in the manual setup and a systemd service in the test setup, we unify the mechanisms and use uwsgi throughout the environments. Signed-off-by: Stefan Kober On-behalf-of: SAP stefan.kober@sap.com --- modules/controller/nova.nix | 6 +- modules/controller/openstack-controller.nix | 2 + modules/controller/placement.nix | 79 +++++++++------------ tests/openstack-default-setup.nix | 1 - tests/openstack-live-migration.nix | 1 - 5 files changed, 39 insertions(+), 50 deletions(-) diff --git a/modules/controller/nova.nix b/modules/controller/nova.nix index d22744f..81825a1 100644 --- a/modules/controller/nova.nix +++ b/modules/controller/nova.nix @@ -193,7 +193,7 @@ in "neutron-server.service" "nova.service" "glance-api.service" - "placement-api.service" + "uwsgi.service" "postgresql.service" "mysql.service" "keystone.service" @@ -225,7 +225,7 @@ in after = [ "neutron-server.service" "glance-api.service" - "placement-api.service" + "uwsgi.service" "nova.service" "postgresql.service" "mysql.service" @@ -280,7 +280,7 @@ in after = [ "neutron-server.service" "glance-api.service" - "placement-api.service" + "uwsgi.service" "nova.service" "postgresql.service" "mysql.service" diff --git a/modules/controller/openstack-controller.nix b/modules/controller/openstack-controller.nix index 7c1d7e0..fca59ee 100644 --- a/modules/controller/openstack-controller.nix +++ b/modules/controller/openstack-controller.nix @@ -433,6 +433,8 @@ in User = "root"; Group = "root"; ExecStart = "+${placementStartScript}"; + RuntimeDirectory = "placement-setup"; + ExecStartPost = "${pkgs.coreutils}/bin/touch /run/placement-setup/ready"; }; }; diff --git a/modules/controller/placement.nix b/modules/controller/placement.nix index 0278fba..3a426ad 100644 --- a/modules/controller/placement.nix +++ b/modules/controller/placement.nix @@ -71,53 +71,42 @@ in }; }; - # create systemd service only if running in non production mode (CI/CD setup) - systemd.services.placement-api = lib.mkIf (!config.openstack.manual_setup) { - description = "OpenStack Placement API Daemon"; - after = [ - "placement.service" - "rabbitmq.service" - "mysql.service" - "network.target" - ]; - path = [ placement ]; - wantedBy = [ "multi-user.target" ]; - serviceConfig = { - User = "placement"; - Group = "placement"; - ExecStart = pkgs.writeShellScript "placement-api.sh" '' - # Using the placement-api binary directly warns about that should only - # be done in development environments. The "correct" way is to use - # uwsgi, which is kinda tricky in nixos because we would have to start - # the keystone uwsgi service at first, and placement later. That - # cannot be expressed in the nixos config AFAIK. - placement-api --port 8778 - ''; - }; - enable = cfg.enable; - }; + services.uwsgi = { + instance.vassals = optionalAttrs cfg.enable { + placement-api = { + type = "normal"; + http-socket = "0.0.0.0:8778"; + wsgi-file = "${placement}/bin/.placement-api-wrapped"; + pyargv = "--config-file ${cfg.config}"; - # create uwsgi vassal configuration only in production setup - services.uwsgi = lib.mkIf (config.openstack.manual_setup) { - instance.vassals.placement-api = mkIf cfg.enable { - type = "normal"; - http-socket = "0.0.0.0:8778"; - wsgi-file = "${placement}/bin/.placement-api-wrapped"; - pyargv = "--config-file ${cfg.config}"; + master = true; + processes = 4; + enable-threads = true; + thunder-lock = true; + lazy-apps = true; + die-on-term = true; + vacuum = true; + need-app = true; + buffer-size = 65535; - master = true; - processes = 4; - enable-threads = true; - thunder-lock = true; - lazy-apps = true; - die-on-term = true; - vacuum = true; - need-app = true; - buffer-size = 65535; - - immediate-uid = "placement"; - immediate-gid = "placement"; - env = cfg.env; + immediate-uid = "placement"; + immediate-gid = "placement"; + env = cfg.env; + } + // optionalAttrs (!config.openstack.manual_setup) { + # Keystone must start in the same emperor before Placement setup can + # finish. Wait only in this vassal, using a hook that works as non-root. + exec-asap = pkgs.writeShellScript "placement-api-wait-for-setup" '' + for ((attempt = 0; attempt < 300; attempt++)); do + if test -f /run/placement-setup/ready; then + exit 0 + fi + ${pkgs.coreutils}/bin/sleep 1 + done + echo "Timed out waiting for Placement database setup" >&2 + exit 1 + ''; + }; }; }; }; diff --git a/tests/openstack-default-setup.nix b/tests/openstack-default-setup.nix index 4edff97..5b3490d 100644 --- a/tests/openstack-default-setup.nix +++ b/tests/openstack-default-setup.nix @@ -106,7 +106,6 @@ pkgs.testers.nixosTest { start_all() controllerVM.wait_for_unit("glance-api.service") - controllerVM.wait_for_unit("placement-api.service") controllerVM.wait_for_unit("neutron-server.service") controllerVM.wait_for_unit("nova-scheduler.service") controllerVM.wait_for_unit("nova-conductor.service") diff --git a/tests/openstack-live-migration.nix b/tests/openstack-live-migration.nix index 0fed9dc..9c7f58b 100644 --- a/tests/openstack-live-migration.nix +++ b/tests/openstack-live-migration.nix @@ -250,7 +250,6 @@ pkgs.testers.nixosTest { start_all() controllerVM.wait_for_unit("glance-api.service") - controllerVM.wait_for_unit("placement-api.service") controllerVM.wait_for_unit("neutron-server.service") controllerVM.wait_for_unit("nova-scheduler.service") controllerVM.wait_for_unit("nova-conductor.service") From a0fa378686c6181a29c85878f79469fce901f7c1 Mon Sep 17 00:00:00 2001 From: Stefan Kober Date: Wed, 21 Oct 2026 12:26:50 +0200 Subject: [PATCH 80/83] controller: enumerate setup scripts The setup scripts are numbered by the order they should be invoked for easier usage. Signed-off-by: Stefan Kober On-behalf-of: SAP stefan.kober@sap.com --- modules/controller/openstack-controller.nix | 22 ++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/modules/controller/openstack-controller.nix b/modules/controller/openstack-controller.nix index fca59ee..8848a85 100644 --- a/modules/controller/openstack-controller.nix +++ b/modules/controller/openstack-controller.nix @@ -315,17 +315,17 @@ in config = { environment.systemPackages = [ - (setupCommand "openstack-database-cleanup" databaseCleanupScript) - (setupCommand "openstack-database-setup" databaseSetupScript) - (setupCommand "openstack-keystone-pre-setup" keystonePreStartScript) - (setupCommand "openstack-keystone-setup" keystoneStartScript) - (setupCommand "openstack-glance-setup" glanceStartScript) - (setupCommand "openstack-cinder-setup" cinderStartScript) - (setupCommand "openstack-placement-setup" placementStartScript) - (setupCommand "openstack-nova-setup" novaStartScript) - (setupCommand "openstack-neutron-setup" neutronStartScript) - (setupCommand "openstack-designate-setup" designateStartScript) - (setupCommand "openstack-check-controller" checkControllerScript) + (setupCommand "openstack-database-cleanup-000" databaseCleanupScript) + (setupCommand "openstack-database-setup-000" databaseSetupScript) + (setupCommand "openstack-keystone-pre-setup-001" keystonePreStartScript) + (setupCommand "openstack-keystone-setup-002" keystoneStartScript) + (setupCommand "openstack-glance-setup-003" glanceStartScript) + (setupCommand "openstack-cinder-setup-004" cinderStartScript) + (setupCommand "openstack-placement-setup-005" placementStartScript) + (setupCommand "openstack-neutron-setup-006" neutronStartScript) + (setupCommand "openstack-nova-setup-007" novaStartScript) + (setupCommand "openstack-designate-setup-008" designateStartScript) + (setupCommand "openstack-check-controller-100" checkControllerScript) python-openstackclient cinder designate From d9e4e4c1a87c9bafdb234f02d33adc8eb8d3684c Mon Sep 17 00:00:00 2001 From: Stefan Kober Date: Wed, 21 Oct 2026 14:24:00 +0200 Subject: [PATCH 81/83] controller: use adminEnv per default Signed-off-by: Stefan Kober On-behalf-of: SAP stefan.kober@sap.com --- modules/controller/openstack-controller.nix | 2 ++ 1 file changed, 2 insertions(+) diff --git a/modules/controller/openstack-controller.nix b/modules/controller/openstack-controller.nix index 8848a85..0ef2cbd 100644 --- a/modules/controller/openstack-controller.nix +++ b/modules/controller/openstack-controller.nix @@ -314,6 +314,8 @@ in config = { + environment.variables = adminEnv; + environment.systemPackages = [ (setupCommand "openstack-database-cleanup-000" databaseCleanupScript) (setupCommand "openstack-database-setup-000" databaseSetupScript) From 43c48a37845f9e4621286cbc6741b193eee273ce Mon Sep 17 00:00:00 2001 From: Stefan Kober Date: Fri, 23 Oct 2026 11:32:09 +0200 Subject: [PATCH 82/83] package: fix oslo-middleware test failure There is a test failure that depends on the order of test execution. Therefore, it does not occur everytime and is dependend on the environment. We fix it by adding a small custom patch, as also later versions of oslo-middleware seem to be affected. Signed-off-by: Stefan Kober On-behalf-of: SAP stefan.kober@sap.com --- packages/oslo-middleware.nix | 5 +++++ ...slo-middleware-healthcheck-config-fixture.patch | 14 ++++++++++++++ 2 files changed, 19 insertions(+) create mode 100644 packages/patches/oslo-middleware-healthcheck-config-fixture.patch diff --git a/packages/oslo-middleware.nix b/packages/oslo-middleware.nix index c4158a3..f98a1a2 100644 --- a/packages/oslo-middleware.nix +++ b/packages/oslo-middleware.nix @@ -27,6 +27,11 @@ python3Packages.buildPythonPackage rec { pname = "oslo.middleware"; version = "6.3.0"; + # The standalone server test otherwise leaks detailed=True to later tests. + patches = [ + ./patches/oslo-middleware-healthcheck-config-fixture.patch + ]; + pyproject = true; build-system = [ python3Packages.pbr diff --git a/packages/patches/oslo-middleware-healthcheck-config-fixture.patch b/packages/patches/oslo-middleware-healthcheck-config-fixture.patch new file mode 100644 index 0000000..9414df6 --- /dev/null +++ b/packages/patches/oslo-middleware-healthcheck-config-fixture.patch @@ -0,0 +1,14 @@ +diff --git a/oslo_middleware/tests/test_healthcheck.py b/oslo_middleware/tests/test_healthcheck.py +--- a/oslo_middleware/tests/test_healthcheck.py ++++ b/oslo_middleware/tests/test_healthcheck.py +@@ -31,6 +31,10 @@ from oslo_middleware.healthcheck import __main__ + + class HealthcheckMainTests(test_base.BaseTestCase): + ++ def setUp(self): ++ super().setUp() ++ self.useFixture(config.Config()) ++ + def test_startup_response(self): + server = __main__.create_server(0) + th = threading.Thread(target=server.serve_forever) From 3be1368022775156f2b16adee5cbe1bab576f277 Mon Sep 17 00:00:00 2001 From: Stefan Kober Date: Fri, 23 Oct 2026 12:09:52 +0200 Subject: [PATCH 83/83] glance: fix parallel test execution When running the glance test on a machine with a lot of worker threads, we might run into failures because tests have implicit dependencies. Fix the dependencies so execution on larger runners work. Signed-off-by: Stefan Kober On-behalf-of: SAP stefan.kober@sap.com --- packages/glance.nix | 5 ++++ .../glance-single-store-test-options.patch | 23 +++++++++++++++++++ 2 files changed, 28 insertions(+) create mode 100644 packages/patches/glance-single-store-test-options.patch diff --git a/packages/glance.nix b/packages/glance.nix index 198cb00..e3c0965 100644 --- a/packages/glance.nix +++ b/packages/glance.nix @@ -41,6 +41,11 @@ in python3Packages.buildPythonPackage (rec { pname = "glance"; version = "29.0.0"; + # Single-store tests must register their options independently of test order. + patches = [ + ./patches/glance-single-store-test-options.patch + ]; + pyproject = true; build-system = [ python3Packages.pbr diff --git a/packages/patches/glance-single-store-test-options.patch b/packages/patches/glance-single-store-test-options.patch new file mode 100644 index 0000000..ad64cd2 --- /dev/null +++ b/packages/patches/glance-single-store-test-options.patch @@ -0,0 +1,23 @@ +diff --git a/glance/tests/unit/common/test_utils.py b/glance/tests/unit/common/test_utils.py +--- a/glance/tests/unit/common/test_utils.py ++++ b/glance/tests/unit/common/test_utils.py +@@ -796,17 +796,20 @@ class TestUtils(test_utils.BaseTestCase): + utils.get_stores_from_request, req, body) + + def test_single_store_http_enabled_and_http_not_in_url(self): ++ store.register_opts(CONF) + self.config(stores="http,file", group="glance_store") + loc_url = "rbd://aaaaaaaa/images/id" + self.assertFalse(utils.is_http_store_configured(loc_url)) + + def test_single_store_http_disabled_and_http_in_url(self): ++ store.register_opts(CONF) + self.config(stores="rbd,file", group="glance_store") + loc_url = BASE_URI + self.assertFalse(utils.is_http_store_configured(loc_url)) + + def test_single_store_http_enabled_and_http_in_url(self): ++ store.register_opts(CONF) + self.config(stores="http,file", group="glance_store") + loc_url = BASE_URI + self.assertTrue(utils.is_http_store_configured(loc_url))