From 77693effd55c6a4736d7eb1e5397dca42d2878f6 Mon Sep 17 00:00:00 2001 From: Karel van Klink <karel.vanklink@geant.org> Date: Mon, 23 Jun 2025 16:12:20 +0200 Subject: [PATCH] Add new BGP MED and local preference attributes to R&E L3 services --- ...0c7_add_attributes_to_r_e_product_block.py | 113 ++++++++++++++++++ gso/products/product_blocks/r_and_e_lhcone.py | 14 +++ gso/products/product_blocks/r_and_e_peer.py | 14 +++ gso/utils/shared_enums.py | 2 + gso/utils/types/multi_exit_discriminator.py | 28 +++++ .../create_imported_r_and_e_lhcone.py | 26 +++- .../r_and_e_lhcone/create_r_and_e_lhcone.py | 27 ++++- .../r_and_e_lhcone/modify_r_and_e_lhcone.py | 27 ++++- .../l3_core_service/r_and_e_lhcone/shared.py | 25 ++++ .../create_imported_r_and_e_peer.py | 26 +++- .../r_and_e_peer/create_r_and_e_peer.py | 27 ++++- .../r_and_e_peer/modify_r_and_e_peer.py | 27 ++++- .../l3_core_service/r_and_e_peer/shared.py | 25 ++++ test/utils/types/__init__.py | 0 .../types/test_multi_exit_discriminator.py | 30 +++++ .../test_create_imported_l3_core_service.py | 13 ++ .../test_create_l3_core_service.py | 11 ++ .../test_modify_l3_core_service.py | 50 +++++++- 18 files changed, 476 insertions(+), 9 deletions(-) create mode 100644 gso/migrations/versions/2025-06-20_b2b5137ef0c7_add_attributes_to_r_e_product_block.py create mode 100644 gso/utils/types/multi_exit_discriminator.py create mode 100644 gso/workflows/l3_core_service/r_and_e_lhcone/shared.py create mode 100644 gso/workflows/l3_core_service/r_and_e_peer/shared.py create mode 100644 test/utils/types/__init__.py create mode 100644 test/utils/types/test_multi_exit_discriminator.py diff --git a/gso/migrations/versions/2025-06-20_b2b5137ef0c7_add_attributes_to_r_e_product_block.py b/gso/migrations/versions/2025-06-20_b2b5137ef0c7_add_attributes_to_r_e_product_block.py new file mode 100644 index 000000000..188bcd79a --- /dev/null +++ b/gso/migrations/versions/2025-06-20_b2b5137ef0c7_add_attributes_to_r_e_product_block.py @@ -0,0 +1,113 @@ +"""Add attributes to R&E product block. + +Revision ID: b2b5137ef0c7 +Revises: 550e3aebc1c5 +Create Date: 2025-06-20 16:45:01.403416 + +""" +import sqlalchemy as sa +from alembic import op + +# revision identifiers, used by Alembic. +revision = 'b2b5137ef0c7' +down_revision = '7c3094cd282a' +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 ('v6_bgp_local_preference', 'BGP Local Preference for IPv6') RETURNING resource_types.resource_type_id + """)) + conn.execute(sa.text(""" +INSERT INTO resource_types (resource_type, description) VALUES ('v4_bgp_local_preference', 'BGP Local Preference for IPv4') RETURNING resource_types.resource_type_id + """)) + conn.execute(sa.text(""" +INSERT INTO resource_types (resource_type, description) VALUES ('v4_bgp_med', 'BGP Multi Exit Discriminant for IPv4') RETURNING resource_types.resource_type_id + """)) + conn.execute(sa.text(""" +INSERT INTO resource_types (resource_type, description) VALUES ('v6_bgp_med', 'BGP Multi Exit Discriminant for IPv6') 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 ('RAndELHCOneBlock')), (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('v6_bgp_local_preference'))) + """)) + 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 ('RAndELHCOneBlock')), (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('v4_bgp_local_preference'))) + """)) + 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 ('RAndELHCOneBlock')), (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('v4_bgp_med'))) + """)) + 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 ('RAndELHCOneBlock')), (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('v6_bgp_med'))) + """)) + 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 ('RAndEPeerBlock')), (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('v6_bgp_local_preference'))) + """)) + 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 ('RAndEPeerBlock')), (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('v4_bgp_local_preference'))) + """)) + 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 ('RAndEPeerBlock')), (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('v4_bgp_med'))) + """)) + 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 ('RAndEPeerBlock')), (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('v6_bgp_med'))) + """)) + + +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 ('RAndELHCOneBlock')) AND product_block_resource_types.resource_type_id = (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('v6_bgp_local_preference')) + """)) + 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 ('RAndELHCOneBlock'))) AND product_block_resource_types.resource_type_id = (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('v6_bgp_local_preference')) + """)) + 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 ('RAndELHCOneBlock')) AND product_block_resource_types.resource_type_id = (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('v4_bgp_local_preference')) + """)) + 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 ('RAndELHCOneBlock'))) AND product_block_resource_types.resource_type_id = (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('v4_bgp_local_preference')) + """)) + 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 ('RAndELHCOneBlock')) AND product_block_resource_types.resource_type_id = (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('v4_bgp_med')) + """)) + 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 ('RAndELHCOneBlock'))) AND product_block_resource_types.resource_type_id = (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('v4_bgp_med')) + """)) + 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 ('RAndELHCOneBlock')) AND product_block_resource_types.resource_type_id = (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('v6_bgp_med')) + """)) + 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 ('RAndELHCOneBlock'))) AND product_block_resource_types.resource_type_id = (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('v6_bgp_med')) + """)) + 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 ('RAndEPeerBlock')) AND product_block_resource_types.resource_type_id = (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('v6_bgp_local_preference')) + """)) + 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 ('RAndEPeerBlock'))) AND product_block_resource_types.resource_type_id = (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('v6_bgp_local_preference')) + """)) + 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 ('RAndEPeerBlock')) AND product_block_resource_types.resource_type_id = (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('v4_bgp_local_preference')) + """)) + 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 ('RAndEPeerBlock'))) AND product_block_resource_types.resource_type_id = (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('v4_bgp_local_preference')) + """)) + 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 ('RAndEPeerBlock')) AND product_block_resource_types.resource_type_id = (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('v4_bgp_med')) + """)) + 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 ('RAndEPeerBlock'))) AND product_block_resource_types.resource_type_id = (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('v4_bgp_med')) + """)) + 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 ('RAndEPeerBlock')) AND product_block_resource_types.resource_type_id = (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('v6_bgp_med')) + """)) + 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 ('RAndEPeerBlock'))) AND product_block_resource_types.resource_type_id = (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('v6_bgp_med')) + """)) + 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 ('v6_bgp_local_preference', 'v4_bgp_local_preference', 'v4_bgp_med', 'v6_bgp_med')) + """)) + conn.execute(sa.text(""" +DELETE FROM resource_types WHERE resource_types.resource_type IN ('v6_bgp_local_preference', 'v4_bgp_local_preference', 'v4_bgp_med', 'v6_bgp_med') + """)) diff --git a/gso/products/product_blocks/r_and_e_lhcone.py b/gso/products/product_blocks/r_and_e_lhcone.py index e4dda2e22..8b5248678 100644 --- a/gso/products/product_blocks/r_and_e_lhcone.py +++ b/gso/products/product_blocks/r_and_e_lhcone.py @@ -2,12 +2,14 @@ from orchestrator.domain.base import ProductBlockModel from orchestrator.types import SubscriptionLifecycle +from pydantic import NonNegativeInt from gso.products.product_blocks.l3_core_service import ( L3CoreServiceBlock, L3CoreServiceBlockInactive, L3CoreServiceBlockProvisioning, ) +from gso.utils.types.multi_exit_discriminator import MultiExitDiscriminator class RAndELHCOneBlockInactive( @@ -16,15 +18,27 @@ class RAndELHCOneBlockInactive( """An inactive R&E LHCONE product block. See `RAndELHCOneBlock`.""" l3_core: L3CoreServiceBlockInactive + v4_bgp_local_preference: NonNegativeInt = 100 + v4_bgp_med: MultiExitDiscriminator = "igp" + v6_bgp_local_preference: NonNegativeInt = 100 + v6_bgp_med: MultiExitDiscriminator = "igp" class RAndELHCOneBlockProvisioning(RAndELHCOneBlockInactive, lifecycle=[SubscriptionLifecycle.PROVISIONING]): """A provisioning R&E LHCONE product block. See `RAndELHCOneBlock`.""" l3_core: L3CoreServiceBlockProvisioning + v4_bgp_local_preference: NonNegativeInt + v4_bgp_med: MultiExitDiscriminator + v6_bgp_local_preference: NonNegativeInt + v6_bgp_med: MultiExitDiscriminator class RAndELHCOneBlock(RAndELHCOneBlockProvisioning, lifecycle=[SubscriptionLifecycle.ACTIVE]): """An active R&E LHCONE product block.""" l3_core: L3CoreServiceBlock + v4_bgp_local_preference: NonNegativeInt + v4_bgp_med: MultiExitDiscriminator + v6_bgp_local_preference: NonNegativeInt + v6_bgp_med: MultiExitDiscriminator diff --git a/gso/products/product_blocks/r_and_e_peer.py b/gso/products/product_blocks/r_and_e_peer.py index 8fbbeb84e..c83222d73 100644 --- a/gso/products/product_blocks/r_and_e_peer.py +++ b/gso/products/product_blocks/r_and_e_peer.py @@ -2,12 +2,14 @@ from orchestrator.domain.base import ProductBlockModel from orchestrator.types import SubscriptionLifecycle +from pydantic import NonNegativeInt from gso.products.product_blocks.l3_core_service import ( L3CoreServiceBlock, L3CoreServiceBlockInactive, L3CoreServiceBlockProvisioning, ) +from gso.utils.types.multi_exit_discriminator import MultiExitDiscriminator class RAndEPeerBlockInactive( @@ -16,15 +18,27 @@ class RAndEPeerBlockInactive( """An inactive R&E Peer product block. See `RAndEPeerBlock`.""" l3_core: L3CoreServiceBlockInactive + v4_bgp_local_preference: NonNegativeInt = 100 + v4_bgp_med: MultiExitDiscriminator = "igp" + v6_bgp_local_preference: NonNegativeInt = 100 + v6_bgp_med: MultiExitDiscriminator = "igp" class RAndEPeerBlockProvisioning(RAndEPeerBlockInactive, lifecycle=[SubscriptionLifecycle.PROVISIONING]): """A provisioning R&E Peer product block. See `RAndEPeerBlock`.""" l3_core: L3CoreServiceBlockProvisioning + v4_bgp_local_preference: NonNegativeInt + v4_bgp_med: MultiExitDiscriminator + v6_bgp_local_preference: NonNegativeInt + v6_bgp_med: MultiExitDiscriminator class RAndEPeerBlock(RAndEPeerBlockProvisioning, lifecycle=[SubscriptionLifecycle.ACTIVE]): """An active R&E Peer product block.""" l3_core: L3CoreServiceBlock + v4_bgp_local_preference: NonNegativeInt + v4_bgp_med: MultiExitDiscriminator + v6_bgp_local_preference: NonNegativeInt + v6_bgp_med: MultiExitDiscriminator diff --git a/gso/utils/shared_enums.py b/gso/utils/shared_enums.py index dfbf5361d..34599a0fe 100644 --- a/gso/utils/shared_enums.py +++ b/gso/utils/shared_enums.py @@ -41,6 +41,8 @@ class APType(strEnum): """Backup.""" LOAD_BALANCED = "LOAD_BALANCED" """Load-balanced.""" + IGNORE = "IGNORE" + """Ignored.""" class SBPType(strEnum): diff --git a/gso/utils/types/multi_exit_discriminator.py b/gso/utils/types/multi_exit_discriminator.py new file mode 100644 index 000000000..d894fd448 --- /dev/null +++ b/gso/utils/types/multi_exit_discriminator.py @@ -0,0 +1,28 @@ +"""Type definition for a BGP Multi Exit Discriminator.""" + +import contextlib +from typing import Annotated, Any + +from pydantic import AfterValidator, BeforeValidator + + +def _ensure_str(value: Any) -> Any: + if not isinstance(value, str): + return str(value) + return value + + +def _multi_exit_discriminator_valid(value: str) -> str: + with contextlib.suppress(ValueError): + int_value = int(value) + if int_value >= 0: + return value + + if value in {"igp", "min-igp"}: + return value + + msg = "Multi Exit Discriminator must be either a positive integer, 'igp', or 'min-igp'" + raise ValueError(msg) + + +MultiExitDiscriminator = Annotated[str, BeforeValidator(_ensure_str), AfterValidator(_multi_exit_discriminator_valid)] diff --git a/gso/workflows/l3_core_service/r_and_e_lhcone/create_imported_r_and_e_lhcone.py b/gso/workflows/l3_core_service/r_and_e_lhcone/create_imported_r_and_e_lhcone.py index a4447da01..f3f50c825 100644 --- a/gso/workflows/l3_core_service/r_and_e_lhcone/create_imported_r_and_e_lhcone.py +++ b/gso/workflows/l3_core_service/r_and_e_lhcone/create_imported_r_and_e_lhcone.py @@ -1,19 +1,42 @@ """A creation workflow for adding an existing Imported R&E LHCONE to the service database.""" from orchestrator import workflow +from orchestrator.forms import SubmitFormPage from orchestrator.targets import Target from orchestrator.types import SubscriptionLifecycle from orchestrator.workflow import StepList, begin, done, step from orchestrator.workflows.steps import resync, set_status, store_process_subscription +from pydantic import NonNegativeInt +from pydantic_forms.types import FormGenerator from gso.products import ProductName from gso.products.product_types.r_and_e_lhcone import ImportedRAndELHCOneInactive from gso.services.partners import get_partner_by_name from gso.services.subscriptions import get_product_id_by_name +from gso.utils.types.multi_exit_discriminator import MultiExitDiscriminator from gso.workflows.l3_core_service.base_create_imported_l3_core_service import ( - initial_input_form_generator, + ServiceBindingPort, initialize_subscription, ) +from gso.workflows.l3_core_service.r_and_e_lhcone.shared import update_r_and_e_lhcone_subscription_model +from gso.workflows.l3_core_service.shared import L3ProductNameType + + +def initial_input_form_generator() -> FormGenerator: + """Initial input form generator for creating a new imported R&E LHCOne subscription.""" + + class ImportL3CoreServiceForm(SubmitFormPage): + partner: str + service_binding_ports: list[ServiceBindingPort] + product_name: L3ProductNameType + v4_bgp_local_preference: NonNegativeInt = 100 + v4_bgp_med: MultiExitDiscriminator = "igp" + v6_bgp_local_preference: NonNegativeInt = 100 + v6_bgp_med: MultiExitDiscriminator = "igp" + + user_input = yield ImportL3CoreServiceForm + + return user_input.model_dump() @step("Create subscription") @@ -37,6 +60,7 @@ def create_imported_r_and_e_lhcone() -> StepList: >> create_subscription >> store_process_subscription(Target.CREATE) >> initialize_subscription + >> update_r_and_e_lhcone_subscription_model >> set_status(SubscriptionLifecycle.ACTIVE) >> resync >> done diff --git a/gso/workflows/l3_core_service/r_and_e_lhcone/create_r_and_e_lhcone.py b/gso/workflows/l3_core_service/r_and_e_lhcone/create_r_and_e_lhcone.py index 2063c415f..195c70bdb 100644 --- a/gso/workflows/l3_core_service/r_and_e_lhcone/create_r_and_e_lhcone.py +++ b/gso/workflows/l3_core_service/r_and_e_lhcone/create_r_and_e_lhcone.py @@ -1,14 +1,17 @@ """Create R&E LHCONE subscription workflow.""" +from orchestrator.forms import FormPage from orchestrator.targets import Target from orchestrator.types import SubscriptionLifecycle from orchestrator.workflow import StepList, begin, done, step, workflow from orchestrator.workflows.steps import resync, set_status, store_process_subscription from orchestrator.workflows.utils import wrap_create_initial_input_form -from pydantic_forms.types import State, UUIDstr +from pydantic import NonNegativeInt +from pydantic_forms.types import FormGenerator, State, UUIDstr from gso.products.product_types.r_and_e_lhcone import RAndELHCOneInactive from gso.services.lso_client import lso_interaction +from gso.utils.types.multi_exit_discriminator import MultiExitDiscriminator from gso.utils.workflow_steps import prompt_sharepoint_checklist_url, start_moodi, stop_moodi from gso.workflows.l3_core_service.base_create_l3_core_service import ( check_bgp_peers, @@ -16,12 +19,31 @@ from gso.workflows.l3_core_service.base_create_l3_core_service import ( create_new_sharepoint_checklist, deploy_bgp_peers_dry, deploy_bgp_peers_real, - initial_input_form_generator, initialize_subscription, provision_sbp_dry, provision_sbp_real, update_dns_records, ) +from gso.workflows.l3_core_service.base_create_l3_core_service import ( + initial_input_form_generator as base_initial_input_form_generator, +) +from gso.workflows.l3_core_service.r_and_e_lhcone.shared import update_r_and_e_lhcone_subscription_model + + +def initial_input_form_generator(product_name: str) -> FormGenerator: + """Initial input form generator for creating a new R&E LHCOne subscription.""" + initial_generator = base_initial_input_form_generator(product_name) + initial_user_input = yield from initial_generator + + # Additional R&E LHCOne step + class RAndELHCOneExtraForm(FormPage): + v4_bgp_local_preference: NonNegativeInt = 100 + v4_bgp_med: MultiExitDiscriminator = "igp" + v6_bgp_local_preference: NonNegativeInt = 100 + v6_bgp_med: MultiExitDiscriminator = "igp" + + r_and_e_lhcone_extra_form = yield RAndELHCOneExtraForm + return initial_user_input | r_and_e_lhcone_extra_form.model_dump() @step("Create subscription") @@ -44,6 +66,7 @@ def create_r_and_e_lhcone() -> StepList: >> create_subscription >> store_process_subscription(Target.CREATE) >> initialize_subscription + >> update_r_and_e_lhcone_subscription_model >> start_moodi() >> lso_interaction(provision_sbp_dry) >> lso_interaction(provision_sbp_real) diff --git a/gso/workflows/l3_core_service/r_and_e_lhcone/modify_r_and_e_lhcone.py b/gso/workflows/l3_core_service/r_and_e_lhcone/modify_r_and_e_lhcone.py index 7182a48b2..82bf3c71b 100644 --- a/gso/workflows/l3_core_service/r_and_e_lhcone/modify_r_and_e_lhcone.py +++ b/gso/workflows/l3_core_service/r_and_e_lhcone/modify_r_and_e_lhcone.py @@ -1,11 +1,16 @@ """Modification workflow for an R&E LHCONE subscription.""" from orchestrator import begin, conditional, done, workflow +from orchestrator.forms import FormPage from orchestrator.targets import Target from orchestrator.workflow import StepList from orchestrator.workflows.steps import resync, store_process_subscription, unsync from orchestrator.workflows.utils import wrap_modify_initial_input_form +from pydantic import NonNegativeInt +from pydantic_forms.types import FormGenerator, UUIDstr +from gso.products.product_types.r_and_e_lhcone import RAndELHCOne +from gso.utils.types.multi_exit_discriminator import MultiExitDiscriminator from gso.workflows.l3_core_service.base_modify_l3_core_service import ( Operation, create_new_sbp, @@ -13,11 +18,30 @@ from gso.workflows.l3_core_service.base_modify_l3_core_service import ( modify_existing_sbp, remove_old_sbp, ) +from gso.workflows.l3_core_service.r_and_e_lhcone.shared import update_r_and_e_lhcone_subscription_model + + +def modify_input_form_generator(subscription_id: UUIDstr) -> FormGenerator: + """Initial form generator for modifying the custom attributes of an existing IAS subscription.""" + initial_generator = initial_input_form_generator(subscription_id) + initial_user_input = yield from initial_generator + + subscription = RAndELHCOne.from_subscription(subscription_id) + + # Additional R&E LHCOne step + class RAndELHCOneExtraForm(FormPage): + v4_bgp_local_preference: NonNegativeInt = subscription.r_and_e_lhcone.v4_bgp_local_preference + v4_bgp_med: MultiExitDiscriminator = subscription.r_and_e_lhcone.v4_bgp_med + v6_bgp_local_preference: NonNegativeInt = subscription.r_and_e_lhcone.v6_bgp_local_preference + v6_bgp_med: MultiExitDiscriminator = subscription.r_and_e_lhcone.v6_bgp_med + + r_and_e_lhcone_extra_form = yield RAndELHCOneExtraForm + return initial_user_input | r_and_e_lhcone_extra_form.model_dump() @workflow( "Modify R&E LHCONE", - initial_input_form=wrap_modify_initial_input_form(initial_input_form_generator), + initial_input_form=wrap_modify_initial_input_form(modify_input_form_generator), target=Target.MODIFY, ) def modify_r_and_e_lhcone() -> StepList: @@ -30,6 +54,7 @@ def modify_r_and_e_lhcone() -> StepList: begin >> store_process_subscription(Target.MODIFY) >> unsync + >> update_r_and_e_lhcone_subscription_model >> access_port_is_added(create_new_sbp) >> access_port_is_removed(remove_old_sbp) >> access_port_is_modified(modify_existing_sbp) diff --git a/gso/workflows/l3_core_service/r_and_e_lhcone/shared.py b/gso/workflows/l3_core_service/r_and_e_lhcone/shared.py new file mode 100644 index 000000000..532eb69b2 --- /dev/null +++ b/gso/workflows/l3_core_service/r_and_e_lhcone/shared.py @@ -0,0 +1,25 @@ +"""Shared logic for R&E LHCOne service workflows.""" + +from orchestrator import step +from orchestrator.domain import SubscriptionModel +from pydantic import NonNegativeInt +from pydantic_forms.types import State + +from gso.utils.types.multi_exit_discriminator import MultiExitDiscriminator + + +@step("Update R&E LHCOne-specific attributes") +def update_r_and_e_lhcone_subscription_model( + subscription: SubscriptionModel, + v4_bgp_local_preference: NonNegativeInt, + v4_bgp_med: MultiExitDiscriminator, + v6_bgp_local_preference: NonNegativeInt, + v6_bgp_med: MultiExitDiscriminator, +) -> State: + """Update the subscription model of an R&E LHCOne subscription.""" + subscription.r_and_e_lhcone.v4_bgp_local_preference = v4_bgp_local_preference # type: ignore[attr-defined] + subscription.r_and_e_lhcone.v4_bgp_med = v4_bgp_med # type: ignore[attr-defined] + subscription.r_and_e_lhcone.v6_bgp_local_preference = v6_bgp_local_preference # type: ignore[attr-defined] + subscription.r_and_e_lhcone.v6_bgp_med = v6_bgp_med # type: ignore[attr-defined] + + return {"subscription": subscription} diff --git a/gso/workflows/l3_core_service/r_and_e_peer/create_imported_r_and_e_peer.py b/gso/workflows/l3_core_service/r_and_e_peer/create_imported_r_and_e_peer.py index 6e56e435e..6a7d87725 100644 --- a/gso/workflows/l3_core_service/r_and_e_peer/create_imported_r_and_e_peer.py +++ b/gso/workflows/l3_core_service/r_and_e_peer/create_imported_r_and_e_peer.py @@ -1,19 +1,42 @@ """A creation workflow for adding an existing Imported R&E Peer to the service database.""" from orchestrator import workflow +from orchestrator.forms import SubmitFormPage from orchestrator.targets import Target from orchestrator.types import SubscriptionLifecycle from orchestrator.workflow import StepList, begin, done, step from orchestrator.workflows.steps import resync, set_status, store_process_subscription +from pydantic import NonNegativeInt +from pydantic_forms.types import FormGenerator from gso.products import ProductName from gso.products.product_types.r_and_e_peer import ImportedRAndEPeerInactive from gso.services.partners import get_partner_by_name from gso.services.subscriptions import get_product_id_by_name +from gso.utils.types.multi_exit_discriminator import MultiExitDiscriminator from gso.workflows.l3_core_service.base_create_imported_l3_core_service import ( - initial_input_form_generator, + ServiceBindingPort, initialize_subscription, ) +from gso.workflows.l3_core_service.r_and_e_peer.shared import update_r_and_e_peer_subscription_model +from gso.workflows.l3_core_service.shared import L3ProductNameType + + +def initial_input_form_generator() -> FormGenerator: + """Initial input form generator for creating a new imported R&E Peer subscription.""" + + class ImportL3CoreServiceForm(SubmitFormPage): + partner: str + service_binding_ports: list[ServiceBindingPort] + product_name: L3ProductNameType + v4_bgp_local_preference: NonNegativeInt = 100 + v4_bgp_med: MultiExitDiscriminator = "igp" + v6_bgp_local_preference: NonNegativeInt = 100 + v6_bgp_med: MultiExitDiscriminator = "igp" + + user_input = yield ImportL3CoreServiceForm + + return user_input.model_dump() @step("Create subscription") @@ -37,6 +60,7 @@ def create_imported_r_and_e_peer() -> StepList: >> create_subscription >> store_process_subscription(Target.CREATE) >> initialize_subscription + >> update_r_and_e_peer_subscription_model >> set_status(SubscriptionLifecycle.ACTIVE) >> resync >> done diff --git a/gso/workflows/l3_core_service/r_and_e_peer/create_r_and_e_peer.py b/gso/workflows/l3_core_service/r_and_e_peer/create_r_and_e_peer.py index 93493e9d3..2ba1dab69 100644 --- a/gso/workflows/l3_core_service/r_and_e_peer/create_r_and_e_peer.py +++ b/gso/workflows/l3_core_service/r_and_e_peer/create_r_and_e_peer.py @@ -1,14 +1,17 @@ """Create R&E Peer subscription workflow.""" +from orchestrator.forms import FormPage from orchestrator.targets import Target from orchestrator.types import SubscriptionLifecycle from orchestrator.workflow import StepList, begin, done, step, workflow from orchestrator.workflows.steps import resync, set_status, store_process_subscription from orchestrator.workflows.utils import wrap_create_initial_input_form -from pydantic_forms.types import State, UUIDstr +from pydantic import NonNegativeInt +from pydantic_forms.types import FormGenerator, State, UUIDstr from gso.products.product_types.r_and_e_peer import RAndEPeerInactive from gso.services.lso_client import lso_interaction +from gso.utils.types.multi_exit_discriminator import MultiExitDiscriminator from gso.utils.workflow_steps import prompt_sharepoint_checklist_url, start_moodi, stop_moodi from gso.workflows.l3_core_service.base_create_l3_core_service import ( check_bgp_peers, @@ -16,12 +19,31 @@ from gso.workflows.l3_core_service.base_create_l3_core_service import ( create_new_sharepoint_checklist, deploy_bgp_peers_dry, deploy_bgp_peers_real, - initial_input_form_generator, initialize_subscription, provision_sbp_dry, provision_sbp_real, update_dns_records, ) +from gso.workflows.l3_core_service.base_create_l3_core_service import ( + initial_input_form_generator as base_initial_input_form_generator, +) +from gso.workflows.l3_core_service.r_and_e_peer.shared import update_r_and_e_peer_subscription_model + + +def initial_input_form_generator(product_name: str) -> FormGenerator: + """Initial input form generator for creating a new R&E Peer subscription.""" + initial_generator = base_initial_input_form_generator(product_name) + initial_user_input = yield from initial_generator + + # Additional R&E Peer step + class RAndEPeerExtraForm(FormPage): + v4_bgp_local_preference: NonNegativeInt = 100 + v4_bgp_med: MultiExitDiscriminator = "igp" + v6_bgp_local_preference: NonNegativeInt = 100 + v6_bgp_med: MultiExitDiscriminator = "igp" + + r_and_e_peer_extra_form = yield RAndEPeerExtraForm + return initial_user_input | r_and_e_peer_extra_form.model_dump() @step("Create subscription") @@ -44,6 +66,7 @@ def create_r_and_e_peer() -> StepList: >> create_subscription >> store_process_subscription(Target.CREATE) >> initialize_subscription + >> update_r_and_e_peer_subscription_model >> start_moodi() >> lso_interaction(provision_sbp_dry) >> lso_interaction(provision_sbp_real) diff --git a/gso/workflows/l3_core_service/r_and_e_peer/modify_r_and_e_peer.py b/gso/workflows/l3_core_service/r_and_e_peer/modify_r_and_e_peer.py index f04009dfe..7ed008a07 100644 --- a/gso/workflows/l3_core_service/r_and_e_peer/modify_r_and_e_peer.py +++ b/gso/workflows/l3_core_service/r_and_e_peer/modify_r_and_e_peer.py @@ -1,11 +1,16 @@ """Modification workflow for an R&E Peer subscription.""" from orchestrator import begin, conditional, done, workflow +from orchestrator.forms import FormPage from orchestrator.targets import Target from orchestrator.workflow import StepList from orchestrator.workflows.steps import resync, store_process_subscription, unsync from orchestrator.workflows.utils import wrap_modify_initial_input_form +from pydantic import NonNegativeInt +from pydantic_forms.types import FormGenerator, UUIDstr +from gso.products.product_types.r_and_e_peer import RAndEPeer +from gso.utils.types.multi_exit_discriminator import MultiExitDiscriminator from gso.workflows.l3_core_service.base_modify_l3_core_service import ( Operation, create_new_sbp, @@ -13,11 +18,30 @@ from gso.workflows.l3_core_service.base_modify_l3_core_service import ( modify_existing_sbp, remove_old_sbp, ) +from gso.workflows.l3_core_service.r_and_e_peer.shared import update_r_and_e_peer_subscription_model + + +def modify_input_form_generator(subscription_id: UUIDstr) -> FormGenerator: + """Initial form generator for modifying the custom attributes of an existing IAS subscription.""" + initial_generator = initial_input_form_generator(subscription_id) + initial_user_input = yield from initial_generator + + subscription = RAndEPeer.from_subscription(subscription_id) + + # Additional R&E Peer step + class RAndEPeerExtraForm(FormPage): + v4_bgp_local_preference: NonNegativeInt = subscription.r_and_e_peer.v4_bgp_local_preference + v4_bgp_med: MultiExitDiscriminator = subscription.r_and_e_peer.v4_bgp_med + v6_bgp_local_preference: NonNegativeInt = subscription.r_and_e_peer.v6_bgp_local_preference + v6_bgp_med: MultiExitDiscriminator = subscription.r_and_e_peer.v6_bgp_med + + r_and_e_peer_extra_form = yield RAndEPeerExtraForm + return initial_user_input | r_and_e_peer_extra_form.model_dump() @workflow( "Modify R&E Peer", - initial_input_form=wrap_modify_initial_input_form(initial_input_form_generator), + initial_input_form=wrap_modify_initial_input_form(modify_input_form_generator), target=Target.MODIFY, ) def modify_r_and_e_peer() -> StepList: @@ -30,6 +54,7 @@ def modify_r_and_e_peer() -> StepList: begin >> store_process_subscription(Target.MODIFY) >> unsync + >> update_r_and_e_peer_subscription_model >> access_port_is_added(create_new_sbp) >> access_port_is_removed(remove_old_sbp) >> access_port_is_modified(modify_existing_sbp) diff --git a/gso/workflows/l3_core_service/r_and_e_peer/shared.py b/gso/workflows/l3_core_service/r_and_e_peer/shared.py new file mode 100644 index 000000000..d6af00f10 --- /dev/null +++ b/gso/workflows/l3_core_service/r_and_e_peer/shared.py @@ -0,0 +1,25 @@ +"""Shared logic for R&E Peer service workflows.""" + +from orchestrator import step +from orchestrator.domain import SubscriptionModel +from pydantic import NonNegativeInt +from pydantic_forms.types import State + +from gso.utils.types.multi_exit_discriminator import MultiExitDiscriminator + + +@step("Update R&E Peer-specific attributes") +def update_r_and_e_peer_subscription_model( + subscription: SubscriptionModel, + v4_bgp_local_preference: NonNegativeInt, + v4_bgp_med: MultiExitDiscriminator, + v6_bgp_local_preference: NonNegativeInt, + v6_bgp_med: MultiExitDiscriminator, +) -> State: + """Update the subscription model of an R&E Peer subscription.""" + subscription.r_and_e_peer.v4_bgp_local_preference = v4_bgp_local_preference # type: ignore[attr-defined] + subscription.r_and_e_peer.v4_bgp_med = v4_bgp_med # type: ignore[attr-defined] + subscription.r_and_e_peer.v6_bgp_local_preference = v6_bgp_local_preference # type: ignore[attr-defined] + subscription.r_and_e_peer.v6_bgp_med = v6_bgp_med # type: ignore[attr-defined] + + return {"subscription": subscription} diff --git a/test/utils/types/__init__.py b/test/utils/types/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/test/utils/types/test_multi_exit_discriminator.py b/test/utils/types/test_multi_exit_discriminator.py new file mode 100644 index 000000000..e24ffba7b --- /dev/null +++ b/test/utils/types/test_multi_exit_discriminator.py @@ -0,0 +1,30 @@ +import pytest +from pydantic import BaseModel, ValidationError + +from gso.utils.types.multi_exit_discriminator import MultiExitDiscriminator + + +class BGPDiscriminator(BaseModel): + bgp_med: MultiExitDiscriminator + + +@pytest.mark.parametrize( + ("input_value", "is_valid"), + [ + ("igp", True), + ("min-igp", True), + ("40", True), + ("0", True), + (43, True), + ("-74", False), + ("45.6", False), + (-91, False), + ("abc", False), + ], +) +def test_multi_exit_discriminator(input_value, is_valid): + if is_valid: + assert BGPDiscriminator(bgp_med=input_value).bgp_med == str(input_value) + else: + with pytest.raises(ValidationError): + BGPDiscriminator(bgp_med=input_value) diff --git a/test/workflows/l3_core_service/test_create_imported_l3_core_service.py b/test/workflows/l3_core_service/test_create_imported_l3_core_service.py index 21a00b73b..b6984fb90 100644 --- a/test/workflows/l3_core_service/test_create_imported_l3_core_service.py +++ b/test/workflows/l3_core_service/test_create_imported_l3_core_service.py @@ -72,6 +72,9 @@ def test_create_imported_l3_core_service_success(faker, partner_factory, edge_po } if product_name == ProductName.IAS: creation_form_input_data["ias_flavor"] = IASFlavor.IASGWS + elif product_name in {ProductName.R_AND_E_PEER, ProductName.R_AND_E_LHCONE}: + creation_form_input_data["v6_bgp_local_preference"] = 9999 + creation_form_input_data["v6_bgp_med"] = 8888 result, _, _ = run_workflow(f"{L3_CREAT_IMPORTED_WF_MAP[product_name]}", creation_form_input_data) state = extract_state(result) subscription = SubscriptionModel.from_subscription(state["subscription_id"]) @@ -81,3 +84,13 @@ def test_create_imported_l3_core_service_success(faker, partner_factory, edge_po if product_name == ProductName.IAS: assert subscription.ias.ias_flavor == IASFlavor.IASGWS + elif product_name == ProductName.R_AND_E_PEER: + assert subscription.r_and_e_peer.v4_bgp_local_preference == 100 + assert subscription.r_and_e_peer.v4_bgp_med == "igp" + assert subscription.r_and_e_peer.v6_bgp_local_preference == 9999 + assert subscription.r_and_e_peer.v6_bgp_med == "8888" + elif product_name == ProductName.R_AND_E_LHCONE: + assert subscription.r_and_e_lhcone.v4_bgp_local_preference == 100 + assert subscription.r_and_e_lhcone.v4_bgp_med == "igp" + assert subscription.r_and_e_lhcone.v6_bgp_local_preference == 9999 + assert subscription.r_and_e_lhcone.v6_bgp_med == "8888" 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 b5865a74e..6edf078fe 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 @@ -89,6 +89,12 @@ def test_create_l3_core_service_success( "ias_flavor": IASFlavor.IASGWS, } form_input_data.append(extra_ias_data) + elif product_name in {ProductName.R_AND_E_PEER, ProductName.R_AND_E_LHCONE}: + extra_r_and_e_data = { + "v6_bgp_local_preference": 5555, + "v6_bgp_med": "min-igp", + } + form_input_data.append(extra_r_and_e_data) lso_interaction_count = 7 @@ -117,3 +123,8 @@ def test_create_l3_core_service_success( if product_name == ProductName.IAS: assert subscription.ias.ias_flavor == IASFlavor.IASGWS + elif product_name == ProductName.R_AND_E_PEER: + assert subscription.r_and_e_peer.v4_bgp_local_preference == 100 + assert subscription.r_and_e_peer.v4_bgp_med == "igp" + assert subscription.r_and_e_peer.v6_bgp_local_preference == 5555 + assert subscription.r_and_e_peer.v6_bgp_med == "min-igp" 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 b3421c79e..e9a7df9d3 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 @@ -26,6 +26,12 @@ def test_modify_l3_core_service_remove_edge_port_success(faker, l3_core_service_ "ias_flavor": IASFlavor.IASGWS, } input_form_data.append(extra_ias_data) + elif product_name in {ProductName.R_AND_E_PEER, ProductName.R_AND_E_LHCONE}: + extra_r_and_e_data = { + "v6_bgp_local_preference": 5555, + "v6_bgp_med": "min-igp", + } + input_form_data.append(extra_r_and_e_data) result, _, _ = run_workflow(L3_MODIFICATION_WF_MAP[product_name], input_form_data) @@ -36,6 +42,16 @@ def test_modify_l3_core_service_remove_edge_port_success(faker, l3_core_service_ assert ap_list[0].ap_type == APType.BACKUP if product_name == ProductName.IAS: assert subscription.ias.ias_flavor == IASFlavor.IASGWS + elif product_name == ProductName.R_AND_E_PEER: + assert subscription.r_and_e_peer.v4_bgp_local_preference == 100 + assert subscription.r_and_e_peer.v4_bgp_med == "igp" + assert subscription.r_and_e_peer.v6_bgp_local_preference == 5555 + assert subscription.r_and_e_peer.v6_bgp_med == "min-igp" + elif product_name == ProductName.R_AND_E_LHCONE: + assert subscription.r_and_e_lhcone.v4_bgp_local_preference == 100 + assert subscription.r_and_e_lhcone.v4_bgp_med == "igp" + assert subscription.r_and_e_lhcone.v6_bgp_local_preference == 5555 + assert subscription.r_and_e_lhcone.v6_bgp_med == "min-igp" @pytest.mark.parametrize("product_name", L3_PRODUCT_NAMES) @@ -87,6 +103,12 @@ def test_modify_l3_core_service_add_new_edge_port_success( "ias_flavor": IASFlavor.IASGWS, } input_form_data.append(extra_ias_data) + elif product_name in {ProductName.R_AND_E_PEER, ProductName.R_AND_E_LHCONE}: + extra_r_and_e_data = { + "v6_bgp_local_preference": 5555, + "v6_bgp_med": "123123", + } + input_form_data.append(extra_r_and_e_data) result, _, _ = run_workflow(L3_MODIFICATION_WF_MAP[product_name], input_form_data) @@ -104,6 +126,16 @@ def test_modify_l3_core_service_add_new_edge_port_success( assert len(ap_list) == 3 if product_name == ProductName.IAS: assert subscription.ias.ias_flavor == IASFlavor.IASGWS + elif product_name == ProductName.R_AND_E_PEER: + assert subscription.r_and_e_peer.v4_bgp_local_preference == 100 + assert subscription.r_and_e_peer.v4_bgp_med == "igp" + assert subscription.r_and_e_peer.v6_bgp_local_preference == 5555 + assert subscription.r_and_e_peer.v6_bgp_med == "123123" + elif product_name == ProductName.R_AND_E_LHCONE: + assert subscription.r_and_e_lhcone.v4_bgp_local_preference == 100 + assert subscription.r_and_e_lhcone.v4_bgp_med == "igp" + assert subscription.r_and_e_lhcone.v6_bgp_local_preference == 5555 + assert subscription.r_and_e_lhcone.v6_bgp_med == "123123" @pytest.fixture() @@ -153,7 +185,7 @@ def sbp_input_form_data(faker): @pytest.mark.parametrize("product_name", L3_PRODUCT_NAMES) @pytest.mark.workflow() -def test_modify_l3_core_service_modify_edge_port_success( +def test_modify_l3_core_service_modify_edge_port_success( # noqa: PLR0915 faker, l3_core_service_subscription_factory, product_name, sbp_input_form_data ): subscription = l3_core_service_subscription_factory(product_name=product_name) @@ -171,6 +203,12 @@ def test_modify_l3_core_service_modify_edge_port_success( "ias_flavor": IASFlavor.IASGWS, } input_form_data.append(extra_ias_data) + elif product_name in {ProductName.R_AND_E_PEER, ProductName.R_AND_E_LHCONE}: + extra_r_and_e_data = { + "v6_bgp_local_preference": 5555, + "v6_bgp_med": "min-igp", + } + input_form_data.append(extra_r_and_e_data) result, _, _ = run_workflow(L3_MODIFICATION_WF_MAP[product_name], input_form_data) @@ -223,3 +261,13 @@ def test_modify_l3_core_service_modify_edge_port_success( if product_name == ProductName.IAS: assert subscription.ias.ias_flavor == IASFlavor.IASGWS + elif product_name == ProductName.R_AND_E_PEER: + assert subscription.r_and_e_peer.v4_bgp_local_preference == 100 + assert subscription.r_and_e_peer.v4_bgp_med == "igp" + assert subscription.r_and_e_peer.v6_bgp_local_preference == 5555 + assert subscription.r_and_e_peer.v6_bgp_med == "min-igp" + elif product_name == ProductName.R_AND_E_LHCONE: + assert subscription.r_and_e_lhcone.v4_bgp_local_preference == 100 + assert subscription.r_and_e_lhcone.v4_bgp_med == "igp" + assert subscription.r_and_e_lhcone.v6_bgp_local_preference == 5555 + assert subscription.r_and_e_lhcone.v6_bgp_med == "min-igp" -- GitLab