diff --git a/global/overlay/etc/puppet/modules/cdn/files/l4lb/sunet-l4lb-namespace b/global/overlay/etc/puppet/modules/cdn/files/l4lb/sunet-l4lb-namespace index 39d86d0..2463f63 100755 --- a/global/overlay/etc/puppet/modules/cdn/files/l4lb/sunet-l4lb-namespace +++ b/global/overlay/etc/puppet/modules/cdn/files/l4lb/sunet-l4lb-namespace @@ -13,6 +13,7 @@ pylint sunet-l4lb-namespace mypy --strict sunet-l4lb-namespace """ +import ipaddress import json import shlex import subprocess @@ -21,6 +22,7 @@ import sys def run_command(cmd: str) -> subprocess.CompletedProcess[str]: """Execute subprocess command""" + print(f"{cmd}") args = shlex.split(cmd) try: proc = subprocess.run(args, capture_output=True, check=True, encoding="utf-8") @@ -35,12 +37,15 @@ def run_command(cmd: str) -> subprocess.CompletedProcess[str]: return proc -def configure_interfaces( +def configure_interfaces( # pylint: disable=too-many-locals,too-many-branches namespace: str, if_data: dict[str, dict[str, list[str]]] ) -> None: """Configure interfaces""" - proc = run_command("ip netns exec l4lb ip -j addr show") + proc = run_command(f"ip netns exec {namespace} ip -j addr show") namespace_ifs = json.loads(proc.stdout) + + ipv4_key = "ipv4" + ipv6_key = "ipv6" for if_name, data in if_data.items(): if_exists = next( (True for interface in namespace_ifs if interface["ifname"] == if_name), @@ -54,38 +59,73 @@ def configure_interfaces( else: run_command(f"ip link set {if_name} netns {namespace}") + run_command(f"ip netns exec {namespace} ip link set {if_name} up") + proc = run_command(f"ip netns exec {namespace} ip -j addr show dev {if_name}") if_conf = json.loads(proc.stdout) - for ipv4_cidr in data["ipv4"]: - ip4, prefix = ipv4_cidr.split("/") - v4_addr_exists = next( - ( - True - for addr in if_conf[0]["addr_info"] - if addr["local"] == ip4 and addr["prefixlen"] == int(prefix) - ), - False, - ) - if not v4_addr_exists: - run_command( - f"ip netns exec {namespace} ip addr add {ipv4_cidr} dev {if_name}" + + # Add missing addresses from config + if ipv4_key in data: + for configured_ipv4_cidr in data[ipv4_key]: + ip4, prefix = configured_ipv4_cidr.split("/") + v4_addr_exists = next( + ( + True + for addr in if_conf[0]["addr_info"] + if addr["local"] == ip4 and addr["prefixlen"] == int(prefix) + ), + False, ) - for ipv6_cidr in data["ipv6"]: - ip6, prefix = ipv6_cidr.split("/") - v6_addr_exists = next( - ( - True - for addr in if_conf[0]["addr_info"] - if addr["local"] == ip6 and addr["prefixlen"] == int(prefix) - ), - False, - ) - if not v6_addr_exists: - run_command( - f"ip netns exec {namespace} ip addr add {ipv6_cidr} dev {if_name}" + if not v4_addr_exists: + run_command( + f"ip netns exec {namespace} ip addr add {configured_ipv4_cidr} dev {if_name}" # pylint: disable=line-too-long + ) + if ipv6_key in data: + for ipv6_cidr in data[ipv6_key]: + ip6, prefix = ipv6_cidr.split("/") + v6_addr_exists = next( + ( + True + for addr in if_conf[0]["addr_info"] + if addr["local"] == ip6 and addr["prefixlen"] == int(prefix) + ), + False, + ) + if not v6_addr_exists: + run_command( + f"ip netns exec {namespace} ip addr add {ipv6_cidr} dev {if_name}" + ) + + # Remove no longer configured addresseses + for addr_info in if_conf[0]["addr_info"]: + # Ignore addresses like fe80 + if addr_info["scope"] != "global": + continue + + cidr = "/".join((addr_info["local"], str(addr_info["prefixlen"]))) + + # We need strict=False because otherwise ip_network() gets angry if + # there are host bits set in the address (which of course there is + # because we are parsing actual interface configs, not pure + # "networks") + cidr_net = ipaddress.ip_network(cidr, strict=False) + + needs_removal = False + if cidr_net.version == 4: + if ipv4_key not in data or cidr not in data[ipv4_key]: + needs_removal = True + elif cidr_net.version == 6: + if ipv6_key not in data or cidr not in data[ipv6_key]: + needs_removal = True + else: + raise ValueError( + f"Expected IPv4 or IPv6, got something else: {cidr_net.version}" ) - run_command(f"ip netns exec {namespace} ip link set {if_name} up") + if needs_removal: + run_command( + f"ip netns exec {namespace} ip addr del {cidr} dev {if_name}" + ) def setup_namespaces(netns_data: dict[str, dict[str, dict[str, list[str]]]]) -> None: @@ -99,8 +139,8 @@ def setup_namespaces(netns_data: dict[str, dict[str, dict[str, list[str]]]]) -> if not netns_exists: run_command(f"ip netns add {namespace}") - # Make localhost available - run_command(f"ip netns exec {namespace} ip link set lo up") + # Make localhost available + run_command(f"ip netns exec {namespace} ip link set lo up") configure_interfaces(namespace, if_data) @@ -129,10 +169,20 @@ def main() -> None: # } # } # } - with open("/opt/sunet-cdn/l4lb/conf/netns.json", encoding="utf-8") as f: - netns_data = json.load(f) - setup_namespaces(netns_data) + input_files = [ + "/opt/sunet-cdn/l4lb/conf/netns-base.json", + "/opt/sunet-cdn/l4lb/conf/netns-sunet-cdn-agent.json", + ] + + for input_file in input_files: + try: + with open(input_file, encoding="utf-8") as f: + netns_data = json.load(f) + except FileNotFoundError: + print(f"skipping nonexistant file '{input_file}'") + continue + setup_namespaces(netns_data) if __name__ == "__main__": diff --git a/global/overlay/etc/puppet/modules/cdn/manifests/l4lb.pp b/global/overlay/etc/puppet/modules/cdn/manifests/l4lb.pp index 6b8c12a..49b7290 100644 --- a/global/overlay/etc/puppet/modules/cdn/manifests/l4lb.pp +++ b/global/overlay/etc/puppet/modules/cdn/manifests/l4lb.pp @@ -54,12 +54,12 @@ class cdn::l4lb( mode => '0640', } - file { '/opt/sunet-cdn/l4lb/conf/netns.json': + file { '/opt/sunet-cdn/l4lb/conf/netns-base.json': ensure => file, owner => 'root', group => 'root', mode => '0644', - content => template('cdn/l4lb/netns.json.erb'), + content => template('cdn/l4lb/netns-base.json.erb'), } file { '/usr/local/bin/sunet-l4lb-namespace': diff --git a/global/overlay/etc/puppet/modules/cdn/templates/l4lb/netns.json.erb b/global/overlay/etc/puppet/modules/cdn/templates/l4lb/netns-base.json.erb similarity index 69% rename from global/overlay/etc/puppet/modules/cdn/templates/l4lb/netns.json.erb rename to global/overlay/etc/puppet/modules/cdn/templates/l4lb/netns-base.json.erb index af5c4ee..4cf6b36 100644 --- a/global/overlay/etc/puppet/modules/cdn/templates/l4lb/netns.json.erb +++ b/global/overlay/etc/puppet/modules/cdn/templates/l4lb/netns-base.json.erb @@ -15,14 +15,6 @@ "ipv6": [ "2001:6b0:2006:75::1/127" ] - }, - "dummy0": { - "ipv4": [ - "188.240.152.1/32" - ], - "ipv6": [ - "2001:6b0:2100::1/128" - ] } } }