diff --git a/gso/utils/workflow_steps.py b/gso/utils/workflow_steps.py index 6ab00644bef668cecdf1c159e9502ec4879ff8a7..939feff6d433547b9cafbc7390f5c20100d80199 100644 --- a/gso/utils/workflow_steps.py +++ b/gso/utils/workflow_steps.py @@ -6,6 +6,7 @@ from typing import Any from orchestrator import inputstep, step from orchestrator.config.assignee import Assignee from orchestrator.types import State, UUIDstr +from orchestrator.utils.errors import ProcessFailureError from orchestrator.utils.json import json_dumps from pydantic import ConfigDict from pydantic_forms.core import FormPage @@ -121,3 +122,15 @@ def prompt_sharepoint_checklist_url(checklist_url: str) -> FormGenerator: yield SharepointPrompt return {} + + +@step("Detect configuration drift") +def detect_configuration_drift(callback_result: dict) -> None: + """Inspect the diff for deploying configuration, to make sure there is no drift.""" + if callback_result["return_code"] != 0: + # If deployment never was successful in the first place, raise an exception. + raise ProcessFailureError(message="Provisioning proxy failure", details=callback_result) + + output_lines = callback_result["output"] + if any(str.startswith(line, "-") or str.startswith(line, "+") for line in callback_result["output"]): + raise ProcessFailureError(message="Configuration drift detected", details=output_lines) diff --git a/gso/workflows/router/validate_router.py b/gso/workflows/router/validate_router.py index ab12ffd831cc9fe0717f24963578e1222bf66c59..5e1447cebcccca74250740b4774cdf02d41098f6 100644 --- a/gso/workflows/router/validate_router.py +++ b/gso/workflows/router/validate_router.py @@ -1,17 +1,18 @@ """Router validation workflow. Used in a nightly schedule.""" -import json - from orchestrator.targets import Target from orchestrator.utils.errors import ProcessFailureError -from orchestrator.utils.json import json_dumps from orchestrator.workflow import StepList, done, init, step, workflow from orchestrator.workflows.steps import resync, store_process_subscription, unsync from orchestrator.workflows.utils import wrap_modify_initial_input_form +from workflows.router.update_ibgp_mesh import add_p_to_mesh_dry from gso.products.product_types.router import Router from gso.services import infoblox -from gso.services.lso_client import execute_playbook, lso_interaction +from gso.services.librenms_client import LibreNMSClient +from gso.services.lso_client import anonymous_lso_interaction +from gso.services.netbox_client import NetboxClient +from gso.utils.workflow_steps import deploy_base_config_dry, detect_configuration_drift @step("Verify IPAM resources for loopback interface") @@ -26,17 +27,26 @@ def verify_ipam_loopback(subscription: Router) -> None: raise ProcessFailureError(msg) -@step("Validate router configuration") -def validate_router_config(subscription: Router, callback_route: str) -> None: - """Run an Ansible playbook that validates the configuration that is present on an active Router.""" - extra_vars = {"wfo_router": json.loads(json_dumps(subscription)), "verb": "validate"} +@step("Verify correct Netbox entry") +def verify_netbox_entry(subscription: Router) -> None: + """Validate the Netbox entry for a Router. - execute_playbook( - playbook_name="base_config.yaml", - callback_route=callback_route, - inventory=subscription.router.router_fqdn, - extra_vars=extra_vars, - ) + This will only ensure existence of the node itself in Netbox. Validation of separate interfaces takes places in + other subscriptions' validation workflows. + """ + client = NetboxClient() + # Try and fetch the host, which will raise an exception on failure. + client.get_device_by_name(subscription.router.router_fqdn) + + +@step("Verify correct LibreNMS entry") +def verify_librenms_entry(subscription: Router) -> None: + """Validate the LibreNMS entry for a Router. + + Raises an HTTP error 404 when the device is not present in LibreNMS. + """ + client = LibreNMSClient() + client.get_device(subscription.router.router_fqdn) @workflow( @@ -48,14 +58,20 @@ def validate_router() -> StepList: """Validate an existing, active Router subscription. * Verify that the loopback interface is correctly configured in :term:`IPAM`. + * Verify that the router is correctly configured in Netbox. + * Verify that the router is correctly configured in LibreNMS. * Redeploy base config to verify the configuration is intact. + * Validate configuration of the iBGP mesh """ return ( init >> store_process_subscription(Target.SYSTEM) >> unsync >> verify_ipam_loopback - >> lso_interaction(validate_router_config) + >> verify_netbox_entry + >> verify_librenms_entry + >> anonymous_lso_interaction(deploy_base_config_dry, detect_configuration_drift) + >> anonymous_lso_interaction(add_p_to_mesh_dry, detect_configuration_drift) >> resync >> done )