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 0000000000000000000000000000000000000000..188bcd79a088d6321679189522c9510d780e71a3 --- /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 e4dda2e22a52953ddf2dcf333640bed98a9ef76e..8b5248678e65b572d18b6ed273aa4c6d0936b51b 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 8fbbeb84e62ee88b944eeb4782ee1800d723a405..c83222d7371abebedae61e6e901feb39c6416ba2 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 dfbf5361d696896f7c14746ff224032c7e973fc1..34599a0fe3585b4de9b020f4a245f53bfd7baefc 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 0000000000000000000000000000000000000000..d894fd448f07c4516b47f3ffdb8da2a635586a6b --- /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 a4447da019a640d0c0832ebf8f5baa5be0a1a2c9..f3f50c825fa6440004cab5e26a62f90586a05ec5 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 2063c415f9f2ed1e6cb49a3b32ea4b5fabefecc7..195c70bdb84c0a8a3fa551569336140db1798d0d 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 7182a48b221894630cd96da21d426ad1ea19dceb..82bf3c71bfeab4a6fda595cc550bf0a96aceaff9 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 0000000000000000000000000000000000000000..532eb69b267549899810d07c22a4f50fd7038105 --- /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 6e56e435edefda5fcfd09cab33ad291483093898..6a7d87725d382c6ff343f76d860b2984454ab1d9 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 93493e9d3749b6b7ff7811914e5d90189f174b5e..2ba1dab69d5db4294a6da5d5e059fda95492b8d0 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 f04009dfe04e2e9c7fac2c1c0e766baebecf7886..7ed008a073f4361a70be110ee8043ae3056ec17d 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 0000000000000000000000000000000000000000..d6af00f10cc2a35cb6816e2c0086b04fd727754c --- /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 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 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 0000000000000000000000000000000000000000..e24ffba7bb8e54f37de1ea7e2075f9c19f763ff5 --- /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 21a00b73bbd76383a94b4e3985f8e6f06388544c..b6984fb90fb4af6b510da1c39a8cde3203857e7f 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 b5865a74e417d2002aa666d030be5f956e456742..6edf078fe4bffb344399ecf5d87cc200cfc2a82f 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 b3421c79e0089cb7a05744bcc95053973a31a5a3..e9a7df9d36f5888a9481b34aff3e3e30e606f1fc 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"