Skip to content
Snippets Groups Projects
Commit 5339b149 authored by Karel van Klink's avatar Karel van Klink :smiley_cat:
Browse files

update iptrunk migration workflow, adding all steps as skeleton

parent 4cffba2f
Branches
Tags
1 merge request!64new IP trunk migration
......@@ -19,12 +19,11 @@ from pydantic import validator
from gso import settings
from gso.products.product_types.iptrunk import Iptrunk, IptrunkProvisioning
from gso.products.product_types.router import RouterProvisioning
from gso.products.product_types.router import Router, RouterProvisioning
logger = logging.getLogger(__name__)
"""{class}`logging.Logger` instance."""
DEFAULT_LABEL = "Provisioning proxy is running. Please come back later for the results."
"""The default label displayed when the provisioning proxy is running."""
"""The default label displayed when the provisioning proxy is running, in case no custom label is provided."""
class CUDOperation(strEnum):
......@@ -159,6 +158,52 @@ def deprovision_ip_trunk(subscription: Iptrunk, process_id: UUIDstr, dry_run: bo
_send_request("ip_trunk", parameters, process_id, CUDOperation.DELETE)
def migrate_ip_trunk(
subscription: Iptrunk,
new_node: Router,
new_lag_interface: str,
new_lag_member_interfaces: list[str],
replace_index: int,
process_id: UUIDstr,
verb: str,
dry_run: bool = True,
) -> None:
"""Migrate an IP trunk service using {term}`LSO`.
:param subscription: The subscription object that's to be migrated.
:type subscription: {class}`Iptrunk`
:param new_node: The new node that is being migrated to
:type new_node: {class}`Router`
:param new_lag_interface: The name of the new aggregated Ethernet interface
:type new_lag_interface: str
:param new_lag_member_interfaces: The new list of interfaces that are part of the LAG
:type new_lag_member_interfaces: list[str]
:param replace_index: The index of the side that is going to be replaced as part of the existing trunk,
can be `0` or `1`.
:type replace_index: int
:param process_id: The related process ID, used for callback.
:type process_id: UUIDstr
:param verb: The verb that is passed to the executed playbook
:type verb: str
:param dry_run: A boolean indicating whether this should be a dry run or not, defaults to `True`.
:type dry_run: bool
:rtype: None
"""
parameters = {
"subscription": json.loads(json_dumps(subscription)),
"new_side": {
"new_node": json.loads(json_dumps(new_node)),
"new_lag_interface": new_lag_interface,
"new_lag_member_interfaces": new_lag_member_interfaces,
"replace_index": replace_index,
},
"verb": verb,
"dry_run": dry_run,
}
_send_request("ip_trunk/migrate", parameters, process_id, CUDOperation.POST)
@inputstep("Await provisioning proxy results", assignee=Assignee("SYSTEM"))
def _await_pp_results(subscription: SubscriptionModel, label_text: str = DEFAULT_LABEL) -> FormGenerator:
"""Input step that forces the workflow to go into a `SUSPENDED` state.
......
from logging import getLogger
from orchestrator import step
from orchestrator.types import State
from products import Iptrunk
logger = getLogger(__name__)
@step("Set ISIS metric to 9000")
def set_isis_to_9000(subscription: Iptrunk) -> State:
old_isis_metric = subscription.iptrunk.iptrunk_isis_metric
subscription.iptrunk.iptrunk_isis_metric = 90000
logger.warning("ISIS metric is only updated in the subscription, not in the real world.")
return {"subscription": subscription, "old_isis_metric": old_isis_metric}
@step("Restore ISIS metric to original value")
def restore_isis_metric(subscription: Iptrunk, old_isis_metric: int) -> State:
subscription.iptrunk.iptrunk_isis_metric = old_isis_metric
logger.warning("ISIS metric is only updated in the subscription, not in the real world.")
return {"subscription": subscription}
from uuid import UUID
from orchestrator.db.models import ProductTable, SubscriptionTable
from orchestrator.forms import FormPage
from orchestrator.forms.validators import Choice, UniqueConstrainedList
......@@ -10,7 +8,7 @@ from orchestrator.workflows.steps import resync, set_status, store_process_subsc
from orchestrator.workflows.utils import wrap_create_initial_input_form
from gso.products.product_blocks import PhyPortCapacity
from gso.products.product_blocks.iptrunk import IptrunkType, IptrunkSideBlock
from gso.products.product_blocks.iptrunk import IptrunkType
from gso.products.product_types.iptrunk import IptrunkInactive, IptrunkProvisioning
from gso.products.product_types.router import Router
from gso.services import ipam, provisioning_proxy, subscriptions
......
import re
from logging import getLogger
from typing import NoReturn
from orchestrator import step, workflow
from orchestrator.config.assignee import Assignee
from orchestrator.db import ProductTable, SubscriptionTable
from orchestrator.forms import FormPage
from orchestrator.forms.validators import Choice, UniqueConstrainedList
from orchestrator.forms.validators import Choice, Label, UniqueConstrainedList
from orchestrator.targets import Target
from orchestrator.types import FormGenerator, State, UUIDstr
from orchestrator.workflow import StepList, done, init
from orchestrator.workflow import StepList, done, init, inputstep
from orchestrator.workflows.steps import resync, store_process_subscription, unsync
from orchestrator.workflows.utils import wrap_modify_initial_input_form
from products import Iptrunk, Router
from pydantic import validator
from services import provisioning_proxy
from services.provisioning_proxy import pp_interaction
from workflows.iptrunk import restore_isis_metric, set_isis_to_9000
from products import Iptrunk
logger = getLogger(__name__)
def initial_input_form_generator(subscription_id: UUIDstr) -> FormGenerator:
......@@ -35,15 +41,10 @@ def initial_input_form_generator(subscription_id: UUIDstr) -> FormGenerator:
routers[str(router_id)] = router_description
NewRouterEnum = Choice("Select a new router", zip(routers.keys(), routers.items())) # type: ignore
ReplacedSide = Choice(
"Select the side of the IP trunk to be replaced",
[ # type: ignore
(str(subscription.iptrunk.iptrunk_sides[0].iptrunk_side_node.subscription.subscription_id),
subscription.iptrunk.iptrunk_sides[0].iptrunk_side_node.subscription.description),
(str(subscription.iptrunk.iptrunk_sides[1].iptrunk_side_node.subscription.subscription_id),
subscription.iptrunk.iptrunk_sides[1].iptrunk_side_node.subscription.description),
],
)
sides_dict = {
side.subscription.subscription_id: side.subscription.description for side in subscription.iptrunk.iptrunk_sides
}
ReplacedSide = Choice("Select the side of the IP trunk to be replaced", zip(sides_dict.keys(), sides_dict.items()))
class LagMemberList(UniqueConstrainedList[str]):
min_items = len(subscription.iptrunk.iptrunk_sides[0].iptrunk_side_ae_members)
......@@ -74,12 +75,285 @@ def initial_input_form_generator(subscription_id: UUIDstr) -> FormGenerator:
user_input = yield ModifyIptrunkForm
return user_input.dict()
def _find_updated_side_of_trunk(trunk: Iptrunk, new_side: str) -> int:
sides = trunk.iptrunk.iptrunk_sides
if str(sides[0].iptrunk_side_node.subscription.subscription_id) == new_side:
return 0
elif str(sides[1].iptrunk_side_node.subscription.subscription_id) == new_side:
return 1
raise ValueError("Invalid Router id provided to be replaced!")
replace_index = _find_updated_side_of_trunk(subscription, user_input["replace_side"])
return user_input.dict() | {"replace_index": replace_index}
@inputstep("Wait for confirmation", assignee=Assignee.SYSTEM)
def confirm_continue() -> State:
class ProvisioningResultPage(FormPage):
class Config:
title = "Please confirm before continuing"
info_label: Label = ( # type: ignore
"ISIS metric has been set to 9000, please confirm to continue the workflow when ready."
)
yield ProvisioningResultPage
return {}
@step("[DRY RUN] Disable configuration on old router")
def disable_old_config_dry(
subscription: Iptrunk,
new_node: Router,
new_lag_interface: str,
new_lag_member_interfaces: list[str],
replace_index: int,
process_id: UUIDstr,
) -> State:
provisioning_proxy.migrate_ip_trunk(
subscription,
new_node,
new_lag_interface,
new_lag_member_interfaces,
replace_index,
process_id,
"VERB NEEDS TO BE UPDATED",
)
return {
"subscription": subscription,
"label_text": "[DRY RUN] Migrating old trunk interface, please refresh to get the results of the playbook.",
}
@step("[REAL] Disable configuration on old router")
def disable_old_config_real(
subscription: Iptrunk,
new_node: Router,
new_lag_interface: str,
new_lag_member_interfaces: list[str],
replace_index: int,
process_id: UUIDstr,
) -> State:
provisioning_proxy.migrate_ip_trunk(
subscription,
new_node,
new_lag_interface,
new_lag_member_interfaces,
replace_index,
process_id,
"VERB NEEDS TO BE UPDATED",
False,
)
logger.warning("Playbook verb is not yet properly set.")
return {
"subscription": subscription,
"label_text": "Migrating old trunk interface, please refresh to get the results of the playbook.",
}
@step("[DRY RUN] Deploy configuration on new router")
def deploy_new_config_dry(
subscription: Iptrunk,
new_node: Router,
new_lag_interface: str,
new_lag_member_interfaces: list[str],
replace_index: int,
process_id: UUIDstr,
) -> State:
provisioning_proxy.migrate_ip_trunk(
subscription,
new_node,
new_lag_interface,
new_lag_member_interfaces,
replace_index,
process_id,
"VERB NEEDS TO BE UPDATED",
)
logger.warning("Playbook verb is not yet properly set.")
return {
"subscription": subscription,
"label_text": "[DRY RUN] Deploying new trunk interface, please refresh to get the results of the playbook.",
}
@step("Deploy configuration on new router")
def deploy_new_config_real(
subscription: Iptrunk,
new_node: Router,
new_lag_interface: str,
new_lag_member_interfaces: list[str],
replace_index: int,
process_id: UUIDstr,
) -> State:
provisioning_proxy.migrate_ip_trunk(
subscription,
new_node,
new_lag_interface,
new_lag_member_interfaces,
replace_index,
process_id,
"VERB NEEDS TO BE UPDATED",
False,
)
logger.warning("Playbook verb is not yet properly set.")
return {
"subscription": subscription,
"label_text": "Deploying new trunk interface, please refresh to get the results of the playbook.",
}
@step("Run interface checks")
def run_interface_checks(
subscription: Iptrunk,
new_node: Router,
new_lag_interface: str,
new_lag_member_interfaces: list[str],
replace_index: int,
process_id: UUIDstr,
) -> State:
provisioning_proxy.migrate_ip_trunk(
subscription,
new_node,
new_lag_interface,
new_lag_member_interfaces,
replace_index,
process_id,
"MIGRATION_INTERFACE_CHECK",
)
logger.warning("Playbook verb is not yet properly set.")
return {
"subscription": subscription,
"label_text": "Running checks on the new trunk interface, please refresh to get the results of the playbook.",
}
@step("Deploy configuration on new router")
def deploy_new_isis(
subscription: Iptrunk,
new_node: Router,
new_lag_interface: str,
new_lag_member_interfaces: list[str],
replace_index: int,
process_id: UUIDstr,
) -> State:
provisioning_proxy.migrate_ip_trunk(
subscription,
new_node,
new_lag_interface,
new_lag_member_interfaces,
replace_index,
process_id,
"VERB NEEDS TO BE UPDATED",
False,
)
logger.warning("Playbook verb is not yet properly set.")
return {
"subscription": subscription,
"label_text": "Updating new ISIS metric, please refresh to get the results of the playbook.",
}
@step("Check ISIS metric")
def check_isis(subscription: Iptrunk, process_id: UUIDstr) -> State:
provisioning_proxy.check_ip_trunk(subscription, process_id, "VERB NEEDS TO BE UPDATED")
logger.warning("Playbook verb is not yet properly set.")
return {
"subscription": subscription,
"label_text": "Checking ISIS functionality, please refresh to get the results of the playbook.",
}
@step("[DRY RUN] Delete configuration on old router")
def delete_old_config_dry(
subscription: Iptrunk,
new_node: Router,
new_lag_interface: str,
new_lag_member_interfaces: list[str],
replace_index: int,
process_id: UUIDstr,
) -> State:
provisioning_proxy.migrate_ip_trunk(
subscription,
new_node,
new_lag_interface,
new_lag_member_interfaces,
replace_index,
process_id,
"VERB NEEDS TO BE UPDATED",
)
logger.warning("Playbook verb is not yet properly set.")
return {
"subscription": subscription,
"label_text": "[DRY RUN] Removing configuration from old router,"
"please refresh to get the results of the playbook.",
}
@step("Delete configuration on old router")
def delete_old_config_real(
subscription: Iptrunk,
new_node: Router,
new_lag_interface: str,
new_lag_member_interfaces: list[str],
replace_index: int,
process_id: UUIDstr,
) -> State:
provisioning_proxy.migrate_ip_trunk(
subscription,
new_node,
new_lag_interface,
new_lag_member_interfaces,
replace_index,
process_id,
"VERB NEEDS TO BE UPDATED",
False,
)
logger.warning("Playbook verb is not yet properly set.")
return {
"subscription": subscription,
"label_text": "Removing configuration from old router, please refresh to get the results of the playbook.",
}
@step("Update IPAM")
def update_ipam(subscription: Iptrunk) -> State:
pass
return {"subscription": subscription}
@step("Update subscription model")
def update_subscription_model(
subscription: Iptrunk,
replace_index: int,
new_node: UUIDstr,
new_lag_interface: str,
new_lag_member_interfaces: list[str],
) -> State:
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 = new_lag_member_interfaces
@step("Hilversum Stinks")
def temp_test_step(subscription: Iptrunk) -> State:
return {"test": subscription}
return {"subscription": subscription}
@workflow(
......@@ -89,14 +363,25 @@ def temp_test_step(subscription: Iptrunk) -> State:
)
def migrate_iptrunk() -> StepList:
return (
init
>> store_process_subscription(Target.MODIFY)
>> unsync
>> temp_test_step
# >> set ISIS to 9000
# >> wait confirm
# >> disable config
# >>
>> resync
>> done
init
>> store_process_subscription(Target.MODIFY)
>> unsync
>> set_isis_to_9000
>> confirm_continue
>> pp_interaction(disable_old_config_dry, 3)
>> pp_interaction(disable_old_config_real, 3)
>> pp_interaction(deploy_new_config_dry, 3)
>> pp_interaction(deploy_new_config_real, 3)
>> confirm_continue
>> pp_interaction(run_interface_checks, 3)
>> pp_interaction(deploy_new_isis, 3)
>> pp_interaction(check_isis, 3)
>> confirm_continue
>> restore_isis_metric
>> pp_interaction(delete_old_config_dry, 3)
>> pp_interaction(delete_old_config_real, 3)
>> update_ipam
>> update_subscription_model
>> resync
>> done
)
......@@ -7,6 +7,7 @@ from orchestrator.types import FormGenerator, State, SubscriptionLifecycle, UUID
from orchestrator.workflow import StepList, conditional, done, init, step, workflow
from orchestrator.workflows.steps import resync, set_status, store_process_subscription, unsync
from orchestrator.workflows.utils import wrap_modify_initial_input_form
from workflows.iptrunk import set_isis_to_9000
from gso.products.product_types.iptrunk import Iptrunk
from gso.services import ipam, provisioning_proxy
......@@ -27,22 +28,13 @@ def initial_input_form_generator() -> FormGenerator:
return user_input.dict()
@step("Set iptrunk ISIS metric to 9000")
def update_isis_metric(subscription: Iptrunk) -> State:
subscription.iptrunk.iptrunk_isis_metric = 9000
return {"subscription": subscription}
@step("Drain traffic from trunk")
def drain_traffic_from_ip_trunk(subscription: Iptrunk, process_id: UUIDstr) -> State:
provisioning_proxy.provision_ip_trunk(subscription, process_id, "isis_interface", False)
return {
"subscription": subscription,
"label_text": "This is setting the ISIS metric of the trunk to 9000"
"trunk. "
"Press refresh to get the results\n"
"When traffic is drained, confirm to continue",
"label_text": "This is setting the ISIS metric of the trunk to 9000. Press refresh to get the results."
"When traffic is drained, confirm to continue.",
}
......@@ -94,7 +86,7 @@ def terminate_iptrunk() -> StepList:
run_ipam_steps = conditional(lambda state: state.get("clean_up_ipam", True))
config_steps = (
StepList([update_isis_metric])
StepList([set_isis_to_9000])
>> pp_interaction(drain_traffic_from_ip_trunk, 3)
>> pp_interaction(deprovision_ip_trunk_dry, 3)
>> pp_interaction(deprovision_ip_trunk_real, 3)
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment