From 51d33a1a875fdd4421efb32bda47186adb4e9a76 Mon Sep 17 00:00:00 2001 From: Neda Moeini <neda.moeini@GA0479-NMOEINI.local> Date: Thu, 19 Oct 2023 13:13:28 +0200 Subject: [PATCH] Fixed interface list drop down. --- gso/api/v1/imports.py | 2 +- gso/utils/helpers.py | 47 +++++++++++++- gso/workflows/iptrunk/create_iptrunk.py | 62 +++++++++---------- gso/workflows/iptrunk/migrate_iptrunk.py | 2 +- .../iptrunk/modify_trunk_interface.py | 2 +- gso/workflows/iptrunk/terminate_iptrunk.py | 2 +- gso/workflows/iptrunk/utils.py | 28 --------- gso/workflows/tasks/import_iptrunk.py | 2 +- test/workflows/iptrunk/test_create_iptrunk.py | 12 ++-- 9 files changed, 85 insertions(+), 74 deletions(-) delete mode 100644 gso/workflows/iptrunk/utils.py diff --git a/gso/api/v1/imports.py b/gso/api/v1/imports.py index 6f1cf496..51f58c5d 100644 --- a/gso/api/v1/imports.py +++ b/gso/api/v1/imports.py @@ -15,7 +15,7 @@ from gso.products.product_blocks.router import RouterRole, RouterVendor 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.workflows.iptrunk.utils import LAGMember +from gso.utils.helpers import LAGMember router = APIRouter(prefix="/imports", tags=["Imports"], dependencies=[Depends(opa_security_default)]) diff --git a/gso/utils/helpers.py b/gso/utils/helpers.py index 32d891ca..ff37395b 100644 --- a/gso/utils/helpers.py +++ b/gso/utils/helpers.py @@ -2,14 +2,40 @@ import re from ipaddress import IPv4Address from uuid import UUID -from orchestrator.forms.validators import Choice -from orchestrator.types import UUIDstr +from orchestrator import step +from orchestrator.types import State, UUIDstr +from pydantic import BaseModel +from pydantic_forms.validators import Choice from gso.products.product_blocks.router import RouterVendor +from gso.products.product_types.iptrunk import Iptrunk from gso.products.product_types.router import Router +from gso.services import provisioning_proxy from gso.services.netbox_client import NetboxClient +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 + subscription.iptrunk.iptrunk_isis_metric = 90000 + provisioning_proxy.provision_ip_trunk(subscription, process_id, tt_number, "isis_interface", False) + + return { + "subscription": subscription, + "old_isis_metric": old_isis_metric, + "label_text": "ISIS is being set to 90K by the provisioning proxy, please wait for the results", + } + + def available_interfaces_choices(router_id: UUID, speed: str) -> Choice | None: """Return a list of available interfaces for a given router and speed. @@ -81,3 +107,20 @@ def validate_router_in_netbox(subscription_id: UUIDstr) -> UUIDstr | None: if not device: raise ValueError("The selected router does not exist in Netbox.") return subscription_id + + +def validate_iptrunk_unique_interface(interfaces: list[LAGMember]) -> list[LAGMember]: + """Verify if the interfaces are unique. + + Args: + ---- + interfaces (list[LAGMember]): The list of interfaces. + + Returns: + ------- + list[LAGMember]: The list of interfaces or raises an error. + """ + interface_names = [member.interface_name for member in interfaces] + if len(interface_names) != len(set(interface_names)): + raise ValueError("Interfaces must be unique.") + return interfaces diff --git a/gso/workflows/iptrunk/create_iptrunk.py b/gso/workflows/iptrunk/create_iptrunk.py index cd56d9da..f022181b 100644 --- a/gso/workflows/iptrunk/create_iptrunk.py +++ b/gso/workflows/iptrunk/create_iptrunk.py @@ -16,15 +16,16 @@ from gso.products.product_types.iptrunk import IptrunkInactive, IptrunkProvision from gso.products.product_types.router import Router from gso.services import infoblox, provisioning_proxy, subscriptions from gso.services.crm import customer_selector -from gso.services.netbox_client import NetboxClient, NotFoundError +from gso.services.netbox_client import NetboxClient from gso.services.provisioning_proxy import pp_interaction from gso.utils.helpers import ( + LAGMember, available_interfaces_choices, available_lags_choices, get_router_vendor, + validate_iptrunk_unique_interface, validate_router_in_netbox, ) -from gso.workflows.iptrunk.utils import LAGMember def initial_input_form_generator(product_name: str) -> FormGenerator: @@ -66,27 +67,22 @@ def initial_input_form_generator(product_name: str) -> FormGenerator: user_input_router_side_a = yield SelectRouterSideA router_a = user_input_router_side_a.side_a_node_id.name - if get_router_vendor(router_a) == RouterVendor.NOKIA: - available_interfaces = available_interfaces_choices(router_a, initial_user_input.iptrunk_speed) - if available_interfaces is None: - raise NotFoundError(f"Router {router_a} could not be found in Netbox.") + class JuniperAeMembers(UniqueConstrainedList[LAGMember]): + min_items = initial_user_input.iptrunk_minimum_links - class NokiaLAGMember(LAGMember): - interface_name: Choice = available_interfaces # type: ignore[assignment] + if get_router_vendor(router_a) == RouterVendor.NOKIA: - def __hash__(self) -> int: - return hash((self.interface_name, self.interface_description)) + class NokiaLAGMemberA(LAGMember): + interface_name: available_interfaces_choices( # type: ignore[valid-type] + router_a, initial_user_input.iptrunk_speed + ) - class NokiaAeMembersA(UniqueConstrainedList[NokiaLAGMember]): + class NokiaAeMembersA(UniqueConstrainedList[NokiaLAGMemberA]): min_items = initial_user_input.iptrunk_minimum_links ae_members_side_a = NokiaAeMembersA else: - - class JuniperAeMembersA(UniqueConstrainedList[LAGMember]): - min_items = initial_user_input.iptrunk_minimum_links - - ae_members_side_a = JuniperAeMembersA # type: ignore[assignment] + ae_members_side_a = JuniperAeMembers # type: ignore[assignment] class CreateIptrunkSideAForm(FormPage): class Config: @@ -96,6 +92,10 @@ def initial_input_form_generator(product_name: str) -> FormGenerator: side_a_ae_geant_a_sid: str side_a_ae_members: ae_members_side_a # type: ignore[valid-type] + @validator("side_a_ae_members", allow_reuse=True) + def validate_iptrunk_unique_interface_side_a(cls, side_a_ae_members: list[LAGMember]) -> list[LAGMember]: + return validate_iptrunk_unique_interface(side_a_ae_members) + user_input_side_a = yield CreateIptrunkSideAForm # Remove the selected router for side A, to prevent any loops routers.pop(str(router_a)) @@ -115,28 +115,20 @@ def initial_input_form_generator(product_name: str) -> FormGenerator: router_b = user_input_router_side_b.side_b_node_id.name if get_router_vendor(router_b) == RouterVendor.NOKIA: - available_interfaces = available_interfaces_choices(router_b, initial_user_input.iptrunk_speed) - if available_interfaces is None: - raise NotFoundError(f"Router {router_b} could not be found in Netbox.") - class NokiaLAGMember(LAGMember): # type: ignore[no-redef] - interface_name: Choice = available_interfaces # type: ignore[assignment] - - def __hash__(self) -> int: - return hash((self.interface_name, self.interface_description)) + class NokiaLAGMemberB(LAGMember): + interface_name: available_interfaces_choices( # type: ignore[valid-type] + router_b, initial_user_input.iptrunk_speed + ) class NokiaAeMembersB(UniqueConstrainedList): min_items = len(user_input_side_a.side_a_ae_members) max_items = len(user_input_side_a.side_a_ae_members) - item_type = NokiaLAGMember + item_type = NokiaLAGMemberB ae_members_side_b = NokiaAeMembersB else: - - class JuniperAeMembersB(UniqueConstrainedList[LAGMember]): - min_items = len(user_input_side_a.side_a_ae_members) - - ae_members_side_b = JuniperAeMembersB # type: ignore[assignment] + ae_members_side_b = JuniperAeMembers # type: ignore[assignment] class CreateIptrunkSideBForm(FormPage): class Config: @@ -146,6 +138,10 @@ def initial_input_form_generator(product_name: str) -> FormGenerator: side_b_ae_geant_a_sid: str side_b_ae_members: ae_members_side_b # type: ignore[valid-type] + @validator("side_b_ae_members", allow_reuse=True) + def validate_iptrunk_unique_interface_side_b(cls, side_b_ae_members: list[LAGMember]) -> list[LAGMember]: + return validate_iptrunk_unique_interface(side_b_ae_members) + user_input_side_b = yield CreateIptrunkSideBForm return ( @@ -307,12 +303,12 @@ def reserve_interfaces_in_netbox(subscription: IptrunkProvisioning) -> State: nbclient.attach_interface_to_lag( device_name=subscription.iptrunk.iptrunk_sides[side].iptrunk_side_node.router_fqdn, lag_name=lag_interface.name, - iface_name=interface, + iface_name=interface.interface_name, description=str(subscription.subscription_id), ) nbclient.reserve_interface( device_name=subscription.iptrunk.iptrunk_sides[side].iptrunk_side_node.router_fqdn, - iface_name=interface, + iface_name=interface.interface_name, ) return { "subscription": subscription, @@ -328,7 +324,7 @@ def allocate_interfaces_in_netbox(subscription: IptrunkProvisioning) -> State: for interface in subscription.iptrunk.iptrunk_sides[side].iptrunk_side_ae_members: NetboxClient().allocate_interface( device_name=subscription.iptrunk.iptrunk_sides[side].iptrunk_side_node.router_fqdn, - iface_name=interface, + iface_name=interface.interface_name, ) return { "subscription": subscription, diff --git a/gso/workflows/iptrunk/migrate_iptrunk.py b/gso/workflows/iptrunk/migrate_iptrunk.py index 3b2c1b4d..4b918d00 100644 --- a/gso/workflows/iptrunk/migrate_iptrunk.py +++ b/gso/workflows/iptrunk/migrate_iptrunk.py @@ -18,7 +18,7 @@ from gso.products.product_types.iptrunk import Iptrunk from gso.products.product_types.router import Router from gso.services import provisioning_proxy from gso.services.provisioning_proxy import pp_interaction -from gso.workflows.iptrunk.utils import set_isis_to_90000 +from gso.utils.helpers import set_isis_to_90000 logger = getLogger(__name__) diff --git a/gso/workflows/iptrunk/modify_trunk_interface.py b/gso/workflows/iptrunk/modify_trunk_interface.py index 9e402bbe..28460250 100644 --- a/gso/workflows/iptrunk/modify_trunk_interface.py +++ b/gso/workflows/iptrunk/modify_trunk_interface.py @@ -13,7 +13,7 @@ from gso.products.product_blocks.iptrunk import IptrunkInterfaceBlock, IptrunkTy from gso.products.product_types.iptrunk import Iptrunk from gso.services import provisioning_proxy from gso.services.provisioning_proxy import pp_interaction -from gso.workflows.iptrunk.utils import LAGMember +from gso.utils.helpers import LAGMember def initial_input_form_generator(subscription_id: UUIDstr) -> FormGenerator: diff --git a/gso/workflows/iptrunk/terminate_iptrunk.py b/gso/workflows/iptrunk/terminate_iptrunk.py index b2310d89..c0a0da62 100644 --- a/gso/workflows/iptrunk/terminate_iptrunk.py +++ b/gso/workflows/iptrunk/terminate_iptrunk.py @@ -11,7 +11,7 @@ from orchestrator.workflows.utils import wrap_modify_initial_input_form from gso.products.product_types.iptrunk import Iptrunk from gso.services import infoblox, provisioning_proxy from gso.services.provisioning_proxy import pp_interaction -from gso.workflows.iptrunk.utils import set_isis_to_90000 +from gso.utils.helpers import set_isis_to_90000 def initial_input_form_generator() -> FormGenerator: diff --git a/gso/workflows/iptrunk/utils.py b/gso/workflows/iptrunk/utils.py deleted file mode 100644 index 57c11715..00000000 --- a/gso/workflows/iptrunk/utils.py +++ /dev/null @@ -1,28 +0,0 @@ -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 - subscription.iptrunk.iptrunk_isis_metric = 90000 - provisioning_proxy.provision_ip_trunk(subscription, process_id, tt_number, "isis_interface", False) - - return { - "subscription": subscription, - "old_isis_metric": old_isis_metric, - "label_text": "ISIS is being set to 90K by the provisioning proxy, please wait for the results", - } diff --git a/gso/workflows/tasks/import_iptrunk.py b/gso/workflows/tasks/import_iptrunk.py index fe58ea11..84c12aa6 100644 --- a/gso/workflows/tasks/import_iptrunk.py +++ b/gso/workflows/tasks/import_iptrunk.py @@ -13,8 +13,8 @@ from gso.products.product_blocks.iptrunk import IptrunkType, PhyPortCapacity from gso.products.product_types.iptrunk import IptrunkInactive, IptrunkProvisioning from gso.services import subscriptions from gso.services.crm import get_customer_by_name +from gso.utils.helpers import LAGMember from gso.workflows.iptrunk.create_iptrunk import initialize_subscription -from gso.workflows.iptrunk.utils import LAGMember def _generate_routers() -> dict[str, str]: diff --git a/test/workflows/iptrunk/test_create_iptrunk.py b/test/workflows/iptrunk/test_create_iptrunk.py index 4f40c640..a8f273f7 100644 --- a/test/workflows/iptrunk/test_create_iptrunk.py +++ b/test/workflows/iptrunk/test_create_iptrunk.py @@ -7,7 +7,7 @@ from gso.products import Iptrunk, ProductType from gso.products.product_blocks.iptrunk import IptrunkType, PhyPortCapacity from gso.services.crm import customer_selector, get_customer_by_name from gso.services.subscriptions import get_product_id_by_name -from gso.workflows.iptrunk.utils import LAGMember +from gso.utils.helpers import LAGMember from test.workflows import ( assert_aborted, assert_complete, @@ -33,7 +33,7 @@ class MockedNetboxClient: def get_available_interfaces(self): interfaces = [] - for interface in range(1, 5): + for interface in range(5): interface_data = { "name": f"Interface{interface}", "module": {"display": f"Module{interface}"}, @@ -97,8 +97,8 @@ def input_form_wizard_data(router_subscription_factory, faker): "side_a_ae_iface": "LAG1", "side_a_ae_geant_a_sid": faker.geant_sid(), "side_a_ae_members": [ - LAGMember(interface_name=faker.network_interface(), interface_description=faker.sentence()) - for _ in range(5) + LAGMember(interface_name=f"Interface{interface}", interface_description=faker.sentence()) + for interface in range(5) ], } create_ip_trunk_side_b_router_name = {"side_b_node_id": router_side_b} @@ -106,8 +106,8 @@ def input_form_wizard_data(router_subscription_factory, faker): "side_b_ae_iface": "LAG4", "side_b_ae_geant_a_sid": faker.geant_sid(), "side_b_ae_members": [ - LAGMember(interface_name=faker.network_interface(), interface_description=faker.sentence()) - for _ in range(5) + LAGMember(interface_name=f"Interface{interface}", interface_description=faker.sentence()) + for interface in range(5) ], } -- GitLab