"""Router validation workflow. Used in a nightly schedule.""" from orchestrator.targets import Target from orchestrator.utils.errors import ProcessFailureError from orchestrator.workflow import StepList, begin, conditional, done, step, workflow from orchestrator.workflows.steps import resync, store_process_subscription, unsync from orchestrator.workflows.utils import wrap_modify_initial_input_form from pydantic_forms.types import State, UUIDstr from gso.products.product_types.router import Router from gso.services import infoblox from gso.services.librenms_client import LibreNMSClient from gso.services.lso_client import anonymous_lso_interaction, execute_playbook from gso.services.netbox_client import NetboxClient from gso.utils.shared_enums import Vendor @step("Prepare required keys in state") def prepare_state(subscription_id: UUIDstr) -> State: """Add required keys to the state for the workflow to run successfully.""" router = Router.from_subscription(subscription_id) return {"subscription": router} @step("Verify IPAM resources for loopback interface") def verify_ipam_loopback(subscription: Router) -> None: """Validate the :term:`IPAM` resources for the loopback interface. Raises an :class:`orchestrator.utils.errors.ProcessFailureError` if :term:`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: msg = "Loopback record is incorrectly configured in IPAM, please investigate this manually!" raise ProcessFailureError(msg) @step("Verify correct Netbox entry") def check_netbox_entry_exists(subscription: Router) -> None: """Validate the Netbox entry for a Router. 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 check_librenms_entry_exists(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() errors = client.validate_device(subscription.router.router_fqdn) if errors: raise ProcessFailureError(message="LibreNMS configuration error", details=errors) @step("Check base config for drift") def verify_base_config(subscription: Router, callback_route: str) -> None: """Workflow step for running a playbook that checks whether base config has drifted.""" execute_playbook( playbook_name="base_config.yaml", callback_route=callback_route, inventory=subscription.router.router_fqdn, extra_vars={"wfo_router_json": subscription, "verb": "check-drift"}, ) @step("Validate iBGP mesh configuration") def validate_ibgp_mesh_config(subscription: Router, callback_route: str) -> None: """Workflow step for running a playbook that check iBGP mesh configuration.""" execute_playbook( playbook_name="ibgp_checks.yaml", callback_route=callback_route, inventory=subscription.router.router_fqdn, extra_vars={"wfo_router_json": subscription}, ) @workflow( "Validate router configuration", target=Target.SYSTEM, initial_input_form=wrap_modify_initial_input_form(None) ) 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 """ is_juniper_router = conditional(lambda state: state["subscription"]["router"]["vendor"] == Vendor.JUNIPER) return ( begin >> store_process_subscription(Target.SYSTEM) >> prepare_state >> is_juniper_router(done) >> unsync >> verify_ipam_loopback >> check_netbox_entry_exists >> check_librenms_entry_exists >> anonymous_lso_interaction(verify_base_config) >> anonymous_lso_interaction(validate_ibgp_mesh_config) >> resync >> done )