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 (4)
"""Update switch domain model.
Revision ID: bd9be532b3f9
Revises: 87a05eddee3e
Create Date: 2024-08-29 11:41:06.936423
"""
import sqlalchemy as sa
from alembic import op
# revision identifiers, used by Alembic.
revision = 'bd9be532b3f9'
down_revision = '87a05eddee3e'
branch_labels = None
depends_on = None
def upgrade() -> None:
conn = op.get_bind()
conn.execute(sa.text("""
UPDATE resource_types SET resource_type='switch_fqdn' WHERE resource_types.resource_type = 'switch_hostname'
"""))
def downgrade() -> None:
conn = op.get_bind()
conn.execute(sa.text("""
UPDATE resource_types SET resource_type='switch_hostname' WHERE resource_types.resource_type = 'switch_fqdn'
"""))
......@@ -26,7 +26,7 @@ class SwitchBlockInactive(
):
"""A switch that's being currently inactive. See :class:`SwitchBlock`."""
switch_hostname: str | None = None
switch_fqdn: str | None = None
switch_ts_port: PortNumber | None = None
switch_site: SiteBlockInactive | None = None
switch_vendor: Vendor | None = None
......@@ -36,18 +36,18 @@ class SwitchBlockInactive(
class SwitchBlockProvisioning(SwitchBlockInactive, lifecycle=[SubscriptionLifecycle.PROVISIONING]):
"""A switch that's being provisioned. See :class:`SwitchBlock`."""
switch_hostname: str
switch_fqdn: str
switch_ts_port: PortNumber
switch_site: SiteBlockProvisioning
switch_vendor: Vendor
switch_model: SwitchModel | None = None
switch_model: SwitchModel
class SwitchBlock(SwitchBlockProvisioning, lifecycle=[SubscriptionLifecycle.ACTIVE]):
"""A switch that's currently deployed in the network."""
#: The hostname of the switch.
switch_hostname: str
#: The :term:`FQDN` of the switch.
switch_fqdn: str
#: The port of the terminal server that this switch is connected to. Used to offer out of band access.
switch_ts_port: PortNumber
#: The :class:`Site` that this switch resides in. Both physically and computationally.
......@@ -55,4 +55,4 @@ class SwitchBlock(SwitchBlockProvisioning, lifecycle=[SubscriptionLifecycle.ACTI
#: The vendor of the switch.
switch_vendor: Vendor
#: The model of the switch.
switch_model: SwitchModel | None = None
switch_model: SwitchModel
......@@ -50,6 +50,9 @@ LazyWorkflowInstance("gso.workflows.router.validate_router", "validate_router")
LazyWorkflowInstance("gso.workflows.router.promote_p_to_pe", "promote_p_to_pe")
LazyWorkflowInstance("gso.workflows.router.modify_kentik_license", "modify_router_kentik_license")
# Switch workflows
LazyWorkflowInstance("gso.workflows.switch.create_switch", "create_switch")
# Site workflows
LazyWorkflowInstance("gso.workflows.site.create_site", "create_site")
LazyWorkflowInstance("gso.workflows.site.modify_site", "modify_site")
......
"""Workflows for switches."""
"""Workflow for activating a switch, making it available to other subscriptions."""
from orchestrator.config.assignee import Assignee
from orchestrator.forms import FormPage
from orchestrator.forms.validators import Label
from orchestrator.targets import Target
from orchestrator.types import FormGenerator, SubscriptionLifecycle, UUIDstr
from orchestrator.workflow import StepList, begin, done, inputstep, workflow
from orchestrator.workflows.steps import resync, set_status, store_process_subscription, unsync
from orchestrator.workflows.utils import wrap_modify_initial_input_form
from gso.products.product_types.switch import Switch
def _initial_input_form(subscription_id: UUIDstr) -> FormGenerator:
switch = Switch.from_subscription(subscription_id)
class ActivateSwitchForm(FormPage):
info_label: Label = "Start approval process for switch activation."
yield ActivateSwitchForm
return {"subscription": switch}
@inputstep("Verify checklist completion", assignee=Assignee.SYSTEM)
def verify_complete_checklist() -> FormGenerator:
"""Ask the operator to provide a link to the completed checklist in SharePoint."""
class VerifyCompleteForm(FormPage):
info_label: Label = "Please enter URL to the completed checklist in SharePoint. Then continue this workflow."
checklist_url: str = ""
user_input = yield VerifyCompleteForm
return {"checklist_url": user_input.model_dump()["checklist_url"]}
@workflow(
"Activate switch", initial_input_form=(wrap_modify_initial_input_form(_initial_input_form)), target=Target.MODIFY
)
def activate_switch() -> StepList:
"""Take a switch and move it from a `PROVISIONING` to an `ACTIVE` state."""
return (
begin
>> store_process_subscription(Target.MODIFY)
>> unsync
>> verify_complete_checklist
>> set_status(SubscriptionLifecycle.ACTIVE)
>> resync
>> done
)
"""A creation workflow for adding a new switch to the subscription database."""
from typing import Self
from orchestrator.config.assignee import Assignee
from orchestrator.forms import FormPage
from orchestrator.targets import Target
from orchestrator.types import FormGenerator, State, SubscriptionLifecycle, UUIDstr
from orchestrator.utils.errors import ProcessFailureError
from orchestrator.workflow import StepList, begin, done, inputstep, step, workflow
from orchestrator.workflows.steps import resync, set_status, store_process_subscription
from orchestrator.workflows.utils import wrap_create_initial_input_form
from pydantic import ConfigDict, model_validator
from pydantic_forms.validators import Label, ReadOnlyField
from gso.products.product_blocks.switch import SwitchModel
from gso.products.product_types.site import Site
from gso.products.product_types.switch import SwitchInactive
from gso.services import infoblox
from gso.services.lso_client import execute_playbook, lso_interaction
from gso.services.partners import get_partner_by_name
from gso.services.sharepoint import SharePointClient
from gso.settings import load_oss_params
from gso.utils.helpers import active_site_selector, generate_fqdn
from gso.utils.shared_enums import PortNumber, Vendor
from gso.utils.types import TTNumber
from gso.utils.workflow_steps import prompt_sharepoint_checklist_url
def _initial_input_form_generator(product_name: str) -> FormGenerator:
"""Input form for creating a new Switch."""
class CreateSwitchForm(FormPage):
model_config = ConfigDict(title=product_name)
tt_number: TTNumber
switch_site: active_site_selector() # type: ignore[valid-type]
hostname: str
ts_port: PortNumber
vendor: ReadOnlyField(Vendor.JUNIPER, default_type=Vendor) # type: ignore[valid-type]
model: ReadOnlyField(SwitchModel.EX3400, default_type=SwitchModel) # type: ignore[valid-type]
@model_validator(mode="after")
def hostname_must_be_available(self) -> Self:
if not self.switch_site:
msg = "Please select a site before setting the hostname."
raise ValueError(msg)
selected_site = Site.from_subscription(self.switch_site).site
input_fqdn = generate_fqdn(self.hostname, selected_site.site_name, selected_site.site_country_code)
if not infoblox.hostname_available(input_fqdn):
msg = f'FQDN "{input_fqdn}" is not available.'
raise ValueError(msg)
return self
user_input = yield CreateSwitchForm
return user_input.model_dump()
@step("Create subscription")
def create_subscription(product: UUIDstr, partner: str) -> State:
"""Create a new subscription object."""
subscription = SwitchInactive.from_product_id(product, get_partner_by_name(partner)["partner_id"])
return {"subscription": subscription}
@step("Initialize subscription")
def initialize_subscription(
subscription: SwitchInactive,
switch_site: str,
ts_port: PortNumber,
vendor: Vendor,
model: SwitchModel,
hostname: str,
) -> State:
"""Initialize the subscription with user input."""
subscription.switch.switch_site = Site.from_subscription(switch_site).site
subscription.switch.switch_fqdn = generate_fqdn(
hostname, subscription.switch.switch_site.site_name, subscription.switch.switch_site.site_country_code
)
subscription.switch.switch_ts_port = ts_port
subscription.switch.switch_vendor = vendor
subscription.switch.switch_model = model
return {"subscription": subscription}
@step("[DRY RUN] Deploy base config")
def deploy_base_config_dry(subscription: dict, tt_number: str, callback_route: str, process_id: UUIDstr) -> None:
"""Perform a dry run of provisioning base config on a switch."""
inventory = subscription["switch"]["switch_fqdn"]
extra_vars = {
"subscription_json": subscription,
"dry_run": True,
"verb": "deploy",
"commit_comment": f"GSO_PROCESS_ID: {process_id} - TT_NUMBER: {tt_number} - Deploy base config",
}
execute_playbook(
playbook_name="switch_base_config.yaml",
callback_route=callback_route,
inventory=inventory,
extra_vars=extra_vars,
)
@step("[FOR REAL] Deploy base config")
def deploy_base_config_real(subscription: dict, tt_number: str, callback_route: str, process_id: UUIDstr) -> None:
"""Provision base config on a switch."""
inventory = subscription["switch"]["switch_fqdn"]
extra_vars = {
"subscription_json": subscription,
"dry_run": False,
"verb": "deploy",
"commit_comment": f"GSO_PROCESS_ID: {process_id} - TT_NUMBER: {tt_number} - Deploy base config",
}
execute_playbook(
playbook_name="switch_base_config.yaml",
callback_route=callback_route,
inventory=inventory,
extra_vars=extra_vars,
)
@inputstep("Prompt for console login", assignee=Assignee.SYSTEM)
def prompt_console_login() -> FormGenerator:
"""Wait for confirmation from an operator that console login is possible."""
class ConsoleLoginPage(FormPage):
model_config = ConfigDict(title="Please confirm before continuing")
info_label: Label = "Please confirm you are able to log in to the switch using out of band connectivity."
yield ConsoleLoginPage
return {}
@inputstep("Prompt IMS insertion", assignee=Assignee.SYSTEM)
def prompt_insert_in_ims() -> FormGenerator:
"""Wait for confirmation from an operator that the switch has been inserted in IMS."""
class IMSPrompt(FormPage):
model_config = ConfigDict(title="Update IMS mediation server")
info_label_1: Label = "Insert the switch into IMS."
info_label_2: Label = "Once this is done, press submit to continue the workflow."
yield IMSPrompt
return {}
@step("Create Netbox device")
def create_netbox_device() -> State:
"""Add the switch as a new device in Netbox."""
return {"netbox_device": "Not implemented."}
@step("Run post-deployment checks")
def run_post_deploy_checks(subscription: dict, callback_route: str) -> None:
"""Workflow step for running checks after installing base config."""
execute_playbook(
playbook_name="switch_base_config_checks.yaml",
callback_route=callback_route,
inventory=subscription["switch"]["switch_fqdn"],
extra_vars={"subscription_json": subscription},
)
@step("Create a new SharePoint checklist")
def create_new_sharepoint_checklist(subscription: SwitchInactive, tt_number: str, process_id: UUIDstr) -> State:
"""Create a new checklist in SharePoint for approving this router."""
if not subscription.switch.switch_fqdn:
msg = "Switch is missing an FQDN."
raise ProcessFailureError(msg, details=subscription.subscription_id)
new_list_item_url = SharePointClient().add_list_item(
list_name="switch",
fields={
"Title": subscription.switch.switch_fqdn,
"TT_NUMBER": tt_number,
"GAP_PROCESS_URL": f"{load_oss_params().GENERAL.public_hostname}/workflows/{process_id}",
},
)
return {"checklist_url": new_list_item_url}
@workflow(
"Create Switch",
initial_input_form=wrap_create_initial_input_form(_initial_input_form_generator),
target=Target.CREATE,
)
def create_switch() -> StepList:
"""Create a new Switch.
* Create a subscription object in the service database
* Deploy base configuration on the switch
* Add the switch to Netbox
* Run a check playbook after deploying base configuration
* Create a new checklist in SharePoint
"""
return (
begin
>> create_subscription
>> store_process_subscription(Target.CREATE)
>> initialize_subscription
>> lso_interaction(deploy_base_config_dry)
>> lso_interaction(deploy_base_config_real)
>> prompt_console_login
>> prompt_insert_in_ims
>> create_netbox_device
>> lso_interaction(run_post_deploy_checks)
>> set_status(SubscriptionLifecycle.PROVISIONING)
>> create_new_sharepoint_checklist
>> prompt_sharepoint_checklist_url
>> resync
>> done
)
"""Workflow for terminating a switch."""
from orchestrator import begin, done, workflow
from orchestrator.forms import FormPage
from orchestrator.targets import Target
from orchestrator.types import SubscriptionLifecycle
from orchestrator.utils.errors import ProcessFailureError
from orchestrator.workflow import StepList, step
from orchestrator.workflows.steps import resync, set_status, store_process_subscription, unsync
from orchestrator.workflows.utils import wrap_modify_initial_input_form
from pydantic_forms.types import FormGenerator, UUIDstr
from pydantic_forms.validators import Label
from gso.products.product_types.switch import Switch
from gso.utils.types import TTNumber
def _input_form_generator(subscription_id: UUIDstr) -> FormGenerator:
"""Input form to confirm that this switch indeed must be terminated."""
switch = Switch.from_subscription(subscription_id)
class TerminateForm(FormPage):
if switch.status == SubscriptionLifecycle.INITIAL:
info_label: Label = (
"This will immediately mark the subscription as terminated, preventing any other workflows from "
"interacting with this product subscription."
)
info_label_2: Label = "ONLY EXECUTE THIS WORKFLOW WHEN YOU ARE ABSOLUTELY SURE WHAT YOU ARE DOING."
tt_number: TTNumber
yield TerminateForm
return {"subscription": switch}
@step("Remove switch from Netbox")
def remove_device_from_netbox(subscription: dict) -> None:
"""Remove the switch from Netbox."""
if subscription["switch"]:
msg = "Removal from Netbox is not implemented."
raise ProcessFailureError(msg)
@workflow(
"Terminate switch",
initial_input_form=wrap_modify_initial_input_form(_input_form_generator),
target=Target.TERMINATE,
)
def terminate_switch() -> StepList:
"""Terminate a switch subscription.
* Remove the switch from Netbox
* Mark the service as terminated in the service database
"""
return (
begin
>> store_process_subscription(Target.TERMINATE)
>> unsync
>> remove_device_from_netbox
>> set_status(SubscriptionLifecycle.TERMINATED)
>> resync
>> done
)