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
b980ff4a
Commit
b980ff4a
authored
1 year ago
by
Karel van Klink
Browse files
Options
Downloads
Patches
Plain Diff
Update infoblox service and add IPAM step to migrate IP trunk workflow
parent
ac9b297a
No related branches found
Branches containing commit
No related tags found
Tags containing commit
1 merge request
!135
Feature/trunk migration ipam
Changes
2
Hide whitespace changes
Inline
Side-by-side
Showing
2 changed files
gso/services/infoblox.py
+52
-16
52 additions, 16 deletions
gso/services/infoblox.py
gso/workflows/iptrunk/migrate_iptrunk.py
+67
-43
67 additions, 43 deletions
gso/workflows/iptrunk/migrate_iptrunk.py
with
119 additions
and
59 deletions
gso/services/infoblox.py
+
52
−
16
View file @
b980ff4a
...
...
@@ -12,6 +12,7 @@ from infoblox_client.exceptions import (
from
gso.settings
import
IPAMParams
,
load_oss_params
logger
=
getLogger
(
__name__
)
NULL_MAC
=
"
00:00:00:00:00:00
"
class
AllocationError
(
Exception
):
...
...
@@ -40,11 +41,7 @@ def _setup_connection() -> tuple[connector.Connector, IPAMParams]:
def
_allocate_network
(
conn
:
connector
.
Connector
,
dns_view
:
str
,
netmask
:
int
,
containers
:
list
[
str
],
comment
:
str
|
None
=
""
,
conn
:
connector
.
Connector
,
dns_view
:
str
,
netmask
:
int
,
containers
:
list
[
str
],
comment
:
str
|
None
=
""
)
->
ipaddress
.
IPv4Network
|
ipaddress
.
IPv6Network
:
"""
Allocate a new network in Infoblox.
...
...
@@ -160,10 +157,7 @@ def delete_network(ip_network: ipaddress.IPv4Network | ipaddress.IPv6Network) ->
def
allocate_host
(
hostname
:
str
,
service_type
:
str
,
cname_aliases
:
list
[
str
],
comment
:
str
,
hostname
:
str
,
service_type
:
str
,
cname_aliases
:
list
[
str
],
comment
:
str
)
->
tuple
[
ipaddress
.
IPv4Address
,
ipaddress
.
IPv6Address
]:
"""
Allocate a new host record in Infoblox.
...
...
@@ -194,7 +188,7 @@ def allocate_host(
created_v6
=
None
for
ipv6_range
in
allocation_networks_v6
:
v6_alloc
=
objects
.
IPAllocation
.
next_available_ip_from_cidr
(
dns_view
,
str
(
ipv6_range
))
ipv6_object
=
objects
.
IP
.
create
(
ip
=
v6_alloc
,
mac
=
"
00:00:00:00:00:00
"
,
configure_for_dhcp
=
False
)
ipv6_object
=
objects
.
IP
.
create
(
ip
=
v6_alloc
,
mac
=
NULL_MAC
,
configure_for_dhcp
=
False
)
try
:
new_host
=
objects
.
HostRecord
.
create
(
conn
,
...
...
@@ -216,7 +210,7 @@ def allocate_host(
created_v4
=
None
for
ipv4_range
in
allocation_networks_v4
:
v4_alloc
=
objects
.
IPAllocation
.
next_available_ip_from_cidr
(
dns_view
,
str
(
ipv4_range
))
ipv4_object
=
objects
.
IP
.
create
(
ip
=
v4_alloc
,
mac
=
"
00:00:00:00:00:00
"
,
configure_for_dhcp
=
False
)
ipv4_object
=
objects
.
IP
.
create
(
ip
=
v4_alloc
,
mac
=
NULL_MAC
,
configure_for_dhcp
=
False
)
new_host
=
objects
.
HostRecord
.
search
(
conn
,
name
=
hostname
)
new_host
.
ipv4addrs
=
[
ipv4_object
]
try
:
...
...
@@ -234,9 +228,39 @@ def allocate_host(
return
created_v4
,
created_v6
def
find_host_by_ip
(
ip_addr
:
ipaddress
.
IPv4Address
|
ipaddress
.
IPv6Address
,
)
->
objects
.
HostRecord
|
None
:
def
create_host_by_ip
(
hostname
:
str
,
ipv4_address
:
ipaddress
.
IPv4Address
,
ipv6_address
:
ipaddress
.
IPv6Address
,
service_type
:
str
,
comment
:
str
,
)
->
None
:
"""
Create a new host record with a given IPv4 and IPv6 address.
:param str hostname: The :term:`FQDN` of the new host.
:param IPv4Address ipv4_address: The IPv4 address of the new host.
:param IPv6Address ipv6_address: The IPv6 address of the new host.
:param str service_type: The relevant service type, used to deduce the correct ``dns_view`` in Infoblox.
:param str comment: The comment stored in this Infoblox record, most likely the relevant ``subscription_id`` in
:term:`GSO`.
"""
if
not
hostname_available
(
hostname
):
msg
=
f
"
Cannot allocate new host, FQDN
{
hostname
}
already taken.
"
raise
AllocationError
(
msg
)
conn
,
oss
=
_setup_connection
()
ipv6_object
=
objects
.
IP
.
create
(
ip
=
ipv6_address
,
mac
=
NULL_MAC
,
configure_for_dhcp
=
False
)
ipv4_object
=
objects
.
IP
.
create
(
ip
=
ipv4_address
,
mac
=
NULL_MAC
,
configure_for_dhcp
=
False
)
dns_view
=
getattr
(
oss
,
service_type
).
dns_view
# This needs to be done in two steps, otherwise only one of the IP addresses is stored.
objects
.
HostRecord
.
create
(
conn
,
ip
=
ipv6_object
,
name
=
hostname
,
comment
=
comment
,
dns_view
=
dns_view
)
new_host
=
find_host_by_fqdn
(
hostname
)
new_host
.
ipv4addrs
=
[
ipv4_object
]
new_host
.
update
()
def
find_host_by_ip
(
ip_addr
:
ipaddress
.
IPv4Address
|
ipaddress
.
IPv6Address
)
->
objects
.
HostRecord
|
None
:
"""
Find a host record in Infoblox by its associated IP address.
:param ip_addr: The IP address of a host that is searched for.
...
...
@@ -249,14 +273,14 @@ def find_host_by_ip(
ipv4addr
=
ip_addr
,
return_fields
=
[
"
ipv4addrs
"
,
"
name
"
,
"
view
"
,
"
aliases
"
,
"
comment
"
],
)
return
objects
.
HostRecord
.
search
(
return
objects
.
HostRecord
V6
.
search
(
conn
,
ipv6addr
=
ip_addr
,
return_fields
=
[
"
ipv6addrs
"
,
"
name
"
,
"
view
"
,
"
aliases
"
,
"
comment
"
],
)
def
find_host_by_fqdn
(
fqdn
:
str
)
->
objects
.
HostRecord
|
None
:
def
find_host_by_fqdn
(
fqdn
:
str
)
->
objects
.
HostRecord
:
"""
Find a host record by its associated :term:`FQDN`.
:param fqdn: The :term:`FQDN` of a host that is searched for.
...
...
@@ -270,6 +294,18 @@ def find_host_by_fqdn(fqdn: str) -> objects.HostRecord | None:
)
def
find_v6_host_by_fqdn
(
fqdn
:
str
)
->
objects
.
HostRecordV6
:
"""
Find a host record by its associated :term:`FQDN`.
This specific method will return the IPv6 variant of a record, if it exists.
:param str fqdn: The :term:`FQDN` of a host that is searched for.
"""
conn
,
_
=
_setup_connection
()
return
objects
.
HostRecordV6
.
search
(
conn
,
name
=
fqdn
,
return_fields
=
[
"
ipv6addrs
"
,
"
name
"
,
"
view
"
,
"
aliases
"
,
"
comment
"
]
)
def
delete_host_by_ip
(
ip_addr
:
ipaddress
.
IPv4Address
|
ipaddress
.
IPv6Address
)
->
None
:
"""
Delete a host from Infoblox.
...
...
This diff is collapsed.
Click to expand it.
gso/workflows/iptrunk/migrate_iptrunk.py
+
67
−
43
View file @
b980ff4a
...
...
@@ -16,6 +16,7 @@ from orchestrator.forms import FormPage
from
orchestrator.forms.validators
import
Choice
,
Label
,
UniqueConstrainedList
from
orchestrator.targets
import
Target
from
orchestrator.types
import
FormGenerator
,
State
,
UUIDstr
from
orchestrator.utils.errors
import
ProcessFailureError
from
orchestrator.utils.json
import
json_dumps
from
orchestrator.workflow
import
StepList
,
conditional
,
done
,
init
,
inputstep
from
orchestrator.workflows.steps
import
resync
,
store_process_subscription
,
unsync
...
...
@@ -23,11 +24,13 @@ from orchestrator.workflows.utils import wrap_modify_initial_input_form
from
pydantic
import
validator
from
pydantic_forms.core
import
ReadOnlyField
from
pynetbox.models.dcim
import
Interfaces
from
services.infoblox
import
DeletionError
from
gso.products.product_blocks.iptrunk
import
IptrunkInterfaceBlock
from
gso.products.product_blocks.router
import
RouterVendor
from
gso.products.product_types.iptrunk
import
Iptrunk
from
gso.products.product_types.router
import
Router
from
gso.services
import
infoblox
from
gso.services.netbox_client
import
NetboxClient
from
gso.services.provisioning_proxy
import
execute_playbook
,
pp_interaction
from
gso.services.subscriptions
import
get_active_router_subscriptions
...
...
@@ -167,6 +170,50 @@ def initial_input_form_generator(subscription_id: UUIDstr) -> FormGenerator:
)
@step
(
"
Netbox: Reserve new interfaces
"
)
def
netbox_reserve_interfaces
(
subscription
:
Iptrunk
,
new_node
:
UUIDstr
,
new_lag_interface
:
str
,
new_lag_member_interfaces
:
list
[
dict
]
)
->
State
:
"""
Reserve new interfaces in Netbox, only when the new side
'
s router is a NOKIA router.
"""
new_side
=
Router
.
from_subscription
(
new_node
).
router
nbclient
=
NetboxClient
()
# Create :term:`LAG` interfaces
lag_interface
:
Interfaces
=
nbclient
.
create_interface
(
iface_name
=
new_lag_interface
,
interface_type
=
"
lag
"
,
device_name
=
new_side
.
router_fqdn
,
description
=
str
(
subscription
.
subscription_id
),
enabled
=
True
,
)
# Attach physical interfaces to :term:`LAG`
# Reserve interfaces
for
interface
in
new_lag_member_interfaces
:
nbclient
.
attach_interface_to_lag
(
device_name
=
new_side
.
router_fqdn
,
lag_name
=
lag_interface
.
name
,
iface_name
=
interface
[
"
interface_name
"
],
description
=
str
(
subscription
.
subscription_id
),
)
nbclient
.
reserve_interface
(
device_name
=
new_side
.
router_fqdn
,
iface_name
=
interface
[
"
interface_name
"
],
)
return
{
"
subscription
"
:
subscription
}
@step
(
"
Calculate old side data
"
)
def
calculate_old_side_data
(
subscription
:
Iptrunk
,
replace_index
:
int
)
->
State
:
"""
Store subscription information of the old side in the state of the workflow for later use.
"""
old_subscription
=
copy
.
deepcopy
(
subscription
)
old_side_data
=
{
"
iptrunk_side_node
"
:
old_subscription
.
iptrunk
.
iptrunk_sides
[
replace_index
].
iptrunk_side_node
,
"
iptrunk_side_ae_iface
"
:
old_subscription
.
iptrunk
.
iptrunk_sides
[
replace_index
].
iptrunk_side_ae_iface
,
"
iptrunk_side_ae_members
"
:
old_subscription
.
iptrunk
.
iptrunk_sides
[
replace_index
].
iptrunk_side_ae_members
,
}
return
{
"
old_side_data
"
:
old_side_data
}
@step
(
"
[DRY RUN] Disable configuration on old router
"
)
def
disable_old_config_dry
(
subscription
:
Iptrunk
,
...
...
@@ -488,12 +535,28 @@ def delete_old_config_real(
return
{
"
subscription
"
:
subscription
}
@step
(
"
Update IPAM
"
)
def
update_ipam
(
subscription
:
Iptrunk
)
->
State
:
@step
(
"
Update
IP records in
IPAM
"
)
def
update_ipam
(
subscription
:
Iptrunk
,
old_side_data
:
dict
,
new_node
:
Router
,
new_lag_interface
:
str
)
->
State
:
"""
Update :term:`IPAM` resources.
TODO: implement
Move the DNS record pointing to the old side of the trunk, to the new side.
"""
old_fqdn
=
f
"
{
old_side_data
[
'
iptrunk_side_ae_iface
'
]
}
.
{
old_side_data
[
'
iptrunk_side_node
'
][
'
router_fqdn
'
]
}
"
trunk_v4
=
infoblox
.
find_host_by_fqdn
(
old_fqdn
)
trunk_v6
=
infoblox
.
find_v6_host_by_fqdn
(
old_fqdn
)
# Out with the old
try
:
infoblox
.
delete_host_by_fqdn
(
old_fqdn
)
except
DeletionError
as
e
:
msg
=
"
Failed to delete record from Infoblox.
"
raise
ProcessFailureError
(
msg
)
from
e
# And in with the new
new_fqdn
=
f
"
{
new_lag_interface
}
.
{
new_node
.
router
.
router_fqdn
}
"
comment
=
str
(
subscription
.
subscription_id
)
infoblox
.
create_host_by_ip
(
new_fqdn
,
trunk_v4
.
ipv4addr
,
trunk_v6
.
ipv6addr
,
service_type
=
"
TRUNK
"
,
comment
=
comment
)
return
{
"
subscription
"
:
subscription
}
...
...
@@ -507,12 +570,6 @@ def update_subscription_model(
)
->
State
:
"""
Update the subscription model in the database.
"""
# Deep copy of subscription data
old_subscription
=
copy
.
deepcopy
(
subscription
)
old_side_data
=
{
"
iptrunk_side_node
"
:
old_subscription
.
iptrunk
.
iptrunk_sides
[
replace_index
].
iptrunk_side_node
,
"
iptrunk_side_ae_iface
"
:
old_subscription
.
iptrunk
.
iptrunk_sides
[
replace_index
].
iptrunk_side_ae_iface
,
"
iptrunk_side_ae_members
"
:
old_subscription
.
iptrunk
.
iptrunk_sides
[
replace_index
].
iptrunk_side_ae_members
,
}
subscription
.
iptrunk
.
iptrunk_sides
[
replace_index
].
iptrunk_side_node
=
Router
.
from_subscription
(
new_node
).
router
subscription
.
iptrunk
.
iptrunk_sides
[
replace_index
].
iptrunk_side_ae_iface
=
new_lag_interface
subscription
.
iptrunk
.
iptrunk_sides
[
replace_index
].
iptrunk_side_ae_members
.
clear
()
...
...
@@ -522,40 +579,6 @@ def update_subscription_model(
IptrunkInterfaceBlock
.
new
(
subscription_id
=
uuid4
(),
**
member
),
)
return
{
"
subscription
"
:
subscription
,
"
old_side_data
"
:
old_side_data
}
@step
(
"
Netbox: Reserve new interfaces
"
)
def
netbox_reserve_interfaces
(
subscription
:
Iptrunk
,
new_node
:
UUIDstr
,
new_lag_interface
:
str
,
new_lag_member_interfaces
:
list
[
dict
],
)
->
State
:
"""
Reserve new interfaces in Netbox, only when the new side
'
s router is a NOKIA router.
"""
new_side
=
Router
.
from_subscription
(
new_node
).
router
nbclient
=
NetboxClient
()
# Create :term:`LAG` interfaces
lag_interface
:
Interfaces
=
nbclient
.
create_interface
(
iface_name
=
new_lag_interface
,
interface_type
=
"
lag
"
,
device_name
=
new_side
.
router_fqdn
,
description
=
str
(
subscription
.
subscription_id
),
enabled
=
True
,
)
# Attach physical interfaces to :term:`LAG`
# Reserve interfaces
for
interface
in
new_lag_member_interfaces
:
nbclient
.
attach_interface_to_lag
(
device_name
=
new_side
.
router_fqdn
,
lag_name
=
lag_interface
.
name
,
iface_name
=
interface
[
"
interface_name
"
],
description
=
str
(
subscription
.
subscription_id
),
)
nbclient
.
reserve_interface
(
device_name
=
new_side
.
router_fqdn
,
iface_name
=
interface
[
"
interface_name
"
],
)
return
{
"
subscription
"
:
subscription
}
...
...
@@ -628,6 +651,7 @@ def migrate_iptrunk() -> StepList:
>>
store_process_subscription
(
Target
.
MODIFY
)
>>
unsync
>>
new_side_is_nokia
(
netbox_reserve_interfaces
)
>>
calculate_old_side_data
>>
pp_interaction
(
set_isis_to_90000
)
>>
pp_interaction
(
disable_old_config_dry
)
>>
pp_interaction
(
disable_old_config_real
)
...
...
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