From 02d113be4c7d81f529dbab6ac8b436f62db7c905 Mon Sep 17 00:00:00 2001
From: Ubuntu <jorge.sasiain@ehu.eus>
Date: Wed, 3 May 2023 12:37:06 +0000
Subject: [PATCH] NAT-152: support allocate host by specific ip address in a
 specific service container

---
 gso/services/_ipam.py | 23 ++++++++++++++++-------
 gso/services/ipam.py  | 11 +++++++++++
 2 files changed, 27 insertions(+), 7 deletions(-)

diff --git a/gso/services/_ipam.py b/gso/services/_ipam.py
index d3478337..08f54c1b 100644
--- a/gso/services/_ipam.py
+++ b/gso/services/_ipam.py
@@ -290,13 +290,14 @@ def _allocate_host(hostname=None, addr=None, network=None) -> Union[V4HostAddres
     else:
         return V6HostAddress(v6=addr)
 
-def allocate_service_host(hostname=None, service_type=None, service_networks: ServiceNetworks = None) -> HostAddresses:
+def allocate_service_host(hostname=None, service_type=None, service_networks: ServiceNetworks = None, host_addresses: HostAddresses = None) -> HostAddresses:
     """
     Allocate host with both IPv4 and IPv6 address (and respective DNS records).
     The domain name is also taken from the service type and appended to specified hostname.
     If service_networks is provided, that one is used.
-    If service_networks is not provided, the first network with available space for this service type is used.
-        Note that if WFO will always specify the network after creating it, this mode won't be needed.
+    If service_networks is not provided, and host_addresses is provided, those specific addresses are used.
+    If neither is not provided, the first network with available space for this service type is used.
+        Note that if WFO will always specify the network/addresses after creating it, this mode won't be needed.
     """
     oss = settings.load_oss_params()
     assert oss.IPAM
@@ -308,7 +309,7 @@ def allocate_service_host(hostname=None, service_type=None, service_networks: Se
     domain_name = getattr(ipam_params, service_type).domain_name
 
     # IPv4
-    if not service_networks:
+    if not service_networks and not host_addresses:
         ipv4_networks_info = _find_networks(network_container=str(ipv4_container), ip_version=4)
         assert len(ipv4_networks_info) >= 1, "No IPv4 network exists in the container for this service type."
         first_nonfull_ipv4_network = None
@@ -323,13 +324,17 @@ def allocate_service_host(hostname=None, service_type=None, service_networks: Se
             first_nonfull_ipv4_network = str(allocate_service_ipv4_network(service_type=service_type).v4)
         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)
-    else:
+    elif service_networks:
         network = service_networks.v4
         assert network.subnet_of(ipv4_container)
         v4_host = _allocate_host(hostname=hostname+domain_name, network=str(network))
+    elif host_addresses:
+        addr = host_addresses.v4
+        assert addr in ipv4_container
+        v4_host = _allocate_host(hostname=hostname+domain_name, addr=str(addr))
 
     # IPv6
-    if not service_networks:
+    if not service_networks and not host_addresses:
         # ipv6 does not support capacity fetching (not even the GUI displays it).
         # Maybe it's assumed that there is always available space?
         ipv6_networks_info = _find_networks(network_container=str(ipv6_container), ip_version=6)
@@ -337,10 +342,14 @@ def allocate_service_host(hostname=None, service_type=None, service_networks: Se
         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'])
-    else:
+    elif service_networks:
         network = service_networks.v6
         assert network.subnet_of(ipv6_container)
         v6_host = _allocate_host(hostname=hostname+domain_name, network=str(network))
+    elif host_addresses:
+        addr = host_addresses.v6
+        assert addr in ipv6_container
+        v6_host = _allocate_host(hostname=hostname+domain_name, addr=str(addr))
 
     return HostAddresses(v4=v4_host.v4,v6=v6_host.v6)
 
diff --git a/gso/services/ipam.py b/gso/services/ipam.py
index 723e112e..92e66a0e 100644
--- a/gso/services/ipam.py
+++ b/gso/services/ipam.py
@@ -44,12 +44,23 @@ def new_service_host(hostname, service_type, service_networks: ServiceNetworks)
 
 
 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
+
     hostname_A = 'newhost1'
     hostname_B = 'newhost2'
+    # h1 loopback
     lo1_service_networks = new_service_networks('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)
\ No newline at end of file
-- 
GitLab