diff --git a/Changelog.md b/Changelog.md index d490a8bfe7769d4024704346facc5b5b18eaac93..2711dd1ca9518252d537b187665950696a9bc924 100644 --- a/Changelog.md +++ b/Changelog.md @@ -1,5 +1,11 @@ # Changelog +# [3.1] - 2025-04-29 +- Allow running the prefix validation workflow on out-of-sync subscriptions +- Only check Kentik licenses during router validation in production +- Add optional TTL security field to BGP session product block +- Refactor the code and improve the code quality + # [3.0] - 2025-04-23 - Breaking change: reworked all the Layer 3 Core services, splitting them up into separate product types. - Allow for creating an Edge Port on a Juniper router. diff --git a/docs/includes/glossary.md b/docs/includes/glossary.md index a29d245259baa8a8b27cd16feade66bd5fa6c732..af5cb334a0a5bb5417b18de02a196add0967bd44 100644 --- a/docs/includes/glossary.md +++ b/docs/includes/glossary.md @@ -36,6 +36,7 @@ *[NREN]: National Research and Education Network *[OOB]: Out-of-band *[OSS]: Operational Support Systems +*[OTRS]: Trouble Ticket system software package *[PoP]: Point of Presence *[REST]: Representational State Transfer *[RFC]: Request For Comments @@ -44,6 +45,7 @@ *[SNMP]: Simple Network Management Protocol *[SOT]: Source Of Truth *[TBA]: To be added +*[TTL]: Time To Live *[UAT]: User Acceptance Testing *[VM]: Virtual Machine *[VRF]: Virtual Routing and Forwarding diff --git a/docs/vale/styles/config/vocabularies/geant-jargon/accept.txt b/docs/vale/styles/config/vocabularies/geant-jargon/accept.txt index aeb0c40d8ae8adc250f8ab38c568ad95d4855393..a7e4a6f7c0f83ed5c864b816583fe3e667cc4449 100644 --- a/docs/vale/styles/config/vocabularies/geant-jargon/accept.txt +++ b/docs/vale/styles/config/vocabularies/geant-jargon/accept.txt @@ -55,6 +55,7 @@ OIDC OOB OPA (OSS|oss) +OTRS PHASE 1 Po[Pp] Pydantic @@ -69,6 +70,7 @@ SOT SURF TBA TERMINATED? +TTL TWAMP UAT UTC diff --git a/gso/cli/imports.py b/gso/cli/imports.py index 6a82ddecc513fc71496de598d83d2499272bab5a..fdcba0f6bdb0df597fc0a155b94e595a09eaa08e 100644 --- a/gso/cli/imports.py +++ b/gso/cli/imports.py @@ -259,6 +259,7 @@ class L3CoreServiceImportModel(BaseModel): is_multi_hop: bool rtbh_enabled: bool # whether Remote Triggered Blackhole is enabled prefix_limit: NonNegativeInt | None = None + ttl_security: NonNegativeInt | None = None class BFDSettingsModel(BaseModel): """BFD Settings model.""" diff --git a/gso/migrations/versions/2025-04-28_a3177c5f9641_add_optional_ttl_security_to_bgp_session.py b/gso/migrations/versions/2025-04-28_a3177c5f9641_add_optional_ttl_security_to_bgp_session.py new file mode 100644 index 0000000000000000000000000000000000000000..cbcb3558ba6586aac3d518c5cbce6f1ed74a503d --- /dev/null +++ b/gso/migrations/versions/2025-04-28_a3177c5f9641_add_optional_ttl_security_to_bgp_session.py @@ -0,0 +1,41 @@ +"""Add optional TTL security to BGP session. + +Revision ID: a3177c5f9641 +Revises: fffe36624681 +Create Date: 2025-04-28 10:21:54.820219 + +""" +import sqlalchemy as sa +from alembic import op + +# revision identifiers, used by Alembic. +revision = 'a3177c5f9641' +down_revision = 'fffe36624681' +branch_labels = None +depends_on = None + + +def upgrade() -> None: + conn = op.get_bind() + conn.execute(sa.text(""" +INSERT INTO resource_types (resource_type, description) VALUES ('ttl_security', 'BGP TTL security') RETURNING resource_types.resource_type_id + """)) + conn.execute(sa.text(""" +INSERT INTO product_block_resource_types (product_block_id, resource_type_id) VALUES ((SELECT product_blocks.product_block_id FROM product_blocks WHERE product_blocks.name IN ('BGPSession')), (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('ttl_security'))) + """)) + + +def downgrade() -> None: + conn = op.get_bind() + conn.execute(sa.text(""" +DELETE FROM product_block_resource_types WHERE product_block_resource_types.product_block_id IN (SELECT product_blocks.product_block_id FROM product_blocks WHERE product_blocks.name IN ('BGPSession')) AND product_block_resource_types.resource_type_id = (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('ttl_security')) + """)) + conn.execute(sa.text(""" +DELETE FROM subscription_instance_values USING product_block_resource_types WHERE subscription_instance_values.subscription_instance_id IN (SELECT subscription_instances.subscription_instance_id FROM subscription_instances WHERE subscription_instances.subscription_instance_id IN (SELECT product_blocks.product_block_id FROM product_blocks WHERE product_blocks.name IN ('BGPSession'))) AND product_block_resource_types.resource_type_id = (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('ttl_security')) + """)) + conn.execute(sa.text(""" +DELETE FROM subscription_instance_values WHERE subscription_instance_values.resource_type_id IN (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('ttl_security')) + """)) + conn.execute(sa.text(""" +DELETE FROM resource_types WHERE resource_types.resource_type IN ('ttl_security') + """)) diff --git a/gso/products/product_blocks/bgp_session.py b/gso/products/product_blocks/bgp_session.py index 863a2da0ac904a5522b58fb15e048c9ee013173b..638c06607ab5ad4690257b8d7893528469500e55 100644 --- a/gso/products/product_blocks/bgp_session.py +++ b/gso/products/product_blocks/bgp_session.py @@ -48,6 +48,7 @@ class BGPSessionInactive(ProductBlockModel, lifecycle=[SubscriptionLifecycle.INI bfd_enabled: bool = False ip_type: IPTypes | None = None prefix_limit: NonNegativeInt | None = None + ttl_security: NonNegativeInt | None = None class BGPSessionProvisioning(BGPSessionInactive, lifecycle=[SubscriptionLifecycle.PROVISIONING]): @@ -65,6 +66,7 @@ class BGPSessionProvisioning(BGPSessionInactive, lifecycle=[SubscriptionLifecycl bfd_enabled: bool ip_type: IPTypes prefix_limit: NonNegativeInt | None + ttl_security: NonNegativeInt | None class BGPSession(BGPSessionProvisioning, lifecycle=[SubscriptionLifecycle.ACTIVE]): @@ -83,6 +85,7 @@ class BGPSession(BGPSessionProvisioning, lifecycle=[SubscriptionLifecycle.ACTIVE bfd_enabled: Settings for BFD. ip_type: The IP type of the session. prefix_limit: A prefix limit, if required. + ttl_security: A limit on time-to-live used for TTL security. """ peer_address: IPAddress @@ -97,3 +100,4 @@ class BGPSession(BGPSessionProvisioning, lifecycle=[SubscriptionLifecycle.ACTIVE bfd_enabled: bool ip_type: IPTypes prefix_limit: NonNegativeInt | None + ttl_security: NonNegativeInt | None diff --git a/gso/services/lso_client.py b/gso/services/lso_client.py index 2318cffd7396e51c620fa55087faea21e7043187..67e2791752d1d5d9c4be584b21915e9f5ec9832a 100644 --- a/gso/services/lso_client.py +++ b/gso/services/lso_client.py @@ -14,7 +14,7 @@ from orchestrator.forms import SubmitFormPage from orchestrator.utils.errors import ProcessFailureError from orchestrator.workflow import Step, StepList, begin, callback_step, conditional, inputstep from pydantic import ConfigDict -from pydantic_forms.types import FormGenerator, State +from pydantic_forms.types import FormGenerator, State, UUIDstr from pydantic_forms.validators import Label, LongText, ReadOnlyField from unidecode import unidecode @@ -64,7 +64,7 @@ def _send_request(parameters: dict, callback_route: str) -> None: @step("Execute Ansible playbook") def _execute_playbook( - playbook_name: str, callback_route: str, inventory: dict[str, Any], extra_vars: dict[str, Any] + playbook_name: str, callback_route: str, inventory: dict[str, Any], extra_vars: dict[str, Any], process_id: UUIDstr ) -> None: """Execute a playbook remotely through the provisioning proxy. @@ -112,7 +112,10 @@ def _execute_playbook( extra_vars: Any extra variables that the playbook relies on. This can include a subscription object, a boolean value indicating a dry run, a commit comment, etc. All unicode character values are decoded to prevent sending special characters to remote machines that don't support this. + process_id: The process ID of the workflow that is running the playbook. This is used in Ansible playbooks to + fetch custom configuration. """ + extra_vars["gso_process_id"] = process_id parameters = { "playbook_name": playbook_name, "inventory": inventory, diff --git a/gso/translations/en-GB.json b/gso/translations/en-GB.json index 98d936fa0b0bbc830395531474ccf0e9b866d619..b1d6a36a4ee673823c9a346447340c9211c90bb8 100644 --- a/gso/translations/en-GB.json +++ b/gso/translations/en-GB.json @@ -58,6 +58,7 @@ "v4_bgp_bfd_enabled": "IPv4 BGP - BFD enabled", "v4_bgp_multipath_enabled": "IPv4 - BGP multipath enabled", "v4_bgp_prefix_limit": "IPv4 - BGP prefix limit", + "v4_bgp_ttl_security": "IPv4 - BGP TTL security", "v4_bgp_is_passive": "IPv4 - BGP is passive", "v4_bgp_send_default_route": "IPv4 - BGP send default route", "v4_bgp_add_v4_multicast": "IPv4 - BGP add multicast", @@ -71,6 +72,7 @@ "v6_bgp_bfd_enabled": "IPv6 - BGP BFD enabled", "v6_bgp_multipath_enabled": "IPv6 - BGP multipath enabled", "v6_bgp_prefix_limit": "IPv6 - BGP prefix limit", + "v6_bgp_ttl_security": "IPv6 - BGP TTL security", "v6_bgp_is_passive": "IPv6 - BGP is passive", "v6_bgp_send_default_route": "IPv6 - BGP send default route", "v6_bgp_add_v6_multicast": "IPv6 - BGP add multicast" diff --git a/gso/workflows/__init__.py b/gso/workflows/__init__.py index f69279021b2fae317e08094b3aae013ff14bcda7..8b1f4065d11869daa09e3ab124caf4b7484a89e0 100644 --- a/gso/workflows/__init__.py +++ b/gso/workflows/__init__.py @@ -1,6 +1,6 @@ """Initialisation class that imports all workflows into GSO.""" -from orchestrator.services.subscriptions import WF_USABLE_MAP +from orchestrator.services.subscriptions import WF_USABLE_MAP, WF_USABLE_WHILE_OUT_OF_SYNC from orchestrator.types import SubscriptionLifecycle from orchestrator.workflows import LazyWorkflowInstance @@ -28,6 +28,8 @@ WF_USABLE_MAP.update({ "validate_iptrunk": [SubscriptionLifecycle.PROVISIONING, SubscriptionLifecycle.ACTIVE], }) +WF_USABLE_WHILE_OUT_OF_SYNC.extend(["validate_geant_ip_prefix_list"]) + # IP trunk workflows LazyWorkflowInstance("gso.workflows.iptrunk.activate_iptrunk", "activate_iptrunk") LazyWorkflowInstance("gso.workflows.iptrunk.create_iptrunk", "create_iptrunk") diff --git a/gso/workflows/l3_core_service/base_create_imported_l3_core_service.py b/gso/workflows/l3_core_service/base_create_imported_l3_core_service.py index 3f8bf13f324771ea4dd2c4f33dffbafb65d2c26e..7261633b4439a9ab2602237865d9e80a1f8a26d2 100644 --- a/gso/workflows/l3_core_service/base_create_imported_l3_core_service.py +++ b/gso/workflows/l3_core_service/base_create_imported_l3_core_service.py @@ -40,6 +40,7 @@ def initial_input_form_generator() -> FormGenerator: is_multi_hop: bool rtbh_enabled: bool prefix_limit: NonNegativeInt | None = None + ttl_security: NonNegativeInt | None = None class ServiceBindingPort(BaseModel): edge_port: UUIDstr diff --git a/gso/workflows/l3_core_service/base_create_l3_core_service.py b/gso/workflows/l3_core_service/base_create_l3_core_service.py index 6d8c451ea82e3af081020977c05943e06797c4d2..90221ec0ed482de386762c097c88eb7a9e8298e0 100644 --- a/gso/workflows/l3_core_service/base_create_l3_core_service.py +++ b/gso/workflows/l3_core_service/base_create_l3_core_service.py @@ -70,6 +70,7 @@ def initial_input_form_generator(product_name: str) -> FormGenerator: bfd_enabled: bool = False multipath_enabled: bool = False prefix_limit: NonNegativeInt | None = None + ttl_security: NonNegativeInt | None = None is_passive: bool = False add_v4_multicast: bool = Field(default=False, exclude=True) send_default_route: bool = False @@ -91,6 +92,7 @@ def initial_input_form_generator(product_name: str) -> FormGenerator: bfd_enabled: bool = False multipath_enabled: bool = False prefix_limit: NonNegativeInt | None = None + ttl_security: NonNegativeInt | None = None is_passive: bool = False add_v6_multicast: bool = Field(default=False, exclude=True) send_default_route: bool = False diff --git a/gso/workflows/l3_core_service/base_modify_l3_core_service.py b/gso/workflows/l3_core_service/base_modify_l3_core_service.py index 4185ef26965e3fe003c51dcf8cdb0c0f61db64b4..e16364ad1095734f4904c7288d54f46f9dbd808c 100644 --- a/gso/workflows/l3_core_service/base_modify_l3_core_service.py +++ b/gso/workflows/l3_core_service/base_modify_l3_core_service.py @@ -74,6 +74,7 @@ def initial_input_form_generator(subscription_id: UUIDstr) -> FormGenerator: bfd_enabled: bool = False multipath_enabled: bool = False prefix_limit: NonNegativeInt | None = None + ttl_security: NonNegativeInt | None = None is_passive: bool = False add_v4_multicast: bool = Field(default=False, exclude=True) send_default_route: bool = False @@ -95,6 +96,7 @@ def initial_input_form_generator(subscription_id: UUIDstr) -> FormGenerator: bfd_enabled: bool = False multipath_enabled: bool = False prefix_limit: NonNegativeInt | None = None + ttl_security: NonNegativeInt | None = None is_passive: bool = False add_v6_multicast: bool = Field(default=False, exclude=True) send_default_route: bool = False @@ -282,6 +284,7 @@ def initial_input_form_generator(subscription_id: UUIDstr) -> FormGenerator: v4_bgp_bfd_enabled: bool = Field(v4_peer.bfd_enabled, exclude=True) v4_bgp_multipath_enabled: bool = Field(v4_peer.multipath_enabled, exclude=True) v4_bgp_prefix_limit: NonNegativeInt | None = Field(v4_peer.prefix_limit, exclude=True) + v4_bgp_ttl_security: NonNegativeInt | None = Field(v4_peer.ttl_security, exclude=True) v4_bgp_is_passive: bool = Field(v4_peer.is_passive, exclude=True) v4_bgp_send_default_route: bool = Field(v4_peer.send_default_route, exclude=True) v4_bgp_add_v4_multicast: bool = Field(bool(IPFamily.V4MULTICAST in v4_peer.families), exclude=True) @@ -299,6 +302,7 @@ def initial_input_form_generator(subscription_id: UUIDstr) -> FormGenerator: v6_bgp_bfd_enabled: bool = Field(v6_peer.bfd_enabled, exclude=True) v6_bgp_multipath_enabled: bool = Field(v6_peer.multipath_enabled, exclude=True) v6_bgp_prefix_limit: NonNegativeInt | None = Field(v6_peer.prefix_limit, exclude=True) + v6_bgp_ttl_security: NonNegativeInt | None = Field(v6_peer.ttl_security, exclude=True) v6_bgp_is_passive: bool = Field(v6_peer.is_passive, exclude=True) v6_bgp_send_default_route: bool = Field(v6_peer.send_default_route, exclude=True) v6_bgp_add_v6_multicast: bool = Field(bool(IPFamily.V6MULTICAST in v6_peer.families), exclude=True) @@ -323,6 +327,7 @@ def initial_input_form_generator(subscription_id: UUIDstr) -> FormGenerator: bfd_enabled=self.v4_bgp_bfd_enabled, multipath_enabled=self.v4_bgp_multipath_enabled, prefix_limit=self.v4_bgp_prefix_limit, + ttl_security=self.v4_bgp_ttl_security, is_passive=self.v4_bgp_is_passive, send_default_route=self.v4_bgp_send_default_route, add_v4_multicast=self.v4_bgp_add_v4_multicast, @@ -348,6 +353,7 @@ def initial_input_form_generator(subscription_id: UUIDstr) -> FormGenerator: bfd_enabled=self.v6_bgp_bfd_enabled, multipath_enabled=self.v6_bgp_multipath_enabled, prefix_limit=self.v6_bgp_prefix_limit, + ttl_security=self.v6_bgp_ttl_security, is_passive=self.v6_bgp_is_passive, send_default_route=self.v6_bgp_send_default_route, add_v6_multicast=self.v6_bgp_add_v6_multicast, diff --git a/gso/workflows/l3_core_service/base_validate_prefix_list.py b/gso/workflows/l3_core_service/base_validate_prefix_list.py deleted file mode 100644 index 1771426c9fa5a701051f64b363c346ba3a282c84..0000000000000000000000000000000000000000 --- a/gso/workflows/l3_core_service/base_validate_prefix_list.py +++ /dev/null @@ -1,105 +0,0 @@ -"""Prefix Validation workflow for L3 Core Service subscription objects.""" - -from typing import Any - -from orchestrator.config.assignee import Assignee -from orchestrator.domain import SubscriptionModel -from orchestrator.forms import SubmitFormPage -from orchestrator.workflow import inputstep, step -from pydantic import Field -from pydantic_forms.types import FormGenerator, State, UUIDstr -from pydantic_forms.validators import Label - -from gso.services.lso_client import LSOState -from gso.services.partners import get_partner_by_id -from gso.utils.shared_enums import Vendor - - -@step("Prepare list of all Access Ports") -def build_fqdn_list(subscription_id: UUIDstr) -> State: - """Build the list of all FQDNs in the access ports of L3 Core Service subscription, excluding Juniper devices.""" - subscription = SubscriptionModel.from_subscription(subscription_id) - ap_list = subscription.l3_core.ap_list # type: ignore[attr-defined] - ap_fqdn_list = [ - ap.sbp.edge_port.node.router_fqdn for ap in ap_list if ap.sbp.edge_port.node.vendor != Vendor.JUNIPER - ] - return {"ap_fqdn_list": ap_fqdn_list, "subscription": subscription} - - -@step("[DRY RUN] Validate Prefix-Lists") -def validate_prefix_lists_dry(subscription: dict[str, Any], process_id: UUIDstr, ap_fqdn_list: list[str]) -> LSOState: - """Workflow step for running a playbook that validates prefix-lists in dry run mode.""" - extra_vars = { - "subscription": subscription, - "partner_name": get_partner_by_id(subscription["customer_id"]).name, - "dry_run": True, - "verb": "deploy", - "object": "prefix_list", - "is_verification_workflow": "true", - "commit_comment": f"GSO_PROCESS_ID: {process_id} - Validate prefix-lists for {subscription["description"]}", - } - - return { - "playbook_name": "gap_ansible/playbooks/validate_prefix_list.yaml", - "inventory": {"all": {"hosts": dict.fromkeys(ap_fqdn_list)}}, - "extra_vars": extra_vars, - } - - -@step("Evaluate validation of Prefix-Lists") -def evaluate_result_has_diff(callback_result: dict) -> State: - """Evaluate the result of the playbook that validates prefix-lists.""" - return {"callback_result": callback_result, "prefix_list_drift": bool(callback_result["return_code"] != 0)} - - -@inputstep("Await operator confirmation", assignee=Assignee.SYSTEM) -def await_operator() -> FormGenerator: - """Show a form for the operator to start redeploying the prefix list that has drifted.""" - - class AwaitOperatorForm(SubmitFormPage): - info_label_a: Label = Field("A drift has been detected for this prefix list!", exclude=True) - info_label_b: Label = Field("Please continue this workflow to redeploy the drifted prefix list.", exclude=True) - - yield AwaitOperatorForm - - return {} - - -@step("[DRY RUN] Deploy Prefix-Lists") -def deploy_prefix_lists_dry(subscription: dict[str, Any], process_id: UUIDstr, ap_fqdn_list: list[str]) -> LSOState: - """Workflow step for running a playbook that deploys prefix-lists in dry run mode.""" - extra_vars = { - "subscription": subscription, - "partner_name": get_partner_by_id(subscription["customer_id"]).name, - "dry_run": True, - "verb": "deploy", - "object": "prefix_list", - "is_verification_workflow": "false", - "commit_comment": f"GSO_PROCESS_ID: {process_id} - Deploy prefix-lists for {subscription["description"]}", - } - - return { - "playbook_name": "gap_ansible/playbooks/deploy_prefix_list.yaml", - "inventory": {"all": {"hosts": dict.fromkeys(ap_fqdn_list)}}, - "extra_vars": extra_vars, - } - - -@step("[REAL] Deploy Prefix-Lists") -def deploy_prefix_lists_real(subscription: dict[str, Any], process_id: UUIDstr, ap_fqdn_list: list[str]) -> LSOState: - """Workflow step for running a playbook that deploys prefix-lists.""" - extra_vars = { - "subscription": subscription, - "partner_name": get_partner_by_id(subscription["customer_id"]).name, - "dry_run": False, - "verb": "deploy", - "object": "prefix_list", - "is_verification_workflow": "false", - "commit_comment": f"GSO_PROCESS_ID: {process_id} - Deploy prefix-lists for {subscription["description"]}", - } - - return { - "playbook_name": "gap_ansible/playbooks/deploy_prefix_list.yaml", - "inventory": {"all": {"hosts": dict.fromkeys(ap_fqdn_list)}}, - "extra_vars": extra_vars, - } diff --git a/gso/workflows/l3_core_service/geant_ip/validate_prefix_list.py b/gso/workflows/l3_core_service/geant_ip/validate_prefix_list.py index 5f259b576889bbfcb801d7b77dce71413426a9d1..ec335abd7aa3f70ac054a5d6d518893c97a4a548 100644 --- a/gso/workflows/l3_core_service/geant_ip/validate_prefix_list.py +++ b/gso/workflows/l3_core_service/geant_ip/validate_prefix_list.py @@ -1,19 +1,111 @@ """Prefix Validation workflow for GÉANT IP subscription objects.""" +from typing import Any + +from orchestrator.config.assignee import Assignee +from orchestrator.domain import SubscriptionModel +from orchestrator.forms import SubmitFormPage from orchestrator.targets import Target -from orchestrator.workflow import StepList, begin, conditional, done, 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 Field +from pydantic_forms.types import FormGenerator, State, UUIDstr +from pydantic_forms.validators import Label -from gso.services.lso_client import anonymous_lso_interaction, lso_interaction -from gso.workflows.l3_core_service.base_validate_prefix_list import ( - await_operator, - build_fqdn_list, - deploy_prefix_lists_dry, - deploy_prefix_lists_real, - evaluate_result_has_diff, - validate_prefix_lists_dry, -) +from gso.services.lso_client import LSOState, anonymous_lso_interaction, lso_interaction +from gso.services.partners import get_partner_by_id +from gso.utils.shared_enums import Vendor + + +@step("Prepare list of all Access Ports") +def build_fqdn_list(subscription_id: UUIDstr) -> State: + """Build the list of all FQDNs in the access ports of L3 Core Service subscription, excluding Juniper devices.""" + subscription = SubscriptionModel.from_subscription(subscription_id) + ap_list = subscription.l3_core.ap_list # type: ignore[attr-defined] + ap_fqdn_list = [ + ap.sbp.edge_port.node.router_fqdn for ap in ap_list if ap.sbp.edge_port.node.vendor != Vendor.JUNIPER + ] + return {"ap_fqdn_list": ap_fqdn_list, "subscription": subscription} + + +@step("[DRY RUN] Validate Prefix-Lists") +def validate_prefix_lists_dry(subscription: dict[str, Any], process_id: UUIDstr, ap_fqdn_list: list[str]) -> LSOState: + """Workflow step for running a playbook that validates prefix-lists in dry run mode.""" + extra_vars = { + "subscription": subscription, + "partner_name": get_partner_by_id(subscription["customer_id"]).name, + "dry_run": True, + "verb": "deploy", + "object": "prefix_list", + "is_verification_workflow": "true", + "commit_comment": f"GSO_PROCESS_ID: {process_id} - Validate prefix-lists for {subscription["description"]}", + } + + return { + "playbook_name": "gap_ansible/playbooks/validate_prefix_list.yaml", + "inventory": {"all": {"hosts": dict.fromkeys(ap_fqdn_list)}}, + "extra_vars": extra_vars, + } + + +@step("Evaluate validation of Prefix-Lists") +def evaluate_result_has_diff(callback_result: dict) -> State: + """Evaluate the result of the playbook that validates prefix-lists.""" + return {"callback_result": callback_result, "prefix_list_drift": bool(callback_result["return_code"] != 0)} + + +@inputstep("Await operator confirmation", assignee=Assignee.SYSTEM) +def await_operator() -> FormGenerator: + """Show a form for the operator to start redeploying the prefix list that has drifted.""" + + class AwaitOperatorForm(SubmitFormPage): + info_label_a: Label = Field("A drift has been detected for this prefix list!", exclude=True) + info_label_b: Label = Field("Please continue this workflow to redeploy the drifted prefix list.", exclude=True) + + yield AwaitOperatorForm + + return {} + + +@step("[DRY RUN] Deploy Prefix-Lists") +def deploy_prefix_lists_dry(subscription: dict[str, Any], process_id: UUIDstr, ap_fqdn_list: list[str]) -> LSOState: + """Workflow step for running a playbook that deploys prefix-lists in dry run mode.""" + extra_vars = { + "subscription": subscription, + "partner_name": get_partner_by_id(subscription["customer_id"]).name, + "dry_run": True, + "verb": "deploy", + "object": "prefix_list", + "is_verification_workflow": "false", + "commit_comment": f"GSO_PROCESS_ID: {process_id} - Deploy prefix-lists for {subscription["description"]}", + } + + return { + "playbook_name": "gap_ansible/playbooks/deploy_prefix_list.yaml", + "inventory": {"all": {"hosts": dict.fromkeys(ap_fqdn_list)}}, + "extra_vars": extra_vars, + } + + +@step("[REAL] Deploy Prefix-Lists") +def deploy_prefix_lists_real(subscription: dict[str, Any], process_id: UUIDstr, ap_fqdn_list: list[str]) -> LSOState: + """Workflow step for running a playbook that deploys prefix-lists.""" + extra_vars = { + "subscription": subscription, + "partner_name": get_partner_by_id(subscription["customer_id"]).name, + "dry_run": False, + "verb": "deploy", + "object": "prefix_list", + "is_verification_workflow": "false", + "commit_comment": f"GSO_PROCESS_ID: {process_id} - Deploy prefix-lists for {subscription["description"]}", + } + + return { + "playbook_name": "gap_ansible/playbooks/deploy_prefix_list.yaml", + "inventory": {"all": {"hosts": dict.fromkeys(ap_fqdn_list)}}, + "extra_vars": extra_vars, + } @workflow( diff --git a/gso/workflows/router/validate_router.py b/gso/workflows/router/validate_router.py index 675def732c33824ac386713707303b5968afef9d..38f6c4b6e9269ff482abc542271ba256deb4fd62 100644 --- a/gso/workflows/router/validate_router.py +++ b/gso/workflows/router/validate_router.py @@ -17,7 +17,7 @@ from gso.services.librenms_client import LibreNMSClient from gso.services.lso_client import LSOState, anonymous_lso_interaction from gso.services.netbox_client import NetboxClient from gso.services.subscriptions import get_active_layer_3_services_on_router, get_active_vrfs_linked_to_router -from gso.settings import load_oss_params +from gso.settings import EnvironmentEnum, load_oss_params from gso.utils.helpers import generate_inventory_for_routers from gso.utils.shared_enums import Vendor @@ -148,7 +148,7 @@ def check_kentik_entry_exists(subscription: Router) -> None: license on it. This is because there can be multiple, valid, non-archiving licenses for devices. Raises: - ProcessFailureError when a Kentik device is missing, or configured incorrectly. + ProcessFailureError: when a Kentik device is missing, or configured incorrectly. """ client = KentikClient() @@ -159,9 +159,15 @@ def check_kentik_entry_exists(subscription: Router) -> None: message="Device not found in Kentik", details={"device": subscription.router.router_fqdn} ) + oss_params = load_oss_params() + # If there are active layer 3 services, check the license type. It may not be the placeholder or archiving license. - if bool(get_active_layer_3_services_on_router(subscription.subscription_id)): - kentik_params = load_oss_params().KENTIK + # This check is only performed in a production environment + if ( + bool(get_active_layer_3_services_on_router(subscription.subscription_id)) + and oss_params.GENERAL.environment == EnvironmentEnum.PRODUCTION + ): + kentik_params = oss_params.KENTIK archive_plan = client.get_plan_by_name(kentik_params.archive_license_key) if any(device["device_name"] == subscription.router.router_fqdn for device in archive_plan["devices"]): raise ProcessFailureError( diff --git a/setup.py b/setup.py index 44b4f9e5879d24193991457627c9cbb5ae48e788..3bd22e6b5e166aeaee0dffad210c82f73a15cf92 100644 --- a/setup.py +++ b/setup.py @@ -4,7 +4,7 @@ from setuptools import find_packages, setup setup( name="geant-service-orchestrator", - version="3.0", + version="3.1", author="GÉANT Orchestration and Automation Team", author_email="goat@geant.org", description="GÉANT Service Orchestrator", diff --git a/test/conftest.py b/test/conftest.py index 2d2c1a784d6d89b546a13d4c691968f05fcb1484..076a485e2b5b868f7be7498762f6531885bec920 100644 --- a/test/conftest.py +++ b/test/conftest.py @@ -12,7 +12,7 @@ from alembic.config import Config from faker import Faker from faker.providers import BaseProvider from oauth2_lib.settings import oauth2lib_settings -from orchestrator import app_settings +from orchestrator import app_settings, step from orchestrator.db import ( Database, ProductBlockTable, @@ -26,7 +26,7 @@ from orchestrator.db.database import ENGINE_ARGUMENTS, SESSION_ARGUMENTS, BaseMo from orchestrator.domain import SUBSCRIPTION_MODEL_REGISTRY, SubscriptionModel from orchestrator.domain.base import ProductBlockModel from orchestrator.types import SubscriptionLifecycle -from pydantic_forms.types import strEnum +from pydantic_forms.types import UUIDstr, strEnum from sqlalchemy import create_engine, select, text from sqlalchemy.engine import make_url from sqlalchemy.orm import scoped_session, sessionmaker @@ -606,3 +606,20 @@ def _no_mail(monkeypatch): logger.info(email) monkeypatch.setattr(gso.services.mailer, "send_mail", send_mail) + + +@pytest.fixture(autouse=True) +def _no_lso_interactions(monkeypatch): + """Remove all external LSO calls.""" + + @step("Mocked playbook execution") + def _execute_playbook( + playbook_name: str, callback_route: str, inventory: dict, extra_vars: dict, process_id: UUIDstr + ) -> None: + assert playbook_name + assert callback_route + assert inventory + assert extra_vars + assert process_id + + monkeypatch.setattr(gso.services.lso_client, "_execute_playbook", _execute_playbook) diff --git a/test/fixtures/l3_core_service_fixtures.py b/test/fixtures/l3_core_service_fixtures.py index 04e9c175f48585a42f4c48e4687f00757ad7b0e0..d23bc42978c325220fa304782f6192841b25df7c 100644 --- a/test/fixtures/l3_core_service_fixtures.py +++ b/test/fixtures/l3_core_service_fixtures.py @@ -62,6 +62,7 @@ def bgp_session_subscription_factory(faker): has_custom_policies: bool = False, multipath_enabled: bool | None = True, prefix_limit: NonNegativeInt | None = None, + ttl_security: NonNegativeInt | None = None, send_default_route: bool | None = True, is_passive: bool | None = False, rtbh_enabled: bool | None = False, @@ -77,6 +78,7 @@ def bgp_session_subscription_factory(faker): authentication_key=authentication_key or faker.password(), multipath_enabled=multipath_enabled, prefix_limit=prefix_limit, + ttl_security=ttl_security, send_default_route=send_default_route, is_multi_hop=is_multi_hop, rtbh_enabled=rtbh_enabled, diff --git a/test/services/test_lso_client.py b/test/services/test_lso_client.py index 2f52072a723c8656f79a2ea5abb0ee6127920cc7..78af95fc212a205f906ddb8e34d1bf5e5fec112c 100644 --- a/test/services/test_lso_client.py +++ b/test/services/test_lso_client.py @@ -4,7 +4,8 @@ from gso.services.lso_client import _execute_playbook @patch("gso.services.lso_client.requests.post") -def test_replace_unicode_in_lso_call_success(mock_post): +def test_replace_unicode_in_lso_call_success(mock_post, faker): + mocked_uuid = faker.uuid4() extra_vars = { "deployment_description": "I am going to deploy the best GÉANT service EVER!!", "email": "goat@géant.org", @@ -19,10 +20,11 @@ def test_replace_unicode_in_lso_call_success(mock_post): "deployment_description": "I am going to deploy the best GEANT service EVER!!", "email": "goat@geant.org", "translations": {"ja": "zieantonosugoinasa-bisuwodepuroisuru"}, + "gso_process_id": mocked_uuid, }, } execute_playbook = _execute_playbook.__wrapped__ - execute_playbook("playbook.yaml", "/api/callback_route", {}, extra_vars) + execute_playbook("playbook.yaml", "/api/callback_route", {}, extra_vars, mocked_uuid) mock_post.assert_called_once_with("https://localhost:44444/api/playbook", json=expected_parameters, timeout=10) diff --git a/test/workflows/edge_port/test_create_edge_port.py b/test/workflows/edge_port/test_create_edge_port.py index b907a4138ae89ea52c335b849edd087326775641..b73b9fe0af72b099e93e1e98b779346e11c48c61 100644 --- a/test/workflows/edge_port/test_create_edge_port.py +++ b/test/workflows/edge_port/test_create_edge_port.py @@ -87,9 +87,7 @@ def input_form_wizard_data(request, router_subscription_factory, partner_factory @pytest.mark.workflow() @pytest.mark.parametrize("router_vendor", [*Vendor.values()]) -@patch("gso.services.lso_client._send_request") def test_successful_edge_port_creation( - mock_execute_playbook, router_vendor, input_form_wizard_data, faker, @@ -119,13 +117,10 @@ def test_successful_edge_port_creation( == f"Edge Port {initial_data[2]["name"]} on {router_fqdn}, GAAR, {subscription.edge_port.ga_id}" ) assert len(subscription.edge_port.edge_port_ae_members) == 2 - assert mock_execute_playbook.call_count == 4 @pytest.mark.workflow() -@patch("gso.services.lso_client._send_request") def test_successful_edge_port_creation_with_auto_ga_id_creation( - mock_execute_playbook, input_form_wizard_data, faker, _netbox_client_mock, # noqa: PT019 @@ -150,7 +145,6 @@ def test_successful_edge_port_creation_with_auto_ga_id_creation( assert subscription.status == "active" assert subscription.edge_port.ga_id.startswith("GA-5000") - assert mock_execute_playbook.call_count == 4 def test_edge_port_creation_with_invalid_input( @@ -173,9 +167,7 @@ def test_edge_port_creation_with_invalid_input( @pytest.mark.workflow() -@patch("gso.services.lso_client._send_request") def test_edge_port_creation_with_existing_ga_id( - mock_execute_playbook, input_form_wizard_data, faker, _netbox_client_mock, # noqa: PT019 diff --git a/test/workflows/edge_port/test_migrate_edge_port.py b/test/workflows/edge_port/test_migrate_edge_port.py index 821e7e82b9c8e071008c706ab736e9d164ac26be..85ab31cfc826bacd30cde8523583d46a35f56341 100644 --- a/test/workflows/edge_port/test_migrate_edge_port.py +++ b/test/workflows/edge_port/test_migrate_edge_port.py @@ -75,9 +75,7 @@ def input_form_wizard_data(request, router_subscription_factory, partner, faker) @pytest.mark.workflow() @patch("gso.tasks.start_process.start_process_task.apply_async") -@patch("gso.services.lso_client._send_request") def test_successful_edge_port_migration( - mock_execute_playbook, start_process_task_apply_async, input_form_wizard_data, faker, @@ -128,4 +126,3 @@ def test_successful_edge_port_migration( assert subscription.edge_port.ga_id is not None assert subscription.description == f"Edge Port lag-21 on {router_fqdn}, GAAR, {subscription.edge_port.ga_id}" assert len(subscription.edge_port.edge_port_ae_members) == 2 - assert mock_execute_playbook.call_count == 4 diff --git a/test/workflows/edge_port/test_modify_edge_port.py b/test/workflows/edge_port/test_modify_edge_port.py index 0d71e92cbc07513f1026bc06924c778ed4800ded..987344969177779c2b321d2a69716ce11a1c563a 100644 --- a/test/workflows/edge_port/test_modify_edge_port.py +++ b/test/workflows/edge_port/test_modify_edge_port.py @@ -56,7 +56,6 @@ def test_modify_edge_port_with_invalid_ga_id( @pytest.mark.workflow() -@patch("gso.services.lso_client._send_request") @patch("gso.services.netbox_client.NetboxClient.get_available_interfaces") @patch("gso.services.netbox_client.NetboxClient.attach_interface_to_lag") @patch("gso.services.netbox_client.NetboxClient.reserve_interface") @@ -72,7 +71,6 @@ def test_modify_edge_port_with_changing_capacity( mocked_reserve_interface, mocked_attach_interface_to_lag, mocked_get_available_interfaces, - mocked_execute_playbook, input_form_wizard_data, faker, ): @@ -99,7 +97,6 @@ def test_modify_edge_port_with_changing_capacity( subscription = EdgePort.from_subscription(subscription_id) assert subscription.status == "active" - assert mocked_execute_playbook.call_count == 2 # The number of members have been changed from 2 to 1 assert mocked_reserve_interface.call_count == 1 @@ -132,7 +129,6 @@ def input_form_wizard_without_changing_capacity(request, faker, edge_port_subscr @pytest.mark.workflow() -@patch("gso.services.lso_client._send_request") @patch("gso.services.netbox_client.NetboxClient.get_available_interfaces") @patch("gso.services.netbox_client.NetboxClient.attach_interface_to_lag") @patch("gso.services.netbox_client.NetboxClient.reserve_interface") @@ -148,7 +144,6 @@ def test_modify_edge_port_without_changing_capacity( mocked_reserve_interface, mocked_attach_interface_to_lag, mocked_get_available_interfaces, - mocked_execute_playbook, input_form_wizard_without_changing_capacity, faker, ): @@ -173,7 +168,6 @@ def test_modify_edge_port_without_changing_capacity( assert subscription.status == "active" # The capacity has not been changed so the following methods should not be called - assert mocked_execute_playbook.call_count == 0 assert mocked_reserve_interface.call_count == 0 assert mocked_attach_interface_to_lag.call_count == 0 assert mocked_free_interface.call_count == 0 diff --git a/test/workflows/edge_port/test_terminate_edge_port.py b/test/workflows/edge_port/test_terminate_edge_port.py index 58c7d087208913006c52988d4b7cbf87330ff033..d399dd377d04093e4ebf208219cc00b43a623c6d 100644 --- a/test/workflows/edge_port/test_terminate_edge_port.py +++ b/test/workflows/edge_port/test_terminate_edge_port.py @@ -13,13 +13,11 @@ from test.workflows import ( @pytest.mark.workflow() -@patch("gso.services.lso_client._send_request") @patch("gso.services.netbox_client.NetboxClient.delete_interface") @patch("gso.services.netbox_client.NetboxClient.free_interface") def test_successful_edge_port_termination( mocked_free_interface, mocked_delete_interface, - mock_execute_playbook, edge_port_subscription_factory, faker, ): @@ -52,4 +50,3 @@ def test_successful_edge_port_termination( subscription = EdgePort.from_subscription(subscription_id) assert subscription.status == "terminated" - assert mock_execute_playbook.call_count == 2 diff --git a/test/workflows/edge_port/test_validate_edge_port.py b/test/workflows/edge_port/test_validate_edge_port.py index d6ce39e05b49f5017a10364961191fd6a24486e9..92ee9454d08fc55520c20837c20c03e22298c6a6 100644 --- a/test/workflows/edge_port/test_validate_edge_port.py +++ b/test/workflows/edge_port/test_validate_edge_port.py @@ -13,11 +13,9 @@ from test.workflows import ( @pytest.mark.workflow() -@patch("gso.services.lso_client._send_request") @patch("gso.services.netbox_client.NetboxClient.get_interface_by_name_and_device") def test_validate_edge_port_success( mock_get_interface_by_name_and_device, - mock_execute_playbook, edge_port_subscription_factory, faker, ): @@ -56,6 +54,6 @@ def test_validate_edge_port_success( assert_complete(result) subscription = EdgePort.from_subscription(subscription_id) assert subscription.status == "active" - assert mock_execute_playbook.call_count == 1 + # One time for getting the LAG and two times for getting the interfaces assert mock_get_interface_by_name_and_device.call_count == 3 diff --git a/test/workflows/iptrunk/test_create_iptrunk.py b/test/workflows/iptrunk/test_create_iptrunk.py index 861c6d325cd878ccb8d2624561f6e426d270117d..95522cfe16af44c6b135d3cb509b9b33f29e0e07 100644 --- a/test/workflows/iptrunk/test_create_iptrunk.py +++ b/test/workflows/iptrunk/test_create_iptrunk.py @@ -100,7 +100,6 @@ def input_form_wizard_data(request, router_subscription_factory, faker): @pytest.mark.workflow() -@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") @@ -114,7 +113,6 @@ def test_successful_iptrunk_creation_with_standard_lso_result( mock_create_host, mock_allocate_v4_network, mock_allocate_v6_network, - mock_execute_playbook, input_form_wizard_data, faker, _netbox_client_mock, # noqa: PT019 @@ -154,14 +152,12 @@ def test_successful_iptrunk_creation_with_standard_lso_result( f"{subscription.iptrunk.gs_id}" ) - assert mock_execute_playbook.call_count == 6 # We search for 6 hosts in total, 2 in a /31 and 4 in a /126 assert mock_find_host_by_ip.call_count == 6 assert mock_ping.call_count == 6 @pytest.mark.workflow() -@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") @@ -171,7 +167,6 @@ def test_iptrunk_creation_fails_when_lso_return_code_is_one( mock_find_host_by_ip, mock_allocate_v4_network, mock_allocate_v6_network, - mock_execute_playbook, input_form_wizard_data, faker, _netbox_client_mock, # noqa: PT019 @@ -189,14 +184,12 @@ def test_iptrunk_creation_fails_when_lso_return_code_is_one( assert_lso_interaction_failure(result, process_stat, step_log) - assert mock_execute_playbook.call_count == 2 assert mock_find_host_by_ip.call_count == 6 assert mock_ping.call_count == 6 @pytest.mark.parametrize("input_form_wizard_data", [Vendor.JUNIPER], indirect=True) @pytest.mark.workflow() -@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") @@ -210,7 +203,6 @@ def test_successful_iptrunk_creation_with_juniper_interface_names( mock_create_host, mock_allocate_v4_network, mock_allocate_v6_network, - mock_execute_playbook, input_form_wizard_data, faker, _netbox_client_mock, # noqa: PT019 @@ -233,7 +225,7 @@ def test_successful_iptrunk_creation_with_juniper_interface_names( result, step_log = resume_suspended_workflow(result, process_stat, step_log, input_data=USER_CONFIRM_EMPTY_FORM) assert_complete(result) - assert mock_execute_playbook.call_count == 6 + assert mock_find_host_by_ip.call_count == 6 assert mock_ping.call_count == 6 diff --git a/test/workflows/iptrunk/test_deploy_twamp.py b/test/workflows/iptrunk/test_deploy_twamp.py index 7791940fc60458a6c9eadb3e5c5fef0125c2dd5a..d43d213d971d9e7a90f7aff08d69bf9fcbaf04de 100644 --- a/test/workflows/iptrunk/test_deploy_twamp.py +++ b/test/workflows/iptrunk/test_deploy_twamp.py @@ -1,5 +1,3 @@ -from unittest.mock import patch - import pytest from gso.products.product_types.iptrunk import Iptrunk @@ -12,9 +10,7 @@ from test.workflows import ( @pytest.mark.workflow() -@patch("gso.services.lso_client._send_request") def test_iptrunk_deploy_twamp_success( - mock_execute_playbook, iptrunk_subscription_factory, faker, ): @@ -35,4 +31,3 @@ def test_iptrunk_deploy_twamp_success( subscription = Iptrunk.from_subscription(subscription_id) assert subscription.status == "active" - assert mock_execute_playbook.call_count == 3 diff --git a/test/workflows/iptrunk/test_migrate_iptrunk.py b/test/workflows/iptrunk/test_migrate_iptrunk.py index 8054dfba1b9341dd41dad2ec47dae8c7661a79e8..d2bd637b248bdda453d6a1f68685562a4dcacf7c 100644 --- a/test/workflows/iptrunk/test_migrate_iptrunk.py +++ b/test/workflows/iptrunk/test_migrate_iptrunk.py @@ -111,7 +111,6 @@ def interface_lists_are_equal(list1, list2): @pytest.mark.workflow() @patch("gso.services.infoblox.create_host_by_ip") @patch("gso.services.infoblox.delete_host_by_ip") -@patch("gso.services.lso_client._send_request") @patch("gso.services.netbox_client.NetboxClient.get_available_interfaces") @patch("gso.services.netbox_client.NetboxClient.get_available_lags") @patch("gso.services.netbox_client.NetboxClient.create_interface") @@ -131,7 +130,6 @@ def test_migrate_iptrunk_success( # noqa: PLR0915 mocked_create_interface, mocked_get_available_lags, mocked_get_available_interfaces, - mock_execute_playbook, mock_delete_host_by_ip, mock_create_host_by_ip, migrate_form_input, @@ -180,7 +178,7 @@ def test_migrate_iptrunk_success( # noqa: PLR0915 subscription = Iptrunk.from_subscription(subscription_id) assert subscription.status == "active" - assert mock_execute_playbook.call_count == (17 if restore_isis_metric else 16) + assert mock_create_host_by_ip.call_count == 1 assert mock_delete_host_by_ip.call_count == 1 diff --git a/test/workflows/iptrunk/test_modify_isis_metric.py b/test/workflows/iptrunk/test_modify_isis_metric.py index 551ab0c2628e144013376093353babf2f8f6221d..6b1bc416c72cce9b68a3fbeff763511c781a24da 100644 --- a/test/workflows/iptrunk/test_modify_isis_metric.py +++ b/test/workflows/iptrunk/test_modify_isis_metric.py @@ -1,5 +1,3 @@ -from unittest.mock import patch - import pytest from gso.products.product_types.iptrunk import Iptrunk @@ -12,9 +10,7 @@ from test.workflows import ( @pytest.mark.workflow() -@patch("gso.services.lso_client._send_request") def test_iptrunk_modify_isis_metric_success( - mock_provision_ip_trunk, iptrunk_subscription_factory, faker, ): @@ -40,5 +36,4 @@ def test_iptrunk_modify_isis_metric_success( subscription = Iptrunk.from_subscription(subscription_id) assert subscription.status == "active" - assert mock_provision_ip_trunk.call_count == 2 assert subscription.iptrunk.iptrunk_isis_metric == new_isis_metric diff --git a/test/workflows/iptrunk/test_modify_trunk_interface.py b/test/workflows/iptrunk/test_modify_trunk_interface.py index e636c2cc3db61f6b4adf5041f7c125a21b42ef70..4b0f2577208fc5b001dbc2fa90865596445a7be1 100644 --- a/test/workflows/iptrunk/test_modify_trunk_interface.py +++ b/test/workflows/iptrunk/test_modify_trunk_interface.py @@ -92,7 +92,6 @@ def input_form_iptrunk_data( indirect=True, ) @pytest.mark.workflow() -@patch("gso.services.lso_client._send_request") @patch("gso.services.netbox_client.NetboxClient.get_available_interfaces") @patch("gso.services.netbox_client.NetboxClient.attach_interface_to_lag") @patch("gso.services.netbox_client.NetboxClient.reserve_interface") @@ -106,7 +105,6 @@ def test_iptrunk_modify_trunk_interface_success( mocked_reserve_interface, mocked_attach_interface_to_lag, mocked_get_available_interfaces, - mock_provision_ip_trunk, input_form_iptrunk_data, faker, ): @@ -134,7 +132,6 @@ def test_iptrunk_modify_trunk_interface_success( subscription = Iptrunk.from_subscription(subscription_id) assert subscription.status == "active" - assert mock_provision_ip_trunk.call_count == lso_interaction_count # Assert all Netbox calls have been made new_sid = input_form_iptrunk_data[1]["gs_id"] new_side_a_gid = input_form_iptrunk_data[3]["side_a_ga_id"] diff --git a/test/workflows/iptrunk/test_terminate_iptrunk.py b/test/workflows/iptrunk/test_terminate_iptrunk.py index 14c6c5298d16e58b1dba9f9b97570d56af2e9120..6ce271a1a03e8c273b7ea196b29348f44c332072 100644 --- a/test/workflows/iptrunk/test_terminate_iptrunk.py +++ b/test/workflows/iptrunk/test_terminate_iptrunk.py @@ -15,7 +15,6 @@ from test.workflows import ( @pytest.mark.workflow() -@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,7 +22,6 @@ def test_successful_iptrunk_termination( mocked_free_interface, mocked_delete_interface, mock_infoblox_delete_network, - mock_execute_playbook, iptrunk_subscription_factory, faker, router_subscription_factory, @@ -62,6 +60,6 @@ def test_successful_iptrunk_termination( subscription = Iptrunk.from_subscription(subscription_id) assert subscription.status == "terminated" - 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 a6460cb3a59a823f5812d0fcf71120db907edd5f..e192980e779f08c3cac71abbc1c6298f51b76932 100644 --- a/test/workflows/iptrunk/test_validate_iptrunk.py +++ b/test/workflows/iptrunk/test_validate_iptrunk.py @@ -45,11 +45,9 @@ 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.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, - mock_validate_iptrunk, mock_find_host_by_fqdn, mock_find_v6_host_by_fqdn, mock_find_network_by_cidr, @@ -191,7 +189,6 @@ def test_validate_iptrunk_success( subscription = Iptrunk.from_subscription(subscription_id) assert subscription.status == subscription_status - assert mock_validate_iptrunk.call_count == 3 assert mock_find_host_by_fqdn.call_count == 2 assert mock_find_v6_host_by_fqdn.call_count == 2 assert mock_find_network_by_cidr.call_count == 2 @@ -203,11 +200,9 @@ 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.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, - mock_validate_iptrunk, mock_find_host_by_fqdn, mock_find_v6_host_by_fqdn, mock_find_network_by_cidr, @@ -235,7 +230,6 @@ def test_validate_iptrunk_skip_legacy_trunks( assert subscription.status == subscription_status assert mock_get_interface_by_name.call_count == 0 - assert mock_validate_iptrunk.call_count == 0 assert mock_find_host_by_fqdn.call_count == 0 assert mock_find_v6_host_by_fqdn.call_count == 0 assert mock_find_network_by_cidr.call_count == 0 diff --git a/test/workflows/l2_circuit/test_create_layer_2_circuit.py b/test/workflows/l2_circuit/test_create_layer_2_circuit.py index 4d282f6f73f5f2df113a069c75a2f3126773c2d6..b892b5df4cd8dfeaf466b8fb4ddb2d04e675c271 100644 --- a/test/workflows/l2_circuit/test_create_layer_2_circuit.py +++ b/test/workflows/l2_circuit/test_create_layer_2_circuit.py @@ -1,5 +1,3 @@ -from unittest.mock import patch - import pytest from orchestrator.types import SubscriptionLifecycle @@ -60,9 +58,7 @@ def layer_2_circuit_ethernet_input( @pytest.mark.parametrize("layer_2_circuit_service_type", LAYER_2_CIRCUIT_SERVICE_TYPES) @pytest.mark.workflow() -@patch("gso.services.lso_client._send_request") def test_create_layer_2_circuit_success( - mock_lso_interaction, layer_2_circuit_service_type, layer_2_circuit_input, faker, @@ -76,7 +72,7 @@ def test_create_layer_2_circuit_success( assert_complete(result) state = extract_state(result) subscription = Layer2Circuit.from_subscription(state["subscription_id"]) - assert mock_lso_interaction.call_count == 2 + assert subscription.status == SubscriptionLifecycle.ACTIVE assert subscription.layer_2_circuit.virtual_circuit_id is not None assert len(subscription.layer_2_circuit.layer_2_circuit_sides) == 2 @@ -113,9 +109,7 @@ def test_create_layer_2_circuit_success( @pytest.mark.parametrize("layer_2_circuit_service_type", LAYER_2_CIRCUIT_SERVICE_TYPES) @pytest.mark.workflow() -@patch("gso.services.lso_client._send_request") def test_create_layer_2_circuit_with_ethernet_type( - mock_lso_interaction, layer_2_circuit_service_type, layer_2_circuit_ethernet_input, faker, @@ -129,7 +123,7 @@ def test_create_layer_2_circuit_with_ethernet_type( assert_complete(result) state = extract_state(result) subscription = Layer2Circuit.from_subscription(state["subscription_id"]) - assert mock_lso_interaction.call_count == 2 + assert subscription.status == SubscriptionLifecycle.ACTIVE assert subscription.layer_2_circuit.virtual_circuit_id is not None assert len(subscription.layer_2_circuit.layer_2_circuit_sides) == 2 diff --git a/test/workflows/l2_circuit/test_migrate_layer_2_circuit.py b/test/workflows/l2_circuit/test_migrate_layer_2_circuit.py index a0f05946cde87647941cb5421af0399a30ce0e47..7b66d9b3afd8fe186de844f1a5f89f0c591d9118 100644 --- a/test/workflows/l2_circuit/test_migrate_layer_2_circuit.py +++ b/test/workflows/l2_circuit/test_migrate_layer_2_circuit.py @@ -1,5 +1,3 @@ -from unittest.mock import patch - import pytest from gso.products.product_types.edge_port import EdgePort @@ -13,9 +11,7 @@ from test.workflows import assert_complete, assert_lso_interaction_success, extr @pytest.mark.parametrize("run_old_side_ansible", [False, True]) @pytest.mark.parametrize("run_new_side_ansible", [False, True]) @pytest.mark.parametrize("generate_new_vc_id", [False, True]) -@patch("gso.services.lso_client._send_request") def test_migrate_layer_2_circuit( - mock_lso_interaction, generate_new_vc_id, run_new_side_ansible, run_old_side_ansible, @@ -64,7 +60,6 @@ def test_migrate_layer_2_circuit( subscription_id = state["subscription_id"] subscription = Layer2Circuit.from_subscription(subscription_id) assert subscription.status == "active" - assert mock_lso_interaction.call_count == lso_step_count replaced_edge_port = subscription.layer_2_circuit.layer_2_circuit_sides[1].sbp.edge_port assert replaced_edge_port.model_dump(exclude="edge_port_ae_members") == new_edge_port.edge_port.model_dump( diff --git a/test/workflows/l2_circuit/test_modify_layer_2_circuit.py b/test/workflows/l2_circuit/test_modify_layer_2_circuit.py index e4f4dcce33efeabe2dac11c54a41b014577ab0de..7637efc200f697236ecdc7e2931849b32ab28014 100644 --- a/test/workflows/l2_circuit/test_modify_layer_2_circuit.py +++ b/test/workflows/l2_circuit/test_modify_layer_2_circuit.py @@ -1,5 +1,3 @@ -from unittest.mock import patch - import pytest from orchestrator.types import SubscriptionLifecycle @@ -11,9 +9,7 @@ from test.workflows import assert_complete, assert_lso_interaction_success, extr @pytest.mark.parametrize("layer_2_circuit_service_type", LAYER_2_CIRCUIT_SERVICE_TYPES) @pytest.mark.parametrize("run_ansible_steps", [False, True]) @pytest.mark.workflow() -@patch("gso.services.lso_client._send_request") def test_modify_layer_2_circuit_change_policer_bandwidth( - mock_lso_interaction, layer_2_circuit_service_type, run_ansible_steps, layer_2_circuit_subscription_factory, @@ -48,7 +44,7 @@ def test_modify_layer_2_circuit_change_policer_bandwidth( subscription = Layer2Circuit.from_subscription(str(subscription.subscription_id)) assert_complete(result) - assert mock_lso_interaction.call_count == lso_step_count + assert subscription.status == SubscriptionLifecycle.ACTIVE assert subscription.layer_2_circuit.policer_enabled is False assert subscription.layer_2_circuit.bandwidth is None @@ -70,9 +66,7 @@ def test_modify_layer_2_circuit_change_policer_bandwidth( @pytest.mark.parametrize("layer_2_circuit_service_type", LAYER_2_CIRCUIT_SERVICE_TYPES) @pytest.mark.parametrize("run_ansible_steps", [False, True]) @pytest.mark.workflow() -@patch("gso.services.lso_client._send_request") def test_modify_layer_2_circuit_change_circuit_type( - mock_lso_interaction, layer_2_circuit_service_type, run_ansible_steps, layer_2_circuit_subscription_factory, @@ -105,7 +99,7 @@ def test_modify_layer_2_circuit_change_circuit_type( assert_complete(result) state = extract_state(result) subscription = Layer2Circuit.from_subscription(state["subscription_id"]) - assert mock_lso_interaction.call_count == lso_step_count + assert subscription.status == SubscriptionLifecycle.ACTIVE assert subscription.layer_2_circuit.vlan_range_lower_bound is None assert subscription.layer_2_circuit.vlan_range_upper_bound is None diff --git a/test/workflows/l2_circuit/test_terminate_layer_2_circuit.py b/test/workflows/l2_circuit/test_terminate_layer_2_circuit.py index 55deedededc9c3c43ef3ca56d6145508cbf7f5d4..eba60dafc6940d270f145585ea9a04b4086b54ac 100644 --- a/test/workflows/l2_circuit/test_terminate_layer_2_circuit.py +++ b/test/workflows/l2_circuit/test_terminate_layer_2_circuit.py @@ -1,5 +1,3 @@ -from unittest.mock import patch - import pytest from gso.products.product_types.layer_2_circuit import LAYER_2_CIRCUIT_SERVICE_TYPES, Layer2Circuit @@ -9,9 +7,8 @@ from test.workflows import assert_complete, assert_lso_interaction_success, extr @pytest.mark.workflow() @pytest.mark.parametrize("layer_2_circuit_service_type", LAYER_2_CIRCUIT_SERVICE_TYPES) @pytest.mark.parametrize("run_ansible_steps", [True, False]) -@patch("gso.services.lso_client._send_request") def test_terminate_layer_2_circuit( - mock_lso_interaction, layer_2_circuit_service_type, run_ansible_steps, layer_2_circuit_subscription_factory, faker + layer_2_circuit_service_type, run_ansible_steps, layer_2_circuit_subscription_factory, faker ): subscription_id = str( layer_2_circuit_subscription_factory(layer_2_circuit_service_type=layer_2_circuit_service_type).subscription_id @@ -33,4 +30,3 @@ def test_terminate_layer_2_circuit( subscription_id = state["subscription_id"] subscription = Layer2Circuit.from_subscription(subscription_id) assert subscription.status == "terminated" - assert mock_lso_interaction.call_count == lso_step_count diff --git a/test/workflows/l3_core_service/test_create_l3_core_service.py b/test/workflows/l3_core_service/test_create_l3_core_service.py index 20d307f4977b83c011b77e88e2b24eca8cf57de9..a8ee5bc6a27b090acfaf936010999fc8ab0be7fe 100644 --- a/test/workflows/l3_core_service/test_create_l3_core_service.py +++ b/test/workflows/l3_core_service/test_create_l3_core_service.py @@ -39,11 +39,9 @@ def base_bgp_peer_input(faker): @pytest.mark.parametrize("product_name", L3_PRODUCT_NAMES) @pytest.mark.workflow() -@patch("gso.services.lso_client._send_request") @patch("gso.workflows.l3_core_service.base_create_l3_core_service.SharePointClient") def test_create_l3_core_service_success( mock_sharepoint_client, - mock_lso_client, product_name, faker, partner_factory, @@ -108,7 +106,6 @@ def test_create_l3_core_service_success( subscription = SubscriptionModel.from_subscription(state["subscription_id"]) assert subscription.product.name == product_name - assert mock_lso_client.call_count == lso_interaction_count + 1 assert subscription.status == SubscriptionLifecycle.ACTIVE assert len(subscription.l3_core.ap_list) == 1 assert ( diff --git a/test/workflows/l3_core_service/test_migrate_l3_core_service.py b/test/workflows/l3_core_service/test_migrate_l3_core_service.py index 18c5682e777ccef18fc95e3a235b829e9814c7e6..eb1b292a90b769ffd091e1d677a91df16518803f 100644 --- a/test/workflows/l3_core_service/test_migrate_l3_core_service.py +++ b/test/workflows/l3_core_service/test_migrate_l3_core_service.py @@ -1,5 +1,3 @@ -from unittest.mock import patch - import pytest from orchestrator.domain import SubscriptionModel @@ -19,9 +17,7 @@ from test.workflows import ( @pytest.mark.workflow() @pytest.mark.parametrize("product_name", L3_PRODUCT_NAMES) -@patch("gso.services.lso_client._send_request") def test_migrate_l3_core_service_success( - mock_execute_playbook, faker, edge_port_subscription_factory, partner_factory, @@ -62,7 +58,7 @@ def test_migrate_l3_core_service_success( assert_complete(result) state = extract_state(result) subscription = SubscriptionModel.from_subscription(state["subscription_id"]) - assert mock_execute_playbook.call_count == 11 + assert subscription.insync assert len(subscription.l3_core.ap_list) == 1 assert str(subscription.l3_core.ap_list[0].sbp.edge_port.owner_subscription_id) == destination_edge_port @@ -70,9 +66,7 @@ def test_migrate_l3_core_service_success( @pytest.mark.workflow() @pytest.mark.parametrize("product_name", L3_PRODUCT_NAMES) -@patch("gso.services.lso_client._send_request") def test_migrate_l3_core_service_scoped_emission( - mock_execute_playbook, faker, edge_port_subscription_factory, access_port_factory, @@ -139,7 +133,7 @@ def test_migrate_l3_core_service_scoped_emission( assert_complete(result) state = extract_state(result) subscription = SubscriptionModel.from_subscription(state["subscription_id"]) - assert mock_execute_playbook.call_count == 11 + assert subscription.insync ap_list = subscription.l3_core.ap_list assert len(ap_list) == 5 diff --git a/test/workflows/l3_core_service/test_modify_l3_core_service.py b/test/workflows/l3_core_service/test_modify_l3_core_service.py index 0b2a9f3f9377b3c2513f8db551d5c45fd860677c..93f8680b68dc20273030535499364f8a5a0e121f 100644 --- a/test/workflows/l3_core_service/test_modify_l3_core_service.py +++ b/test/workflows/l3_core_service/test_modify_l3_core_service.py @@ -68,7 +68,8 @@ def test_modify_l3_core_service_add_new_edge_port_success( "authentication_key": faker.password(), "peer_address": faker.ipv4(), "bfd_enabled": False, - "prefix_limit": 1000, + "prefix_limit": faker.random_int(min=500, max=1000), + "ttl_security": faker.random_int(max=255), }, "v6_bgp_peer": { "authentication_key": faker.password(), @@ -142,7 +143,8 @@ def sbp_input_form_data(faker): "v6_bgp_is_passive": True, "v6_bgp_peer_address": faker.ipv6(), "v6_bgp_add_v6_multicast": True, - "v6_bgp_prefix_limit": 3000, + "v6_bgp_prefix_limit": faker.random_int(min=2500, max=3000), + "v6_bgp_ttl_security": faker.random_int(max=255), } return _generate_form_data diff --git a/test/workflows/l3_core_service/test_validate_l3_core_service.py b/test/workflows/l3_core_service/test_validate_l3_core_service.py index ad6a06ca2ebf68737ca0e8a84173782a5d3f2caa..60192558b2d603895c765fb546c672a1b58b3648 100644 --- a/test/workflows/l3_core_service/test_validate_l3_core_service.py +++ b/test/workflows/l3_core_service/test_validate_l3_core_service.py @@ -1,5 +1,3 @@ -from unittest.mock import patch - import pytest from orchestrator.domain import SubscriptionModel @@ -8,9 +6,8 @@ from test.workflows import assert_complete, assert_lso_success, extract_state, r @pytest.mark.workflow() -@patch("gso.services.lso_client._send_request") @pytest.mark.parametrize("product_name", L3_PRODUCT_NAMES) -def test_validate_l3_core_service(mock_lso_interaction, l3_core_service_subscription_factory, faker, product_name): +def test_validate_l3_core_service(l3_core_service_subscription_factory, faker, product_name): subscription_id = str(l3_core_service_subscription_factory(product_name=product_name).subscription_id) initial_l3_core_service_data = [{"subscription_id": subscription_id}] result, process_stat, step_log = run_workflow(L3_VALIDATION_WF_MAP[product_name], initial_l3_core_service_data) @@ -23,4 +20,3 @@ def test_validate_l3_core_service(mock_lso_interaction, l3_core_service_subscrip subscription = SubscriptionModel.from_subscription(subscription_id) assert subscription.status == "active" assert subscription.insync is True - assert mock_lso_interaction.call_count == 2 diff --git a/test/workflows/l3_core_service/test_validate_prefix_list.py b/test/workflows/l3_core_service/test_validate_prefix_list.py index a2baecb6839c73125d52da4eda76ac84f9c4e389..42e945200def7af9a56e1fbe5e6c2b0326fd22bc 100644 --- a/test/workflows/l3_core_service/test_validate_prefix_list.py +++ b/test/workflows/l3_core_service/test_validate_prefix_list.py @@ -1,5 +1,3 @@ -from unittest.mock import patch - import pytest from gso.products.product_types.geant_ip import GeantIP @@ -18,8 +16,7 @@ from test.workflows import ( @pytest.mark.workflow() -@patch("gso.services.lso_client._send_request") -def test_validate_prefix_list_success(mock_lso_interaction, geant_ip_subscription_factory, faker): +def test_validate_prefix_list_success(geant_ip_subscription_factory, faker): subscription_id = str(geant_ip_subscription_factory().subscription_id) initial_l3_core_service_data = [{"subscription_id": subscription_id}] # Run the workflow and extract results @@ -36,13 +33,10 @@ def test_validate_prefix_list_success(mock_lso_interaction, geant_ip_subscriptio # Verify the subscription has no Juniper devices for ap in subscription.geant_ip.l3_core.ap_list: assert ap.sbp.edge_port.node.vendor != Vendor.JUNIPER - # Verify the number of LSO interactions - assert mock_lso_interaction.call_count == 1 @pytest.mark.workflow() -@patch("gso.services.lso_client._send_request") -def test_validate_prefix_list_with_diff(mock_lso_interaction, geant_ip_subscription_factory, faker): +def test_validate_prefix_list_with_diff(geant_ip_subscription_factory, faker): """Test case where playbook_has_diff qualifies and additional steps are executed.""" subscription_id = str(geant_ip_subscription_factory().subscription_id) initial_l3_core_service_data = [{"subscription_id": subscription_id}] @@ -68,13 +62,10 @@ def test_validate_prefix_list_with_diff(mock_lso_interaction, geant_ip_subscript subscription = GeantIP.from_subscription(subscription_id) assert subscription.status == "active" assert subscription.insync is True - # Verify the number of LSO interactions - assert mock_lso_interaction.call_count == 3 # One for validation and two for deployment @pytest.mark.workflow() -@patch("gso.services.lso_client._send_request") -def test_validate_prefix_list_without_diff(mock_lso_interaction, geant_ip_subscription_factory, faker): +def test_validate_prefix_list_without_diff(geant_ip_subscription_factory, faker): """Test case where playbook_has_diff does not qualify and skips additional steps.""" subscription_id = str(geant_ip_subscription_factory().subscription_id) initial_l3_core_service_data = [{"subscription_id": subscription_id}] @@ -89,8 +80,6 @@ def test_validate_prefix_list_without_diff(mock_lso_interaction, geant_ip_subscr subscription = GeantIP.from_subscription(subscription_id) assert subscription.status == "active" assert subscription.insync is True - # Verify the number of LSO interactions - assert mock_lso_interaction.call_count == 1 # Only validation is performed @pytest.mark.workflow() diff --git a/test/workflows/lan_switch_interconnect/test_validate_lan_switch_interconnect.py b/test/workflows/lan_switch_interconnect/test_validate_lan_switch_interconnect.py index 18e67d17117179eee6ec25083af2f90b7688b534..db180c7d57de409b0d58dd43539a19f5cb5fd297 100644 --- a/test/workflows/lan_switch_interconnect/test_validate_lan_switch_interconnect.py +++ b/test/workflows/lan_switch_interconnect/test_validate_lan_switch_interconnect.py @@ -10,9 +10,8 @@ from test.workflows import assert_complete, assert_lso_success, extract_state, r @pytest.mark.workflow() @patch("gso.services.infoblox.find_host_by_fqdn") @patch("gso.services.infoblox.find_network_by_cidr") -@patch("gso.services.lso_client._send_request") def test_validate_lan_switch_interconnect( - mock_lso_interaction, mock_find_network, mock_find_host, lan_switch_interconnect_subscription_factory, faker + mock_find_network, mock_find_host, lan_switch_interconnect_subscription_factory, faker ): subscription_id = str(lan_switch_interconnect_subscription_factory().subscription_id) mocked_netbox_reply = objects.HostRecord( diff --git a/test/workflows/router/test_create_router.py b/test/workflows/router/test_create_router.py index 67d317b1e7632111dc5c897ea2da08cd803e9bc9..8300a42bd9572dfe80b507ada904ce8825b3e4a2 100644 --- a/test/workflows/router/test_create_router.py +++ b/test/workflows/router/test_create_router.py @@ -38,7 +38,6 @@ def router_creation_input_form_data(site_subscription_factory, faker): @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") @@ -52,7 +51,6 @@ def test_create_nokia_router_success( mock_find_host_by_fqdn, mock_hostname_available, mock_netbox_create_device, - mock_provision_router, router_creation_input_form_data, router_role, faker, @@ -113,7 +111,6 @@ def test_create_nokia_router_success( assert subscription.status == "provisioning" assert subscription.description == f"Router {mock_fqdn}" - assert mock_provision_router.call_count == 3 assert mock_netbox_create_device.call_count == 1 assert mock_find_host_by_fqdn.call_count == 1 assert mock_sharepoint_client.call_count == 1 @@ -122,7 +119,6 @@ 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") @patch("gso.workflows.router.create_router.infoblox.find_network_by_cidr") @@ -140,7 +136,6 @@ def test_create_nokia_router_lso_failure( mock_find_network_by_cidr, mock_hostname_available, mock_netbox_create_device, - mock_provision_router, router_creation_input_form_data, router_role, faker, @@ -198,7 +193,6 @@ def test_create_nokia_router_lso_failure( assert subscription.status == "initial" assert subscription.description == f"Router {mock_fqdn}" - assert mock_provision_router.call_count == 2 assert mock_netbox_create_device.call_count == 0 assert mock_find_host_by_fqdn.call_count == 0 assert mock_find_network_by_cidr.call_count == 0 diff --git a/test/workflows/router/test_promote_p_to_pe.py b/test/workflows/router/test_promote_p_to_pe.py index 46ab40b7fa7215de2b94bbfd3980c5bea0506268..3b510b36024075a0f6c82568eb9c290c5db20319 100644 --- a/test/workflows/router/test_promote_p_to_pe.py +++ b/test/workflows/router/test_promote_p_to_pe.py @@ -18,11 +18,9 @@ from test.workflows import ( @pytest.mark.workflow() -@patch("gso.services.lso_client._send_request") @patch("gso.utils.workflow_steps.KentikClient") def test_promote_p_to_pe_success( mock_kentik_client, - mock_execute_playbook, router_subscription_factory, faker, ): @@ -45,7 +43,7 @@ def test_promote_p_to_pe_success( result, step_log = assert_lso_interaction_success(result, process_stat, step_log) state = extract_state(result) assert_complete(result) - assert mock_execute_playbook.call_count == 24 + assert state["subscription"]["router"]["router_role"] == RouterRole.PE @@ -66,8 +64,7 @@ def test_promote_p_to_pe_juniper_router(router_subscription_factory, faker): @pytest.mark.workflow() -@patch("gso.services.lso_client._send_request") -def test_promote_p_to_pe_nokia_pe_router(mock_execute_playbook, router_subscription_factory, faker): +def test_promote_p_to_pe_nokia_pe_router(router_subscription_factory, faker): """Test that the workflow does not run for a Nokia PE router since it is already a PE router.""" router_id = str( router_subscription_factory( diff --git a/test/workflows/router/test_redeploy_base_config.py b/test/workflows/router/test_redeploy_base_config.py index 5106c59aa98105115434625dd3fe1a75bf4bc7a5..4dee5a5e9d18ed93cc08812f85f256db5174688c 100644 --- a/test/workflows/router/test_redeploy_base_config.py +++ b/test/workflows/router/test_redeploy_base_config.py @@ -1,5 +1,3 @@ -from unittest.mock import patch - import pytest from gso.products.product_types.router import Router @@ -12,9 +10,7 @@ from test.workflows import ( @pytest.mark.workflow() -@patch("gso.services.lso_client._send_request") def test_redeploy_base_config_success( - mock_provision_router, router_subscription_factory, faker, ): @@ -35,4 +31,3 @@ def test_redeploy_base_config_success( subscription = Router.from_subscription(subscription_id) assert subscription.status == "active" - assert mock_provision_router.call_count == 2 diff --git a/test/workflows/router/test_terminate_router.py b/test/workflows/router/test_terminate_router.py index a760472efae75808a1a613d031eafb1494e1e18f..a7303181ecbf3ab50106ed9627f539e96b1add6f 100644 --- a/test/workflows/router/test_terminate_router.py +++ b/test/workflows/router/test_terminate_router.py @@ -12,7 +12,6 @@ from test.workflows import assert_complete, assert_lso_interaction_success, extr @pytest.mark.parametrize("remove_configuration", [True, False]) @pytest.mark.parametrize("update_ibgp_mesh", [True, False]) @pytest.mark.parametrize("update_sdp_mesh", [True, False]) -@patch("gso.services.lso_client._send_request") @patch("gso.workflows.router.terminate_router.NetboxClient.delete_device") @patch("gso.workflows.router.terminate_router.infoblox.delete_host_by_ip") @patch("gso.workflows.router.terminate_router.KentikClient") @@ -22,7 +21,6 @@ def test_terminate_pe_router_full_success( mock_kentik_client, mock_delete_host_by_ip, mock_delete_device, - mock_execute_playbook, remove_configuration, update_ibgp_mesh, update_sdp_mesh, @@ -65,13 +63,11 @@ def test_terminate_pe_router_full_success( assert mock_delete_device.call_count == 1 assert mock_delete_host_by_ip.call_count == 1 assert mock_librenms_remove_device.call_count == 1 - assert mock_execute_playbook.call_count == lso_interaction_count @pytest.mark.workflow() @pytest.mark.parametrize("remove_configuration", [True, False]) @pytest.mark.parametrize("update_ibgp_mesh", [True, False]) -@patch("gso.services.lso_client._send_request") @patch("gso.workflows.router.terminate_router.NetboxClient.delete_device") @patch("gso.workflows.router.terminate_router.infoblox.delete_host_by_ip") @patch("gso.workflows.router.terminate_router.LibreNMSClient.remove_device") @@ -79,7 +75,6 @@ def test_terminate_p_router_full_success( mock_librenms_remove_device, mock_delete_host_by_ip, mock_delete_device, - mock_execute_playbook, remove_configuration, update_ibgp_mesh, router_subscription_factory, @@ -117,4 +112,3 @@ def test_terminate_p_router_full_success( assert mock_delete_device.call_count == 1 assert mock_delete_host_by_ip.call_count == 1 assert mock_librenms_remove_device.call_count == 1 - assert mock_execute_playbook.call_count == lso_interaction_count diff --git a/test/workflows/router/test_update_ibgp_mesh.py b/test/workflows/router/test_update_ibgp_mesh.py index 2cd098b3986dbf2adc58e0a9bd209b0925b0d829..ca7ae4ee715200d30c3a5e18e110b707df88fa5d 100644 --- a/test/workflows/router/test_update_ibgp_mesh.py +++ b/test/workflows/router/test_update_ibgp_mesh.py @@ -21,13 +21,11 @@ from test.workflows import ( @pytest.mark.parametrize("update_ibgp_mesh", [True, False]) @pytest.mark.parametrize("update_sbp_mesh", [True, False]) @pytest.mark.workflow() -@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, update_sbp_mesh, update_ibgp_mesh, router_role, @@ -80,7 +78,6 @@ def test_update_ibgp_mesh_success( state = extract_state(result) - 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 8e9397885733b9a836ee14f827e6d94c097d4017..c67aa4e741f9204aa8e85312a270fec38d6589a7 100644 --- a/test/workflows/router/test_validate_router.py +++ b/test/workflows/router/test_validate_router.py @@ -20,7 +20,6 @@ from test.workflows import ( @pytest.mark.parametrize("router_role", [RouterRole.P, RouterRole.PE]) @pytest.mark.workflow() @patch("gso.services.infoblox.find_host_by_fqdn") -@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") @@ -28,7 +27,6 @@ def test_validate_nokia_router_success( mock_kentik_client, mock_validate_librenms_device, mock_get_device_by_name, - mock_execute_playbook, mock_find_host_by_fqdn, router_subscription_factory, faker, @@ -76,7 +74,7 @@ def test_validate_nokia_router_success( subscription = Router.from_subscription(subscription_id) assert subscription.status == router_state - assert mock_execute_playbook.call_count == lso_execution_count + assert mock_find_host_by_fqdn.call_count == 1 assert mock_get_device_by_name.call_count == 1 assert mock_validate_librenms_device.call_count == 1 diff --git a/test/workflows/switch/test_create_switch.py b/test/workflows/switch/test_create_switch.py index a6e02d83cbc2f816a48c9b494ae4d64cbaf41a99..137ff7a66b32685473def2ad432c8b4c3acfa72c 100644 --- a/test/workflows/switch/test_create_switch.py +++ b/test/workflows/switch/test_create_switch.py @@ -20,12 +20,9 @@ from test.workflows import ( @pytest.mark.workflow() -@patch("gso.services.lso_client._send_request") @patch("gso.services.infoblox.hostname_available") @patch("gso.services.sharepoint.SharePointClient") -def test_create_switch_success( - mock_sharepoint_client, mock_hostname_available, mock_execute_playbook, faker, site_subscription_factory -): +def test_create_switch_success(mock_sharepoint_client, mock_hostname_available, faker, site_subscription_factory): product_id = get_product_id_by_name(ProductName.SWITCH) initial_form_input = [ {"product": product_id}, @@ -63,4 +60,3 @@ def test_create_switch_success( subscription_id = state["subscription_id"] subscription = Switch.from_subscription(subscription_id) assert subscription.status == "provisioning" - assert mock_execute_playbook.call_count == 3 diff --git a/test/workflows/switch/test_validate_switch.py b/test/workflows/switch/test_validate_switch.py index 0e5a3b1349621dcc40af9a4d9f1ce4c588d35e44..8b9c57693a70b29b07573706ebf70c966bd76cd0 100644 --- a/test/workflows/switch/test_validate_switch.py +++ b/test/workflows/switch/test_validate_switch.py @@ -12,11 +12,9 @@ from test.workflows import ( @pytest.mark.workflow() -@patch("gso.services.lso_client._send_request") @patch("gso.services.netbox_client.NetboxClient.get_device_by_name") def test_validate_switch_success( mock_get_device_by_name, - mock_execute_playbook, switch_subscription_factory, faker, geant_partner, @@ -33,5 +31,5 @@ def test_validate_switch_success( subscription = Switch.from_subscription(state["subscription_id"]) assert subscription.status == "active" - assert mock_execute_playbook.call_count == 1 + assert mock_get_device_by_name.call_count == 1 diff --git a/test/workflows/vrf/test_modify_vrf_router_list.py b/test/workflows/vrf/test_modify_vrf_router_list.py index 2dbcd9ef1b3f7ee6423ca8fc83ff4d924045fa3d..47136c1f860145ed0a3036f036e98ba0e78f1df1 100644 --- a/test/workflows/vrf/test_modify_vrf_router_list.py +++ b/test/workflows/vrf/test_modify_vrf_router_list.py @@ -1,5 +1,4 @@ import uuid -from unittest.mock import patch import pytest from pydantic_forms.exceptions import FormValidationError @@ -9,10 +8,7 @@ from test.workflows import assert_complete, assert_lso_interaction_success, extr @pytest.mark.workflow() -@patch("gso.services.lso_client._send_request") -def test_modify_vrf_router_list_add_a_router( - mock_lso_call, vrf_subscription_factory, router_subscription_factory, faker -): +def test_modify_vrf_router_list_add_a_router(vrf_subscription_factory, router_subscription_factory, faker): subscription_id = str(vrf_subscription_factory().subscription_id) initial_vrf_data = [ {"subscription_id": subscription_id}, @@ -30,14 +26,10 @@ def test_modify_vrf_router_list_add_a_router( subscription = VRF.from_subscription(subscription_id) assert subscription.status == "active" assert len(subscription.vrf.vrf_router_list) == 1 - assert mock_lso_call.call_count == 2 @pytest.mark.workflow() -@patch("gso.services.lso_client._send_request") -def test_modify_vrf_router_list_remove_router( - mock_lso_call, vrf_subscription_factory, router_subscription_factory, faker -): +def test_modify_vrf_router_list_remove_router(vrf_subscription_factory, router_subscription_factory, faker): old_router = router_subscription_factory() subscription_id = str(vrf_subscription_factory(vrf_router_list=[old_router]).subscription_id) initial_vrf_data = [ @@ -56,7 +48,6 @@ def test_modify_vrf_router_list_remove_router( subscription = VRF.from_subscription(subscription_id) assert subscription.status == "active" assert len(subscription.vrf.vrf_router_list) == 0 - assert mock_lso_call.call_count == 2 @pytest.mark.workflow() diff --git a/test/workflows/vrf/test_redeploy_vrf.py b/test/workflows/vrf/test_redeploy_vrf.py index cb1d7f2e97bebe2bf9040add81d0201d544c0a7d..effabeb081e5584a47f3a7c1e715f7b4df9d7414 100644 --- a/test/workflows/vrf/test_redeploy_vrf.py +++ b/test/workflows/vrf/test_redeploy_vrf.py @@ -1,5 +1,3 @@ -from unittest.mock import patch - import pytest from gso.products.product_types.vrf import VRF @@ -7,8 +5,7 @@ from test.workflows import assert_complete, assert_lso_interaction_success, extr @pytest.mark.workflow() -@patch("gso.services.lso_client._send_request") -def test_redeploy_vrf(mock_lso_call, vrf_subscription_factory, router_subscription_factory, faker): +def test_redeploy_vrf(vrf_subscription_factory, router_subscription_factory, faker): router_a = router_subscription_factory() router_b = router_subscription_factory() subscription_id = str(vrf_subscription_factory(vrf_router_list=[router_a, router_b]).subscription_id) @@ -26,4 +23,3 @@ def test_redeploy_vrf(mock_lso_call, vrf_subscription_factory, router_subscripti subscription_id = state["subscription_id"] subscription = VRF.from_subscription(subscription_id) assert subscription.status == "active" - assert mock_lso_call.call_count == 2