Skip to content
Snippets Groups Projects
Commit 7e8a8f18 authored by JORGE SASIAIN's avatar JORGE SASIAIN
Browse files

NAT-152: support list of containers oer service

parent 02d113be
No related branches found
No related tags found
1 merge request!9Ipam service
...@@ -9,18 +9,18 @@ ...@@ -9,18 +9,18 @@
"password": "robot-user-password" "password": "robot-user-password"
}, },
"LO": { "LO": {
"V4": {"container": "1.1.0.0/24", "mask": 31}, "V4": {"containers": ["1.1.0.0/24"], "mask": 32},
"V6": {"container": "dead:beef::/64", "mask": 126}, "V6": {"containers": ["dead:beef::/64"], "mask": 128},
"domain_name": ".lo" "domain_name": ".lo"
}, },
"TRUNK": { "TRUNK": {
"V4": {"container": "1.1.1.0/24", "mask": 31}, "V4": {"containers": ["1.1.1.0/24"], "mask": 31},
"V6": {"container": "dead:beef::/64", "mask": 126}, "V6": {"containers": ["dead:beef::/64"], "mask": 126},
"domain_name": ".trunk" "domain_name": ".trunk"
}, },
"GEANT_IP": { "GEANT_IP": {
"V4": {"container": "1.1.2.0/24", "mask": 31}, "V4": {"containers": ["1.1.2.0/24"], "mask": 31},
"V6": {"container": "dead:beef::/64", "mask": 126}, "V6": {"containers": ["dead:beef::/64"], "mask": 126},
"domain_name": ".geantip" "domain_name": ".geantip"
} }
} }
......
import requests
from requests.auth import HTTPBasicAuth
import json
import ipaddress import ipaddress
import json
import random
import requests
from enum import Enum
from pydantic import BaseSettings from pydantic import BaseSettings
from requests.auth import HTTPBasicAuth
from typing import Union from typing import Union
import random
from gso import settings from gso import settings
...@@ -35,12 +36,19 @@ class HostAddresses(BaseSettings): ...@@ -35,12 +36,19 @@ class HostAddresses(BaseSettings):
v6: ipaddress.IPv6Address v6: ipaddress.IPv6Address
class IPAMErrors(Enum):
# HTTP error code, match in error message
CONTAINER_FULL = 400, "Can not find requested number of networks"
# TODO: remove this! # TODO: remove this!
# lab infoblox cert is not valid for the ipv4 address # lab infoblox cert is not valid for the ipv4 address
# ... disable warnings for now # ... disable warnings for now
requests.packages.urllib3.disable_warnings() requests.packages.urllib3.disable_warnings()
def _match_error_code(response, error_code):
return response.status_code == error_code.value[0] and error_code.value[1] in response.text
def _wapi(infoblox_params: settings.InfoBloxParams): def _wapi(infoblox_params: settings.InfoBloxParams):
return (f'https://{infoblox_params.host}' return (f'https://{infoblox_params.host}'
f'/wapi/{infoblox_params.wapi_version}') f'/wapi/{infoblox_params.wapi_version}')
...@@ -68,7 +76,7 @@ def _ip_network_version(network): ...@@ -68,7 +76,7 @@ def _ip_network_version(network):
def _find_networks(network_container=None, network=None, ip_version=4): def _find_networks(network_container=None, network=None, ip_version=4):
""" """
If network_container is not None, find all networks within the specified container. If network_container is not None, find all networks within the specified container.
Otherwise if network is not None, find the specified network. Otherwise, if network is not None, find the specified network.
Otherwise find all networks. Otherwise find all networks.
""" """
assert ip_version in [4, 6] assert ip_version in [4, 6]
...@@ -121,46 +129,40 @@ def _get_network_capacity(network=None): ...@@ -121,46 +129,40 @@ def _get_network_capacity(network=None):
def _allocate_network(infoblox_params: settings.InfoBloxParams, network_params: Union[settings.V4NetworkParams, settings.V6NetworkParams], ip_version=4) -> Union[V4ServiceNetwork, V6ServiceNetwork]: def _allocate_network(infoblox_params: settings.InfoBloxParams, network_params: Union[settings.V4NetworkParams, settings.V6NetworkParams], ip_version=4) -> Union[V4ServiceNetwork, V6ServiceNetwork]:
assert ip_version in [4, 6] assert ip_version in [4, 6]
endpoint = 'network' if ip_version == 4 else 'ipv6network' endpoint = 'network' if ip_version == 4 else 'ipv6network'
ip_container = 'networkcontainer' if ip_version == 4 else 'ipv6networkcontainer'
ipv4_req_payload = { req_payload = {
"network": { "network": {
"_object_function": "next_available_network", "_object_function": "next_available_network",
"_parameters": { "_parameters": {
"cidr": network_params.mask "cidr": network_params.mask
}, },
"_object": "networkcontainer", "_object": ip_container,
"_object_parameters": { "_object_parameters": {
"network": str(network_params.container) "network": str(network_params.containers[0])
}, },
"_result_field": "networks", # only return in the response the allocated network, not all available "_result_field": "networks", # only return in the response the allocated network, not all available
} }
} }
ipv6_req_payload = { container_index = 0
"network": { while True:
"_object_function": "next_available_network", r = requests.post(
"_parameters": { f'{_wapi(infoblox_params)}/{endpoint}',
"cidr": network_params.mask params={'_return_fields': 'network'},
}, json=req_payload,
"_object": "ipv6networkcontainer", auth=HTTPBasicAuth(infoblox_params.username, infoblox_params.password),
"_object_parameters": { headers={'content-type': "application/json"},
"network": str(network_params.container) verify=False
}, )
"_result_field": "networks", # only return in the response the allocated network, not all available if not _match_error_code(response=r, error_code=IPAMErrors.CONTAINER_FULL):
} break
} # Container full: try with next valid container for the service (if any)
container_index += 1
req_payload = ipv4_req_payload if ip_version == 4 else ipv6_req_payload if len(network_params.containers) < (container_index + 1):
break
req_payload["network"]["_object_parameters"]["network"] = str(network_params.containers[container_index])
r = requests.post(
f'{_wapi(infoblox_params)}/{endpoint}',
params={'_return_fields': 'network'},
json=req_payload,
auth=HTTPBasicAuth(infoblox_params.username, infoblox_params.password),
headers={'content-type': "application/json"},
verify=False
)
# TODO: handle no more available space for networks in the container
assert r.status_code >= 200 and r.status_code < 300, f"HTTP error {r.status_code}: {r.reason}\n\n{r.text}" assert r.status_code >= 200 and r.status_code < 300, f"HTTP error {r.status_code}: {r.reason}\n\n{r.text}"
assert 'network' in r.json() assert 'network' in r.json()
...@@ -226,10 +228,10 @@ def _allocate_host(hostname=None, addr=None, network=None) -> Union[V4HostAddres ...@@ -226,10 +228,10 @@ def _allocate_host(hostname=None, addr=None, network=None) -> Union[V4HostAddres
else: else:
ip_version = _ip_addr_version(addr) ip_version = _ip_addr_version(addr)
ipv4_req_payload = { ip_req_payload = {
"ipv4addrs": [ f"ipv{ip_version}addrs": [
{ {
"ipv4addr": addr f"ipv{ip_version}addr": addr
} }
], ],
"name": hostname, "name": hostname,
...@@ -237,19 +239,6 @@ def _allocate_host(hostname=None, addr=None, network=None) -> Union[V4HostAddres ...@@ -237,19 +239,6 @@ def _allocate_host(hostname=None, addr=None, network=None) -> Union[V4HostAddres
"view": "default" "view": "default"
} }
ipv6_req_payload = {
"ipv6addrs": [
{
"ipv6addr": addr
}
],
"name": hostname,
"configure_for_dns": False,
"view": "default"
}
ip_req_payload = ipv4_req_payload if ip_version == 4 else ipv6_req_payload
r = requests.post( r = requests.post(
f'{_wapi(infoblox_params)}/record:host', f'{_wapi(infoblox_params)}/record:host',
json=ip_req_payload, json=ip_req_payload,
...@@ -298,19 +287,20 @@ def allocate_service_host(hostname=None, service_type=None, service_networks: Se ...@@ -298,19 +287,20 @@ def allocate_service_host(hostname=None, service_type=None, service_networks: Se
If service_networks is not provided, and host_addresses is provided, those specific addresses are used. 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. 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. Note that if WFO will always specify the network/addresses after creating it, this mode won't be needed.
Currently this mode doesn't look further than the first container, so if needed, this will need to be updated.
""" """
oss = settings.load_oss_params() oss = settings.load_oss_params()
assert oss.IPAM assert oss.IPAM
ipam_params = oss.IPAM ipam_params = oss.IPAM
assert hasattr(ipam_params, service_type) and service_type != 'INFOBLOX', "Invalid service type." assert hasattr(ipam_params, service_type) and service_type != 'INFOBLOX', "Invalid service type."
ipv4_container = getattr(ipam_params, service_type).V4.container ipv4_containers = getattr(ipam_params, service_type).V4.containers
ipv6_container = getattr(ipam_params, service_type).V6.container ipv6_containers = getattr(ipam_params, service_type).V6.containers
domain_name = getattr(ipam_params, service_type).domain_name domain_name = getattr(ipam_params, service_type).domain_name
# IPv4 # IPv4
if not service_networks and not host_addresses: if not service_networks and not host_addresses:
ipv4_networks_info = _find_networks(network_container=str(ipv4_container), ip_version=4) ipv4_networks_info = _find_networks(network_container=str(ipv4_containers[0]), ip_version=4)
assert len(ipv4_networks_info) >= 1, "No IPv4 network exists in the container for this service type." assert len(ipv4_networks_info) >= 1, "No IPv4 network exists in the container for this service type."
first_nonfull_ipv4_network = None first_nonfull_ipv4_network = None
for ipv4_network_info in ipv4_networks_info: for ipv4_network_info in ipv4_networks_info:
...@@ -326,29 +316,30 @@ def allocate_service_host(hostname=None, service_type=None, service_networks: Se ...@@ -326,29 +316,30 @@ def allocate_service_host(hostname=None, service_type=None, service_networks: Se
v4_host = _allocate_host(hostname=hostname+domain_name, network=first_nonfull_ipv4_network) v4_host = _allocate_host(hostname=hostname+domain_name, network=first_nonfull_ipv4_network)
elif service_networks: elif service_networks:
network = service_networks.v4 network = service_networks.v4
assert network.subnet_of(ipv4_container) assert any(network.subnet_of(ipv4_container) for ipv4_container in ipv4_containers)
v4_host = _allocate_host(hostname=hostname+domain_name, network=str(network)) v4_host = _allocate_host(hostname=hostname+domain_name, network=str(network))
elif host_addresses: elif host_addresses:
addr = host_addresses.v4 addr = host_addresses.v4
assert addr in ipv4_container assert any(addr in ipv4_container for ipv4_container in ipv4_containers)
v4_host = _allocate_host(hostname=hostname+domain_name, addr=str(addr)) v4_host = _allocate_host(hostname=hostname+domain_name, addr=str(addr))
# IPv6 # IPv6
if not service_networks and not host_addresses: if not service_networks and not host_addresses:
# ipv6 does not support capacity fetching (not even the GUI displays it). # ipv6 does not support capacity fetching (not even the GUI displays it).
# Maybe it's assumed that there is always available space? # Maybe it's assumed that there is always available space?
ipv6_networks_info = _find_networks(network_container=str(ipv6_container), ip_version=6) ipv6_networks_info = _find_networks(network_container=str(ipv6_containers[0]), ip_version=6)
assert len(ipv6_networks_info) >= 1, "No IPv6 network exists in the container for this service type." assert len(ipv6_networks_info) >= 1, "No IPv6 network exists in the container for this service type."
assert 'network' in ipv6_networks_info[0] assert 'network' in ipv6_networks_info[0]
# TODO: if "no available IP" error, create a new network? # TODO: if "no available IP" error, create a new network?
v6_host = _allocate_host(hostname=hostname+domain_name, network=ipv6_networks_info[0]['network']) v6_host = _allocate_host(hostname=hostname+domain_name, network=ipv6_networks_info[0]['network'])
elif service_networks: elif service_networks:
network = service_networks.v6 network = service_networks.v6
assert network.subnet_of(ipv6_container) assert any(network.subnet_of(ipv6_container) for ipv6_container in ipv6_containers)
v6_host = _allocate_host(hostname=hostname+domain_name, network=str(network)) v6_host = _allocate_host(hostname=hostname+domain_name, network=str(network))
elif host_addresses: elif host_addresses:
addr = host_addresses.v6 addr = host_addresses.v6
assert addr in ipv6_container assert any(addr in ipv6_container for ipv6_container in ipv6_containers)
v6_host = _allocate_host(hostname=hostname+domain_name, addr=str(addr)) v6_host = _allocate_host(hostname=hostname+domain_name, addr=str(addr))
return HostAddresses(v4=v4_host.v4,v6=v6_host.v6) return HostAddresses(v4=v4_host.v4,v6=v6_host.v6)
......
...@@ -13,12 +13,12 @@ class InfoBloxParams(BaseSettings): ...@@ -13,12 +13,12 @@ class InfoBloxParams(BaseSettings):
class V4NetworkParams(BaseSettings): class V4NetworkParams(BaseSettings):
container: ipaddress.IPv4Network containers: list[ipaddress.IPv4Network]
mask: int # TODO: validation on mask? mask: int # TODO: validation on mask?
class V6NetworkParams(BaseSettings): class V6NetworkParams(BaseSettings):
container: ipaddress.IPv6Network containers: list[ipaddress.IPv6Network]
mask: int # TODO: validation on mask? mask: int # TODO: validation on mask?
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment