Skip to content
Snippets Groups Projects
Verified Commit dc24f4f8 authored by Karel van Klink's avatar Karel van Klink :smiley_cat:
Browse files

update IPtrunk product block where the list of LAG members is a separate product block

parent 438316c0
No related branches found
No related tags found
1 merge request!84Rework the IPtrunk model
This commit is part of merge request !83. Comments created here will be created in the context of that merge request.
Showing
with 276 additions and 232 deletions
import logging import logging
import os import os
from alembic import context
from sqlalchemy import engine_from_config, pool
import orchestrator import orchestrator
from alembic import context
from orchestrator.db.database import BaseModel from orchestrator.db.database import BaseModel
from orchestrator.settings import app_settings from orchestrator.settings import app_settings
from sqlalchemy import engine_from_config, pool
# this is the Alembic Config object, which provides # this is the Alembic Config object, which provides
# access to the values within the .ini file in use. # access to the values within the .ini file in use.
...@@ -61,7 +61,7 @@ def run_migrations_online() -> None: ...@@ -61,7 +61,7 @@ def run_migrations_online() -> None:
# this callback is used to prevent an auto-migration from being generated # this callback is used to prevent an auto-migration from being generated
# when there are no changes to the schema # when there are no changes to the schema
# reference: http://alembic.zzzcomputing.com/en/latest/cookbook.html # 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): if getattr(config.cmd_opts, "autogenerate", False):
script = directives[0] script = directives[0]
if script.upgrade_ops.is_empty(): if script.upgrade_ops.is_empty():
......
"""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')
"""))
...@@ -6,7 +6,6 @@ from typing import TypeVar ...@@ -6,7 +6,6 @@ from typing import TypeVar
from orchestrator.domain.base import ProductBlockModel from orchestrator.domain.base import ProductBlockModel
from orchestrator.forms.validators import UniqueConstrainedList from orchestrator.forms.validators import UniqueConstrainedList
from orchestrator.types import SubscriptionLifecycle, strEnum from orchestrator.types import SubscriptionLifecycle, strEnum
from pydantic import Field
from gso.products.product_blocks.router import RouterBlock, RouterBlockInactive, RouterBlockProvisioning from gso.products.product_blocks.router import RouterBlock, RouterBlockInactive, RouterBlockProvisioning
from gso.utils.types.phy_port import PhyPortCapacity from gso.utils.types.phy_port import PhyPortCapacity
...@@ -20,6 +19,10 @@ class IptrunkType(strEnum): ...@@ -20,6 +19,10 @@ class IptrunkType(strEnum):
T = TypeVar("T", covariant=True) T = TypeVar("T", covariant=True)
class LAGMemberList(UniqueConstrainedList[T]): # type: ignore[type-var]
pass
class IptrunkInterfaceBlockInactive( class IptrunkInterfaceBlockInactive(
ProductBlockModel, lifecycle=[SubscriptionLifecycle.INITIAL], product_block_name="IptrunkInterfaceBlock" ProductBlockModel, lifecycle=[SubscriptionLifecycle.INITIAL], product_block_name="IptrunkInterfaceBlock"
): ):
...@@ -49,21 +52,21 @@ class IptrunkSideBlockInactive( ...@@ -49,21 +52,21 @@ class IptrunkSideBlockInactive(
iptrunk_side_node: RouterBlockInactive iptrunk_side_node: RouterBlockInactive
iptrunk_side_ae_iface: str | None = None iptrunk_side_ae_iface: str | None = None
iptrunk_side_ae_geant_a_sid: 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]): class IptrunkSideBlockProvisioning(IptrunkSideBlockInactive, lifecycle=[SubscriptionLifecycle.PROVISIONING]):
iptrunk_side_node: RouterBlockProvisioning iptrunk_side_node: RouterBlockProvisioning
iptrunk_side_ae_iface: str | None = None iptrunk_side_ae_iface: str | None = None
iptrunk_side_ae_geant_a_sid: 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]): class IptrunkSideBlock(IptrunkSideBlockProvisioning, lifecycle=[SubscriptionLifecycle.ACTIVE]):
iptrunk_side_node: RouterBlock iptrunk_side_node: RouterBlock
iptrunk_side_ae_iface: str | None = None iptrunk_side_ae_iface: str | None = None
iptrunk_side_ae_geant_a_sid: 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( class IptrunkBlockInactive(
...@@ -79,7 +82,6 @@ class IptrunkBlockInactive( ...@@ -79,7 +82,6 @@ class IptrunkBlockInactive(
iptrunk_isis_metric: int | None = None iptrunk_isis_metric: int | None = None
iptrunk_ipv4_network: ipaddress.IPv4Network | None = None iptrunk_ipv4_network: ipaddress.IPv4Network | None = None
iptrunk_ipv6_network: ipaddress.IPv6Network | None = None iptrunk_ipv6_network: ipaddress.IPv6Network | None = None
#
iptrunk_sides: IptrunkSides[IptrunkSideBlockInactive] iptrunk_sides: IptrunkSides[IptrunkSideBlockInactive]
...@@ -94,7 +96,6 @@ class IptrunkBlockProvisioning(IptrunkBlockInactive, lifecycle=[SubscriptionLife ...@@ -94,7 +96,6 @@ class IptrunkBlockProvisioning(IptrunkBlockInactive, lifecycle=[SubscriptionLife
iptrunk_isis_metric: int | None = None iptrunk_isis_metric: int | None = None
iptrunk_ipv4_network: ipaddress.IPv4Network | None = None iptrunk_ipv4_network: ipaddress.IPv4Network | None = None
iptrunk_ipv6_network: ipaddress.IPv6Network | None = None iptrunk_ipv6_network: ipaddress.IPv6Network | None = None
#
iptrunk_sides: IptrunkSides[IptrunkSideBlockProvisioning] iptrunk_sides: IptrunkSides[IptrunkSideBlockProvisioning]
......
...@@ -94,7 +94,9 @@ class NetBoxClient: ...@@ -94,7 +94,9 @@ class NetBoxClient:
# First get manufacturer id # First get manufacturer id
manufacturer_id = int(self.netbox.dcim.manufacturers.get(name=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)) return self.netbox.dcim.device_types.create(dict(device_type))
def create_device_role(self, name: str, slug: str) -> DeviceRole: def create_device_role(self, name: str, slug: str) -> DeviceRole:
......
...@@ -10,6 +10,7 @@ from gso.products.product_blocks.site import SiteTier ...@@ -10,6 +10,7 @@ from gso.products.product_blocks.site import SiteTier
from gso.services import subscriptions from gso.services import subscriptions
from gso.services.crm import CustomerNotFoundError, get_customer_by_name from gso.services.crm import CustomerNotFoundError, get_customer_by_name
from gso.utils.types.phy_port import PhyPortCapacity from gso.utils.types.phy_port import PhyPortCapacity
from gso.workflows.iptrunk.utils import LAGMember
class ImportResponseModel(BaseModel): class ImportResponseModel(BaseModel):
...@@ -54,16 +55,14 @@ class IptrunkImportModel(BaseModel): ...@@ -54,16 +55,14 @@ class IptrunkImportModel(BaseModel):
iptrunk_description: str iptrunk_description: str
iptrunk_speed: PhyPortCapacity iptrunk_speed: PhyPortCapacity
iptrunk_minimum_links: int iptrunk_minimum_links: int
iptrunk_sideA_node_id: str side_a_node_id: str
iptrunk_sideA_ae_iface: str side_a_ae_iface: str
iptrunk_sideA_ae_geant_a_sid: str side_a_ae_geant_a_sid: str
iptrunk_sideA_ae_members: list[str] side_a_ae_members: list[LAGMember]
iptrunk_sideA_ae_members_descriptions: list[str] side_b_node_id: str
iptrunk_sideB_node_id: str side_b_ae_iface: str
iptrunk_sideB_ae_iface: str side_b_ae_geant_a_sid: str
iptrunk_sideB_ae_geant_a_sid: str side_b_ae_members: list[LAGMember]
iptrunk_sideB_ae_members: list[str]
iptrunk_sideB_ae_members_descriptions: list[str]
iptrunk_ipv4_network: ipaddress.IPv4Network iptrunk_ipv4_network: ipaddress.IPv4Network
iptrunk_ipv6_network: ipaddress.IPv6Network iptrunk_ipv6_network: ipaddress.IPv6Network
...@@ -83,14 +82,14 @@ class IptrunkImportModel(BaseModel): ...@@ -83,14 +82,14 @@ class IptrunkImportModel(BaseModel):
return value 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: def check_if_router_side_is_available(cls, value: str) -> str:
if value not in cls._get_active_routers(): if value not in cls._get_active_routers():
raise ValueError("Router not found") raise ValueError("Router not found")
return value 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]: def check_side_uniqueness(cls, value: list[str]) -> list[str]:
if len(value) != len(set(value)): if len(value) != len(set(value)):
raise ValueError("Items must be unique") raise ValueError("Items must be unique")
...@@ -100,26 +99,16 @@ class IptrunkImportModel(BaseModel): ...@@ -100,26 +99,16 @@ class IptrunkImportModel(BaseModel):
@root_validator @root_validator
def check_members(cls, values: dict[str, Any]) -> dict[str, Any]: def check_members(cls, values: dict[str, Any]) -> dict[str, Any]:
min_links = values["iptrunk_minimum_links"] min_links = values["iptrunk_minimum_links"]
side_a_members = values.get("iptrunk_sideA_ae_members", []) side_a_members = values.get("side_a_ae_members", {})
side_a_descriptions = values.get("iptrunk_sideA_ae_members_descriptions", []) side_b_members = values.get("side_b_ae_members", {})
side_b_members = values.get("iptrunk_sideB_ae_members", [])
side_b_descriptions = values.get("iptrunk_sideB_ae_members_descriptions", [])
len_a = len(side_a_members) len_a = len(side_a_members)
len_a_desc = len(side_a_descriptions)
len_b = len(side_b_members) len_b = len(side_b_members)
len_b_desc = len(side_b_descriptions)
if len_a < min_links: if len_a < min_links:
raise ValueError(f"Side A members should be at least {min_links} (iptrunk_minimum_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: if len_a != len_b:
raise ValueError("Mismatch between Side A and B members") 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 return values
from uuid import uuid4
from orchestrator.forms import FormPage from orchestrator.forms import FormPage
from orchestrator.forms.validators import Choice, UniqueConstrainedList from orchestrator.forms.validators import Choice, UniqueConstrainedList
from orchestrator.targets import Target from orchestrator.targets import Target
...@@ -6,13 +8,14 @@ from orchestrator.workflow import StepList, done, init, step, workflow ...@@ -6,13 +8,14 @@ from orchestrator.workflow import StepList, done, init, step, workflow
from orchestrator.workflows.steps import resync, set_status, store_process_subscription from orchestrator.workflows.steps import resync, set_status, store_process_subscription
from orchestrator.workflows.utils import wrap_create_initial_input_form from orchestrator.workflows.utils import wrap_create_initial_input_form
from gso.products.product_blocks.iptrunk import IptrunkType from gso.products.product_blocks.iptrunk import IptrunkInterfaceBlockInactive, IptrunkType
from gso.products.product_types.iptrunk import IptrunkInactive, IptrunkProvisioning from gso.products.product_types.iptrunk import IptrunkInactive, IptrunkProvisioning
from gso.products.product_types.router import Router from gso.products.product_types.router import Router
from gso.services import infoblox, provisioning_proxy, subscriptions from gso.services import infoblox, provisioning_proxy, subscriptions
from gso.services.crm import customer_selector from gso.services.crm import customer_selector
from gso.services.provisioning_proxy import pp_interaction from gso.services.provisioning_proxy import pp_interaction
from gso.utils.types.phy_port import PhyPortCapacity from gso.utils.types.phy_port import PhyPortCapacity
from gso.workflows.iptrunk.utils import LAGMember
def initial_input_form_generator(product_name: str) -> FormGenerator: def initial_input_form_generator(product_name: str) -> FormGenerator:
...@@ -39,7 +42,7 @@ def initial_input_form_generator(product_name: str) -> FormGenerator: ...@@ -39,7 +42,7 @@ def initial_input_form_generator(product_name: str) -> FormGenerator:
initial_user_input = yield CreateIptrunkForm initial_user_input = yield CreateIptrunkForm
class AeMembersListA(UniqueConstrainedList[str]): class AeMembersListA(UniqueConstrainedList[LAGMember]):
min_items = initial_user_input.iptrunk_minimum_links min_items = initial_user_input.iptrunk_minimum_links
RouterEnumA = Choice("Select a router", zip(routers.keys(), routers.items())) # type: ignore[arg-type] RouterEnumA = Choice("Select a router", zip(routers.keys(), routers.items())) # type: ignore[arg-type]
...@@ -48,31 +51,29 @@ def initial_input_form_generator(product_name: str) -> FormGenerator: ...@@ -48,31 +51,29 @@ def initial_input_form_generator(product_name: str) -> FormGenerator:
class Config: class Config:
title = "Provide subscription details for side A of the trunk." title = "Provide subscription details for side A of the trunk."
iptrunk_sideA_node_id: RouterEnumA # type: ignore[valid-type] side_a_node_id: RouterEnumA # type: ignore[valid-type]
iptrunk_sideA_ae_iface: str side_a_ae_iface: str
iptrunk_sideA_ae_geant_a_sid: str side_a_ae_geant_a_sid: str
iptrunk_sideA_ae_members: AeMembersListA side_a_ae_members: AeMembersListA
iptrunk_sideA_ae_members_descriptions: AeMembersListA
user_input_side_a = yield CreateIptrunkSideAForm user_input_side_a = yield CreateIptrunkSideAForm
# Remove the selected router for side A, to prevent any loops # Remove the selected router for side A, to prevent any loops
routers.pop(str(user_input_side_a.iptrunk_sideA_node_id.name)) routers.pop(str(user_input_side_a.side_a_node_id.name))
RouterEnumB = Choice("Select a router", zip(routers.keys(), routers.items())) # type: ignore[arg-type] RouterEnumB = Choice("Select a router", zip(routers.keys(), routers.items())) # type: ignore[arg-type]
class AeMembersListB(UniqueConstrainedList[str]): class AeMembersListB(UniqueConstrainedList[LAGMember]):
min_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.iptrunk_sideA_ae_members) max_items = len(user_input_side_a.side_a_ae_members)
class CreateIptrunkSideBForm(FormPage): class CreateIptrunkSideBForm(FormPage):
class Config: class Config:
title = "Provide subscription details for side B of the trunk." title = "Provide subscription details for side B of the trunk."
iptrunk_sideB_node_id: RouterEnumB # type: ignore[valid-type] side_b_node_id: RouterEnumB # type: ignore[valid-type]
iptrunk_sideB_ae_iface: str side_b_ae_iface: str
iptrunk_sideB_ae_geant_a_sid: str side_b_ae_geant_a_sid: str
iptrunk_sideB_ae_members: AeMembersListB side_b_ae_members: AeMembersListB
iptrunk_sideB_ae_members_descriptions: AeMembersListB
user_input_side_b = yield CreateIptrunkSideBForm user_input_side_b = yield CreateIptrunkSideBForm
...@@ -109,35 +110,37 @@ def initialize_subscription( ...@@ -109,35 +110,37 @@ def initialize_subscription(
iptrunk_description: str, iptrunk_description: str,
iptrunk_speed: PhyPortCapacity, iptrunk_speed: PhyPortCapacity,
iptrunk_minimum_links: int, iptrunk_minimum_links: int,
iptrunk_sideA_node_id: str, side_a_node_id: str,
iptrunk_sideA_ae_iface: str, side_a_ae_iface: str,
iptrunk_sideA_ae_geant_a_sid: str, side_a_ae_geant_a_sid: str,
iptrunk_sideA_ae_members: list[str], side_a_ae_members: list[dict],
iptrunk_sideA_ae_members_descriptions: list[str], side_b_node_id: str,
iptrunk_sideB_node_id: str, side_b_ae_iface: str,
iptrunk_sideB_ae_iface: str, side_b_ae_geant_a_sid: str,
iptrunk_sideB_ae_geant_a_sid: str, side_b_ae_members: list[dict],
iptrunk_sideB_ae_members: list[str],
iptrunk_sideB_ae_members_descriptions: list[str],
) -> State: ) -> State:
subscription.iptrunk.geant_s_sid = geant_s_sid subscription.iptrunk.geant_s_sid = geant_s_sid
subscription.iptrunk.iptrunk_description = iptrunk_description subscription.iptrunk.iptrunk_description = iptrunk_description
subscription.iptrunk.iptrunk_type = iptrunk_type subscription.iptrunk.iptrunk_type = iptrunk_type
subscription.iptrunk.iptrunk_speed = iptrunk_speed 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_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_node = Router.from_subscription(side_a_node_id).router
subscription.iptrunk.iptrunk_sides[0].iptrunk_side_ae_iface = iptrunk_sideA_ae_iface subscription.iptrunk.iptrunk_sides[0].iptrunk_side_ae_iface = side_a_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_geant_a_sid = side_a_ae_geant_a_sid
subscription.iptrunk.iptrunk_sides[0].iptrunk_side_ae_members = iptrunk_sideA_ae_members for member in side_a_ae_members:
subscription.iptrunk.iptrunk_sides[0].iptrunk_side_ae_members_description = iptrunk_sideA_ae_members_descriptions 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(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_node = Router.from_subscription(side_b_node_id).router
subscription.iptrunk.iptrunk_sides[1].iptrunk_side_ae_members = iptrunk_sideB_ae_members subscription.iptrunk.iptrunk_sides[1].iptrunk_side_ae_iface = side_b_ae_iface
subscription.iptrunk.iptrunk_sides[1].iptrunk_side_ae_members_description = iptrunk_sideB_ae_members_descriptions 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.description = f"IP trunk, geant_s_sid:{geant_s_sid}"
subscription = IptrunkProvisioning.from_other_lifecycle(subscription, SubscriptionLifecycle.PROVISIONING) subscription = IptrunkProvisioning.from_other_lifecycle(subscription, SubscriptionLifecycle.PROVISIONING)
......
import ipaddress import ipaddress
from uuid import uuid4
from orchestrator.forms import FormPage, ReadOnlyField from orchestrator.forms import FormPage, ReadOnlyField
from orchestrator.forms.validators import UniqueConstrainedList from orchestrator.forms.validators import UniqueConstrainedList
...@@ -8,11 +9,12 @@ from orchestrator.workflow import StepList, done, init, step, workflow ...@@ -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.steps import resync, store_process_subscription, unsync
from orchestrator.workflows.utils import wrap_modify_initial_input_form 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.products.product_types.iptrunk import Iptrunk
from gso.services import provisioning_proxy from gso.services import provisioning_proxy
from gso.services.provisioning_proxy import pp_interaction from gso.services.provisioning_proxy import pp_interaction
from gso.utils.types.phy_port import PhyPortCapacity from gso.utils.types.phy_port import PhyPortCapacity
from gso.workflows.iptrunk.utils import LAGMember
def initial_input_form_generator(subscription_id: UUIDstr) -> FormGenerator: def initial_input_form_generator(subscription_id: UUIDstr) -> FormGenerator:
...@@ -31,38 +33,32 @@ 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 initial_user_input = yield ModifyIptrunkForm
class AeMembersListA(UniqueConstrainedList[str]): class AeMembersListA(UniqueConstrainedList[LAGMember]):
min_items = initial_user_input.iptrunk_minimum_links min_items = initial_user_input.iptrunk_minimum_links
class ModifyIptrunkSideAForm(FormPage): class ModifyIptrunkSideAForm(FormPage):
class Config: class Config:
title = "Provide subscription details for side A of the trunk." 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) side_a_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) side_a_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 side_a_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 side_a_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
user_input_side_a = yield ModifyIptrunkSideAForm user_input_side_a = yield ModifyIptrunkSideAForm
class AeMembersListB(UniqueConstrainedList[str]): class AeMembersListB(UniqueConstrainedList[LAGMember]):
min_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.iptrunk_sideA_ae_members) max_items = len(user_input_side_a.side_a_ae_members)
class ModifyIptrunkSideBForm(FormPage): class ModifyIptrunkSideBForm(FormPage):
class Config: class Config:
title = "Provide subscription details for side B of the trunk." 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) side_b_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) side_b_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 side_b_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 side_b_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
user_input_side_b = yield ModifyIptrunkSideBForm user_input_side_b = yield ModifyIptrunkSideBForm
...@@ -77,12 +73,10 @@ def modify_iptrunk_subscription( ...@@ -77,12 +73,10 @@ def modify_iptrunk_subscription(
iptrunk_description: str, iptrunk_description: str,
iptrunk_speed: PhyPortCapacity, iptrunk_speed: PhyPortCapacity,
iptrunk_minimum_links: int, iptrunk_minimum_links: int,
iptrunk_sideA_ae_geant_a_sid: str, side_a_ae_geant_a_sid: str,
iptrunk_sideA_ae_members: list[str], side_a_ae_members: list[dict],
iptrunk_sideA_ae_members_descriptions: list[str], side_b_ae_geant_a_sid: str,
iptrunk_sideB_ae_geant_a_sid: str, side_b_ae_members: list[dict],
iptrunk_sideB_ae_members: list[str],
iptrunk_sideB_ae_members_descriptions: list[str],
) -> State: ) -> State:
subscription.iptrunk.geant_s_sid = geant_s_sid subscription.iptrunk.geant_s_sid = geant_s_sid
subscription.iptrunk.iptrunk_description = iptrunk_description subscription.iptrunk.iptrunk_description = iptrunk_description
...@@ -90,13 +84,21 @@ def modify_iptrunk_subscription( ...@@ -90,13 +84,21 @@ def modify_iptrunk_subscription(
subscription.iptrunk.iptrunk_speed = iptrunk_speed subscription.iptrunk.iptrunk_speed = iptrunk_speed
subscription.iptrunk.iptrunk_minimum_links = iptrunk_minimum_links 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_geant_a_sid = side_a_ae_geant_a_sid
subscription.iptrunk.iptrunk_sides[0].iptrunk_side_ae_members = iptrunk_sideA_ae_members # Flush the old list of member interfaces
subscription.iptrunk.iptrunk_sides[0].iptrunk_side_ae_members_description = iptrunk_sideA_ae_members_descriptions subscription.iptrunk.iptrunk_sides[0].iptrunk_side_ae_members.clear()
# And update the list to only include the new member interfaces
subscription.iptrunk.iptrunk_sides[1].iptrunk_side_ae_geant_a_sid = iptrunk_sideB_ae_geant_a_sid for member in side_a_ae_members:
subscription.iptrunk.iptrunk_sides[1].iptrunk_side_ae_members = iptrunk_sideB_ae_members subscription.iptrunk.iptrunk_sides[0].iptrunk_side_ae_members.append(
subscription.iptrunk.iptrunk_sides[1].iptrunk_side_ae_members_description = iptrunk_sideB_ae_members_descriptions 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}" subscription.description = f"IP trunk, geant_s_sid:{geant_s_sid}"
......
from orchestrator import step from orchestrator import step
from orchestrator.types import State, UUIDstr from orchestrator.types import State, UUIDstr
from pydantic import BaseModel
from gso.products.product_types.iptrunk import Iptrunk from gso.products.product_types.iptrunk import Iptrunk
from gso.services import provisioning_proxy 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") @step("[COMMIT] Set ISIS metric to 90000")
def set_isis_to_90000(subscription: Iptrunk, process_id: UUIDstr, tt_number: str) -> State: def set_isis_to_90000(subscription: Iptrunk, process_id: UUIDstr, tt_number: str) -> State:
old_isis_metric = subscription.iptrunk.iptrunk_isis_metric old_isis_metric = subscription.iptrunk.iptrunk_isis_metric
......
...@@ -159,7 +159,7 @@ def provision_router_real(subscription: RouterProvisioning, process_id: UUIDstr, ...@@ -159,7 +159,7 @@ def provision_router_real(subscription: RouterProvisioning, process_id: UUIDstr,
@step("Create NetBox Device") @step("Create NetBox Device")
def create_netbox_device(subscription: RouterProvisioning) -> State: def create_netbox_device(subscription: RouterProvisioning) -> State:
NetBoxClient().create_device( 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} return {"subscription": subscription}
......
...@@ -15,6 +15,7 @@ from gso.services import subscriptions ...@@ -15,6 +15,7 @@ from gso.services import subscriptions
from gso.services.crm import get_customer_by_name from gso.services.crm import get_customer_by_name
from gso.utils.types.phy_port import PhyPortCapacity from gso.utils.types.phy_port import PhyPortCapacity
from gso.workflows.iptrunk.create_iptrunk import initialize_subscription from gso.workflows.iptrunk.create_iptrunk import initialize_subscription
from gso.workflows.iptrunk.utils import LAGMember
def _generate_routers() -> dict[str, str]: def _generate_routers() -> dict[str, str]:
...@@ -42,17 +43,15 @@ def initial_input_form_generator() -> FormGenerator: ...@@ -42,17 +43,15 @@ def initial_input_form_generator() -> FormGenerator:
iptrunk_speed: PhyPortCapacity iptrunk_speed: PhyPortCapacity
iptrunk_minimum_links: int iptrunk_minimum_links: int
iptrunk_sideA_node_id: RouterEnum # type: ignore[valid-type] side_a_node_id: RouterEnum # type: ignore[valid-type]
iptrunk_sideA_ae_iface: str side_a_ae_iface: str
iptrunk_sideA_ae_geant_a_sid: str side_a_ae_geant_a_sid: str
iptrunk_sideA_ae_members: UniqueConstrainedList[str] side_a_ae_members: UniqueConstrainedList[LAGMember]
iptrunk_sideA_ae_members_descriptions: UniqueConstrainedList[str]
iptrunk_sideB_node_id: RouterEnum # type: ignore[valid-type] side_b_node_id: RouterEnum # type: ignore[valid-type]
iptrunk_sideB_ae_iface: str side_b_ae_iface: str
iptrunk_sideB_ae_geant_a_sid: str side_b_ae_geant_a_sid: str
iptrunk_sideB_ae_members: UniqueConstrainedList[str] side_b_ae_members: UniqueConstrainedList[LAGMember]
iptrunk_sideB_ae_members_descriptions: UniqueConstrainedList[str]
iptrunk_ipv4_network: ipaddress.IPv4Network iptrunk_ipv4_network: ipaddress.IPv4Network
iptrunk_ipv6_network: ipaddress.IPv6Network iptrunk_ipv6_network: ipaddress.IPv6Network
...@@ -82,6 +81,7 @@ def update_ipam_stub_for_subscription( ...@@ -82,6 +81,7 @@ def update_ipam_stub_for_subscription(
) -> State: ) -> State:
subscription.iptrunk.iptrunk_ipv4_network = iptrunk_ipv4_network subscription.iptrunk.iptrunk_ipv4_network = iptrunk_ipv4_network
subscription.iptrunk.iptrunk_ipv6_network = iptrunk_ipv6_network subscription.iptrunk.iptrunk_ipv6_network = iptrunk_ipv6_network
subscription.iptrunk.iptrunk_ipv6_network = iptrunk_ipv6_network
return {"subscription": subscription} return {"subscription": subscription}
......
...@@ -46,6 +46,8 @@ show_error_codes = true ...@@ -46,6 +46,8 @@ show_error_codes = true
show_column_numbers = true show_column_numbers = true
# Suppress "note: By default the bodies of untyped functions are not checked" # Suppress "note: By default the bodies of untyped functions are not checked"
disable_error_code = "annotation-unchecked" 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] [tool.ruff]
exclude = [ exclude = [
......
...@@ -6,7 +6,7 @@ from orchestrator.domain import SubscriptionModel ...@@ -6,7 +6,7 @@ from orchestrator.domain import SubscriptionModel
from orchestrator.types import SubscriptionLifecycle, UUIDstr from orchestrator.types import SubscriptionLifecycle, UUIDstr
from gso.products import ProductType 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.router import RouterRole, RouterVendor
from gso.products.product_blocks.site import SiteTier from gso.products.product_blocks.site import SiteTier
from gso.products.product_types.iptrunk import IptrunkInactive from gso.products.product_types.iptrunk import IptrunkInactive
...@@ -140,10 +140,13 @@ def iptrunk_side_subscription_factory(router_subscription_factory, faker): ...@@ -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_node = Router.from_subscription(iptrunk_side_node_id).router
iptrunk_side_ae_iface = iptrunk_side_ae_iface or faker.pystr() 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_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 = iptrunk_side_ae_members or [
iptrunk_side_ae_members_description = iptrunk_side_ae_members_description or [ IptrunkInterfaceBlock.new(
faker.sentence(), faker.uuid4(), interface_name=faker.network_interface(), interface_description=faker.sentence()
faker.sentence(), ),
IptrunkInterfaceBlock.new(
faker.uuid4(), interface_name=faker.network_interface(), interface_description=faker.sentence()
),
] ]
return IptrunkSideBlock.new( return IptrunkSideBlock.new(
......
...@@ -26,16 +26,18 @@ def iptrunk_data(router_subscription_factory, faker): ...@@ -26,16 +26,18 @@ def iptrunk_data(router_subscription_factory, faker):
"iptrunk_description": faker.sentence(), "iptrunk_description": faker.sentence(),
"iptrunk_speed": PhyPortCapacity.HUNDRED_GIGABIT_PER_SECOND, "iptrunk_speed": PhyPortCapacity.HUNDRED_GIGABIT_PER_SECOND,
"iptrunk_minimum_links": 5, "iptrunk_minimum_links": 5,
"iptrunk_sideA_node_id": router_side_a, "side_a_node_id": router_side_a,
"iptrunk_sideA_ae_iface": faker.pystr(), "side_a_ae_iface": faker.pystr(),
"iptrunk_sideA_ae_geant_a_sid": faker.pystr(), "side_a_ae_geant_a_sid": faker.pystr(),
"iptrunk_sideA_ae_members": [faker.pystr() for _ in range(5)], "side_a_ae_members": [
"iptrunk_sideA_ae_members_descriptions": [faker.sentence() for _ in range(5)], {"interface_name": faker.network_interface(), "interface_description": faker.sentence()} for _ in range(5)
"iptrunk_sideB_node_id": router_side_b, ],
"iptrunk_sideB_ae_iface": faker.pystr(), "side_b_node_id": router_side_b,
"iptrunk_sideB_ae_geant_a_sid": faker.pystr(), "side_b_ae_iface": faker.pystr(),
"iptrunk_sideB_ae_members": [faker.pystr() for _ in range(5)], "side_b_ae_geant_a_sid": faker.pystr(),
"iptrunk_sideB_ae_members_descriptions": [faker.sentence() for _ in range(5)], "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_ipv4_network": str(faker.ipv4_network()),
"iptrunk_ipv6_network": str(faker.ipv6_network()), "iptrunk_ipv6_network": str(faker.ipv6_network()),
} }
...@@ -43,13 +45,13 @@ def iptrunk_data(router_subscription_factory, faker): ...@@ -43,13 +45,13 @@ def iptrunk_data(router_subscription_factory, faker):
@pytest.fixture @pytest.fixture
def mock_routers(iptrunk_data): 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 = [ side_effects = [
first_call, first_call,
first_call, first_call,
[ [
(iptrunk_data["iptrunk_sideA_node_id"], "iptrunk_sideA_node_id description"), (iptrunk_data["side_a_node_id"], "side_a_node_id description"),
(iptrunk_data["iptrunk_sideB_node_id"], "iptrunk_sideB_node_id description"), (iptrunk_data["side_b_node_id"], "side_b_node_id description"),
(str(uuid4()), "random description"), (str(uuid4()), "random description"),
], ],
] ]
...@@ -203,26 +205,40 @@ def test_import_iptrunk_invalid_router_id_side_a_and_b(mock_start_process, test_ ...@@ -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.status_code == 422
assert response.json() == { assert response.json() == {
"detail": [ "detail": [
{"loc": ["body", "iptrunk_sideA_node_id"], "msg": "Router not found", "type": "value_error"}, {"loc": ["body", "side_a_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_b_node_id"], "msg": "Router not found", "type": "value_error"},
] ]
} }
@patch("gso.api.v1.imports._start_process") @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" mock_start_process.return_value = "123e4567-e89b-12d3-a456-426655440000"
iptrunk_data["iptrunk_sideA_ae_members"] = [5, 5, 5, 5, 5] repeat_interface_a = {"interface_name": faker.network_interface(), "interface_description": faker.sentence()}
iptrunk_data["iptrunk_sideB_ae_members"] = [4, 4, 4, 5, 5] 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) response = test_client.post(IPTRUNK_IMPORT_API_URL, json=iptrunk_data)
assert response.status_code == 422 assert response.status_code == 422
assert response.json() == { assert response.json() == {
"detail": [ "detail": [
{"loc": ["body", "iptrunk_sideA_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", "iptrunk_sideB_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__"], "loc": ["body", "__root__"],
"msg": "Side A members should be at least 5 (iptrunk_minimum_links)", "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 ...@@ -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") @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, test_client, iptrunk_data, mock_routers
): ):
mock_start_process.return_value = "123e4567-e89b-12d3-a456-426655440000" 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) 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( ...@@ -255,36 +271,12 @@ def test_iptrunk_import_fails_on_side_a_member_count_mismatch(
@patch("gso.api.v1.imports._start_process") @patch("gso.api.v1.imports._start_process")
def test_iptrunk_import_fails_on_side_a_member_description_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_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(
mock_start_process, test_client, iptrunk_data, mock_routers mock_start_process, test_client, iptrunk_data, mock_routers
): ):
mock_start_process.return_value = "123e4567-e89b-12d3-a456-426655440000" 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) 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( ...@@ -292,27 +284,3 @@ def test_iptrunk_import_fails_on_side_a_and_b_members_mismatch(
assert response.json() == { assert response.json() == {
"detail": [{"loc": ["body", "__root__"], "msg": "Mismatch between Side A and B members", "type": "value_error"}] "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",
}
]
}
...@@ -33,19 +33,21 @@ def input_form_wizard_data(router_subscription_factory, faker): ...@@ -33,19 +33,21 @@ def input_form_wizard_data(router_subscription_factory, faker):
"iptrunk_minimum_links": 5, "iptrunk_minimum_links": 5,
} }
create_ip_trunk_side_a_step = { create_ip_trunk_side_a_step = {
"iptrunk_sideA_node_id": router_side_a, "side_a_node_id": router_side_a,
"iptrunk_sideA_ae_iface": faker.pystr(), "side_a_ae_iface": faker.pystr(),
"iptrunk_sideA_ae_geant_a_sid": faker.pystr(), "side_a_ae_geant_a_sid": faker.pystr(),
"iptrunk_sideA_ae_members": [faker.pystr() for _ in range(5)], "side_a_ae_members": [
"iptrunk_sideA_ae_members_descriptions": [faker.sentence() for _ in range(5)], {"interface_name": faker.network_interface(), "interface_description": faker.sentence()} for _ in range(5)
],
} }
create_ip_trunk_side_b_step = { create_ip_trunk_side_b_step = {
"iptrunk_sideB_node_id": router_side_b, "side_b_node_id": router_side_b,
"iptrunk_sideB_ae_iface": faker.pystr(), "side_b_ae_iface": faker.pystr(),
"iptrunk_sideB_ae_geant_a_sid": faker.pystr(), "side_b_ae_geant_a_sid": faker.pystr(),
"iptrunk_sideB_ae_members": [faker.pystr() for _ in range(5)], "side_b_ae_members": [
"iptrunk_sideB_ae_members_descriptions": [faker.sentence() for _ in range(5)], {"interface_name": faker.network_interface(), "interface_description": faker.sentence()} for _ in range(5)
],
} }
return [create_ip_trunk_step, create_ip_trunk_side_a_step, create_ip_trunk_side_b_step] return [create_ip_trunk_step, create_ip_trunk_side_a_step, create_ip_trunk_side_b_step]
......
...@@ -32,34 +32,12 @@ def test_iptrunk_modify_trunk_interface_success( ...@@ -32,34 +32,12 @@ def test_iptrunk_modify_trunk_interface_success(
new_side_a_sid = faker.geant_sid() new_side_a_sid = faker.geant_sid()
new_side_a_ae_members = [ new_side_a_ae_members = [
faker.network_interface(), {"interface_name": faker.network_interface(), "interface_description": faker.sentence()} for _ in range(5)
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(),
] ]
new_side_b_sid = faker.geant_sid() new_side_b_sid = faker.geant_sid()
new_side_b_ae_members = [ new_side_b_ae_members = [
faker.network_interface(), {"interface_name": faker.network_interface(), "interface_description": faker.sentence()} for _ in range(5)
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(),
] ]
# Run workflow # Run workflow
...@@ -74,14 +52,12 @@ def test_iptrunk_modify_trunk_interface_success( ...@@ -74,14 +52,12 @@ def test_iptrunk_modify_trunk_interface_success(
"iptrunk_minimum_links": new_link_count, "iptrunk_minimum_links": new_link_count,
}, },
{ {
"iptrunk_sideA_ae_geant_a_sid": new_side_a_sid, "side_a_ae_geant_a_sid": new_side_a_sid,
"iptrunk_sideA_ae_members": new_side_a_ae_members, "side_a_ae_members": new_side_a_ae_members,
"iptrunk_sideA_ae_members_descriptions": new_side_a_ae_descriptions,
}, },
{ {
"iptrunk_sideB_ae_geant_a_sid": new_side_b_sid, "side_b_ae_geant_a_sid": new_side_b_sid,
"iptrunk_sideB_ae_members": new_side_b_ae_members, "side_b_ae_members": new_side_b_ae_members,
"iptrunk_sideB_ae_members_descriptions": new_side_b_ae_descriptions,
}, },
] ]
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment