Skip to content
GitLab
Explore
Sign in
Primary navigation
Search or go to…
Project
GÉANT Service Orchestrator
Manage
Activity
Members
Labels
Plan
Issues
Issue boards
Milestones
Wiki
Code
Merge requests
Repository
Branches
Commits
Tags
Repository graph
Compare revisions
Snippets
Build
Pipelines
Jobs
Pipeline schedules
Artifacts
Deploy
Releases
Package Registry
Container Registry
Model registry
Operate
Environments
Terraform modules
Monitor
Incidents
Analyze
Value stream analytics
Contributor analytics
CI/CD analytics
Repository analytics
Model experiments
Help
Help
Support
GitLab documentation
Compare GitLab plans
Community forum
Contribute to GitLab
Provide feedback
Keyboard shortcuts
?
Snippets
Groups
Projects
Show more breadcrumbs
GÉANT Orchestration and Automation Team
GAP
GÉANT Service Orchestrator
Commits
1c5f45a3
Commit
1c5f45a3
authored
1 year ago
by
JORGE SASIAIN
Browse files
Options
Downloads
Patches
Plain Diff
NAT-185: combine IPv4/IPv6/DNS in host record and update tests
parent
8e6ef232
No related branches found
Branches containing commit
No related tags found
Tags containing commit
2 merge requests
!27
Merge develop into NAT-185
,
!15
Nat 185
Changes
3
Hide whitespace changes
Inline
Side-by-side
Showing
3 changed files
gso/services/_ipam.py
+140
-152
140 additions, 152 deletions
gso/services/_ipam.py
gso/services/ipam.py
+4
-8
4 additions, 8 deletions
gso/services/ipam.py
test/test_ipam.py
+29
-2
29 additions, 2 deletions
test/test_ipam.py
with
173 additions
and
162 deletions
gso/services/_ipam.py
+
140
−
152
View file @
1c5f45a3
...
...
@@ -109,39 +109,6 @@ def _find_networks(network_container=None, network=None, ip_version=4):
return
r
.
json
()
def
_get_network_capacity
(
network
=
None
):
"""
Get utilization of a IPv4 network in a fraction of 1000.
"""
oss
=
settings
.
load_oss_params
()
assert
oss
.
IPAM
.
INFOBLOX
infoblox_params
=
oss
.
IPAM
.
INFOBLOX
ip_version
=
_ip_network_version
(
network
)
assert
ip_version
==
4
,
"
Utilization is only available for IPv4 networks.
"
params
=
{
'
network
'
:
network
,
'
_return_fields
'
:
'
network,total_hosts,utilization
'
}
r
=
requests
.
get
(
f
'
{
_wapi
(
infoblox_params
)
}
/network
'
,
params
=
params
,
auth
=
HTTPBasicAuth
(
infoblox_params
.
username
,
infoblox_params
.
password
),
verify
=
False
)
# Utilization info takes several minutes to converge.
# The IPAM utilization bar in the GUI as well. Why?
assert
r
.
status_code
>=
200
and
r
.
status_code
<
300
,
\
f
"
HTTP error
{
r
.
status_code
}
:
{
r
.
reason
}
\n\n
{
r
.
text
}
"
capacity_info
=
r
.
json
()
assert
len
(
capacity_info
)
==
1
,
"
Requested IPv4 network doesn
'
t exist.
"
assert
'
utilization
'
in
capacity_info
[
0
]
utilization
=
capacity_info
[
0
][
'
utilization
'
]
return
utilization
def
_allocate_network
(
infoblox_params
:
settings
.
InfoBloxParams
,
network_params
:
Union
[
settings
.
V4NetworkParams
,
settings
.
V6NetworkParams
],
...
...
@@ -254,42 +221,64 @@ def _find_next_available_ip(infoblox_params, network_ref):
return
received_ip
[
0
]
def
_allocate_host
(
hostname
=
None
,
addr
=
None
,
network
=
None
,
extattrs
=
{}
)
->
Union
[
V4HostAddress
,
V6HostAddress
]:
def
_allocate_host
(
hostname
=
None
,
addrs
=
None
,
networks
=
None
,
extattrs
=
{}
)
->
HostAddresses
:
"""
If network is not None, allocate host in th
at
network.
Otherwise if addr is not None, allocate host with th
at
address.
If network
s
is not None, allocate host in th
ose
network
s
.
Otherwise if addr
s
is not None, allocate host with th
ose
address
es
.
hostname parameter must be full name including domain name.
"""
# TODO: should hostnames be unique
# (i.e. fail if hostname already exists in this domain/service)?
assert
addr
or
network
,
\
"
You must specify either the host address or the network CIDR.
"
assert
addr
s
or
network
s
,
\
"
You must specify either the host address
es
or the network
s
CIDR.
"
oss
=
settings
.
load_oss_params
()
assert
oss
.
IPAM
.
INFOBLOX
infoblox_params
=
oss
.
IPAM
.
INFOBLOX
if
network
:
ip_version
=
_ip_network_version
(
network
)
# Find the next available IP address in the network
network_info
=
_find_networks
(
network
=
network
,
ip_version
=
ip_version
)
if
networks
:
ipv4_network
=
networks
[
0
]
ipv6_network
=
networks
[
1
]
assert
_ip_network_version
(
ipv4_network
)
==
4
assert
_ip_network_version
(
ipv6_network
)
==
6
# Find the next available IP address in each network
network_info
=
_find_networks
(
network
=
ipv4_network
,
ip_version
=
4
)
assert
len
(
network_info
)
==
1
,
\
"
IPv4 Network does not exist. Create it first.
"
assert
'
_ref
'
in
network_info
[
0
]
ipv4_addr
=
_find_next_available_ip
(
infoblox_params
,
network_info
[
0
][
"
_ref
"
])
network_info
=
_find_networks
(
network
=
ipv6_network
,
ip_version
=
6
)
assert
len
(
network_info
)
==
1
,
\
"
Network does not exist. Create it first.
"
"
IPv6
Network does not exist. Create it first.
"
assert
'
_ref
'
in
network_info
[
0
]
addr
=
_find_next_available_ip
(
infoblox_params
,
network_info
[
0
][
"
_ref
"
])
ipv6_
addr
=
_find_next_available_ip
(
infoblox_params
,
network_info
[
0
][
"
_ref
"
])
else
:
ip_version
=
_ip_addr_version
(
addr
)
ipv4_addr
=
addrs
[
0
]
ipv6_addr
=
addrs
[
1
]
assert
_ip_addr_version
(
ipv4_addr
)
==
4
assert
_ip_addr_version
(
ipv6_addr
)
==
6
ip_req_payload
=
{
f
"
ipv
{
ip_version
}
addrs
"
:
[
"
ipv
4
addrs
"
:
[
{
f
"
ipv
{
ip_version
}
addr
"
:
addr
"
ipv4addr
"
:
ipv4_addr
}
],
"
ipv6addrs
"
:
[
{
"
ipv6addr
"
:
ipv6_addr
}
],
"
name
"
:
hostname
,
"
configure_for_dns
"
:
Fals
e
,
"
configure_for_dns
"
:
Tru
e
,
"
view
"
:
"
default
"
,
"
extattrs
"
:
extattrs
}
...
...
@@ -306,31 +295,8 @@ def _allocate_host(hostname=None, addr=None, network=None, extattrs={}
assert
isinstance
(
r
.
json
(),
str
)
assert
r
.
json
().
startswith
(
"
record:host/
"
)
dns_req_payload
=
{
f
"
ipv
{
ip_version
}
addr
"
:
addr
,
"
name
"
:
hostname
,
"
view
"
:
"
default
"
,
"
extattrs
"
:
extattrs
}
endpoint
=
'
record:a
'
if
ip_version
==
4
else
'
record:aaaa
'
r
=
requests
.
post
(
f
'
{
_wapi
(
infoblox_params
)
}
/
{
endpoint
}
'
,
json
=
dns_req_payload
,
auth
=
HTTPBasicAuth
(
infoblox_params
.
username
,
infoblox_params
.
password
),
verify
=
False
)
assert
r
.
status_code
>=
200
and
r
.
status_code
<
300
,
\
f
"
HTTP error
{
r
.
status_code
}
:
{
r
.
reason
}
\n\n
{
r
.
text
}
"
assert
isinstance
(
r
.
json
(),
str
)
assert
r
.
json
().
startswith
(
f
"
{
endpoint
}
/
"
)
if
ip_version
==
4
:
return
V4HostAddress
(
v4
=
addr
)
else
:
return
V6HostAddress
(
v6
=
addr
)
return
HostAddresses
(
v4
=
ipaddress
.
ip_address
(
ipv4_addr
),
v6
=
ipaddress
.
ip_address
(
ipv6_addr
))
def
allocate_service_host
(
hostname
=
None
,
...
...
@@ -340,19 +306,16 @@ def allocate_service_host(hostname=None,
extattrs
=
{}
)
->
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, and host_addresses is provided,
Allocate host record with both IPv4 and IPv6 address, and respective DNS
A and AAAA records.
- If service_networks is provided, that one is 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.
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.
- If neither is not provided, new ipv4 and ipv6 networks are created and
those are used. Note that in this case extattrs is for the hosts and not
for the networks.
The domain name is taken from the service type and appended to the
specified hostname.
"""
oss
=
settings
.
load_oss_params
()
assert
oss
.
IPAM
...
...
@@ -364,74 +327,59 @@ def allocate_service_host(hostname=None,
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_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
:
assert
'
network
'
in
ipv4_network_info
capacity
=
_get_network_capacity
(
ipv4_network_info
[
"
network
"
])
if
capacity
<
1000
:
first_nonfull_ipv4_network
=
ipv4_network_info
[
"
network
"
]
break
# Create a new network if the existing networks in the container for
# the service type are all full.
if
not
first_nonfull_ipv4_network
:
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
,
extattrs
=
extattrs
)
# IPv4
ipv4_network
=
str
(
allocate_service_ipv4_network
(
service_type
=
service_type
).
v4
)
assert
ipv4_network
,
\
"
No available space for IPv4 networks for this service type.
"
# IPv6
ipv6_network
=
str
(
allocate_service_ipv6_network
(
service_type
=
service_type
).
v6
)
assert
ipv6_network
,
\
"
No available space for IPv6 networks for this service type.
"
network_tuple
=
(
ipv4_network
,
ipv6_network
)
host
=
_allocate_host
(
hostname
=
hostname
+
domain_name
,
networks
=
network_tuple
,
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
),
extattrs
=
extattrs
)
elif
host_addresses
:
addr
=
host_addresses
.
v4
assert
any
(
addr
in
ipv4_container
# IPv4
ipv4_network
=
service_networks
.
v4
assert
any
(
ipv4_network
.
subnet_of
(
ipv4_container
)
for
ipv4_container
in
ipv4_containers
)
v4_host
=
_allocate_host
(
hostname
=
hostname
+
domain_name
,
addr
=
str
(
addr
),
extattrs
=
extattrs
)
# 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_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
'
],
extattrs
=
extattrs
)
elif
service_networks
:
network
=
service_networks
.
v6
assert
any
(
network
.
subnet_of
(
ipv6_container
)
# IPv6
ipv6_network
=
service_networks
.
v6
assert
any
(
ipv6_network
.
subnet_of
(
ipv6_container
)
for
ipv6_container
in
ipv6_containers
)
v6_host
=
_allocate_host
(
hostname
=
hostname
+
domain_name
,
network
=
str
(
network
),
extattrs
=
extattrs
)
host
=
_allocate_host
(
hostname
=
hostname
+
domain_name
,
networks
=
(
str
(
ipv4_network
),
str
(
ipv6_network
)),
extattrs
=
extattrs
)
elif
host_addresses
:
addr
=
host_addresses
.
v6
assert
any
(
addr
in
ipv6_container
# IPv4
ipv4_addr
=
host_addresses
.
v4
assert
any
(
ipv4_addr
in
ipv4_container
for
ipv4_container
in
ipv4_containers
)
# IPv6
ipv6_addr
=
host_addresses
.
v6
assert
any
(
ipv6_addr
in
ipv6_container
for
ipv6_container
in
ipv6_containers
)
v6_host
=
_allocate_host
(
hostname
=
hostname
+
domain_name
,
addr
=
str
(
addr
),
extattrs
=
extattrs
)
return
HostAddresses
(
v4
=
v4_host
.
v4
,
v6
=
v6_host
.
v6
)
host
=
_allocate_host
(
hostname
=
hostname
+
domain_name
,
addrs
=
(
str
(
ipv4_addr
),
str
(
ipv6_addr
)),
extattrs
=
extattrs
)
return
host
"""
...
...
@@ -462,6 +410,39 @@ def _find_containers(network=None, ip_version=4):
return r.json()
def _get_network_capacity(network=None):
"""
Get utilization of a IPv4 network in a fraction of 1000.
"""
oss = settings.load_oss_params()
assert oss.IPAM.INFOBLOX
infoblox_params = oss.IPAM.INFOBLOX
ip_version = _ip_network_version(network)
assert ip_version == 4,
"
Utilization is only available for IPv4 networks.
"
params = {
'
network
'
: network,
'
_return_fields
'
:
'
network,total_hosts,utilization
'
}
r = requests.get(
f
'
{_wapi(infoblox_params)}/network
'
,
params=params,
auth=HTTPBasicAuth(infoblox_params.username,
infoblox_params.password),
verify=False
)
# Utilization info takes several minutes to converge.
# The IPAM utilization bar in the GUI as well. Why?
assert r.status_code >= 200 and r.status_code < 300, \
f
"
HTTP error {r.status_code}: {r.reason}
\n\n
{r.text}
"
capacity_info = r.json()
assert len(capacity_info) == 1,
"
Requested IPv4 network doesn
'
t exist.
"
assert
'
utilization
'
in capacity_info[0]
utilization = capacity_info[0][
'
utilization
'
]
return utilization
def _delete_network(network) -> Union[V4ServiceNetwork, V6ServiceNetwork]:
"""
Delete IPv4 or IPv6 network by CIDR.
...
...
@@ -642,15 +623,22 @@ if __name__ == '__main__':
elif choice ==
'
6
'
:
hostname = input(
"
Enter host name (full name w/ domain name):
"
)
addr = input(
"
Enter IP address to allocate:
"
)
alloc_ip = _allocate_host(hostname=hostname, addr=addr)
addrv4 = input(
"
Enter IPv4 address to allocate:
"
)
addrv6 = input(
"
Enter IPv6 address to allocate:
"
)
alloc_ip = _allocate_host(hostname=hostname,
addrs=(addrv4,addrv6))
print(json.dumps(str(alloc_ip), indent=2))
elif choice ==
'
7
'
:
hostname = input(
"
Enter host name (full name w/ domain name):
"
)
network = input(
"
Enter existing network to allocate from (CIDR notation):
"
)
alloc_ip = _allocate_host(hostname=hostname, network=network)
networkv4 = input(
"
Enter existing ipv4 network to use (CIDR notation):
"
)
networkv6 = input(
"
Enter existing ipv6 network to use (CIDR notation):
"
)
alloc_ip = _allocate_host(
hostname=hostname,
networks=(networkv4, networkv6)
)
print(json.dumps(str(alloc_ip), indent=2))
elif choice ==
'
8
'
:
...
...
This diff is collapsed.
Click to expand it.
gso/services/ipam.py
+
4
−
8
View file @
1c5f45a3
...
...
@@ -55,7 +55,6 @@ def new_service_host(hostname,
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
...
...
@@ -75,11 +74,9 @@ if __name__ == '__main__':
)
lo1_v4_host_address
=
lo1_service_networks
.
v4
.
network_address
lo1_v6_host_address
=
lo1_service_networks
.
v6
.
network_address
print(lo1_v4_host_address)
print(lo1_v6_host_address)
lo1_host_addresses
=
HostAddresses
(
v4
=
lo1_v4_host_address
,
v6
=
lo1_v6_host_address
)
new_service_host(hostname=hostname_A,
new_service_host
(
hostname
=
hostname_A
+
"
_LO
"
,
service_type
=
'
LO
'
,
host_addresses
=
lo1_host_addresses
)
...
...
@@ -94,7 +91,7 @@ if __name__ == '__main__':
new_service_networks
(
service_type
=
'
LO
'
,
comment
=
"
Network for h2 LO
"
,
extattrs
=
lo2_network_extattrs
)
new_service_host(hostname=hostname_B,
new_service_host
(
hostname
=
hostname_B
+
"
_LO
"
,
service_type
=
'
LO
'
,
service_networks
=
lo2_service_networks
,
extattrs
=
lo2_host_extattrs
)
...
...
@@ -104,10 +101,9 @@ if __name__ == '__main__':
service_type
=
'
TRUNK
'
,
comment
=
"
Network for h1-h2 TRUNK
"
)
new_service_host(hostname=hostname_A,
new_service_host
(
hostname
=
hostname_A
+
"
_TRUNK
"
,
service_type
=
'
TRUNK
'
,
service_networks
=
trunk12_service_networks
)
new_service_host(hostname=hostname_B,
new_service_host
(
hostname
=
hostname_B
+
"
_TRUNK
"
,
service_type
=
'
TRUNK
'
,
service_networks
=
trunk12_service_networks
)
'''
This diff is collapsed.
Click to expand it.
test/test_ipam.py
+
29
−
2
View file @
1c5f45a3
...
...
@@ -81,16 +81,34 @@ def test_new_service_host(data_config_filename):
responses
.
add
(
method
=
responses
.
POST
,
url
=
re
.
compile
(
r
'
.*/wapi.*/network.*/.*?_function=next_available_ip&num=1
.*
'
),
# noqa: E501
url
=
re
.
compile
(
r
'
.*/wapi.*/network.*/.*?_function=next_available_ip&num=1
$
'
),
# noqa: E501
json
=
{
'
ips
'
:
[
'
10.255.255.20
'
]}
)
responses
.
add
(
method
=
responses
.
POST
,
url
=
re
.
compile
(
r
'
.*/wapi.*/ipv6network.*/.*?_function=next_available_ip&num=1
.*
'
),
# noqa: E501
url
=
re
.
compile
(
r
'
.*/wapi.*/ipv6network.*/.*?_function=next_available_ip&num=1
$
'
),
# noqa: E501
json
=
{
'
ips
'
:
[
'
dead:beef::18
'
]}
)
responses
.
add
(
method
=
responses
.
POST
,
url
=
re
.
compile
(
r
'
.*/wapi.*/network.*_return_fields.*
'
),
json
=
{
'
_ref
'
:
'
network/ZG5zLm5ldHdvcmskMTAuMjU1LjI1NS4yMC8zMi8w:10.255.255.20/32/default
'
,
# noqa: E501
'
network
'
:
'
10.255.255.20/32
'
}
)
responses
.
add
(
method
=
responses
.
POST
,
url
=
re
.
compile
(
r
'
.*/wapi.*/ipv6network.*_return_fields.*
'
),
json
=
{
'
_ref
'
:
'
ipv6network/ZG5zLm5ldHdvcmskZGVhZDpiZWVmOjoxOC8xMjgvMA:dead%3Abeef%3A%3A18/128/default
'
,
# noqa: E501
'
network
'
:
'
dead:beef::18/128
'
}
)
service_hosts
=
ipam
.
new_service_host
(
hostname
=
'
test
'
,
service_type
=
'
LO
'
,
...
...
@@ -116,3 +134,12 @@ def test_new_service_host(data_config_filename):
v4
=
ipaddress
.
ip_address
(
'
10.255.255.20
'
),
v6
=
ipaddress
.
ip_address
(
'
dead:beef::18
'
)
)
service_hosts
=
ipam
.
new_service_host
(
hostname
=
'
test
'
,
service_type
=
'
LO
'
)
assert
service_hosts
==
ipam
.
HostAddresses
(
v4
=
ipaddress
.
ip_address
(
'
10.255.255.20
'
),
v6
=
ipaddress
.
ip_address
(
'
dead:beef::18
'
)
)
This diff is collapsed.
Click to expand it.
Preview
0%
Loading
Try again
or
attach a new file
.
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Save comment
Cancel
Please
register
or
sign in
to comment