diff --git a/lib/distributed-service/tests/default.nix b/lib/distributed-service/tests/default.nix index 3ef549646..4ab86e351 100644 --- a/lib/distributed-service/tests/default.nix +++ b/lib/distributed-service/tests/default.nix @@ -59,4 +59,269 @@ in expr = res.evals ? "self-simple-module"; expected = true; }; + + # A module can be imported multiple times + # A module can also have multiple instances within the same module + # This mean modules must be grouped together, imported once + # All instances should be included within one evaluation to make all of them available + test_module_grouping = + let + res = callInventoryAdapter { + # Authored module + # A minimal module looks like this + # It isn't exactly doing anything but it's a valid module that produces an output + modules."A" = { + _class = "clan.service"; + manifest = { + name = "A-name"; + }; + + perMachine = { }: { }; + }; + modules."B" = { + _class = "clan.service"; + manifest = { + name = "B-name"; + }; + + perMachine = { }: { }; + }; + # User config + instances."instance_foo" = { + module = { + name = "A"; + }; + }; + instances."instance_bar" = { + module = { + name = "B"; + }; + }; + instances."instance_baz" = { + module = { + name = "A"; + }; + }; + }; + in + { + # Test that the module is mapped into the output + # We might change the attribute name in the future + expr = lib.mapAttrs (_n: v: builtins.length v) res.grouped; + expected = { + self-A = 2; + self-B = 1; + }; + }; + + test_creates_all_instances = + let + res = callInventoryAdapter { + # Authored module + # A minimal module looks like this + # It isn't exactly doing anything but it's a valid module that produces an output + modules."A" = { + _class = "clan.service"; + manifest = { + name = "network"; + }; + + perMachine = { }: { }; + }; + instances."instance_foo" = { + module = { + name = "A"; + }; + }; + instances."instance_bar" = { + module = { + name = "A"; + }; + }; + instances."instance_zaza" = { + module = { + name = "B"; + }; + }; + }; + in + { + # Test that the module is mapped into the output + # We might change the attribute name in the future + expr = lib.attrNames res.evals.self-A.config.instances; + expected = [ + "instance_bar" + "instance_foo" + ]; + }; + + # Membership via roles + test_add_machines_directly = + let + res = callInventoryAdapter { + # Authored module + # A minimal module looks like this + # It isn't exactly doing anything but it's a valid module that produces an output + modules."A" = { + _class = "clan.service"; + manifest = { + name = "network"; + }; + # Define a role without special behavior + roles.peer = { }; + + # perMachine = {}: {}; + }; + machines = { + jon = { }; + sara = { }; + hxi = { }; + }; + instances."instance_foo" = { + module = { + name = "A"; + }; + roles.peer.machines.jon = { }; + }; + instances."instance_bar" = { + module = { + name = "A"; + }; + roles.peer.machines.sara = { }; + }; + instances."instance_zaza" = { + module = { + name = "B"; + }; + roles.peer.tags.all = { }; + }; + }; + in + { + # Test that the module is mapped into the output + # We might change the attribute name in the future + expr = lib.attrNames res.evals.self-A.config.result.allMachines; + expected = [ + "jon" + "sara" + ]; + }; + + # Membership via tags + test_add_machines_via_tags = + let + res = callInventoryAdapter { + # Authored module + # A minimal module looks like this + # It isn't exactly doing anything but it's a valid module that produces an output + modules."A" = { + _class = "clan.service"; + manifest = { + name = "network"; + }; + # Define a role without special behavior + roles.peer = { }; + + # perMachine = {}: {}; + }; + machines = { + jon = { + tags = [ "foo" ]; + }; + sara = { + tags = [ "foo" ]; + }; + hxi = { }; + }; + instances."instance_foo" = { + module = { + name = "A"; + }; + roles.peer.tags.foo = { }; + }; + instances."instance_zaza" = { + module = { + name = "B"; + }; + roles.peer.tags.all = { }; + }; + }; + in + { + # Test that the module is mapped into the output + # We might change the attribute name in the future + expr = lib.attrNames res.evals.self-A.config.result.allMachines; + expected = [ + "jon" + "sara" + ]; + }; + + per_machine_args = import ./per_machine_args.nix { inherit lib callInventoryAdapter; }; + # test_per_machine_receives_instances = + # let + # res = callInventoryAdapter { + # # Authored module + # # A minimal module looks like this + # # It isn't exactly doing anything but it's a valid module that produces an output + # modules."A" = { + # _class = "clan.service"; + # manifest = { + # name = "network"; + # }; + # # Define a role without special behavior + # roles.peer = { }; + + # perMachine = + # { instances, ... }: + # { + # nixosModule = instances; + # }; + # }; + # machines = { + # jon = { }; + # sara = { }; + # }; + # instances."instance_foo" = { + # module = { + # name = "A"; + # }; + # roles.peer.machines.jon = { }; + # }; + # instances."instance_bar" = { + # module = { + # name = "A"; + # }; + # roles.peer.machines.sara = { }; + # }; + # instances."instance_zaza" = { + # module = { + # name = "B"; + # }; + # roles.peer.tags.all = { }; + # }; + # }; + # in + # { + # expr = { + # hasMachineSettings = + # res.evals.self-A.config.result.allMachines.jon.nixosModule. # { {instanceName} :: { roles :: { {roleName} :: { machines :: { {machineName} :: { settings :: {} } } } } } } + # instance_foo.roles.peer.machines.jon ? settings; + # machineSettingsEmpty = + # lib.filterAttrs (n: _v: n != "__functor" ) res.evals.self-A.config.result.allMachines.jon.nixosModule. # { {instanceName} :: { roles :: { {roleName} :: { machines :: { {machineName} :: { settings :: {} } } } } } } + # instance_foo.roles.peer.machines.jon.settings; + # hasRoleSettings = + # res.evals.self-A.config.result.allMachines.jon.nixosModule. # { {instanceName} :: { roles :: { {roleName} :: { machines :: { {machineName} :: { settings :: {} } } } } } } + # instance_foo.roles.peer ? settings; + # roleSettingsEmpty = + # lib.filterAttrs (n: _v: n != "__functor" ) res.evals.self-A.config.result.allMachines.jon.nixosModule. # { {instanceName} :: { roles :: { {roleName} :: { machines :: { {machineName} :: { settings :: {} } } } } } } + # instance_foo.roles.peer.settings; + # }; + # expected = { + # hasMachineSettings = true; + # machineSettingsEmpty = {}; + # hasRoleSettings = true; + # roleSettingsEmpty = {}; + # }; + # }; } diff --git a/lib/distributed-service/tests/per_machine_args.nix b/lib/distributed-service/tests/per_machine_args.nix new file mode 100644 index 000000000..ad4784af6 --- /dev/null +++ b/lib/distributed-service/tests/per_machine_args.nix @@ -0,0 +1,107 @@ +{ lib, callInventoryAdapter }: + +let # Authored module + # A minimal module looks like this + # It isn't exactly doing anything but it's a valid module that produces an output + modules."A" = { + _class = "clan.service"; + manifest = { + name = "network"; + }; + # Define two roles with unmergeable interfaces + # Both define some 'timeout' but with completely different types. + roles.peer.interface = + { lib, ... }: + { + options.timeout = lib.mkOption { + type = lib.types.str; + }; + }; + roles.server.interface = + { lib, ... }: + { + options.timeout = lib.mkOption { + type = lib.types.submodule; + }; + }; + + perMachine = + { instances, ... }: + { + nixosModule = instances; + }; + }; + machines = { + jon = { }; + sara = { }; + }; + res = callInventoryAdapter { + inherit modules machines; + instances."instance_foo" = { + module = { + name = "A"; + }; + roles.peer.machines.jon = { + settings.timeout = lib.mkForce "foo-peer-jon"; + }; + roles.peer = { + settings.timeout = "foo-peer"; + }; + }; + instances."instance_bar" = { + module = { + name = "A"; + }; + roles.peer.machines.jon = { + settings.timeout = "bar-peer-jon"; + }; + }; + instances."instance_zaza" = { + module = { + name = "B"; + }; + roles.peer.tags.all = { }; + }; + }; + + filterInternals = lib.filterAttrs (n: _v: !lib.hasPrefix "_" n); +in + +{ + + # settings should evaluate + test_per_machine_receives_instance_settings = { + expr = { + hasMachineSettings = + res.evals.self-A.config.result.allMachines.jon.nixosModule.instance_foo.roles.peer.machines.jon + ? settings; + + # settings are specific. + # Below we access: + # instance = instance_foo + # roles = peer + # machines = jon + specificMachineSettings = filterInternals res.evals.self-A.config.result.allMachines.jon.nixosModule.instance_foo.roles.peer.machines.jon.settings; + + hasRoleSettings = + res.evals.self-A.config.result.allMachines.jon.nixosModule.instance_foo.roles.peer ? settings; + + # settings are specific. + # Below we access: + # instance = instance_foo + # roles = peer + # machines = * + specificRoleSettings = filterInternals res.evals.self-A.config.result.allMachines.jon.nixosModule.instance_foo.roles.peer.settings; + }; + expected = { + hasMachineSettings = true; + specificMachineSettings = { + timeout = "foo-peer-jon"; + }; + hasRoleSettings = true; + specificRoleSettings = { + timeout = "foo-peer"; + }; + }; + }; +}