diff --git a/gso/utils/workflow_steps.py b/gso/utils/workflow_steps.py index 8131c3601d2f7c812fe7805d7d79be5ed7655b61..9b82b69e1a26a6eb1bd4ff4b7042fec065957915 100644 --- a/gso/utils/workflow_steps.py +++ b/gso/utils/workflow_steps.py @@ -6,6 +6,7 @@ from typing import Any from orchestrator import inputstep, step from orchestrator.config.assignee import Assignee from orchestrator.types import State, UUIDstr +from orchestrator.utils.errors import ProcessFailureError from orchestrator.utils.json import json_dumps from orchestrator.workflow import StepList, conditional from pydantic import ConfigDict @@ -15,6 +16,7 @@ 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.kentik_client import KentikClient, NewKentikDevice from gso.services.lso_client import LSOState, indifferent_lso_interaction from gso.settings import load_oss_params from gso.utils.helpers import generate_inventory_for_active_routers @@ -423,3 +425,28 @@ def stop_moodi() -> StepList: } return _is_moodi_enabled(indifferent_lso_interaction(_stop_moodi)) + + +@step("Create Kentik device") +def create_kentik_device(state: State) -> State: + """Create a new device in Kentik.""" + kentik_client = KentikClient() + kentik_site = kentik_client.get_site_by_name(state["subscription"]["router"]["router_site"]["site_name"]) + + if not kentik_site: + msg = "Site could not be found in Kentik." + raise ProcessFailureError(msg, details=state["subscription"]["router"]["router_site"]["site_name"]) + + new_device = NewKentikDevice( + device_name=state["subscription"]["router"]["router_fqdn"], + device_description=str(state["subscription"]["subscription_id"]), + sending_ips=[str(state["subscription"]["router"]["router_lo_ipv4_address"])], + site_id=kentik_site["id"], + device_snmp_ip=str(state["subscription"]["router"]["router_lo_ipv4_address"]), + device_bgp_flowspec=False, + device_bgp_neighbor_ip=str(state["subscription"]["router"]["router_lo_ipv4_address"]), + device_bgp_neighbor_ip6=str(state["subscription"]["router"]["router_lo_ipv6_address"]), + ) + kentik_device = kentik_client.create_device(new_device) + + return {"kentik_device": kentik_device} diff --git a/gso/workflows/router/create_router.py b/gso/workflows/router/create_router.py index edc4ec0b7877313f28af8508c3da4833e9161c55..920973a7a77e03ce1143040785654789247ce793 100644 --- a/gso/workflows/router/create_router.py +++ b/gso/workflows/router/create_router.py @@ -28,6 +28,7 @@ from gso.utils.shared_enums import Vendor from gso.utils.types.ip_address import PortNumber from gso.utils.types.tt_number import TTNumber from gso.utils.workflow_steps import ( + create_kentik_device, deploy_base_config_dry, deploy_base_config_real, prompt_sharepoint_checklist_url, @@ -254,6 +255,7 @@ def create_router() -> StepList: * Create a new device in Netbox """ router_is_nokia = conditional(lambda state: state["vendor"] == Vendor.NOKIA) + router_is_pe = conditional(lambda state: state["router_role"] == RouterRole.PE) return ( begin @@ -261,6 +263,7 @@ def create_router() -> StepList: >> store_process_subscription(Target.CREATE) >> initialize_subscription >> ipam_allocate_loopback + >> router_is_pe(create_kentik_device) >> lso_interaction(deploy_base_config_dry) >> lso_interaction(deploy_base_config_real) >> verify_ipam_loopback diff --git a/gso/workflows/router/promote_p_to_pe.py b/gso/workflows/router/promote_p_to_pe.py index 8bf78ea033cc00a8fe936ab10d0223ae9ee1f9dc..68518265637a9f9072006de336c7c17c0ac41b56 100644 --- a/gso/workflows/router/promote_p_to_pe.py +++ b/gso/workflows/router/promote_p_to_pe.py @@ -8,7 +8,6 @@ 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.utils.errors import ProcessFailureError from orchestrator.utils.json import json_dumps from orchestrator.workflow import StepList, begin, done, inputstep, step, workflow from orchestrator.workflows.steps import resync, store_process_subscription, unsync @@ -17,7 +16,6 @@ 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.kentik_client import KentikClient, NewKentikDevice 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 @@ -34,6 +32,7 @@ from gso.utils.workflow_steps import ( add_pe_to_pe_mesh_real, check_l3_services, check_pe_ibgp, + create_kentik_device, update_sdp_mesh_dry, update_sdp_mesh_real, ) @@ -130,40 +129,6 @@ def prompt_insert_in_earl(subscription: dict[str, Any]) -> FormGenerator: return {} -@step("Create Kentik device") -def create_kentik_device(subscription: Router) -> State: - """Create a new device in Kentik.""" - if not ( - subscription.router.router_site - and subscription.router.router_site.site_name - and subscription.router.router_site.site_tier - and subscription.router.router_fqdn - ): - msg = "Router object is missing required properties." - raise ProcessFailureError(msg) - - kentik_client = KentikClient() - kentik_site = kentik_client.get_site_by_name(subscription.router.router_site.site_name) - - if not kentik_site: - msg = "Site could not be found in Kentik." - raise ProcessFailureError(msg, details=subscription.router.router_site.site_name) - - new_device = NewKentikDevice( - device_name=subscription.router.router_fqdn, - device_description=str(subscription.subscription_id), - sending_ips=[str(subscription.router.router_lo_ipv4_address)], - site_id=kentik_site["id"], - device_snmp_ip=str(subscription.router.router_lo_ipv4_address), - device_bgp_flowspec=False, - device_bgp_neighbor_ip=str(subscription.router.router_lo_ipv4_address), - device_bgp_neighbor_ip6=str(subscription.router.router_lo_ipv6_address), - ) - kentik_device = kentik_client.create_device(new_device) - - return {"kentik_device": kentik_device} - - @step("[DRY RUN] Remove P from all PEs") 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.""" diff --git a/test/workflows/router/test_create_router.py b/test/workflows/router/test_create_router.py index f9fb37fcfa38159a5d92c1aca8a9bd0aea2c5051..ae4219501cb28bcf84e77a99fb30d0bfaaf233e2 100644 --- a/test/workflows/router/test_create_router.py +++ b/test/workflows/router/test_create_router.py @@ -10,7 +10,7 @@ from gso.products.product_types.site import Site from gso.services.subscriptions import get_product_id_by_name from gso.utils.shared_enums import Vendor from test import USER_CONFIRM_EMPTY_FORM -from test.services.conftest import MockedSharePointClient +from test.services.conftest import MockedKentikClient, MockedSharePointClient from test.workflows import ( assert_complete, assert_lso_interaction_failure, @@ -31,19 +31,21 @@ def router_creation_input_form_data(site_subscription_factory, faker): "router_site": router_site, "hostname": faker.pystr(), "ts_port": faker.pyint(), - "router_role": faker.random_choices(elements=(RouterRole.P, RouterRole.PE, RouterRole.AMT), length=1)[0], "vendor": Vendor.NOKIA, } @pytest.mark.workflow() +@pytest.mark.parametrize("router_role", [RouterRole.P, RouterRole.PE]) @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") @patch("gso.workflows.router.create_router.infoblox.allocate_host") @patch("gso.workflows.router.create_router.SharePointClient") +@patch("gso.utils.workflow_steps.KentikClient") def test_create_nokia_router_success( + mock_kentik_client, mock_sharepoint_client, mock_allocate_host, mock_find_host_by_fqdn, @@ -51,10 +53,11 @@ def test_create_nokia_router_success( mock_netbox_create_device, mock_provision_router, router_creation_input_form_data, + router_role, faker, - data_config_filename, ): # Set up mock return values + mock_kentik_client.return_value = MockedKentikClient product_id = get_product_id_by_name(ProductName.ROUTER) mock_site = Site.from_subscription(router_creation_input_form_data["router_site"]).site mock_v4 = faker.ipv4() @@ -68,7 +71,7 @@ def test_create_nokia_router_success( mock_sharepoint_client.return_value = MockedSharePointClient # Run workflow - initial_router_data = [{"product": product_id}, router_creation_input_form_data, {}] + initial_router_data = [{"product": product_id}, router_creation_input_form_data | {"router_role": router_role}, {}] result, process_stat, step_log = run_workflow("create_router", initial_router_data) state = extract_state(result) @@ -118,6 +121,7 @@ def test_create_nokia_router_success( @pytest.mark.workflow() +@pytest.mark.parametrize("router_role", [RouterRole.P, RouterRole.PE]) @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") @@ -126,7 +130,9 @@ def test_create_nokia_router_success( @patch("gso.workflows.router.create_router.infoblox.allocate_v6_network") @patch("gso.workflows.router.create_router.infoblox.allocate_v4_network") @patch("gso.workflows.router.create_router.infoblox.allocate_host") +@patch("gso.utils.workflow_steps.KentikClient") def test_create_nokia_router_lso_failure( + mock_kentik_client, mock_allocate_host, mock_allocate_v4_network, mock_allocate_v6_network, @@ -136,9 +142,11 @@ def test_create_nokia_router_lso_failure( mock_netbox_create_device, mock_provision_router, router_creation_input_form_data, + router_role, faker, ): # Set up mock return values + mock_kentik_client.return_value = MockedKentikClient mock_site = Site.from_subscription(router_creation_input_form_data["router_site"]).site mock_v4 = faker.ipv4() mock_v4_net = faker.ipv4(network=True) @@ -176,7 +184,7 @@ def test_create_nokia_router_lso_failure( # Run workflow product_id = get_product_id_by_name(ProductName.ROUTER) - initial_router_data = [{"product": product_id}, router_creation_input_form_data, {}] + initial_router_data = [{"product": product_id}, router_creation_input_form_data | {"router_role": router_role}, {}] result, process_stat, step_log = run_workflow("create_router", initial_router_data) result, step_log = assert_lso_interaction_success(result, process_stat, step_log) diff --git a/test/workflows/router/test_promote_p_to_pe.py b/test/workflows/router/test_promote_p_to_pe.py index a0245726b480ce65123f307d8d6a73b4767bbdbd..9446d17c6f3e33dbb90a7cfcfa6e9adefac734e8 100644 --- a/test/workflows/router/test_promote_p_to_pe.py +++ b/test/workflows/router/test_promote_p_to_pe.py @@ -19,7 +19,7 @@ from test.workflows import ( @pytest.mark.workflow() @patch("gso.services.lso_client._send_request") -@patch("gso.workflows.router.promote_p_to_pe.KentikClient") +@patch("gso.utils.workflow_steps.KentikClient") def test_promote_p_to_pe_success( mock_kentik_client, mock_execute_playbook,