1{ lib, nodes, ... }:
2
3let
4 inherit (lib)
5 attrNames concatMap concatMapStrings flip forEach head
6 listToAttrs mkDefault mkOption nameValuePair optionalString
7 range types zipListsWith zipLists
8 mdDoc
9 ;
10
11 nodeNumbers =
12 listToAttrs
13 (zipListsWith
14 nameValuePair
15 (attrNames nodes)
16 (range 1 254)
17 );
18
19 networkModule = { config, nodes, pkgs, ... }:
20 let
21 interfacesNumbered = zipLists config.virtualisation.vlans (range 1 255);
22 interfaces = forEach interfacesNumbered ({ fst, snd }:
23 nameValuePair "eth${toString snd}" {
24 ipv4.addresses =
25 [{
26 address = "192.168.${toString fst}.${toString config.virtualisation.test.nodeNumber}";
27 prefixLength = 24;
28 }];
29 });
30
31 networkConfig =
32 {
33 networking.hostName = mkDefault config.virtualisation.test.nodeName;
34
35 networking.interfaces = listToAttrs interfaces;
36
37 networking.primaryIPAddress =
38 optionalString (interfaces != [ ]) (head (head interfaces).value.ipv4.addresses).address;
39
40 # Put the IP addresses of all VMs in this machine's
41 # /etc/hosts file. If a machine has multiple
42 # interfaces, use the IP address corresponding to
43 # the first interface (i.e. the first network in its
44 # virtualisation.vlans option).
45 networking.extraHosts = flip concatMapStrings (attrNames nodes)
46 (m':
47 let config = nodes.${m'}; in
48 optionalString (config.networking.primaryIPAddress != "")
49 ("${config.networking.primaryIPAddress} " +
50 optionalString (config.networking.domain != null)
51 "${config.networking.hostName}.${config.networking.domain} " +
52 "${config.networking.hostName}\n"));
53
54 virtualisation.qemu.options =
55 let qemu-common = import ../qemu-common.nix { inherit lib pkgs; };
56 in
57 flip concatMap interfacesNumbered
58 ({ fst, snd }: qemu-common.qemuNICFlags snd fst config.virtualisation.test.nodeNumber);
59 };
60
61 in
62 {
63 key = "ip-address";
64 config = networkConfig // {
65 # Expose the networkConfig items for tests like nixops
66 # that need to recreate the network config.
67 system.build.networkConfig = networkConfig;
68 };
69 };
70
71 nodeNumberModule = (regular@{ config, name, ... }: {
72 options = {
73 virtualisation.test.nodeName = mkOption {
74 internal = true;
75 default = name;
76 # We need to force this in specilisations, otherwise it'd be
77 # readOnly = true;
78 description = mdDoc ''
79 The `name` in `nodes.<name>`; stable across `specialisations`.
80 '';
81 };
82 virtualisation.test.nodeNumber = mkOption {
83 internal = true;
84 type = types.int;
85 readOnly = true;
86 default = nodeNumbers.${config.virtualisation.test.nodeName};
87 description = mdDoc ''
88 A unique number assigned for each node in `nodes`.
89 '';
90 };
91
92 # specialisations override the `name` module argument,
93 # so we push the real `virtualisation.test.nodeName`.
94 specialisation = mkOption {
95 type = types.attrsOf (types.submodule {
96 options.configuration = mkOption {
97 type = types.submoduleWith {
98 modules = [
99 {
100 config.virtualisation.test.nodeName =
101 # assert regular.config.virtualisation.test.nodeName != "configuration";
102 regular.config.virtualisation.test.nodeName;
103 }
104 ];
105 };
106 };
107 });
108 };
109 };
110 });
111
112in
113{
114 config = {
115 extraBaseModules = { imports = [ networkModule nodeNumberModule ]; };
116 };
117}