diff --git a/Changelog.md b/Changelog.md index 2df8db9ae19a869f38b9012ab4ad6d9b343f7bd6..6d66ea1c8e0c2debf3bb4d5baed2b39666fa9c50 100644 --- a/Changelog.md +++ b/Changelog.md @@ -1,5 +1,12 @@ # Changelog +## [2.19] - 2024-10-09 +- LSO interaction rework: Skip a playbook automatically if the inventory is empty. +- Introduction of LSOState type. +- Celery: add as Executor, cleanup. +- `promote_p_to_pe` and `update_ibgp_mesh`: add conditionals to distinguish between P and PE for the iBGP/SDP updates and PE-specific functions and logic. +- Update of unit test in `test_update_ibgp_mesh`. + ## [2.18] - 2024-10-01 - Use solo pool for Celery workers diff --git a/gso/__init__.py b/gso/__init__.py index f1d1debdc8208a7368f90d811d586679367f9cad..478efc94c1cba9cec5745153b8a47890c83feab0 100644 --- a/gso/__init__.py +++ b/gso/__init__.py @@ -4,9 +4,12 @@ import os import sentry_sdk import typer +from celery import Celery from orchestrator import OrchestratorCore, app_settings from orchestrator.cli.main import app as cli_app from orchestrator.graphql import SCALAR_OVERRIDES +from orchestrator.services.tasks import initialise_celery +from orchestrator.settings import ExecutorType # noinspection PyUnresolvedReferences import gso.products @@ -20,6 +23,12 @@ from gso.settings import load_oss_params SCALAR_OVERRIDES.update(GSO_SCALAR_OVERRIDES) +def gso_initialise_celery(celery: Celery) -> None: + """Initialise the :term:`Celery` app.""" + initialise_celery(celery) + celery.conf.task_routes = {} + + def init_gso_app() -> OrchestratorCore: """Initialise the :term:`GSO` app.""" app = OrchestratorCore(base_settings=app_settings) @@ -28,12 +37,21 @@ def init_gso_app() -> OrchestratorCore: app.register_graphql_authorization(graphql_opa_instance) app.register_graphql() app.include_router(api_router, prefix="/api") - return app + if app_settings.EXECUTOR == ExecutorType.WORKER: + config = load_oss_params() + celery = Celery( + "geant-service-orchestrator", + broker=config.CELERY.broker_url, + backend=config.CELERY.result_backend, + include=["orchestrator.services.tasks"], + ) + celery.conf.update( + result_expires=config.CELERY.result_expires, + ) + gso_initialise_celery(celery) -def init_worker_app() -> OrchestratorCore: - """Initialise a :term:`GSO` instance as Celery worker.""" - return OrchestratorCore(base_settings=app_settings) + return app def init_cli_app() -> typer.Typer: diff --git a/gso/services/lso_client.py b/gso/services/lso_client.py index 77c2264798a009c79fb5a28dcd15a317e17b87c5..cae5dd9c28d55d3b166f7250513ad7a5d7946cba 100644 --- a/gso/services/lso_client.py +++ b/gso/services/lso_client.py @@ -5,14 +5,14 @@ import json import logging -from typing import Any +from typing import Any, Literal, TypedDict import requests from orchestrator import step from orchestrator.config.assignee import Assignee from orchestrator.types import State from orchestrator.utils.errors import ProcessFailureError -from orchestrator.workflow import Step, StepList, begin, callback_step, inputstep +from orchestrator.workflow import Step, StepList, begin, callback_step, conditional, inputstep from pydantic import ConfigDict from pydantic_forms.core import FormPage from pydantic_forms.types import FormGenerator @@ -23,6 +23,18 @@ from gso import settings logger = logging.getLogger(__name__) +class _LSOState(TypedDict): # noqa: PYI049 + """An expanded state that must contain at least the required keys for the execution of an Ansible playbook.""" + + playbook_name: str + extra_vars: dict[str, Any] + inventory: dict[Literal["all"], dict[Literal["hosts"], dict[str, Any] | None]] + __extra_values__: Any # This is feature unavailable in python 3.12 + + +LSOState = State # FIXME: Use the above definition when python3.13 is released + + def _send_request(parameters: dict, callback_route: str) -> None: """Send a request to :term:`LSO`. The callback address is derived using the process ID provided. @@ -48,11 +60,9 @@ def _send_request(parameters: dict, callback_route: str) -> None: request.raise_for_status() -def execute_playbook( - playbook_name: str, - callback_route: str, - inventory: dict[str, Any] | str, - extra_vars: dict[str, Any], +@step("Execute Ansible playbook") +def _execute_playbook( + playbook_name: str, callback_route: str, inventory: dict[str, Any], extra_vars: dict[str, Any] ) -> None: """Execute a playbook remotely through the provisioning proxy. @@ -71,7 +81,8 @@ def execute_playbook( }, "host2.local": { "key": "value" - } + }, + "host3.local": None } } } @@ -141,7 +152,38 @@ def _show_results(state: State) -> FormGenerator: @step("Clean up keys from state") def _clean_state() -> State: - return {"__remove_keys": ["run_results", "lso_result_title", "lso_result_extra_label", "callback_result"]} + return { + "__remove_keys": [ + "run_results", + "lso_result_title", + "lso_result_extra_label", + "callback_result", + "playbook_name", + "callback_route", + "inventory", + "extra_vars", + ] + } + + +def _inventory_is_set(state: State) -> bool: + """Validate whether the passed Ansible inventory is empty. + + If the inventory is empty, which can happen in select cases, there should be no playbook run. This conditional will + prevent from calling out to :term:`LSO` with an empty playbook, which would cause the Ansible runner process to + hang. This in turn will result in a workflow step that is never called back to. + """ + if "inventory" not in state: + msg = "Missing Ansible inventory for playbook." + raise ProcessFailureError(msg, details="Key 'inventory' not found in state.") + if "all" not in state["inventory"] or "hosts" not in state["inventory"]["all"]: + msg = "Malformed Ansible inventory found in state." + raise ProcessFailureError(msg, details="Ansible inventory must be in YAML form, not string.") + + return state["inventory"]["all"]["hosts"] + + +_inventory_is_not_empty = conditional(_inventory_is_set) def lso_interaction(provisioning_step: Step) -> StepList: @@ -162,9 +204,15 @@ def lso_interaction(provisioning_step: Step) -> StepList: """ return ( begin - >> callback_step(name=provisioning_step.name, action_step=provisioning_step, validate_step=_evaluate_results) - >> step("Inject result title")(lambda: {"lso_result_title": provisioning_step.name}) - >> _show_results + >> provisioning_step + >> _inventory_is_not_empty( + begin + >> callback_step( + name="Running Ansible playbook", action_step=_execute_playbook, validate_step=_evaluate_results + ) + >> step("Inject result title")(lambda: {"lso_result_title": provisioning_step.name}) + >> _show_results + ) >> _clean_state ) @@ -187,9 +235,15 @@ def indifferent_lso_interaction(provisioning_step: Step) -> StepList: """ return ( begin - >> callback_step(name=provisioning_step.name, action_step=provisioning_step, validate_step=_ignore_results) - >> step("Inject result title")(lambda: {"lso_result_title": provisioning_step.name}) - >> _show_results + >> provisioning_step + >> _inventory_is_not_empty( + begin + >> callback_step( + name="Running Ansible playbook", action_step=_execute_playbook, validate_step=_ignore_results + ) + >> step("Inject result title")(lambda: {"lso_result_title": provisioning_step.name}) + >> _show_results + ) >> _clean_state ) @@ -207,6 +261,9 @@ def anonymous_lso_interaction(provisioning_step: Step, validation_step: Step = _ """ return ( begin - >> callback_step(name=provisioning_step.name, action_step=provisioning_step, validate_step=validation_step) + >> provisioning_step + >> _inventory_is_not_empty( + callback_step(name="Running Ansible playbook", action_step=_execute_playbook, validate_step=validation_step) + ) >> _clean_state ) diff --git a/gso/utils/workflow_steps.py b/gso/utils/workflow_steps.py index 93891456bd96a02fb52f40b7b250e40825983047..2095e7621a51038c95cf8ba18f4ade74961f8b19 100644 --- a/gso/utils/workflow_steps.py +++ b/gso/utils/workflow_steps.py @@ -12,21 +12,21 @@ from pydantic_forms.core import FormPage from pydantic_forms.types import FormGenerator from pydantic_forms.validators import Label +from gso.products.product_blocks.router import RouterRole from gso.products.product_types.iptrunk import Iptrunk -from gso.services import lso_client +from gso.services.lso_client import LSOState from gso.settings import load_oss_params +from gso.utils.helpers import generate_inventory_for_active_routers +from gso.utils.shared_enums import Vendor def _deploy_base_config( subscription: dict[str, Any], tt_number: str, - callback_route: str, process_id: UUIDstr, *, dry_run: bool, -) -> None: - inventory = subscription["router"]["router_fqdn"] - +) -> LSOState: extra_vars = { "wfo_router_json": subscription, "dry_run": dry_run, @@ -34,42 +34,278 @@ def _deploy_base_config( "commit_comment": f"GSO_PROCESS_ID: {process_id} - TT_NUMBER: {tt_number} - Deploy base config", } - lso_client.execute_playbook( - playbook_name="base_config.yaml", - callback_route=callback_route, - inventory=inventory, - extra_vars=extra_vars, + return { + "playbook_name": "base_config.yaml", + "inventory": {"all": {"hosts": {subscription["router"]["router_fqdn"]: None}}}, + "extra_vars": extra_vars, + } + + +def _update_sdp_mesh( + subscription: dict[str, Any], + tt_number: str, + process_id: UUIDstr, + *, + dry_run: bool, +) -> LSOState: + inventory = generate_inventory_for_active_routers( + router_role=RouterRole.PE, router_vendor=Vendor.NOKIA, exclude_routers=[subscription["router"]["router_fqdn"]] ) + extra_vars = { + "dry_run": dry_run, + "subscription": subscription, + "commit_comment": f"GSO_PROCESS_ID: {process_id} - TT_NUMBER: {tt_number} - " + f"Update the SDP mesh for L2circuits(epipes) config on PE NOKIA routers", + "verb": "update_sdp_mesh", + "pe_router_list": { + subscription["router"]["router_fqdn"]: { + "lo4": str(subscription["router"]["router_lo_ipv4_address"]), + "lo6": str(subscription["router"]["router_lo_ipv6_address"]), + } + }, + } -@step("[DRY RUN] Deploy base config") -def deploy_base_config_dry( + return { + "playbook_name": "update_pe_sdp_mesh.yaml", + "inventory": inventory, + "extra_vars": extra_vars, + } + + +def _update_sdp_single_pe( subscription: dict[str, Any], tt_number: str, - callback_route: str, process_id: UUIDstr, -) -> State: - """Perform a dry run of provisioning base config on a router.""" - _deploy_base_config(subscription, tt_number, callback_route, process_id, dry_run=True) + *, + dry_run: bool, +) -> LSOState: + extra_vars = { + "dry_run": dry_run, + "subscription": subscription, + "commit_comment": f"GSO_PROCESS_ID: {process_id} - TT_NUMBER: {tt_number} - " + f"Update the SDP mesh for L2circuits(epipes) config on PE NOKIA routers", + "verb": "update_sdp_mesh", + "pe_router_list": generate_inventory_for_active_routers( + router_role=RouterRole.PE, + router_vendor=Vendor.NOKIA, + exclude_routers=[subscription["router"]["router_fqdn"]], + )["all"]["hosts"], + } - return {"subscription": subscription} + if not extra_vars["pe_router_list"]: + return { + "playbook_name": "", + "inventory": {"all": {"hosts": {}}}, + "extra_vars": {}, + } + return { + "playbook_name": "update_pe_sdp_mesh.yaml", + "inventory": {"all": {"hosts": {subscription["router"]["router_fqdn"]: None}}}, + "extra_vars": extra_vars, + } -@step("[FOR REAL] Deploy base config") -def deploy_base_config_real( + +def _add_pe_mesh_to_pe( + subscription: dict[str, Any], + tt_number: str, + process_id: UUIDstr, + *, + dry_run: bool, +) -> LSOState: + extra_vars = { + "dry_run": dry_run, + "subscription": subscription, + "commit_comment": f"GSO_PROCESS_ID: {process_id} - TT_NUMBER: {tt_number} - " + f"Add list of PE routers into iGEANT/iGEANT6 groups of the PE router", + "verb": "add_pe_mesh_to_pe", + "pe_router_list": generate_inventory_for_active_routers( + router_role=RouterRole.PE, exclude_routers=[subscription["router"]["router_fqdn"]] + )["all"]["hosts"], + } + + if not extra_vars["pe_router_list"]: + return { + "playbook_name": "", + "inventory": {"all": {"hosts": {}}}, + "extra_vars": {}, + } + + return { + "playbook_name": "update_ibgp_mesh.yaml", + "inventory": {"all": {"hosts": {subscription["router"]["router_fqdn"]: None}}}, + "extra_vars": extra_vars, + } + + +def _add_pe_to_pe_mesh( + subscription: dict[str, Any], + tt_number: str, + process_id: UUIDstr, + *, + dry_run: bool, +) -> LSOState: + inventory = generate_inventory_for_active_routers( + router_role=RouterRole.PE, exclude_routers=[subscription["router"]["router_fqdn"]] + ) + extra_vars = { + "dry_run": dry_run, + "subscription": subscription, + "commit_comment": f"GSO_PROCESS_ID: {process_id} - TT_NUMBER: {tt_number} - " + f"Add the PE router to all PE routers in iGEANT/iGEANT6.", + "verb": "add_pe_to_pe_mesh", + } + + return { + "playbook_name": "update_ibgp_mesh.yaml", + "inventory": inventory, + "extra_vars": extra_vars, + } + + +def _add_all_p_to_pe( + subscription: dict[str, Any], + tt_number: str, + process_id: UUIDstr, + *, + dry_run: bool, +) -> LSOState: + extra_vars = { + "dry_run": dry_run, + "subscription": subscription, + "commit_comment": f"GSO_PROCESS_ID: {process_id} - TT_NUMBER: {tt_number} - Add all P-routers to this new PE", + "verb": "add_all_p_to_pe", + "p_router_list": generate_inventory_for_active_routers( + router_role=RouterRole.P, exclude_routers=[subscription["router"]["router_fqdn"]] + )["all"]["hosts"], + } + + if not extra_vars["p_router_list"]: + return { + "playbook_name": "", + "inventory": {"all": {"hosts": {}}}, + "extra_vars": {}, + } + + return { + "playbook_name": "update_ibgp_mesh.yaml", + "inventory": {"all": {"hosts": {subscription["router"]["router_fqdn"]: None}}}, + "extra_vars": extra_vars, + } + + +def _add_pe_to_all_p( subscription: dict[str, Any], tt_number: str, - callback_route: str, process_id: UUIDstr, -) -> State: + *, + dry_run: bool, +) -> LSOState: + inventory = generate_inventory_for_active_routers( + router_role=RouterRole.P, exclude_routers=[subscription["router"]["router_fqdn"]] + ) + extra_vars = { + "dry_run": dry_run, + "subscription": subscription, + "commit_comment": f"GSO_PROCESS_ID: {process_id} - TT_NUMBER: {tt_number} - " + f"Add promoted router to all PE routers in iGEANT/iGEANT6", + "verb": "add_pe_to_all_p", + } + + return { + "playbook_name": "update_ibgp_mesh.yaml", + "inventory": inventory, + "extra_vars": extra_vars, + } + + +@step("[DRY RUN] Deploy base config") +def deploy_base_config_dry(subscription: dict[str, Any], tt_number: str, process_id: UUIDstr) -> State: + """Perform a dry run of provisioning base config on a router.""" + return _deploy_base_config(subscription, tt_number, process_id, dry_run=True) + + +@step("[FOR REAL] Deploy base config") +def deploy_base_config_real(subscription: dict[str, Any], tt_number: str, process_id: UUIDstr) -> State: """Deploy base config on a router using the provisioning proxy.""" - _deploy_base_config(subscription, tt_number, callback_route, process_id, dry_run=False) + return _deploy_base_config(subscription, tt_number, process_id, dry_run=False) + + +@step("[DRY RUN] Add the PE to all P routers") +def add_pe_to_all_p_dry(subscription: dict[str, Any], tt_number: str, process_id: UUIDstr) -> State: + """Perform a dry run of adding the PE router to all P routers.""" + return _add_pe_to_all_p(subscription, tt_number, process_id, dry_run=True) + + +@step("[FOR REAL] Add the PE to all P routers") +def add_pe_to_all_p_real(subscription: dict[str, Any], tt_number: str, process_id: UUIDstr) -> State: + """Perform a real run of adding the PE router to all P routers.""" + return _add_pe_to_all_p(subscription, tt_number, process_id, dry_run=False) + + +@step("[DRY RUN] Add all P routers to the PE") +def add_all_p_to_pe_dry(subscription: dict[str, Any], tt_number: str, process_id: UUIDstr) -> State: + """Perform a dry run of adding all P routers to the PE router.""" + return _add_all_p_to_pe(subscription, tt_number, process_id, dry_run=True) + + +@step("[FOR REAL] Add all P routers to the PE") +def add_all_p_to_pe_real(subscription: dict[str, Any], tt_number: str, process_id: UUIDstr) -> State: + """Perform a real run of adding all P routers to the PE router.""" + return _add_all_p_to_pe(subscription, tt_number, process_id, dry_run=False) + + +@step("[DRY RUN] Add the PE to PE mesh") +def add_pe_to_pe_mesh_dry(subscription: dict[str, Any], tt_number: str, process_id: UUIDstr) -> State: + """Perform a dry run of adding the PE router to all PE routers in iGEANT/iGEANT6.""" + return _add_pe_to_pe_mesh(subscription, tt_number, process_id, dry_run=True) + + +@step("[FOR REAL] Add the PE to PE mesh") +def add_pe_to_pe_mesh_real(subscription: dict[str, Any], tt_number: str, process_id: UUIDstr) -> State: + """Perform a real run of adding the PE router to all PE routers in iGEANT/iGEANT6.""" + return _add_pe_to_pe_mesh(subscription, tt_number, process_id, dry_run=False) - return {"subscription": subscription} + +@step("[DRY RUN] Add PE mesh to the PE") +def add_pe_mesh_to_pe_dry(subscription: dict[str, Any], tt_number: str, process_id: UUIDstr) -> State: + """Perform a dry run of adding list of PE routers into iGEANT/iGEANT6 of the router.""" + return _add_pe_mesh_to_pe(subscription, tt_number, process_id, dry_run=True) + + +@step("[FOR REAL] Add PE mesh to the PE") +def add_pe_mesh_to_pe_real(subscription: dict[str, Any], tt_number: str, process_id: UUIDstr) -> State: + """Perform a real run of adding list of PE routers into iGEANT/iGEANT6 of the router.""" + return _add_pe_mesh_to_pe(subscription, tt_number, process_id, dry_run=False) + + +@step("[DRY RUN] Include the PE into SDP mesh on other Nokia PEs") +def update_sdp_mesh_dry(subscription: dict[str, Any], tt_number: str, process_id: UUIDstr) -> State: + """Perform a dry run of including new PE router in SDP mesh on other NOKIA PE routers.""" + return _update_sdp_mesh(subscription, tt_number, process_id, dry_run=True) + + +@step("[FOR REAL] Include the PE into SDP mesh on other Nokia PEs") +def update_sdp_mesh_real(subscription: dict[str, Any], tt_number: str, process_id: UUIDstr) -> State: + """Perform a real run of including new PE router in SDP mesh on other NOKIA PE routers.""" + return _update_sdp_mesh(subscription, tt_number, process_id, dry_run=False) + + +@step("[DRY RUN] Configure SDP on the PE to all other Nokia PEs") +def update_sdp_single_pe_dry(subscription: dict[str, Any], tt_number: str, process_id: UUIDstr) -> State: + """Perform a dry run of configuring SDP on a new PE router to all other NOKIA PE routers.""" + return _update_sdp_single_pe(subscription, tt_number, process_id, dry_run=True) + + +@step("[FOR REAL] Configure SDP on the PE to all other Nokia PEs") +def update_sdp_single_pe_real(subscription: dict[str, Any], tt_number: str, process_id: UUIDstr) -> State: + """Configure SDP on a new PE router to all other NOKIA PE routers.""" + return _update_sdp_single_pe(subscription, tt_number, process_id, dry_run=False) @step("[FOR REAL] Set ISIS metric to very high value") -def set_isis_to_max(subscription: Iptrunk, process_id: UUIDstr, callback_route: str, tt_number: str) -> State: +def set_isis_to_max(subscription: Iptrunk, process_id: UUIDstr, tt_number: str) -> LSOState: """Workflow step for setting the :term:`ISIS` metric to an arbitrarily high value to drain a link.""" old_isis_metric = subscription.iptrunk.iptrunk_isis_metric params = load_oss_params() @@ -83,29 +319,62 @@ def set_isis_to_max(subscription: Iptrunk, process_id: UUIDstr, callback_route: f"{subscription.iptrunk.geant_s_sid}", } - lso_client.execute_playbook( - playbook_name="iptrunks.yaml", - callback_route=callback_route, - inventory=f"{subscription.iptrunk.iptrunk_sides[0].iptrunk_side_node.router_fqdn}\n" - f"{subscription.iptrunk.iptrunk_sides[1].iptrunk_side_node.router_fqdn}\n", - extra_vars=extra_vars, - ) - return { "subscription": subscription, + "playbook_name": "iptrunks.yaml", + "inventory": { + "all": { + "hosts": { + subscription.iptrunk.iptrunk_sides[0].iptrunk_side_node.router_fqdn: None, + subscription.iptrunk.iptrunk_sides[1].iptrunk_side_node.router_fqdn: None, + } + } + }, + "extra_vars": extra_vars, "old_isis_metric": old_isis_metric, } @step("Run show commands after base config install") -def run_checks_after_base_config(subscription: dict[str, Any], callback_route: str) -> None: +def run_checks_after_base_config(subscription: dict[str, Any]) -> LSOState: """Workflow step for running show commands after installing base config.""" - lso_client.execute_playbook( - playbook_name="base_config_checks.yaml", - callback_route=callback_route, - inventory=subscription["router"]["router_fqdn"], - extra_vars={"wfo_router_json": subscription}, - ) + return { + "playbook_name": "base_config_checks.yaml", + "inventory": {"all": {"hosts": {subscription["router"]["router_fqdn"]}}}, + "extra_vars": {"wfo_router_json": subscription}, + } + + +@step("Check iBGP session") +def check_pe_ibgp(subscription: dict[str, Any]) -> LSOState: + """Check the iBGP session.""" + extra_vars = { + "dry_run": False, + "subscription": subscription, + "verb": "check_pe_ibgp", + } + + return { + "playbook_name": "check_ibgp.yaml", + "inventory": {"all": {"hosts": {subscription["router"]["router_fqdn"]: None}}}, + "extra_vars": extra_vars, + } + + +@step("Check L3 services") +def check_l3_services(subscription: dict[str, Any]) -> LSOState: + """Check L3 services.""" + extra_vars = { + "dry_run": False, + "subscription": subscription, + "verb": "check_base_ris", + } + + return { + "playbook_name": "check_l3_services.yaml", + "inventory": {"all": {"hosts": {subscription["router"]["router_fqdn"]: None}}}, + "extra_vars": extra_vars, + } @inputstep("Prompt for new SharePoint checklist", assignee=Assignee.SYSTEM) diff --git a/gso/worker.py b/gso/worker.py index 300eb5908457aead3fbbaa7425e8c05bf71d0de5..c2e825cc8d8b4224acc67ada88c6b3b2721b1dce 100644 --- a/gso/worker.py +++ b/gso/worker.py @@ -1,23 +1,81 @@ """Module that sets up :term:`GSO` as a Celery worker. This will allow for the scheduling of regular task workflows.""" +from typing import Any +from uuid import UUID + from celery import Celery +from celery.signals import setup_logging, worker_shutting_down +from nwastdlib.logging import initialise_logging +from orchestrator import app_settings +from orchestrator.db import init_database +from orchestrator.domain import SUBSCRIPTION_MODEL_REGISTRY +from orchestrator.log_config import LOGGER_OVERRIDES, logger_config +from orchestrator.types import BroadcastFunc +from orchestrator.websocket import broadcast_process_update_to_websocket, init_websocket_manager +from orchestrator.websocket.websocket_manager import WebSocketManager +from orchestrator.workflows import ALL_WORKFLOWS +from structlog import get_logger -from gso import init_worker_app +from gso import gso_initialise_celery from gso.settings import load_oss_params +logger = get_logger(__name__) + +LOGGER_OVERRIDES_CELERY = LOGGER_OVERRIDES | dict([ + logger_config("celery"), + logger_config("kombu"), +]) + + +@setup_logging.connect # type: ignore[misc] +def on_setup_logging(**kwargs: Any) -> None: # noqa: ARG001 + """Set up logging for the Celery worker.""" + initialise_logging(additional_loggers=LOGGER_OVERRIDES_CELERY) + -class OrchestratorCelery(Celery): +def process_broadcast_fn(process_id: UUID) -> None: + """Broadcast process update to WebSocket.""" + # Catch all exceptions as broadcasting failure is noncritical to workflow completion + try: + broadcast_process_update_to_websocket(process_id) + except Exception as e: + logger.exception(e) # noqa: TRY401 + + +class OrchestratorWorker(Celery): """A :term:`GSO` instance that functions as a Celery worker.""" - def on_init(self) -> None: # noqa: PLR6301 + websocket_manager: WebSocketManager + process_broadcast_fn: BroadcastFunc + + def on_init(self) -> None: """Initialise a new Celery worker.""" - init_worker_app() + init_database(app_settings) + + # Prepare the wrapped_websocket_manager + # Note: cannot prepare the redis connections here as broadcasting is async + self.websocket_manager = init_websocket_manager(app_settings) + self.process_broadcast_fn = process_broadcast_fn + + # Load the products and load the workflows + import gso.products # noqa: PLC0415 + import gso.workflows # noqa: PLC0415,F401 + + logger.info( + "Loaded the workflows and products", + workflows=len(ALL_WORKFLOWS.values()), + products=len(SUBSCRIPTION_MODEL_REGISTRY.values()), + ) + + def close(self) -> None: + """Close Celery worker cleanly.""" + super().close() settings = load_oss_params() -celery = OrchestratorCelery( - "worker", +celery = OrchestratorWorker( + "geant-service-orchestrator-worker", broker=settings.CELERY.broker_url, backend=settings.CELERY.result_backend, include=[ @@ -26,8 +84,27 @@ celery = OrchestratorCelery( "gso.schedules.validate_subscriptions", "gso.schedules.send_email_notifications", "gso.schedules.clean_old_tasks", + "orchestrator.services.tasks", ], ) -celery.conf.update(result_expires=settings.CELERY.result_expires) -celery.conf.update(redbeat_redis_url=settings.CELERY.broker_url) +if app_settings.TESTING: + celery.conf.update(backend=settings.CELERY.result_backend, task_ignore_result=False) +else: + celery.conf.update(task_ignore_result=True) + +celery.conf.update( + result_expires=settings.CELERY.result_expires, + worker_prefetch_multiplier=1, + worker_send_task_event=True, + task_send_sent_event=True, + redbeat_redis_url=settings.CELERY.broker_url, +) + +gso_initialise_celery(celery) + + +@worker_shutting_down.connect # type: ignore[misc] +def worker_shutting_down_handler(sig, how, exitcode, **kwargs) -> None: # type: ignore[no-untyped-def] # noqa: ARG001 + """Handle the Celery worker shutdown event.""" + celery.close() diff --git a/gso/workflows/iptrunk/create_iptrunk.py b/gso/workflows/iptrunk/create_iptrunk.py index 56907a6c22e52c5f7d82642563e78d0a62b09853..ae3c6a74a54054b6a4fe07cd2cef9e9ee75ff3fa 100644 --- a/gso/workflows/iptrunk/create_iptrunk.py +++ b/gso/workflows/iptrunk/create_iptrunk.py @@ -28,7 +28,7 @@ from gso.products.product_blocks.iptrunk import ( from gso.products.product_types.iptrunk import Iptrunk, IptrunkInactive, IptrunkProvisioning from gso.products.product_types.router import Router from gso.services import infoblox, subscriptions -from gso.services.lso_client import execute_playbook, lso_interaction +from gso.services.lso_client import LSOState, lso_interaction from gso.services.netbox_client import NetboxClient from gso.services.partners import get_partner_by_name from gso.services.sharepoint import SharePointClient @@ -325,12 +325,7 @@ def initialize_subscription( @step("[DRY RUN] Provision IP trunk interface") -def provision_ip_trunk_iface_dry( - subscription: IptrunkInactive, - callback_route: str, - process_id: UUIDstr, - tt_number: str, -) -> State: +def provision_ip_trunk_iface_dry(subscription: IptrunkInactive, process_id: UUIDstr, tt_number: str) -> LSOState: """Perform a dry run of deploying configuration on both sides of the trunk.""" extra_vars = { "wfo_trunk_json": json.loads(json_dumps(subscription)), @@ -341,24 +336,22 @@ def provision_ip_trunk_iface_dry( f"{subscription.iptrunk.geant_s_sid}", } - execute_playbook( - playbook_name="iptrunks.yaml", - callback_route=callback_route, - inventory=f"{subscription.iptrunk.iptrunk_sides[0].iptrunk_side_node.router_fqdn}\n" - f"{subscription.iptrunk.iptrunk_sides[1].iptrunk_side_node.router_fqdn}\n", - extra_vars=extra_vars, - ) - - return {"subscription": subscription} + return { + "playbook_name": "iptrunks.yaml", + "inventory": { + "all": { + "hosts": { + subscription.iptrunk.iptrunk_sides[0].iptrunk_side_node.router_fqdn: None, + subscription.iptrunk.iptrunk_sides[1].iptrunk_side_node.router_fqdn: None, + } + } + }, + "extra_vars": extra_vars, + } @step("[FOR REAL] Provision IP trunk interface") -def provision_ip_trunk_iface_real( - subscription: IptrunkInactive, - callback_route: str, - process_id: UUIDstr, - tt_number: str, -) -> State: +def provision_ip_trunk_iface_real(subscription: IptrunkInactive, process_id: UUIDstr, tt_number: str) -> LSOState: """Deploy IP trunk configuration on both sides.""" extra_vars = { "wfo_trunk_json": json.loads(json_dumps(subscription)), @@ -369,42 +362,34 @@ def provision_ip_trunk_iface_real( f"{subscription.iptrunk.geant_s_sid}", } - execute_playbook( - playbook_name="iptrunks.yaml", - callback_route=callback_route, - inventory=f"{subscription.iptrunk.iptrunk_sides[0].iptrunk_side_node.router_fqdn}\n" - f"{subscription.iptrunk.iptrunk_sides[1].iptrunk_side_node.router_fqdn}\n", - extra_vars=extra_vars, - ) - - return {"subscription": subscription} + return { + "playbook_name": "iptrunks.yaml", + "inventory": { + "all": { + "hosts": { + subscription.iptrunk.iptrunk_sides[0].iptrunk_side_node.router_fqdn: None, + subscription.iptrunk.iptrunk_sides[1].iptrunk_side_node.router_fqdn: None, + } + } + }, + "extra_vars": extra_vars, + } @step("Check IP connectivity of the trunk") -def check_ip_trunk_connectivity( - subscription: IptrunkInactive, - callback_route: str, -) -> State: +def check_ip_trunk_connectivity(subscription: IptrunkInactive) -> LSOState: """Check successful connectivity across the new trunk.""" extra_vars = {"wfo_ip_trunk_json": json.loads(json_dumps(subscription)), "check": "ping"} - execute_playbook( - playbook_name="iptrunks_checks.yaml", - callback_route=callback_route, - inventory=subscription.iptrunk.iptrunk_sides[0].iptrunk_side_node.router_fqdn, # type: ignore[arg-type] - extra_vars=extra_vars, - ) - - return {"subscription": subscription} + return { + "playbook_name": "iptrunks_checks.yaml", + "inventory": {"all": {"hosts": {subscription.iptrunk.iptrunk_sides[0].iptrunk_side_node.router_fqdn: None}}}, + "extra_vars": extra_vars, + } @step("[DRY RUN] Provision IP trunk ISIS interface") -def provision_ip_trunk_isis_iface_dry( - subscription: IptrunkInactive, - callback_route: str, - process_id: UUIDstr, - tt_number: str, -) -> State: +def provision_ip_trunk_isis_iface_dry(subscription: IptrunkInactive, process_id: UUIDstr, tt_number: str) -> LSOState: """Perform a dry run of deploying :term:`ISIS` configuration.""" extra_vars = { "wfo_trunk_json": json.loads(json_dumps(subscription)), @@ -415,24 +400,22 @@ def provision_ip_trunk_isis_iface_dry( f"{subscription.iptrunk.geant_s_sid}", } - execute_playbook( - playbook_name="iptrunks.yaml", - callback_route=callback_route, - inventory=f"{subscription.iptrunk.iptrunk_sides[0].iptrunk_side_node.router_fqdn}\n" - f"{subscription.iptrunk.iptrunk_sides[1].iptrunk_side_node.router_fqdn}\n", - extra_vars=extra_vars, - ) - - return {"subscription": subscription} + return { + "playbook_name": "iptrunks.yaml", + "inventory": { + "all": { + "hosts": { + subscription.iptrunk.iptrunk_sides[0].iptrunk_side_node.router_fqdn: None, + subscription.iptrunk.iptrunk_sides[1].iptrunk_side_node.router_fqdn: None, + } + } + }, + "extra_vars": extra_vars, + } @step("[FOR REAL] Provision IP trunk ISIS interface") -def provision_ip_trunk_isis_iface_real( - subscription: IptrunkInactive, - callback_route: str, - process_id: UUIDstr, - tt_number: str, -) -> State: +def provision_ip_trunk_isis_iface_real(subscription: IptrunkInactive, process_id: UUIDstr, tt_number: str) -> LSOState: """Deploy :term:`ISIS` configuration on both sides.""" extra_vars = { "wfo_trunk_json": json.loads(json_dumps(subscription)), @@ -443,33 +426,30 @@ def provision_ip_trunk_isis_iface_real( f"{subscription.iptrunk.geant_s_sid}", } - execute_playbook( - playbook_name="iptrunks.yaml", - callback_route=callback_route, - inventory=f"{subscription.iptrunk.iptrunk_sides[0].iptrunk_side_node.router_fqdn}\n" - f"{subscription.iptrunk.iptrunk_sides[1].iptrunk_side_node.router_fqdn}\n", - extra_vars=extra_vars, - ) - - return {"subscription": subscription} + return { + "playbook_name": "iptrunks.yaml", + "inventory": { + "all": { + "hosts": { + subscription.iptrunk.iptrunk_sides[0].iptrunk_side_node.router_fqdn: None, + subscription.iptrunk.iptrunk_sides[1].iptrunk_side_node.router_fqdn: None, + } + } + }, + "extra_vars": extra_vars, + } @step("Check ISIS adjacency") -def check_ip_trunk_isis( - subscription: IptrunkInactive, - callback_route: str, -) -> State: +def check_ip_trunk_isis(subscription: IptrunkInactive) -> LSOState: """Run an Ansible playbook to confirm :term:`ISIS` adjacency.""" extra_vars = {"wfo_ip_trunk_json": json.loads(json_dumps(subscription)), "check": "isis"} - execute_playbook( - playbook_name="iptrunks_checks.yaml", - callback_route=callback_route, - inventory=subscription.iptrunk.iptrunk_sides[0].iptrunk_side_node.router_fqdn, # type: ignore[arg-type] - extra_vars=extra_vars, - ) - - return {"subscription": subscription} + return { + "playbook_name": "iptrunks_checks.yaml", + "inventory": {"all": {"hosts": {subscription.iptrunk.iptrunk_sides[0].iptrunk_side_node.router_fqdn: None}}}, + "extra_vars": extra_vars, + } @step("Register DNS records for both sides of the trunk") diff --git a/gso/workflows/iptrunk/deploy_twamp.py b/gso/workflows/iptrunk/deploy_twamp.py index 11dce53ba260fd8c8dae08611ae66e4069619d49..b43de54c60885b130c9ad57d8bc5a3adf435c084 100644 --- a/gso/workflows/iptrunk/deploy_twamp.py +++ b/gso/workflows/iptrunk/deploy_twamp.py @@ -5,14 +5,14 @@ import json from orchestrator.forms import FormPage from orchestrator.forms.validators import Label from orchestrator.targets import Target -from orchestrator.types import FormGenerator, State, UUIDstr +from orchestrator.types import FormGenerator, UUIDstr from orchestrator.utils.json import json_dumps from orchestrator.workflow import StepList, begin, done, step, workflow from orchestrator.workflows.steps import resync, store_process_subscription, unsync from orchestrator.workflows.utils import wrap_modify_initial_input_form from gso.products.product_types.iptrunk import Iptrunk -from gso.services.lso_client import execute_playbook, lso_interaction +from gso.services.lso_client import LSOState, lso_interaction from gso.utils.types.tt_number import TTNumber @@ -33,7 +33,7 @@ def _initial_input_form_generator(subscription_id: UUIDstr) -> FormGenerator: @step("[DRY RUN] Deploy TWAMP on both sides") -def deploy_twamp_dry(subscription: Iptrunk, process_id: UUIDstr, callback_route: str, tt_number: str) -> State: +def deploy_twamp_dry(subscription: Iptrunk, process_id: UUIDstr, tt_number: str) -> LSOState: """Perform a dry run of deploying the :term:`TWAMP` session.""" extra_vars = { "subscription": json.loads(json_dumps(subscription)), @@ -43,18 +43,22 @@ def deploy_twamp_dry(subscription: Iptrunk, process_id: UUIDstr, callback_route: "commit_comment": f"GSO_PROCESS_ID: {process_id} - TT_NUMBER: {tt_number} - Deploy TWAMP", } - inventory = ( - f"{subscription.iptrunk.iptrunk_sides[0].iptrunk_side_node.router_fqdn}" - f"\n{subscription.iptrunk.iptrunk_sides[1].iptrunk_side_node.router_fqdn}" - ) - - execute_playbook("deploy_twamp.yaml", callback_route, inventory, extra_vars) - - return {"subscription": subscription} + return { + "playbook_name": "deploy_twamp.yaml", + "inventory": { + "all": { + "hosts": { + subscription.iptrunk.iptrunk_sides[0].iptrunk_side_node.router_fqdn: None, + subscription.iptrunk.iptrunk_sides[1].iptrunk_side_node.router_fqdn: None, + } + } + }, + "extra_vars": extra_vars, + } @step("[FOR REAL] Deploy TWAMP on both sides") -def deploy_twamp_real(subscription: Iptrunk, process_id: UUIDstr, callback_route: str, tt_number: str) -> State: +def deploy_twamp_real(subscription: Iptrunk, process_id: UUIDstr, tt_number: str) -> LSOState: """Deploy the :term:`TWAMP` session.""" extra_vars = { "subscription": json.loads(json_dumps(subscription)), @@ -64,32 +68,40 @@ def deploy_twamp_real(subscription: Iptrunk, process_id: UUIDstr, callback_route "commit_comment": f"GSO_PROCESS_ID: {process_id} - TT_NUMBER: {tt_number} - Deploy TWAMP", } - inventory = ( - f"{subscription.iptrunk.iptrunk_sides[0].iptrunk_side_node.router_fqdn}" - f"\n{subscription.iptrunk.iptrunk_sides[1].iptrunk_side_node.router_fqdn}" - ) - - execute_playbook("deploy_twamp.yaml", callback_route, inventory, extra_vars) - - return {"subscription": subscription} + return { + "playbook_name": "deploy_twamp.yaml", + "inventory": { + "all": { + "hosts": { + subscription.iptrunk.iptrunk_sides[0].iptrunk_side_node.router_fqdn: None, + subscription.iptrunk.iptrunk_sides[1].iptrunk_side_node.router_fqdn: None, + } + } + }, + "extra_vars": extra_vars, + } @step("Check TWAMP status on both sides") -def check_twamp_status(subscription: Iptrunk, callback_route: str) -> State: +def check_twamp_status(subscription: Iptrunk) -> LSOState: """Check TWAMP session.""" extra_vars = { "subscription": json.loads(json_dumps(subscription)), "verb": "check_twamp", } - inventory = ( - f"{subscription.iptrunk.iptrunk_sides[0].iptrunk_side_node.router_fqdn}" - f"\n{subscription.iptrunk.iptrunk_sides[1].iptrunk_side_node.router_fqdn}" - ) - - execute_playbook("deploy_twamp.yaml", callback_route, inventory, extra_vars) - - return {"subscription": subscription} + return { + "playbook_name": "deploy_twamp.yaml", + "inventory": { + "all": { + "hosts": { + subscription.iptrunk.iptrunk_sides[0].iptrunk_side_node.router_fqdn: None, + subscription.iptrunk.iptrunk_sides[1].iptrunk_side_node.router_fqdn: None, + } + } + }, + "extra_vars": extra_vars, + } @workflow( diff --git a/gso/workflows/iptrunk/migrate_iptrunk.py b/gso/workflows/iptrunk/migrate_iptrunk.py index c7021875a1d7f2e2fde5424b5493de028b209d58..3c04fb8e03213eb218f6fc0f230b1b5b504343e5 100644 --- a/gso/workflows/iptrunk/migrate_iptrunk.py +++ b/gso/workflows/iptrunk/migrate_iptrunk.py @@ -28,7 +28,7 @@ from gso.products.product_blocks.iptrunk import IptrunkInterfaceBlock, IptrunkTy from gso.products.product_types.iptrunk import Iptrunk 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.lso_client import LSOState, lso_interaction from gso.services.netbox_client import NetboxClient from gso.services.sharepoint import SharePointClient from gso.services.subscriptions import get_active_router_subscriptions @@ -203,29 +203,28 @@ def calculate_old_side_data(subscription: Iptrunk, replace_index: int) -> State: @step("Check Optical PRE levels on the trunk endpoint") -def check_ip_trunk_optical_levels_pre(subscription: Iptrunk, callback_route: str) -> State: +def check_ip_trunk_optical_levels_pre(subscription: Iptrunk) -> LSOState: """Check Optical PRE levels on the trunk.""" extra_vars = {"wfo_ip_trunk_json": json.loads(json_dumps(subscription)), "check": "optical_pre"} - execute_playbook( - playbook_name="iptrunks_checks.yaml", - callback_route=callback_route, - inventory=f"{subscription.iptrunk.iptrunk_sides[0].iptrunk_side_node.router_fqdn}\n" - f"{subscription.iptrunk.iptrunk_sides[1].iptrunk_side_node.router_fqdn}\n", - extra_vars=extra_vars, - ) - - return {"subscription": subscription} + return { + "playbook_name": "iptrunks_checks.yaml", + "inventory": { + "all": { + "hosts": { + subscription.iptrunk.iptrunk_sides[0].iptrunk_side_node.router_fqdn: None, + subscription.iptrunk.iptrunk_sides[1].iptrunk_side_node.router_fqdn: None, + } + } + }, + "extra_vars": extra_vars, + } @step("Check Optical POST levels on the trunk endpoint") def check_ip_trunk_optical_levels_post( - subscription: Iptrunk, - callback_route: str, - new_node: Router, - new_lag_member_interfaces: list[dict], - replace_index: int, -) -> State: + subscription: Iptrunk, new_node: Router, new_lag_member_interfaces: list[dict], replace_index: int +) -> LSOState: """Check Optical POST levels on the trunk.""" extra_vars = { "wfo_ip_trunk_json": json.loads(json_dumps(subscription)), @@ -235,25 +234,24 @@ def check_ip_trunk_optical_levels_post( "check": "optical_post", } - execute_playbook( - playbook_name="iptrunks_checks.yaml", - callback_route=callback_route, - inventory=f"{subscription.iptrunk.iptrunk_sides[1 - replace_index].iptrunk_side_node.router_fqdn}\n" - f"{new_node.router.router_fqdn}\n", - extra_vars=extra_vars, - ) - - return {"subscription": subscription} + return { + "playbook_name": "iptrunks_checks.yaml", + "inventory": { + "all": { + "hosts": { + subscription.iptrunk.iptrunk_sides[1 - replace_index].iptrunk_side_node.router_fqdn: None, + new_node.router.router_fqdn: None, + } + } + }, + "extra_vars": extra_vars, + } @step("Check LLDP on the trunk endpoints") def check_ip_trunk_lldp( - subscription: Iptrunk, - callback_route: str, - new_node: Router, - new_lag_member_interfaces: list[dict], - replace_index: int, -) -> State: + subscription: Iptrunk, new_node: Router, new_lag_member_interfaces: list[dict], replace_index: int +) -> LSOState: """Check LLDP on the new trunk endpoints.""" extra_vars = { "wfo_ip_trunk_json": json.loads(json_dumps(subscription)), @@ -263,28 +261,30 @@ def check_ip_trunk_lldp( "check": "lldp", } - execute_playbook( - playbook_name="iptrunks_checks.yaml", - callback_route=callback_route, - inventory=f"{subscription.iptrunk.iptrunk_sides[1 - replace_index].iptrunk_side_node.router_fqdn}\n" - f"{new_node.router.router_fqdn}\n", - extra_vars=extra_vars, - ) - - return {"subscription": subscription} + return { + "playbook_name": "iptrunks_checks.yaml", + "inventory": { + "all": { + "hosts": { + subscription.iptrunk.iptrunk_sides[1 - replace_index].iptrunk_side_node.router_fqdn: None, + new_node.router.router_fqdn: None, + } + } + }, + "extra_vars": extra_vars, + } @step("[DRY RUN] Disable configuration on old router") def disable_old_config_dry( subscription: Iptrunk, - callback_route: str, new_node: Router, new_lag_interface: str, new_lag_member_interfaces: list[dict], replace_index: int, process_id: UUIDstr, tt_number: str, -) -> State: +) -> LSOState: """Perform a dry run of disabling the old configuration on the routers.""" extra_vars = { "wfo_trunk_json": json.loads(json_dumps(subscription)), @@ -299,29 +299,31 @@ def disable_old_config_dry( f"- Deploy config for {subscription.iptrunk.geant_s_sid}", } - execute_playbook( - playbook_name="iptrunks_migration.yaml", - callback_route=callback_route, - inventory=f"{subscription.iptrunk.iptrunk_sides[0].iptrunk_side_node.router_fqdn}\n" - f"{subscription.iptrunk.iptrunk_sides[1].iptrunk_side_node.router_fqdn}\n" - f"{new_node.router.router_fqdn}\n", - extra_vars=extra_vars, - ) - - return {"subscription": subscription} + return { + "playbook_name": "iptrunks_migration.yaml", + "inventory": { + "all": { + "hosts": { + subscription.iptrunk.iptrunk_sides[0].iptrunk_side_node.router_fqdn: None, + subscription.iptrunk.iptrunk_sides[1].iptrunk_side_node.router_fqdn: None, + new_node.router.router_fqdn: None, + } + } + }, + "extra_vars": extra_vars, + } @step("[FOR REAL] Disable configuration on old router") def disable_old_config_real( subscription: Iptrunk, - callback_route: str, new_node: Router, new_lag_interface: str, new_lag_member_interfaces: list[dict], replace_index: int, process_id: UUIDstr, tt_number: str, -) -> State: +) -> LSOState: """Disable old configuration on the routers.""" extra_vars = { "wfo_trunk_json": json.loads(json_dumps(subscription)), @@ -336,31 +338,31 @@ def disable_old_config_real( f"- Deploy config for {subscription.iptrunk.geant_s_sid}", } - execute_playbook( - playbook_name="iptrunks_migration.yaml", - callback_route=callback_route, - inventory=f"{subscription.iptrunk.iptrunk_sides[0].iptrunk_side_node.router_fqdn}\n" - f"{subscription.iptrunk.iptrunk_sides[1].iptrunk_side_node.router_fqdn}\n" - f"{new_node.router.router_fqdn}\n", - extra_vars=extra_vars, - ) - return { - "subscription": subscription, + "playbook_name": "iptrunks_migration.yaml", + "inventory": { + "all": { + "hosts": { + subscription.iptrunk.iptrunk_sides[0].iptrunk_side_node.router_fqdn: None, + subscription.iptrunk.iptrunk_sides[1].iptrunk_side_node.router_fqdn: None, + new_node.router.router_fqdn: None, + } + } + }, + "extra_vars": extra_vars, } @step("[DRY RUN] Deploy configuration on new router") def deploy_new_config_dry( subscription: Iptrunk, - callback_route: str, new_node: Router, new_lag_interface: str, new_lag_member_interfaces: list[dict], replace_index: int, process_id: UUIDstr, tt_number: str, -) -> State: +) -> LSOState: """Perform a dry run of deploying configuration on the new router.""" extra_vars = { "wfo_trunk_json": json.loads(json_dumps(subscription)), @@ -375,29 +377,31 @@ def deploy_new_config_dry( f"- Deploy config for {subscription.iptrunk.geant_s_sid}", } - execute_playbook( - playbook_name="iptrunks_migration.yaml", - callback_route=callback_route, - inventory=f"{subscription.iptrunk.iptrunk_sides[0].iptrunk_side_node.router_fqdn}\n" - f"{subscription.iptrunk.iptrunk_sides[1].iptrunk_side_node.router_fqdn}\n" - f"{new_node.router.router_fqdn}\n", - extra_vars=extra_vars, - ) - - return {"subscription": subscription} + return { + "playbook_name": "iptrunks_migration.yaml", + "inventory": { + "all": { + "hosts": { + subscription.iptrunk.iptrunk_sides[0].iptrunk_side_node.router_fqdn: None, + subscription.iptrunk.iptrunk_sides[1].iptrunk_side_node.router_fqdn: None, + new_node.router.router_fqdn: None, + } + } + }, + "extra_vars": extra_vars, + } @step("[FOR REAL] Deploy configuration on new router") def deploy_new_config_real( subscription: Iptrunk, - callback_route: str, new_node: Router, new_lag_interface: str, new_lag_member_interfaces: list[dict], replace_index: int, process_id: UUIDstr, tt_number: str, -) -> State: +) -> LSOState: """Deploy configuration on the new router.""" extra_vars = { "wfo_trunk_json": json.loads(json_dumps(subscription)), @@ -412,27 +416,25 @@ def deploy_new_config_real( f"- Deploy config for {subscription.iptrunk.geant_s_sid}", } - execute_playbook( - playbook_name="iptrunks_migration.yaml", - callback_route=callback_route, - inventory=f"{subscription.iptrunk.iptrunk_sides[0].iptrunk_side_node.router_fqdn}\n" - f"{subscription.iptrunk.iptrunk_sides[1].iptrunk_side_node.router_fqdn}\n" - f"{new_node.router.router_fqdn}\n", - extra_vars=extra_vars, - ) - - return {"subscription": subscription} + return { + "playbook_name": "iptrunks_migration.yaml", + "inventory": { + "all": { + "hosts": { + subscription.iptrunk.iptrunk_sides[0].iptrunk_side_node.router_fqdn: None, + subscription.iptrunk.iptrunk_sides[1].iptrunk_side_node.router_fqdn: None, + new_node.router.router_fqdn: None, + } + } + }, + "extra_vars": extra_vars, + } @step("[DRY RUN] Update BFD on the remaining side") def update_remaining_side_bfd_dry( - subscription: Iptrunk, - callback_route: str, - new_node: Router, - replace_index: int, - process_id: UUIDstr, - tt_number: str, -) -> State: + subscription: Iptrunk, new_node: Router, replace_index: int, process_id: UUIDstr, tt_number: str +) -> LSOState: """Perform a dry run of updating configuration on the remaining router.""" extra_vars = { "wfo_trunk_json": json.loads(json_dumps(subscription)), @@ -444,25 +446,23 @@ def update_remaining_side_bfd_dry( "commit_comment": f"GSO_PROCESS_ID: {process_id} - TT_NUMBER: {tt_number} " f"- Update BFD config.", } - execute_playbook( - playbook_name="iptrunks_migration.yaml", - callback_route=callback_route, - inventory=subscription.iptrunk.iptrunk_sides[1 - replace_index].iptrunk_side_node.router_fqdn, - extra_vars=extra_vars, - ) - - return {"subscription": subscription} + return { + "playbook_name": "iptrunks_migration.yaml", + "inventory": { + "all": { + "hosts": { + subscription.iptrunk.iptrunk_sides[1 - replace_index].iptrunk_side_node.router_fqdn: None, + } + } + }, + "extra_vars": extra_vars, + } @step("[FOR REAL] Update BFD on the remaining side") def update_remaining_side_bfd_real( - subscription: Iptrunk, - callback_route: str, - new_node: Router, - replace_index: int, - process_id: UUIDstr, - tt_number: str, -) -> State: + subscription: Iptrunk, new_node: Router, replace_index: int, process_id: UUIDstr, tt_number: str +) -> LSOState: """Update configuration on the remaining router.""" extra_vars = { "wfo_trunk_json": json.loads(json_dumps(subscription)), @@ -474,23 +474,21 @@ def update_remaining_side_bfd_real( "commit_comment": f"GSO_PROCESS_ID: {process_id} - TT_NUMBER: {tt_number} " f"- Update BFD config.", } - execute_playbook( - playbook_name="iptrunks_migration.yaml", - callback_route=callback_route, - inventory=subscription.iptrunk.iptrunk_sides[1 - replace_index].iptrunk_side_node.router_fqdn, - extra_vars=extra_vars, - ) - - return {"subscription": subscription} + return { + "playbook_name": "iptrunks_migration.yaml", + "inventory": { + "all": { + "hosts": { + subscription.iptrunk.iptrunk_sides[1 - replace_index].iptrunk_side_node.router_fqdn: None, + } + } + }, + "extra_vars": extra_vars, + } @step("Check BFD session over trunk") -def check_ip_trunk_bfd( - subscription: Iptrunk, - callback_route: str, - new_node: Router, - replace_index: int, -) -> State: +def check_ip_trunk_bfd(subscription: Iptrunk, new_node: Router, replace_index: int) -> LSOState: """Check BFD session across the new trunk.""" extra_vars = { "wfo_ip_trunk_json": json.loads(json_dumps(subscription)), @@ -498,14 +496,17 @@ def check_ip_trunk_bfd( "check": "bfd", } - execute_playbook( - playbook_name="iptrunks_checks.yaml", - callback_route=callback_route, - inventory=subscription.iptrunk.iptrunk_sides[1 - replace_index].iptrunk_side_node.router_fqdn, - extra_vars=extra_vars, - ) - - return {"subscription": subscription} + return { + "playbook_name": "iptrunks_checks.yaml", + "inventory": { + "all": { + "hosts": { + subscription.iptrunk.iptrunk_sides[1 - replace_index].iptrunk_side_node.router_fqdn: None, + } + } + }, + "extra_vars": extra_vars, + } @inputstep("Wait for confirmation", assignee=Assignee.SYSTEM) @@ -523,35 +524,33 @@ def confirm_continue_move_fiber() -> FormGenerator: @step("Check IP connectivity of the trunk") -def check_ip_trunk_connectivity( - subscription: Iptrunk, - callback_route: str, - replace_index: int, -) -> State: +def check_ip_trunk_connectivity(subscription: Iptrunk, replace_index: int) -> LSOState: """Check successful connectivity across the new trunk.""" extra_vars = {"wfo_ip_trunk_json": json.loads(json_dumps(subscription)), "check": "ping"} - execute_playbook( - playbook_name="iptrunks_checks.yaml", - callback_route=callback_route, - inventory=subscription.iptrunk.iptrunk_sides[1 - replace_index].iptrunk_side_node.router_fqdn, - extra_vars=extra_vars, - ) - - return {"subscription": subscription} + return { + "playbook_name": "iptrunks_checks.yaml", + "inventory": { + "all": { + "hosts": { + subscription.iptrunk.iptrunk_sides[1 - replace_index].iptrunk_side_node.router_fqdn: None, + } + } + }, + "extra_vars": extra_vars, + } @step("[FOR REAL] Deploy ISIS configuration on new router") def deploy_new_isis( subscription: Iptrunk, - callback_route: str, new_node: Router, new_lag_interface: str, new_lag_member_interfaces: list[dict], replace_index: int, process_id: UUIDstr, tt_number: str, -) -> State: +) -> LSOState: """Deploy :term:`ISIS` configuration.""" extra_vars = { "wfo_trunk_json": json.loads(json_dumps(subscription)), @@ -566,35 +565,37 @@ def deploy_new_isis( f"- Deploy config for {subscription.iptrunk.geant_s_sid}", } - execute_playbook( - playbook_name="iptrunks_migration.yaml", - callback_route=callback_route, - inventory=f"{subscription.iptrunk.iptrunk_sides[0].iptrunk_side_node.router_fqdn}\n" - f"{subscription.iptrunk.iptrunk_sides[1].iptrunk_side_node.router_fqdn}\n" - f"{new_node.router.router_fqdn}\n", - extra_vars=extra_vars, - ) - - return {"subscription": subscription} + return { + "playbook_name": "iptrunks_migration.yaml", + "inventory": { + "all": { + "hosts": { + subscription.iptrunk.iptrunk_sides[0].iptrunk_side_node.router_fqdn: None, + subscription.iptrunk.iptrunk_sides[1].iptrunk_side_node.router_fqdn: None, + new_node.router.router_fqdn: None, + } + } + }, + "extra_vars": extra_vars, + } @step("Check ISIS adjacency") -def check_ip_trunk_isis( - subscription: Iptrunk, - callback_route: str, - replace_index: int, -) -> State: +def check_ip_trunk_isis(subscription: Iptrunk, replace_index: int) -> LSOState: """Run an Ansible playbook to confirm :term:`ISIS` adjacency.""" extra_vars = {"wfo_ip_trunk_json": json.loads(json_dumps(subscription)), "check": "isis"} - execute_playbook( - playbook_name="iptrunks_checks.yaml", - callback_route=callback_route, - inventory=subscription.iptrunk.iptrunk_sides[1 - replace_index].iptrunk_side_node.router_fqdn, - extra_vars=extra_vars, - ) - - return {"subscription": subscription} + return { + "playbook_name": "iptrunks_checks.yaml", + "inventory": { + "all": { + "hosts": { + subscription.iptrunk.iptrunk_sides[1 - replace_index].iptrunk_side_node.router_fqdn: None, + } + } + }, + "extra_vars": extra_vars, + } @inputstep("Wait for confirmation", assignee=Assignee.SYSTEM) @@ -615,10 +616,9 @@ def confirm_continue_restore_isis() -> FormGenerator: def restore_isis_metric( subscription: Iptrunk, process_id: UUIDstr, - callback_route: str, tt_number: str, old_isis_metric: int, -) -> State: +) -> LSOState: """Restore the :term:`ISIS` metric to its original value.""" subscription.iptrunk.iptrunk_isis_metric = old_isis_metric extra_vars = { @@ -630,28 +630,30 @@ def restore_isis_metric( f"{subscription.iptrunk.geant_s_sid}", } - execute_playbook( - playbook_name="iptrunks.yaml", - callback_route=callback_route, - inventory=f"{subscription.iptrunk.iptrunk_sides[0].iptrunk_side_node.router_fqdn}\n" - f"{subscription.iptrunk.iptrunk_sides[1].iptrunk_side_node.router_fqdn}\n", - extra_vars=extra_vars, - ) - - return {"subscription": subscription} + return { + "playbook_name": "iptrunks.yaml", + "inventory": { + "all": { + "hosts": { + subscription.iptrunk.iptrunk_sides[0].iptrunk_side_node.router_fqdn: None, + subscription.iptrunk.iptrunk_sides[1].iptrunk_side_node.router_fqdn: None, + } + } + }, + "extra_vars": extra_vars, + } @step("[DRY RUN] Delete configuration on old router") def delete_old_config_dry( subscription: Iptrunk, - callback_route: str, new_node: Router, new_lag_interface: str, new_lag_member_interfaces: list[dict], replace_index: int, process_id: UUIDstr, tt_number: str, -) -> State: +) -> LSOState: """Perform a dry run of deleting the old configuration.""" extra_vars = { "wfo_trunk_json": json.loads(json_dumps(subscription)), @@ -666,29 +668,31 @@ def delete_old_config_dry( f"- Deploy config for {subscription.iptrunk.geant_s_sid}", } - execute_playbook( - playbook_name="iptrunks_migration.yaml", - callback_route=callback_route, - inventory=f"{subscription.iptrunk.iptrunk_sides[0].iptrunk_side_node.router_fqdn}\n" - f"{subscription.iptrunk.iptrunk_sides[1].iptrunk_side_node.router_fqdn}\n" - f"{new_node.router.router_fqdn}\n", - extra_vars=extra_vars, - ) - - return {"subscription": subscription} + return { + "playbook_name": "iptrunks_migration.yaml", + "inventory": { + "all": { + "hosts": { + subscription.iptrunk.iptrunk_sides[0].iptrunk_side_node.router_fqdn: None, + subscription.iptrunk.iptrunk_sides[1].iptrunk_side_node.router_fqdn: None, + new_node.router.router_fqdn: None, + } + } + }, + "extra_vars": extra_vars, + } @step("[FOR REAL] Delete configuration on old router") def delete_old_config_real( subscription: Iptrunk, - callback_route: str, new_node: Router, new_lag_interface: str, new_lag_member_interfaces: list[dict], replace_index: int, process_id: UUIDstr, tt_number: str, -) -> State: +) -> LSOState: """Delete old configuration from the routers.""" extra_vars = { "wfo_trunk_json": json.loads(json_dumps(subscription)), @@ -703,16 +707,19 @@ def delete_old_config_real( f"- Deploy config for {subscription.iptrunk.geant_s_sid}", } - execute_playbook( - playbook_name="iptrunks_migration.yaml", - callback_route=callback_route, - inventory=f"{subscription.iptrunk.iptrunk_sides[0].iptrunk_side_node.router_fqdn}\n" - f"{subscription.iptrunk.iptrunk_sides[1].iptrunk_side_node.router_fqdn}\n" - f"{new_node.router.router_fqdn}\n", - extra_vars=extra_vars, - ) - - return {"subscription": subscription} + return { + "playbook_name": "iptrunks_migration.yaml", + "inventory": { + "all": { + "hosts": { + subscription.iptrunk.iptrunk_sides[0].iptrunk_side_node.router_fqdn: None, + subscription.iptrunk.iptrunk_sides[1].iptrunk_side_node.router_fqdn: None, + new_node.router.router_fqdn: None, + } + } + }, + "extra_vars": extra_vars, + } @step("Update IP records in IPAM") diff --git a/gso/workflows/iptrunk/modify_isis_metric.py b/gso/workflows/iptrunk/modify_isis_metric.py index da14d078cea88f0c52256e02ef3dce0429cf7374..d95a6eb633000cce5238e044d747743b77295f8a 100644 --- a/gso/workflows/iptrunk/modify_isis_metric.py +++ b/gso/workflows/iptrunk/modify_isis_metric.py @@ -11,7 +11,7 @@ from orchestrator.workflows.steps import resync, store_process_subscription, uns from orchestrator.workflows.utils import wrap_modify_initial_input_form from gso.products.product_types.iptrunk import Iptrunk -from gso.services.lso_client import execute_playbook, lso_interaction +from gso.services.lso_client import LSOState, lso_interaction from gso.utils.types.tt_number import TTNumber @@ -37,12 +37,7 @@ def modify_iptrunk_subscription(subscription: Iptrunk, isis_metric: int) -> Stat @step("[DRY RUN] Provision IP trunk ISIS interface") -def provision_ip_trunk_isis_iface_dry( - subscription: Iptrunk, - process_id: UUIDstr, - callback_route: str, - tt_number: str, -) -> State: +def provision_ip_trunk_isis_iface_dry(subscription: Iptrunk, process_id: UUIDstr, tt_number: str) -> LSOState: """Perform a dry run of deploying the new :term:`ISIS` metric on both sides of the trunk.""" extra_vars = { "wfo_trunk_json": json.loads(json_dumps(subscription)), @@ -53,24 +48,22 @@ def provision_ip_trunk_isis_iface_dry( f"{subscription.iptrunk.geant_s_sid}", } - execute_playbook( - playbook_name="iptrunks.yaml", - callback_route=callback_route, - inventory=f"{subscription.iptrunk.iptrunk_sides[0].iptrunk_side_node.router_fqdn}\n" - f"{subscription.iptrunk.iptrunk_sides[1].iptrunk_side_node.router_fqdn}\n", - extra_vars=extra_vars, - ) - - return {"subscription": subscription} + return { + "playbook_name": "iptrunks.yaml", + "inventory": { + "all": { + "hosts": { + subscription.iptrunk.iptrunk_sides[0].iptrunk_side_node.router_fqdn: None, + subscription.iptrunk.iptrunk_sides[1].iptrunk_side_node.router_fqdn: None, + } + } + }, + "extra_vars": extra_vars, + } @step("[FOR REAL] Provision IP trunk ISIS interface") -def provision_ip_trunk_isis_iface_real( - subscription: Iptrunk, - process_id: UUIDstr, - callback_route: str, - tt_number: str, -) -> State: +def provision_ip_trunk_isis_iface_real(subscription: Iptrunk, process_id: UUIDstr, tt_number: str) -> LSOState: """Deploy the new :term:`ISIS` metric on both sides of the trunk.""" extra_vars = { "wfo_trunk_json": json.loads(json_dumps(subscription)), @@ -81,15 +74,18 @@ def provision_ip_trunk_isis_iface_real( f"{subscription.iptrunk.geant_s_sid}", } - execute_playbook( - playbook_name="iptrunks.yaml", - callback_route=callback_route, - inventory=f"{subscription.iptrunk.iptrunk_sides[0].iptrunk_side_node.router_fqdn}\n" - f"{subscription.iptrunk.iptrunk_sides[1].iptrunk_side_node.router_fqdn}\n", - extra_vars=extra_vars, - ) - - return {"subscription": subscription} + return { + "playbook_name": "iptrunks.yaml", + "inventory": { + "all": { + "hosts": { + subscription.iptrunk.iptrunk_sides[0].iptrunk_side_node.router_fqdn: None, + subscription.iptrunk.iptrunk_sides[1].iptrunk_side_node.router_fqdn: None, + } + } + }, + "extra_vars": extra_vars, + } @workflow( diff --git a/gso/workflows/iptrunk/modify_trunk_interface.py b/gso/workflows/iptrunk/modify_trunk_interface.py index e947fd52b4569506d452629b3d2a1fd547468ef1..0dcc5d83cafecd34161ca2a2b272804c35e85a0b 100644 --- a/gso/workflows/iptrunk/modify_trunk_interface.py +++ b/gso/workflows/iptrunk/modify_trunk_interface.py @@ -21,7 +21,7 @@ from gso.products.product_blocks.iptrunk import ( IptrunkType, ) from gso.products.product_types.iptrunk import Iptrunk -from gso.services.lso_client import execute_playbook, lso_interaction +from gso.services.lso_client import LSOState, lso_interaction from gso.services.netbox_client import NetboxClient from gso.utils.helpers import ( available_interfaces_choices, @@ -186,34 +186,40 @@ def determine_change_in_capacity( @step("Check IP connectivity of the trunk") -def check_ip_trunk_connectivity(subscription: Iptrunk, callback_route: str) -> State: +def check_ip_trunk_connectivity(subscription: Iptrunk) -> LSOState: """Check successful connectivity across a trunk.""" extra_vars = {"wfo_ip_trunk_json": json.loads(json_dumps(subscription)), "check": "ping"} - execute_playbook( - playbook_name="iptrunks_checks.yaml", - callback_route=callback_route, - inventory=subscription.iptrunk.iptrunk_sides[0].iptrunk_side_node.router_fqdn, - extra_vars=extra_vars, - ) - - return {"subscription": subscription} + return { + "playbook_name": "iptrunks_checks.yaml", + "inventory": { + "all": { + "hosts": { + subscription.iptrunk.iptrunk_sides[0].iptrunk_side_node.router_fqdn: None, + } + } + }, + "extra_vars": extra_vars, + } @step("Check LLDP on the trunk endpoints") -def check_ip_trunk_lldp(subscription: Iptrunk, callback_route: str) -> State: +def check_ip_trunk_lldp(subscription: Iptrunk) -> LSOState: """Check LLDP on trunk endpoints.""" extra_vars = {"wfo_ip_trunk_json": json.loads(json_dumps(subscription)), "check": "lldp"} - execute_playbook( - playbook_name="iptrunks_checks.yaml", - callback_route=callback_route, - inventory=f"{subscription.iptrunk.iptrunk_sides[0].iptrunk_side_node.router_fqdn}\n" - f"{subscription.iptrunk.iptrunk_sides[1].iptrunk_side_node.router_fqdn}\n", - extra_vars=extra_vars, - ) - - return {"subscription": subscription} + return { + "playbook_name": "iptrunks_checks.yaml", + "inventory": { + "all": { + "hosts": { + subscription.iptrunk.iptrunk_sides[0].iptrunk_side_node.router_fqdn: None, + subscription.iptrunk.iptrunk_sides[1].iptrunk_side_node.router_fqdn: None, + } + } + }, + "extra_vars": extra_vars, + } def update_side_members(subscription: Iptrunk, side_index: int, new_members: list[dict]) -> None: @@ -301,12 +307,8 @@ def modify_iptrunk_subscription( @step("[DRY RUN] Provision IP trunk interface") def provision_ip_trunk_iface_dry( - subscription: Iptrunk, - process_id: UUIDstr, - callback_route: str, - tt_number: str, - removed_ae_members: list[str], -) -> State: + subscription: Iptrunk, process_id: UUIDstr, tt_number: str, removed_ae_members: list[str] +) -> LSOState: """Perform a dry run of deploying the updated IP trunk.""" extra_vars = { "wfo_trunk_json": json.loads(json_dumps(subscription)), @@ -318,25 +320,24 @@ def provision_ip_trunk_iface_dry( f"{subscription.iptrunk.geant_s_sid}", } - execute_playbook( - playbook_name="iptrunks.yaml", - callback_route=callback_route, - inventory=f"{subscription.iptrunk.iptrunk_sides[0].iptrunk_side_node.router_fqdn}\n" - f"{subscription.iptrunk.iptrunk_sides[1].iptrunk_side_node.router_fqdn}\n", - extra_vars=extra_vars, - ) - - return {"subscription": subscription} + return { + "playbook_name": "iptrunks.yaml", + "inventory": { + "all": { + "hosts": { + subscription.iptrunk.iptrunk_sides[0].iptrunk_side_node.router_fqdn: None, + subscription.iptrunk.iptrunk_sides[1].iptrunk_side_node.router_fqdn: None, + } + } + }, + "extra_vars": extra_vars, + } @step("[FOR REAL] Provision IP trunk interface") def provision_ip_trunk_iface_real( - subscription: Iptrunk, - process_id: UUIDstr, - callback_route: str, - tt_number: str, - removed_ae_members: list[str], -) -> State: + subscription: Iptrunk, process_id: UUIDstr, tt_number: str, removed_ae_members: list[str] +) -> LSOState: """Provision the new IP trunk with updated interfaces.""" extra_vars = { "wfo_trunk_json": json.loads(json_dumps(subscription)), @@ -348,15 +349,18 @@ def provision_ip_trunk_iface_real( f"{subscription.iptrunk.geant_s_sid}", } - execute_playbook( - playbook_name="iptrunks.yaml", - callback_route=callback_route, - inventory=f"{subscription.iptrunk.iptrunk_sides[0].iptrunk_side_node.router_fqdn}\n" - f"{subscription.iptrunk.iptrunk_sides[1].iptrunk_side_node.router_fqdn}\n", - extra_vars=extra_vars, - ) - - return {"subscription": subscription} + return { + "playbook_name": "iptrunks.yaml", + "inventory": { + "all": { + "hosts": { + subscription.iptrunk.iptrunk_sides[0].iptrunk_side_node.router_fqdn: None, + subscription.iptrunk.iptrunk_sides[1].iptrunk_side_node.router_fqdn: None, + } + } + }, + "extra_vars": extra_vars, + } def _netbox_update_interfaces( @@ -455,19 +459,22 @@ def allocate_interfaces_in_netbox_side_b(subscription: Iptrunk, previous_ae_memb @step("Check Optical POST levels on the trunk endpoint") -def check_ip_trunk_optical_levels_post(subscription: Iptrunk, callback_route: str) -> State: +def check_ip_trunk_optical_levels_post(subscription: Iptrunk) -> LSOState: """Check Optical POST levels on the trunk.""" extra_vars = {"wfo_ip_trunk_json": json.loads(json_dumps(subscription)), "check": "optical_post"} - execute_playbook( - playbook_name="iptrunks_checks.yaml", - callback_route=callback_route, - inventory=f"{subscription.iptrunk.iptrunk_sides[0].iptrunk_side_node.router_fqdn}\n" - f"{subscription.iptrunk.iptrunk_sides[1].iptrunk_side_node.router_fqdn}\n", - extra_vars=extra_vars, - ) - - return {"subscription": subscription} + return { + "playbook_name": "iptrunks_checks.yaml", + "inventory": { + "all": { + "hosts": { + subscription.iptrunk.iptrunk_sides[0].iptrunk_side_node.router_fqdn: None, + subscription.iptrunk.iptrunk_sides[1].iptrunk_side_node.router_fqdn: None, + } + } + }, + "extra_vars": extra_vars, + } @workflow( diff --git a/gso/workflows/iptrunk/terminate_iptrunk.py b/gso/workflows/iptrunk/terminate_iptrunk.py index ac628d393a9c6dd52880ee28ceb39d403f023fd2..549bc3c23c6de32a8a8cb1cf8f1fd667aea9813b 100644 --- a/gso/workflows/iptrunk/terminate_iptrunk.py +++ b/gso/workflows/iptrunk/terminate_iptrunk.py @@ -20,7 +20,7 @@ from orchestrator.workflows.utils import wrap_modify_initial_input_form from gso.products.product_blocks.iptrunk import IptrunkSideBlock from gso.products.product_types.iptrunk import Iptrunk from gso.services import infoblox -from gso.services.lso_client import execute_playbook, lso_interaction +from gso.services.lso_client import LSOState, lso_interaction from gso.services.netbox_client import NetboxClient from gso.utils.helpers import get_router_vendor from gso.utils.shared_enums import Vendor @@ -51,7 +51,7 @@ def initial_input_form_generator(subscription_id: UUIDstr) -> FormGenerator: @step("[DRY RUN] Deprovision IP trunk") -def deprovision_ip_trunk_dry(subscription: Iptrunk, process_id: UUIDstr, callback_route: str, tt_number: str) -> State: +def deprovision_ip_trunk_dry(subscription: Iptrunk, process_id: UUIDstr, tt_number: str) -> LSOState: """Perform a dry run of deleting configuration from the routers.""" extra_vars = { "wfo_trunk_json": json.loads(json_dumps(subscription)), @@ -62,19 +62,22 @@ def deprovision_ip_trunk_dry(subscription: Iptrunk, process_id: UUIDstr, callbac f"- Remove config for {subscription.iptrunk.geant_s_sid}", } - execute_playbook( - playbook_name="iptrunks.yaml", - inventory=f"{subscription.iptrunk.iptrunk_sides[0].iptrunk_side_node.router_fqdn}\n" - f"{subscription.iptrunk.iptrunk_sides[1].iptrunk_side_node.router_fqdn}\n", - extra_vars=extra_vars, - callback_route=callback_route, - ) - - return {"subscription": subscription} + return { + "playbook_name": "iptrunks.yaml", + "inventory": { + "all": { + "hosts": { + subscription.iptrunk.iptrunk_sides[0].iptrunk_side_node.router_fqdn: None, + subscription.iptrunk.iptrunk_sides[1].iptrunk_side_node.router_fqdn: None, + } + } + }, + "extra_vars": extra_vars, + } @step("[FOR REAL] Deprovision IP trunk") -def deprovision_ip_trunk_real(subscription: Iptrunk, process_id: UUIDstr, callback_route: str, tt_number: str) -> State: +def deprovision_ip_trunk_real(subscription: Iptrunk, process_id: UUIDstr, tt_number: str) -> LSOState: """Delete configuration from the routers.""" extra_vars = { "wfo_trunk_json": json.loads(json_dumps(subscription)), @@ -85,15 +88,18 @@ def deprovision_ip_trunk_real(subscription: Iptrunk, process_id: UUIDstr, callba f"- Remove config for {subscription.iptrunk.geant_s_sid}", } - execute_playbook( - playbook_name="iptrunks.yaml", - inventory=f"{subscription.iptrunk.iptrunk_sides[0].iptrunk_side_node.router_fqdn}\n" - f"{subscription.iptrunk.iptrunk_sides[1].iptrunk_side_node.router_fqdn}\n", - extra_vars=extra_vars, - callback_route=callback_route, - ) - - return {"subscription": subscription} + return { + "playbook_name": "iptrunks.yaml", + "inventory": { + "all": { + "hosts": { + subscription.iptrunk.iptrunk_sides[0].iptrunk_side_node.router_fqdn: None, + subscription.iptrunk.iptrunk_sides[1].iptrunk_side_node.router_fqdn: None, + } + } + }, + "extra_vars": extra_vars, + } def _free_up_interfaces_from_netbox(side_block: IptrunkSideBlock) -> None: diff --git a/gso/workflows/iptrunk/validate_iptrunk.py b/gso/workflows/iptrunk/validate_iptrunk.py index bdfc4d3b952eca2e998d46ceab47250a56cfdb21..7832487fba8cddcbef46247ed8ac85f0cc5a3dd1 100644 --- a/gso/workflows/iptrunk/validate_iptrunk.py +++ b/gso/workflows/iptrunk/validate_iptrunk.py @@ -11,24 +11,29 @@ from orchestrator.workflows.utils import wrap_modify_initial_input_form from gso.products.product_types.iptrunk import Iptrunk from gso.services import infoblox -from gso.services.lso_client import anonymous_lso_interaction, execute_playbook +from gso.services.lso_client import LSOState, anonymous_lso_interaction from gso.services.netbox_client import NetboxClient from gso.utils.helpers import get_router_vendor from gso.utils.shared_enums import Vendor @step("Validate IP trunk configuration") -def validate_router_config(subscription: Iptrunk, callback_route: str) -> None: +def validate_router_config(subscription: Iptrunk) -> LSOState: """Run an Ansible playbook that validates the configuration that is present on an active IP trunk.""" extra_vars = {"wfo_trunk_json": json.loads(json_dumps(subscription)), "verb": "validate"} - execute_playbook( - playbook_name="base_config.yaml", - callback_route=callback_route, - inventory=f"{subscription.iptrunk.iptrunk_sides[0].iptrunk_side_node.router_fqdn}\n" - f"{subscription.iptrunk.iptrunk_sides[1].iptrunk_side_node.router_fqdn}\n", - extra_vars=extra_vars, - ) + return { + "playbook_name": "base_config.yaml", + "inventory": { + "all": { + "hosts": { + subscription.iptrunk.iptrunk_sides[0].iptrunk_side_node.router_fqdn: None, + subscription.iptrunk.iptrunk_sides[1].iptrunk_side_node.router_fqdn: None, + } + } + }, + "extra_vars": extra_vars, + } @step("Verify IPAM resources for LAG interfaces") @@ -129,56 +134,71 @@ def verify_netbox_entries(subscription: Iptrunk) -> None: @step("Verify configuration of IPtrunk") -def verify_iptrunk_config(subscription: Iptrunk, callback_route: str) -> None: +def verify_iptrunk_config(subscription: Iptrunk) -> LSOState: """Check for configuration drift on the relevant routers.""" - execute_playbook( - playbook_name="iptrunks.yaml", - callback_route=callback_route, - inventory=f"{subscription.iptrunk.iptrunk_sides[0].iptrunk_side_node.router_fqdn}\n" - f"{subscription.iptrunk.iptrunk_sides[1].iptrunk_side_node.router_fqdn}\n", - extra_vars={ + return { + "playbook_name": "iptrunks.yaml", + "inventory": { + "all": { + "hosts": { + subscription.iptrunk.iptrunk_sides[0].iptrunk_side_node.router_fqdn: None, + subscription.iptrunk.iptrunk_sides[1].iptrunk_side_node.router_fqdn: None, + } + } + }, + "extra_vars": { "wfo_trunk_json": json.loads(json_dumps(subscription)), "verb": "deploy", "dry_run": "true", "config_object": "trunk_interface", "is_verification_workflow": "true", }, - ) + } @step("Check ISIS configuration") -def check_ip_trunk_isis(subscription: Iptrunk, callback_route: str) -> None: +def check_ip_trunk_isis(subscription: Iptrunk) -> LSOState: """Run an Ansible playbook to check for any :term:`ISIS` configuration drift.""" - execute_playbook( - playbook_name="iptrunks.yaml", - callback_route=callback_route, - inventory=f"{subscription.iptrunk.iptrunk_sides[0].iptrunk_side_node.router_fqdn}\n" - f"{subscription.iptrunk.iptrunk_sides[1].iptrunk_side_node.router_fqdn}\n", - extra_vars={ + return { + "playbook_name": "iptrunks.yaml", + "inventory": { + "all": { + "hosts": { + subscription.iptrunk.iptrunk_sides[0].iptrunk_side_node.router_fqdn: None, + subscription.iptrunk.iptrunk_sides[1].iptrunk_side_node.router_fqdn: None, + } + } + }, + "extra_vars": { "wfo_trunk_json": json.loads(json_dumps(subscription)), "verb": "deploy", "dry_run": "true", "config_object": "isis_interface", "is_verification_workflow": "true", }, - ) + } @step("Verify TWAMP configuration") -def verify_twamp_config(subscription: Iptrunk, callback_route: str) -> None: +def verify_twamp_config(subscription: Iptrunk) -> LSOState: """Check for configuration drift of TWAMP.""" - execute_playbook( - playbook_name="deploy_twamp.yaml", - callback_route=callback_route, - inventory=f"{subscription.iptrunk.iptrunk_sides[0].iptrunk_side_node.router_fqdn}\n" - f"{subscription.iptrunk.iptrunk_sides[1].iptrunk_side_node.router_fqdn}\n", - extra_vars={ + return { + "playbook_name": "deploy_twamp.yaml", + "inventory": { + "all": { + "hosts": { + subscription.iptrunk.iptrunk_sides[0].iptrunk_side_node.router_fqdn: None, + subscription.iptrunk.iptrunk_sides[1].iptrunk_side_node.router_fqdn: None, + } + } + }, + "extra_vars": { "subscription": json.loads(json_dumps(subscription)), "verb": "deploy", "dry_run": "true", "is_verification_workflow": "true", }, - ) + } @workflow( diff --git a/gso/workflows/router/promote_p_to_pe.py b/gso/workflows/router/promote_p_to_pe.py index 0f0b31da1fcf7efe8049e9d24cd1e20726bff9c0..101e4c65c5cddc0ecd8695ce2de4809362775177 100644 --- a/gso/workflows/router/promote_p_to_pe.py +++ b/gso/workflows/router/promote_p_to_pe.py @@ -17,13 +17,26 @@ from pydantic import ConfigDict, model_validator from gso.products.product_blocks.router import RouterRole from gso.products.product_types.router import Router -from gso.services import lso_client from gso.services.kentik_client import KentikClient, NewKentikDevice -from gso.services.lso_client import lso_interaction +from gso.services.lso_client import LSOState, lso_interaction from gso.services.subscriptions import get_all_active_sites from gso.utils.helpers import generate_inventory_for_active_routers from gso.utils.shared_enums import Vendor from gso.utils.types.tt_number import TTNumber +from gso.utils.workflow_steps import ( + add_all_p_to_pe_dry, + add_all_p_to_pe_real, + add_pe_mesh_to_pe_dry, + add_pe_mesh_to_pe_real, + add_pe_to_all_p_dry, + add_pe_to_all_p_real, + add_pe_to_pe_mesh_dry, + add_pe_to_pe_mesh_real, + check_l3_services, + check_pe_ibgp, + update_sdp_mesh_dry, + update_sdp_mesh_real, +) def initial_input_form_generator(subscription_id: UUIDstr) -> FormGenerator: @@ -49,7 +62,7 @@ def initial_input_form_generator(subscription_id: UUIDstr) -> FormGenerator: @step("Evacuate the router by setting isis_overload") -def set_isis_overload(subscription: dict[str, Any], callback_route: str, tt_number: str, process_id: UUIDstr) -> None: +def set_isis_overload(subscription: dict[str, Any], tt_number: str, process_id: UUIDstr) -> LSOState: """Evacuate the router by setting isis overload.""" extra_vars = { "dry_run": False, @@ -58,18 +71,15 @@ def set_isis_overload(subscription: dict[str, Any], callback_route: str, tt_numb "verb": "set_isis_overload", } - lso_client.execute_playbook( - playbook_name="promote_p_to_pe.yaml", - callback_route=callback_route, - inventory=subscription["router"]["router_fqdn"], - extra_vars=extra_vars, - ) + return { + "playbook_name": "promote_p_to_pe.yaml", + "inventory": {"all": {"hosts": {subscription["router"]["router_fqdn"]: None}}}, + "extra_vars": extra_vars, + } @step("[DRY RUN] Deploy PE base config") -def deploy_pe_base_config_dry( - subscription: dict[str, Any], callback_route: str, tt_number: str, process_id: UUIDstr -) -> None: +def deploy_pe_base_config_dry(subscription: dict[str, Any], tt_number: str, process_id: UUIDstr) -> LSOState: """Perform a dry run of adding the base config to the router.""" extra_vars = { "dry_run": True, @@ -80,18 +90,15 @@ def deploy_pe_base_config_dry( "geant_sites": json.loads(json_dumps(get_all_active_sites())), } - lso_client.execute_playbook( - playbook_name="promote_p_to_pe.yaml", - callback_route=callback_route, - inventory=subscription["router"]["router_fqdn"], - extra_vars=extra_vars, - ) + return { + "playbook_name": "promote_p_to_pe.yaml", + "inventory": {"all": {"hosts": {subscription["router"]["router_fqdn"]: None}}}, + "extra_vars": extra_vars, + } @step("[FOR REAL] Deploy PE base config") -def deploy_pe_base_config_real( - subscription: dict[str, Any], callback_route: str, tt_number: str, process_id: UUIDstr -) -> None: +def deploy_pe_base_config_real(subscription: dict[str, Any], tt_number: str, process_id: UUIDstr) -> LSOState: """Perform a real run of adding the base config to the router.""" extra_vars = { "dry_run": False, @@ -102,12 +109,11 @@ def deploy_pe_base_config_real( "geant_sites": json.loads(json_dumps(get_all_active_sites())), } - lso_client.execute_playbook( - playbook_name="promote_p_to_pe.yaml", - callback_route=callback_route, - inventory=subscription["router"]["router_fqdn"], - extra_vars=extra_vars, - ) + return { + "playbook_name": "promote_p_to_pe.yaml", + "inventory": {"all": {"hosts": {subscription["router"]["router_fqdn"]: None}}}, + "extra_vars": extra_vars, + } @inputstep("Prompt EARL insertion", assignee=Assignee.SYSTEM) @@ -158,62 +164,8 @@ def create_kentik_device(subscription: Router) -> State: return {"kentik_device": kentik_device} -@step("[DRY RUN] Include new PE into SDP mesh on other Nokia PEs") -def update_sdp_mesh_dry(subscription: dict[str, Any], callback_route: str, tt_number: str, process_id: UUIDstr) -> None: - """Perform a dry run for updating the SDP mesh with the new router.""" - extra_vars = { - "dry_run": True, - "subscription": subscription, - "commit_comment": f"GSO_PROCESS_ID: {process_id} - TT_NUMBER: {tt_number} - " - f"Update the SDP mesh for L2circuits(epipes) config on PE NOKIA routers", - "verb": "update_sdp_mesh", - "pe_router_list": { - subscription["router"]["router_fqdn"]: { - "lo4": str(subscription["router"]["router_lo_ipv4_address"]), - "lo6": str(subscription["router"]["router_lo_ipv6_address"]), - } - }, - } - - lso_client.execute_playbook( - playbook_name="update_pe_sdp_mesh.yaml", - callback_route=callback_route, - inventory=generate_inventory_for_active_routers(router_role=RouterRole.PE, router_vendor=Vendor.NOKIA), - extra_vars=extra_vars, - ) - - -@step("[FOR REAL] Include new PE into SDP mesh on other Nokia PEs") -def update_sdp_mesh_real( - subscription: dict[str, Any], callback_route: str, tt_number: str, process_id: UUIDstr -) -> None: - """Update the SDP mesh for L2 circuits(epipes) config on PE NOKIA routers.""" - extra_vars = { - "dry_run": False, - "subscription": subscription, - "commit_comment": f"GSO_PROCESS_ID: {process_id} - TT_NUMBER: {tt_number} - " - f"Update the SDP mesh for l2circuits(epipes) config on PE NOKIA routers", - "verb": "update_sdp_mesh", - "pe_router_list": { - subscription["router"]["router_fqdn"]: { - "lo4": str(subscription["router"]["router_lo_ipv4_address"]), - "lo6": str(subscription["router"]["router_lo_ipv6_address"]), - } - }, - } - - lso_client.execute_playbook( - playbook_name="update_pe_sdp_mesh.yaml", - callback_route=callback_route, - inventory=generate_inventory_for_active_routers(router_role=RouterRole.PE, router_vendor=Vendor.NOKIA), - extra_vars=extra_vars, - ) - - @step("[DRY RUN] Remove P from all PEs") -def remove_p_from_pe_dry( - subscription: dict[str, Any], callback_route: str, tt_number: str, process_id: UUIDstr -) -> None: +def remove_p_from_pe_dry(subscription: dict[str, Any], tt_number: str, process_id: UUIDstr) -> LSOState: """Perform a dry run of removing the P router from all the PE routers.""" extra_vars = { "dry_run": True, @@ -223,18 +175,15 @@ def remove_p_from_pe_dry( "verb": "remove_p_from_pe", } - lso_client.execute_playbook( - playbook_name="update_ibgp_mesh.yaml", - callback_route=callback_route, - inventory=generate_inventory_for_active_routers(RouterRole.PE), - extra_vars=extra_vars, - ) + return { + "playbook_name": "update_ibgp_mesh.yaml", + "inventory": generate_inventory_for_active_routers(RouterRole.PE), + "extra_vars": extra_vars, + } @step("[FOR REAL] Remove P from all PEs") -def remove_p_from_pe_real( - subscription: dict[str, Any], callback_route: str, tt_number: str, process_id: UUIDstr -) -> None: +def remove_p_from_pe_real(subscription: dict[str, Any], tt_number: str, process_id: UUIDstr) -> LSOState: """Remove the P router from all the PE routers.""" extra_vars = { "dry_run": False, @@ -244,121 +193,15 @@ def remove_p_from_pe_real( "verb": "remove_p_from_pe", } - lso_client.execute_playbook( - playbook_name="update_ibgp_mesh.yaml", - callback_route=callback_route, - inventory=generate_inventory_for_active_routers(RouterRole.PE), - extra_vars=extra_vars, - ) - - -@step("[DRY RUN] Add PE mesh to PE") -def add_pe_mesh_to_pe_dry( - subscription: dict[str, Any], callback_route: str, tt_number: str, process_id: UUIDstr -) -> None: - """Perform a dry run of adding list of PE routers into iGEANT/iGEANT6 of promoted router.""" - extra_vars = { - "dry_run": True, - "subscription": subscription, - "commit_comment": f"GSO_PROCESS_ID: {process_id} - TT_NUMBER: {tt_number} - " - f"Add list of PE routers into iGEANT/iGEANT6 of promoted router", - "verb": "add_pe_mesh_to_pe", - "pe_router_list": generate_inventory_for_active_routers(RouterRole.PE)["all"]["hosts"], - } - - lso_client.execute_playbook( - playbook_name="update_ibgp_mesh.yaml", - callback_route=callback_route, - inventory=subscription["router"]["router_fqdn"], - extra_vars=extra_vars, - ) - - -@step("[FOR REAL] Add PE mesh to PE") -def add_pe_mesh_to_pe_real( - subscription: dict[str, Any], callback_route: str, tt_number: str, process_id: UUIDstr -) -> None: - """Perform a real run of adding list of PE routers into iGEANT/iGEANT6 of promoted router.""" - extra_vars = { - "dry_run": False, - "subscription": subscription, - "commit_comment": f"GSO_PROCESS_ID: {process_id} - TT_NUMBER: {tt_number} - " - f"Add list of PE routers into iGEANT/iGEANT6 of promoted router", - "verb": "add_pe_mesh_to_pe", - "pe_router_list": generate_inventory_for_active_routers(RouterRole.PE)["all"]["hosts"], + return { + "playbook_name": "update_ibgp_mesh.yaml", + "inventory": generate_inventory_for_active_routers(RouterRole.PE), + "extra_vars": extra_vars, } - lso_client.execute_playbook( - playbook_name="update_ibgp_mesh.yaml", - callback_route=callback_route, - inventory=subscription["router"]["router_fqdn"], - extra_vars=extra_vars, - ) - - -@step("[DRY RUN] Add PE to PE mesh") -def add_pe_to_pe_mesh_dry( - subscription: dict[str, Any], callback_route: str, tt_number: str, process_id: UUIDstr -) -> None: - """Perform a dry run of adding the promoted router to all PE routers in iGEANT/iGEANT6.""" - extra_vars = { - "dry_run": True, - "subscription": subscription, - "commit_comment": f"GSO_PROCESS_ID: {process_id} - TT_NUMBER: {tt_number} - " - f"Add promoted router to all PE routers in iGEANT/iGEANT6.", - "verb": "add_pe_to_pe_mesh", - } - - lso_client.execute_playbook( - playbook_name="update_ibgp_mesh.yaml", - callback_route=callback_route, - inventory=generate_inventory_for_active_routers(RouterRole.PE), - extra_vars=extra_vars, - ) - - -@step("[FOR REAL] Add PE to PE mesh") -def add_pe_to_pe_mesh_real( - subscription: dict[str, Any], callback_route: str, tt_number: str, process_id: UUIDstr -) -> None: - """Perform a real run of adding the promoted router to all PE routers in iGEANT/iGEANT6.""" - extra_vars = { - "dry_run": False, - "subscription": subscription, - "commit_comment": f"GSO_PROCESS_ID: {process_id} - TT_NUMBER: {tt_number} - " - f"Add promoted router to all PE routers in iGEANT/iGEANT6.", - "verb": "add_pe_to_pe_mesh", - } - - lso_client.execute_playbook( - playbook_name="update_ibgp_mesh.yaml", - callback_route=callback_route, - inventory=generate_inventory_for_active_routers(RouterRole.PE), - extra_vars=extra_vars, - ) - - -@step("Check iBGP session") -def check_pe_ibgp(subscription: dict[str, Any], callback_route: str) -> None: - """Check the iBGP session.""" - extra_vars = { - "dry_run": False, - "subscription": subscription, - "verb": "check_pe_ibgp", - } - - lso_client.execute_playbook( - playbook_name="check_ibgp.yaml", - callback_route=callback_route, - inventory=subscription["router"]["router_fqdn"], - extra_vars=extra_vars, - ) - @step("[DRY RUN] Deploy routing instances") -def deploy_routing_instances_dry( - subscription: dict[str, Any], callback_route: str, tt_number: str, process_id: UUIDstr -) -> None: +def deploy_routing_instances_dry(subscription: dict[str, Any], tt_number: str, process_id: UUIDstr) -> LSOState: """Perform a dry run of deploying routing instances.""" extra_vars = { "dry_run": True, @@ -367,18 +210,15 @@ def deploy_routing_instances_dry( "verb": "deploy_routing_instances", } - lso_client.execute_playbook( - playbook_name="promote_p_to_pe.yaml", - callback_route=callback_route, - inventory=subscription["router"]["router_fqdn"], - extra_vars=extra_vars, - ) + return { + "playbook_name": "promote_p_to_pe.yaml", + "inventory": {"all": {"hosts": {subscription["router"]["router_fqdn"]: None}}}, + "extra_vars": extra_vars, + } @step("[FOR REAL] Deploy routing instances") -def deploy_routing_instances_real( - subscription: dict[str, Any], callback_route: str, tt_number: str, process_id: UUIDstr -) -> None: +def deploy_routing_instances_real(subscription: dict[str, Any], tt_number: str, process_id: UUIDstr) -> LSOState: """Perform a real run of deploying routing instances.""" extra_vars = { "dry_run": False, @@ -387,35 +227,15 @@ def deploy_routing_instances_real( "verb": "deploy_routing_instances", } - lso_client.execute_playbook( - playbook_name="promote_p_to_pe.yaml", - callback_route=callback_route, - inventory=subscription["router"]["router_fqdn"], - extra_vars=extra_vars, - ) - - -@step("Check L3 services") -def check_l3_services(subscription: dict[str, Any], callback_route: str) -> None: - """Check L3 services.""" - extra_vars = { - "dry_run": False, - "subscription": subscription, - "verb": "check_base_ris", + return { + "playbook_name": "promote_p_to_pe.yaml", + "inventory": {"all": {"hosts": {subscription["router"]["router_fqdn"]: None}}}, + "extra_vars": extra_vars, } - lso_client.execute_playbook( - playbook_name="check_l3_services.yaml", - callback_route=callback_route, - inventory=subscription["router"]["router_fqdn"], - extra_vars=extra_vars, - ) - @step("Remove ISIS overload") -def remove_isis_overload( - subscription: dict[str, Any], callback_route: str, tt_number: str, process_id: UUIDstr -) -> None: +def remove_isis_overload(subscription: dict[str, Any], tt_number: str, process_id: UUIDstr) -> LSOState: """Remove ISIS overload.""" extra_vars = { "dry_run": False, @@ -424,12 +244,11 @@ def remove_isis_overload( "verb": "remove_isis_overload", } - lso_client.execute_playbook( - playbook_name="promote_p_to_pe.yaml", - callback_route=callback_route, - inventory=subscription["router"]["router_fqdn"], - extra_vars=extra_vars, - ) + return { + "playbook_name": "promote_p_to_pe.yaml", + "inventory": {"all": {"hosts": {subscription["router"]["router_fqdn"]: None}}}, + "extra_vars": extra_vars, + } @step("Set router role to PE (Update subscription model)") @@ -440,98 +259,8 @@ def update_subscription_model(subscription: Router) -> State: return {"subscription": subscription} -@step("[DRY RUN] Add all P to this new PE") -def add_all_p_to_pe_dry(subscription: dict[str, Any], callback_route: str, tt_number: str, process_id: UUIDstr) -> None: - """Perform a dry run of adding all P routers to the PE router.""" - extra_vars = { - "dry_run": True, - "subscription": subscription, - "commit_comment": f"GSO_PROCESS_ID: {process_id} - TT_NUMBER: {tt_number} - Add all P-routers to this new PE", - "verb": "add_all_p_to_pe", - "p_router_list": generate_inventory_for_active_routers( - RouterRole.P, exclude_routers=[subscription["router"]["router_fqdn"]] - )["all"]["hosts"], - } - - lso_client.execute_playbook( - playbook_name="update_ibgp_mesh.yaml", - callback_route=callback_route, - inventory=subscription["router"]["router_fqdn"], - extra_vars=extra_vars, - ) - - -@step("[FOR REAL] Add all P to this new PE") -def add_all_p_to_pe_real( - subscription: dict[str, Any], callback_route: str, tt_number: str, process_id: UUIDstr -) -> None: - """Perform a real run of adding all P routers to the PE router.""" - extra_vars = { - "dry_run": False, - "subscription": subscription, - "commit_comment": f"GSO_PROCESS_ID: {process_id} - TT_NUMBER: {tt_number} - Add all P-routers to this new PE", - "verb": "add_all_p_to_pe", - "p_router_list": generate_inventory_for_active_routers( - RouterRole.P, exclude_routers=[subscription["router"]["router_fqdn"]] - )["all"]["hosts"], - } - - lso_client.execute_playbook( - playbook_name="update_ibgp_mesh.yaml", - callback_route=callback_route, - inventory=subscription["router"]["router_fqdn"], - extra_vars=extra_vars, - ) - - -@step("[DRY RUN] Add this new PE to all P") -def add_pe_to_all_p_dry(subscription: dict[str, Any], callback_route: str, tt_number: str, process_id: UUIDstr) -> None: - """Perform a dry run of adding promoted router to all PE routers in iGEANT/iGEANT6.""" - extra_vars = { - "dry_run": True, - "subscription": subscription, - "commit_comment": f"GSO_PROCESS_ID: {process_id} - TT_NUMBER: {tt_number} - " - f"Add promoted router to all PE routers in iGEANT/iGEANT6", - "verb": "add_pe_to_all_p", - } - - lso_client.execute_playbook( - playbook_name="update_ibgp_mesh.yaml", - callback_route=callback_route, - inventory=generate_inventory_for_active_routers( - RouterRole.P, exclude_routers=[subscription["router"]["router_fqdn"]] - ), - extra_vars=extra_vars, - ) - - -@step("[FOR REAL] Add this new PE to all P") -def add_pe_to_all_p_real( - subscription: dict[str, Any], callback_route: str, tt_number: str, process_id: UUIDstr -) -> None: - """Perform a real run of adding promoted router to all PE routers in iGEANT/iGEANT6.""" - extra_vars = { - "dry_run": False, - "subscription": subscription, - "commit_comment": f"GSO_PROCESS_ID: {process_id} - TT_NUMBER: {tt_number} - " - f"Add promoted router to all PE routers in iGEANT/iGEANT6", - "verb": "add_pe_to_all_p", - } - - lso_client.execute_playbook( - playbook_name="update_ibgp_mesh.yaml", - callback_route=callback_route, - inventory=generate_inventory_for_active_routers( - RouterRole.P, exclude_routers=[subscription["router"]["router_fqdn"]] - ), - extra_vars=extra_vars, - ) - - @step("[DRY RUN] Delete default routes") -def delete_default_routes_dry( - subscription: dict[str, Any], callback_route: str, tt_number: str, process_id: UUIDstr -) -> None: +def delete_default_routes_dry(subscription: dict[str, Any], tt_number: str, process_id: UUIDstr) -> LSOState: """Perform a dry run of deleting the default routes.""" extra_vars = { "dry_run": True, @@ -541,18 +270,15 @@ def delete_default_routes_dry( "verb": "delete_default_routes", } - lso_client.execute_playbook( - playbook_name="promote_p_to_pe.yaml", - callback_route=callback_route, - inventory=subscription["router"]["router_fqdn"], - extra_vars=extra_vars, - ) + return { + "playbook_name": "promote_p_to_pe.yaml", + "inventory": {"all": {"hosts": {subscription["router"]["router_fqdn"]: None}}}, + "extra_vars": extra_vars, + } @step("[FOR REAL] Delete default routes") -def delete_default_routes_real( - subscription: dict[str, Any], callback_route: str, tt_number: str, process_id: UUIDstr -) -> None: +def delete_default_routes_real(subscription: dict[str, Any], tt_number: str, process_id: UUIDstr) -> LSOState: """Perform a real run of deleting the default routes.""" extra_vars = { "dry_run": False, @@ -562,12 +288,11 @@ def delete_default_routes_real( "verb": "delete_default_routes", } - lso_client.execute_playbook( - playbook_name="promote_p_to_pe.yaml", - callback_route=callback_route, - inventory=subscription["router"]["router_fqdn"], - extra_vars=extra_vars, - ) + return { + "playbook_name": "promote_p_to_pe.yaml", + "inventory": {"all": {"hosts": {subscription["router"]["router_fqdn"]: None}}}, + "extra_vars": extra_vars, + } @workflow( diff --git a/gso/workflows/router/terminate_router.py b/gso/workflows/router/terminate_router.py index f7caa6c9936154f3afff8320f5942504f9477414..13cef223c8dfd0160130064d8e29e2290aa54f75 100644 --- a/gso/workflows/router/terminate_router.py +++ b/gso/workflows/router/terminate_router.py @@ -22,10 +22,10 @@ from orchestrator.workflows.utils import wrap_modify_initial_input_form from gso.products.product_blocks.router import RouterRole from gso.products.product_types.router import Router -from gso.services import infoblox, lso_client +from gso.services import infoblox from gso.services.kentik_client import KentikClient from gso.services.librenms_client import LibreNMSClient -from gso.services.lso_client import execute_playbook, lso_interaction +from gso.services.lso_client import LSOState, lso_interaction from gso.services.netbox_client import NetboxClient from gso.settings import load_oss_params from gso.utils.helpers import generate_inventory_for_active_routers @@ -69,9 +69,7 @@ def deprovision_loopback_ips(subscription: Router) -> dict: @step("[DRY RUN] Remove configuration from router") -def remove_config_from_router_dry( - subscription: Router, callback_route: str, process_id: UUIDstr, tt_number: str -) -> None: +def remove_config_from_router_dry(subscription: Router, process_id: UUIDstr, tt_number: str) -> LSOState: """Remove configuration from the router, first as a dry run.""" extra_vars = { "wfo_router_json": json.loads(json_dumps(subscription)), @@ -81,18 +79,15 @@ def remove_config_from_router_dry( f"{subscription.router.router_fqdn}", } - execute_playbook( - playbook_name="base_config.yaml", - callback_route=callback_route, - inventory=subscription.router.router_fqdn, - extra_vars=extra_vars, - ) + return { + "playbook_name": "base_config.yaml", + "inventory": {"all": {"hosts": {subscription.router.router_fqdn: None}}}, + "extra_vars": extra_vars, + } @step("[FOR REAL] Remove configuration from router") -def remove_config_from_router_real( - subscription: Router, callback_route: str, process_id: UUIDstr, tt_number: str -) -> None: +def remove_config_from_router_real(subscription: Router, process_id: UUIDstr, tt_number: str) -> LSOState: """Remove configuration from the router.""" extra_vars = { "wfo_router_json": json.loads(json_dumps(subscription)), @@ -102,12 +97,11 @@ def remove_config_from_router_real( f"{subscription.router.router_fqdn}", } - execute_playbook( - playbook_name="base_config.yaml", # FIX: need to use correct playbook. - callback_route=callback_route, - inventory=subscription.router.router_fqdn, - extra_vars=extra_vars, - ) + return { + "playbook_name": "base_config.yaml", + "inventory": {"all": {"hosts": {subscription.router.router_fqdn: None}}}, + "extra_vars": extra_vars, + } @step("Remove Device from Netbox") @@ -118,7 +112,7 @@ def remove_device_from_netbox(subscription: Router) -> dict[str, Router]: @step("[DRY RUN] Remove P router from all the PE routers") -def remove_p_from_all_pe_dry(subscription: Router, callback_route: str, tt_number: str, process_id: UUIDstr) -> None: +def remove_p_from_all_pe_dry(subscription: Router, tt_number: str, process_id: UUIDstr) -> LSOState: """Perform a dry run of removing the terminated router from all the PE routers.""" extra_vars = { "dry_run": True, @@ -128,16 +122,15 @@ def remove_p_from_all_pe_dry(subscription: Router, callback_route: str, tt_numbe "verb": "remove_p_from_pe", } - lso_client.execute_playbook( - playbook_name="update_ibgp_mesh.yaml", - callback_route=callback_route, - inventory=generate_inventory_for_active_routers(RouterRole.PE), - extra_vars=extra_vars, - ) + return { + "playbook_name": "update_ibgp_mesh.yaml", + "inventory": generate_inventory_for_active_routers(RouterRole.PE), + "extra_vars": extra_vars, + } @step("[REAL RUN] Remove P router from all the PE routers") -def remove_p_from_all_pe_real(subscription: Router, callback_route: str, tt_number: str, process_id: UUIDstr) -> None: +def remove_p_from_all_pe_real(subscription: Router, tt_number: str, process_id: UUIDstr) -> LSOState: """Perform a real run of removing the terminated router from all the PE routers.""" extra_vars = { "dry_run": False, @@ -147,16 +140,15 @@ def remove_p_from_all_pe_real(subscription: Router, callback_route: str, tt_numb "verb": "remove_p_from_pe", } - lso_client.execute_playbook( - playbook_name="update_ibgp_mesh.yaml", - callback_route=callback_route, - inventory=generate_inventory_for_active_routers(RouterRole.PE), - extra_vars=extra_vars, - ) + return { + "playbook_name": "update_ibgp_mesh.yaml", + "inventory": generate_inventory_for_active_routers(RouterRole.PE), + "extra_vars": extra_vars, + } @step("[DRY RUN] Remove PE router from all the PE routers") -def remove_pe_from_all_pe_dry(subscription: Router, callback_route: str, tt_number: str, process_id: UUIDstr) -> None: +def remove_pe_from_all_pe_dry(subscription: Router, tt_number: str, process_id: UUIDstr) -> LSOState: """Perform a dry run of removing the terminated PE router from the PE router mesh.""" extra_vars = { "dry_run": True, @@ -166,18 +158,17 @@ def remove_pe_from_all_pe_dry(subscription: Router, callback_route: str, tt_numb "verb": "remove_pe_from_pe", } - lso_client.execute_playbook( - playbook_name="update_ibgp_mesh.yaml", - callback_route=callback_route, - inventory=generate_inventory_for_active_routers( + return { + "playbook_name": "update_ibgp_mesh.yaml", + "inventory": generate_inventory_for_active_routers( RouterRole.PE, exclude_routers=[subscription.router.router_fqdn] ), - extra_vars=extra_vars, - ) + "extra_vars": extra_vars, + } @step("[REAL RUN] Remove all PE routers from all the PE routers") -def remove_pe_from_all_pe_real(subscription: Router, callback_route: str, tt_number: str, process_id: UUIDstr) -> None: +def remove_pe_from_all_pe_real(subscription: Router, tt_number: str, process_id: UUIDstr) -> LSOState: """Perform a real run of removing terminated PE router from PE the router mesh.""" extra_vars = { "dry_run": False, @@ -187,18 +178,17 @@ def remove_pe_from_all_pe_real(subscription: Router, callback_route: str, tt_num "verb": "remove_pe_from_pe", } - lso_client.execute_playbook( - playbook_name="update_ibgp_mesh.yaml", - callback_route=callback_route, - inventory=generate_inventory_for_active_routers( + return { + "playbook_name": "update_ibgp_mesh.yaml", + "inventory": generate_inventory_for_active_routers( RouterRole.PE, exclude_routers=[subscription.router.router_fqdn] ), - extra_vars=extra_vars, - ) + "extra_vars": extra_vars, + } @step("[DRY RUN] Remove PE router from all the P routers") -def remove_pe_from_all_p_dry(subscription: Router, callback_route: str, tt_number: str, process_id: UUIDstr) -> None: +def remove_pe_from_all_p_dry(subscription: Router, tt_number: str, process_id: UUIDstr) -> LSOState: """Perform a dry run of removing PE router from all P routers.""" extra_vars = { "dry_run": True, @@ -208,16 +198,15 @@ def remove_pe_from_all_p_dry(subscription: Router, callback_route: str, tt_numbe "verb": "remove_pe_from_p", } - lso_client.execute_playbook( - playbook_name="update_ibgp_mesh.yaml", - callback_route=callback_route, - inventory=generate_inventory_for_active_routers(RouterRole.P), - extra_vars=extra_vars, - ) + return { + "playbook_name": "update_ibgp_mesh.yaml", + "inventory": generate_inventory_for_active_routers(RouterRole.P), + "extra_vars": extra_vars, + } @step("[REAL RUN] Remove PE router from all P routers") -def remove_pe_from_all_p_real(subscription: Router, callback_route: str, tt_number: str, process_id: UUIDstr) -> None: +def remove_pe_from_all_p_real(subscription: Router, tt_number: str, process_id: UUIDstr) -> LSOState: """Perform a real run of removing PE router from all P routers.""" extra_vars = { "dry_run": False, @@ -227,12 +216,11 @@ def remove_pe_from_all_p_real(subscription: Router, callback_route: str, tt_numb "verb": "remove_pe_from_p", } - lso_client.execute_playbook( - playbook_name="update_ibgp_mesh.yaml", - callback_route=callback_route, - inventory=generate_inventory_for_active_routers(RouterRole.P), - extra_vars=extra_vars, - ) + return { + "playbook_name": "update_ibgp_mesh.yaml", + "inventory": generate_inventory_for_active_routers(RouterRole.P), + "extra_vars": extra_vars, + } @step("Remove Device from Librenms") diff --git a/gso/workflows/router/update_ibgp_mesh.py b/gso/workflows/router/update_ibgp_mesh.py index a506e625ae15014439c538070fb0cf074c605309..e79c0274e173ebccd23e5d3834eea5ed6c77e9b2 100644 --- a/gso/workflows/router/update_ibgp_mesh.py +++ b/gso/workflows/router/update_ibgp_mesh.py @@ -7,19 +7,35 @@ from orchestrator.forms import FormPage from orchestrator.forms.validators import Label from orchestrator.targets import Target from orchestrator.types import FormGenerator, State, SubscriptionLifecycle, UUIDstr -from orchestrator.workflow import StepList, begin, done, inputstep, step, workflow +from orchestrator.workflow import StepList, begin, conditional, done, inputstep, step, workflow from orchestrator.workflows.steps import resync, store_process_subscription, unsync from orchestrator.workflows.utils import wrap_modify_initial_input_form from pydantic import ConfigDict, model_validator from gso.products.product_blocks.router import RouterRole from gso.products.product_types.router import Router -from gso.services import librenms_client, lso_client -from gso.services.lso_client import lso_interaction +from gso.services import librenms_client +from gso.services.lso_client import LSOState, lso_interaction from gso.services.subscriptions import get_trunks_that_terminate_on_router from gso.utils.helpers import generate_inventory_for_active_routers from gso.utils.types.snmp import SNMPVersion from gso.utils.types.tt_number import TTNumber +from gso.utils.workflow_steps import ( + add_all_p_to_pe_dry, + add_all_p_to_pe_real, + add_pe_mesh_to_pe_dry, + add_pe_mesh_to_pe_real, + add_pe_to_all_p_dry, + add_pe_to_all_p_real, + add_pe_to_pe_mesh_dry, + add_pe_to_pe_mesh_real, + check_l3_services, + check_pe_ibgp, + update_sdp_mesh_dry, + update_sdp_mesh_real, + update_sdp_single_pe_dry, + update_sdp_single_pe_real, +) def initial_input_form_generator(subscription_id: UUIDstr) -> FormGenerator: @@ -52,7 +68,7 @@ def initial_input_form_generator(subscription_id: UUIDstr) -> FormGenerator: @step("[DRY RUN] Add P router to iBGP mesh") -def add_p_to_mesh_dry(subscription: dict[str, Any], callback_route: str, tt_number: str, process_id: UUIDstr) -> None: +def add_p_to_mesh_dry(subscription: dict[str, Any], tt_number: str, process_id: UUIDstr) -> LSOState: """Perform a dry run of adding the new P router to the PE router mesh.""" extra_vars = { "dry_run": True, @@ -61,16 +77,15 @@ def add_p_to_mesh_dry(subscription: dict[str, Any], callback_route: str, tt_numb "verb": "add_p_to_pe", } - lso_client.execute_playbook( - playbook_name="update_ibgp_mesh.yaml", - callback_route=callback_route, - inventory=generate_inventory_for_active_routers(RouterRole.PE), - extra_vars=extra_vars, - ) + return { + "playbook_name": "update_ibgp_mesh.yaml", + "inventory": generate_inventory_for_active_routers(RouterRole.PE), + "extra_vars": extra_vars, + } @step("[FOR REAL] Add P router to iBGP mesh") -def add_p_to_mesh_real(subscription: dict[str, Any], callback_route: str, tt_number: str, process_id: UUIDstr) -> None: +def add_p_to_mesh_real(subscription: dict[str, Any], tt_number: str, process_id: UUIDstr) -> LSOState: """Add the P router to the mesh of PE routers.""" extra_vars = { "dry_run": False, @@ -79,16 +94,15 @@ def add_p_to_mesh_real(subscription: dict[str, Any], callback_route: str, tt_num "verb": "add_p_to_pe", } - lso_client.execute_playbook( - playbook_name="update_ibgp_mesh.yaml", - callback_route=callback_route, - inventory=generate_inventory_for_active_routers(RouterRole.PE), - extra_vars=extra_vars, - ) + return { + "playbook_name": "update_ibgp_mesh.yaml", + "inventory": generate_inventory_for_active_routers(RouterRole.PE), + "extra_vars": extra_vars, + } @step("[DRY RUN] Add all PE routers to P router iBGP group") -def add_all_pe_to_p_dry(subscription: dict[str, Any], callback_route: str) -> None: +def add_all_pe_to_p_dry(subscription: dict[str, Any]) -> LSOState: """Perform a dry run of adding the list of all PE routers to the new P router.""" extra_vars = { "dry_run": True, @@ -97,18 +111,15 @@ def add_all_pe_to_p_dry(subscription: dict[str, Any], callback_route: str) -> No "verb": "add_pe_to_p", } - lso_client.execute_playbook( - playbook_name="update_ibgp_mesh.yaml", - callback_route=callback_route, - inventory=subscription["router"]["router_fqdn"], - extra_vars=extra_vars, - ) + return { + "playbook_name": "update_ibgp_mesh.yaml", + "inventory": {"all": {"hosts": {subscription["router"]["router_fqdn"]: None}}}, + "extra_vars": extra_vars, + } @step("[FOR REAL] Add all PE routers to P router iBGP group") -def add_all_pe_to_p_real( - subscription: dict[str, Any], callback_route: str, tt_number: str, process_id: UUIDstr -) -> None: +def add_all_pe_to_p_real(subscription: dict[str, Any], tt_number: str, process_id: UUIDstr) -> LSOState: """Add the list of all PE routers to the new P router.""" extra_vars = { "dry_run": False, @@ -118,23 +129,21 @@ def add_all_pe_to_p_real( "verb": "add_pe_to_p", } - lso_client.execute_playbook( - playbook_name="update_ibgp_mesh.yaml", - callback_route=callback_route, - inventory=subscription["router"]["router_fqdn"], - extra_vars=extra_vars, - ) + return { + "playbook_name": "update_ibgp_mesh.yaml", + "inventory": {"all": {"hosts": {subscription["router"]["router_fqdn"]: None}}}, + "extra_vars": extra_vars, + } @step("Verify iBGP session health") -def check_ibgp_session(subscription: Router, callback_route: str) -> None: +def check_ibgp_session(subscription: Router) -> LSOState: """Run a playbook using the provisioning proxy, to check the health of the new iBGP session.""" - lso_client.execute_playbook( - playbook_name="check_ibgp.yaml", - callback_route=callback_route, - inventory=subscription.router.router_fqdn, - extra_vars={}, - ) + return { + "playbook_name": "check_ibgp.yaml", + "inventory": {"all": {"hosts": {subscription.router.router_fqdn: None}}}, + "extra_vars": {}, + } @step("Add the router to LibreNMS") @@ -201,15 +210,32 @@ def update_ibgp_mesh() -> StepList: * Add the new P-router to LibreNMS. * Update the subscription model. """ + router_is_pe = conditional(lambda state: state["subscription"]["router"]["router_role"] == RouterRole.PE) + router_is_p = conditional(lambda state: state["subscription"]["router"]["router_role"] == RouterRole.P) + return ( begin >> store_process_subscription(Target.MODIFY) >> unsync - >> lso_interaction(add_p_to_mesh_dry) - >> lso_interaction(add_p_to_mesh_real) - >> lso_interaction(add_all_pe_to_p_dry) - >> lso_interaction(add_all_pe_to_p_real) - >> lso_interaction(check_ibgp_session) + >> router_is_p(lso_interaction(add_p_to_mesh_dry)) + >> router_is_p(lso_interaction(add_p_to_mesh_real)) + >> router_is_p(lso_interaction(add_all_pe_to_p_dry)) + >> router_is_p(lso_interaction(add_all_pe_to_p_real)) + >> router_is_p(lso_interaction(check_ibgp_session)) + >> router_is_pe(lso_interaction(add_pe_mesh_to_pe_dry)) + >> router_is_pe(lso_interaction(add_pe_mesh_to_pe_real)) + >> router_is_pe(lso_interaction(add_pe_to_pe_mesh_dry)) + >> router_is_pe(lso_interaction(add_pe_to_pe_mesh_real)) + >> router_is_pe(lso_interaction(add_all_p_to_pe_dry)) + >> router_is_pe(lso_interaction(add_all_p_to_pe_real)) + >> router_is_pe(lso_interaction(add_pe_to_all_p_dry)) + >> router_is_pe(lso_interaction(add_pe_to_all_p_real)) + >> router_is_pe(lso_interaction(update_sdp_single_pe_dry)) + >> router_is_pe(lso_interaction(update_sdp_single_pe_real)) + >> router_is_pe(lso_interaction(update_sdp_mesh_dry)) + >> router_is_pe(lso_interaction(update_sdp_mesh_real)) + >> router_is_pe(lso_interaction(check_pe_ibgp)) + >> router_is_pe(lso_interaction(check_l3_services)) >> add_device_to_librenms >> prompt_insert_in_radius >> prompt_radius_login diff --git a/gso/workflows/router/validate_router.py b/gso/workflows/router/validate_router.py index aa22b773d6004862aaab0b8c7ec2bf08b89f24be..9e13f521cb4edb062086b696846ff3d8bc17d8f9 100644 --- a/gso/workflows/router/validate_router.py +++ b/gso/workflows/router/validate_router.py @@ -1,11 +1,9 @@ """Router validation workflow. Used in a nightly schedule.""" -import json from typing import Any from orchestrator.targets import Target from orchestrator.utils.errors import ProcessFailureError -from orchestrator.utils.json import json_dumps 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 @@ -13,10 +11,10 @@ from pydantic_forms.types import State, UUIDstr from gso.products.product_blocks.router import RouterRole from gso.products.product_types.router import Router -from gso.services import infoblox, lso_client +from gso.services import infoblox from gso.services.kentik_client import KentikClient from gso.services.librenms_client import LibreNMSClient -from gso.services.lso_client import anonymous_lso_interaction, execute_playbook +from gso.services.lso_client import LSOState, anonymous_lso_interaction from gso.services.netbox_client import NetboxClient from gso.utils.helpers import generate_inventory_for_active_routers from gso.utils.shared_enums import Vendor @@ -55,7 +53,7 @@ def check_netbox_entry_exists(subscription: Router) -> None: @step("Verify BGP configuration on P router") -def verify_p_ibgp(subscription: dict[str, Any], callback_route: str) -> None: +def verify_p_ibgp(subscription: dict[str, Any]) -> LSOState: """Perform a dry run of adding the list of all PE routers to the new P router.""" extra_vars = { "dry_run": True, @@ -65,12 +63,11 @@ def verify_p_ibgp(subscription: dict[str, Any], callback_route: str) -> None: "is_verification_workflow": "true", } - lso_client.execute_playbook( - playbook_name="update_ibgp_mesh.yaml", - callback_route=callback_route, - inventory=subscription["router"]["router_fqdn"], - extra_vars=extra_vars, - ) + return { + "playbook_name": "update_ibgp_mesh.yaml", + "inventory": {"all": {"hosts": {subscription["router"]["router_fqdn"]: None}}}, + "extra_vars": extra_vars, + } @step("Verify correct LibreNMS entry") @@ -100,19 +97,18 @@ def check_kentik_entry_exists(subscription: Router) -> None: @step("Check base config for drift") -def verify_base_config(subscription: Router, callback_route: str) -> None: +def verify_base_config(subscription: dict[str, Any]) -> LSOState: """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": json.loads(json_dumps(subscription)), + return { + "playbook_name": "base_config.yaml", + "inventory": {"all": {"hosts": {subscription["router"]["router_fqdn"]: None}}}, + "extra_vars": { + "wfo_router_json": subscription, "verb": "deploy", "dry_run": "true", "is_verification_workflow": "true", }, - ) + } @workflow( diff --git a/setup.py b/setup.py index 569ae33fda96e4dd727cfbf89ef3cb03488d4847..840d33679d74054f70199c0537e6b780fb23bbec 100644 --- a/setup.py +++ b/setup.py @@ -4,7 +4,7 @@ from setuptools import find_packages, setup setup( name="geant-service-orchestrator", - version="2.18", + version="2.19", author="GÉANT Orchestration and Automation Team", author_email="goat@geant.org", description="GÉANT Service Orchestrator", diff --git a/test/workflows/iptrunk/test_create_iptrunk.py b/test/workflows/iptrunk/test_create_iptrunk.py index 0b7e00e638db87ddb27674d524bcbdde990d6067..267cd3827f7dbd908c472faf545e949fd231ee32 100644 --- a/test/workflows/iptrunk/test_create_iptrunk.py +++ b/test/workflows/iptrunk/test_create_iptrunk.py @@ -101,7 +101,7 @@ def input_form_wizard_data(request, juniper_router_subscription_factory, nokia_r @pytest.mark.workflow() -@patch("gso.workflows.iptrunk.create_iptrunk.execute_playbook") +@patch("gso.services.lso_client._send_request") @patch("gso.workflows.iptrunk.create_iptrunk.infoblox.allocate_v6_network") @patch("gso.workflows.iptrunk.create_iptrunk.infoblox.allocate_v4_network") @patch("gso.workflows.iptrunk.create_iptrunk.infoblox.create_host_by_ip") @@ -162,7 +162,7 @@ def test_successful_iptrunk_creation_with_standard_lso_result( @pytest.mark.workflow() -@patch("gso.workflows.iptrunk.create_iptrunk.execute_playbook") +@patch("gso.services.lso_client._send_request") @patch("gso.workflows.iptrunk.create_iptrunk.infoblox.allocate_v6_network") @patch("gso.workflows.iptrunk.create_iptrunk.infoblox.allocate_v4_network") @patch("gso.workflows.iptrunk.create_iptrunk.infoblox.find_host_by_ip") @@ -199,7 +199,7 @@ def test_iptrunk_creation_fails_when_lso_return_code_is_one( @pytest.mark.parametrize("input_form_wizard_data", [Vendor.JUNIPER], indirect=True) @pytest.mark.workflow() -@patch("gso.workflows.iptrunk.create_iptrunk.execute_playbook") +@patch("gso.services.lso_client._send_request") @patch("gso.workflows.iptrunk.create_iptrunk.infoblox.allocate_v6_network") @patch("gso.workflows.iptrunk.create_iptrunk.infoblox.allocate_v4_network") @patch("gso.workflows.iptrunk.create_iptrunk.infoblox.create_host_by_ip") diff --git a/test/workflows/iptrunk/test_deploy_twamp.py b/test/workflows/iptrunk/test_deploy_twamp.py index 8584cd99cae310bccf70ac22a6cd377b5672cefa..e65feac79a0a1fd9988eff99fe0bf2e55f68899b 100644 --- a/test/workflows/iptrunk/test_deploy_twamp.py +++ b/test/workflows/iptrunk/test_deploy_twamp.py @@ -12,7 +12,7 @@ from test.workflows import ( @pytest.mark.workflow() -@patch("gso.workflows.iptrunk.deploy_twamp.execute_playbook") +@patch("gso.services.lso_client._send_request") def test_iptrunk_deploy_twamp_success( mock_execute_playbook, iptrunk_subscription_factory, diff --git a/test/workflows/iptrunk/test_modify_isis_metric.py b/test/workflows/iptrunk/test_modify_isis_metric.py index 38f4b4e89e5d178b86c48eaafc32a311f07787c2..26a9bbd490eb4c73728a2e11defd836bf3701316 100644 --- a/test/workflows/iptrunk/test_modify_isis_metric.py +++ b/test/workflows/iptrunk/test_modify_isis_metric.py @@ -12,7 +12,7 @@ from test.workflows import ( @pytest.mark.workflow() -@patch("gso.services.lso_client.execute_playbook") +@patch("gso.services.lso_client._send_request") def test_iptrunk_modify_isis_metric_success( mock_provision_ip_trunk, iptrunk_subscription_factory, diff --git a/test/workflows/iptrunk/test_terminate_iptrunk.py b/test/workflows/iptrunk/test_terminate_iptrunk.py index 7fcca946895247f644783eb83ed183f334e73321..7319596ffa58936b0f92e2f2dfc948619eb386fd 100644 --- a/test/workflows/iptrunk/test_terminate_iptrunk.py +++ b/test/workflows/iptrunk/test_terminate_iptrunk.py @@ -3,6 +3,7 @@ from unittest.mock import patch import pytest from gso.products import Iptrunk +from gso.products.product_blocks.router import RouterRole from gso.settings import load_oss_params from test.services.conftest import MockedNetboxClient from test.workflows import ( @@ -14,8 +15,7 @@ from test.workflows import ( @pytest.mark.workflow() -@patch("gso.workflows.iptrunk.terminate_iptrunk.execute_playbook") -@patch("gso.utils.workflow_steps.lso_client.execute_playbook") +@patch("gso.services.lso_client._send_request") @patch("gso.workflows.iptrunk.terminate_iptrunk.infoblox.delete_network") @patch("gso.services.netbox_client.NetboxClient.delete_interface") @patch("gso.services.netbox_client.NetboxClient.free_interface") @@ -23,17 +23,20 @@ def test_successful_iptrunk_termination( mocked_free_interface, mocked_delete_interface, mock_infoblox_delete_network, - mock_set_isis_to_90k, mock_execute_playbook, iptrunk_subscription_factory, faker, data_config_filename, + nokia_router_subscription_factory, ): # Set up mock return values product_id = iptrunk_subscription_factory() mocked_netbox = MockedNetboxClient() mocked_delete_interface.return_value = mocked_netbox.delete_interface() mocked_free_interface.return_value = mocked_netbox.free_interface() + # Add two more routers to our fake network + nokia_router_subscription_factory(router_role=RouterRole.P) + nokia_router_subscription_factory(router_role=RouterRole.PE) # Run workflow oss_params = load_oss_params() @@ -60,7 +63,6 @@ def test_successful_iptrunk_termination( subscription = Iptrunk.from_subscription(subscription_id) assert subscription.status == "terminated" - assert mock_execute_playbook.call_count == 2 - assert mock_set_isis_to_90k.call_count == 1 + assert mock_execute_playbook.call_count == 3 assert mock_infoblox_delete_network.call_count == 2 assert subscription.iptrunk.iptrunk_isis_metric == oss_params.GENERAL.isis_high_metric diff --git a/test/workflows/iptrunk/test_validate_iptrunk.py b/test/workflows/iptrunk/test_validate_iptrunk.py index 13bcaed9b4be4284a6f1dc5fa4e41410d2f8dab6..6b132abca2e3b2616b2a9c8937fc5e64122837ba 100644 --- a/test/workflows/iptrunk/test_validate_iptrunk.py +++ b/test/workflows/iptrunk/test_validate_iptrunk.py @@ -69,7 +69,7 @@ def _mocked_netbox_client(): @patch("gso.services.infoblox.find_network_by_cidr") @patch("gso.services.infoblox.find_v6_host_by_fqdn") @patch("gso.services.infoblox.find_host_by_fqdn") -@patch("gso.workflows.iptrunk.validate_iptrunk.execute_playbook") +@patch("gso.services.lso_client._send_request") @patch("gso.services.netbox_client.NetboxClient.get_interface_by_name_and_device") def test_validate_iptrunk_success( mock_get_interface_by_name, @@ -219,7 +219,7 @@ def test_validate_iptrunk_success( @patch("gso.services.infoblox.find_network_by_cidr") @patch("gso.services.infoblox.find_v6_host_by_fqdn") @patch("gso.services.infoblox.find_host_by_fqdn") -@patch("gso.workflows.iptrunk.validate_iptrunk.execute_playbook") +@patch("gso.services.lso_client._send_request") @patch("gso.services.netbox_client.NetboxClient.get_interface_by_name_and_device") def test_validate_iptrunk_skip_legacy_trunks( mock_get_interface_by_name, diff --git a/test/workflows/router/test_create_router.py b/test/workflows/router/test_create_router.py index 83a2e53354eb202e44a665125d154245b4cb826e..a6bf456f3ef249d52817b2381c9cc3fcd40a5c86 100644 --- a/test/workflows/router/test_create_router.py +++ b/test/workflows/router/test_create_router.py @@ -36,7 +36,7 @@ def router_creation_input_form_data(site_subscription_factory, faker): @pytest.mark.workflow() -@patch("gso.utils.workflow_steps.lso_client.execute_playbook") +@patch("gso.services.lso_client._send_request") @patch("gso.workflows.router.create_router.NetboxClient.create_device") @patch("gso.workflows.router.create_router.infoblox.hostname_available") @patch("gso.workflows.router.create_router.infoblox.find_host_by_fqdn") @@ -117,7 +117,7 @@ def test_create_nokia_router_success( @pytest.mark.workflow() -@patch("gso.utils.workflow_steps.lso_client.execute_playbook") +@patch("gso.services.lso_client._send_request") @patch("gso.workflows.router.create_router.NetboxClient.create_device") @patch("gso.workflows.router.create_router.infoblox.hostname_available") @patch("gso.workflows.router.create_router.infoblox.find_network_by_cidr") diff --git a/test/workflows/router/test_promote_p_to_pe.py b/test/workflows/router/test_promote_p_to_pe.py index 3bd7fdca1a65e0ad7bd0e1df59883cd020e9aca0..5e14dcaffb510fea4cee70f8672e1372c3634e7f 100644 --- a/test/workflows/router/test_promote_p_to_pe.py +++ b/test/workflows/router/test_promote_p_to_pe.py @@ -17,7 +17,7 @@ from test.workflows import ( @pytest.mark.workflow() -@patch("gso.workflows.router.promote_p_to_pe.lso_client.execute_playbook") +@patch("gso.services.lso_client._send_request") @patch("gso.workflows.router.promote_p_to_pe.KentikClient") def test_promote_p_to_pe_success( mock_kentik_client, @@ -29,6 +29,9 @@ def test_promote_p_to_pe_success( """Test the successful promotion of a Nokia P router to a PE router.""" mock_kentik_client.return_value = MockedKentikClient router_id = nokia_router_subscription_factory(router_role=RouterRole.P, status=SubscriptionLifecycle.ACTIVE) + # Add two more routers to our fake network + nokia_router_subscription_factory(router_role=RouterRole.P) + nokia_router_subscription_factory(router_role=RouterRole.PE) input_data = [{"subscription_id": router_id}, {"tt_number": faker.tt_number()}] result, process_stat, step_log = run_workflow("promote_p_to_pe", input_data) for _ in range(3): @@ -55,7 +58,7 @@ def test_promote_p_to_pe_juniper_router(juniper_router_subscription_factory, dat @pytest.mark.workflow() -@patch("gso.workflows.router.promote_p_to_pe.lso_client.execute_playbook") +@patch("gso.services.lso_client._send_request") def test_promote_p_to_pe_nokia_pe_router( mock_execute_playbook, nokia_router_subscription_factory, data_config_filename, faker ): diff --git a/test/workflows/router/test_terminate_router.py b/test/workflows/router/test_terminate_router.py index f2cb638ab1cf5f918184e218c0f2d31d4f65cc87..a5f4fd3d0176113eb3f4dce0ae8c5a6274a51dc0 100644 --- a/test/workflows/router/test_terminate_router.py +++ b/test/workflows/router/test_terminate_router.py @@ -30,6 +30,9 @@ def test_terminate_pe_router_full_success( ): # Prepare mock values and expected results product_id = nokia_router_subscription_factory() + # Add two more routers to our fake network + nokia_router_subscription_factory(router_role=RouterRole.P) + nokia_router_subscription_factory(router_role=RouterRole.PE) router_termination_input_form_data = { "tt_number": faker.tt_number(), "remove_configuration": remove_configuration, @@ -81,6 +84,9 @@ def test_terminate_p_router_full_success( ): # Prepare mock values and expected results product_id = nokia_router_subscription_factory(router_role=RouterRole.P) + # Add two more routers to our fake network + nokia_router_subscription_factory(router_role=RouterRole.P) + nokia_router_subscription_factory(router_role=RouterRole.PE) router_termination_input_form_data = { "tt_number": faker.tt_number(), "remove_configuration": remove_configuration, diff --git a/test/workflows/router/test_update_ibgp_mesh.py b/test/workflows/router/test_update_ibgp_mesh.py index 2aa0a7b78cc952d97bafac6956b40961bbe8e62a..e1ebb16f140d747aad8c750218a8bc17d11186fe 100644 --- a/test/workflows/router/test_update_ibgp_mesh.py +++ b/test/workflows/router/test_update_ibgp_mesh.py @@ -18,21 +18,36 @@ from test.workflows import ( @pytest.mark.parametrize("trunk_status", [SubscriptionLifecycle.PROVISIONING, SubscriptionLifecycle.ACTIVE]) +@pytest.mark.parametrize("router_role", [RouterRole.P, RouterRole.PE]) @pytest.mark.workflow() -@patch("gso.workflows.router.update_ibgp_mesh.lso_client.execute_playbook") +@patch("gso.services.lso_client._send_request") @patch("gso.workflows.router.update_ibgp_mesh.librenms_client.LibreNMSClient.add_device") @patch("gso.workflows.router.update_ibgp_mesh.librenms_client.LibreNMSClient.device_exists") def test_update_ibgp_mesh_success( mock_librenms_device_exists, mock_librenms_add_device, mock_execute_playbook, + router_role, trunk_status, iptrunk_subscription_factory, + iptrunk_side_subscription_factory, + nokia_router_subscription_factory, data_config_filename, faker, ): mock_librenms_device_exists.return_value = False - ip_trunk = Iptrunk.from_subscription(iptrunk_subscription_factory(status=trunk_status)) + side_a = iptrunk_side_subscription_factory( + iptrunk_side_node=nokia_router_subscription_factory(router_role=router_role) + ) + side_b = iptrunk_side_subscription_factory() + # Add some extra devices to the network. + nokia_router_subscription_factory(router_role=RouterRole.P) + nokia_router_subscription_factory() + + ip_trunk = Iptrunk.from_subscription( + iptrunk_subscription_factory(status=trunk_status, iptrunk_sides=[side_a, side_b]) + ) + callback_step_count = 5 if router_role == RouterRole.P else 14 ibgp_mesh_input_form_data = { "subscription_id": ip_trunk.iptrunk.iptrunk_sides[0].iptrunk_side_node.owner_subscription_id } @@ -40,7 +55,7 @@ def test_update_ibgp_mesh_success( "update_ibgp_mesh", [ibgp_mesh_input_form_data, {"tt_number": faker.tt_number()}] ) - for _ in range(5): + for _ in range(callback_step_count): result, step_log = assert_lso_interaction_success(result, process_stat, step_log) # Handle two consecutive user input steps @@ -50,7 +65,7 @@ def test_update_ibgp_mesh_success( state = extract_state(result) - assert mock_execute_playbook.call_count == 5 + assert mock_execute_playbook.call_count == callback_step_count assert mock_librenms_add_device.call_count == 1 assert result.status == StepStatus.COMPLETE assert state["subscription"]["router"]["router_access_via_ts"] is False diff --git a/test/workflows/router/test_validate_router.py b/test/workflows/router/test_validate_router.py index 924577e23fa83471e1206a32ea554f5558733ab2..07fc78d74a8297f6d6b2a5f6d458aa1c6eeb5cab 100644 --- a/test/workflows/router/test_validate_router.py +++ b/test/workflows/router/test_validate_router.py @@ -15,7 +15,7 @@ from test.workflows import ( @pytest.mark.workflow() @patch("gso.services.infoblox.find_host_by_fqdn") -@patch("gso.services.lso_client.execute_playbook") +@patch("gso.services.lso_client._send_request") @patch("gso.services.netbox_client.NetboxClient.get_device_by_name") @patch("gso.services.librenms_client.LibreNMSClient.validate_device") @patch("gso.services.kentik_client.KentikClient") diff --git a/tox.ini b/tox.ini index c773a943410e00c6ac3936b0320aa68cdbef74be..a8eab7b2fc84925ef5a1d2f859e9e5c3bce1eec4 100644 --- a/tox.ini +++ b/tox.ini @@ -10,6 +10,8 @@ passenv = DATABASE_URI_TEST,SKIP_ALL_TESTS,ENVIRONMENT_IGNORE_MUTATION_DISABLED setenv = OAUTH2_ACTIVE = False TRANSLATIONS_DIR = ./gso/translations + TESTING=true + EXECUTOR=threadpool deps = coverage -r requirements.txt