diff --git a/gso/migrations/versions/2023-09-22_6c986f219e3f_add_router_validation_task.py b/gso/migrations/versions/2023-09-22_6c986f219e3f_add_router_validation_task.py new file mode 100644 index 0000000000000000000000000000000000000000..3d176c13f26ce5beb56c2d29b94c3226b767382b --- /dev/null +++ b/gso/migrations/versions/2023-09-22_6c986f219e3f_add_router_validation_task.py @@ -0,0 +1,39 @@ +"""Add router validation task. + +Revision ID: 6c986f219e3f +Revises: f0764c6f392c +Create Date: 2023-09-22 17:13:01.223133 + +""" +import sqlalchemy as sa +from alembic import op + +# revision identifiers, used by Alembic. +revision = '6c986f219e3f' +down_revision = 'f0764c6f392c' +branch_labels = None +depends_on = None + + +from orchestrator.migrations.helpers import create_workflow, delete_workflow + +new_workflows = [ + { + "name": "validate_router", + "target": "SYSTEM", + "description": "Validate router configuration", + "product_type": "Router" + } +] + + +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"]) diff --git a/gso/migrations/versions/2023-11-21_6dbd6c4c04b4_add_ip_trunk_workflows.py b/gso/migrations/versions/2023-11-21_6dbd6c4c04b4_add_ip_trunk_workflows.py index b2ae1da22f4836940059267a31ed4be661dfd300..e8e81febcde701a09ef26a92f4e2fe65b5799b71 100644 --- a/gso/migrations/versions/2023-11-21_6dbd6c4c04b4_add_ip_trunk_workflows.py +++ b/gso/migrations/versions/2023-11-21_6dbd6c4c04b4_add_ip_trunk_workflows.py @@ -1,7 +1,7 @@ """Add IP Trunk workflows. Revision ID: 6dbd6c4c04b4 -Revises: 815033570ad7 +Revises: 0c31b60487c8 Create Date: 2023-11-21 12:55:29.689402 """ diff --git a/gso/migrations/versions/2023-11-21_815033570ad7_add_router_workflows.py b/gso/migrations/versions/2023-11-21_815033570ad7_add_router_workflows.py index 1d996616d410081c01938ade185fc45455e48046..ed03212a5e681390280bc455d3fe0ff9c7a19f33 100644 --- a/gso/migrations/versions/2023-11-21_815033570ad7_add_router_workflows.py +++ b/gso/migrations/versions/2023-11-21_815033570ad7_add_router_workflows.py @@ -1,7 +1,7 @@ """Add router workflows. Revision ID: 815033570ad7 -Revises: 0c31b60487c8 +Revises: 0c904cf0b66b Create Date: 2023-11-21 12:53:26.413333 """ diff --git a/gso/migrations/versions/2023-12-27_f0764c6f392c_add_twamp_deployment_workflow.py b/gso/migrations/versions/2023-12-27_f0764c6f392c_add_twamp_deployment_workflow.py index 9e9782492fe7b6b1cc24a9a2eba2c92f0ec75be6..c0f3c473a67f0101a5bbf3e9b3991e9281de85b7 100644 --- a/gso/migrations/versions/2023-12-27_f0764c6f392c_add_twamp_deployment_workflow.py +++ b/gso/migrations/versions/2023-12-27_f0764c6f392c_add_twamp_deployment_workflow.py @@ -1,7 +1,7 @@ """Add TWAMP deployment workflow. Revision ID: f0764c6f392c -Revises: 815033570ad7 +Revises: b689d4636694 Create Date: 2023-12-27 14:31:42.285180 """ diff --git a/gso/services/netbox_client.py b/gso/services/netbox_client.py index 212b1fb9d60ec1e26719a64b5074b74b4d03ae0f..570ecbeb1fb1865cc2c433fdf5047491deaa9a57 100644 --- a/gso/services/netbox_client.py +++ b/gso/services/netbox_client.py @@ -223,9 +223,7 @@ class NetboxClient: # Assign interface to :term:`LAG`, ensuring it does not already belong to a :term:`LAG`. if iface.lag: msg = f"The interface: {iface_name} on device: {device_name} already belongs to a LAG: {iface.lag.name}." - raise WorkflowStateError( - msg, - ) + raise WorkflowStateError(msg) iface.lag = lag.id # Set description if provided diff --git a/gso/translations/en-GB.json b/gso/translations/en-GB.json index 9fdf1dfafa8ff3304e5e9cf6b0e36669f8412446..232bcd0a89b6816616de3db2eb03b25b5154e988 100644 --- a/gso/translations/en-GB.json +++ b/gso/translations/en-GB.json @@ -55,6 +55,7 @@ "import_router": "NOT FOR HUMANS -- Finalize import into a Router product", "import_iptrunk": "NOT FOR HUMANS -- Finalize import into an IP trunk product", "import_office_router": "NOT FOR HUMANS -- Finalize import into an Office router product", - "import_super_pop_switch": "NOT FOR HUMANS -- Finalize import into a Super PoP switch" + "import_super_pop_switch": "NOT FOR HUMANS -- Finalize import into a Super PoP switch", + "validate_router": "Validate router configuration" } } diff --git a/gso/workflows/__init__.py b/gso/workflows/__init__.py index cf2eca0f71b724ca482b15107c7cf1844f554918..54d6a8a168442935f481e5f4d2fa6fde6f99919d 100644 --- a/gso/workflows/__init__.py +++ b/gso/workflows/__init__.py @@ -63,3 +63,4 @@ LazyWorkflowInstance("gso.workflows.office_router.create_imported_office_router" # Opengear workflows LazyWorkflowInstance("gso.workflows.opengear.create_imported_opengear", "create_imported_opengear") LazyWorkflowInstance("gso.workflows.opengear.import_opengear", "import_opengear") + diff --git a/gso/workflows/router/create_router.py b/gso/workflows/router/create_router.py index 700f3f9c86112562d1a1ef127dad209da22b96be..dfa0b27b517c0233231fb11c14f335adc7749e44 100644 --- a/gso/workflows/router/create_router.py +++ b/gso/workflows/router/create_router.py @@ -7,6 +7,7 @@ from orchestrator.forms import FormPage from orchestrator.forms.validators import Choice, Label from orchestrator.targets import Target from orchestrator.types import FormGenerator, State, SubscriptionLifecycle, UUIDstr +from orchestrator.utils.errors import ProcessFailureError from orchestrator.workflow import StepList, conditional, done, init, inputstep, step, workflow from orchestrator.workflows.steps import resync, set_status, store_process_subscription from orchestrator.workflows.utils import wrap_create_initial_input_form @@ -143,13 +144,15 @@ def create_netbox_device(subscription: RouterInactive) -> State: @step("Verify IPAM resources for loopback interface") -def verify_ipam_loopback(subscription: RouterInactive) -> State: - """Validate the :term:`IPAM` resources for the loopback interface.""" +def verify_ipam_loopback(subscription: RouterInactive) -> None: + """Validate the :term:`IPAM` resources for the loopback interface. + + Raises an :class:`orchestrator.utils.errors.ProcessFailureError` if IPAM is configured incorrectly. + """ host_record = infoblox.find_host_by_fqdn(f"lo0.{subscription.router.router_fqdn}") if not host_record or str(subscription.subscription_id) not in host_record.comment: - return {"ipam_warning": "Loopback record is incorrectly configured in IPAM, please investigate this manually!"} - - return {"subscription": subscription} + msg = "Loopback record is incorrectly configured in IPAM, please investigate this manually!" + raise ProcessFailureError(msg) @inputstep("Prompt to reboot", assignee=Assignee.SYSTEM) diff --git a/gso/workflows/tasks/validate_router.py b/gso/workflows/tasks/validate_router.py new file mode 100644 index 0000000000000000000000000000000000000000..18ab6e3e73564efed9bb90997ad5a3bdd13eeac3 --- /dev/null +++ b/gso/workflows/tasks/validate_router.py @@ -0,0 +1,41 @@ +import json + +from orchestrator.targets import Target +from orchestrator.types import State +from orchestrator.utils.json import json_dumps +from orchestrator.workflow import StepList, init, workflow, done, step +from orchestrator.workflows.steps import store_process_subscription, unsync, resync + +from gso.services.provisioning_proxy import pp_interaction, execute_playbook +from gso.workflows.router.create_router import verify_ipam_loopback +from products import Router + + +@step("Validate router configuration") +def validate_router_config(subscription: Router, callback_route: str) -> None: + extra_vars = {"wfo_router_json": json.loads(json_dumps(subscription)), "verb": "validate"} + + execute_playbook( + playbook_name="base_config.yaml", + callback_route=callback_route, + inventory=subscription.router.router_fqdn, + extra_vars=extra_vars + ) + + +@workflow("Validate router configuration", target=Target.SYSTEM) +def validate_router() -> StepList: + """Validate an existing, active Router subscription. + + * Run an Ansible playbook to verify the configuration is intact. + * Verify that the loopback interface is correctly configured in IPAM. + """ + return ( + init + >> store_process_subscription(Target.SYSTEM) + >> unsync + >> pp_interaction(validate_router_config) + >> verify_ipam_loopback + >> resync + >> done + )