From 80a419b0a7dcf3391b41785e34f07d41de94ecc5 Mon Sep 17 00:00:00 2001 From: Ubuntu <jorge.sasiain@ehu.eus> Date: Thu, 4 May 2023 10:45:58 +0000 Subject: [PATCH] NAT-152: add support for extattrs --- gso/services/_ipam.py | 48 +++++++++++++++++++++----------- gso/services/ipam.py | 64 ++++++++++++++++++++++++++++++------------- 2 files changed, 77 insertions(+), 35 deletions(-) diff --git a/gso/services/_ipam.py b/gso/services/_ipam.py index d6a620cb..64d6c6b8 100644 --- a/gso/services/_ipam.py +++ b/gso/services/_ipam.py @@ -38,6 +38,8 @@ class HostAddresses(BaseSettings): class IPAMErrors(Enum): # HTTP error code, match in error message CONTAINER_FULL = 400, "Can not find requested number of networks" + EXTATTR_UNKNOWN = 400, "Unknown extensible attribute" + EXTATTR_BADVALUE = 400, "Bad value for extensible attribute" # TODO: remove this! @@ -144,7 +146,8 @@ def _get_network_capacity(network=None): def _allocate_network( infoblox_params: settings.InfoBloxParams, network_params: Union[settings.V4NetworkParams, settings.V6NetworkParams], - ip_version=4 + ip_version=4, + extattrs={} ) -> Union[V4ServiceNetwork, V6ServiceNetwork]: assert ip_version in [4, 6] endpoint = 'network' if ip_version == 4 else 'ipv6network' @@ -152,6 +155,7 @@ def _allocate_network( 'ipv6networkcontainer' # only return in the response the allocated network, not all available + # TODO: any validation needed for extrattrs wherever it's used? req_payload = { "network": { "_object_function": "next_available_network", @@ -163,7 +167,8 @@ def _allocate_network( "network": str(network_params.containers[0]) }, "_result_field": "networks", - } + }, + "extattrs": extattrs } container_index = 0 @@ -198,7 +203,7 @@ def _allocate_network( return V6ServiceNetwork(v6=allocated_network) -def allocate_service_ipv4_network(service_type) -> V4ServiceNetwork: +def allocate_service_ipv4_network(service_type, extattrs) -> V4ServiceNetwork: """ Allocate IPv4 network within the container of the specified service type. """ @@ -209,10 +214,11 @@ def allocate_service_ipv4_network(service_type) -> V4ServiceNetwork: and service_type != 'INFOBLOX', "Invalid service type." return _allocate_network(ipam_params.INFOBLOX, getattr(ipam_params, service_type).V4, - 4) + 4, + extattrs) -def allocate_service_ipv6_network(service_type) -> V6ServiceNetwork: +def allocate_service_ipv6_network(service_type, extattrs) -> V6ServiceNetwork: """ Allocate IPv6 network within the container of the specified service type. """ @@ -223,7 +229,8 @@ def allocate_service_ipv6_network(service_type) -> V6ServiceNetwork: and service_type != 'INFOBLOX', "Invalid service type." return _allocate_network(ipam_params.INFOBLOX, getattr(ipam_params, service_type).V6, - 6) + 6, + extattrs) def _find_next_available_ip(infoblox_params, network_ref): @@ -242,7 +249,7 @@ def _find_next_available_ip(infoblox_params, network_ref): return received_ip[0] -def _allocate_host(hostname=None, addr=None, network=None +def _allocate_host(hostname=None, addr=None, network=None, extattrs={} ) -> Union[V4HostAddress, V6HostAddress]: """ If network is not None, allocate host in that network. @@ -278,7 +285,8 @@ def _allocate_host(hostname=None, addr=None, network=None ], "name": hostname, "configure_for_dns": False, - "view": "default" + "view": "default", + "extattrs": extattrs } r = requests.post( @@ -296,7 +304,8 @@ def _allocate_host(hostname=None, addr=None, network=None dns_req_payload = { f"ipv{ip_version}addr": addr, "name": hostname, - "view": "default" + "view": "default", + "extattrs": extattrs } endpoint = 'record:a' if ip_version == 4 else 'record:aaaa' @@ -322,7 +331,8 @@ def _allocate_host(hostname=None, addr=None, network=None def allocate_service_host(hostname=None, service_type=None, service_networks: ServiceNetworks = None, - host_addresses: HostAddresses = None + host_addresses: HostAddresses = None, + extattrs={} ) -> HostAddresses: """ Allocate host with both IPv4 and IPv6 address (and respective DNS @@ -370,19 +380,22 @@ def allocate_service_host(hostname=None, assert first_nonfull_ipv4_network, \ "No available IPv4 addresses for this service type." v4_host = _allocate_host(hostname=hostname+domain_name, - network=first_nonfull_ipv4_network) + network=first_nonfull_ipv4_network, + extattrs=extattrs) elif service_networks: network = service_networks.v4 assert any(network.subnet_of(ipv4_container) for ipv4_container in ipv4_containers) v4_host = _allocate_host(hostname=hostname+domain_name, - network=str(network)) + network=str(network), + extattrs=extattrs) elif host_addresses: addr = host_addresses.v4 assert any(addr in ipv4_container for ipv4_container in ipv4_containers) v4_host = _allocate_host(hostname=hostname+domain_name, - addr=str(addr)) + addr=str(addr), + extattrs=extattrs) # IPv6 if not service_networks and not host_addresses: @@ -396,19 +409,22 @@ def allocate_service_host(hostname=None, assert 'network' in ipv6_networks_info[0] # TODO: if "no available IP" error, create a new network? v6_host = _allocate_host(hostname=hostname+domain_name, - network=ipv6_networks_info[0]['network']) + network=ipv6_networks_info[0]['network'], + extattrs=extattrs) elif service_networks: network = service_networks.v6 assert any(network.subnet_of(ipv6_container) for ipv6_container in ipv6_containers) v6_host = _allocate_host(hostname=hostname+domain_name, - network=str(network)) + network=str(network), + extattrs=extattrs) elif host_addresses: addr = host_addresses.v6 assert any(addr in ipv6_container for ipv6_container in ipv6_containers) v6_host = _allocate_host(hostname=hostname+domain_name, - addr=str(addr)) + addr=str(addr), + extattrs=extattrs) return HostAddresses(v4=v4_host.v4, v6=v6_host.v6) diff --git a/gso/services/ipam.py b/gso/services/ipam.py index 5777d1d6..1c846be8 100644 --- a/gso/services/ipam.py +++ b/gso/services/ipam.py @@ -30,11 +30,11 @@ class HostAddresses(BaseSettings): v6: ipaddress.IPv6Address -def new_service_networks(service_type) -> ServiceNetworks: +def new_service_networks(service_type, extattrs={}) -> ServiceNetworks: v4_service_network = _ipam.allocate_service_ipv4_network( - service_type=service_type) + service_type=service_type, extattrs=extattrs) v6_service_network = _ipam.allocate_service_ipv6_network( - service_type=service_type) + service_type=service_type, extattrs=extattrs) return ServiceNetworks( v4=v4_service_network.v4, v6=v6_service_network.v6) @@ -42,32 +42,58 @@ def new_service_networks(service_type) -> ServiceNetworks: def new_service_host(hostname, service_type, - service_networks: ServiceNetworks) -> HostAddresses: + service_networks: ServiceNetworks = None, + host_addresses: HostAddresses = None, + extattrs={}) -> HostAddresses: return _ipam.allocate_service_host( hostname=hostname, service_type=service_type, - service_networks=service_networks) + service_networks=service_networks, + host_addresses=host_addresses, + extattrs=extattrs) if __name__ == '__main__': # sample call flow to allocate two loopback interfaces and a trunk service # new_service_host can be called passing networks or addresses - # - the first time is called by passing a specific ipv4/ipv6 address pair - # - the rest are called by passing the ipv4/ipv6 network pair + # - host h1 for service LO uses a specific ipv4/ipv6 address pair + # - the rest use the ipv4/ipv6 network pair + # networks and hosts can be allocated with extensible attributes + # - host h2 for service LO uses extattrs for both network and address/DNS + # - the rest don't use extattrs - hostname_A = 'newhost1' - hostname_B = 'newhost2' - # h1 loopback - lo1_service_networks = new_service_networks('LO') + hostname_A = 'h1' + hostname_B = 'h2' + + # h1 LO (loopback) + lo1_service_networks = new_service_networks(service_type='LO') lo1_v4_host_address = lo1_service_networks.v4.network_address lo1_v6_host_address = lo1_service_networks.v6.network_address lo1_host_addresses = HostAddresses(v4=lo1_v4_host_address, v6=lo1_v6_host_address) - new_service_host(hostname_A, 'LO', lo1_service_networks) - # h2 loopback - lo2_service_networks = new_service_networks('LO') - new_service_host(hostname_B, 'LO', lo2_service_networks) - # h1-h2 trunk - trunk12_service_networks = new_service_networks('TRUNK') - new_service_host(hostname_A, 'TRUNK', trunk12_service_networks) - new_service_host(hostname_B, 'TRUNK', trunk12_service_networks) + new_service_host(hostname=hostname_A, + service_type='LO', + host_addresses=lo1_host_addresses) + + # h2 LO (loopback) + lo2_network_extattrs = { + "vrf_name": {"value": "dummy_vrf"}, + } + lo2_host_extattrs = { + "Site": {"value": "dummy_site"}, + } + lo2_service_networks = \ + new_service_networks(service_type='LO', extattrs=lo2_network_extattrs) + new_service_host(hostname=hostname_B, + service_type='LO', + service_networks=lo2_service_networks, + extattrs=lo2_host_extattrs) + + # h1-h2 TRUNK + trunk12_service_networks = new_service_networks(service_type='TRUNK') + new_service_host(hostname=hostname_A, + service_type='TRUNK', + service_networks=trunk12_service_networks) + new_service_host(hostname=hostname_B, + service_type='TRUNK', + service_networks=trunk12_service_networks) -- GitLab