Skip to content
Snippets Groups Projects
Verified Commit 1527f73d authored by Karel van Klink's avatar Karel van Klink :smiley_cat:
Browse files

add docstrings to the IP trunk workflows

parent f94f214e
No related branches found
No related tags found
1 merge request!111Feature/ruff everything party hat emoji
"""All workflows that can be executed on IP trunks."""
"""A creation workflow that deploys a new IP trunk service."""
from uuid import uuid4 from uuid import uuid4
from orchestrator.forms import FormPage from orchestrator.forms import FormPage
...@@ -33,6 +35,7 @@ from gso.utils.helpers import ( ...@@ -33,6 +35,7 @@ from gso.utils.helpers import (
def initial_input_form_generator(product_name: str) -> FormGenerator: def initial_input_form_generator(product_name: str) -> FormGenerator:
"""Gather input from the user in three steps. General information, and information on both sides of the trunk."""
# TODO: implement more strict validation: # TODO: implement more strict validation:
# * interface names must be validated # * interface names must be validated
...@@ -160,6 +163,7 @@ def initial_input_form_generator(product_name: str) -> FormGenerator: ...@@ -160,6 +163,7 @@ def initial_input_form_generator(product_name: str) -> FormGenerator:
@step("Create subscription") @step("Create subscription")
def create_subscription(product: UUIDstr, customer: UUIDstr) -> State: def create_subscription(product: UUIDstr, customer: UUIDstr) -> State:
"""Create a new subscription object in the database."""
subscription = IptrunkInactive.from_product_id(product, customer) subscription = IptrunkInactive.from_product_id(product, customer)
return { return {
...@@ -170,6 +174,7 @@ def create_subscription(product: UUIDstr, customer: UUIDstr) -> State: ...@@ -170,6 +174,7 @@ def create_subscription(product: UUIDstr, customer: UUIDstr) -> State:
@step("Get information from IPAM") @step("Get information from IPAM")
def get_info_from_ipam(subscription: IptrunkProvisioning) -> State: def get_info_from_ipam(subscription: IptrunkProvisioning) -> State:
"""Allocate :term:`IP` resources in :term:`IPAM`."""
subscription.iptrunk.iptrunk_ipv4_network = infoblox.allocate_v4_network( subscription.iptrunk.iptrunk_ipv4_network = infoblox.allocate_v4_network(
"TRUNK", "TRUNK",
subscription.iptrunk.iptrunk_description, subscription.iptrunk.iptrunk_description,
...@@ -199,6 +204,7 @@ def initialize_subscription( ...@@ -199,6 +204,7 @@ def initialize_subscription(
side_b_ae_geant_a_sid: str, side_b_ae_geant_a_sid: str,
side_b_ae_members: list[dict], side_b_ae_members: list[dict],
) -> State: ) -> State:
"""Take all input from the user, and store it in the database."""
subscription.iptrunk.geant_s_sid = geant_s_sid subscription.iptrunk.geant_s_sid = geant_s_sid
subscription.iptrunk.iptrunk_description = iptrunk_description subscription.iptrunk.iptrunk_description = iptrunk_description
subscription.iptrunk.iptrunk_type = iptrunk_type subscription.iptrunk.iptrunk_type = iptrunk_type
...@@ -235,6 +241,7 @@ def provision_ip_trunk_iface_dry( ...@@ -235,6 +241,7 @@ def provision_ip_trunk_iface_dry(
process_id: UUIDstr, process_id: UUIDstr,
tt_number: str, tt_number: str,
) -> State: ) -> State:
"""Perform a dry run of deploying configuration on both sides of the trunk."""
provisioning_proxy.provision_ip_trunk( provisioning_proxy.provision_ip_trunk(
subscription, process_id, callback_route, tt_number, "trunk_interface", dry_run=True, subscription, process_id, callback_route, tt_number, "trunk_interface", dry_run=True,
) )
...@@ -249,6 +256,7 @@ def provision_ip_trunk_iface_real( ...@@ -249,6 +256,7 @@ def provision_ip_trunk_iface_real(
process_id: UUIDstr, process_id: UUIDstr,
tt_number: str, tt_number: str,
) -> State: ) -> State:
"""Deploy IP trunk configuration on both sides."""
provisioning_proxy.provision_ip_trunk( provisioning_proxy.provision_ip_trunk(
subscription, process_id, callback_route, tt_number, "trunk_interface", dry_run=False, subscription, process_id, callback_route, tt_number, "trunk_interface", dry_run=False,
) )
...@@ -263,6 +271,7 @@ def check_ip_trunk_connectivity( ...@@ -263,6 +271,7 @@ def check_ip_trunk_connectivity(
process_id: UUIDstr, process_id: UUIDstr,
tt_number: str, tt_number: str,
) -> State: ) -> State:
"""Check successful connectivity across the new trunk."""
provisioning_proxy.check_ip_trunk(subscription, process_id, callback_route, tt_number, "ping") provisioning_proxy.check_ip_trunk(subscription, process_id, callback_route, tt_number, "ping")
return {"subscription": subscription} return {"subscription": subscription}
...@@ -275,6 +284,7 @@ def provision_ip_trunk_isis_iface_dry( ...@@ -275,6 +284,7 @@ def provision_ip_trunk_isis_iface_dry(
process_id: UUIDstr, process_id: UUIDstr,
tt_number: str, tt_number: str,
) -> State: ) -> State:
"""Perform a dry run of deploying :term:`ISIS` configuration."""
provisioning_proxy.provision_ip_trunk(subscription, process_id, callback_route, tt_number, "isis_interface") provisioning_proxy.provision_ip_trunk(subscription, process_id, callback_route, tt_number, "isis_interface")
return {"subscription": subscription} return {"subscription": subscription}
...@@ -287,6 +297,7 @@ def provision_ip_trunk_isis_iface_real( ...@@ -287,6 +297,7 @@ def provision_ip_trunk_isis_iface_real(
process_id: UUIDstr, process_id: UUIDstr,
tt_number: str, tt_number: str,
) -> State: ) -> State:
"""Deploy :term:`ISIS` configuration on both sides."""
provisioning_proxy.provision_ip_trunk( provisioning_proxy.provision_ip_trunk(
subscription, process_id, callback_route, tt_number, "isis_interface", dry_run=False, subscription, process_id, callback_route, tt_number, "isis_interface", dry_run=False,
) )
...@@ -301,6 +312,7 @@ def check_ip_trunk_isis( ...@@ -301,6 +312,7 @@ def check_ip_trunk_isis(
process_id: UUIDstr, process_id: UUIDstr,
tt_number: str, tt_number: str,
) -> State: ) -> State:
"""Run an Ansible playbook to confirm :term:`ISIS` adjacency."""
provisioning_proxy.check_ip_trunk(subscription, process_id, callback_route, tt_number, "isis") provisioning_proxy.check_ip_trunk(subscription, process_id, callback_route, tt_number, "isis")
return {"subscription": subscription} return {"subscription": subscription}
...@@ -360,6 +372,18 @@ def allocate_interfaces_in_netbox(subscription: IptrunkProvisioning) -> State: ...@@ -360,6 +372,18 @@ def allocate_interfaces_in_netbox(subscription: IptrunkProvisioning) -> State:
target=Target.CREATE, target=Target.CREATE,
) )
def create_iptrunk() -> StepList: def create_iptrunk() -> StepList:
"""Create a new IP trunk.
* Create the subscription object in the database
* Gather relevant information from Infoblox
* Reserve interfaces in Netbox
* Deploy configuration on the two sides of the trunk, first as a dry run
* Check connectivity on the new trunk
* Deploy the new :term:`ISIS` metric on the trunk, first as a dry run
* Verify :term:`ISIS` adjacency
* Allocate the interfaces in Netbox
* Set the subscription to active in the database
"""
return ( return (
init init
>> create_subscription >> create_subscription
......
"""A modification workflow that migrates an IP trunk to a different endpoint.
For a trunk that originally connected endpoints A and B, this workflow introduces a new endpoint C. The trunk is then
configured to run from A to C. B is then no longer associated with this IP trunk.
"""
import copy import copy
import re import re
from logging import getLogger from logging import getLogger
...@@ -37,6 +43,7 @@ logger = getLogger(__name__) ...@@ -37,6 +43,7 @@ logger = getLogger(__name__)
def initial_input_form_generator(subscription_id: UUIDstr) -> FormGenerator: def initial_input_form_generator(subscription_id: UUIDstr) -> FormGenerator:
"""Gather input from the operator on the new router that the IP trunk should terminate on."""
subscription = Iptrunk.from_subscription(subscription_id) subscription = Iptrunk.from_subscription(subscription_id)
form_title = ( form_title = (
f"Subscription {subscription.iptrunk.geant_s_sid} " f"Subscription {subscription.iptrunk.geant_s_sid} "
...@@ -165,6 +172,7 @@ def disable_old_config_dry( ...@@ -165,6 +172,7 @@ def disable_old_config_dry(
process_id: UUIDstr, process_id: UUIDstr,
tt_number: str, tt_number: str,
) -> State: ) -> State:
"""Perform a dry run of disabling the old configuration on the routers."""
provisioning_proxy.migrate_ip_trunk( provisioning_proxy.migrate_ip_trunk(
subscription, subscription,
new_node, new_node,
...@@ -194,6 +202,7 @@ def disable_old_config_real( ...@@ -194,6 +202,7 @@ def disable_old_config_real(
process_id: UUIDstr, process_id: UUIDstr,
tt_number: str, tt_number: str,
) -> State: ) -> State:
"""Disable old configuration on the routers."""
provisioning_proxy.migrate_ip_trunk( provisioning_proxy.migrate_ip_trunk(
subscription, subscription,
new_node, new_node,
...@@ -224,6 +233,10 @@ def deploy_new_config_dry( ...@@ -224,6 +233,10 @@ def deploy_new_config_dry(
process_id: UUIDstr, process_id: UUIDstr,
tt_number: str, tt_number: str,
) -> State: ) -> State:
"""Perform a dry run of deploying configuration on the new router.
TODO: set the proper playbook verb
"""
provisioning_proxy.migrate_ip_trunk( provisioning_proxy.migrate_ip_trunk(
subscription, subscription,
new_node, new_node,
...@@ -255,6 +268,10 @@ def deploy_new_config_real( ...@@ -255,6 +268,10 @@ def deploy_new_config_real(
process_id: UUIDstr, process_id: UUIDstr,
tt_number: str, tt_number: str,
) -> State: ) -> State:
"""Deploy configuration on the new router.
TODO: set the proper playbook verb
"""
provisioning_proxy.migrate_ip_trunk( provisioning_proxy.migrate_ip_trunk(
subscription, subscription,
new_node, new_node,
...@@ -278,20 +295,21 @@ def deploy_new_config_real( ...@@ -278,20 +295,21 @@ def deploy_new_config_real(
@inputstep("Wait for confirmation", assignee=Assignee.SYSTEM) @inputstep("Wait for confirmation", assignee=Assignee.SYSTEM)
def confirm_continue_move_fiber() -> FormGenerator: def confirm_continue_move_fiber() -> FormGenerator:
"""Wait for confirmation from an operator that the physical fiber has been moved."""
class ProvisioningResultPage(FormPage): class ProvisioningResultPage(FormPage):
class Config: class Config:
title = "Please confirm before continuing" title = "Please confirm before continuing"
info_label: Label = "New Trunk interface has been deployed, wait for the physical connection to be moved." # type: ignore[assignment] info_label: Label = (
"New trunk interface has been deployed, "
"wait for the physical connection to be moved." # type: ignore[assignment]
)
yield ProvisioningResultPage yield ProvisioningResultPage
return {} return {}
# Interface checks go here
@step("Deploy ISIS configuration on new router") @step("Deploy ISIS configuration on new router")
def deploy_new_isis( def deploy_new_isis(
subscription: Iptrunk, subscription: Iptrunk,
...@@ -303,6 +321,10 @@ def deploy_new_isis( ...@@ -303,6 +321,10 @@ def deploy_new_isis(
process_id: UUIDstr, process_id: UUIDstr,
tt_number: str, tt_number: str,
) -> State: ) -> State:
"""Deploy :term:`ISIS` configuration.
TODO: set the proper playbook verb.
"""
provisioning_proxy.migrate_ip_trunk( provisioning_proxy.migrate_ip_trunk(
subscription, subscription,
new_node, new_node,
...@@ -326,11 +348,14 @@ def deploy_new_isis( ...@@ -326,11 +348,14 @@ def deploy_new_isis(
@inputstep("Wait for confirmation", assignee=Assignee.SYSTEM) @inputstep("Wait for confirmation", assignee=Assignee.SYSTEM)
def confirm_continue_restore_isis() -> FormGenerator: def confirm_continue_restore_isis() -> FormGenerator:
"""Wait for an operator to confirm that the old :term:`ISIS` metric should be restored."""
class ProvisioningResultPage(FormPage): class ProvisioningResultPage(FormPage):
class Config: class Config:
title = "Please confirm before continuing" title = "Please confirm before continuing"
info_label: Label = "ISIS config has been deployed, confirm if you want to restore the old metric." # type: ignore[assignment] info_label: Label = (
"ISIS config has been deployed, confirm if you want to restore the old metric." # type: ignore[assignment]
)
yield ProvisioningResultPage yield ProvisioningResultPage
...@@ -345,6 +370,7 @@ def restore_isis_metric( ...@@ -345,6 +370,7 @@ def restore_isis_metric(
tt_number: str, tt_number: str,
old_isis_metric: int, old_isis_metric: int,
) -> State: ) -> State:
"""Restore the :term:`ISIS` metric to its original value."""
subscription.iptrunk.iptrunk_isis_metric = old_isis_metric subscription.iptrunk.iptrunk_isis_metric = old_isis_metric
provisioning_proxy.provision_ip_trunk( provisioning_proxy.provision_ip_trunk(
subscription, process_id, callback_route, tt_number, "isis_interface", dry_run=False, subscription, process_id, callback_route, tt_number, "isis_interface", dry_run=False,
...@@ -364,6 +390,10 @@ def delete_old_config_dry( ...@@ -364,6 +390,10 @@ def delete_old_config_dry(
process_id: UUIDstr, process_id: UUIDstr,
tt_number: str, tt_number: str,
) -> State: ) -> State:
"""Perform a dry run of deleting the old configuration.
TODO: set the proper playbook verb
"""
provisioning_proxy.migrate_ip_trunk( provisioning_proxy.migrate_ip_trunk(
subscription, subscription,
new_node, new_node,
...@@ -393,6 +423,10 @@ def delete_old_config_real( ...@@ -393,6 +423,10 @@ def delete_old_config_real(
process_id: UUIDstr, process_id: UUIDstr,
tt_number: str, tt_number: str,
) -> State: ) -> State:
"""Delete old configuration from the routers.
TODO: set the proper playbook verb
"""
provisioning_proxy.migrate_ip_trunk( provisioning_proxy.migrate_ip_trunk(
subscription, subscription,
new_node, new_node,
...@@ -414,6 +448,10 @@ def delete_old_config_real( ...@@ -414,6 +448,10 @@ def delete_old_config_real(
@step("Update IPAM") @step("Update IPAM")
def update_ipam(subscription: Iptrunk) -> State: def update_ipam(subscription: Iptrunk) -> State:
"""Update :term:`IPAM` resources.
TODO: implement
"""
return {"subscription": subscription} return {"subscription": subscription}
...@@ -425,6 +463,7 @@ def update_subscription_model( ...@@ -425,6 +463,7 @@ def update_subscription_model(
new_lag_interface: str, new_lag_interface: str,
new_lag_member_interfaces: list[dict], new_lag_member_interfaces: list[dict],
) -> State: ) -> State:
"""Update the subscription model in the database."""
# Deep copy of subscription data # Deep copy of subscription data
old_subscription = copy.deepcopy(subscription) old_subscription = copy.deepcopy(subscription)
old_side_data = { old_side_data = {
...@@ -451,6 +490,7 @@ def reserve_interfaces_in_netbox( ...@@ -451,6 +490,7 @@ def reserve_interfaces_in_netbox(
new_lag_interface: str, new_lag_interface: str,
new_lag_member_interfaces: list[dict], new_lag_member_interfaces: list[dict],
) -> State: ) -> State:
"""Reserve new interfaces in Netbox."""
new_side = Router.from_subscription(new_node).router new_side = Router.from_subscription(new_node).router
nbclient = NetboxClient() nbclient = NetboxClient()
...@@ -485,6 +525,7 @@ def update_netbox( ...@@ -485,6 +525,7 @@ def update_netbox(
replace_index: int, replace_index: int,
old_side_data: dict, old_side_data: dict,
) -> State: ) -> State:
"""Update Netbox, reallocating the old and new interfaces."""
new_side = subscription.iptrunk.iptrunk_sides[replace_index] new_side = subscription.iptrunk.iptrunk_sides[replace_index]
nbclient = NetboxClient() nbclient = NetboxClient()
if new_side.iptrunk_side_node.router_vendor == RouterVendor.NOKIA: if new_side.iptrunk_side_node.router_vendor == RouterVendor.NOKIA:
...@@ -515,6 +556,23 @@ def update_netbox( ...@@ -515,6 +556,23 @@ def update_netbox(
target=Target.MODIFY, target=Target.MODIFY,
) )
def migrate_iptrunk() -> StepList: def migrate_iptrunk() -> StepList:
"""Migrate an IP trunk.
* Reserve new interfaces in Netbox
* Set the :term:`ISIS` metric of the current trunk to an arbitrarily high value to drain all traffic
* Disable - but don't delete - the old configuration on the routers, first as a dry run
* Deploy the new configuration on the routers, first as a dry run
* Wait for operator confirmation that the physical fiber has been moved before continuing
* Deploy a new :term:`ISIS` interface between routers A and C
* Wait for operator confirmation that :term:`ISIS` is behaving as expected
* Restore the old :term:`ISIS` metric on the new trunk
* Delete the old, disabled configuration on the routers, first as a dry run
* Reflect the changes made in :term:`IPAM`
* Update the subscription model in the database accordingly
* Update the reserved interfaces in Netbox
TODO: add interface checks
"""
return ( return (
init init
>> store_process_subscription(Target.MODIFY) >> store_process_subscription(Target.MODIFY)
......
"""A modification workflow for setting a new :term:`ISIS` metric for an IP trunk."""
from orchestrator.forms import FormPage from orchestrator.forms import FormPage
from orchestrator.targets import Target from orchestrator.targets import Target
from orchestrator.types import FormGenerator, State, UUIDstr from orchestrator.types import FormGenerator, State, UUIDstr
...@@ -11,6 +13,7 @@ from gso.services.provisioning_proxy import pp_interaction ...@@ -11,6 +13,7 @@ from gso.services.provisioning_proxy import pp_interaction
def initial_input_form_generator(subscription_id: UUIDstr) -> FormGenerator: def initial_input_form_generator(subscription_id: UUIDstr) -> FormGenerator:
"""Ask the operator for the new :term:`ISIS` metric."""
subscription = Iptrunk.from_subscription(subscription_id) subscription = Iptrunk.from_subscription(subscription_id)
class ModifyIptrunkForm(FormPage): class ModifyIptrunkForm(FormPage):
...@@ -24,6 +27,7 @@ def initial_input_form_generator(subscription_id: UUIDstr) -> FormGenerator: ...@@ -24,6 +27,7 @@ def initial_input_form_generator(subscription_id: UUIDstr) -> FormGenerator:
@step("Update subscription") @step("Update subscription")
def modify_iptrunk_subscription(subscription: Iptrunk, isis_metric: int) -> State: def modify_iptrunk_subscription(subscription: Iptrunk, isis_metric: int) -> State:
"""Store the new :term:`ISIS` metric in the database by updating the subscription."""
subscription.iptrunk.iptrunk_isis_metric = isis_metric subscription.iptrunk.iptrunk_isis_metric = isis_metric
return {"subscription": subscription} return {"subscription": subscription}
...@@ -36,6 +40,7 @@ def provision_ip_trunk_isis_iface_dry( ...@@ -36,6 +40,7 @@ def provision_ip_trunk_isis_iface_dry(
callback_route: str, callback_route: str,
tt_number: str, tt_number: str,
) -> State: ) -> State:
"""Perform a dry run of deploying the new :term:`ISIS` metric on both sides of the trunk."""
provisioning_proxy.provision_ip_trunk(subscription, process_id, callback_route, tt_number, "isis_interface") provisioning_proxy.provision_ip_trunk(subscription, process_id, callback_route, tt_number, "isis_interface")
return {"subscription": subscription} return {"subscription": subscription}
...@@ -48,6 +53,7 @@ def provision_ip_trunk_isis_iface_real( ...@@ -48,6 +53,7 @@ def provision_ip_trunk_isis_iface_real(
callback_route: str, callback_route: str,
tt_number: str, tt_number: str,
) -> State: ) -> State:
"""Deploy the new :term:`ISIS` metric on both sides of the trunk."""
provisioning_proxy.provision_ip_trunk( provisioning_proxy.provision_ip_trunk(
subscription, process_id, callback_route, tt_number, "isis_interface", dry_run=False, subscription, process_id, callback_route, tt_number, "isis_interface", dry_run=False,
) )
...@@ -61,6 +67,12 @@ def provision_ip_trunk_isis_iface_real( ...@@ -61,6 +67,12 @@ def provision_ip_trunk_isis_iface_real(
target=Target.MODIFY, target=Target.MODIFY,
) )
def modify_isis_metric() -> StepList: def modify_isis_metric() -> StepList:
"""Modify the :term:`ISIS` metric of an existing IP trunk.
* Modify the subscription model in the database
* Perform a dry run of setting the new :term:`ISIS` metric
* Deploy the new :term:`ISIS` metric on both sides of the trunk
"""
return ( return (
init init
>> store_process_subscription(Target.MODIFY) >> store_process_subscription(Target.MODIFY)
......
"""A modification workflow that updates the :term:`LAG` interfaces that are part of an existing IP trunk."""
import ipaddress import ipaddress
from uuid import uuid4 from uuid import uuid4
...@@ -66,6 +68,7 @@ def initialize_ae_members(subscription: Iptrunk, initial_user_input: dict, side_ ...@@ -66,6 +68,7 @@ def initialize_ae_members(subscription: Iptrunk, initial_user_input: dict, side_
def initial_input_form_generator(subscription_id: UUIDstr) -> FormGenerator: def initial_input_form_generator(subscription_id: UUIDstr) -> FormGenerator:
"""Gather input from the operator on the interfaces that should be modified."""
subscription = Iptrunk.from_subscription(subscription_id) subscription = Iptrunk.from_subscription(subscription_id)
class ModifyIptrunkForm(FormPage): class ModifyIptrunkForm(FormPage):
...@@ -141,6 +144,7 @@ def modify_iptrunk_subscription( ...@@ -141,6 +144,7 @@ def modify_iptrunk_subscription(
side_b_ae_geant_a_sid: str, side_b_ae_geant_a_sid: str,
side_b_ae_members: list[dict], side_b_ae_members: list[dict],
) -> State: ) -> State:
"""Modify the subscription in the service database, reflecting the changes to the newly selected interfaces."""
# Prepare the list of removed AE members # Prepare the list of removed AE members
previous_ae_members = {} previous_ae_members = {}
removed_ae_members = {} removed_ae_members = {}
...@@ -197,14 +201,15 @@ def provision_ip_trunk_iface_dry( ...@@ -197,14 +201,15 @@ def provision_ip_trunk_iface_dry(
tt_number: str, tt_number: str,
removed_ae_members: list[str], removed_ae_members: list[str],
) -> State: ) -> State:
"""Perform a dry run of deploying the updated IP trunk."""
provisioning_proxy.provision_ip_trunk( provisioning_proxy.provision_ip_trunk(
subscription, subscription,
process_id, process_id,
callback_route, callback_route,
tt_number, tt_number,
"trunk_interface", "trunk_interface",
True, dry_run=True,
removed_ae_members, removed_ae_members=removed_ae_members,
) )
return {"subscription": subscription} return {"subscription": subscription}
...@@ -218,14 +223,15 @@ def provision_ip_trunk_iface_real( ...@@ -218,14 +223,15 @@ def provision_ip_trunk_iface_real(
tt_number: str, tt_number: str,
removed_ae_members: list[str], removed_ae_members: list[str],
) -> State: ) -> State:
"""Provision the new IP trunk with updated interfaces."""
provisioning_proxy.provision_ip_trunk( provisioning_proxy.provision_ip_trunk(
subscription, subscription,
process_id, process_id,
callback_route, callback_route,
tt_number, tt_number,
"trunk_interface", "trunk_interface",
False, dry_run=False,
removed_ae_members, removed_ae_members=removed_ae_members,
) )
return {"subscription": subscription} return {"subscription": subscription}
...@@ -233,6 +239,7 @@ def provision_ip_trunk_iface_real( ...@@ -233,6 +239,7 @@ def provision_ip_trunk_iface_real(
@step("Update interfaces in Netbox. Reserving interfaces.") @step("Update interfaces in Netbox. Reserving interfaces.")
def update_interfaces_in_netbox(subscription: Iptrunk, removed_ae_members: dict, previous_ae_members: dict) -> State: def update_interfaces_in_netbox(subscription: Iptrunk, removed_ae_members: dict, previous_ae_members: dict) -> State:
"""Update Netbox such that it contains the new interfaces."""
nbclient = NetboxClient() nbclient = NetboxClient()
for side in range(2): for side in range(2):
if subscription.iptrunk.iptrunk_sides[side].iptrunk_side_node.router_vendor == RouterVendor.NOKIA: if subscription.iptrunk.iptrunk_sides[side].iptrunk_side_node.router_vendor == RouterVendor.NOKIA:
...@@ -269,7 +276,7 @@ def update_interfaces_in_netbox(subscription: Iptrunk, removed_ae_members: dict, ...@@ -269,7 +276,7 @@ def update_interfaces_in_netbox(subscription: Iptrunk, removed_ae_members: dict,
def allocate_interfaces_in_netbox(subscription: Iptrunk, previous_ae_members: dict) -> State: def allocate_interfaces_in_netbox(subscription: Iptrunk, previous_ae_members: dict) -> State:
"""Allocate the LAG interfaces in NetBox. """Allocate the LAG interfaces in NetBox.
attach the lag interfaces to the physical interfaces detach old ones from the LAG. Attach the :term:`LAG` interfaces to the physical interfaces detach old ones from the :term:`LAG`.
""" """
for side in range(2): for side in range(2):
nbclient = NetboxClient() nbclient = NetboxClient()
...@@ -299,6 +306,13 @@ def allocate_interfaces_in_netbox(subscription: Iptrunk, previous_ae_members: di ...@@ -299,6 +306,13 @@ def allocate_interfaces_in_netbox(subscription: Iptrunk, previous_ae_members: di
target=Target.MODIFY, target=Target.MODIFY,
) )
def modify_trunk_interface() -> StepList: def modify_trunk_interface() -> StepList:
"""Modify the interfaces that are part of an IP trunk.
* Update the subscription in the database
* Reserve new interfaces in Netbox
* Provision the updated version of the IP trunk, first as a dry run
* Allocate the previously reserved interfaces in Netbox
"""
return ( return (
init init
>> store_process_subscription(Target.MODIFY) >> store_process_subscription(Target.MODIFY)
......
"""A termination workflow for an active IP trunk."""
import ipaddress import ipaddress
from orchestrator.forms import FormPage from orchestrator.forms import FormPage
...@@ -22,6 +24,7 @@ from gso.utils.helpers import set_isis_to_90000 ...@@ -22,6 +24,7 @@ from gso.utils.helpers import set_isis_to_90000
def initial_input_form_generator() -> FormGenerator: def initial_input_form_generator() -> FormGenerator:
"""Ask the operator to confirm whether router configuration and/or IPAM resources should be deleted."""
class TerminateForm(FormPage): class TerminateForm(FormPage):
termination_label: Label = ( termination_label: Label = (
"Please confirm whether configuration should get removed from the A and B sides of the trunk, and whether " "Please confirm whether configuration should get removed from the A and B sides of the trunk, and whether "
...@@ -42,6 +45,10 @@ def drain_traffic_from_ip_trunk( ...@@ -42,6 +45,10 @@ def drain_traffic_from_ip_trunk(
callback_route: str, callback_route: str,
tt_number: str, tt_number: str,
) -> State: ) -> State:
"""Drain all traffic from the trunk.
XXX: Should this not be done with the isis-90k-step?
"""
provisioning_proxy.provision_ip_trunk( provisioning_proxy.provision_ip_trunk(
subscription, process_id, callback_route, tt_number, "isis_interface", dry_run=False, subscription, process_id, callback_route, tt_number, "isis_interface", dry_run=False,
) )
...@@ -51,6 +58,7 @@ def drain_traffic_from_ip_trunk( ...@@ -51,6 +58,7 @@ def drain_traffic_from_ip_trunk(
@step("Deprovision IP trunk [DRY RUN]") @step("Deprovision IP trunk [DRY RUN]")
def deprovision_ip_trunk_dry(subscription: Iptrunk, process_id: UUIDstr, callback_route: str, tt_number: str) -> State: def deprovision_ip_trunk_dry(subscription: Iptrunk, process_id: UUIDstr, callback_route: str, tt_number: str) -> State:
"""Perform a dry run of deleting configuration from the routers."""
provisioning_proxy.deprovision_ip_trunk(subscription, process_id, callback_route, tt_number, dry_run=True) provisioning_proxy.deprovision_ip_trunk(subscription, process_id, callback_route, tt_number, dry_run=True)
return {"subscription": subscription} return {"subscription": subscription}
...@@ -58,13 +66,18 @@ def deprovision_ip_trunk_dry(subscription: Iptrunk, process_id: UUIDstr, callbac ...@@ -58,13 +66,18 @@ def deprovision_ip_trunk_dry(subscription: Iptrunk, process_id: UUIDstr, callbac
@step("Deprovision IP trunk [FOR REAL]") @step("Deprovision IP trunk [FOR REAL]")
def deprovision_ip_trunk_real(subscription: Iptrunk, process_id: UUIDstr, callback_route: str, tt_number: str) -> State: def deprovision_ip_trunk_real(subscription: Iptrunk, process_id: UUIDstr, callback_route: str, tt_number: str) -> State:
"""Delete configuration from the routers."""
provisioning_proxy.deprovision_ip_trunk(subscription, process_id, callback_route, tt_number, dry_run=False) provisioning_proxy.deprovision_ip_trunk(subscription, process_id, callback_route, tt_number, dry_run=False)
return {"subscription": subscription} return {"subscription": subscription}
@step("Remove IP Trunk from NetBox") @step("Remove IP Trunk from Netbox")
def free_interfaces_in_netbox(subscription: Iptrunk) -> State: def free_interfaces_in_netbox(subscription: Iptrunk) -> State:
"""Mark used interfaces as free in Netbox.
TODO: decide on the conditionality of this step
"""
for side in [0, 1]: for side in [0, 1]:
router = subscription.iptrunk.iptrunk_sides[side].iptrunk_side_node router = subscription.iptrunk.iptrunk_sides[side].iptrunk_side_node
router_fqdn = router.router_fqdn router_fqdn = router.router_fqdn
...@@ -84,6 +97,7 @@ def free_interfaces_in_netbox(subscription: Iptrunk) -> State: ...@@ -84,6 +97,7 @@ def free_interfaces_in_netbox(subscription: Iptrunk) -> State:
@step("Deprovision IPv4 networks") @step("Deprovision IPv4 networks")
def deprovision_ip_trunk_ipv4(subscription: Iptrunk) -> dict: def deprovision_ip_trunk_ipv4(subscription: Iptrunk) -> dict:
"""Clear up IPv4 resources in :term:`IPAM`."""
infoblox.delete_network(ipaddress.IPv4Network(subscription.iptrunk.iptrunk_ipv4_network)) infoblox.delete_network(ipaddress.IPv4Network(subscription.iptrunk.iptrunk_ipv4_network))
return {"subscription": subscription} return {"subscription": subscription}
...@@ -91,6 +105,7 @@ def deprovision_ip_trunk_ipv4(subscription: Iptrunk) -> dict: ...@@ -91,6 +105,7 @@ def deprovision_ip_trunk_ipv4(subscription: Iptrunk) -> dict:
@step("Deprovision IPv6 networks") @step("Deprovision IPv6 networks")
def deprovision_ip_trunk_ipv6(subscription: Iptrunk) -> dict: def deprovision_ip_trunk_ipv6(subscription: Iptrunk) -> dict:
"""Clear up IPv6 resources in :term:`IPAM`."""
infoblox.delete_network(ipaddress.IPv6Network(subscription.iptrunk.iptrunk_ipv6_network)) infoblox.delete_network(ipaddress.IPv6Network(subscription.iptrunk.iptrunk_ipv6_network))
return {"subscription": subscription} return {"subscription": subscription}
...@@ -102,6 +117,15 @@ def deprovision_ip_trunk_ipv6(subscription: Iptrunk) -> dict: ...@@ -102,6 +117,15 @@ def deprovision_ip_trunk_ipv6(subscription: Iptrunk) -> dict:
target=Target.TERMINATE, target=Target.TERMINATE,
) )
def terminate_iptrunk() -> StepList: def terminate_iptrunk() -> StepList:
"""Terminate an IP trunk.
* Let the operator decide whether to remove configuration from the routers, if so:
* Set the :term:`ISIS` metric of the IP trunk to an arbitrarily high value
* Disable and remove configuration from the routers, first as a dry run
* Mark the IP trunk interfaces as free in Netbox
* Clear IPAM resources, if selected by the operator
* Terminate the subscription in the service database
"""
run_config_steps = conditional(lambda state: state["remove_configuration"]) run_config_steps = conditional(lambda state: state["remove_configuration"])
run_ipam_steps = conditional(lambda state: state["clean_up_ipam"]) run_ipam_steps = conditional(lambda state: state["clean_up_ipam"])
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment