From bcd63bc4121f4223cf72acbb6cb9c1281e40562e Mon Sep 17 00:00:00 2001 From: Neda Moeini <neda.moeini@geant.org> Date: Wed, 21 Aug 2024 08:41:59 +0200 Subject: [PATCH] Add EdgePort product block and product type. --- gso/products/__init__.py | 4 + gso/products/product_blocks/edge_port.py | 136 +++++++++++++++++++++++ gso/products/product_types/edge_port.py | 28 +++++ 3 files changed, 168 insertions(+) create mode 100644 gso/products/product_blocks/edge_port.py create mode 100644 gso/products/product_types/edge_port.py diff --git a/gso/products/__init__.py b/gso/products/__init__.py index 9278fbe7..5fc48361 100644 --- a/gso/products/__init__.py +++ b/gso/products/__init__.py @@ -8,6 +8,7 @@ from orchestrator.domain import SUBSCRIPTION_MODEL_REGISTRY from pydantic_forms.types import strEnum +from gso.products.product_types.edge_port import EdgePort from gso.products.product_types.iptrunk import ImportedIptrunk, Iptrunk from gso.products.product_types.lan_switch_interconnect import LanSwitchInterconnect from gso.products.product_types.office_router import ImportedOfficeRouter, OfficeRouter @@ -37,6 +38,7 @@ class ProductName(strEnum): IMPORTED_OFFICE_ROUTER = "Imported office router" OPENGEAR = "Opengear" IMPORTED_OPENGEAR = "Imported Opengear" + EDGE_PORT = "Edge port" class ProductType(strEnum): @@ -57,6 +59,7 @@ class ProductType(strEnum): IMPORTED_OFFICE_ROUTER = ImportedOfficeRouter.__name__ OPENGEAR = Opengear.__name__ IMPORTED_OPENGEAR = Opengear.__name__ + EDGE_PORT = EdgePort.__name__ SUBSCRIPTION_MODEL_REGISTRY.update( @@ -76,5 +79,6 @@ SUBSCRIPTION_MODEL_REGISTRY.update( ProductName.IMPORTED_OFFICE_ROUTER.value: ImportedOfficeRouter, ProductName.OPENGEAR.value: Opengear, ProductName.IMPORTED_OPENGEAR.value: ImportedOpengear, + ProductName.EDGE_PORT.value: EdgePort, }, ) diff --git a/gso/products/product_blocks/edge_port.py b/gso/products/product_blocks/edge_port.py new file mode 100644 index 00000000..cb042581 --- /dev/null +++ b/gso/products/product_blocks/edge_port.py @@ -0,0 +1,136 @@ +"""Edge port product block. + +Edge port sets the boundary between Geant network and an external entity that could also be a different technological +domain still managed by GEANT. In other words, an Edge port determines where the network ends. +""" + +from typing import Annotated + +from annotated_types import Len +from orchestrator.domain.base import ProductBlockModel, T +from orchestrator.types import SubscriptionLifecycle, strEnum +from pydantic import AfterValidator +from pydantic_forms.validators import validate_unique_list +from typing_extensions import Doc + +from gso.products.product_blocks.iptrunk import PhysicalPortCapacity +from gso.products.product_blocks.router import RouterBlockInactive + +LAGMemberList = Annotated[ + list[T], AfterValidator(validate_unique_list), Len(min_length=0), Doc("A list of :term:`LAG` member interfaces.") +] + + +class EdgePortInterfaceBlockInactive( + ProductBlockModel, + lifecycle=[SubscriptionLifecycle.INITIAL], + product_block_name="EdgePortInterfaceBlock", +): + """An inactive edge port interface that's currently inactive.""" + + interface_name: str | None = None + interface_description: str | None = None + + +class EdgePortInterfaceBlockProvisioning( + EdgePortInterfaceBlockInactive, lifecycle=[SubscriptionLifecycle.PROVISIONING] +): + """An IP trunk interface that is being provisioned.""" + + interface_name: str + interface_description: str | None = None + + +class EdgePortInterfaceBlock(EdgePortInterfaceBlockProvisioning, lifecycle=[SubscriptionLifecycle.ACTIVE]): + """An active edge port interface.""" + + interface_name: str + interface_description: str | None = None + + +class EncapsulationType(strEnum): + """Enum representing different Ethernet encapsulation service options. + + Null supports a single service on the port. + Dot1Q supports multiple services for one customer or services for multiple customers. + QinQ expands VLAN space by double-tagging frames. + """ + + DOT1Q = "dot1q" + QINQ = "qinq" + NULL = "null" + + +class EdgePortType(strEnum): + """Types of edge ports.""" + + CUSTOMER = "CUSTOMER" + INFRASTRUCTURE = "INFRASTRUCTURE" + PRIVATE = "PRIVATE" + PUBLIC = "PUBLIC" + RE_INTERCONNECT = "RE_INTERCONNECT" + + +class EdgePortBlockInactive( + ProductBlockModel, lifecycle=[SubscriptionLifecycle.INITIAL], product_block_name="EdgePortBlock" +): + """An edge port that's currently inactive. See :class:`EdgePortBlock`.""" + + node: RouterBlockInactive + edge_port_name: str + enable_lacp: bool + edge_port_encapsulation: EncapsulationType = EncapsulationType.DOT1Q + edge_port_mac_address: str | None = None + edge_port_member_speed: PhysicalPortCapacity + edge_port_minimum_links: int | None = None + edge_port_type: EdgePortType + edge_port_ignore_if_down: bool = False + edge_port_geant_ga_id: str | None = None + edge_port_ae_iface: str | None = None + edge_port_ae_members: LAGMemberList[EdgePortInterfaceBlockInactive] + + +class EdgePortBlockProvisioning(EdgePortBlockInactive, lifecycle=[SubscriptionLifecycle.PROVISIONING]): + """An edge port that's being provisioned. See :class:`EdgePortBlock`.""" + + node: RouterBlockInactive + edge_port_name: str + edge_port_enable_lacp: bool + edge_port_encapsulation: EncapsulationType = EncapsulationType.DOT1Q + edge_port_mac_address: str | None = None + edge_port_member_speed: PhysicalPortCapacity + edge_port_minimum_links: int | None = None + edge_port_type: EdgePortType + edge_port_ignore_if_down: bool = False + edge_port_geant_ga_id: str | None = None + edge_port_ae_iface: str | None = None + edge_port_ae_members: LAGMemberList[EdgePortInterfaceBlockProvisioning] # type: ignore[assignment] + + +class EdgePortBlock(EdgePortBlockProvisioning, lifecycle=[SubscriptionLifecycle.ACTIVE]): + """An edge port that's currently deployed in the network.""" + + #: The router that this edge port is connected to. + node: RouterBlockInactive + #: The name of the edge port. + edge_port_name: str + #: Indicates whether LACP (Link Aggregation Control Protocol) is enabled for this edge port. + edge_port_enable_lacp: bool + #: The type of encapsulation used on this edge port, by default DOT1Q. + edge_port_encapsulation: EncapsulationType = EncapsulationType.DOT1Q + #: The MAC address assigned to this edge port, if applicable. + edge_port_mac_address: str | None = None + #: The speed capacity of each member in the physical port. + edge_port_member_speed: PhysicalPortCapacity + #: The minimum number of links required for this edge port. + edge_port_minimum_links: int | None = None + #: The type of edge port (e.g., access, trunk). + edge_port_type: EdgePortType + #: If set to True, the edge port will be ignored if it is down. + edge_port_ignore_if_down: bool = False + #: The GEANT GA ID associated with this edge port, if any. + edge_port_geant_ga_id: str | None = None + #: The interface name for the aggregated Ethernet (AE) interface, if applicable. + edge_port_ae_iface: str | None = None + #: A list of LAG members associated with this edge port. + edge_port_ae_members: LAGMemberList[EdgePortInterfaceBlock] # type: ignore[assignment] diff --git a/gso/products/product_types/edge_port.py b/gso/products/product_types/edge_port.py new file mode 100644 index 00000000..021aa026 --- /dev/null +++ b/gso/products/product_types/edge_port.py @@ -0,0 +1,28 @@ +"""Product types for Edge Port.""" + +from orchestrator.domain.base import SubscriptionModel +from orchestrator.types import SubscriptionLifecycle + +from gso.products.product_blocks.edge_port import ( + EdgePortBlock, + EdgePortBlockInactive, + EdgePortBlockProvisioning, +) + + +class EdgePortInactive(SubscriptionModel, is_base=True): + """An Edge Port that is inactive.""" + + edge_port: EdgePortBlockInactive + + +class EdgePortProvisioning(EdgePortInactive, lifecycle=[SubscriptionLifecycle.PROVISIONING]): + """An Edge Port that is being provisioned.""" + + edge_port: EdgePortBlockProvisioning + + +class EdgePort(EdgePortProvisioning, lifecycle=[SubscriptionLifecycle.ACTIVE]): + """An Edge Port that is active.""" + + edge_port: EdgePortBlock -- GitLab