Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found
Select Git revision
  • 1048-service-config-backfilling
  • NAT-1154-import-edge-port-update
  • develop
  • feature/10GGBS-NAT-980
  • feature/NAT-1150-model-commecial-peers
  • feature/NAT-1182-rename-geant-plus-descriptions
  • feature/NAT-732-ias-to-re-interconnect
  • feature/add-moodi-wf-to-router
  • feature/mass-base-config-redeploy
  • feature/nat-1211-edgeport-lacp-xmit
  • feature/rename-geant-plus-descriptions
  • fix/NAT-1009/fix-redeploy-base-config-if-there-is-a-vprn
  • fix/l3-imports
  • fix/nat-1120-sdp-validation
  • master
  • update_change_log
  • 0.1
  • 0.2
  • 0.3
  • 0.4
  • 0.5
  • 0.6
  • 0.7
  • 0.8
  • 0.9
  • 1.0
  • 1.1
  • 1.4
  • 1.5
  • 2.0
  • 2.1
  • 2.10
  • 2.11
  • 2.12
  • 2.13
  • 2.14
  • 2.15
  • 2.16
  • 2.17
  • 2.18
  • 2.19
  • 2.2
  • 2.20
  • 2.21
  • 2.22
  • 2.23
  • 2.24
  • 2.25
  • 2.26
  • 2.27
  • 2.28
  • 2.29
  • 2.3
  • 2.31
  • 2.32
  • 2.33
  • 2.34
  • 2.35
  • 2.36
  • 2.37
  • 2.38
  • 2.39
  • 2.4
  • 2.40
  • 2.41
  • 2.42
  • 2.43
  • 2.44
  • 2.45
  • 2.46
  • 2.47
  • 2.48
  • 2.5
  • 2.6
  • 2.7
  • 2.8
  • 2.9
  • 3.0
  • 3.1
  • 3.2
  • 3.3
  • 3.4
  • 3.5
  • 3.6
  • 3.7
  • 3.8
  • Lime-Seal
87 results

Target

Select target project
  • goat/gap/geant-service-orchestrator
1 result
Select Git revision
  • 1048-service-config-backfilling
  • NAT-1154-import-edge-port-update
  • develop
  • feature/10GGBS-NAT-980
  • feature/NAT-1150-model-commecial-peers
  • feature/NAT-1182-rename-geant-plus-descriptions
  • feature/NAT-732-ias-to-re-interconnect
  • feature/add-moodi-wf-to-router
  • feature/mass-base-config-redeploy
  • feature/nat-1211-edgeport-lacp-xmit
  • feature/rename-geant-plus-descriptions
  • fix/NAT-1009/fix-redeploy-base-config-if-there-is-a-vprn
  • fix/l3-imports
  • fix/nat-1120-sdp-validation
  • master
  • update_change_log
  • 0.1
  • 0.2
  • 0.3
  • 0.4
  • 0.5
  • 0.6
  • 0.7
  • 0.8
  • 0.9
  • 1.0
  • 1.1
  • 1.4
  • 1.5
  • 2.0
  • 2.1
  • 2.10
  • 2.11
  • 2.12
  • 2.13
  • 2.14
  • 2.15
  • 2.16
  • 2.17
  • 2.18
  • 2.19
  • 2.2
  • 2.20
  • 2.21
  • 2.22
  • 2.23
  • 2.24
  • 2.25
  • 2.26
  • 2.27
  • 2.28
  • 2.29
  • 2.3
  • 2.31
  • 2.32
  • 2.33
  • 2.34
  • 2.35
  • 2.36
  • 2.37
  • 2.38
  • 2.39
  • 2.4
  • 2.40
  • 2.41
  • 2.42
  • 2.43
  • 2.44
  • 2.45
  • 2.46
  • 2.47
  • 2.48
  • 2.5
  • 2.6
  • 2.7
  • 2.8
  • 2.9
  • 3.0
  • 3.1
  • 3.2
  • 3.3
  • 3.4
  • 3.5
  • 3.6
  • 3.7
  • 3.8
  • Lime-Seal
