From 459af11f3d259b94aad6786bf357eefd25178b77 Mon Sep 17 00:00:00 2001 From: Karel van Klink <karel.vanklink@geant.org> Date: Thu, 12 Oct 2023 16:48:52 +0200 Subject: [PATCH] update IPtrunk product block where the list of LAG members is a separate product block --- gso/migrations/env.py | 6 +- ...0-11_394dc60d5c02_modify_ip_trunk_model.py | 86 ++++++++++++++ gso/products/product_blocks/iptrunk.py | 13 +- gso/services/netbox_client.py | 4 +- gso/utils/types/imports.py | 37 ++---- gso/workflows/iptrunk/create_iptrunk.py | 83 +++++++------ .../iptrunk/modify_trunk_interface.py | 66 ++++++----- gso/workflows/iptrunk/utils.py | 10 ++ gso/workflows/router/create_router.py | 2 +- gso/workflows/tasks/import_iptrunk.py | 20 ++-- pyproject.toml | 2 + test/fixtures.py | 13 +- test/imports/test_imports.py | 112 +++++++----------- test/workflows/iptrunk/test_create_iptrunk.py | 20 ++-- .../iptrunk/test_modify_trunk_interface.py | 36 +----- 15 files changed, 279 insertions(+), 231 deletions(-) create mode 100644 gso/migrations/versions/2023-10-11_394dc60d5c02_modify_ip_trunk_model.py diff --git a/gso/migrations/env.py b/gso/migrations/env.py index 2f41e241..a1f9b9fc 100644 --- a/gso/migrations/env.py +++ b/gso/migrations/env.py @@ -1,11 +1,11 @@ import logging import os -from alembic import context -from sqlalchemy import engine_from_config, pool import orchestrator +from alembic import context from orchestrator.db.database import BaseModel from orchestrator.settings import app_settings +from sqlalchemy import engine_from_config, pool # this is the Alembic Config object, which provides # access to the values within the .ini file in use. @@ -61,7 +61,7 @@ def run_migrations_online() -> None: # this callback is used to prevent an auto-migration from being generated # when there are no changes to the schema # reference: http://alembic.zzzcomputing.com/en/latest/cookbook.html - def process_revision_directives(context, revision, directives): # type: ignore + def process_revision_directives(context, revision, directives): # type: ignore[no-untyped-def] if getattr(config.cmd_opts, "autogenerate", False): script = directives[0] if script.upgrade_ops.is_empty(): diff --git a/gso/migrations/versions/2023-10-11_394dc60d5c02_modify_ip_trunk_model.py b/gso/migrations/versions/2023-10-11_394dc60d5c02_modify_ip_trunk_model.py new file mode 100644 index 00000000..ce76bb6d --- /dev/null +++ b/gso/migrations/versions/2023-10-11_394dc60d5c02_modify_ip_trunk_model.py @@ -0,0 +1,86 @@ +"""Modify IP trunk model. + +Revision ID: 394dc60d5c02 +Revises: 01e42c100448 +Create Date: 2023-10-11 17:55:38.289125 + +""" +import sqlalchemy as sa +from alembic import op + +# revision identifiers, used by Alembic. +revision = '394dc60d5c02' +down_revision = '01e42c100448' +branch_labels = None +depends_on = None + + +def upgrade() -> 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 ('IptrunkSideBlock')) AND product_block_resource_types.resource_type_id = (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('iptrunk_side_ae_members_description')) + """)) + 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 ('IptrunkSideBlock'))) AND product_block_resource_types.resource_type_id = (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('iptrunk_side_ae_members_description')) + """)) + 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 ('IptrunkSideBlock')) AND product_block_resource_types.resource_type_id = (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('iptrunk_side_ae_members')) + """)) + 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 ('IptrunkSideBlock'))) AND product_block_resource_types.resource_type_id = (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('iptrunk_side_ae_members')) + """)) + 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 ('iptrunk_side_ae_members_description', 'iptrunk_side_ae_members')) + """)) + conn.execute(sa.text(""" +DELETE FROM resource_types WHERE resource_types.resource_type IN ('iptrunk_side_ae_members_description', 'iptrunk_side_ae_members') + """)) + conn.execute(sa.text(""" +INSERT INTO product_blocks (name, description, tag, status) VALUES ('IptrunkInterfaceBlock', 'Interface in a LAG as part of an IP trunk', 'IPTINT', 'active') RETURNING product_blocks.product_block_id + """)) + conn.execute(sa.text(""" +INSERT INTO resource_types (resource_type, description) VALUES ('interface_description', 'Description of a LAG interface') RETURNING resource_types.resource_type_id + """)) + conn.execute(sa.text(""" +INSERT INTO resource_types (resource_type, description) VALUES ('interface_name', 'Interface name of a LAG member') RETURNING resource_types.resource_type_id + """)) + conn.execute(sa.text(""" +INSERT INTO product_block_relations (in_use_by_id, depends_on_id) VALUES ((SELECT product_blocks.product_block_id FROM product_blocks WHERE product_blocks.name IN ('IptrunkSideBlock')), (SELECT product_blocks.product_block_id FROM product_blocks WHERE product_blocks.name IN ('IptrunkInterfaceBlock'))) + """)) + 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 ('IptrunkInterfaceBlock')), (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('interface_description'))) + """)) + 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 ('IptrunkInterfaceBlock')), (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('interface_name'))) + """)) + + +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 ('IptrunkInterfaceBlock')) AND product_block_resource_types.resource_type_id = (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('interface_description')) + """)) + 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 ('IptrunkInterfaceBlock'))) AND product_block_resource_types.resource_type_id = (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('interface_description')) + """)) + 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 ('IptrunkInterfaceBlock')) AND product_block_resource_types.resource_type_id = (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('interface_name')) + """)) + 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 ('IptrunkInterfaceBlock'))) AND product_block_resource_types.resource_type_id = (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('interface_name')) + """)) + 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 ('interface_description', 'interface_name')) + """)) + conn.execute(sa.text(""" +DELETE FROM resource_types WHERE resource_types.resource_type IN ('interface_description', 'interface_name') + """)) + conn.execute(sa.text(""" +DELETE FROM product_block_relations WHERE product_block_relations.in_use_by_id IN (SELECT product_blocks.product_block_id FROM product_blocks WHERE product_blocks.name IN ('IptrunkSideBlock')) AND product_block_relations.depends_on_id IN (SELECT product_blocks.product_block_id FROM product_blocks WHERE product_blocks.name IN ('IptrunkInterfaceBlock')) + """)) + conn.execute(sa.text(""" +DELETE FROM subscription_instances WHERE subscription_instances.product_block_id IN (SELECT product_blocks.product_block_id FROM product_blocks WHERE product_blocks.name IN ('IptrunkInterfaceBlock')) + """)) + conn.execute(sa.text(""" +DELETE FROM product_blocks WHERE product_blocks.name IN ('IptrunkInterfaceBlock') + """)) diff --git a/gso/products/product_blocks/iptrunk.py b/gso/products/product_blocks/iptrunk.py index 787648b2..4f0a4ed5 100644 --- a/gso/products/product_blocks/iptrunk.py +++ b/gso/products/product_blocks/iptrunk.py @@ -6,7 +6,6 @@ from typing import TypeVar from orchestrator.domain.base import ProductBlockModel from orchestrator.forms.validators import UniqueConstrainedList from orchestrator.types import SubscriptionLifecycle, strEnum -from pydantic import Field from gso.products.product_blocks.router import RouterBlock, RouterBlockInactive, RouterBlockProvisioning from gso.utils.types.phy_port import PhyPortCapacity @@ -20,6 +19,10 @@ class IptrunkType(strEnum): T = TypeVar("T", covariant=True) +class LAGMemberList(UniqueConstrainedList[T]): # type: ignore[type-var] + pass + + class IptrunkInterfaceBlockInactive( ProductBlockModel, lifecycle=[SubscriptionLifecycle.INITIAL], product_block_name="IptrunkInterfaceBlock" ): @@ -49,21 +52,21 @@ class IptrunkSideBlockInactive( iptrunk_side_node: RouterBlockInactive iptrunk_side_ae_iface: str | None = None iptrunk_side_ae_geant_a_sid: str | None = None - iptrunk_side_ae_members: list[IptrunkInterfaceBlockInactive] = Field(default_factory=list) + iptrunk_side_ae_members: LAGMemberList[IptrunkInterfaceBlockInactive] class IptrunkSideBlockProvisioning(IptrunkSideBlockInactive, lifecycle=[SubscriptionLifecycle.PROVISIONING]): iptrunk_side_node: RouterBlockProvisioning iptrunk_side_ae_iface: str | None = None iptrunk_side_ae_geant_a_sid: str | None = None - iptrunk_side_ae_members: list[IptrunkInterfaceBlockProvisioning] = Field(default_factory=list) + iptrunk_side_ae_members: LAGMemberList[IptrunkInterfaceBlockProvisioning] class IptrunkSideBlock(IptrunkSideBlockProvisioning, lifecycle=[SubscriptionLifecycle.ACTIVE]): iptrunk_side_node: RouterBlock iptrunk_side_ae_iface: str | None = None iptrunk_side_ae_geant_a_sid: str | None = None - iptrunk_side_ae_members: list[IptrunkInterfaceBlock] = Field(default_factory=list) + iptrunk_side_ae_members: LAGMemberList[IptrunkInterfaceBlock] class IptrunkBlockInactive( @@ -79,7 +82,6 @@ class IptrunkBlockInactive( iptrunk_isis_metric: int | None = None iptrunk_ipv4_network: ipaddress.IPv4Network | None = None iptrunk_ipv6_network: ipaddress.IPv6Network | None = None - # iptrunk_sides: IptrunkSides[IptrunkSideBlockInactive] @@ -94,7 +96,6 @@ class IptrunkBlockProvisioning(IptrunkBlockInactive, lifecycle=[SubscriptionLife iptrunk_isis_metric: int | None = None iptrunk_ipv4_network: ipaddress.IPv4Network | None = None iptrunk_ipv6_network: ipaddress.IPv6Network | None = None - # iptrunk_sides: IptrunkSides[IptrunkSideBlockProvisioning] diff --git a/gso/services/netbox_client.py b/gso/services/netbox_client.py index d583448c..28774d24 100644 --- a/gso/services/netbox_client.py +++ b/gso/services/netbox_client.py @@ -101,7 +101,9 @@ class NetboxClient: # First get manufacturer id manufacturer_id = int(self.netbox.dcim.manufacturers.get(name=manufacturer).id) - device_type = DeviceType(**{"manufacturer": manufacturer_id, "model": model, "slug": slug}) # type: ignore + device_type = DeviceType( + **{"manufacturer": manufacturer_id, "model": model, "slug": slug} # type: ignore[arg-type] + ) return self.netbox.dcim.device_types.create(dict(device_type)) def create_device_role(self, name: str, slug: str) -> DeviceRole: diff --git a/gso/utils/types/imports.py b/gso/utils/types/imports.py index d0ce27c0..7c13292c 100644 --- a/gso/utils/types/imports.py +++ b/gso/utils/types/imports.py @@ -10,6 +10,7 @@ from gso.products.product_blocks.site import SiteTier from gso.services import subscriptions from gso.services.crm import CustomerNotFoundError, get_customer_by_name from gso.utils.types.phy_port import PhyPortCapacity +from gso.workflows.iptrunk.utils import LAGMember class ImportResponseModel(BaseModel): @@ -54,16 +55,14 @@ class IptrunkImportModel(BaseModel): iptrunk_description: str iptrunk_speed: PhyPortCapacity iptrunk_minimum_links: int - iptrunk_sideA_node_id: str - iptrunk_sideA_ae_iface: str - iptrunk_sideA_ae_geant_a_sid: str - iptrunk_sideA_ae_members: list[str] - iptrunk_sideA_ae_members_descriptions: list[str] - iptrunk_sideB_node_id: str - iptrunk_sideB_ae_iface: str - iptrunk_sideB_ae_geant_a_sid: str - iptrunk_sideB_ae_members: list[str] - iptrunk_sideB_ae_members_descriptions: list[str] + side_a_node_id: str + side_a_ae_iface: str + side_a_ae_geant_a_sid: str + side_a_ae_members: list[LAGMember] + side_b_node_id: str + side_b_ae_iface: str + side_b_ae_geant_a_sid: str + side_b_ae_members: list[LAGMember] iptrunk_ipv4_network: ipaddress.IPv4Network iptrunk_ipv6_network: ipaddress.IPv6Network @@ -83,14 +82,14 @@ class IptrunkImportModel(BaseModel): return value - @validator("iptrunk_sideA_node_id", "iptrunk_sideB_node_id") + @validator("side_a_node_id", "side_b_node_id") def check_if_router_side_is_available(cls, value: str) -> str: if value not in cls._get_active_routers(): raise ValueError("Router not found") return value - @validator("iptrunk_sideA_ae_members", "iptrunk_sideB_ae_members") + @validator("side_a_ae_members", "side_b_ae_members") def check_side_uniqueness(cls, value: list[str]) -> list[str]: if len(value) != len(set(value)): raise ValueError("Items must be unique") @@ -100,26 +99,16 @@ class IptrunkImportModel(BaseModel): @root_validator def check_members(cls, values: dict[str, Any]) -> dict[str, Any]: min_links = values["iptrunk_minimum_links"] - side_a_members = values.get("iptrunk_sideA_ae_members", []) - side_a_descriptions = values.get("iptrunk_sideA_ae_members_descriptions", []) - side_b_members = values.get("iptrunk_sideB_ae_members", []) - side_b_descriptions = values.get("iptrunk_sideB_ae_members_descriptions", []) + side_a_members = values.get("side_a_ae_members", {}) + side_b_members = values.get("side_b_ae_members", {}) len_a = len(side_a_members) - len_a_desc = len(side_a_descriptions) len_b = len(side_b_members) - len_b_desc = len(side_b_descriptions) if len_a < min_links: raise ValueError(f"Side A members should be at least {min_links} (iptrunk_minimum_links)") - if len_a != len_a_desc: - raise ValueError("Mismatch in Side A members and their descriptions") - if len_a != len_b: raise ValueError("Mismatch between Side A and B members") - if len_a != len_b_desc: - raise ValueError("Mismatch in Side B members and their descriptions") - return values diff --git a/gso/workflows/iptrunk/create_iptrunk.py b/gso/workflows/iptrunk/create_iptrunk.py index 154c89dd..8501fc8b 100644 --- a/gso/workflows/iptrunk/create_iptrunk.py +++ b/gso/workflows/iptrunk/create_iptrunk.py @@ -1,3 +1,5 @@ +from uuid import uuid4 + from orchestrator.forms import FormPage from orchestrator.forms.validators import Choice, ChoiceList, UniqueConstrainedList from orchestrator.targets import Target @@ -8,7 +10,7 @@ from orchestrator.workflows.utils import wrap_create_initial_input_form from pydantic import validator from pynetbox.models.dcim import Interfaces -from gso.products.product_blocks.iptrunk import IptrunkType +from gso.products.product_blocks.iptrunk import IptrunkInterfaceBlockInactive, IptrunkType from gso.products.product_blocks.router import RouterVendor from gso.products.product_types.iptrunk import IptrunkInactive, IptrunkProvisioning from gso.products.product_types.router import Router @@ -24,6 +26,7 @@ from gso.workflows.utils import ( validate_router_in_netbox, ) from gso.utils.types.phy_port import PhyPortCapacity +from gso.workflows.iptrunk.utils import LAGMember def initial_input_form_generator(product_name: str) -> FormGenerator: @@ -71,7 +74,7 @@ def initial_input_form_generator(product_name: str) -> FormGenerator: item_type = available_interfaces_choices(router_a, initial_user_input.iptrunk_speed) # type: ignore unique_items = True - class JuniperAeMembers(UniqueConstrainedList[str]): + class JuniperAeMembers(UniqueConstrainedList[LAGMember]): min_items = initial_user_input.iptrunk_minimum_links unique_items = True @@ -84,15 +87,15 @@ def initial_input_form_generator(product_name: str) -> FormGenerator: class Config: title = "Provide subscription details for side A of the trunk." - iptrunk_sideA_ae_iface: side_a_ae_iface # type: ignore[valid-type] - iptrunk_sideA_ae_geant_a_sid: str - iptrunk_sideA_ae_members: ae_members_side_a # type: ignore[valid-type] - iptrunk_sideA_ae_members_descriptions: AeMembersDescriptionListA + side_a_ae_iface: side_a_ae_iface # type: ignore[valid-type] + side_a_ae_geant_a_sid: str + side_a_ae_members: ae_members_side_a # type: ignore[valid-type] + side_a_ae_members_descriptions: AeMembersDescriptionListA user_input_side_a = yield CreateIptrunkSideAForm # Remove the selected router for side A, to prevent any loops - routers.pop(str(user_input_router_side_a.iptrunk_sideA_node_id.name)) - router_enum_b = Choice("Select a router", zip(routers.keys(), routers.items())) # type: ignore + routers.pop(str(user_input_side_a.side_a_node_id.name)) + router_enum_b = Choice("Select a router", zip(routers.keys(), routers.items())) # type: ignore[arg-type] class SelectRouterSideB(FormPage): class Config: @@ -109,25 +112,25 @@ def initial_input_form_generator(product_name: str) -> FormGenerator: side_b_ae_iface = available_lags_choices(router_b) or str class AeMembersListB(ChoiceList): - min_items = len(user_input_side_a.iptrunk_sideA_ae_members) - max_items = len(user_input_side_a.iptrunk_sideA_ae_members) + min_items = len(user_input_side_a.side_a_ae_members) + max_items = len(user_input_side_a.side_a_ae_members) item_type = available_interfaces_choices(router_b, initial_user_input.iptrunk_speed) # type: ignore unique_items = True ae_members_side_b = AeMembersListB if get_router_vendor(router_b) == RouterVendor.NOKIA else JuniperAeMembers - class AeMembersDescriptionListB(UniqueConstrainedList[str]): - min_items = len(user_input_side_a.iptrunk_sideA_ae_members) - max_items = len(user_input_side_a.iptrunk_sideA_ae_members) + class AeMembersDescriptionListB(UniqueConstrainedList[LAGMember]): + min_items = len(user_input_side_a.side_a_ae_members) + max_items = len(user_input_side_a.side_a_ae_members) class CreateIptrunkSideBForm(FormPage): class Config: title = "Provide subscription details for side B of the trunk." - iptrunk_sideB_ae_iface: side_b_ae_iface # type: ignore[valid-type] - iptrunk_sideB_ae_geant_a_sid: str - iptrunk_sideB_ae_members: ae_members_side_b # type: ignore[valid-type] - iptrunk_sideB_ae_members_descriptions: AeMembersDescriptionListB + side_b_ae_iface: side_b_ae_iface # type: ignore[valid-type] + side_b_ae_geant_a_sid: str + side_b_ae_members: ae_members_side_b # type: ignore[valid-type] + side_b_ae_members_descriptions: AeMembersDescriptionListB user_input_side_b = yield CreateIptrunkSideBForm @@ -170,35 +173,37 @@ def initialize_subscription( iptrunk_description: str, iptrunk_speed: PhyPortCapacity, iptrunk_minimum_links: int, - iptrunk_sideA_node_id: str, - iptrunk_sideA_ae_iface: str, - iptrunk_sideA_ae_geant_a_sid: str, - iptrunk_sideA_ae_members: list[str], - iptrunk_sideA_ae_members_descriptions: list[str], - iptrunk_sideB_node_id: str, - iptrunk_sideB_ae_iface: str, - iptrunk_sideB_ae_geant_a_sid: str, - iptrunk_sideB_ae_members: list[str], - iptrunk_sideB_ae_members_descriptions: list[str], + side_a_node_id: str, + side_a_ae_iface: str, + side_a_ae_geant_a_sid: str, + side_a_ae_members: list[dict], + side_b_node_id: str, + side_b_ae_iface: str, + side_b_ae_geant_a_sid: str, + side_b_ae_members: list[dict], ) -> State: subscription.iptrunk.geant_s_sid = geant_s_sid subscription.iptrunk.iptrunk_description = iptrunk_description subscription.iptrunk.iptrunk_type = iptrunk_type subscription.iptrunk.iptrunk_speed = iptrunk_speed - subscription.iptrunk.iptrunk_isis_metric = 9000 + subscription.iptrunk.iptrunk_isis_metric = 90000 subscription.iptrunk.iptrunk_minimum_links = iptrunk_minimum_links - subscription.iptrunk.iptrunk_sides[0].iptrunk_side_node = Router.from_subscription(iptrunk_sideA_node_id).router - subscription.iptrunk.iptrunk_sides[0].iptrunk_side_ae_iface = iptrunk_sideA_ae_iface - subscription.iptrunk.iptrunk_sides[0].iptrunk_side_ae_geant_a_sid = iptrunk_sideA_ae_geant_a_sid - subscription.iptrunk.iptrunk_sides[0].iptrunk_side_ae_members = iptrunk_sideA_ae_members - subscription.iptrunk.iptrunk_sides[0].iptrunk_side_ae_members_description = iptrunk_sideA_ae_members_descriptions - - subscription.iptrunk.iptrunk_sides[1].iptrunk_side_node = Router.from_subscription(iptrunk_sideB_node_id).router - subscription.iptrunk.iptrunk_sides[1].iptrunk_side_ae_iface = iptrunk_sideB_ae_iface - subscription.iptrunk.iptrunk_sides[1].iptrunk_side_ae_geant_a_sid = iptrunk_sideB_ae_geant_a_sid - subscription.iptrunk.iptrunk_sides[1].iptrunk_side_ae_members = iptrunk_sideB_ae_members - subscription.iptrunk.iptrunk_sides[1].iptrunk_side_ae_members_description = iptrunk_sideB_ae_members_descriptions + subscription.iptrunk.iptrunk_sides[0].iptrunk_side_node = Router.from_subscription(side_a_node_id).router + subscription.iptrunk.iptrunk_sides[0].iptrunk_side_ae_iface = side_a_ae_iface + subscription.iptrunk.iptrunk_sides[0].iptrunk_side_ae_geant_a_sid = side_a_ae_geant_a_sid + for member in side_a_ae_members: + subscription.iptrunk.iptrunk_sides[0].iptrunk_side_ae_members.append( + IptrunkInterfaceBlockInactive.new(subscription_id=uuid4(), **member) + ) + + subscription.iptrunk.iptrunk_sides[1].iptrunk_side_node = Router.from_subscription(side_b_node_id).router + subscription.iptrunk.iptrunk_sides[1].iptrunk_side_ae_iface = side_b_ae_iface + subscription.iptrunk.iptrunk_sides[1].iptrunk_side_ae_geant_a_sid = side_b_ae_geant_a_sid + for member in side_b_ae_members: + subscription.iptrunk.iptrunk_sides[1].iptrunk_side_ae_members.append( + IptrunkInterfaceBlockInactive.new(subscription_id=uuid4(), **member) + ) subscription.description = f"IP trunk, geant_s_sid:{geant_s_sid}" subscription = IptrunkProvisioning.from_other_lifecycle(subscription, SubscriptionLifecycle.PROVISIONING) diff --git a/gso/workflows/iptrunk/modify_trunk_interface.py b/gso/workflows/iptrunk/modify_trunk_interface.py index 0dcdbe96..0b2b37f4 100644 --- a/gso/workflows/iptrunk/modify_trunk_interface.py +++ b/gso/workflows/iptrunk/modify_trunk_interface.py @@ -1,4 +1,5 @@ import ipaddress +from uuid import uuid4 from orchestrator.forms import FormPage, ReadOnlyField from orchestrator.forms.validators import UniqueConstrainedList @@ -8,11 +9,12 @@ from orchestrator.workflow import StepList, done, init, step, workflow from orchestrator.workflows.steps import resync, store_process_subscription, unsync from orchestrator.workflows.utils import wrap_modify_initial_input_form -from gso.products.product_blocks.iptrunk import IptrunkType +from gso.products.product_blocks.iptrunk import IptrunkInterfaceBlock, IptrunkType from gso.products.product_types.iptrunk import Iptrunk from gso.services import provisioning_proxy from gso.services.provisioning_proxy import pp_interaction from gso.utils.types.phy_port import PhyPortCapacity +from gso.workflows.iptrunk.utils import LAGMember def initial_input_form_generator(subscription_id: UUIDstr) -> FormGenerator: @@ -31,38 +33,32 @@ def initial_input_form_generator(subscription_id: UUIDstr) -> FormGenerator: initial_user_input = yield ModifyIptrunkForm - class AeMembersListA(UniqueConstrainedList[str]): + class AeMembersListA(UniqueConstrainedList[LAGMember]): min_items = initial_user_input.iptrunk_minimum_links class ModifyIptrunkSideAForm(FormPage): class Config: title = "Provide subscription details for side A of the trunk." - iptrunk_sideA_node: str = ReadOnlyField(subscription.iptrunk.iptrunk_sides[0].iptrunk_side_node.router_fqdn) - iptrunk_sideA_ae_iface: str = ReadOnlyField(subscription.iptrunk.iptrunk_sides[0].iptrunk_side_ae_iface) - iptrunk_sideA_ae_geant_a_sid: str = subscription.iptrunk.iptrunk_sides[0].iptrunk_side_ae_geant_a_sid - iptrunk_sideA_ae_members: AeMembersListA = subscription.iptrunk.iptrunk_sides[0].iptrunk_side_ae_members - iptrunk_sideA_ae_members_descriptions: AeMembersListA = subscription.iptrunk.iptrunk_sides[ - 0 - ].iptrunk_side_ae_members_description + side_a_node: str = ReadOnlyField(subscription.iptrunk.iptrunk_sides[0].iptrunk_side_node.router_fqdn) + side_a_ae_iface: str = ReadOnlyField(subscription.iptrunk.iptrunk_sides[0].iptrunk_side_ae_iface) + side_a_ae_geant_a_sid: str = subscription.iptrunk.iptrunk_sides[0].iptrunk_side_ae_geant_a_sid + side_a_ae_members: AeMembersListA = subscription.iptrunk.iptrunk_sides[0].iptrunk_side_ae_members user_input_side_a = yield ModifyIptrunkSideAForm - class AeMembersListB(UniqueConstrainedList[str]): - min_items = len(user_input_side_a.iptrunk_sideA_ae_members) - max_items = len(user_input_side_a.iptrunk_sideA_ae_members) + class AeMembersListB(UniqueConstrainedList[LAGMember]): + min_items = len(user_input_side_a.side_a_ae_members) + max_items = len(user_input_side_a.side_a_ae_members) class ModifyIptrunkSideBForm(FormPage): class Config: title = "Provide subscription details for side B of the trunk." - iptrunk_sideB_node: str = ReadOnlyField(subscription.iptrunk.iptrunk_sides[1].iptrunk_side_node.router_fqdn) - iptrunk_sideB_ae_iface: str = ReadOnlyField(subscription.iptrunk.iptrunk_sides[1].iptrunk_side_ae_iface) - iptrunk_sideB_ae_geant_a_sid: str = subscription.iptrunk.iptrunk_sides[1].iptrunk_side_ae_geant_a_sid - iptrunk_sideB_ae_members: AeMembersListB = subscription.iptrunk.iptrunk_sides[1].iptrunk_side_ae_members - iptrunk_sideB_ae_members_descriptions: AeMembersListB = subscription.iptrunk.iptrunk_sides[ - 1 - ].iptrunk_side_ae_members_description + side_b_node: str = ReadOnlyField(subscription.iptrunk.iptrunk_sides[1].iptrunk_side_node.router_fqdn) + side_b_ae_iface: str = ReadOnlyField(subscription.iptrunk.iptrunk_sides[1].iptrunk_side_ae_iface) + side_b_ae_geant_a_sid: str = subscription.iptrunk.iptrunk_sides[1].iptrunk_side_ae_geant_a_sid + side_b_ae_members: AeMembersListB = subscription.iptrunk.iptrunk_sides[1].iptrunk_side_ae_members user_input_side_b = yield ModifyIptrunkSideBForm @@ -77,12 +73,10 @@ def modify_iptrunk_subscription( iptrunk_description: str, iptrunk_speed: PhyPortCapacity, iptrunk_minimum_links: int, - iptrunk_sideA_ae_geant_a_sid: str, - iptrunk_sideA_ae_members: list[str], - iptrunk_sideA_ae_members_descriptions: list[str], - iptrunk_sideB_ae_geant_a_sid: str, - iptrunk_sideB_ae_members: list[str], - iptrunk_sideB_ae_members_descriptions: list[str], + side_a_ae_geant_a_sid: str, + side_a_ae_members: list[dict], + side_b_ae_geant_a_sid: str, + side_b_ae_members: list[dict], ) -> State: subscription.iptrunk.geant_s_sid = geant_s_sid subscription.iptrunk.iptrunk_description = iptrunk_description @@ -90,13 +84,21 @@ def modify_iptrunk_subscription( subscription.iptrunk.iptrunk_speed = iptrunk_speed subscription.iptrunk.iptrunk_minimum_links = iptrunk_minimum_links - subscription.iptrunk.iptrunk_sides[0].iptrunk_side_ae_geant_a_sid = iptrunk_sideA_ae_geant_a_sid - subscription.iptrunk.iptrunk_sides[0].iptrunk_side_ae_members = iptrunk_sideA_ae_members - subscription.iptrunk.iptrunk_sides[0].iptrunk_side_ae_members_description = iptrunk_sideA_ae_members_descriptions - - subscription.iptrunk.iptrunk_sides[1].iptrunk_side_ae_geant_a_sid = iptrunk_sideB_ae_geant_a_sid - subscription.iptrunk.iptrunk_sides[1].iptrunk_side_ae_members = iptrunk_sideB_ae_members - subscription.iptrunk.iptrunk_sides[1].iptrunk_side_ae_members_description = iptrunk_sideB_ae_members_descriptions + subscription.iptrunk.iptrunk_sides[0].iptrunk_side_ae_geant_a_sid = side_a_ae_geant_a_sid + # Flush the old list of member interfaces + subscription.iptrunk.iptrunk_sides[0].iptrunk_side_ae_members.clear() + # And update the list to only include the new member interfaces + for member in side_a_ae_members: + subscription.iptrunk.iptrunk_sides[0].iptrunk_side_ae_members.append( + IptrunkInterfaceBlock.new(subscription_id=uuid4(), **member) + ) + + subscription.iptrunk.iptrunk_sides[1].iptrunk_side_ae_geant_a_sid = side_b_ae_geant_a_sid + subscription.iptrunk.iptrunk_sides[1].iptrunk_side_ae_members.clear() + for member in side_b_ae_members: + subscription.iptrunk.iptrunk_sides[1].iptrunk_side_ae_members.append( + IptrunkInterfaceBlock.new(subscription_id=uuid4(), **member) + ) subscription.description = f"IP trunk, geant_s_sid:{geant_s_sid}" diff --git a/gso/workflows/iptrunk/utils.py b/gso/workflows/iptrunk/utils.py index 690cfc58..57c11715 100644 --- a/gso/workflows/iptrunk/utils.py +++ b/gso/workflows/iptrunk/utils.py @@ -1,10 +1,20 @@ from orchestrator import step from orchestrator.types import State, UUIDstr +from pydantic import BaseModel from gso.products.product_types.iptrunk import Iptrunk from gso.services import provisioning_proxy +class LAGMember(BaseModel): + # TODO: validate interface name + interface_name: str + interface_description: str + + def __hash__(self) -> int: + return hash((self.interface_name, self.interface_description)) + + @step("[COMMIT] Set ISIS metric to 90000") def set_isis_to_90000(subscription: Iptrunk, process_id: UUIDstr, tt_number: str) -> State: old_isis_metric = subscription.iptrunk.iptrunk_isis_metric diff --git a/gso/workflows/router/create_router.py b/gso/workflows/router/create_router.py index 3d19684e..48011b04 100644 --- a/gso/workflows/router/create_router.py +++ b/gso/workflows/router/create_router.py @@ -160,7 +160,7 @@ def provision_router_real(subscription: RouterProvisioning, process_id: UUIDstr, def create_netbox_device(subscription: RouterProvisioning) -> State: if subscription.router.router_vendor == RouterVendor.NOKIA: NetboxClient().create_device( - subscription.router.router_fqdn, subscription.router.router_site.site_tier # type: ignore + subscription.router.router_fqdn, subscription.router.router_site.site_tier # type: ignore[arg-type, union-attr] ) return {"subscription": subscription, "label_text": "Creating NetBox device"} return {"subscription": subscription, "label_text": "Skipping NetBox device creation for Juniper router."} diff --git a/gso/workflows/tasks/import_iptrunk.py b/gso/workflows/tasks/import_iptrunk.py index f3769aac..8203a905 100644 --- a/gso/workflows/tasks/import_iptrunk.py +++ b/gso/workflows/tasks/import_iptrunk.py @@ -15,6 +15,7 @@ from gso.services import subscriptions from gso.services.crm import get_customer_by_name from gso.utils.types.phy_port import PhyPortCapacity from gso.workflows.iptrunk.create_iptrunk import initialize_subscription +from gso.workflows.iptrunk.utils import LAGMember def _generate_routers() -> dict[str, str]: @@ -42,17 +43,15 @@ def initial_input_form_generator() -> FormGenerator: iptrunk_speed: PhyPortCapacity iptrunk_minimum_links: int - iptrunk_sideA_node_id: RouterEnum # type: ignore[valid-type] - iptrunk_sideA_ae_iface: str - iptrunk_sideA_ae_geant_a_sid: str - iptrunk_sideA_ae_members: UniqueConstrainedList[str] - iptrunk_sideA_ae_members_descriptions: UniqueConstrainedList[str] + side_a_node_id: RouterEnum # type: ignore[valid-type] + side_a_ae_iface: str + side_a_ae_geant_a_sid: str + side_a_ae_members: UniqueConstrainedList[LAGMember] - iptrunk_sideB_node_id: RouterEnum # type: ignore[valid-type] - iptrunk_sideB_ae_iface: str - iptrunk_sideB_ae_geant_a_sid: str - iptrunk_sideB_ae_members: UniqueConstrainedList[str] - iptrunk_sideB_ae_members_descriptions: UniqueConstrainedList[str] + side_b_node_id: RouterEnum # type: ignore[valid-type] + side_b_ae_iface: str + side_b_ae_geant_a_sid: str + side_b_ae_members: UniqueConstrainedList[LAGMember] iptrunk_ipv4_network: ipaddress.IPv4Network iptrunk_ipv6_network: ipaddress.IPv6Network @@ -82,6 +81,7 @@ def update_ipam_stub_for_subscription( ) -> State: subscription.iptrunk.iptrunk_ipv4_network = iptrunk_ipv4_network subscription.iptrunk.iptrunk_ipv6_network = iptrunk_ipv6_network + subscription.iptrunk.iptrunk_ipv6_network = iptrunk_ipv6_network return {"subscription": subscription} diff --git a/pyproject.toml b/pyproject.toml index 78360777..19d22785 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -46,6 +46,8 @@ show_error_codes = true show_column_numbers = true # Suppress "note: By default the bodies of untyped functions are not checked" disable_error_code = "annotation-unchecked" +# Forbid the use of a generic "type: ignore" without specifying the exact error that is ignored +enable_error_code = "ignore-without-code" [tool.ruff] exclude = [ diff --git a/test/fixtures.py b/test/fixtures.py index 3b7df108..b7e95eb1 100644 --- a/test/fixtures.py +++ b/test/fixtures.py @@ -6,7 +6,7 @@ from orchestrator.domain import SubscriptionModel from orchestrator.types import SubscriptionLifecycle, UUIDstr from gso.products import ProductType -from gso.products.product_blocks.iptrunk import IptrunkSideBlock, IptrunkType +from gso.products.product_blocks.iptrunk import IptrunkInterfaceBlock, IptrunkSideBlock, IptrunkType from gso.products.product_blocks.router import RouterRole, RouterVendor from gso.products.product_blocks.site import SiteTier from gso.products.product_types.iptrunk import IptrunkInactive @@ -140,10 +140,13 @@ def iptrunk_side_subscription_factory(router_subscription_factory, faker): iptrunk_side_node = Router.from_subscription(iptrunk_side_node_id).router iptrunk_side_ae_iface = iptrunk_side_ae_iface or faker.pystr() iptrunk_side_ae_geant_a_sid = iptrunk_side_ae_geant_a_sid or faker.geant_sid() - iptrunk_side_ae_members = iptrunk_side_ae_members or [faker.network_interface(), faker.network_interface()] - iptrunk_side_ae_members_description = iptrunk_side_ae_members_description or [ - faker.sentence(), - faker.sentence(), + iptrunk_side_ae_members = iptrunk_side_ae_members or [ + IptrunkInterfaceBlock.new( + faker.uuid4(), interface_name=faker.network_interface(), interface_description=faker.sentence() + ), + IptrunkInterfaceBlock.new( + faker.uuid4(), interface_name=faker.network_interface(), interface_description=faker.sentence() + ), ] return IptrunkSideBlock.new( diff --git a/test/imports/test_imports.py b/test/imports/test_imports.py index 639ace3e..b2bc58b6 100644 --- a/test/imports/test_imports.py +++ b/test/imports/test_imports.py @@ -26,16 +26,18 @@ def iptrunk_data(router_subscription_factory, faker): "iptrunk_description": faker.sentence(), "iptrunk_speed": PhyPortCapacity.HUNDRED_GIGABIT_PER_SECOND, "iptrunk_minimum_links": 5, - "iptrunk_sideA_node_id": router_side_a, - "iptrunk_sideA_ae_iface": faker.pystr(), - "iptrunk_sideA_ae_geant_a_sid": faker.pystr(), - "iptrunk_sideA_ae_members": [faker.pystr() for _ in range(5)], - "iptrunk_sideA_ae_members_descriptions": [faker.sentence() for _ in range(5)], - "iptrunk_sideB_node_id": router_side_b, - "iptrunk_sideB_ae_iface": faker.pystr(), - "iptrunk_sideB_ae_geant_a_sid": faker.pystr(), - "iptrunk_sideB_ae_members": [faker.pystr() for _ in range(5)], - "iptrunk_sideB_ae_members_descriptions": [faker.sentence() for _ in range(5)], + "side_a_node_id": router_side_a, + "side_a_ae_iface": faker.pystr(), + "side_a_ae_geant_a_sid": faker.pystr(), + "side_a_ae_members": [ + {"interface_name": faker.network_interface(), "interface_description": faker.sentence()} for _ in range(5) + ], + "side_b_node_id": router_side_b, + "side_b_ae_iface": faker.pystr(), + "side_b_ae_geant_a_sid": faker.pystr(), + "side_b_ae_members": [ + {"interface_name": faker.network_interface(), "interface_description": faker.sentence()} for _ in range(5) + ], "iptrunk_ipv4_network": str(faker.ipv4_network()), "iptrunk_ipv6_network": str(faker.ipv6_network()), } @@ -43,13 +45,13 @@ def iptrunk_data(router_subscription_factory, faker): @pytest.fixture def mock_routers(iptrunk_data): - first_call = [iptrunk_data["iptrunk_sideA_node_id"], iptrunk_data["iptrunk_sideB_node_id"], str(uuid4())] + first_call = [iptrunk_data["side_a_node_id"], iptrunk_data["side_b_node_id"], str(uuid4())] side_effects = [ first_call, first_call, [ - (iptrunk_data["iptrunk_sideA_node_id"], "iptrunk_sideA_node_id description"), - (iptrunk_data["iptrunk_sideB_node_id"], "iptrunk_sideB_node_id description"), + (iptrunk_data["side_a_node_id"], "side_a_node_id description"), + (iptrunk_data["side_b_node_id"], "side_b_node_id description"), (str(uuid4()), "random description"), ], ] @@ -203,26 +205,40 @@ def test_import_iptrunk_invalid_router_id_side_a_and_b(mock_start_process, test_ assert response.status_code == 422 assert response.json() == { "detail": [ - {"loc": ["body", "iptrunk_sideA_node_id"], "msg": "Router not found", "type": "value_error"}, - {"loc": ["body", "iptrunk_sideB_node_id"], "msg": "Router not found", "type": "value_error"}, + {"loc": ["body", "side_a_node_id"], "msg": "Router not found", "type": "value_error"}, + {"loc": ["body", "side_b_node_id"], "msg": "Router not found", "type": "value_error"}, ] } @patch("gso.api.v1.imports._start_process") -def test_import_iptrunk_non_unique_members_side_a(mock_start_process, test_client, iptrunk_data, mock_routers): +def test_import_iptrunk_non_unique_members_side_a(mock_start_process, test_client, iptrunk_data, mock_routers, faker): mock_start_process.return_value = "123e4567-e89b-12d3-a456-426655440000" - iptrunk_data["iptrunk_sideA_ae_members"] = [5, 5, 5, 5, 5] - iptrunk_data["iptrunk_sideB_ae_members"] = [4, 4, 4, 5, 5] + repeat_interface_a = {"interface_name": faker.network_interface(), "interface_description": faker.sentence()} + repeat_interface_b = {"interface_name": faker.network_interface(), "interface_description": faker.sentence()} + iptrunk_data["side_a_ae_members"] = [ + repeat_interface_a, + repeat_interface_a, + repeat_interface_a, + repeat_interface_a, + repeat_interface_a, + ] + iptrunk_data["side_b_ae_members"] = [ + repeat_interface_b, + repeat_interface_a, + repeat_interface_a, + repeat_interface_b, + repeat_interface_b, + ] response = test_client.post(IPTRUNK_IMPORT_API_URL, json=iptrunk_data) assert response.status_code == 422 assert response.json() == { "detail": [ - {"loc": ["body", "iptrunk_sideA_ae_members"], "msg": "Items must be unique", "type": "value_error"}, - {"loc": ["body", "iptrunk_sideB_ae_members"], "msg": "Items must be unique", "type": "value_error"}, + {"loc": ["body", "side_a_ae_members"], "msg": "Items must be unique", "type": "value_error"}, + {"loc": ["body", "side_b_ae_members"], "msg": "Items must be unique", "type": "value_error"}, { "loc": ["body", "__root__"], "msg": "Side A members should be at least 5 (iptrunk_minimum_links)", @@ -233,12 +249,12 @@ def test_import_iptrunk_non_unique_members_side_a(mock_start_process, test_clien @patch("gso.api.v1.imports._start_process") -def test_iptrunk_import_fails_on_side_a_member_count_mismatch( +def test_import_iptrunk_fails_on_side_a_member_count_mismatch( mock_start_process, test_client, iptrunk_data, mock_routers ): mock_start_process.return_value = "123e4567-e89b-12d3-a456-426655440000" - iptrunk_data["iptrunk_sideA_ae_members"].remove(iptrunk_data["iptrunk_sideA_ae_members"][0]) + iptrunk_data["side_a_ae_members"].remove(iptrunk_data["side_a_ae_members"][0]) response = test_client.post(IPTRUNK_IMPORT_API_URL, json=iptrunk_data) @@ -255,36 +271,12 @@ def test_iptrunk_import_fails_on_side_a_member_count_mismatch( @patch("gso.api.v1.imports._start_process") -def test_iptrunk_import_fails_on_side_a_member_description_mismatch( - mock_start_process, test_client, iptrunk_data, mock_routers -): - mock_start_process.return_value = "123e4567-e89b-12d3-a456-426655440000" - - iptrunk_data["iptrunk_sideA_ae_members_descriptions"].remove( - iptrunk_data["iptrunk_sideA_ae_members_descriptions"][0] - ) - - response = test_client.post(IPTRUNK_IMPORT_API_URL, json=iptrunk_data) - - assert response.status_code == 422 - assert response.json() == { - "detail": [ - { - "loc": ["body", "__root__"], - "msg": "Mismatch in Side A members and their descriptions", - "type": "value_error", - } - ] - } - - -@patch("gso.api.v1.imports._start_process") -def test_iptrunk_import_fails_on_side_a_and_b_members_mismatch( +def test_import_iptrunk_fails_on_side_a_and_b_members_mismatch( mock_start_process, test_client, iptrunk_data, mock_routers ): mock_start_process.return_value = "123e4567-e89b-12d3-a456-426655440000" - iptrunk_data["iptrunk_sideB_ae_members"].remove(iptrunk_data["iptrunk_sideB_ae_members"][0]) + iptrunk_data["side_b_ae_members"].remove(iptrunk_data["side_b_ae_members"][0]) response = test_client.post(IPTRUNK_IMPORT_API_URL, json=iptrunk_data) @@ -292,27 +284,3 @@ def test_iptrunk_import_fails_on_side_a_and_b_members_mismatch( assert response.json() == { "detail": [{"loc": ["body", "__root__"], "msg": "Mismatch between Side A and B members", "type": "value_error"}] } - - -@patch("gso.api.v1.imports._start_process") -def test_iptrunk_import_fails_on_side_b_member_description_mismatch( - mock_start_process, test_client, iptrunk_data, mock_routers -): - mock_start_process.return_value = "123e4567-e89b-12d3-a456-426655440000" - - iptrunk_data["iptrunk_sideB_ae_members_descriptions"].remove( - iptrunk_data["iptrunk_sideB_ae_members_descriptions"][0] - ) - - response = test_client.post(IPTRUNK_IMPORT_API_URL, json=iptrunk_data) - - assert response.status_code == 422 - assert response.json() == { - "detail": [ - { - "loc": ["body", "__root__"], - "msg": "Mismatch in Side B members and their descriptions", - "type": "value_error", - } - ] - } diff --git a/test/workflows/iptrunk/test_create_iptrunk.py b/test/workflows/iptrunk/test_create_iptrunk.py index b9a1930e..cff8bed9 100644 --- a/test/workflows/iptrunk/test_create_iptrunk.py +++ b/test/workflows/iptrunk/test_create_iptrunk.py @@ -94,18 +94,22 @@ def input_form_wizard_data(router_subscription_factory, faker): } create_ip_trunk_side_a_router_name = {"iptrunk_sideA_node_id": router_side_a} create_ip_trunk_side_a_step = { - "iptrunk_sideA_ae_iface": "LAG1", - "iptrunk_sideA_ae_geant_a_sid": faker.pystr(), - "iptrunk_sideA_ae_members": ["Interface1", "Interface2"], - "iptrunk_sideA_ae_members_descriptions": ["Interface1 Description", "Interface2 Description"], + "side_a_node_id": router_side_a, + "side_a_ae_iface": faker.pystr(), + "side_a_ae_geant_a_sid": faker.pystr(), + "side_a_ae_members": [ + {"interface_name": faker.network_interface(), "interface_description": faker.sentence()} for _ in range(5) + ], } create_ip_trunk_side_b_router_name = {"iptrunk_sideB_node_id": router_side_b} create_ip_trunk_side_b_step = { - "iptrunk_sideB_ae_iface": "LAG1", - "iptrunk_sideB_ae_geant_a_sid": faker.pystr(), - "iptrunk_sideB_ae_members": ["Interface1", "Interface2"], - "iptrunk_sideB_ae_members_descriptions": ["Interface1 Description", "Interface2 Description"], + "side_b_node_id": router_side_b, + "side_b_ae_iface": faker.pystr(), + "side_b_ae_geant_a_sid": faker.pystr(), + "side_b_ae_members": [ + {"interface_name": faker.network_interface(), "interface_description": faker.sentence()} for _ in range(5) + ], } return [ diff --git a/test/workflows/iptrunk/test_modify_trunk_interface.py b/test/workflows/iptrunk/test_modify_trunk_interface.py index 452e7971..cf6c39a4 100644 --- a/test/workflows/iptrunk/test_modify_trunk_interface.py +++ b/test/workflows/iptrunk/test_modify_trunk_interface.py @@ -32,34 +32,12 @@ def test_iptrunk_modify_trunk_interface_success( new_side_a_sid = faker.geant_sid() new_side_a_ae_members = [ - faker.network_interface(), - faker.network_interface(), - faker.network_interface(), - faker.network_interface(), - faker.network_interface(), - ] - new_side_a_ae_descriptions = [ - faker.sentence(), - faker.sentence(), - faker.sentence(), - faker.sentence(), - faker.sentence(), + {"interface_name": faker.network_interface(), "interface_description": faker.sentence()} for _ in range(5) ] new_side_b_sid = faker.geant_sid() new_side_b_ae_members = [ - faker.network_interface(), - faker.network_interface(), - faker.network_interface(), - faker.network_interface(), - faker.network_interface(), - ] - new_side_b_ae_descriptions = [ - faker.sentence(), - faker.sentence(), - faker.sentence(), - faker.sentence(), - faker.sentence(), + {"interface_name": faker.network_interface(), "interface_description": faker.sentence()} for _ in range(5) ] # Run workflow @@ -74,14 +52,12 @@ def test_iptrunk_modify_trunk_interface_success( "iptrunk_minimum_links": new_link_count, }, { - "iptrunk_sideA_ae_geant_a_sid": new_side_a_sid, - "iptrunk_sideA_ae_members": new_side_a_ae_members, - "iptrunk_sideA_ae_members_descriptions": new_side_a_ae_descriptions, + "side_a_ae_geant_a_sid": new_side_a_sid, + "side_a_ae_members": new_side_a_ae_members, }, { - "iptrunk_sideB_ae_geant_a_sid": new_side_b_sid, - "iptrunk_sideB_ae_members": new_side_b_ae_members, - "iptrunk_sideB_ae_members_descriptions": new_side_b_ae_descriptions, + "side_b_ae_geant_a_sid": new_side_b_sid, + "side_b_ae_members": new_side_b_ae_members, }, ] -- GitLab