From 4cb01bfd661313ce0507b67564cb92fde4db0e33 Mon Sep 17 00:00:00 2001
From: Karel van Klink <karel.vanklink@geant.org>
Date: Mon, 9 Sep 2024 14:53:34 +0200
Subject: [PATCH] Add product blocks for SBP and BGP session. Small edit to
 edge port product model.

---
 gso/api/v1/network.py                         |  2 -
 gso/products/product_blocks/bgp_session.py    | 71 +++++++++++++++++++
 gso/products/product_blocks/edge_port.py      | 47 ++----------
 .../product_blocks/service_binding_port.py    | 66 +++++++++++++++++
 gso/services/infoblox.py                      |  2 +-
 gso/utils/helpers.py                          |  4 +-
 gso/workflows/edge_port/create_edge_port.py   | 17 ++---
 gso/workflows/edge_port/modify_edge_port.py   | 25 +++----
 .../edge_port/terminate_edge_port.py          |  2 +-
 .../iptrunk/create_imported_iptrunk.py        |  1 -
 gso/workflows/iptrunk/create_iptrunk.py       |  3 -
 gso/workflows/iptrunk/migrate_iptrunk.py      |  2 -
 .../iptrunk/modify_trunk_interface.py         |  2 -
 gso/workflows/iptrunk/terminate_iptrunk.py    |  1 -
 .../create_lan_switch_interconnect.py         |  9 ---
 gso/workflows/router/create_router.py         |  1 -
 gso/workflows/router/promote_p_to_pe.py       |  1 -
 gso/workflows/router/terminate_router.py      |  1 -
 gso/workflows/router/update_ibgp_mesh.py      |  2 -
 test/cli/test_imports.py                      |  1 -
 test/fixtures.py                              |  1 -
 test/utils/test_helpers.py                    |  1 -
 test/workflows/iptrunk/test_create_iptrunk.py |  1 -
 .../iptrunk/test_modify_trunk_interface.py    |  1 -
 24 files changed, 162 insertions(+), 102 deletions(-)
 create mode 100644 gso/products/product_blocks/bgp_session.py
 create mode 100644 gso/products/product_blocks/service_binding_port.py

diff --git a/gso/api/v1/network.py b/gso/api/v1/network.py
index e1a157990..e4a19de9e 100644
--- a/gso/api/v1/network.py
+++ b/gso/api/v1/network.py
@@ -10,8 +10,6 @@ from orchestrator.security import authorize
 from orchestrator.services.subscriptions import build_extended_domain_model
 from starlette import status
 
-from gso.annotations.coordinates import LatitudeCoordinate, LongitudeCoordinate
-from gso.annotations.interfaces import PhysicalPortCapacity
 from gso.products.product_blocks.iptrunk import IptrunkType
 from gso.products.product_blocks.router import RouterRole
 from gso.services.subscriptions import get_active_iptrunk_subscriptions
diff --git a/gso/products/product_blocks/bgp_session.py b/gso/products/product_blocks/bgp_session.py
new file mode 100644
index 000000000..ebf82d32d
--- /dev/null
+++ b/gso/products/product_blocks/bgp_session.py
@@ -0,0 +1,71 @@
+""":term:`BGP` session product block."""
+
+from ipaddress import IPv4Address, IPv6Address
+
+from orchestrator.domain.base import ProductBlockModel
+from orchestrator.types import SubscriptionLifecycle
+from pydantic_forms.types import strEnum
+
+
+class IPFamily(strEnum):
+    """Possible :term:`IP` families of a :term:`BGP` peering."""
+
+    V4UNICAST = "v4unicast"
+    V6UNICAST = "v6unicast"
+    V4MULTICAST = "v4multicast"
+    V6MULTICAST = "v6multicast"
+
+
+class BGPSessionInactive(ProductBlockModel, lifecycle=[SubscriptionLifecycle.INITIAL], product_block_name="BGPSession"):
+    """A :term:`BGP` session that is currently inactive. See :class:`BGPSession`."""
+
+    peer_address: IPv4Address | IPv6Address | None = None
+    bfd_enabled: bool | None = None
+    bfd_interval: int | None = None
+    bfd_multiplier: int | None = None
+    families: list[IPFamily] | None = None
+    has_custom_policies: bool | None = None
+    authentication_key: str | None = None
+    multipath_enabled: bool | None = None
+    send_default_route: bool | None = None
+    is_multi_hop: bool = False
+
+
+class BGPSessionProvisioning(BGPSessionInactive, lifecycle=[SubscriptionLifecycle.PROVISIONING]):
+    """A :term:`BGP` session that is currently being provisioned. See :class:`BGPSession`."""
+
+    peer_address: IPv4Address | IPv6Address
+    bfd_enabled: bool
+    bfd_interval: int | None = None
+    bfd_multiplier: int | None = None
+    families: list[IPFamily]
+    has_custom_policies: bool
+    authentication_key: str
+    multipath_enabled: bool
+    send_default_route: bool
+    is_multi_hop: bool
+
+
+class BGPSession(BGPSessionProvisioning, lifecycle=[SubscriptionLifecycle.ACTIVE]):
+    """A :term:`BGP` session that is currently deployed in the network."""
+
+    #: The peering address of the session.
+    peer_address: IPv4Address | IPv6Address
+    #: Whether :term:`BFD` is enabled.
+    bfd_enabled: bool
+    #: The :term:`BFD` interval, if enabled.
+    bfd_interval: int | None = None
+    #: The :term:`BFD` multiplier, if enabled.
+    bfd_multiplier: int | None = None
+    #: The list of :term:`IP` families enabled for this session.
+    families: list[IPFamily]
+    #: Whether any custom policies exist for this session.
+    has_custom_policies: bool
+    #: The authentication key of the :term:`BGP` session.
+    authentication_key: str
+    #: Whether multipath is enabled.
+    multipath_enabled: bool
+    #: Whether we send a last resort route.
+    send_default_route: bool
+    #: Whether this session is multi-hop or not. Defaults to no.
+    is_multi_hop: bool
diff --git a/gso/products/product_blocks/edge_port.py b/gso/products/product_blocks/edge_port.py
index 2d72ff69b..ffa5ea90b 100644
--- a/gso/products/product_blocks/edge_port.py
+++ b/gso/products/product_blocks/edge_port.py
@@ -4,48 +4,11 @@ Edge port sets the boundary between Geant network and an external entity that co
 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.domain.base import ProductBlockModel
 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 RouterBlock, RouterBlockInactive, RouterBlockProvisioning
-
-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
+from gso.utils.types.interfaces import LAGMember, LAGMemberList, PhysicalPortCapacity
 
 
 class EncapsulationType(strEnum):
@@ -87,7 +50,7 @@ class EdgePortBlockInactive(
     edge_port_type: EdgePortType | None = None
     edge_port_ignore_if_down: bool = False
     edge_port_geant_ga_id: str | None = None
-    edge_port_ae_members: LAGMemberList[EdgePortInterfaceBlockInactive]
+    edge_port_ae_members: LAGMemberList[LAGMember]
 
 
 class EdgePortBlockProvisioning(EdgePortBlockInactive, lifecycle=[SubscriptionLifecycle.PROVISIONING]):
@@ -104,7 +67,7 @@ class EdgePortBlockProvisioning(EdgePortBlockInactive, lifecycle=[SubscriptionLi
     edge_port_type: EdgePortType
     edge_port_ignore_if_down: bool = False
     edge_port_geant_ga_id: str | None = None
-    edge_port_ae_members: LAGMemberList[EdgePortInterfaceBlockProvisioning]  # type: ignore[assignment]
+    edge_port_ae_members: LAGMemberList[LAGMember]
 
 
 class EdgePortBlock(EdgePortBlockProvisioning, lifecycle=[SubscriptionLifecycle.ACTIVE]):
@@ -133,4 +96,4 @@ class EdgePortBlock(EdgePortBlockProvisioning, lifecycle=[SubscriptionLifecycle.
     #: The GEANT GA ID associated with this edge port, if any.
     edge_port_geant_ga_id: str | None = None
     #: A list of LAG members associated with this edge port.
-    edge_port_ae_members: LAGMemberList[EdgePortInterfaceBlock]  # type: ignore[assignment]
+    edge_port_ae_members: LAGMemberList[LAGMember]
diff --git a/gso/products/product_blocks/service_binding_port.py b/gso/products/product_blocks/service_binding_port.py
new file mode 100644
index 000000000..a38a5c6e7
--- /dev/null
+++ b/gso/products/product_blocks/service_binding_port.py
@@ -0,0 +1,66 @@
+"""Service Binding Port.
+
+A service binding port is used to logically attach an edge port to a customer service using a :term:`VLAN`.
+"""
+
+from ipaddress import IPv4Address, IPv6Address
+from typing import Annotated
+
+from orchestrator.domain.base import ProductModel
+from orchestrator.types import SubscriptionLifecycle
+from pydantic import Field
+from pydantic_forms.types import strEnum
+
+VLANID = Annotated[int, Field(gt=0, lt=4096)]
+
+
+class SBPType(strEnum):
+    """Enumerator for the two allowed types of service binding port: layer 2 or layer 3."""
+
+    L2 = "l2"
+    L3 = "l3"
+
+
+class ServiceBindingPortInactive(
+    ProductModel, lifecycle=[SubscriptionLifecycle.INITIAL], product_block_name="ServiceBindingPort"
+):
+    """A Service Binding Port that's currently inactive. See :class:`ServiceBindingPort`."""
+
+    is_tagged: bool | None = None
+    vlan_id: VLANID | None = None
+    sbp_type: SBPType | None = None
+    ipv4_address: IPv4Address | None = None
+    ipv6_address: IPv6Address | None = None
+    custom_firewall_filters: bool | None = None
+    geant_sid: str | None = None
+
+
+class ServiceBindingPortProvisioning(ServiceBindingPortInactive, lifecycle=[SubscriptionLifecycle.PROVISIONING]):
+    """A Service Binding Port that's currently being provisioned. See :class:`ServiceBindingPort`."""
+
+    is_tagged: bool
+    vlan_id: VLANID | None = None
+    sbp_type: SBPType
+    ipv4_address: IPv4Address | None = None
+    ipv6_address: IPv6Address | None = None
+    custom_firewall_filters: bool
+    geant_sid: str
+
+
+class ServiceBindingPort(ServiceBindingPortProvisioning, lifecycle=[SubscriptionLifecycle.ACTIVE]):
+    """A service binding port that is actively used in the network."""
+
+    #: Whether this :term:`VLAN` is tagged or not.
+    is_tagged: bool
+    #: The :term:`VLAN` ID.
+    vlan_id: VLANID | None = None
+    #: Is this service binding port layer 2 or 3?
+    sbp_type: SBPType
+    #: If layer 3, IPv4 resources.
+    ipv4_address: IPv4Address | None = None
+    #: If layer 3, IPv6 resources.
+    ipv6_address: IPv6Address | None = None
+    #: Any custom firewall filters that the partner may require.
+    custom_firewall_filters: bool
+    #: The GÉANT service ID of this binding port.
+    geant_sid: str
diff --git a/gso/services/infoblox.py b/gso/services/infoblox.py
index c47da5bad..fc33f3b03 100644
--- a/gso/services/infoblox.py
+++ b/gso/services/infoblox.py
@@ -37,7 +37,7 @@ def _setup_connection() -> tuple[connector.Connector, IPAMParams]:
     return connector.Connector(options), oss
 
 
-def _allocate_network(
+def _allocate_network(  # noqa: PLR0917
     conn: connector.Connector,
     dns_view: str,
     network_view: str,
diff --git a/gso/utils/helpers.py b/gso/utils/helpers.py
index 00e420c04..4e4c7646d 100644
--- a/gso/utils/helpers.py
+++ b/gso/utils/helpers.py
@@ -11,10 +11,10 @@ from gso.products.product_blocks.router import RouterRole
 from gso.products.product_types.router import Router
 from gso.services import subscriptions
 from gso.services.netbox_client import NetboxClient
-from gso.utils.types.interfaces import PhysicalPortCapacity
-from gso.utils.types.ip_address import IPv4AddressType
 from gso.services.partners import get_all_partners
 from gso.utils.shared_enums import Vendor
+from gso.utils.types.interfaces import PhysicalPortCapacity
+from gso.utils.types.ip_address import IPv4AddressType
 
 if TYPE_CHECKING:
     from gso.products.product_blocks.iptrunk import IptrunkInterfaceBlock
diff --git a/gso/workflows/edge_port/create_edge_port.py b/gso/workflows/edge_port/create_edge_port.py
index ddca7d48c..c961cd7f3 100644
--- a/gso/workflows/edge_port/create_edge_port.py
+++ b/gso/workflows/edge_port/create_edge_port.py
@@ -1,7 +1,6 @@
 """A creation workflow for adding a new edge port to the network."""
 
 from typing import Annotated, Any, Self
-from uuid import uuid4
 
 from annotated_types import Len
 from orchestrator import step, workflow
@@ -9,6 +8,7 @@ from orchestrator.forms import FormPage
 from orchestrator.forms.validators import Choice
 from orchestrator.targets import Target
 from orchestrator.types import FormGenerator, State, SubscriptionLifecycle, UUIDstr
+from orchestrator.utils.errors import ProcessFailureError
 from orchestrator.workflow import StepList, begin, done
 from orchestrator.workflows.steps import resync, set_status, store_process_subscription
 from orchestrator.workflows.utils import wrap_create_initial_input_form
@@ -16,8 +16,7 @@ from pydantic import AfterValidator, ConfigDict, model_validator
 from pydantic_forms.validators import validate_unique_list
 from pynetbox.models.dcim import Interfaces
 
-from gso.products.product_blocks.edge_port import EdgePortInterfaceBlockInactive, EdgePortType, EncapsulationType
-from gso.products.product_blocks.iptrunk import PhysicalPortCapacity
+from gso.products.product_blocks.edge_port import EdgePortType, EncapsulationType
 from gso.products.product_blocks.router import RouterRole
 from gso.products.product_types.edge_port import EdgePortInactive, EdgePortProvisioning
 from gso.products.product_types.router import Router
@@ -25,13 +24,13 @@ from gso.services import lso_client, subscriptions
 from gso.services.lso_client import lso_interaction
 from gso.services.netbox_client import NetboxClient
 from gso.utils.helpers import (
-    LAGMember,
     available_interfaces_choices,
     available_service_lags_choices,
     partner_choice,
     validate_edge_port_number_of_members_based_on_lacp,
 )
-from gso.utils.types import TTNumber
+from gso.utils.types.interfaces import LAGMember, PhysicalPortCapacity
+from gso.utils.types.tt_number import TTNumber
 
 
 def initial_input_form_generator(product_name: str) -> FormGenerator:
@@ -134,9 +133,7 @@ def initialize_subscription(
     subscription.description = f"Edge Port {name} on {router.router_fqdn}, {partner}, {geant_ga_id or ""}"
     subscription.edge_port.edge_port_description = description
     for member in ae_members:
-        subscription.edge_port.edge_port_ae_members.append(
-            EdgePortInterfaceBlockInactive.new(subscription_id=uuid4(), **member),
-        )
+        subscription.edge_port.edge_port_ae_members.append(LAGMember(**member))
     subscription = EdgePortProvisioning.from_other_lifecycle(subscription, SubscriptionLifecycle.PROVISIONING)
 
     return {"subscription": subscription}
@@ -181,8 +178,8 @@ def allocate_interfaces_in_netbox(subscription: EdgePortProvisioning) -> None:
         fqdn = subscription.edge_port.edge_port_node.router_fqdn
         iface_name = interface.interface_name
         if not fqdn or not iface_name:
-            msg = f"FQDN and/or interface name missing in subscription {interface.owner_subscription_id}"
-            raise ValueError(msg)
+            msg = "FQDN and/or interface name missing in subscription"
+            raise ProcessFailureError(msg, details=subscription.subscription_id)
 
         NetboxClient().allocate_interface(device_name=fqdn, iface_name=iface_name)
 
diff --git a/gso/workflows/edge_port/modify_edge_port.py b/gso/workflows/edge_port/modify_edge_port.py
index cb7bcc9bc..7e2801988 100644
--- a/gso/workflows/edge_port/modify_edge_port.py
+++ b/gso/workflows/edge_port/modify_edge_port.py
@@ -1,7 +1,6 @@
 """Modify an existing edge port subscription."""
 
 from typing import Annotated, Any, Self
-from uuid import uuid4
 
 from annotated_types import Len
 from orchestrator import workflow
@@ -14,22 +13,18 @@ from pydantic import AfterValidator, ConfigDict, model_validator
 from pydantic_forms.types import FormGenerator, UUIDstr
 from pydantic_forms.validators import ReadOnlyField, validate_unique_list
 
-from gso.products.product_blocks.edge_port import (
-    EdgePortInterfaceBlock,
-    EncapsulationType,
-)
-from gso.products.product_blocks.iptrunk import PhysicalPortCapacity
+from gso.products.product_blocks.edge_port import EncapsulationType
 from gso.products.product_types.edge_port import EdgePort
 from gso.services.lso_client import execute_playbook, lso_interaction
 from gso.services.netbox_client import NetboxClient
 from gso.services.partners import get_partner_by_id
 from gso.utils.helpers import (
-    LAGMember,
     available_interfaces_choices,
     available_interfaces_choices_including_current_members,
     validate_edge_port_number_of_members_based_on_lacp,
 )
-from gso.utils.types import TTNumber
+from gso.utils.types.interfaces import LAGMember, PhysicalPortCapacity
+from gso.utils.types.tt_number import TTNumber
 
 
 def initial_input_form_generator(subscription_id: UUIDstr) -> FormGenerator:
@@ -151,9 +146,7 @@ def modify_edge_port_subscription(
     )
     subscription.edge_port.edge_port_ae_members.clear()
     for member in ae_members:
-        subscription.edge_port.edge_port_ae_members.append(
-            EdgePortInterfaceBlock.new(subscription_id=uuid4(), **member),
-        )
+        subscription.edge_port.edge_port_ae_members.append(LAGMember(**member))
     subscription.save()
 
     return {
@@ -166,19 +159,19 @@ def modify_edge_port_subscription(
 @step("Update interfaces in NetBox")
 def update_interfaces_in_netbox(
     subscription: EdgePort,
-    removed_ae_members: list[dict],
-    previous_ae_members: list[dict],
+    removed_ae_members: list[LAGMember],
+    previous_ae_members: list[LAGMember],
 ) -> dict[str, Any]:
     """Update the interfaces in NetBox."""
     nbclient = NetboxClient()
     # Free removed interfaces
     for member in removed_ae_members:
-        nbclient.free_interface(subscription.edge_port.edge_port_node.router_fqdn, member["interface_name"])
+        nbclient.free_interface(subscription.edge_port.edge_port_node.router_fqdn, member.interface_name)
     # Attach physical interfaces to :term:`LAG`
     # Update interface description to subscription ID
     # Reserve interfaces
-    for member in subscription.edge_port.edge_port_ae_members:  # type: ignore[assignment]
-        if any(prev_member["interface_name"] == member.interface_name for prev_member in previous_ae_members):
+    for member in subscription.edge_port.edge_port_ae_members:
+        if any(prev_member.interface_name == member.interface_name for prev_member in previous_ae_members):
             continue
         nbclient.attach_interface_to_lag(
             device_name=subscription.edge_port.edge_port_node.router_fqdn,
diff --git a/gso/workflows/edge_port/terminate_edge_port.py b/gso/workflows/edge_port/terminate_edge_port.py
index 53569c1ed..e191d330b 100644
--- a/gso/workflows/edge_port/terminate_edge_port.py
+++ b/gso/workflows/edge_port/terminate_edge_port.py
@@ -14,7 +14,7 @@ from pydantic_forms.types import FormGenerator, UUIDstr
 from gso.products.product_types.edge_port import EdgePort
 from gso.services.lso_client import execute_playbook, lso_interaction
 from gso.services.netbox_client import NetboxClient
-from gso.utils.types import TTNumber
+from gso.utils.types.tt_number import TTNumber
 
 
 def initial_input_form_generator() -> FormGenerator:
diff --git a/gso/workflows/iptrunk/create_imported_iptrunk.py b/gso/workflows/iptrunk/create_imported_iptrunk.py
index a03ce8df9..4d7543750 100644
--- a/gso/workflows/iptrunk/create_imported_iptrunk.py
+++ b/gso/workflows/iptrunk/create_imported_iptrunk.py
@@ -13,7 +13,6 @@ from orchestrator.workflows.steps import resync, set_status, store_process_subsc
 from pydantic import AfterValidator, ConfigDict
 from pydantic_forms.validators import validate_unique_list
 
-from gso.annotations.interfaces import LAGMember, LAGMemberList, PhysicalPortCapacity
 from gso.products import ProductName
 from gso.products.product_blocks.iptrunk import IptrunkInterfaceBlockInactive, IptrunkType
 from gso.products.product_types.iptrunk import ImportedIptrunkInactive
diff --git a/gso/workflows/iptrunk/create_iptrunk.py b/gso/workflows/iptrunk/create_iptrunk.py
index 8dda84798..e10401186 100644
--- a/gso/workflows/iptrunk/create_iptrunk.py
+++ b/gso/workflows/iptrunk/create_iptrunk.py
@@ -20,9 +20,6 @@ from pydantic import ConfigDict
 from pydantic_forms.validators import ReadOnlyField
 from pynetbox.models.dcim import Interfaces
 
-from gso.annotations.interfaces import JuniperLAGMember, LAGMember, LAGMemberList, PhysicalPortCapacity
-from gso.annotations.netbox_router import NetboxEnabledRouter
-from gso.annotations.tt_number import TTNumber
 from gso.products.product_blocks.iptrunk import (
     IptrunkInterfaceBlockInactive,
     IptrunkSideBlockInactive,
diff --git a/gso/workflows/iptrunk/migrate_iptrunk.py b/gso/workflows/iptrunk/migrate_iptrunk.py
index 5afc1e43d..5a7cafb03 100644
--- a/gso/workflows/iptrunk/migrate_iptrunk.py
+++ b/gso/workflows/iptrunk/migrate_iptrunk.py
@@ -24,8 +24,6 @@ from pydantic import ConfigDict
 from pydantic_forms.validators import ReadOnlyField
 from pynetbox.models.dcim import Interfaces
 
-from gso.annotations.interfaces import JuniperAEInterface, JuniperLAGMember, LAGMember, LAGMemberList
-from gso.annotations.tt_number import TTNumber
 from gso.products.product_blocks.iptrunk import IptrunkInterfaceBlock, IptrunkType
 from gso.products.product_types.iptrunk import Iptrunk
 from gso.products.product_types.router import Router
diff --git a/gso/workflows/iptrunk/modify_trunk_interface.py b/gso/workflows/iptrunk/modify_trunk_interface.py
index a43e36348..16de24623 100644
--- a/gso/workflows/iptrunk/modify_trunk_interface.py
+++ b/gso/workflows/iptrunk/modify_trunk_interface.py
@@ -15,8 +15,6 @@ from orchestrator.workflows.utils import wrap_modify_initial_input_form
 from pydantic import ConfigDict
 from pydantic_forms.validators import Label, ReadOnlyField
 
-from gso.annotations.interfaces import JuniperLAGMember, LAGMember, LAGMemberList, PhysicalPortCapacity
-from gso.annotations.tt_number import TTNumber
 from gso.products.product_blocks.iptrunk import (
     IptrunkInterfaceBlock,
     IptrunkSideBlock,
diff --git a/gso/workflows/iptrunk/terminate_iptrunk.py b/gso/workflows/iptrunk/terminate_iptrunk.py
index e86a61eb2..9b5cf71a8 100644
--- a/gso/workflows/iptrunk/terminate_iptrunk.py
+++ b/gso/workflows/iptrunk/terminate_iptrunk.py
@@ -17,7 +17,6 @@ from orchestrator.workflows.steps import (
 )
 from orchestrator.workflows.utils import wrap_modify_initial_input_form
 
-from gso.annotations.tt_number import TTNumber
 from gso.products.product_blocks.iptrunk import IptrunkSideBlock
 from gso.products.product_types.iptrunk import Iptrunk
 from gso.services import infoblox
diff --git a/gso/workflows/lan_switch_interconnect/create_lan_switch_interconnect.py b/gso/workflows/lan_switch_interconnect/create_lan_switch_interconnect.py
index de28e1ced..973cf1d88 100644
--- a/gso/workflows/lan_switch_interconnect/create_lan_switch_interconnect.py
+++ b/gso/workflows/lan_switch_interconnect/create_lan_switch_interconnect.py
@@ -13,15 +13,6 @@ from orchestrator.workflows.utils import wrap_create_initial_input_form
 from pydantic import AfterValidator, ConfigDict
 from pydantic_forms.validators import Divider, ReadOnlyField
 
-from gso.annotations.interfaces import (
-    JuniperAEInterface,
-    JuniperLAGMember,
-    JuniperPhyInterface,
-    LAGMember,
-    PhysicalPortCapacity,
-    validate_interface_names_are_unique,
-)
-from gso.annotations.tt_number import TTNumber
 from gso.products.product_blocks.lan_switch_interconnect import (
     LanSwitchInterconnectAddressSpace,
     LanSwitchInterconnectInterfaceBlockInactive,
diff --git a/gso/workflows/router/create_router.py b/gso/workflows/router/create_router.py
index 8a0fc9b78..04d38b750 100644
--- a/gso/workflows/router/create_router.py
+++ b/gso/workflows/router/create_router.py
@@ -14,7 +14,6 @@ from orchestrator.workflows.utils import wrap_create_initial_input_form
 from pydantic import ConfigDict, model_validator
 from pydantic_forms.validators import ReadOnlyField
 
-from gso.annotations.tt_number import TTNumber
 from gso.products.product_blocks.router import RouterRole
 from gso.products.product_types.router import RouterInactive, RouterProvisioning
 from gso.products.product_types.site import Site
diff --git a/gso/workflows/router/promote_p_to_pe.py b/gso/workflows/router/promote_p_to_pe.py
index 0b7ddf3bd..a82b4823f 100644
--- a/gso/workflows/router/promote_p_to_pe.py
+++ b/gso/workflows/router/promote_p_to_pe.py
@@ -15,7 +15,6 @@ from orchestrator.workflows.steps import resync, store_process_subscription, uns
 from orchestrator.workflows.utils import wrap_modify_initial_input_form
 from pydantic import ConfigDict, model_validator
 
-from gso.annotations.tt_number import TTNumber
 from gso.products.product_blocks.router import RouterRole
 from gso.products.product_types.router import Router
 from gso.services.kentik_client import KentikClient, NewKentikDevice
diff --git a/gso/workflows/router/terminate_router.py b/gso/workflows/router/terminate_router.py
index dfe846abd..9bc48f8ce 100644
--- a/gso/workflows/router/terminate_router.py
+++ b/gso/workflows/router/terminate_router.py
@@ -20,7 +20,6 @@ from orchestrator.workflows.steps import (
 from orchestrator.workflows.utils import wrap_modify_initial_input_form
 from requests import HTTPError
 
-from gso.annotations.tt_number import TTNumber
 from gso.products.product_blocks.router import RouterRole
 from gso.products.product_types.router import Router
 from gso.services import infoblox
diff --git a/gso/workflows/router/update_ibgp_mesh.py b/gso/workflows/router/update_ibgp_mesh.py
index 72a1b5935..298c0878a 100644
--- a/gso/workflows/router/update_ibgp_mesh.py
+++ b/gso/workflows/router/update_ibgp_mesh.py
@@ -12,8 +12,6 @@ from orchestrator.workflows.steps import resync, store_process_subscription, uns
 from orchestrator.workflows.utils import wrap_modify_initial_input_form
 from pydantic import ConfigDict, model_validator
 
-from gso.annotations.snmp import SNMPVersion
-from gso.annotations.tt_number import TTNumber
 from gso.products.product_blocks.router import RouterRole
 from gso.products.product_types.router import Router
 from gso.services import librenms_client
diff --git a/test/cli/test_imports.py b/test/cli/test_imports.py
index 55a07431c..d794d0f0a 100644
--- a/test/cli/test_imports.py
+++ b/test/cli/test_imports.py
@@ -4,7 +4,6 @@ from unittest.mock import patch
 
 import pytest
 
-from gso.annotations.interfaces import PhysicalPortCapacity
 from gso.cli.imports import (
     import_iptrunks,
     import_office_routers,
diff --git a/test/fixtures.py b/test/fixtures.py
index 23ebb0778..0bb920c0c 100644
--- a/test/fixtures.py
+++ b/test/fixtures.py
@@ -21,7 +21,6 @@ from pydantic_forms.core import FormPage
 from pydantic_forms.types import FormGenerator, SubscriptionMapping
 from pydantic_forms.validators import Choice
 
-from gso.annotations.interfaces import PhysicalPortCapacity
 from gso.products import ProductName
 from gso.products.product_blocks.iptrunk import (
     IptrunkInterfaceBlock,
diff --git a/test/utils/test_helpers.py b/test/utils/test_helpers.py
index 2b13bcdb0..28779cd70 100644
--- a/test/utils/test_helpers.py
+++ b/test/utils/test_helpers.py
@@ -3,7 +3,6 @@ from unittest.mock import patch
 import pytest
 from orchestrator.types import SubscriptionLifecycle
 
-from gso.annotations.tt_number import validate_tt_number
 from gso.products import Router
 from gso.products.product_blocks.iptrunk import IptrunkInterfaceBlock
 from gso.products.product_blocks.router import RouterRole
diff --git a/test/workflows/iptrunk/test_create_iptrunk.py b/test/workflows/iptrunk/test_create_iptrunk.py
index c97d73253..267cd3827 100644
--- a/test/workflows/iptrunk/test_create_iptrunk.py
+++ b/test/workflows/iptrunk/test_create_iptrunk.py
@@ -4,7 +4,6 @@ from unittest.mock import patch
 import pytest
 from infoblox_client.objects import HostRecord
 
-from gso.annotations.interfaces import PhysicalPortCapacity
 from gso.products import Iptrunk, ProductName
 from gso.products.product_blocks.iptrunk import IptrunkType
 from gso.services.subscriptions import get_product_id_by_name
diff --git a/test/workflows/iptrunk/test_modify_trunk_interface.py b/test/workflows/iptrunk/test_modify_trunk_interface.py
index 34a0bbb34..85c395e8b 100644
--- a/test/workflows/iptrunk/test_modify_trunk_interface.py
+++ b/test/workflows/iptrunk/test_modify_trunk_interface.py
@@ -2,7 +2,6 @@ from unittest.mock import patch
 
 import pytest
 
-from gso.annotations.interfaces import LAGMemberList, PhysicalPortCapacity
 from gso.products import Iptrunk
 from gso.products.product_blocks.iptrunk import IptrunkType
 from gso.utils.shared_enums import Vendor
-- 
GitLab