87 results
Show changes
Commits on Source (8)
"""Add IP Trunk migration workflow.
Revision ID: e68720f2ec32
Revises: a6eefd32c4f7
Create Date: 2023-08-16 14:48:00.227803
"""
import sqlalchemy as sa
from alembic import op
# revision identifiers, used by Alembic.
revision = 'e68720f2ec32'
down_revision = 'a6eefd32c4f7'
branch_labels = None
depends_on = None
from orchestrator.migrations.helpers import create_workflow, delete_workflow
new_workflows = [
{
"name": "migrate_iptrunk",
"target": "MODIFY",
"description": "Migrate an IP Trunk",
"product_type": "Iptrunk"
}
]
def upgrade() -> None:
conn = op.get_bind()
for workflow in new_workflows:
create_workflow(conn, workflow)
def downgrade() -> None:
conn = op.get_bind()
for workflow in new_workflows:
delete_workflow(conn, workflow["name"])
"""IP trunk product block that has all parameters of a subscription throughout its lifecycle."""
import ipaddress
from typing import Optional
from typing import Optional, TypeVar
from orchestrator.domain.base import ProductBlockModel
from orchestrator.forms.validators import UniqueConstrainedList
from orchestrator.types import SubscriptionLifecycle, strEnum
from pydantic import Field
......@@ -15,6 +16,40 @@ class IptrunkType(strEnum):
LEASED = "Leased"
T = TypeVar("T", covariant=True)
class IptrunkSides(UniqueConstrainedList[T]): # type: ignore
min_items = 2
max_items = 2
class IptrunkSideBlockInactive(
ProductBlockModel, lifecycle=[SubscriptionLifecycle.INITIAL], product_block_name="IptrunkSideBlock"
):
iptrunk_side_node: RouterBlockInactive
iptrunk_side_ae_iface: Optional[str] = None
iptrunk_side_ae_geant_a_sid: Optional[str] = None
iptrunk_side_ae_members: list[str] = Field(default_factory=list)
iptrunk_side_ae_members_description: list[str] = Field(default_factory=list)
class IptrunkSideBlockProvisioning(IptrunkSideBlockInactive, lifecycle=[SubscriptionLifecycle.PROVISIONING]):
iptrunk_side_node: RouterBlockProvisioning
iptrunk_side_ae_iface: Optional[str] = None
iptrunk_side_ae_geant_a_sid: Optional[str] = None
iptrunk_side_ae_members: list[str] = Field(default_factory=list)
iptrunk_side_ae_members_description: list[str] = Field(default_factory=list)
class IptrunkSideBlock(IptrunkSideBlockProvisioning, lifecycle=[SubscriptionLifecycle.ACTIVE]):
iptrunk_side_node: RouterBlock
iptrunk_side_ae_iface: Optional[str] = None
iptrunk_side_ae_geant_a_sid: Optional[str] = None
iptrunk_side_ae_members: list[str] = Field(default_factory=list)
iptrunk_side_ae_members_description: list[str] = Field(default_factory=list)
class IptrunkBlockInactive(
ProductBlockModel, lifecycle=[SubscriptionLifecycle.INITIAL], product_block_name="IptrunkBlock"
):
......@@ -29,17 +64,7 @@ class IptrunkBlockInactive(
iptrunk_ipv4_network: Optional[ipaddress.IPv4Network] = None
iptrunk_ipv6_network: Optional[ipaddress.IPv6Network] = None
#
iptrunk_sideA_node: RouterBlockInactive
iptrunk_sideA_ae_iface: Optional[str] = None
iptrunk_sideA_ae_geant_a_sid: Optional[str] = None
iptrunk_sideA_ae_members: list[str] = Field(default_factory=list)
iptrunk_sideA_ae_members_description: list[str] = Field(default_factory=list)
#
iptrunk_sideB_node: RouterBlockInactive
iptrunk_sideB_ae_iface: Optional[str] = None
iptrunk_sideB_ae_geant_a_sid: Optional[str] = None
iptrunk_sideB_ae_members: list[str] = Field(default_factory=list)
iptrunk_sideB_ae_members_description: list[str] = Field(default_factory=list)
iptrunk_sides: IptrunkSides[IptrunkSideBlockInactive]
class IptrunkBlockProvisioning(IptrunkBlockInactive, lifecycle=[SubscriptionLifecycle.PROVISIONING]):
......@@ -54,17 +79,7 @@ class IptrunkBlockProvisioning(IptrunkBlockInactive, lifecycle=[SubscriptionLife
iptrunk_ipv4_network: Optional[ipaddress.IPv4Network] = None
iptrunk_ipv6_network: Optional[ipaddress.IPv6Network] = None
#
iptrunk_sideA_node: RouterBlockProvisioning
iptrunk_sideA_ae_iface: Optional[str] = None
iptrunk_sideA_ae_geant_a_sid: Optional[str] = None
iptrunk_sideA_ae_members: list[str] = Field(default_factory=list)
iptrunk_sideA_ae_members_description: list[str] = Field(default_factory=list)
#
iptrunk_sideB_node: RouterBlockProvisioning
iptrunk_sideB_ae_iface: Optional[str] = None
iptrunk_sideB_ae_geant_a_sid: Optional[str] = None
iptrunk_sideB_ae_members: list[str] = Field(default_factory=list)
iptrunk_sideB_ae_members_description: list[str] = Field(default_factory=list)
iptrunk_sides: IptrunkSides[IptrunkSideBlockProvisioning]
class IptrunkBlock(IptrunkBlockProvisioning, lifecycle=[SubscriptionLifecycle.ACTIVE]):
......@@ -86,22 +101,4 @@ class IptrunkBlock(IptrunkBlockProvisioning, lifecycle=[SubscriptionLifecycle.AC
"""The IPv4 network used for this trunk."""
iptrunk_ipv6_network: ipaddress.IPv6Network
"""The IPv6 network used for this trunk."""
#
iptrunk_sideA_node: RouterBlock
"""The router that hosts the A side of the trunk."""
iptrunk_sideA_ae_iface: str
"""The name of the interface on which the trunk connects."""
iptrunk_sideA_ae_geant_a_sid: str
"""The service ID of the interface."""
iptrunk_sideA_ae_members: list[str] = Field(default_factory=list)
"""A list of interface members that make up the aggregated Ethernet interface."""
iptrunk_sideA_ae_members_description: list[str] = Field(default_factory=list)
"""The list of descriptions that describe the list of interface members."""
#
iptrunk_sideB_node: RouterBlock
"""The router that hosts the B side of the trunk. It possesses the same attributes as the A-side, including the
interfaces and its descriptions."""
iptrunk_sideB_ae_iface: str
iptrunk_sideB_ae_geant_a_sid: str
iptrunk_sideB_ae_members: list[str] = Field(default_factory=list)
iptrunk_sideB_ae_members_description: list[str] = Field(default_factory=list)
iptrunk_sides: IptrunkSides[IptrunkSideBlock]
......@@ -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.
......
......@@ -30,13 +30,15 @@
"iptrunk_sideB_ae_geant_a_sid": "GÉANT A-SID",
"iptrunk_sideB_ae_members": "Aggregated Ethernet member interface names",
"iptrunk_sideB_ae_members_descriptions": "Aggregated Ethernet member interface descriptions",
"migrate_to_different_site": "Migrating to a different Site",
"remove_configuration": "Remove configuration from the router",
"clean_up_ipam": "Clean up related entries in IPAM"
}
},
"workflow": {
"modify_isis_metric": "Modify the ISIS metric",
"modify_trunk_interface": "Modify IP Trunk interface",
"confirm_info": "Please verify this form looks correct."
"modify_isis_metric": "Modify the ISIS metric",
"modify_trunk_interface": "Modify IP Trunk interface",
"migrate_iptrunk": "Migrate IP Trunk",
"confirm_info": "Please verify this form looks correct."
}
}
"""Initialisation class that imports all workflows into {term}`GSO`."""
from orchestrator.workflows import LazyWorkflowInstance
LazyWorkflowInstance("gso.workflows.router.create_router", "create_router")
LazyWorkflowInstance("gso.workflows.router.terminate_router", "terminate_router")
LazyWorkflowInstance("gso.workflows.iptrunk.create_iptrunk", "create_iptrunk")
LazyWorkflowInstance("gso.workflows.iptrunk.modify_isis_metric", "modify_isis_metric")
LazyWorkflowInstance("gso.workflows.iptrunk.modify_trunk_interface", "modify_trunk_interface")
LazyWorkflowInstance("gso.workflows.iptrunk.migrate_iptrunk", "migrate_iptrunk")
LazyWorkflowInstance("gso.workflows.iptrunk.terminate_iptrunk", "terminate_iptrunk")
LazyWorkflowInstance("gso.workflows.iptrunk.modify_isis_metric", "modify_isis_metric")
LazyWorkflowInstance("gso.workflows.router.create_router", "create_router")
LazyWorkflowInstance("gso.workflows.router.terminate_router", "terminate_router")
LazyWorkflowInstance("gso.workflows.site.create_site", "create_site")
LazyWorkflowInstance("gso.workflows.tasks.import_site", "import_site")
LazyWorkflowInstance("gso.workflows.tasks.import_router", "import_router")
......
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 orchestrator.db.models import ProductTable, SubscriptionTable
from orchestrator.forms import FormPage
from orchestrator.forms.validators import Choice, UniqueConstrainedList
from orchestrator.targets import Target
......@@ -128,17 +129,17 @@ def initialize_subscription(
subscription.iptrunk.iptrunk_isis_metric = 9000
subscription.iptrunk.iptrunk_minimum_links = iptrunk_minimum_links
subscription.iptrunk.iptrunk_sideA_node = Router.from_subscription(iptrunk_sideA_node_id).router
subscription.iptrunk.iptrunk_sideA_ae_iface = iptrunk_sideA_ae_iface
subscription.iptrunk.iptrunk_sideA_ae_geant_a_sid = iptrunk_sideA_ae_geant_a_sid
subscription.iptrunk.iptrunk_sideA_ae_members = iptrunk_sideA_ae_members
subscription.iptrunk.iptrunk_sideA_ae_members_description = iptrunk_sideA_ae_members_descriptions
subscription.iptrunk.iptrunk_sides[0].iptrunk_side_node = Router.from_subscription(iptrunk_sideA_node_id).router
subscription.iptrunk.iptrunk_sides[0].iptrunk_side_ae_iface = iptrunk_sideA_ae_iface
subscription.iptrunk.iptrunk_sides[0].iptrunk_side_ae_geant_a_sid = iptrunk_sideA_ae_geant_a_sid
subscription.iptrunk.iptrunk_sides[0].iptrunk_side_ae_members = iptrunk_sideA_ae_members
subscription.iptrunk.iptrunk_sides[0].iptrunk_side_ae_members_description = iptrunk_sideA_ae_members_descriptions
subscription.iptrunk.iptrunk_sideB_node = Router.from_subscription(iptrunk_sideB_node_id).router
subscription.iptrunk.iptrunk_sideB_ae_iface = iptrunk_sideB_ae_iface
subscription.iptrunk.iptrunk_sideB_ae_geant_a_sid = iptrunk_sideB_ae_geant_a_sid
subscription.iptrunk.iptrunk_sideB_ae_members = iptrunk_sideB_ae_members
subscription.iptrunk.iptrunk_sideB_ae_members_description = iptrunk_sideB_ae_members_descriptions
subscription.iptrunk.iptrunk_sides[1].iptrunk_side_node = Router.from_subscription(iptrunk_sideB_node_id).router
subscription.iptrunk.iptrunk_sides[1].iptrunk_side_ae_iface = iptrunk_sideB_ae_iface
subscription.iptrunk.iptrunk_sides[1].iptrunk_side_ae_geant_a_sid = iptrunk_sideB_ae_geant_a_sid
subscription.iptrunk.iptrunk_sides[1].iptrunk_side_ae_members = iptrunk_sideB_ae_members
subscription.iptrunk.iptrunk_sides[1].iptrunk_side_ae_members_description = iptrunk_sideB_ae_members_descriptions
subscription.description = f"IP trunk, geant_s_sid:{geant_s_sid}"
subscription = IptrunkProvisioning.from_other_lifecycle(subscription, SubscriptionLifecycle.PROVISIONING)
......@@ -202,7 +203,7 @@ def check_ip_trunk_isis(subscription: IptrunkProvisioning, process_id: UUIDstr)
return {
"subscription": subscription,
"label_text": "Checking ISIS adjacencies, please refresh to get the results of the playbook.",
"label_text": "Checking ISIS adjacency, please refresh to get the results of the playbook.",
}
......@@ -226,26 +227,6 @@ def provision_ip_trunk_ldp_iface_real(subscription: IptrunkProvisioning, process
}
@step("Provision IP trunk LLDP interface [DRY RUN]")
def provision_ip_trunk_lldp_iface_dry(subscription: IptrunkProvisioning, process_id: UUIDstr) -> State:
provisioning_proxy.provision_ip_trunk(subscription, process_id, "lldp_interface")
return {
"subscription": subscription,
"label_text": "[DRY RUN] Provisioning LLDP interface, please refresh to get the results of the playbook.",
}
@step("Provision IP trunk LLDP interface [FOR REAL]")
def provision_ip_trunk_lldp_iface_real(subscription: IptrunkProvisioning, process_id: UUIDstr) -> State:
provisioning_proxy.provision_ip_trunk(subscription, process_id, "lldp_interface", False)
return {
"subscription": subscription,
"label_text": "Provisioning LLDP interface, please refresh to get the results of the playbook.",
}
@workflow(
"Create IP trunk",
initial_input_form=wrap_create_initial_input_form(initial_input_form_generator),
......@@ -264,8 +245,6 @@ def create_iptrunk() -> StepList:
>> pp_interaction(provision_ip_trunk_isis_iface_dry, 3)
>> pp_interaction(provision_ip_trunk_isis_iface_real, 3)
>> pp_interaction(check_ip_trunk_isis, 2)
>> pp_interaction(provision_ip_trunk_lldp_iface_dry, 3)
>> pp_interaction(provision_ip_trunk_lldp_iface_real, 3)
>> set_status(SubscriptionLifecycle.ACTIVE)
>> resync
>> done
......
import re
from logging import getLogger
from typing import NoReturn, Optional
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, Label, UniqueConstrainedList
from orchestrator.targets import Target
from orchestrator.types import FormGenerator, State, UUIDstr
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
logger = getLogger(__name__)
def initial_input_form_generator(subscription_id: UUIDstr) -> FormGenerator:
subscription = Iptrunk.from_subscription(subscription_id)
sides_dict = {
str(side.iptrunk_side_node.subscription.subscription_id): side.iptrunk_side_node.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()) # type: ignore
)
class OldSideIptrunkForm(FormPage):
class Config:
title = (
f"Subscription {subscription.iptrunk.geant_s_sid} from "
f"{subscription.iptrunk.iptrunk_sides[0].iptrunk_side_node.router_fqdn} to "
f"{subscription.iptrunk.iptrunk_sides[1].iptrunk_side_node.router_fqdn}"
)
replace_side: ReplacedSide # type: ignore
warning_label: Label = "Are we moving to a different Site?" # type: ignore
migrate_to_different_site: Optional[bool] = False
old_side_input = yield OldSideIptrunkForm
routers = {}
for router_id, router_description in (
SubscriptionTable.query.join(ProductTable)
.filter(
ProductTable.product_type == "Router",
SubscriptionTable.status == "active",
)
.with_entities(SubscriptionTable.subscription_id, SubscriptionTable.description)
.all()
):
if router_id not in [
subscription.iptrunk.iptrunk_sides[0].iptrunk_side_node.subscription.subscription_id,
subscription.iptrunk.iptrunk_sides[1].iptrunk_side_node.subscription.subscription_id,
]:
current_router = Router.from_subscription(router_id)
old_side_site_id = Router.from_subscription(old_side_input.replace_side).router.router_site
if (
not old_side_input.migrate_to_different_site
and current_router.router.router_site.subscription.subscription_id != old_side_site_id
):
continue
routers[str(router_id)] = router_description
NewRouterEnum = Choice("Select a new router", zip(routers.keys(), routers.items())) # type: ignore
class LagMemberList(UniqueConstrainedList[str]):
min_items = len(subscription.iptrunk.iptrunk_sides[0].iptrunk_side_ae_members)
max_items = len(subscription.iptrunk.iptrunk_sides[1].iptrunk_side_ae_members)
class NewSideIptrunkForm(FormPage):
class Config:
title = (
f"Subscription {subscription.iptrunk.geant_s_sid} from "
f"{subscription.iptrunk.iptrunk_sides[0].iptrunk_side_node.router_fqdn} to "
f"{subscription.iptrunk.iptrunk_sides[1].iptrunk_side_node.router_fqdn}"
)
new_node: NewRouterEnum # type: ignore
new_lag_interface: str
new_lag_member_interfaces: LagMemberList
@validator("new_lag_interface", allow_reuse=True, pre=True, always=True)
def lag_interface_proper_name(cls, new_lag_name: str) -> str | NoReturn:
nokia_lag_re = re.compile("^lag-\\d+$")
juniper_lag_re = re.compile("^ae\\d{1,2}$")
if nokia_lag_re.match(new_lag_name) or juniper_lag_re.match(new_lag_name):
return new_lag_name
raise ValueError("Invalid LAG name, please try again.")
new_side_input = yield NewSideIptrunkForm
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: # noqa: RET505
return 1
raise ValueError("Invalid Router id provided to be replaced!")
replace_index = _find_updated_side_of_trunk(subscription, old_side_input.replace_side)
return old_side_input.dict() | new_side_input.dict() | {"replace_index": replace_index}
@inputstep("Wait for confirmation", assignee=Assignee.SYSTEM)
def confirm_continue() -> FormGenerator:
class ProvisioningResultPage(FormPage):
class Config:
title = "Please confirm before continuing"
info_label: Label = (
"ISIS metric has been set to 9000, please confirm to continue the workflow when ready." # type: ignore
)
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
return {"subscription": subscription}
@workflow(
"Migrate an IP Trunk",
initial_input_form=wrap_modify_initial_input_form(initial_input_form_generator),
target=Target.MODIFY,
)
def migrate_iptrunk() -> StepList:
return (
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
)
......@@ -37,13 +37,13 @@ def initial_input_form_generator(subscription_id: UUIDstr) -> FormGenerator:
class Config:
title = "Provide subscription details for side A of the trunk."
iptrunk_sideA_node: str = ReadOnlyField(subscription.iptrunk.iptrunk_sideA_node.router_fqdn)
iptrunk_sideA_ae_iface: str = ReadOnlyField(subscription.iptrunk.iptrunk_sideA_ae_iface)
iptrunk_sideA_ae_geant_a_sid: str = subscription.iptrunk.iptrunk_sideA_ae_geant_a_sid
iptrunk_sideA_node: str = ReadOnlyField(subscription.iptrunk.iptrunk_sides[0].iptrunk_side_node.router_fqdn)
iptrunk_sideA_ae_iface: str = ReadOnlyField(subscription.iptrunk.iptrunk_sides[0].iptrunk_ae_iface)
iptrunk_sideA_ae_geant_a_sid: str = subscription.iptrunk.iptrunk_sides[0].iptrunk_side_ae_geant_a_sid
iptrunk_sideA_ae_members: AeMembersListA = subscription.iptrunk.iptrunk_sideA_ae_members # type: ignore
iptrunk_sideA_ae_members_descriptions: AeMembersListA = (
subscription.iptrunk.iptrunk_sideA_ae_members_description # type: ignore
)
iptrunk_sideA_ae_members_descriptions: AeMembersListA = subscription.iptrunk.iptrunk_sides[
0
].iptrunk_side_ae_members_description
user_input_side_a = yield ModifyIptrunkSideAForm
......@@ -55,13 +55,13 @@ def initial_input_form_generator(subscription_id: UUIDstr) -> FormGenerator:
class Config:
title = "Provide subscription details for side B of the trunk."
iptrunk_sideB_node: str = ReadOnlyField(subscription.iptrunk.iptrunk_sideB_node.router_fqdn)
iptrunk_sideB_ae_iface: str = ReadOnlyField(subscription.iptrunk.iptrunk_sideB_ae_iface)
iptrunk_sideB_ae_geant_a_sid: str = subscription.iptrunk.iptrunk_sideB_ae_geant_a_sid
iptrunk_sideB_node: str = ReadOnlyField(subscription.iptrunk.iptrunk_sides[1].iptrunk_side_node.router_fqdn)
iptrunk_sideB_ae_iface: str = ReadOnlyField(subscription.iptrunk.iptrunk_sides[1].iptrunk_side_ae_iface)
iptrunk_sideB_ae_geant_a_sid: str = subscription.iptrunk.iptrunk_sides[1].iptrunk_side_ae_geant_a_sid
iptrunk_sideB_ae_members: AeMembersListB = subscription.iptrunk.iptrunk_sideB_ae_members # type: ignore
iptrunk_sideB_ae_members_descriptions: AeMembersListB = (
subscription.iptrunk.iptrunk_sideB_ae_members_description # type: ignore
)
iptrunk_sideB_ae_members_descriptions: AeMembersListB = subscription.iptrunk.iptrunk_sides[
1
].iptrunk_side_ae_members_description
user_input_side_b = yield ModifyIptrunkSideBForm
......@@ -89,13 +89,13 @@ def modify_iptrunk_subscription(
subscription.iptrunk.iptrunk_speed = iptrunk_speed
subscription.iptrunk.iptrunk_minimum_links = iptrunk_minimum_links
subscription.iptrunk.iptrunk_sideA_ae_geant_a_sid = iptrunk_sideA_ae_geant_a_sid
subscription.iptrunk.iptrunk_sideA_ae_members = iptrunk_sideA_ae_members
subscription.iptrunk.iptrunk_sideA_ae_members_description = iptrunk_sideA_ae_members_descriptions
subscription.iptrunk.iptrunk_sides[0].iptrunk_side_ae_geant_a_sid = iptrunk_sideA_ae_geant_a_sid
subscription.iptrunk.iptrunk_sides[0].iptrunk_side_ae_members = iptrunk_sideA_ae_members
subscription.iptrunk.iptrunk_sides[0].iptrunk_side_ae_members_description = iptrunk_sideA_ae_members_descriptions
subscription.iptrunk.iptrunk_sideB_ae_geant_a_sid = iptrunk_sideB_ae_geant_a_sid
subscription.iptrunk.iptrunk_sideB_ae_members = iptrunk_sideB_ae_members
subscription.iptrunk.iptrunk_sideB_ae_members_description = iptrunk_sideB_ae_members_descriptions
subscription.iptrunk.iptrunk_sides[1].iptrunk_side_ae_geant_a_sid = iptrunk_sideB_ae_geant_a_sid
subscription.iptrunk.iptrunk_sides[1].iptrunk_side_ae_members = iptrunk_sideB_ae_members
subscription.iptrunk.iptrunk_sides[1].iptrunk_side_ae_members_description = iptrunk_sideB_ae_members_descriptions
subscription.description = f"IP trunk, geant_s_sid:{geant_s_sid}"
......
......@@ -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)
......