at master 5.6 kB view raw
1{ lib, nodes, ... }: 2 3let 4 inherit (lib) 5 attrNames 6 concatMap 7 concatMapStrings 8 flip 9 forEach 10 head 11 listToAttrs 12 mkDefault 13 mkOption 14 nameValuePair 15 optionalString 16 range 17 toLower 18 types 19 zipListsWith 20 zipLists 21 ; 22 23 nodeNumbers = listToAttrs (zipListsWith nameValuePair (attrNames nodes) (range 1 254)); 24 25 networkModule = 26 { 27 config, 28 nodes, 29 pkgs, 30 ... 31 }: 32 let 33 qemu-common = import ../qemu-common.nix { inherit lib pkgs; }; 34 35 # Convert legacy VLANs to named interfaces and merge with explicit interfaces. 36 vlansNumbered = forEach (zipLists config.virtualisation.vlans (range 1 255)) (v: { 37 name = "eth${toString v.snd}"; 38 vlan = v.fst; 39 assignIP = true; 40 }); 41 explicitInterfaces = lib.mapAttrsToList (n: v: v // { name = n; }) config.virtualisation.interfaces; 42 interfaces = vlansNumbered ++ explicitInterfaces; 43 interfacesNumbered = zipLists interfaces (range 1 255); 44 45 # Automatically assign IP addresses to requested interfaces. 46 assignIPs = lib.filter (i: i.assignIP) interfaces; 47 ipInterfaces = forEach assignIPs ( 48 i: 49 nameValuePair i.name { 50 ipv4.addresses = [ 51 { 52 address = "192.168.${toString i.vlan}.${toString config.virtualisation.test.nodeNumber}"; 53 prefixLength = 24; 54 } 55 ]; 56 ipv6.addresses = [ 57 { 58 address = "2001:db8:${toString i.vlan}::${toString config.virtualisation.test.nodeNumber}"; 59 prefixLength = 64; 60 } 61 ]; 62 } 63 ); 64 65 qemuOptions = lib.flatten ( 66 forEach interfacesNumbered ( 67 { fst, snd }: qemu-common.qemuNICFlags snd fst.vlan config.virtualisation.test.nodeNumber 68 ) 69 ); 70 udevRules = forEach interfacesNumbered ( 71 { fst, snd }: 72 # MAC Addresses for QEMU network devices are lowercase, and udev string comparison is case-sensitive. 73 ''SUBSYSTEM=="net",ACTION=="add",ATTR{address}=="${toLower (qemu-common.qemuNicMac fst.vlan config.virtualisation.test.nodeNumber)}",NAME="${fst.name}"'' 74 ); 75 76 networkConfig = { 77 networking.hostName = mkDefault config.virtualisation.test.nodeName; 78 79 networking.interfaces = listToAttrs ipInterfaces; 80 81 networking.primaryIPAddress = 82 optionalString (ipInterfaces != [ ]) 83 (head (head ipInterfaces).value.ipv4.addresses).address; 84 85 networking.primaryIPv6Address = 86 optionalString (ipInterfaces != [ ]) 87 (head (head ipInterfaces).value.ipv6.addresses).address; 88 89 # Put the IP addresses of all VMs in this machine's 90 # /etc/hosts file. If a machine has multiple 91 # interfaces, use the IP address corresponding to 92 # the first interface (i.e. the first network in its 93 # virtualisation.vlans option). 94 networking.extraHosts = flip concatMapStrings (attrNames nodes) ( 95 m': 96 let 97 config = nodes.${m'}; 98 hostnames = 99 optionalString ( 100 config.networking.domain != null 101 ) "${config.networking.hostName}.${config.networking.domain} " 102 + "${config.networking.hostName}\n"; 103 in 104 optionalString ( 105 config.networking.primaryIPAddress != "" 106 ) "${config.networking.primaryIPAddress} ${hostnames}" 107 + optionalString (config.networking.primaryIPv6Address != "") ( 108 "${config.networking.primaryIPv6Address} ${hostnames}" 109 ) 110 ); 111 112 virtualisation.qemu.options = qemuOptions; 113 boot.initrd.services.udev.rules = concatMapStrings (x: x + "\n") udevRules; 114 }; 115 116 in 117 { 118 key = "network-interfaces"; 119 config = networkConfig // { 120 # Expose the networkConfig items for tests like nixops 121 # that need to recreate the network config. 122 system.build.networkConfig = networkConfig; 123 }; 124 }; 125 126 nodeNumberModule = ( 127 regular@{ config, name, ... }: 128 { 129 options = { 130 virtualisation.test.nodeName = mkOption { 131 internal = true; 132 default = name; 133 # We need to force this in specialisations, otherwise it'd be 134 # readOnly = true; 135 description = '' 136 The `name` in `nodes.<name>`; stable across `specialisations`. 137 ''; 138 }; 139 virtualisation.test.nodeNumber = mkOption { 140 internal = true; 141 type = types.int; 142 readOnly = true; 143 default = nodeNumbers.${config.virtualisation.test.nodeName}; 144 description = '' 145 A unique number assigned for each node in `nodes`. 146 ''; 147 }; 148 149 # specialisations override the `name` module argument, 150 # so we push the real `virtualisation.test.nodeName`. 151 specialisation = mkOption { 152 type = types.attrsOf ( 153 types.submodule { 154 options.configuration = mkOption { 155 type = types.submoduleWith { 156 modules = [ 157 { 158 config.virtualisation.test.nodeName = 159 # assert regular.config.virtualisation.test.nodeName != "configuration"; 160 regular.config.virtualisation.test.nodeName; 161 } 162 ]; 163 }; 164 }; 165 } 166 ); 167 }; 168 }; 169 } 170 ); 171 172in 173{ 174 config = { 175 extraBaseModules = { 176 imports = [ 177 networkModule 178 nodeNumberModule 179 ]; 180 }; 181 }; 182}