{
config,
lib,
nixpkgs-pacemaker,
pkgs,
...
}: let
inherit
(lib)
attrNames
attrValues
concatMapStringsSep
elemAt
filterAttrs
isAttrs
mkIf
mkOption
types
;
inherit (builtins) toFile map listToAttrs;
pacemakerPath = "services/cluster/pacemaker/default.nix";
cfg = config.services.pacemaker;
in {
disabledModules = [pacemakerPath];
imports = ["${nixpkgs-pacemaker}/nixos/modules/${pacemakerPath}"];
options.services.pacemaker = {
resources = mkOption {
default = {};
type = with types;
attrsOf (submodule ({name, ...}: {
options = {
enable = mkOption {
default = true;
type = types.bool;
};
systemdName = mkOption {
default = name;
type = types.str;
};
# TODO: add assertion to not have same id
virtualIps = mkOption {
default = [];
type = with types;
listOf (submodule {
options = {
id = mkOption {
type = types.str;
};
interface = mkOption {
default = "eno1";
type = types.str;
};
ip = mkOption {
type = types.str;
};
cidr = mkOption {
default = 24;
type = types.int;
};
};
});
};
# TODO: add assertion, needs to be an existing systemdName
dependsOn = mkOption {
default = [];
type = types.listOf types.str;
};
# TODO: Add extraResources, extraConstraints ...
};
}));
};
};
config = mkIf cfg.enable {
systemd.services = let
mkVirtIps = res:
concatMapStringsSep "\n" (vip: ''
'')
res.virtualIps;
mkSystemdResource = res: ''
'';
mkConstraint = res: first: let
firstName =
if isAttrs first
then first.systemdName
else first;
in ''
'';
mkDependsOn = res: let
mkConstraint' = first:
mkConstraint res first;
in
concatMapStringsSep "\n" mkConstraint' res.dependsOn;
mkVipConstraint = res:
concatMapStringsSep "\n" (
vip:
mkConstraint
res
"${res.systemdName}-${vip.id}-vip"
)
res.virtualIps;
# If we're updating resources we have to kill constraints to add new resources
constraintsEmpty = toFile "constraints.xml" ''
'';
resEnabled = filterAttrs (n: v: v.enable) cfg.resources;
resWithIp = filterAttrs (n: v: ! isNull v.virtualIps) resEnabled;
resources = toFile "resources.xml" ''
${concatMapStringsSep "\n" mkVirtIps (attrValues resWithIp)}
${concatMapStringsSep "\n" mkSystemdResource (attrValues resEnabled)}
'';
constraints = toFile "constraints.xml" ''
${concatMapStringsSep "\n" mkVipConstraint (attrValues resWithIp)}
${concatMapStringsSep "\n" mkDependsOn (attrValues resEnabled)}
'';
host1 = (elemAt config.services.corosync.nodelist 0).name;
in
{
"pacemaker-setup" = {
after = ["corosync.service" "pacemaker.service"];
path = with pkgs; [pacemaker];
script = ''
# The config needs to be installed from one node only
# TODO: add assertion, corosync must be enabled with at least one node
if [ "$(uname -n)" = ${host1} ]; then
# TODO: setup stonith / fencing
crm_attribute --type crm_config --name stonith-enabled --update false
crm_attribute --type crm_config --name no-quorum-policy --delete
# Install config
cibadmin --replace --scope constraints --xml-file ${constraintsEmpty}
cibadmin --replace --scope resources --xml-file ${resources}
cibadmin --replace --scope constraints --xml-file ${constraints}
fi
'';
};
}
# Force all systemd units handled by pacemaker to not start automatically
// listToAttrs (map (x: {
name = x;
value = {
wantedBy = lib.mkForce [];
};
}) (attrNames cfg.resources));
# FIXME: https://github.com/NixOS/nixpkgs/pull/208298
nixpkgs.overlays = [
(final: prev: {
inherit
(nixpkgs-pacemaker.legacyPackages.x86_64-linux)
pacemaker
ocf-resource-agents
;
})
];
};
}