Add DHCP pool support.
This commit is contained in:
parent
f35e402b4d
commit
bf6ba269af
3 changed files with 141 additions and 39 deletions
|
|
@ -83,6 +83,11 @@ let
|
||||||
ci = 23;
|
ci = 23;
|
||||||
media = 24;
|
media = 24;
|
||||||
homeAutomation = 25;
|
homeAutomation = 25;
|
||||||
|
dhcpStart = 29;
|
||||||
|
dhcpPool = 30;
|
||||||
|
dhcpEnd = 31;
|
||||||
|
|
||||||
|
# Legacy alias. New generated DHCP ranges use dhcpStart/dhcpEnd.
|
||||||
pool = 31;
|
pool = 31;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -124,6 +129,9 @@ let
|
||||||
ci = "ci";
|
ci = "ci";
|
||||||
media = "media";
|
media = "media";
|
||||||
homeAutomation = "home-auto";
|
homeAutomation = "home-auto";
|
||||||
|
dhcpStart = "dhcp-start";
|
||||||
|
dhcpPool = "dhcp-pool";
|
||||||
|
dhcpEnd = "dhcp-end";
|
||||||
pool = "dhcp-pool";
|
pool = "dhcp-pool";
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -283,27 +291,44 @@ let
|
||||||
inherit (scope) domain;
|
inherit (scope) domain;
|
||||||
};
|
};
|
||||||
|
|
||||||
# locationName -> numeric id (0, 1, 2, ...)
|
# All networks across all locations, in deterministic attr-name order.
|
||||||
locationIdForSpec =
|
# Network name, not location name, owns the second octet:
|
||||||
|
# 10.{network_id}.{subnet_type * 32 + host_role}.{host_id}
|
||||||
|
networkNamesForSpec =
|
||||||
spec:
|
spec:
|
||||||
let
|
let
|
||||||
locations = spec.locations or (throw "metatron-addressing: spec.locations is required");
|
locations = spec.locations or (throw "metatron-addressing: spec.locations is required");
|
||||||
locationNames = attrNames locations;
|
locationNames = attrNames locations;
|
||||||
|
networkNamesNested = builtins.map
|
||||||
|
(locationName: attrNames (locations.${locationName}.networks or { }))
|
||||||
|
locationNames;
|
||||||
|
in
|
||||||
|
unique (builtins.concatLists networkNamesNested);
|
||||||
|
|
||||||
|
# networkName -> numeric id (0, 1, 2, ...)
|
||||||
|
networkIdForSpec =
|
||||||
|
spec:
|
||||||
|
let
|
||||||
|
networkNames = networkNamesForSpec spec;
|
||||||
in
|
in
|
||||||
name:
|
name:
|
||||||
let
|
let
|
||||||
idx = elemIndex name locationNames;
|
idx = elemIndex name networkNames;
|
||||||
in
|
in
|
||||||
if idx == null then
|
if idx == null then
|
||||||
throw "metatron-addressing: unknown location '${name}'"
|
throw "metatron-addressing: unknown network '${name}'"
|
||||||
else
|
else
|
||||||
idx;
|
idx;
|
||||||
|
|
||||||
|
mkSubnetKey =
|
||||||
|
{ networkName, typeName }:
|
||||||
|
"${networkName}-${typeName}";
|
||||||
|
|
||||||
hostRole = path: hostCfg:
|
hostRole = path: hostCfg:
|
||||||
hostCfg.role or (throw "metatron-addressing: host '${path}' is missing required field 'role' for address generation");
|
hostCfg.role or (throw "metatron-addressing: host '${path}' is missing required field 'role' for address generation");
|
||||||
|
|
||||||
hostMapForSubnet =
|
hostMapForSubnet =
|
||||||
{ loc, locCode, locationName, networkName, typeName, subnetCfg, scope }:
|
{ networkId, networkCode, locationName, networkName, typeName, subnetCfg, scope }:
|
||||||
let
|
let
|
||||||
hostsForSubnet = subnetCfg.hosts or { };
|
hostsForSubnet = subnetCfg.hosts or { };
|
||||||
hostNames = filter (hn: builtins.substring 0 1 hn != "_") (attrNames hostsForSubnet);
|
hostNames = filter (hn: builtins.substring 0 1 hn != "_") (attrNames hostsForSubnet);
|
||||||
|
|
@ -333,7 +358,7 @@ let
|
||||||
hostId = hostCfg.hostId or roleIndex;
|
hostId = hostCfg.hostId or roleIndex;
|
||||||
|
|
||||||
ip = mkIpNamed {
|
ip = mkIpNamed {
|
||||||
location = loc;
|
location = networkId;
|
||||||
inherit typeName;
|
inherit typeName;
|
||||||
roleName = roleName;
|
roleName = roleName;
|
||||||
host = hostId;
|
host = hostId;
|
||||||
|
|
@ -355,10 +380,10 @@ let
|
||||||
location = locationName;
|
location = locationName;
|
||||||
network = networkName;
|
network = networkName;
|
||||||
subnetType = typeName;
|
subnetType = typeName;
|
||||||
subnetKey = "${locationName}-${networkName}-${typeName}";
|
subnetKey = mkSubnetKey { inherit networkName typeName; };
|
||||||
}
|
}
|
||||||
// optionalAttrs (hostScope.domain != null) {
|
// optionalAttrs (hostScope.domain != null) {
|
||||||
fqdn = "${hostname}.${subnetSlug}.${locCode}.${hostScope.domain}";
|
fqdn = "${hostname}.${subnetSlug}.${networkCode}.${hostScope.domain}";
|
||||||
}
|
}
|
||||||
// materializeScope hostScope
|
// materializeScope hostScope
|
||||||
// optionalAttrs (hostCfg ? hw-address) {
|
// optionalAttrs (hostCfg ? hw-address) {
|
||||||
|
|
@ -383,19 +408,19 @@ let
|
||||||
spec:
|
spec:
|
||||||
let
|
let
|
||||||
locations = spec.locations or (throw "metatron-addressing: spec.locations is required");
|
locations = spec.locations or (throw "metatron-addressing: spec.locations is required");
|
||||||
locationId = locationIdForSpec spec;
|
networkId = networkIdForSpec spec;
|
||||||
in
|
in
|
||||||
concatMapAttrs
|
concatMapAttrs
|
||||||
(locationName: locationCfg:
|
(locationName: locationCfg:
|
||||||
let
|
let
|
||||||
loc = locationId locationName;
|
|
||||||
locCode = mkLocationCode locationName;
|
|
||||||
locationScope = mergeScopes emptyScope locationCfg;
|
locationScope = mergeScopes emptyScope locationCfg;
|
||||||
networks = locationCfg.networks or { };
|
networks = locationCfg.networks or { };
|
||||||
in
|
in
|
||||||
concatMapAttrs
|
concatMapAttrs
|
||||||
(networkName: networkCfg:
|
(networkName: networkCfg:
|
||||||
let
|
let
|
||||||
|
net = networkId networkName;
|
||||||
|
networkCode = mkLocationCode networkName;
|
||||||
networkScope = mergeScopes locationScope networkCfg;
|
networkScope = mergeScopes locationScope networkCfg;
|
||||||
subnets = networkCfg.subnets or { };
|
subnets = networkCfg.subnets or { };
|
||||||
in
|
in
|
||||||
|
|
@ -405,7 +430,8 @@ let
|
||||||
subnetScope = mergeScopes networkScope subnetCfg;
|
subnetScope = mergeScopes networkScope subnetCfg;
|
||||||
in
|
in
|
||||||
hostMapForSubnet {
|
hostMapForSubnet {
|
||||||
inherit loc locCode locationName networkName typeName subnetCfg;
|
networkId = net;
|
||||||
|
inherit networkCode locationName networkName typeName subnetCfg;
|
||||||
scope = subnetScope;
|
scope = subnetScope;
|
||||||
})
|
})
|
||||||
subnets)
|
subnets)
|
||||||
|
|
@ -416,19 +442,19 @@ let
|
||||||
spec:
|
spec:
|
||||||
let
|
let
|
||||||
locations = spec.locations or (throw "metatron-addressing: spec.locations is required");
|
locations = spec.locations or (throw "metatron-addressing: spec.locations is required");
|
||||||
locationId = locationIdForSpec spec;
|
networkId = networkIdForSpec spec;
|
||||||
in
|
in
|
||||||
concatMapAttrs
|
concatMapAttrs
|
||||||
(locationName: locationCfg:
|
(locationName: locationCfg:
|
||||||
let
|
let
|
||||||
loc = locationId locationName;
|
|
||||||
locCode = mkLocationCode locationName;
|
|
||||||
locationScope = mergeScopes emptyScope locationCfg;
|
locationScope = mergeScopes emptyScope locationCfg;
|
||||||
networks = locationCfg.networks or { };
|
networks = locationCfg.networks or { };
|
||||||
in
|
in
|
||||||
concatMapAttrs
|
concatMapAttrs
|
||||||
(networkName: networkCfg:
|
(networkName: networkCfg:
|
||||||
let
|
let
|
||||||
|
net = networkId networkName;
|
||||||
|
networkCode = mkLocationCode networkName;
|
||||||
networkScope = mergeScopes locationScope networkCfg;
|
networkScope = mergeScopes locationScope networkCfg;
|
||||||
subnets = networkCfg.subnets or { };
|
subnets = networkCfg.subnets or { };
|
||||||
in
|
in
|
||||||
|
|
@ -436,8 +462,9 @@ let
|
||||||
(typeName: subnetCfg:
|
(typeName: subnetCfg:
|
||||||
let
|
let
|
||||||
subnetScope = mergeScopes networkScope subnetCfg;
|
subnetScope = mergeScopes networkScope subnetCfg;
|
||||||
|
subnetKey = mkSubnetKey { inherit networkName typeName; };
|
||||||
|
|
||||||
cidr = mkSubnetNamed { location = loc; inherit typeName; };
|
cidr = mkSubnetNamed { location = net; inherit typeName; };
|
||||||
|
|
||||||
subnetSlug =
|
subnetSlug =
|
||||||
if builtins.hasAttr typeName subnetSlugs
|
if builtins.hasAttr typeName subnetSlugs
|
||||||
|
|
@ -447,38 +474,73 @@ let
|
||||||
vlan =
|
vlan =
|
||||||
subnetCfg.vlan or
|
subnetCfg.vlan or
|
||||||
subnetCfg._vlan or
|
subnetCfg._vlan or
|
||||||
(mkVlanNamed { location = loc; inherit typeName; });
|
(mkVlanNamed { location = net; inherit typeName; });
|
||||||
|
|
||||||
dhcpCfg = subnetCfg.dhcp or null;
|
# DHCP is generated by default for every subnet unless explicitly disabled.
|
||||||
|
# Default range:
|
||||||
|
# start = 10.{network_id}.{subnet_type * 32 + 29}.1
|
||||||
|
# end = 10.{network_id}.{subnet_type * 32 + 31}.250
|
||||||
|
# Override options:
|
||||||
|
# dhcp = false;
|
||||||
|
# dhcpRange = { startIp = "..."; endIp = "..."; };
|
||||||
|
# dhcp = {
|
||||||
|
# startRole = "dhcpStart";
|
||||||
|
# startHostId = 1;
|
||||||
|
# endRole = "dhcpEnd";
|
||||||
|
# endHostId = 250;
|
||||||
|
# };
|
||||||
|
dhcpRaw = subnetCfg.dhcp or true;
|
||||||
|
dhcpEnabled = dhcpRaw != false;
|
||||||
|
dhcpCfg =
|
||||||
|
if builtins.isAttrs dhcpRaw
|
||||||
|
then dhcpRaw
|
||||||
|
else { };
|
||||||
|
|
||||||
|
dhcpStartRole = dhcpCfg.startRole or "dhcpStart";
|
||||||
|
dhcpEndRole = dhcpCfg.endRole or "dhcpEnd";
|
||||||
|
|
||||||
|
# start/end are kept as compatibility aliases for the old dhcp = { start = ...; end = ...; } shape.
|
||||||
|
dhcpStartHostId = dhcpCfg.startHostId or dhcpCfg.start or 1;
|
||||||
|
dhcpEndHostId = dhcpCfg.endHostId or dhcpCfg.end or 250;
|
||||||
|
|
||||||
|
generatedDhcpRange = {
|
||||||
|
startIp = mkIpNamed {
|
||||||
|
location = net;
|
||||||
|
inherit typeName;
|
||||||
|
roleName = dhcpStartRole;
|
||||||
|
host = dhcpStartHostId;
|
||||||
|
};
|
||||||
|
endIp = mkIpNamed {
|
||||||
|
location = net;
|
||||||
|
inherit typeName;
|
||||||
|
roleName = dhcpEndRole;
|
||||||
|
host = dhcpEndHostId;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
dhcpRange =
|
dhcpRange =
|
||||||
if dhcpCfg == null then null else {
|
if !dhcpEnabled then null
|
||||||
startIp = mkIpNamed {
|
else subnetCfg.dhcpRange or generatedDhcpRange;
|
||||||
location = loc;
|
|
||||||
inherit typeName;
|
effectiveDhcp = {
|
||||||
roleName = "pool";
|
startRole = dhcpStartRole;
|
||||||
host = dhcpCfg.start;
|
startHostId = dhcpStartHostId;
|
||||||
};
|
endRole = dhcpEndRole;
|
||||||
endIp = mkIpNamed {
|
endHostId = dhcpEndHostId;
|
||||||
location = loc;
|
};
|
||||||
inherit typeName;
|
|
||||||
roleName = "pool";
|
|
||||||
host = dhcpCfg.end;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
in
|
in
|
||||||
nameValuePair
|
nameValuePair
|
||||||
"${locationName}-${networkName}-${typeName}"
|
subnetKey
|
||||||
(
|
(
|
||||||
{
|
{
|
||||||
inherit cidr locationName networkName typeName vlan;
|
inherit cidr locationName networkName typeName subnetKey vlan;
|
||||||
}
|
}
|
||||||
// optionalAttrs (subnetScope.domain != null) {
|
// optionalAttrs (subnetScope.domain != null) {
|
||||||
zone = "${subnetSlug}.${locCode}.${subnetScope.domain}";
|
zone = "${subnetSlug}.${networkCode}.${subnetScope.domain}";
|
||||||
}
|
}
|
||||||
// materializeScope subnetScope
|
// materializeScope subnetScope
|
||||||
// optionalAttrs (dhcpCfg != null) {
|
// optionalAttrs (dhcpEnabled && dhcpRange != null) {
|
||||||
dhcp = dhcpCfg;
|
dhcp = effectiveDhcp;
|
||||||
dhcpRange = dhcpRange;
|
dhcpRange = dhcpRange;
|
||||||
}
|
}
|
||||||
))
|
))
|
||||||
|
|
@ -509,6 +571,9 @@ in
|
||||||
mkNetworkFromSpec
|
mkNetworkFromSpec
|
||||||
mkRoleRange
|
mkRoleRange
|
||||||
mkRoleRangeNamed
|
mkRoleRangeNamed
|
||||||
|
networkNamesForSpec
|
||||||
|
networkIdForSpec
|
||||||
|
mkSubnetKey
|
||||||
mergeScopes;
|
mergeScopes;
|
||||||
|
|
||||||
# Shorthand; you were calling mkHosts before.
|
# Shorthand; you were calling mkHosts before.
|
||||||
|
|
|
||||||
4
meta.nix
4
meta.nix
|
|
@ -5,7 +5,7 @@
|
||||||
cloud = {
|
cloud = {
|
||||||
domain = "kasear.net";
|
domain = "kasear.net";
|
||||||
|
|
||||||
networks.default.subnets = {
|
networks.cloud.subnets = {
|
||||||
dmz.hosts = {
|
dmz.hosts = {
|
||||||
eris = {
|
eris = {
|
||||||
role = "router";
|
role = "router";
|
||||||
|
|
@ -47,7 +47,7 @@
|
||||||
norfolk = {
|
norfolk = {
|
||||||
domain = "kasear.net";
|
domain = "kasear.net";
|
||||||
|
|
||||||
networks.default.subnets = {
|
networks.norfolk.subnets = {
|
||||||
dmz.hosts = {
|
dmz.hosts = {
|
||||||
io = {
|
io = {
|
||||||
role = "router";
|
role = "router";
|
||||||
|
|
|
||||||
37
tests/addressing-dhcp-test.nix
Normal file
37
tests/addressing-dhcp-test.nix
Normal file
|
|
@ -0,0 +1,37 @@
|
||||||
|
let
|
||||||
|
lib = import <nixpkgs/lib>;
|
||||||
|
|
||||||
|
addressing = import ../lib/addressing {
|
||||||
|
inherit lib;
|
||||||
|
};
|
||||||
|
|
||||||
|
spec = {
|
||||||
|
locations = {
|
||||||
|
cloud = {
|
||||||
|
domain = "kasear.net";
|
||||||
|
networks.cloud.subnets.dmz.hosts.eris.role = "router";
|
||||||
|
};
|
||||||
|
|
||||||
|
norfolk = {
|
||||||
|
domain = "kasear.net";
|
||||||
|
networks.norfolk.subnets = {
|
||||||
|
main.hosts.loki.role = "adminWorkstation";
|
||||||
|
|
||||||
|
iot = {
|
||||||
|
dhcpRange = {
|
||||||
|
startIp = "10.1.125.10";
|
||||||
|
endIp = "10.1.127.200";
|
||||||
|
};
|
||||||
|
hosts.thermostat.role = "homeAutomation";
|
||||||
|
};
|
||||||
|
|
||||||
|
lab = {
|
||||||
|
dhcp = false;
|
||||||
|
hosts.testbox.role = "labDevice";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
in
|
||||||
|
addressing.mkNetworkFromSpec spec
|
||||||
Loading…
Add table
Add a link
Reference in a new issue