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
Branches
Tags
1 merge request!9Ipam service
......@@ -9,18 +9,18 @@
"password": "robot-user-password"
},
"LO": {
"V4": {"container": "1.1.0.0/24", "mask": 31},
"V6": {"container": "dead:beef::/64", "mask": 126},
"V4": {"containers": ["1.1.0.0/24"], "mask": 32},
"V6": {"containers": ["dead:beef::/64"], "mask": 128},
"domain_name": ".lo"
},
"TRUNK": {
"V4": {"container": "1.1.1.0/24", "mask": 31},
"V6": {"container": "dead:beef::/64", "mask": 126},
"V4": {"containers": ["1.1.1.0/24"], "mask": 31},
"V6": {"containers": ["dead:beef::/64"], "mask": 126},
"domain_name": ".trunk"
},
"GEANT_IP": {
"V4": {"container": "1.1.2.0/24", "mask": 31},
"V6": {"container": "dead:beef::/64", "mask": 126},
"V4": {"containers": ["1.1.2.0/24"], "mask": 31},
"V6": {"containers": ["dead:beef::/64"], "mask": 126},
"domain_name": ".geantip"
}
}
......
import requests
from requests.auth import HTTPBasicAuth
import json
import ipaddress
import json
import random
import requests
from enum import Enum
from pydantic import BaseSettings
from requests.auth import HTTPBasicAuth
from typing import Union
import random
from gso import settings
......@@ -35,12 +36,19 @@ class HostAddresses(BaseSettings):
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!
# lab infoblox cert is not valid for the ipv4 address
# ... disable warnings for now
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):
return (f'https://{infoblox_params.host}'
f'/wapi/{infoblox_params.wapi_version}')
......@@ -68,7 +76,7 @@ def _ip_network_version(network):
def _find_networks(network_container=None, network=None, ip_version=4):
"""
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.
"""
assert ip_version in [4, 6]
......@@ -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]:
assert ip_version in [4, 6]
endpoint = 'network' if ip_version == 4 else 'ipv6network'
ip_container = 'networkcontainer' if ip_version == 4 else 'ipv6networkcontainer'
ipv4_req_payload = {
req_payload = {
"network": {
"_object_function": "next_available_network",
"_parameters": {
"cidr": network_params.mask
},
"_object": "networkcontainer",
"_object": ip_container,
"_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
}
}
ipv6_req_payload = {
"network": {
"_object_function": "next_available_network",
"_parameters": {
"cidr": network_params.mask
},
"_object": "ipv6networkcontainer",
"_object_parameters": {
"network": str(network_params.container)
},
"_result_field": "networks", # only return in the response the allocated network, not all available
}
}
req_payload = ipv4_req_payload if ip_version == 4 else ipv6_req_payload
container_index = 0
while True:
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
)
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
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 'network' in r.json()
......@@ -226,10 +228,10 @@ def _allocate_host(hostname=None, addr=None, network=None) -> Union[V4HostAddres
else:
ip_version = _ip_addr_version(addr)
ipv4_req_payload = {
"ipv4addrs": [
ip_req_payload = {
f"ipv{ip_version}addrs": [
{
"ipv4addr": addr
f"ipv{ip_version}addr": addr
}
],
"name": hostname,
......@@ -237,19 +239,6 @@ def _allocate_host(hostname=None, addr=None, network=None) -> Union[V4HostAddres
"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(
f'{_wapi(infoblox_params)}/record:host',
json=ip_req_payload,
......@@ -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 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.
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()
assert oss.IPAM
ipam_params = oss.IPAM
assert hasattr(ipam_params, service_type) and service_type != 'INFOBLOX', "Invalid service type."
ipv4_container = getattr(ipam_params, service_type).V4.container
ipv6_container = getattr(ipam_params, service_type).V6.container
ipv4_containers = getattr(ipam_params, service_type).V4.containers
ipv6_containers = getattr(ipam_params, service_type).V6.containers
domain_name = getattr(ipam_params, service_type).domain_name
# IPv4
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."
first_nonfull_ipv4_network = None
for ipv4_network_info in ipv4_networks_info:
......@@ -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)
elif service_networks:
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))
elif host_addresses:
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))
# IPv6
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)
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 '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'])
elif service_networks:
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))
elif host_addresses:
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))
return HostAddresses(v4=v4_host.v4,v6=v6_host.v6)
......
......@@ -13,12 +13,12 @@ class InfoBloxParams(BaseSettings):
class V4NetworkParams(BaseSettings):
container: ipaddress.IPv4Network
containers: list[ipaddress.IPv4Network]
mask: int # TODO: validation on mask?
class V6NetworkParams(BaseSettings):
container: ipaddress.IPv6Network
containers: list[ipaddress.IPv6Network]
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