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