From c2e26d403617d8a24ca442af090a8e8f9b836f44 Mon Sep 17 00:00:00 2001
From: Neda Moeini <neda.moeini@geant.org>
Date: Fri, 9 May 2025 14:07:04 +0200
Subject: [PATCH 01/24] IX port PB

---
 gso/products/product_blocks/ix_port.py | 67 ++++++++++++++++++++++++++
 1 file changed, 67 insertions(+)
 create mode 100644 gso/products/product_blocks/ix_port.py

diff --git a/gso/products/product_blocks/ix_port.py b/gso/products/product_blocks/ix_port.py
new file mode 100644
index 000000000..f9fec2e6b
--- /dev/null
+++ b/gso/products/product_blocks/ix_port.py
@@ -0,0 +1,67 @@
+"""IX Port Product Blocks."""
+
+from orchestrator.domain.base import ProductBlockModel
+from orchestrator.types import SubscriptionLifecycle
+from pydantic_forms.types import strEnum
+
+from gso.products.product_blocks.service_binding_port import ServiceBindingPortInactive, ServiceBindingPortProvisioning, \
+    ServiceBindingPort
+
+
+class SessionState(strEnum):
+    """Session state of the IX Port."""
+
+    PROVISIONING = "provisioning"
+    """Provisioning."""
+    ACTIVE = "active"
+    """Active."""
+    DISABLED = "disabled"
+    """Disabled."""
+
+
+class PeeringConnectionInactive(ProductBlockModel, lifecycle=[SubscriptionLifecycle.INITIAL],
+                                product_block_name="PeeringConnectionBlock"):
+    """A Peering Connection that's currently inactive. See `PeeringConnectionBlock`."""
+
+    sbp: ServiceBindingPortInactive
+    prefix_limit: int | None = None
+
+
+class PeeringConnectionProvisioning(PeeringConnectionInactive, lifecycle=[SubscriptionLifecycle.PROVISIONING]):
+    """A Peering Connection that's currently being provisioned. See `PeeringConnectionBlock`."""
+
+    sbp: ServiceBindingPortProvisioning
+    prefix_limit: int | None = None
+
+
+class PeeringConnection(PeeringConnectionProvisioning, lifecycle=[SubscriptionLifecycle.ACTIVE]):
+    """A Peering Connection that's currently active."""
+
+    sbp: ServiceBindingPort
+    prefix_limit: int | None = None
+
+
+class IXPortBlockInactive(
+    ProductBlockModel, lifecycle=[SubscriptionLifecycle.INITIAL], product_block_name="IXPortBlock"
+):
+    """An IX Port that's not yet provisioned. See ``IXPortBlock``."""
+
+    peering_connection: list[PeeringConnectionInactive]
+    minimum_hold_timer: int | None = None
+    session_state: SessionState | None = None
+
+
+class IXPortBlockProvisioning(IXPortBlockInactive, lifecycle=[SubscriptionLifecycle.PROVISIONING]):
+    """An IX Port that's being provisioned. See ``IXPortBlock``."""
+
+    peering_connection: list[PeeringConnectionProvisioning]
+    minimum_hold_timer: int | None = None
+    session_state: SessionState | None = None
+
+
+class IXPortBlock(IXPortBlockProvisioning, lifecycle=[SubscriptionLifecycle.ACTIVE]):
+    """An Internet Exchange Port that's active."""
+
+    peering_connection: list[PeeringConnection]
+    minimum_hold_timer: int | None = None
+    session_state: SessionState
-- 
GitLab


From dad058285ffc5c8608e5d8d8c6801dfb5cedcc0b Mon Sep 17 00:00:00 2001
From: Neda Moeini <neda.moeini@geant.org>
Date: Fri, 9 May 2025 14:07:22 +0200
Subject: [PATCH 02/24] Private Peer Port PB

---
 .../product_blocks/private_peer_port.py       | 27 +++++++++++++++++++
 1 file changed, 27 insertions(+)
 create mode 100644 gso/products/product_blocks/private_peer_port.py

diff --git a/gso/products/product_blocks/private_peer_port.py b/gso/products/product_blocks/private_peer_port.py
new file mode 100644
index 000000000..9e8d8ac8f
--- /dev/null
+++ b/gso/products/product_blocks/private_peer_port.py
@@ -0,0 +1,27 @@
+"""Private Peer Port Product Blocks."""
+
+from orchestrator.domain.base import ProductBlockModel
+from orchestrator.types import SubscriptionLifecycle
+
+from gso.products.product_blocks.ix_port import PeeringConnection, PeeringConnectionInactive, \
+    PeeringConnectionProvisioning
+
+
+class PrivatePeerPortBlockInactive(
+    ProductBlockModel, lifecycle=[SubscriptionLifecycle.INITIAL], product_block_name="PrivatePeerPortBlock"
+):
+    """An Private Peer Port that's not yet provisioned. See ``PrivatePeerPortBlock``."""
+
+    peering_connection: list[PeeringConnectionInactive]
+
+
+class PrivatePeerPortBlockProvisioning(PrivatePeerPortBlockInactive, lifecycle=[SubscriptionLifecycle.PROVISIONING]):
+    """An Private Peer Port that's being provisioned. See ``PrivatePeerPortBlock``."""
+
+    peering_connection: list[PeeringConnectionProvisioning]
+
+
+class PrivatePeerPortBlock(PrivatePeerPortBlockProvisioning, lifecycle=[SubscriptionLifecycle.ACTIVE]):
+    """A Private Provider Port that's active."""
+
+    peering_connection: list[PeeringConnection]
-- 
GitLab


From df016c909da2d8791bb0d630e175523f01140d17 Mon Sep 17 00:00:00 2001
From: Neda Moeini <neda.moeini@geant.org>
Date: Fri, 9 May 2025 14:07:45 +0200
Subject: [PATCH 03/24] Transit Provider Port PB

---
 .../product_blocks/transit_provider_port.py   | 27 +++++++++++++++++++
 1 file changed, 27 insertions(+)
 create mode 100644 gso/products/product_blocks/transit_provider_port.py

diff --git a/gso/products/product_blocks/transit_provider_port.py b/gso/products/product_blocks/transit_provider_port.py
new file mode 100644
index 000000000..3c8ac0470
--- /dev/null
+++ b/gso/products/product_blocks/transit_provider_port.py
@@ -0,0 +1,27 @@
+"""Transit Provider Port Product Blocks."""
+
+from orchestrator.domain.base import ProductBlockModel
+from orchestrator.types import SubscriptionLifecycle
+
+from gso.products.product_blocks.ix_port import PeeringConnection, PeeringConnectionInactive, \
+    PeeringConnectionProvisioning
+
+
+class TransitProviderPortBlockInactive(
+    ProductBlockModel, lifecycle=[SubscriptionLifecycle.INITIAL], product_block_name="TransitProviderPortBlock"
+):
+    """A Transit Provider Port that's not yet provisioned. See ``TransitProviderPortBlock``."""
+
+    peering_connection: list[PeeringConnectionInactive]
+
+
+class TransitProviderPortBlockProvisioning(TransitProviderPortBlockInactive, lifecycle=[SubscriptionLifecycle.PROVISIONING]):
+    """A Transit Provider Port that's being provisioned. See ``TransitProviderPortBlock``."""
+
+    peering_connection: list[PeeringConnectionProvisioning]
+
+
+class TransitProviderPortBlock(TransitProviderPortBlockProvisioning, lifecycle=[SubscriptionLifecycle.ACTIVE]):
+    """A Transit Provider Port that's active."""
+
+    peering_connection: list[PeeringConnection]
-- 
GitLab


From 18eb17d181f2ba3e288a6f3bad70d816593ea240 Mon Sep 17 00:00:00 2001
From: Neda Moeini <neda.moeini@geant.org>
Date: Fri, 9 May 2025 14:15:45 +0200
Subject: [PATCH 04/24] Add private peer port Product Type

---
 .../product_types/private_peer_port.py        | 42 +++++++++++++++++++
 1 file changed, 42 insertions(+)
 create mode 100644 gso/products/product_types/private_peer_port.py

diff --git a/gso/products/product_types/private_peer_port.py b/gso/products/product_types/private_peer_port.py
new file mode 100644
index 000000000..680a5dda9
--- /dev/null
+++ b/gso/products/product_types/private_peer_port.py
@@ -0,0 +1,42 @@
+"""Product type for Private Peer Port."""
+
+from orchestrator.domain import SubscriptionModel
+from orchestrator.types import SubscriptionLifecycle
+
+from gso.products.product_blocks.private_peer_port import (
+    PrivatePeerPortBlock,
+    PrivatePeerPortBlockInactive,
+    PrivatePeerPortBlockProvisioning,
+)
+
+
+class PrivatePeerPortInactive(SubscriptionModel, is_base=True):
+    """A Private Peering Port product that is inactive."""
+
+    private_peer_port: PrivatePeerPortBlockInactive
+
+
+class PrivatePeerPortProvisioning(PrivatePeerPortInactive, lifecycle=[SubscriptionLifecycle.PROVISIONING]):
+    """A Private Peering Port product that is being provisioned."""
+
+    private_peer_port: PrivatePeerPortBlockProvisioning
+
+
+class PrivatePeerPort(PrivatePeerPortProvisioning, lifecycle=[SubscriptionLifecycle.ACTIVE]):
+    """A Private Peering Port product that is active."""
+
+    private_peer_port: PrivatePeerPortBlock
+
+
+class ImportedPrivatePeerPortInactive(SubscriptionModel, is_base=True):
+    """An imported Private Peering Port product that is inactive."""
+
+    private_peer_port: PrivatePeerPortBlockInactive
+
+
+class ImportedPrivatePeerPort(
+    ImportedPrivatePeerPortInactive, lifecycle=[SubscriptionLifecycle.PROVISIONING, SubscriptionLifecycle.ACTIVE]
+):
+    """An imported Private Peering Port product that is active."""
+
+    private_peer_port: PrivatePeerPortBlock
\ No newline at end of file
-- 
GitLab


From 148ca2b6410cd4e77df5b59174faa97df0d6121e Mon Sep 17 00:00:00 2001
From: Neda Moeini <neda.moeini@geant.org>
Date: Fri, 9 May 2025 14:15:58 +0200
Subject: [PATCH 05/24] Add IX port Product Type

---
 gso/products/product_types/ix_port.py | 42 +++++++++++++++++++++++++++
 1 file changed, 42 insertions(+)
 create mode 100644 gso/products/product_types/ix_port.py

diff --git a/gso/products/product_types/ix_port.py b/gso/products/product_types/ix_port.py
new file mode 100644
index 000000000..0a84bb6c2
--- /dev/null
+++ b/gso/products/product_types/ix_port.py
@@ -0,0 +1,42 @@
+"""Product type for IX Port."""
+
+from orchestrator.domain import SubscriptionModel
+from orchestrator.types import SubscriptionLifecycle
+
+from gso.products.product_blocks.ix_port import (
+    IXPortBlock,
+    IXPortBlockInactive,
+    IXPortBlockProvisioning,
+)
+
+
+class IXPortInactive(SubscriptionModel, is_base=True):
+    """An IX Port product that is inactive."""
+
+    ix_port: IXPortBlockInactive
+
+
+class IXPortProvisioning(IXPortInactive, lifecycle=[SubscriptionLifecycle.PROVISIONING]):
+    """An IX Port product that is being provisioned."""
+
+    ix_port: IXPortBlockProvisioning
+
+
+class IXPort(IXPortProvisioning, lifecycle=[SubscriptionLifecycle.ACTIVE]):
+    """An IX Port product that is active."""
+
+    ix_port: IXPortBlock
+
+
+class ImportedIXPortInactive(SubscriptionModel, is_base=True):
+    """An imported IX Port product that is inactive."""
+
+    ix_port: IXPortBlockInactive
+
+
+class ImportedIXPort(
+    ImportedIXPortInactive, lifecycle=[SubscriptionLifecycle.PROVISIONING, SubscriptionLifecycle.ACTIVE]
+):
+    """An imported IX Port product that is active."""
+
+    ix_port: IXPortBlock
-- 
GitLab


From 3a0684b5e1c377e6281a0788f603a8f535b09932 Mon Sep 17 00:00:00 2001
From: Neda Moeini <neda.moeini@geant.org>
Date: Fri, 9 May 2025 14:16:18 +0200
Subject: [PATCH 06/24] Add Transit provider port Product Type

---
 .../product_types/transit_provider_port.py    | 42 +++++++++++++++++++
 1 file changed, 42 insertions(+)
 create mode 100644 gso/products/product_types/transit_provider_port.py

diff --git a/gso/products/product_types/transit_provider_port.py b/gso/products/product_types/transit_provider_port.py
new file mode 100644
index 000000000..1b2b38d98
--- /dev/null
+++ b/gso/products/product_types/transit_provider_port.py
@@ -0,0 +1,42 @@
+"""Product type for Transit Provider Port."""
+
+from orchestrator.domain import SubscriptionModel
+from orchestrator.types import SubscriptionLifecycle
+
+from gso.products.product_blocks.transit_provider_port import (
+    TransitProviderPortBlock,
+    TransitProviderPortBlockInactive,
+    TransitProviderPortBlockProvisioning,
+)
+
+
+class TransitProviderPortInactive(SubscriptionModel, is_base=True):
+    """A Transit Provider Port product that is inactive."""
+
+    transit_provider_port: TransitProviderPortBlockInactive
+
+
+class TransitProviderPortProvisioning(TransitProviderPortInactive, lifecycle=[SubscriptionLifecycle.PROVISIONING]):
+    """A Transit Provider Port product that is being provisioned."""
+
+    transit_provider_port: TransitProviderPortBlockProvisioning
+
+
+class TransitProviderPort(TransitProviderPortProvisioning, lifecycle=[SubscriptionLifecycle.ACTIVE]):
+    """A Transit Provider Port product that is active."""
+
+    transit_provider_port: TransitProviderPortBlock
+
+
+class ImportedTransitProviderPortInactive(SubscriptionModel, is_base=True):
+    """An imported Transit Provider Port product that is inactive."""
+
+    transit_provider_port: TransitProviderPortBlockInactive
+
+
+class ImportedTransitProviderPort(
+    ImportedTransitProviderPortInactive, lifecycle=[SubscriptionLifecycle.PROVISIONING, SubscriptionLifecycle.ACTIVE]
+):
+    """An imported Transit Provider Port product that is active."""
+
+    transit_provider_port: TransitProviderPortBlock
\ No newline at end of file
-- 
GitLab


From 955f04b7b30fc9fc7122f65badbc6de2ad62643c Mon Sep 17 00:00:00 2001
From: Neda Moeini <neda.moeini@geant.org>
Date: Fri, 9 May 2025 14:21:51 +0200
Subject: [PATCH 07/24] Add new product types to the ProductType enumerator.

---
 gso/products/__init__.py | 27 +++++++++++++++++++++++++++
 1 file changed, 27 insertions(+)

diff --git a/gso/products/__init__.py b/gso/products/__init__.py
index fff581523..bc4122d3f 100644
--- a/gso/products/__init__.py
+++ b/gso/products/__init__.py
@@ -13,16 +13,19 @@ from gso.products.product_types.edge_port import EdgePort, ImportedEdgePort
 from gso.products.product_types.geant_ip import GeantIP, ImportedGeantIP
 from gso.products.product_types.ias import IAS, ImportedIAS
 from gso.products.product_types.iptrunk import ImportedIptrunk, Iptrunk
+from gso.products.product_types.ix_port import IXPort, ImportedIXPort
 from gso.products.product_types.lan_switch_interconnect import ImportedLanSwitchInterconnect, LanSwitchInterconnect
 from gso.products.product_types.layer_2_circuit import ImportedLayer2Circuit, Layer2Circuit, Layer2CircuitServiceType
 from gso.products.product_types.lhcone import ImportedLHCOne, LHCOne
 from gso.products.product_types.office_router import ImportedOfficeRouter, OfficeRouter
 from gso.products.product_types.opengear import ImportedOpengear, Opengear
 from gso.products.product_types.pop_vlan import PopVlan
+from gso.products.product_types.private_peer_port import PrivatePeerPort, ImportedPrivatePeerPort
 from gso.products.product_types.router import ImportedRouter, Router
 from gso.products.product_types.site import ImportedSite, Site
 from gso.products.product_types.super_pop_switch import ImportedSuperPopSwitch, SuperPopSwitch
 from gso.products.product_types.switch import ImportedSwitch, Switch
+from gso.products.product_types.transit_provider_port import TransitProviderPort, ImportedTransitProviderPort
 from gso.products.product_types.vrf import VRF
 
 
@@ -78,6 +81,18 @@ class ProductName(strEnum):
     IMPORTED_EXPRESSROUTE = Layer2CircuitServiceType.IMPORTED_EXPRESSROUTE
     VRF = "VRF"
     """VRFs."""
+    IX_PORT = "IX Port"
+    """Internet Exchange Ports."""
+    IMPORTED_IX_PORT = "Imported IX Port"
+    """Imported IX Ports."""
+    PRIVATE_PEER_PORT = "Private Peer Port"
+    """Private Peer Ports."""
+    IMPORTED_PRIVATE_PEER_PORT = "Imported Private Peer Port"
+    """Imported Private Peer Ports."""
+    TRANSIT_PROVIDER_PORT = "Transit Provider Port"
+    """Transit Provider Ports."""
+    IMPORTED_TRANSIT_PROVIDER_PORT = "Imported Transit Provider Port"
+    """Imported Transit Provider Ports."""
 
 
 L2_CIRCUIT_PRODUCT_TYPE = Layer2Circuit.__name__
@@ -118,6 +133,12 @@ class ProductType(strEnum):
     IMPORTED_LHCONE = ImportedLHCOne.__name__
     COPERNICUS = Copernicus.__name__
     IMPORTED_COPERNICUS = ImportedCopernicus.__name__
+    IX_PORT = IXPort.__name__
+    IMPORTED_IX_PORT = ImportedIXPort.__name__
+    PRIVATE_PEER_PORT = PrivatePeerPort.__name__
+    IMPORTED_PRIVATE_PEER_PORT = ImportedPrivatePeerPort.__name__
+    TRANSIT_PROVIDER_PORT = TransitProviderPort.__name__
+    IMPORTED_TRANSIT_PROVIDER_PORT = ImportedTransitProviderPort.__name__
 
 
 SUBSCRIPTION_MODEL_REGISTRY.update(
@@ -154,6 +175,12 @@ SUBSCRIPTION_MODEL_REGISTRY.update(
         ProductName.EXPRESSROUTE.value: Layer2Circuit,
         ProductName.IMPORTED_EXPRESSROUTE.value: ImportedLayer2Circuit,
         ProductName.VRF.value: VRF,
+        ProductName.IX_PORT.value: IXPort,
+        ProductName.IMPORTED_IX_PORT.value: ImportedIXPort,
+        ProductName.PRIVATE_PEER_PORT.value: PrivatePeerPort,
+        ProductName.IMPORTED_PRIVATE_PEER_PORT.value: ImportedPrivatePeerPort,
+        ProductName.TRANSIT_PROVIDER_PORT.value: TransitProviderPort,
+        ProductName.IMPORTED_TRANSIT_PROVIDER_PORT.value: ImportedTransitProviderPort,
     },
 )
 
-- 
GitLab


From 796c0acb55fefd96cd15cb9bfbb2996207f040c2 Mon Sep 17 00:00:00 2001
From: Neda Moeini <neda.moeini@geant.org>
Date: Fri, 9 May 2025 14:25:06 +0200
Subject: [PATCH 08/24] Make ruff happy

---
 gso/products/__init__.py                             |  6 +++---
 gso/products/product_blocks/ix_port.py               | 12 ++++++++----
 gso/products/product_blocks/private_peer_port.py     |  7 +++++--
 gso/products/product_blocks/transit_provider_port.py | 11 ++++++++---
 gso/products/product_types/private_peer_port.py      |  2 +-
 gso/products/product_types/transit_provider_port.py  |  2 +-
 6 files changed, 26 insertions(+), 14 deletions(-)

diff --git a/gso/products/__init__.py b/gso/products/__init__.py
index bc4122d3f..67749f2f8 100644
--- a/gso/products/__init__.py
+++ b/gso/products/__init__.py
@@ -13,19 +13,19 @@ from gso.products.product_types.edge_port import EdgePort, ImportedEdgePort
 from gso.products.product_types.geant_ip import GeantIP, ImportedGeantIP
 from gso.products.product_types.ias import IAS, ImportedIAS
 from gso.products.product_types.iptrunk import ImportedIptrunk, Iptrunk
-from gso.products.product_types.ix_port import IXPort, ImportedIXPort
+from gso.products.product_types.ix_port import ImportedIXPort, IXPort
 from gso.products.product_types.lan_switch_interconnect import ImportedLanSwitchInterconnect, LanSwitchInterconnect
 from gso.products.product_types.layer_2_circuit import ImportedLayer2Circuit, Layer2Circuit, Layer2CircuitServiceType
 from gso.products.product_types.lhcone import ImportedLHCOne, LHCOne
 from gso.products.product_types.office_router import ImportedOfficeRouter, OfficeRouter
 from gso.products.product_types.opengear import ImportedOpengear, Opengear
 from gso.products.product_types.pop_vlan import PopVlan
-from gso.products.product_types.private_peer_port import PrivatePeerPort, ImportedPrivatePeerPort
+from gso.products.product_types.private_peer_port import ImportedPrivatePeerPort, PrivatePeerPort
 from gso.products.product_types.router import ImportedRouter, Router
 from gso.products.product_types.site import ImportedSite, Site
 from gso.products.product_types.super_pop_switch import ImportedSuperPopSwitch, SuperPopSwitch
 from gso.products.product_types.switch import ImportedSwitch, Switch
-from gso.products.product_types.transit_provider_port import TransitProviderPort, ImportedTransitProviderPort
+from gso.products.product_types.transit_provider_port import ImportedTransitProviderPort, TransitProviderPort
 from gso.products.product_types.vrf import VRF
 
 
diff --git a/gso/products/product_blocks/ix_port.py b/gso/products/product_blocks/ix_port.py
index f9fec2e6b..e9a5f941e 100644
--- a/gso/products/product_blocks/ix_port.py
+++ b/gso/products/product_blocks/ix_port.py
@@ -4,8 +4,11 @@ from orchestrator.domain.base import ProductBlockModel
 from orchestrator.types import SubscriptionLifecycle
 from pydantic_forms.types import strEnum
 
-from gso.products.product_blocks.service_binding_port import ServiceBindingPortInactive, ServiceBindingPortProvisioning, \
-    ServiceBindingPort
+from gso.products.product_blocks.service_binding_port import (
+    ServiceBindingPort,
+    ServiceBindingPortInactive,
+    ServiceBindingPortProvisioning,
+)
 
 
 class SessionState(strEnum):
@@ -19,8 +22,9 @@ class SessionState(strEnum):
     """Disabled."""
 
 
-class PeeringConnectionInactive(ProductBlockModel, lifecycle=[SubscriptionLifecycle.INITIAL],
-                                product_block_name="PeeringConnectionBlock"):
+class PeeringConnectionInactive(
+    ProductBlockModel, lifecycle=[SubscriptionLifecycle.INITIAL], product_block_name="PeeringConnectionBlock"
+):
     """A Peering Connection that's currently inactive. See `PeeringConnectionBlock`."""
 
     sbp: ServiceBindingPortInactive
diff --git a/gso/products/product_blocks/private_peer_port.py b/gso/products/product_blocks/private_peer_port.py
index 9e8d8ac8f..f961af943 100644
--- a/gso/products/product_blocks/private_peer_port.py
+++ b/gso/products/product_blocks/private_peer_port.py
@@ -3,8 +3,11 @@
 from orchestrator.domain.base import ProductBlockModel
 from orchestrator.types import SubscriptionLifecycle
 
-from gso.products.product_blocks.ix_port import PeeringConnection, PeeringConnectionInactive, \
-    PeeringConnectionProvisioning
+from gso.products.product_blocks.ix_port import (
+    PeeringConnection,
+    PeeringConnectionInactive,
+    PeeringConnectionProvisioning,
+)
 
 
 class PrivatePeerPortBlockInactive(
diff --git a/gso/products/product_blocks/transit_provider_port.py b/gso/products/product_blocks/transit_provider_port.py
index 3c8ac0470..f4e8648ff 100644
--- a/gso/products/product_blocks/transit_provider_port.py
+++ b/gso/products/product_blocks/transit_provider_port.py
@@ -3,8 +3,11 @@
 from orchestrator.domain.base import ProductBlockModel
 from orchestrator.types import SubscriptionLifecycle
 
-from gso.products.product_blocks.ix_port import PeeringConnection, PeeringConnectionInactive, \
-    PeeringConnectionProvisioning
+from gso.products.product_blocks.ix_port import (
+    PeeringConnection,
+    PeeringConnectionInactive,
+    PeeringConnectionProvisioning,
+)
 
 
 class TransitProviderPortBlockInactive(
@@ -15,7 +18,9 @@ class TransitProviderPortBlockInactive(
     peering_connection: list[PeeringConnectionInactive]
 
 
-class TransitProviderPortBlockProvisioning(TransitProviderPortBlockInactive, lifecycle=[SubscriptionLifecycle.PROVISIONING]):
+class TransitProviderPortBlockProvisioning(
+    TransitProviderPortBlockInactive, lifecycle=[SubscriptionLifecycle.PROVISIONING]
+):
     """A Transit Provider Port that's being provisioned. See ``TransitProviderPortBlock``."""
 
     peering_connection: list[PeeringConnectionProvisioning]
diff --git a/gso/products/product_types/private_peer_port.py b/gso/products/product_types/private_peer_port.py
index 680a5dda9..231f19521 100644
--- a/gso/products/product_types/private_peer_port.py
+++ b/gso/products/product_types/private_peer_port.py
@@ -39,4 +39,4 @@ class ImportedPrivatePeerPort(
 ):
     """An imported Private Peering Port product that is active."""
 
-    private_peer_port: PrivatePeerPortBlock
\ No newline at end of file
+    private_peer_port: PrivatePeerPortBlock
diff --git a/gso/products/product_types/transit_provider_port.py b/gso/products/product_types/transit_provider_port.py
index 1b2b38d98..33e1dfc87 100644
--- a/gso/products/product_types/transit_provider_port.py
+++ b/gso/products/product_types/transit_provider_port.py
@@ -39,4 +39,4 @@ class ImportedTransitProviderPort(
 ):
     """An imported Transit Provider Port product that is active."""
 
-    transit_provider_port: TransitProviderPortBlock
\ No newline at end of file
+    transit_provider_port: TransitProviderPortBlock
-- 
GitLab


From 5c5ef252e1a52d6b42d8bbc138b2374c6f3d04eb Mon Sep 17 00:00:00 2001
From: Neda Moeini <neda.moeini@geant.org>
Date: Mon, 12 May 2025 17:10:18 +0100
Subject: [PATCH 09/24] Improve the models

---
 .../product_blocks/commercial_peer.py         | 67 +++++++++++++++
 gso/products/product_blocks/ix_port.py        | 81 ++++++++-----------
 .../product_blocks/private_peer_port.py       | 42 ++++++++--
 .../product_blocks/transit_provider_port.py   | 46 ++++++++---
 4 files changed, 172 insertions(+), 64 deletions(-)
 create mode 100644 gso/products/product_blocks/commercial_peer.py

diff --git a/gso/products/product_blocks/commercial_peer.py b/gso/products/product_blocks/commercial_peer.py
new file mode 100644
index 000000000..583f3086d
--- /dev/null
+++ b/gso/products/product_blocks/commercial_peer.py
@@ -0,0 +1,67 @@
+"""Commercial Peer Product Blocks."""
+
+from orchestrator.domain.base import ProductBlockModel
+from orchestrator.types import SubscriptionLifecycle
+from pydantic_forms.types import strEnum
+
+from gso.products.product_blocks.bgp_session import BGPSessionInactive, BGPSessionProvisioning, BGPSession
+
+
+class SessionState(strEnum):
+    """Session state of the Peering connection."""
+
+    PROVISIONED = "provisioning"
+    """Provisioning."""
+    ACTIVE = "active"
+    """Active."""
+    DISABLED = "disabled"
+    """Disabled."""
+
+
+class PeeringConnectionInactive(
+    ProductBlockModel, lifecycle=[SubscriptionLifecycle.INITIAL], product_block_name="PeeringConnectionBlock"
+):
+    """A Peering Connection that's currently inactive. See `PeeringConnectionBlock`."""
+
+    bgp_session_v4: BGPSessionInactive
+    bgp_session_v6: BGPSessionInactive
+    minimum_hold_timer: int | None = None
+    session_state: SessionState
+
+
+class PeeringConnectionProvisioning(PeeringConnectionInactive, lifecycle=[SubscriptionLifecycle.PROVISIONING]):
+    """A Peering Connection that's currently being provisioned. See `PeeringConnectionBlock`."""
+
+    bgp_session_v4: BGPSessionProvisioning
+    bgp_session_v6: BGPSessionProvisioning
+    minimum_hold_timer: int | None = None
+    session_state: SessionState
+
+
+class PeeringConnection(PeeringConnectionProvisioning, lifecycle=[SubscriptionLifecycle.ACTIVE]):
+    """A Peering Connection that's currently active."""
+
+    bgp_session_v4: BGPSession
+    bgp_session_v6: BGPSession
+    minimum_hold_timer: int | None = None
+    session_state: SessionState
+
+
+class CommercialPeerBlockInactive(
+    ProductBlockModel, lifecycle=[SubscriptionLifecycle.INITIAL], product_block_name="CommercialPeerBlock"
+):
+    """A Commercial Peer that's not yet provisioned. See ``CommercialPeerBlock``."""
+
+    peering_connection: list[PeeringConnectionInactive]
+
+
+class CommercialPeerBlockProvisioning(CommercialPeerBlockInactive, lifecycle=[SubscriptionLifecycle.PROVISIONING]):
+    """An CommercialPeer that's being provisioned. See ``CommercialPeerBlock``."""
+
+    peering_connection: list[PeeringConnectionProvisioning]
+
+
+class CommercialPeerBlock(CommercialPeerBlockProvisioning, lifecycle=[SubscriptionLifecycle.ACTIVE]):
+    """An Internet Exchange Port that's active."""
+
+    peering_connection: list[PeeringConnection]
diff --git a/gso/products/product_blocks/ix_port.py b/gso/products/product_blocks/ix_port.py
index e9a5f941e..f3388dbd1 100644
--- a/gso/products/product_blocks/ix_port.py
+++ b/gso/products/product_blocks/ix_port.py
@@ -2,47 +2,13 @@
 
 from orchestrator.domain.base import ProductBlockModel
 from orchestrator.types import SubscriptionLifecycle
-from pydantic_forms.types import strEnum
 
+from gso.products.product_blocks.edge_port import EdgePortBlock, EdgePortBlockProvisioning
 from gso.products.product_blocks.service_binding_port import (
-    ServiceBindingPort,
-    ServiceBindingPortInactive,
-    ServiceBindingPortProvisioning,
+    BFDSettings,
 )
-
-
-class SessionState(strEnum):
-    """Session state of the IX Port."""
-
-    PROVISIONING = "provisioning"
-    """Provisioning."""
-    ACTIVE = "active"
-    """Active."""
-    DISABLED = "disabled"
-    """Disabled."""
-
-
-class PeeringConnectionInactive(
-    ProductBlockModel, lifecycle=[SubscriptionLifecycle.INITIAL], product_block_name="PeeringConnectionBlock"
-):
-    """A Peering Connection that's currently inactive. See `PeeringConnectionBlock`."""
-
-    sbp: ServiceBindingPortInactive
-    prefix_limit: int | None = None
-
-
-class PeeringConnectionProvisioning(PeeringConnectionInactive, lifecycle=[SubscriptionLifecycle.PROVISIONING]):
-    """A Peering Connection that's currently being provisioned. See `PeeringConnectionBlock`."""
-
-    sbp: ServiceBindingPortProvisioning
-    prefix_limit: int | None = None
-
-
-class PeeringConnection(PeeringConnectionProvisioning, lifecycle=[SubscriptionLifecycle.ACTIVE]):
-    """A Peering Connection that's currently active."""
-
-    sbp: ServiceBindingPort
-    prefix_limit: int | None = None
+from gso.utils.shared_enums import SBPType
+from gso.utils.types.ip_address import IPv4AddressType, IPv4Netmask, IPv6AddressType, IPv6Netmask
 
 
 class IXPortBlockInactive(
@@ -50,22 +16,43 @@ class IXPortBlockInactive(
 ):
     """An IX Port that's not yet provisioned. See ``IXPortBlock``."""
 
-    peering_connection: list[PeeringConnectionInactive]
-    minimum_hold_timer: int | None = None
-    session_state: SessionState | None = None
+    layer_type: SBPType
+    ipv4_address: IPv4AddressType | None = None
+    ipv4_mask: IPv4Netmask | None = None
+    ipv6_address: IPv6AddressType | None = None
+    ipv6_mask: IPv6Netmask | None = None
+    custom_firewall_filters: bool
+    gs_id: str
+    edge_port: EdgePortBlockProvisioning
+    v4_bfd_settings: BFDSettings
+    v6_bfd_settings: BFDSettings
 
 
 class IXPortBlockProvisioning(IXPortBlockInactive, lifecycle=[SubscriptionLifecycle.PROVISIONING]):
     """An IX Port that's being provisioned. See ``IXPortBlock``."""
 
-    peering_connection: list[PeeringConnectionProvisioning]
-    minimum_hold_timer: int | None = None
-    session_state: SessionState | None = None
+    layer_type: SBPType
+    ipv4_address: IPv4AddressType | None = None
+    ipv4_mask: IPv4Netmask | None = None
+    ipv6_address: IPv6AddressType | None = None
+    ipv6_mask: IPv6Netmask | None = None
+    custom_firewall_filters: bool
+    gs_id: str
+    edge_port: EdgePortBlockProvisioning
+    v4_bfd_settings: BFDSettings
+    v6_bfd_settings: BFDSettings
 
 
 class IXPortBlock(IXPortBlockProvisioning, lifecycle=[SubscriptionLifecycle.ACTIVE]):
     """An Internet Exchange Port that's active."""
 
-    peering_connection: list[PeeringConnection]
-    minimum_hold_timer: int | None = None
-    session_state: SessionState
+    layer_type: SBPType
+    ipv4_address: IPv4AddressType | None = None
+    ipv4_mask: IPv4Netmask | None = None
+    ipv6_address: IPv6AddressType | None = None
+    ipv6_mask: IPv6Netmask | None = None
+    custom_firewall_filters: bool
+    gs_id: str
+    edge_port: EdgePortBlock
+    v4_bfd_settings: BFDSettings
+    v6_bfd_settings: BFDSettings
diff --git a/gso/products/product_blocks/private_peer_port.py b/gso/products/product_blocks/private_peer_port.py
index f961af943..e69051407 100644
--- a/gso/products/product_blocks/private_peer_port.py
+++ b/gso/products/product_blocks/private_peer_port.py
@@ -3,11 +3,10 @@
 from orchestrator.domain.base import ProductBlockModel
 from orchestrator.types import SubscriptionLifecycle
 
-from gso.products.product_blocks.ix_port import (
-    PeeringConnection,
-    PeeringConnectionInactive,
-    PeeringConnectionProvisioning,
-)
+from gso.products.product_blocks.edge_port import EdgePortBlockInactive, EdgePortBlockProvisioning
+from gso.products.product_blocks.service_binding_port import BFDSettings
+from gso.utils.shared_enums import SBPType
+from gso.utils.types.ip_address import IPv4AddressType, IPv4Netmask, IPv6AddressType, IPv6Netmask
 
 
 class PrivatePeerPortBlockInactive(
@@ -15,16 +14,43 @@ class PrivatePeerPortBlockInactive(
 ):
     """An Private Peer Port that's not yet provisioned. See ``PrivatePeerPortBlock``."""
 
-    peering_connection: list[PeeringConnectionInactive]
+    layer_type: SBPType
+    ipv4_address: IPv4AddressType | None = None
+    ipv4_mask: IPv4Netmask | None = None
+    ipv6_address: IPv6AddressType | None = None
+    ipv6_mask: IPv6Netmask | None = None
+    custom_firewall_filters: bool
+    gs_id: str
+    edge_port: EdgePortBlockInactive
+    v4_bfd_settings: BFDSettings
+    v6_bfd_settings: BFDSettings
 
 
 class PrivatePeerPortBlockProvisioning(PrivatePeerPortBlockInactive, lifecycle=[SubscriptionLifecycle.PROVISIONING]):
     """An Private Peer Port that's being provisioned. See ``PrivatePeerPortBlock``."""
 
-    peering_connection: list[PeeringConnectionProvisioning]
+    layer_type: SBPType
+    ipv4_address: IPv4AddressType | None = None
+    ipv4_mask: IPv4Netmask | None = None
+    ipv6_address: IPv6AddressType | None = None
+    ipv6_mask: IPv6Netmask | None = None
+    custom_firewall_filters: bool
+    gs_id: str
+    edge_port: EdgePortBlockProvisioning
+    v4_bfd_settings: BFDSettings
+    v6_bfd_settings: BFDSettings
 
 
 class PrivatePeerPortBlock(PrivatePeerPortBlockProvisioning, lifecycle=[SubscriptionLifecycle.ACTIVE]):
     """A Private Provider Port that's active."""
 
-    peering_connection: list[PeeringConnection]
+    layer_type: SBPType
+    ipv4_address: IPv4AddressType | None = None
+    ipv4_mask: IPv4Netmask | None = None
+    ipv6_address: IPv6AddressType | None = None
+    ipv6_mask: IPv6Netmask | None = None
+    custom_firewall_filters: bool
+    gs_id: str
+    edge_port: EdgePortBlockProvisioning
+    v4_bfd_settings: BFDSettings
+    v6_bfd_settings: BFDSettings
diff --git a/gso/products/product_blocks/transit_provider_port.py b/gso/products/product_blocks/transit_provider_port.py
index f4e8648ff..ae64d8962 100644
--- a/gso/products/product_blocks/transit_provider_port.py
+++ b/gso/products/product_blocks/transit_provider_port.py
@@ -3,11 +3,11 @@
 from orchestrator.domain.base import ProductBlockModel
 from orchestrator.types import SubscriptionLifecycle
 
-from gso.products.product_blocks.ix_port import (
-    PeeringConnection,
-    PeeringConnectionInactive,
-    PeeringConnectionProvisioning,
-)
+from gso.products.product_blocks.edge_port import EdgePortBlock, EdgePortBlockInactive, EdgePortBlockProvisioning
+from gso.products.product_blocks.service_binding_port import BFDSettings
+from gso.utils.shared_enums import SBPType
+from gso.utils.types.ip_address import IPv4AddressType, IPv4Netmask, IPv6AddressType, IPv6Netmask
+from gso.utils.types.virtual_identifiers import VLAN_ID
 
 
 class TransitProviderPortBlockInactive(
@@ -15,18 +15,46 @@ class TransitProviderPortBlockInactive(
 ):
     """A Transit Provider Port that's not yet provisioned. See ``TransitProviderPortBlock``."""
 
-    peering_connection: list[PeeringConnectionInactive]
-
+    is_tagged: bool
+    vlan_id: VLAN_ID | None = None
+    layer_type: SBPType
+    ipv4_address: IPv4AddressType | None = None
+    ipv4_mask: IPv4Netmask | None = None
+    ipv6_address: IPv6AddressType | None = None
+    ipv6_mask: IPv6Netmask | None = None
+    custom_firewall_filters: bool
+    gs_id: str
+    edge_port: EdgePortBlockInactive
+    v4_bfd_settings: BFDSettings
+    v6_bfd_settings: BFDSettings
 
 class TransitProviderPortBlockProvisioning(
     TransitProviderPortBlockInactive, lifecycle=[SubscriptionLifecycle.PROVISIONING]
 ):
     """A Transit Provider Port that's being provisioned. See ``TransitProviderPortBlock``."""
 
-    peering_connection: list[PeeringConnectionProvisioning]
+    layer_type: SBPType
+    ipv4_address: IPv4AddressType | None = None
+    ipv4_mask: IPv4Netmask | None = None
+    ipv6_address: IPv6AddressType | None = None
+    ipv6_mask: IPv6Netmask | None = None
+    custom_firewall_filters: bool
+    gs_id: str
+    edge_port: EdgePortBlockProvisioning
+    v4_bfd_settings: BFDSettings
+    v6_bfd_settings: BFDSettings
 
 
 class TransitProviderPortBlock(TransitProviderPortBlockProvisioning, lifecycle=[SubscriptionLifecycle.ACTIVE]):
     """A Transit Provider Port that's active."""
 
-    peering_connection: list[PeeringConnection]
+    layer_type: SBPType
+    ipv4_address: IPv4AddressType | None = None
+    ipv4_mask: IPv4Netmask | None = None
+    ipv6_address: IPv6AddressType | None = None
+    ipv6_mask: IPv6Netmask | None = None
+    custom_firewall_filters: bool
+    gs_id: str
+    edge_port: EdgePortBlock
+    v4_bfd_settings: BFDSettings
+    v6_bfd_settings: BFDSettings
-- 
GitLab


From 827261aafb80d53bac6401b864d526c2f580716e Mon Sep 17 00:00:00 2001
From: Neda Moeini <neda.moeini@geant.org>
Date: Mon, 12 May 2025 17:19:05 +0100
Subject: [PATCH 10/24] Add Commercial Peer product types

---
 gso/products/product_types/commercial_peer.py | 42 +++++++++++++++++++
 1 file changed, 42 insertions(+)
 create mode 100644 gso/products/product_types/commercial_peer.py

diff --git a/gso/products/product_types/commercial_peer.py b/gso/products/product_types/commercial_peer.py
new file mode 100644
index 000000000..8dcf76181
--- /dev/null
+++ b/gso/products/product_types/commercial_peer.py
@@ -0,0 +1,42 @@
+"""Product type for Commercial Peer."""
+
+from orchestrator.domain import SubscriptionModel
+from orchestrator.types import SubscriptionLifecycle
+
+from gso.products.product_blocks.commercial_peer import (
+    CommercialPeerBlock,
+    CommercialPeerBlockInactive,
+    CommercialPeerBlockProvisioning,
+)
+
+
+class CommercialPeerInactive(SubscriptionModel, is_base=True):
+    """A Commercial Peer product that is inactive."""
+
+    commercial_peer: CommercialPeerBlockInactive
+
+
+class CommercialPeerProvisioning(CommercialPeerInactive, lifecycle=[SubscriptionLifecycle.PROVISIONING]):
+    """A Commercial Peer product that is being provisioned."""
+
+    commercial_peer: CommercialPeerBlockProvisioning
+
+
+class CommercialPeer(CommercialPeerProvisioning, lifecycle=[SubscriptionLifecycle.ACTIVE]):
+    """A Commercial Peer product that is active."""
+
+    commercial_peer: CommercialPeerBlock
+
+
+class ImportedCommercialPeerInactive(SubscriptionModel, is_base=True):
+    """An imported Commercial Peer product that is inactive."""
+
+    commercial_peer: CommercialPeerBlockInactive
+
+
+class ImportedCommercialPeer(
+    ImportedCommercialPeerInactive, lifecycle=[SubscriptionLifecycle.PROVISIONING, SubscriptionLifecycle.ACTIVE]
+):
+    """An imported Commercial Peer product that is active."""
+
+    commercial_peer: CommercialPeerBlock
-- 
GitLab


From c9373390b217f0e6e7b8e3f825e823acab0bff17 Mon Sep 17 00:00:00 2001
From: Neda Moeini <neda.moeini@geant.org>
Date: Fri, 16 May 2025 16:17:33 +0200
Subject: [PATCH 11/24] Move the common fields for private peer, transit
 provider and IX ports to l3 interface block

---
 gso/products/product_blocks/ix_port.py        | 53 ++++++++++++++-----
 .../product_blocks/private_peer_port.py       | 41 +++-----------
 .../product_blocks/transit_provider_port.py   | 42 ++-------------
 3 files changed, 51 insertions(+), 85 deletions(-)

diff --git a/gso/products/product_blocks/ix_port.py b/gso/products/product_blocks/ix_port.py
index f3388dbd1..dcc3b5560 100644
--- a/gso/products/product_blocks/ix_port.py
+++ b/gso/products/product_blocks/ix_port.py
@@ -3,35 +3,39 @@
 from orchestrator.domain.base import ProductBlockModel
 from orchestrator.types import SubscriptionLifecycle
 
-from gso.products.product_blocks.edge_port import EdgePortBlock, EdgePortBlockProvisioning
+from gso.products.product_blocks.edge_port import EdgePortBlock, EdgePortBlockProvisioning, EdgePortBlockInactive
 from gso.products.product_blocks.service_binding_port import (
     BFDSettings,
 )
-from gso.utils.shared_enums import SBPType
 from gso.utils.types.ip_address import IPv4AddressType, IPv4Netmask, IPv6AddressType, IPv6Netmask
+from gso.utils.types.virtual_identifiers import VLAN_ID
 
 
-class IXPortBlockInactive(
-    ProductBlockModel, lifecycle=[SubscriptionLifecycle.INITIAL], product_block_name="IXPortBlock"
+class L3InterfacePortBlockInactive(
+    ProductBlockModel, lifecycle=[SubscriptionLifecycle.INITIAL], product_block_name="L3InterfacePortBlock"
 ):
-    """An IX Port that's not yet provisioned. See ``IXPortBlock``."""
+    """A Layer 3 Interface Port Block that's not yet provisioned. See ``L3InterfacePortBlock``."""
 
-    layer_type: SBPType
+    is_tagged: bool
+    vlan_id: VLAN_ID | None = None
     ipv4_address: IPv4AddressType | None = None
     ipv4_mask: IPv4Netmask | None = None
     ipv6_address: IPv6AddressType | None = None
     ipv6_mask: IPv6Netmask | None = None
     custom_firewall_filters: bool
     gs_id: str
-    edge_port: EdgePortBlockProvisioning
+    edge_port: EdgePortBlockInactive
     v4_bfd_settings: BFDSettings
     v6_bfd_settings: BFDSettings
 
 
-class IXPortBlockProvisioning(IXPortBlockInactive, lifecycle=[SubscriptionLifecycle.PROVISIONING]):
-    """An IX Port that's being provisioned. See ``IXPortBlock``."""
+class L3InterfacePortBlockProvisioning(
+    ProductBlockModel, lifecycle=[SubscriptionLifecycle.INITIAL], product_block_name="L3InterfacePortBlock"
+):
+    """A Layer 3 Interface Port Block that's being provisioned. See ``L3InterfacePortBlock``."""
 
-    layer_type: SBPType
+    is_tagged: bool
+    vlan_id: VLAN_ID | None = None
     ipv4_address: IPv4AddressType | None = None
     ipv4_mask: IPv4Netmask | None = None
     ipv6_address: IPv6AddressType | None = None
@@ -43,10 +47,13 @@ class IXPortBlockProvisioning(IXPortBlockInactive, lifecycle=[SubscriptionLifecy
     v6_bfd_settings: BFDSettings
 
 
-class IXPortBlock(IXPortBlockProvisioning, lifecycle=[SubscriptionLifecycle.ACTIVE]):
-    """An Internet Exchange Port that's active."""
+class L3InterfacePortBlock(
+    ProductBlockModel, lifecycle=[SubscriptionLifecycle.INITIAL], product_block_name="L3InterfacePortBlock"
+):
+    """A Layer 3 Interface Port Block that's active."""
 
-    layer_type: SBPType
+    is_tagged: bool
+    vlan_id: VLAN_ID | None = None
     ipv4_address: IPv4AddressType | None = None
     ipv4_mask: IPv4Netmask | None = None
     ipv6_address: IPv6AddressType | None = None
@@ -56,3 +63,23 @@ class IXPortBlock(IXPortBlockProvisioning, lifecycle=[SubscriptionLifecycle.ACTI
     edge_port: EdgePortBlock
     v4_bfd_settings: BFDSettings
     v6_bfd_settings: BFDSettings
+
+
+class IXPortBlockInactive(
+    ProductBlockModel, lifecycle=[SubscriptionLifecycle.INITIAL], product_block_name="IXPortBlock"
+):
+    """An IX Port that's not yet provisioned. See ``IXPortBlock``."""
+
+    l3_interface: L3InterfacePortBlockInactive
+
+
+class IXPortBlockProvisioning(IXPortBlockInactive, lifecycle=[SubscriptionLifecycle.PROVISIONING]):
+    """An IX Port that's being provisioned. See ``IXPortBlock``."""
+
+    l3_interface: L3InterfacePortBlockProvisioning
+
+
+class IXPortBlock(IXPortBlockProvisioning, lifecycle=[SubscriptionLifecycle.ACTIVE]):
+    """An Internet Exchange Port that's active."""
+
+    l3_interface: L3InterfacePortBlock
diff --git a/gso/products/product_blocks/private_peer_port.py b/gso/products/product_blocks/private_peer_port.py
index e69051407..63925253e 100644
--- a/gso/products/product_blocks/private_peer_port.py
+++ b/gso/products/product_blocks/private_peer_port.py
@@ -3,54 +3,25 @@
 from orchestrator.domain.base import ProductBlockModel
 from orchestrator.types import SubscriptionLifecycle
 
-from gso.products.product_blocks.edge_port import EdgePortBlockInactive, EdgePortBlockProvisioning
-from gso.products.product_blocks.service_binding_port import BFDSettings
-from gso.utils.shared_enums import SBPType
-from gso.utils.types.ip_address import IPv4AddressType, IPv4Netmask, IPv6AddressType, IPv6Netmask
+from gso.products.product_blocks.ix_port import L3InterfacePortBlockProvisioning, L3InterfacePortBlockInactive, \
+    L3InterfacePortBlock
 
 
 class PrivatePeerPortBlockInactive(
     ProductBlockModel, lifecycle=[SubscriptionLifecycle.INITIAL], product_block_name="PrivatePeerPortBlock"
 ):
-    """An Private Peer Port that's not yet provisioned. See ``PrivatePeerPortBlock``."""
+    """A Private Peer Port that's not yet provisioned. See ``PrivatePeerPortBlock``."""
 
-    layer_type: SBPType
-    ipv4_address: IPv4AddressType | None = None
-    ipv4_mask: IPv4Netmask | None = None
-    ipv6_address: IPv6AddressType | None = None
-    ipv6_mask: IPv6Netmask | None = None
-    custom_firewall_filters: bool
-    gs_id: str
-    edge_port: EdgePortBlockInactive
-    v4_bfd_settings: BFDSettings
-    v6_bfd_settings: BFDSettings
+    l3_interface: L3InterfacePortBlockInactive
 
 
 class PrivatePeerPortBlockProvisioning(PrivatePeerPortBlockInactive, lifecycle=[SubscriptionLifecycle.PROVISIONING]):
     """An Private Peer Port that's being provisioned. See ``PrivatePeerPortBlock``."""
 
-    layer_type: SBPType
-    ipv4_address: IPv4AddressType | None = None
-    ipv4_mask: IPv4Netmask | None = None
-    ipv6_address: IPv6AddressType | None = None
-    ipv6_mask: IPv6Netmask | None = None
-    custom_firewall_filters: bool
-    gs_id: str
-    edge_port: EdgePortBlockProvisioning
-    v4_bfd_settings: BFDSettings
-    v6_bfd_settings: BFDSettings
+    l3_interface: L3InterfacePortBlockProvisioning
 
 
 class PrivatePeerPortBlock(PrivatePeerPortBlockProvisioning, lifecycle=[SubscriptionLifecycle.ACTIVE]):
     """A Private Provider Port that's active."""
 
-    layer_type: SBPType
-    ipv4_address: IPv4AddressType | None = None
-    ipv4_mask: IPv4Netmask | None = None
-    ipv6_address: IPv6AddressType | None = None
-    ipv6_mask: IPv6Netmask | None = None
-    custom_firewall_filters: bool
-    gs_id: str
-    edge_port: EdgePortBlockProvisioning
-    v4_bfd_settings: BFDSettings
-    v6_bfd_settings: BFDSettings
+    l3_interface: L3InterfacePortBlock
diff --git a/gso/products/product_blocks/transit_provider_port.py b/gso/products/product_blocks/transit_provider_port.py
index ae64d8962..5b3216b2e 100644
--- a/gso/products/product_blocks/transit_provider_port.py
+++ b/gso/products/product_blocks/transit_provider_port.py
@@ -3,11 +3,8 @@
 from orchestrator.domain.base import ProductBlockModel
 from orchestrator.types import SubscriptionLifecycle
 
-from gso.products.product_blocks.edge_port import EdgePortBlock, EdgePortBlockInactive, EdgePortBlockProvisioning
-from gso.products.product_blocks.service_binding_port import BFDSettings
-from gso.utils.shared_enums import SBPType
-from gso.utils.types.ip_address import IPv4AddressType, IPv4Netmask, IPv6AddressType, IPv6Netmask
-from gso.utils.types.virtual_identifiers import VLAN_ID
+from gso.products.product_blocks.ix_port import L3InterfacePortBlockInactive, L3InterfacePortBlockProvisioning, \
+    L3InterfacePortBlock
 
 
 class TransitProviderPortBlockInactive(
@@ -15,46 +12,17 @@ class TransitProviderPortBlockInactive(
 ):
     """A Transit Provider Port that's not yet provisioned. See ``TransitProviderPortBlock``."""
 
-    is_tagged: bool
-    vlan_id: VLAN_ID | None = None
-    layer_type: SBPType
-    ipv4_address: IPv4AddressType | None = None
-    ipv4_mask: IPv4Netmask | None = None
-    ipv6_address: IPv6AddressType | None = None
-    ipv6_mask: IPv6Netmask | None = None
-    custom_firewall_filters: bool
-    gs_id: str
-    edge_port: EdgePortBlockInactive
-    v4_bfd_settings: BFDSettings
-    v6_bfd_settings: BFDSettings
+    l3_interface: L3InterfacePortBlockInactive
 
 class TransitProviderPortBlockProvisioning(
     TransitProviderPortBlockInactive, lifecycle=[SubscriptionLifecycle.PROVISIONING]
 ):
     """A Transit Provider Port that's being provisioned. See ``TransitProviderPortBlock``."""
 
-    layer_type: SBPType
-    ipv4_address: IPv4AddressType | None = None
-    ipv4_mask: IPv4Netmask | None = None
-    ipv6_address: IPv6AddressType | None = None
-    ipv6_mask: IPv6Netmask | None = None
-    custom_firewall_filters: bool
-    gs_id: str
-    edge_port: EdgePortBlockProvisioning
-    v4_bfd_settings: BFDSettings
-    v6_bfd_settings: BFDSettings
+    l3_interface: L3InterfacePortBlockProvisioning
 
 
 class TransitProviderPortBlock(TransitProviderPortBlockProvisioning, lifecycle=[SubscriptionLifecycle.ACTIVE]):
     """A Transit Provider Port that's active."""
 
-    layer_type: SBPType
-    ipv4_address: IPv4AddressType | None = None
-    ipv4_mask: IPv4Netmask | None = None
-    ipv6_address: IPv6AddressType | None = None
-    ipv6_mask: IPv6Netmask | None = None
-    custom_firewall_filters: bool
-    gs_id: str
-    edge_port: EdgePortBlock
-    v4_bfd_settings: BFDSettings
-    v6_bfd_settings: BFDSettings
+    l3_interface: L3InterfacePortBlock
-- 
GitLab


From 26d37669f241c42ca20fa126c29c199fbd872d5c Mon Sep 17 00:00:00 2001
From: Neda Moeini <neda.moeini@geant.org>
Date: Fri, 16 May 2025 16:19:12 +0200
Subject: [PATCH 12/24] Reorder imports

---
 gso/products/product_blocks/commercial_peer.py       | 2 +-
 gso/products/product_blocks/ix_port.py               | 2 +-
 gso/products/product_blocks/private_peer_port.py     | 7 +++++--
 gso/products/product_blocks/transit_provider_port.py | 8 ++++++--
 4 files changed, 13 insertions(+), 6 deletions(-)

diff --git a/gso/products/product_blocks/commercial_peer.py b/gso/products/product_blocks/commercial_peer.py
index 583f3086d..61891dd8a 100644
--- a/gso/products/product_blocks/commercial_peer.py
+++ b/gso/products/product_blocks/commercial_peer.py
@@ -4,7 +4,7 @@ from orchestrator.domain.base import ProductBlockModel
 from orchestrator.types import SubscriptionLifecycle
 from pydantic_forms.types import strEnum
 
-from gso.products.product_blocks.bgp_session import BGPSessionInactive, BGPSessionProvisioning, BGPSession
+from gso.products.product_blocks.bgp_session import BGPSession, BGPSessionInactive, BGPSessionProvisioning
 
 
 class SessionState(strEnum):
diff --git a/gso/products/product_blocks/ix_port.py b/gso/products/product_blocks/ix_port.py
index dcc3b5560..3ae86a1b8 100644
--- a/gso/products/product_blocks/ix_port.py
+++ b/gso/products/product_blocks/ix_port.py
@@ -3,7 +3,7 @@
 from orchestrator.domain.base import ProductBlockModel
 from orchestrator.types import SubscriptionLifecycle
 
-from gso.products.product_blocks.edge_port import EdgePortBlock, EdgePortBlockProvisioning, EdgePortBlockInactive
+from gso.products.product_blocks.edge_port import EdgePortBlock, EdgePortBlockInactive, EdgePortBlockProvisioning
 from gso.products.product_blocks.service_binding_port import (
     BFDSettings,
 )
diff --git a/gso/products/product_blocks/private_peer_port.py b/gso/products/product_blocks/private_peer_port.py
index 63925253e..e88974582 100644
--- a/gso/products/product_blocks/private_peer_port.py
+++ b/gso/products/product_blocks/private_peer_port.py
@@ -3,8 +3,11 @@
 from orchestrator.domain.base import ProductBlockModel
 from orchestrator.types import SubscriptionLifecycle
 
-from gso.products.product_blocks.ix_port import L3InterfacePortBlockProvisioning, L3InterfacePortBlockInactive, \
-    L3InterfacePortBlock
+from gso.products.product_blocks.ix_port import (
+    L3InterfacePortBlock,
+    L3InterfacePortBlockInactive,
+    L3InterfacePortBlockProvisioning,
+)
 
 
 class PrivatePeerPortBlockInactive(
diff --git a/gso/products/product_blocks/transit_provider_port.py b/gso/products/product_blocks/transit_provider_port.py
index 5b3216b2e..15696e215 100644
--- a/gso/products/product_blocks/transit_provider_port.py
+++ b/gso/products/product_blocks/transit_provider_port.py
@@ -3,8 +3,11 @@
 from orchestrator.domain.base import ProductBlockModel
 from orchestrator.types import SubscriptionLifecycle
 
-from gso.products.product_blocks.ix_port import L3InterfacePortBlockInactive, L3InterfacePortBlockProvisioning, \
-    L3InterfacePortBlock
+from gso.products.product_blocks.ix_port import (
+    L3InterfacePortBlock,
+    L3InterfacePortBlockInactive,
+    L3InterfacePortBlockProvisioning,
+)
 
 
 class TransitProviderPortBlockInactive(
@@ -14,6 +17,7 @@ class TransitProviderPortBlockInactive(
 
     l3_interface: L3InterfacePortBlockInactive
 
+
 class TransitProviderPortBlockProvisioning(
     TransitProviderPortBlockInactive, lifecycle=[SubscriptionLifecycle.PROVISIONING]
 ):
-- 
GitLab


From 4c183ddfc6cf7b10a8ad83f589a08a030c42c2de Mon Sep 17 00:00:00 2001
From: Neda Moeini <neda.moeini@geant.org>
Date: Fri, 16 May 2025 16:21:51 +0200
Subject: [PATCH 13/24] Add Commercial Peer and Imported Commercial Peer
 product types

---
 gso/products/__init__.py | 10 +++++++++-
 1 file changed, 9 insertions(+), 1 deletion(-)

diff --git a/gso/products/__init__.py b/gso/products/__init__.py
index 67749f2f8..95bc3d094 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.commercial_peer import CommercialPeer, ImportedCommercialPeer
 from gso.products.product_types.copernicus import Copernicus, ImportedCopernicus
 from gso.products.product_types.edge_port import EdgePort, ImportedEdgePort
 from gso.products.product_types.geant_ip import GeantIP, ImportedGeantIP
@@ -93,6 +94,10 @@ class ProductName(strEnum):
     """Transit Provider Ports."""
     IMPORTED_TRANSIT_PROVIDER_PORT = "Imported Transit Provider Port"
     """Imported Transit Provider Ports."""
+    COMMERCIAL_PEER = "Commercial Peer"
+    """Commercial Peers."""
+    IMPORTED_COMMERCIAL_PEER = "Imported Commercial Peer"
+    """Imported Commercial Peers."""
 
 
 L2_CIRCUIT_PRODUCT_TYPE = Layer2Circuit.__name__
@@ -139,7 +144,8 @@ class ProductType(strEnum):
     IMPORTED_PRIVATE_PEER_PORT = ImportedPrivatePeerPort.__name__
     TRANSIT_PROVIDER_PORT = TransitProviderPort.__name__
     IMPORTED_TRANSIT_PROVIDER_PORT = ImportedTransitProviderPort.__name__
-
+    COMMERCIAL_PEER = CommercialPeer.__name__
+    IMPORTED_COMMERCIAL_PEER = ImportedCommercialPeer.__name__
 
 SUBSCRIPTION_MODEL_REGISTRY.update(
     {
@@ -181,6 +187,8 @@ SUBSCRIPTION_MODEL_REGISTRY.update(
         ProductName.IMPORTED_PRIVATE_PEER_PORT.value: ImportedPrivatePeerPort,
         ProductName.TRANSIT_PROVIDER_PORT.value: TransitProviderPort,
         ProductName.IMPORTED_TRANSIT_PROVIDER_PORT.value: ImportedTransitProviderPort,
+        ProductName.COMMERCIAL_PEER.value: CommercialPeer,
+        ProductName.IMPORTED_COMMERCIAL_PEER.value: ImportedCommercialPeer,
     },
 )
 
-- 
GitLab


From 7d900f398246af258f9ebd37566be8afd05e14f1 Mon Sep 17 00:00:00 2001
From: Neda Moeini <neda.moeini@geant.org>
Date: Fri, 16 May 2025 16:41:18 +0200
Subject: [PATCH 14/24] Add prefix limit to Commercial Peer models

---
 gso/products/product_blocks/commercial_peer.py | 10 +++++++---
 1 file changed, 7 insertions(+), 3 deletions(-)

diff --git a/gso/products/product_blocks/commercial_peer.py b/gso/products/product_blocks/commercial_peer.py
index 61891dd8a..9cb8878bb 100644
--- a/gso/products/product_blocks/commercial_peer.py
+++ b/gso/products/product_blocks/commercial_peer.py
@@ -2,6 +2,7 @@
 
 from orchestrator.domain.base import ProductBlockModel
 from orchestrator.types import SubscriptionLifecycle
+from pydantic import NonNegativeInt
 from pydantic_forms.types import strEnum
 
 from gso.products.product_blocks.bgp_session import BGPSession, BGPSessionInactive, BGPSessionProvisioning
@@ -52,16 +53,19 @@ class CommercialPeerBlockInactive(
 ):
     """A Commercial Peer that's not yet provisioned. See ``CommercialPeerBlock``."""
 
-    peering_connection: list[PeeringConnectionInactive]
+    peering_connection: list[PeeringConnectionInactive]  # type: ignore[assignment]
+    prefix_limit: NonNegativeInt | None = None
 
 
 class CommercialPeerBlockProvisioning(CommercialPeerBlockInactive, lifecycle=[SubscriptionLifecycle.PROVISIONING]):
     """An CommercialPeer that's being provisioned. See ``CommercialPeerBlock``."""
 
-    peering_connection: list[PeeringConnectionProvisioning]
+    peering_connection: list[PeeringConnectionProvisioning]  # type: ignore[assignment]
+    prefix_limit: NonNegativeInt | None = None
 
 
 class CommercialPeerBlock(CommercialPeerBlockProvisioning, lifecycle=[SubscriptionLifecycle.ACTIVE]):
     """An Internet Exchange Port that's active."""
 
-    peering_connection: list[PeeringConnection]
+    peering_connection: list[PeeringConnection]  # type: ignore[assignment]
+    prefix_limit: NonNegativeInt | None = None
-- 
GitLab


From f4fbf41f4671280f95c4eb15b131862992b68d58 Mon Sep 17 00:00:00 2001
From: Neda Moeini <neda.moeini@geant.org>
Date: Fri, 16 May 2025 17:01:20 +0200
Subject: [PATCH 15/24] Enhance documentation

---
 gso/products/__init__.py                             |  1 +
 gso/products/product_blocks/commercial_peer.py       |  9 +++++++--
 gso/products/product_blocks/ix_port.py               | 12 ++++++++----
 gso/products/product_blocks/private_peer_port.py     | 10 +++++++---
 gso/products/product_blocks/transit_provider_port.py | 10 +++++++---
 5 files changed, 30 insertions(+), 12 deletions(-)

diff --git a/gso/products/__init__.py b/gso/products/__init__.py
index 95bc3d094..143e36334 100644
--- a/gso/products/__init__.py
+++ b/gso/products/__init__.py
@@ -147,6 +147,7 @@ class ProductType(strEnum):
     COMMERCIAL_PEER = CommercialPeer.__name__
     IMPORTED_COMMERCIAL_PEER = ImportedCommercialPeer.__name__
 
+
 SUBSCRIPTION_MODEL_REGISTRY.update(
     {
         ProductName.IP_TRUNK.value: Iptrunk,
diff --git a/gso/products/product_blocks/commercial_peer.py b/gso/products/product_blocks/commercial_peer.py
index 9cb8878bb..ee6cc6fcf 100644
--- a/gso/products/product_blocks/commercial_peer.py
+++ b/gso/products/product_blocks/commercial_peer.py
@@ -53,7 +53,7 @@ class CommercialPeerBlockInactive(
 ):
     """A Commercial Peer that's not yet provisioned. See ``CommercialPeerBlock``."""
 
-    peering_connection: list[PeeringConnectionInactive]  # type: ignore[assignment]
+    peering_connection: list[PeeringConnectionInactive]
     prefix_limit: NonNegativeInt | None = None
 
 
@@ -65,7 +65,12 @@ class CommercialPeerBlockProvisioning(CommercialPeerBlockInactive, lifecycle=[Su
 
 
 class CommercialPeerBlock(CommercialPeerBlockProvisioning, lifecycle=[SubscriptionLifecycle.ACTIVE]):
-    """An Internet Exchange Port that's active."""
+    """An Internet Exchange Port that's active.
+
+    Attributes:
+        peering_connection: The Peering connection block
+        prefix_limit: The prefix limit
+    """
 
     peering_connection: list[PeeringConnection]  # type: ignore[assignment]
     prefix_limit: NonNegativeInt | None = None
diff --git a/gso/products/product_blocks/ix_port.py b/gso/products/product_blocks/ix_port.py
index 3ae86a1b8..bf3f8b71a 100644
--- a/gso/products/product_blocks/ix_port.py
+++ b/gso/products/product_blocks/ix_port.py
@@ -16,7 +16,7 @@ class L3InterfacePortBlockInactive(
 ):
     """A Layer 3 Interface Port Block that's not yet provisioned. See ``L3InterfacePortBlock``."""
 
-    is_tagged: bool
+    is_tagged: bool | None = None
     vlan_id: VLAN_ID | None = None
     ipv4_address: IPv4AddressType | None = None
     ipv4_mask: IPv4Netmask | None = None
@@ -76,10 +76,14 @@ class IXPortBlockInactive(
 class IXPortBlockProvisioning(IXPortBlockInactive, lifecycle=[SubscriptionLifecycle.PROVISIONING]):
     """An IX Port that's being provisioned. See ``IXPortBlock``."""
 
-    l3_interface: L3InterfacePortBlockProvisioning
+    l3_interface: L3InterfacePortBlockProvisioning  # type: ignore[assignment]
 
 
 class IXPortBlock(IXPortBlockProvisioning, lifecycle=[SubscriptionLifecycle.ACTIVE]):
-    """An Internet Exchange Port that's active."""
+    """An Internet Exchange Port that's active.
+
+    Attributes:
+        l3_interface: The Layer 3 interface block associated with this IX port.
+    """
 
-    l3_interface: L3InterfacePortBlock
+    l3_interface: L3InterfacePortBlock  # type: ignore[assignment]
diff --git a/gso/products/product_blocks/private_peer_port.py b/gso/products/product_blocks/private_peer_port.py
index e88974582..dde021ed8 100644
--- a/gso/products/product_blocks/private_peer_port.py
+++ b/gso/products/product_blocks/private_peer_port.py
@@ -21,10 +21,14 @@ class PrivatePeerPortBlockInactive(
 class PrivatePeerPortBlockProvisioning(PrivatePeerPortBlockInactive, lifecycle=[SubscriptionLifecycle.PROVISIONING]):
     """An Private Peer Port that's being provisioned. See ``PrivatePeerPortBlock``."""
 
-    l3_interface: L3InterfacePortBlockProvisioning
+    l3_interface: L3InterfacePortBlockProvisioning  # type: ignore[assignment]
 
 
 class PrivatePeerPortBlock(PrivatePeerPortBlockProvisioning, lifecycle=[SubscriptionLifecycle.ACTIVE]):
-    """A Private Provider Port that's active."""
+    """A Private Provider Port that's active.
 
-    l3_interface: L3InterfacePortBlock
+    Attributes:
+        l3_interface: The Layer 3 interface block associated with this private peer port.
+    """
+
+    l3_interface: L3InterfacePortBlock  # type: ignore[assignment]
diff --git a/gso/products/product_blocks/transit_provider_port.py b/gso/products/product_blocks/transit_provider_port.py
index 15696e215..f2a33eb1e 100644
--- a/gso/products/product_blocks/transit_provider_port.py
+++ b/gso/products/product_blocks/transit_provider_port.py
@@ -23,10 +23,14 @@ class TransitProviderPortBlockProvisioning(
 ):
     """A Transit Provider Port that's being provisioned. See ``TransitProviderPortBlock``."""
 
-    l3_interface: L3InterfacePortBlockProvisioning
+    l3_interface: L3InterfacePortBlockProvisioning  # type: ignore[assignment]
 
 
 class TransitProviderPortBlock(TransitProviderPortBlockProvisioning, lifecycle=[SubscriptionLifecycle.ACTIVE]):
-    """A Transit Provider Port that's active."""
+    """A Transit Provider Port that's active.
 
-    l3_interface: L3InterfacePortBlock
+    Attributes:
+        l3_interface: The Layer 3 interface block associated with this transit provider port.
+    """
+
+    l3_interface: L3InterfacePortBlock  # type: ignore[assignment]
-- 
GitLab


From 8fdced0c8c4561fb6a4cd5650a64b7fee8fb80b8 Mon Sep 17 00:00:00 2001
From: Neda Moeini <neda.moeini@geant.org>
Date: Fri, 16 May 2025 17:29:42 +0200
Subject: [PATCH 16/24] Add database migration for commercial peer, transit
 provider, IX port and private provider peer models

---
 ...7_add_commercial_peer_transit_provider_.py | 251 ++++++++++++++++++
 1 file changed, 251 insertions(+)
 create mode 100644 gso/migrations/versions/2025-05-16_fb88e4914b47_add_commercial_peer_transit_provider_.py

diff --git a/gso/migrations/versions/2025-05-16_fb88e4914b47_add_commercial_peer_transit_provider_.py b/gso/migrations/versions/2025-05-16_fb88e4914b47_add_commercial_peer_transit_provider_.py
new file mode 100644
index 000000000..dd7296f1d
--- /dev/null
+++ b/gso/migrations/versions/2025-05-16_fb88e4914b47_add_commercial_peer_transit_provider_.py
@@ -0,0 +1,251 @@
+"""Add commercial peer, transit provider port, private peer port and IX port..
+
+Revision ID: fb88e4914b47
+Revises: 465008ed496e
+Create Date: 2025-05-16 17:28:08.257848
+
+"""
+import sqlalchemy as sa
+from alembic import op
+
+# revision identifiers, used by Alembic.
+revision = 'fb88e4914b47'
+down_revision = '465008ed496e'
+branch_labels = None
+depends_on = None
+
+
+def upgrade() -> None:
+    conn = op.get_bind()
+    conn.execute(sa.text("""
+INSERT INTO products (name, description, product_type, tag, status) VALUES ('IX Port', 'Internet Exchange Port', 'IXPort', 'IX_PORT', 'active') RETURNING products.product_id
+    """))
+    conn.execute(sa.text("""
+INSERT INTO products (name, description, product_type, tag, status) VALUES ('Imported IX Port', 'Imported  Internet Exchange Port', 'ImportedIXPort', 'IMP_IX_PORT', 'active') RETURNING products.product_id
+    """))
+    conn.execute(sa.text("""
+INSERT INTO products (name, description, product_type, tag, status) VALUES ('Private Peer Port', 'Private Peer Port', 'PrivatePeerPort', 'PP_PORT', 'active') RETURNING products.product_id
+    """))
+    conn.execute(sa.text("""
+INSERT INTO products (name, description, product_type, tag, status) VALUES ('Imported Private Peer Port', 'Imported Private Peer Port', 'ImportedPrivatePeerPort', 'IMP_PP_PORT', 'active') RETURNING products.product_id
+    """))
+    conn.execute(sa.text("""
+INSERT INTO products (name, description, product_type, tag, status) VALUES ('Transit Provider Port', 'Transit Provider Port', 'TransitProviderPort', 'TP_PORT', 'active') RETURNING products.product_id
+    """))
+    conn.execute(sa.text("""
+INSERT INTO products (name, description, product_type, tag, status) VALUES ('Imported Transit Provider Port', 'Imported Transit Provider Port', 'ImportedTransitProviderPort', 'IMP_TP_PORT', 'active') RETURNING products.product_id
+    """))
+    conn.execute(sa.text("""
+INSERT INTO products (name, description, product_type, tag, status) VALUES ('Commercial Peer', 'Commercial Peer', 'CommercialPeer', 'CP', 'active') RETURNING products.product_id
+    """))
+    conn.execute(sa.text("""
+INSERT INTO products (name, description, product_type, tag, status) VALUES ('Imported Commercial Peer', 'Imported Commercial Peer', 'ImportedCommercialPeer', 'IMP_CP', 'active') RETURNING products.product_id
+    """))
+    conn.execute(sa.text("""
+INSERT INTO product_blocks (name, description, tag, status) VALUES ('IXPortBlock', 'IXPortBlock', 'IX_PORT_BLK', 'active') RETURNING product_blocks.product_block_id
+    """))
+    conn.execute(sa.text("""
+INSERT INTO product_blocks (name, description, tag, status) VALUES ('L3InterfacePortBlock', 'L3InterfacePortBlock', 'L3_IF_PORT_BLK', 'active') RETURNING product_blocks.product_block_id
+    """))
+    conn.execute(sa.text("""
+INSERT INTO product_blocks (name, description, tag, status) VALUES ('PrivatePeerPortBlock', 'PrivatePeerPortBlock', 'PP_PORT_BLK', 'active') RETURNING product_blocks.product_block_id
+    """))
+    conn.execute(sa.text("""
+INSERT INTO product_blocks (name, description, tag, status) VALUES ('TransitProviderPortBlock', 'TransitProviderPortBlock', 'TP_PORT_BLK', 'active') RETURNING product_blocks.product_block_id
+    """))
+    conn.execute(sa.text("""
+INSERT INTO product_blocks (name, description, tag, status) VALUES ('CommercialPeerBlock', 'CommercialPeerBlock', 'CP_BLK', 'active') RETURNING product_blocks.product_block_id
+    """))
+    conn.execute(sa.text("""
+INSERT INTO product_blocks (name, description, tag, status) VALUES ('PeeringConnectionBlock', 'PeeringConnectionBlock', 'PC_BLK', 'active') RETURNING product_blocks.product_block_id
+    """))
+    conn.execute(sa.text("""
+INSERT INTO resource_types (resource_type, description) VALUES ('minimum_hold_timer', 'Minimum hold timer') RETURNING resource_types.resource_type_id
+    """))
+    conn.execute(sa.text("""
+INSERT INTO resource_types (resource_type, description) VALUES ('session_state', 'The session state') RETURNING resource_types.resource_type_id
+    """))
+    conn.execute(sa.text("""
+INSERT INTO product_product_blocks (product_id, product_block_id) VALUES ((SELECT products.product_id FROM products WHERE products.name IN ('IX Port')), (SELECT product_blocks.product_block_id FROM product_blocks WHERE product_blocks.name IN ('IXPortBlock'))), ((SELECT products.product_id FROM products WHERE products.name IN ('Imported IX Port')), (SELECT product_blocks.product_block_id FROM product_blocks WHERE product_blocks.name IN ('IXPortBlock')))
+    """))
+    conn.execute(sa.text("""
+INSERT INTO product_product_blocks (product_id, product_block_id) VALUES ((SELECT products.product_id FROM products WHERE products.name IN ('Private Peer Port')), (SELECT product_blocks.product_block_id FROM product_blocks WHERE product_blocks.name IN ('PrivatePeerPortBlock'))), ((SELECT products.product_id FROM products WHERE products.name IN ('Imported Private Peer Port')), (SELECT product_blocks.product_block_id FROM product_blocks WHERE product_blocks.name IN ('PrivatePeerPortBlock')))
+    """))
+    conn.execute(sa.text("""
+INSERT INTO product_product_blocks (product_id, product_block_id) VALUES ((SELECT products.product_id FROM products WHERE products.name IN ('Transit Provider Port')), (SELECT product_blocks.product_block_id FROM product_blocks WHERE product_blocks.name IN ('TransitProviderPortBlock'))), ((SELECT products.product_id FROM products WHERE products.name IN ('Imported Transit Provider Port')), (SELECT product_blocks.product_block_id FROM product_blocks WHERE product_blocks.name IN ('TransitProviderPortBlock')))
+    """))
+    conn.execute(sa.text("""
+INSERT INTO product_product_blocks (product_id, product_block_id) VALUES ((SELECT products.product_id FROM products WHERE products.name IN ('Imported Commercial Peer')), (SELECT product_blocks.product_block_id FROM product_blocks WHERE product_blocks.name IN ('CommercialPeerBlock'))), ((SELECT products.product_id FROM products WHERE products.name IN ('Commercial Peer')), (SELECT product_blocks.product_block_id FROM product_blocks WHERE product_blocks.name IN ('CommercialPeerBlock')))
+    """))
+    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 ('TransitProviderPortBlock')), (SELECT product_blocks.product_block_id FROM product_blocks WHERE product_blocks.name IN ('L3InterfacePortBlock'))), ((SELECT product_blocks.product_block_id FROM product_blocks WHERE product_blocks.name IN ('IXPortBlock')), (SELECT product_blocks.product_block_id FROM product_blocks WHERE product_blocks.name IN ('L3InterfacePortBlock'))), ((SELECT product_blocks.product_block_id FROM product_blocks WHERE product_blocks.name IN ('PrivatePeerPortBlock')), (SELECT product_blocks.product_block_id FROM product_blocks WHERE product_blocks.name IN ('L3InterfacePortBlock')))
+    """))
+    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 ('L3InterfacePortBlock')), (SELECT product_blocks.product_block_id FROM product_blocks WHERE product_blocks.name IN ('EdgePortBlock')))
+    """))
+    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 ('L3InterfacePortBlock')), (SELECT product_blocks.product_block_id FROM product_blocks WHERE product_blocks.name IN ('BFDSettings')))
+    """))
+    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 ('CommercialPeerBlock')), (SELECT product_blocks.product_block_id FROM product_blocks WHERE product_blocks.name IN ('PeeringConnectionBlock')))
+    """))
+    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 ('PeeringConnectionBlock')), (SELECT product_blocks.product_block_id FROM product_blocks WHERE product_blocks.name IN ('BGPSession')))
+    """))
+    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 ('L3InterfacePortBlock')), (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('is_tagged')))
+    """))
+    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 ('L3InterfacePortBlock')), (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('vlan_id')))
+    """))
+    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 ('L3InterfacePortBlock')), (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('ipv4_address')))
+    """))
+    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 ('L3InterfacePortBlock')), (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('ipv4_mask')))
+    """))
+    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 ('L3InterfacePortBlock')), (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('ipv6_address')))
+    """))
+    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 ('L3InterfacePortBlock')), (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('ipv6_mask')))
+    """))
+    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 ('L3InterfacePortBlock')), (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('custom_firewall_filters')))
+    """))
+    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 ('L3InterfacePortBlock')), (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('gs_id')))
+    """))
+    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 ('CommercialPeerBlock')), (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('prefix_limit')))
+    """))
+    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 ('PeeringConnectionBlock')), (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('minimum_hold_timer')))
+    """))
+    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 ('PeeringConnectionBlock')), (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('session_state')))
+    """))
+
+
+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 ('L3InterfacePortBlock')) AND product_block_resource_types.resource_type_id = (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('is_tagged'))
+    """))
+    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 ('L3InterfacePortBlock'))) AND product_block_resource_types.resource_type_id = (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('is_tagged'))
+    """))
+    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 ('L3InterfacePortBlock')) AND product_block_resource_types.resource_type_id = (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('vlan_id'))
+    """))
+    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 ('L3InterfacePortBlock'))) AND product_block_resource_types.resource_type_id = (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('vlan_id'))
+    """))
+    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 ('L3InterfacePortBlock')) AND product_block_resource_types.resource_type_id = (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('ipv4_address'))
+    """))
+    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 ('L3InterfacePortBlock'))) AND product_block_resource_types.resource_type_id = (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('ipv4_address'))
+    """))
+    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 ('L3InterfacePortBlock')) AND product_block_resource_types.resource_type_id = (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('ipv4_mask'))
+    """))
+    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 ('L3InterfacePortBlock'))) AND product_block_resource_types.resource_type_id = (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('ipv4_mask'))
+    """))
+    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 ('L3InterfacePortBlock')) AND product_block_resource_types.resource_type_id = (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('ipv6_address'))
+    """))
+    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 ('L3InterfacePortBlock'))) AND product_block_resource_types.resource_type_id = (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('ipv6_address'))
+    """))
+    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 ('L3InterfacePortBlock')) AND product_block_resource_types.resource_type_id = (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('ipv6_mask'))
+    """))
+    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 ('L3InterfacePortBlock'))) AND product_block_resource_types.resource_type_id = (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('ipv6_mask'))
+    """))
+    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 ('L3InterfacePortBlock')) AND product_block_resource_types.resource_type_id = (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('custom_firewall_filters'))
+    """))
+    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 ('L3InterfacePortBlock'))) AND product_block_resource_types.resource_type_id = (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('custom_firewall_filters'))
+    """))
+    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 ('L3InterfacePortBlock')) AND product_block_resource_types.resource_type_id = (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('gs_id'))
+    """))
+    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 ('L3InterfacePortBlock'))) AND product_block_resource_types.resource_type_id = (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('gs_id'))
+    """))
+    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 ('CommercialPeerBlock')) AND product_block_resource_types.resource_type_id = (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('prefix_limit'))
+    """))
+    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 ('CommercialPeerBlock'))) AND product_block_resource_types.resource_type_id = (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('prefix_limit'))
+    """))
+    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 ('PeeringConnectionBlock')) AND product_block_resource_types.resource_type_id = (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('minimum_hold_timer'))
+    """))
+    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 ('PeeringConnectionBlock'))) AND product_block_resource_types.resource_type_id = (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('minimum_hold_timer'))
+    """))
+    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 ('PeeringConnectionBlock')) AND product_block_resource_types.resource_type_id = (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('session_state'))
+    """))
+    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 ('PeeringConnectionBlock'))) AND product_block_resource_types.resource_type_id = (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('session_state'))
+    """))
+    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 ('minimum_hold_timer', 'session_state'))
+    """))
+    conn.execute(sa.text("""
+DELETE FROM resource_types WHERE resource_types.resource_type IN ('minimum_hold_timer', 'session_state')
+    """))
+    conn.execute(sa.text("""
+DELETE FROM product_product_blocks WHERE product_product_blocks.product_id IN (SELECT products.product_id FROM products WHERE products.name IN ('IX Port', 'Imported IX Port')) AND product_product_blocks.product_block_id IN (SELECT product_blocks.product_block_id FROM product_blocks WHERE product_blocks.name IN ('IXPortBlock'))
+    """))
+    conn.execute(sa.text("""
+DELETE FROM product_product_blocks WHERE product_product_blocks.product_id IN (SELECT products.product_id FROM products WHERE products.name IN ('Private Peer Port', 'Imported Private Peer Port')) AND product_product_blocks.product_block_id IN (SELECT product_blocks.product_block_id FROM product_blocks WHERE product_blocks.name IN ('PrivatePeerPortBlock'))
+    """))
+    conn.execute(sa.text("""
+DELETE FROM product_product_blocks WHERE product_product_blocks.product_id IN (SELECT products.product_id FROM products WHERE products.name IN ('Transit Provider Port', 'Imported Transit Provider Port')) AND product_product_blocks.product_block_id IN (SELECT product_blocks.product_block_id FROM product_blocks WHERE product_blocks.name IN ('TransitProviderPortBlock'))
+    """))
+    conn.execute(sa.text("""
+DELETE FROM product_product_blocks WHERE product_product_blocks.product_id IN (SELECT products.product_id FROM products WHERE products.name IN ('Imported Commercial Peer', 'Commercial Peer')) AND product_product_blocks.product_block_id IN (SELECT product_blocks.product_block_id FROM product_blocks WHERE product_blocks.name IN ('CommercialPeerBlock'))
+    """))
+    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 ('TransitProviderPortBlock', 'IXPortBlock', 'PrivatePeerPortBlock')) AND product_block_relations.depends_on_id IN (SELECT product_blocks.product_block_id FROM product_blocks WHERE product_blocks.name IN ('L3InterfacePortBlock'))
+    """))
+    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 ('L3InterfacePortBlock')) AND product_block_relations.depends_on_id IN (SELECT product_blocks.product_block_id FROM product_blocks WHERE product_blocks.name IN ('EdgePortBlock'))
+    """))
+    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 ('L3InterfacePortBlock')) AND product_block_relations.depends_on_id IN (SELECT product_blocks.product_block_id FROM product_blocks WHERE product_blocks.name IN ('BFDSettings'))
+    """))
+    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 ('CommercialPeerBlock')) AND product_block_relations.depends_on_id IN (SELECT product_blocks.product_block_id FROM product_blocks WHERE product_blocks.name IN ('PeeringConnectionBlock'))
+    """))
+    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 ('PeeringConnectionBlock')) AND product_block_relations.depends_on_id IN (SELECT product_blocks.product_block_id FROM product_blocks WHERE product_blocks.name IN ('BGPSession'))
+    """))
+    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 ('L3InterfacePortBlock', 'TransitProviderPortBlock', 'PrivatePeerPortBlock', 'CommercialPeerBlock', 'PeeringConnectionBlock', 'IXPortBlock'))
+    """))
+    conn.execute(sa.text("""
+DELETE FROM product_blocks WHERE product_blocks.name IN ('L3InterfacePortBlock', 'TransitProviderPortBlock', 'PrivatePeerPortBlock', 'CommercialPeerBlock', 'PeeringConnectionBlock', 'IXPortBlock')
+    """))
+    conn.execute(sa.text("""
+DELETE FROM processes WHERE processes.pid IN (SELECT processes_subscriptions.pid FROM processes_subscriptions WHERE processes_subscriptions.subscription_id IN (SELECT subscriptions.subscription_id FROM subscriptions WHERE subscriptions.product_id IN (SELECT products.product_id FROM products WHERE products.name IN ('IX Port', 'Transit Provider Port', 'Imported Commercial Peer', 'Private Peer Port', 'Imported Transit Provider Port', 'Imported Private Peer Port', 'Commercial Peer', 'Imported IX Port'))))
+    """))
+    conn.execute(sa.text("""
+DELETE FROM processes_subscriptions WHERE processes_subscriptions.subscription_id IN (SELECT subscriptions.subscription_id FROM subscriptions WHERE subscriptions.product_id IN (SELECT products.product_id FROM products WHERE products.name IN ('IX Port', 'Transit Provider Port', 'Imported Commercial Peer', 'Private Peer Port', 'Imported Transit Provider Port', 'Imported Private Peer Port', 'Commercial Peer', 'Imported IX Port')))
+    """))
+    conn.execute(sa.text("""
+DELETE FROM subscription_instances WHERE subscription_instances.subscription_id IN (SELECT subscriptions.subscription_id FROM subscriptions WHERE subscriptions.product_id IN (SELECT products.product_id FROM products WHERE products.name IN ('IX Port', 'Transit Provider Port', 'Imported Commercial Peer', 'Private Peer Port', 'Imported Transit Provider Port', 'Imported Private Peer Port', 'Commercial Peer', 'Imported IX Port')))
+    """))
+    conn.execute(sa.text("""
+DELETE FROM subscriptions WHERE subscriptions.product_id IN (SELECT products.product_id FROM products WHERE products.name IN ('IX Port', 'Transit Provider Port', 'Imported Commercial Peer', 'Private Peer Port', 'Imported Transit Provider Port', 'Imported Private Peer Port', 'Commercial Peer', 'Imported IX Port'))
+    """))
+    conn.execute(sa.text("""
+DELETE FROM products WHERE products.name IN ('IX Port', 'Transit Provider Port', 'Imported Commercial Peer', 'Private Peer Port', 'Imported Transit Provider Port', 'Imported Private Peer Port', 'Commercial Peer', 'Imported IX Port')
+    """))
-- 
GitLab


From e5a88c8ddfc0158ebc4b419cd3897afbb0b9bebc Mon Sep 17 00:00:00 2001
From: Neda Moeini <neda.moeini@geant.org>
Date: Wed, 28 May 2025 10:31:33 +0200
Subject: [PATCH 17/24] Refactor L3 interface port block classes and add new
 L3InterfacePortBlock model

---
 gso/products/product_blocks/ix_port.py        | 63 ++----------------
 .../product_blocks/l3_interface_port.py       | 65 +++++++++++++++++++
 .../product_blocks/private_peer_port.py       |  2 +-
 3 files changed, 70 insertions(+), 60 deletions(-)
 create mode 100644 gso/products/product_blocks/l3_interface_port.py

diff --git a/gso/products/product_blocks/ix_port.py b/gso/products/product_blocks/ix_port.py
index bf3f8b71a..0e6878ddf 100644
--- a/gso/products/product_blocks/ix_port.py
+++ b/gso/products/product_blocks/ix_port.py
@@ -3,66 +3,11 @@
 from orchestrator.domain.base import ProductBlockModel
 from orchestrator.types import SubscriptionLifecycle
 
-from gso.products.product_blocks.edge_port import EdgePortBlock, EdgePortBlockInactive, EdgePortBlockProvisioning
-from gso.products.product_blocks.service_binding_port import (
-    BFDSettings,
+from gso.products.product_blocks.l3_interface_port import (
+    L3InterfacePortBlock,
+    L3InterfacePortBlockInactive,
+    L3InterfacePortBlockProvisioning,
 )
-from gso.utils.types.ip_address import IPv4AddressType, IPv4Netmask, IPv6AddressType, IPv6Netmask
-from gso.utils.types.virtual_identifiers import VLAN_ID
-
-
-class L3InterfacePortBlockInactive(
-    ProductBlockModel, lifecycle=[SubscriptionLifecycle.INITIAL], product_block_name="L3InterfacePortBlock"
-):
-    """A Layer 3 Interface Port Block that's not yet provisioned. See ``L3InterfacePortBlock``."""
-
-    is_tagged: bool | None = None
-    vlan_id: VLAN_ID | None = None
-    ipv4_address: IPv4AddressType | None = None
-    ipv4_mask: IPv4Netmask | None = None
-    ipv6_address: IPv6AddressType | None = None
-    ipv6_mask: IPv6Netmask | None = None
-    custom_firewall_filters: bool
-    gs_id: str
-    edge_port: EdgePortBlockInactive
-    v4_bfd_settings: BFDSettings
-    v6_bfd_settings: BFDSettings
-
-
-class L3InterfacePortBlockProvisioning(
-    ProductBlockModel, lifecycle=[SubscriptionLifecycle.INITIAL], product_block_name="L3InterfacePortBlock"
-):
-    """A Layer 3 Interface Port Block that's being provisioned. See ``L3InterfacePortBlock``."""
-
-    is_tagged: bool
-    vlan_id: VLAN_ID | None = None
-    ipv4_address: IPv4AddressType | None = None
-    ipv4_mask: IPv4Netmask | None = None
-    ipv6_address: IPv6AddressType | None = None
-    ipv6_mask: IPv6Netmask | None = None
-    custom_firewall_filters: bool
-    gs_id: str
-    edge_port: EdgePortBlockProvisioning
-    v4_bfd_settings: BFDSettings
-    v6_bfd_settings: BFDSettings
-
-
-class L3InterfacePortBlock(
-    ProductBlockModel, lifecycle=[SubscriptionLifecycle.INITIAL], product_block_name="L3InterfacePortBlock"
-):
-    """A Layer 3 Interface Port Block that's active."""
-
-    is_tagged: bool
-    vlan_id: VLAN_ID | None = None
-    ipv4_address: IPv4AddressType | None = None
-    ipv4_mask: IPv4Netmask | None = None
-    ipv6_address: IPv6AddressType | None = None
-    ipv6_mask: IPv6Netmask | None = None
-    custom_firewall_filters: bool
-    gs_id: str
-    edge_port: EdgePortBlock
-    v4_bfd_settings: BFDSettings
-    v6_bfd_settings: BFDSettings
 
 
 class IXPortBlockInactive(
diff --git a/gso/products/product_blocks/l3_interface_port.py b/gso/products/product_blocks/l3_interface_port.py
new file mode 100644
index 000000000..8712c4720
--- /dev/null
+++ b/gso/products/product_blocks/l3_interface_port.py
@@ -0,0 +1,65 @@
+"""A Layer 3 Interface Port Block."""
+
+from orchestrator.domain.base import ProductBlockModel
+from orchestrator.types import SubscriptionLifecycle
+
+from gso.products.product_blocks.edge_port import EdgePortBlock, EdgePortBlockInactive, EdgePortBlockProvisioning
+from gso.products.product_blocks.service_binding_port import (
+    BFDSettings,
+)
+from gso.utils.types.ip_address import IPv4AddressType, IPv4Netmask, IPv6AddressType, IPv6Netmask
+from gso.utils.types.virtual_identifiers import VLAN_ID
+
+
+class L3InterfacePortBlockInactive(
+    ProductBlockModel, lifecycle=[SubscriptionLifecycle.INITIAL], product_block_name="L3InterfacePortBlock"
+):
+    """A Layer 3 Interface Port Block that's not yet provisioned. See ``L3InterfacePortBlock``."""
+
+    is_tagged: bool | None = None
+    vlan_id: VLAN_ID | None = None
+    ipv4_address: IPv4AddressType | None = None
+    ipv4_mask: IPv4Netmask | None = None
+    ipv6_address: IPv6AddressType | None = None
+    ipv6_mask: IPv6Netmask | None = None
+    custom_firewall_filters: bool
+    gs_id: str
+    edge_port: EdgePortBlockInactive
+    v4_bfd_settings: BFDSettings
+    v6_bfd_settings: BFDSettings
+
+
+class L3InterfacePortBlockProvisioning(
+    ProductBlockModel, lifecycle=[SubscriptionLifecycle.INITIAL], product_block_name="L3InterfacePortBlock"
+):
+    """A Layer 3 Interface Port Block that's being provisioned. See ``L3InterfacePortBlock``."""
+
+    is_tagged: bool
+    vlan_id: VLAN_ID | None = None
+    ipv4_address: IPv4AddressType | None = None
+    ipv4_mask: IPv4Netmask | None = None
+    ipv6_address: IPv6AddressType | None = None
+    ipv6_mask: IPv6Netmask | None = None
+    custom_firewall_filters: bool
+    gs_id: str
+    edge_port: EdgePortBlockProvisioning
+    v4_bfd_settings: BFDSettings
+    v6_bfd_settings: BFDSettings
+
+
+class L3InterfacePortBlock(
+    ProductBlockModel, lifecycle=[SubscriptionLifecycle.INITIAL], product_block_name="L3InterfacePortBlock"
+):
+    """A Layer 3 Interface Port Block that's active."""
+
+    is_tagged: bool
+    vlan_id: VLAN_ID | None = None
+    ipv4_address: IPv4AddressType | None = None
+    ipv4_mask: IPv4Netmask | None = None
+    ipv6_address: IPv6AddressType | None = None
+    ipv6_mask: IPv6Netmask | None = None
+    custom_firewall_filters: bool
+    gs_id: str
+    edge_port: EdgePortBlock
+    v4_bfd_settings: BFDSettings
+    v6_bfd_settings: BFDSettings
diff --git a/gso/products/product_blocks/private_peer_port.py b/gso/products/product_blocks/private_peer_port.py
index dde021ed8..0f4368567 100644
--- a/gso/products/product_blocks/private_peer_port.py
+++ b/gso/products/product_blocks/private_peer_port.py
@@ -19,7 +19,7 @@ class PrivatePeerPortBlockInactive(
 
 
 class PrivatePeerPortBlockProvisioning(PrivatePeerPortBlockInactive, lifecycle=[SubscriptionLifecycle.PROVISIONING]):
-    """An Private Peer Port that's being provisioned. See ``PrivatePeerPortBlock``."""
+    """A Private Peer Port that's being provisioned. See ``PrivatePeerPortBlock``."""
 
     l3_interface: L3InterfacePortBlockProvisioning  # type: ignore[assignment]
 
-- 
GitLab


From d876ba9236e23b90856e29644abe3795df117f4b Mon Sep 17 00:00:00 2001
From: Neda Moeini <neda.moeini@geant.org>
Date: Wed, 28 May 2025 11:04:16 +0200
Subject: [PATCH 18/24] Make mypy happy

---
 gso/products/product_blocks/private_peer_port.py     | 6 ++++++
 gso/products/product_blocks/transit_provider_port.py | 6 ++++++
 2 files changed, 12 insertions(+)

diff --git a/gso/products/product_blocks/private_peer_port.py b/gso/products/product_blocks/private_peer_port.py
index 0f4368567..0bb8e25e3 100644
--- a/gso/products/product_blocks/private_peer_port.py
+++ b/gso/products/product_blocks/private_peer_port.py
@@ -9,6 +9,12 @@ from gso.products.product_blocks.ix_port import (
     L3InterfacePortBlockProvisioning,
 )
 
+__all__ = [
+    "L3InterfacePortBlock",
+    "L3InterfacePortBlockInactive",
+    "L3InterfacePortBlockProvisioning",
+]
+
 
 class PrivatePeerPortBlockInactive(
     ProductBlockModel, lifecycle=[SubscriptionLifecycle.INITIAL], product_block_name="PrivatePeerPortBlock"
diff --git a/gso/products/product_blocks/transit_provider_port.py b/gso/products/product_blocks/transit_provider_port.py
index f2a33eb1e..c809cd423 100644
--- a/gso/products/product_blocks/transit_provider_port.py
+++ b/gso/products/product_blocks/transit_provider_port.py
@@ -9,6 +9,12 @@ from gso.products.product_blocks.ix_port import (
     L3InterfacePortBlockProvisioning,
 )
 
+__all__ = [
+    "L3InterfacePortBlock",
+    "L3InterfacePortBlockInactive",
+    "L3InterfacePortBlockProvisioning",
+]
+
 
 class TransitProviderPortBlockInactive(
     ProductBlockModel, lifecycle=[SubscriptionLifecycle.INITIAL], product_block_name="TransitProviderPortBlock"
-- 
GitLab


From 5363673ac492b4a1bdf742dd5e3d617f59253801 Mon Sep 17 00:00:00 2001
From: Neda Moeini <neda.moeini@geant.org>
Date: Wed, 28 May 2025 11:10:17 +0200
Subject: [PATCH 19/24] Refactor transit and private peer port imports to use
 L3 interface port block

---
 gso/products/product_blocks/commercial_peer.py     | 14 ++++++++++++++
 gso/products/product_blocks/private_peer_port.py   |  8 +-------
 .../product_blocks/transit_provider_port.py        |  8 +-------
 3 files changed, 16 insertions(+), 14 deletions(-)

diff --git a/gso/products/product_blocks/commercial_peer.py b/gso/products/product_blocks/commercial_peer.py
index ee6cc6fcf..f0aed3925 100644
--- a/gso/products/product_blocks/commercial_peer.py
+++ b/gso/products/product_blocks/commercial_peer.py
@@ -6,6 +6,17 @@ from pydantic import NonNegativeInt
 from pydantic_forms.types import strEnum
 
 from gso.products.product_blocks.bgp_session import BGPSession, BGPSessionInactive, BGPSessionProvisioning
+from gso.products.product_types.ix_port import IXPort, IXPortInactive, IXPortProvisioning
+from gso.products.product_types.private_peer_port import (
+    PrivatePeerPort,
+    PrivatePeerPortInactive,
+    PrivatePeerPortProvisioning,
+)
+from gso.products.product_types.transit_provider_port import (
+    TransitProviderPort,
+    TransitProviderPortInactive,
+    TransitProviderPortProvisioning,
+)
 
 
 class SessionState(strEnum):
@@ -28,6 +39,7 @@ class PeeringConnectionInactive(
     bgp_session_v6: BGPSessionInactive
     minimum_hold_timer: int | None = None
     session_state: SessionState
+    placement_port: IXPortInactive | PrivatePeerPortInactive | TransitProviderPortInactive | None = None
 
 
 class PeeringConnectionProvisioning(PeeringConnectionInactive, lifecycle=[SubscriptionLifecycle.PROVISIONING]):
@@ -37,6 +49,7 @@ class PeeringConnectionProvisioning(PeeringConnectionInactive, lifecycle=[Subscr
     bgp_session_v6: BGPSessionProvisioning
     minimum_hold_timer: int | None = None
     session_state: SessionState
+    placement_port: IXPortProvisioning | PrivatePeerPortProvisioning | TransitProviderPortProvisioning | None = None
 
 
 class PeeringConnection(PeeringConnectionProvisioning, lifecycle=[SubscriptionLifecycle.ACTIVE]):
@@ -46,6 +59,7 @@ class PeeringConnection(PeeringConnectionProvisioning, lifecycle=[SubscriptionLi
     bgp_session_v6: BGPSession
     minimum_hold_timer: int | None = None
     session_state: SessionState
+    placement_port: IXPort | PrivatePeerPort | TransitProviderPort
 
 
 class CommercialPeerBlockInactive(
diff --git a/gso/products/product_blocks/private_peer_port.py b/gso/products/product_blocks/private_peer_port.py
index 0bb8e25e3..c589912f1 100644
--- a/gso/products/product_blocks/private_peer_port.py
+++ b/gso/products/product_blocks/private_peer_port.py
@@ -3,18 +3,12 @@
 from orchestrator.domain.base import ProductBlockModel
 from orchestrator.types import SubscriptionLifecycle
 
-from gso.products.product_blocks.ix_port import (
+from gso.products.product_blocks.l3_interface_port import (
     L3InterfacePortBlock,
     L3InterfacePortBlockInactive,
     L3InterfacePortBlockProvisioning,
 )
 
-__all__ = [
-    "L3InterfacePortBlock",
-    "L3InterfacePortBlockInactive",
-    "L3InterfacePortBlockProvisioning",
-]
-
 
 class PrivatePeerPortBlockInactive(
     ProductBlockModel, lifecycle=[SubscriptionLifecycle.INITIAL], product_block_name="PrivatePeerPortBlock"
diff --git a/gso/products/product_blocks/transit_provider_port.py b/gso/products/product_blocks/transit_provider_port.py
index c809cd423..3423e98d9 100644
--- a/gso/products/product_blocks/transit_provider_port.py
+++ b/gso/products/product_blocks/transit_provider_port.py
@@ -3,18 +3,12 @@
 from orchestrator.domain.base import ProductBlockModel
 from orchestrator.types import SubscriptionLifecycle
 
-from gso.products.product_blocks.ix_port import (
+from gso.products.product_blocks.l3_interface_port import (
     L3InterfacePortBlock,
     L3InterfacePortBlockInactive,
     L3InterfacePortBlockProvisioning,
 )
 
-__all__ = [
-    "L3InterfacePortBlock",
-    "L3InterfacePortBlockInactive",
-    "L3InterfacePortBlockProvisioning",
-]
-
 
 class TransitProviderPortBlockInactive(
     ProductBlockModel, lifecycle=[SubscriptionLifecycle.INITIAL], product_block_name="TransitProviderPortBlock"
-- 
GitLab


From 3a92247810a242812be40ba588be8c719f32b422 Mon Sep 17 00:00:00 2001
From: Neda Moeini <neda.moeini@geant.org>
Date: Mon, 2 Jun 2025 11:33:46 +0200
Subject: [PATCH 20/24] Update down_revision for commercial peer and transit
 provider migration

---
 ...5-16_fb88e4914b47_add_commercial_peer_transit_provider_.py | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/gso/migrations/versions/2025-05-16_fb88e4914b47_add_commercial_peer_transit_provider_.py b/gso/migrations/versions/2025-05-16_fb88e4914b47_add_commercial_peer_transit_provider_.py
index dd7296f1d..0d7b7abe4 100644
--- a/gso/migrations/versions/2025-05-16_fb88e4914b47_add_commercial_peer_transit_provider_.py
+++ b/gso/migrations/versions/2025-05-16_fb88e4914b47_add_commercial_peer_transit_provider_.py
@@ -1,7 +1,7 @@
 """Add commercial peer, transit provider port, private peer port and IX port..
 
 Revision ID: fb88e4914b47
-Revises: 465008ed496e
+Revises: 90547df711c3
 Create Date: 2025-05-16 17:28:08.257848
 
 """
@@ -10,7 +10,7 @@ from alembic import op
 
 # revision identifiers, used by Alembic.
 revision = 'fb88e4914b47'
-down_revision = '465008ed496e'
+down_revision = '90547df711c3'
 branch_labels = None
 depends_on = None
 
-- 
GitLab


From e7cdda2c233e06c507241c904e8c5bd033ab4506 Mon Sep 17 00:00:00 2001
From: Neda Moeini <neda.moeini@geant.org>
Date: Mon, 2 Jun 2025 11:34:41 +0200
Subject: [PATCH 21/24] Add IX Port creation workflow and input forms

---
 gso/workflows/__init__.py               |   3 +
 gso/workflows/ix_port/create_ix_port.py | 122 ++++++++++++++++++++++++
 2 files changed, 125 insertions(+)
 create mode 100644 gso/workflows/ix_port/create_ix_port.py

diff --git a/gso/workflows/__init__.py b/gso/workflows/__init__.py
index 8b1f4065d..98d3f94f8 100644
--- a/gso/workflows/__init__.py
+++ b/gso/workflows/__init__.py
@@ -172,3 +172,6 @@ LazyWorkflowInstance("gso.workflows.vrf.create_vrf", "create_vrf")
 LazyWorkflowInstance("gso.workflows.vrf.modify_vrf_router_list", "modify_vrf_router_list")
 LazyWorkflowInstance("gso.workflows.vrf.redeploy_vrf", "redeploy_vrf")
 LazyWorkflowInstance("gso.workflows.vrf.terminate_vrf", "terminate_vrf")
+
+# IX Port workflows
+LazyWorkflowInstance("gso.workflows.ix_port.create_ix_port", "create_ix_port")
\ No newline at end of file
diff --git a/gso/workflows/ix_port/create_ix_port.py b/gso/workflows/ix_port/create_ix_port.py
new file mode 100644
index 000000000..d5fbccd39
--- /dev/null
+++ b/gso/workflows/ix_port/create_ix_port.py
@@ -0,0 +1,122 @@
+"""Create IX Port Service Workflow"""
+from uuid import uuid4
+
+from orchestrator import step, workflow
+from orchestrator.forms import FormPage, SubmitFormPage
+from orchestrator.targets import Target
+from orchestrator.types import SubscriptionLifecycle
+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
+from pydantic import ConfigDict, Field
+from pydantic_forms.types import FormGenerator, State, UUIDstr
+from pydantic_forms.validators import Divider
+
+from gso.products.product_blocks.service_binding_port import BFDSettings
+from gso.products.product_types.edge_port import EdgePort
+from gso.products.product_types.ix_port import IXPort, IXPortInactive
+from gso.services.partners import get_partner_by_name
+from gso.services.subscriptions import generate_unique_id
+from gso.utils.helpers import active_edge_port_selector, partner_choice
+from gso.utils.types.ip_address import IPv4AddressType, IPv4Netmask, IPv6AddressType, IPv6Netmask
+from gso.utils.types.tt_number import TTNumber
+from gso.utils.types.virtual_identifiers import VLAN_ID
+
+
+def initial_input_generator(product_name: str) -> FormGenerator:
+    """Gather input from the operator about a new IX Port subscription."""
+
+    geant_partner_id = get_partner_by_name("GEANT").partner_id
+
+    class InitialIXPortForm(FormPage):
+        model_config = ConfigDict(title=f"{product_name}")
+
+        tt_number: TTNumber
+        partner: partner_choice()
+    initial_user_input = yield InitialIXPortForm
+
+    class ConfigureIXPortForm(SubmitFormPage):
+        model_config = ConfigDict(title=f"{product_name} - Configure IX Port")
+
+        gs_id: str = Field(default_factory=lambda: generate_unique_id("GS"))
+        edge_port: active_edge_port_selector(
+            partner_id=initial_user_input.partner if initial_user_input.partner != geant_partner_id else None
+        )
+        is_tagged: bool = True
+        vlan_id: VLAN_ID | None = None
+        divider1: Divider = Field(None, exclude=True)
+        ipv4_address: IPv4AddressType | None = None
+        ipv4_mask: IPv4Netmask | None = None
+        ipv6_address: IPv6AddressType | None = None
+        ipv6_mask: IPv6Netmask | None = None
+        custom_firewall_filters: bool = False
+        divider2: Divider = Field(None, exclude=True)
+        v4_bfd_settings: BFDSettings
+        v6_bfd_settings: BFDSettings
+
+    config_input = yield ConfigureIXPortForm
+
+    return {"product_name": product_name} | initial_user_input.model_dump() | config_input.model_dump()
+
+
+@step("Create IXPort subscription")
+def create_subscription(product: UUIDstr, partner: UUIDstr) -> State:
+    """Create a new IXPort subscription object."""
+    subscription = IXPortInactive.from_product_id(product, partner)
+    return {"subscription": subscription, "subscription_id": subscription.subscription_id}
+
+
+@step("Initialize IXPort subscription")
+def initialize_subscription(
+        subscription: IXPortInactive,
+        gs_id: str,
+        edge_port: str,
+        is_tagged: bool,
+        vlan_id: VLAN_ID | None,
+        ipv4_address: IPv4AddressType | None,
+        ipv4_mask: IPv4Netmask | None,
+        ipv6_address: IPv6AddressType | None,
+        ipv6_mask: IPv6Netmask | None,
+        custom_firewall_filters: bool,
+        v4_bfd_settings: BFDSettings,
+        v6_bfd_settings: BFDSettings,
+) -> State:
+    """Initialize the IXPort subscription object."""
+
+    subscription.ix_port.l3_interface = subscription.ix_port.l3_interface.new(
+        uuid4(),
+        gs_id=gs_id,
+        edge_port=EdgePort.from_subscription(subscription_id=edge_port).edge_port,
+        is_tagged=is_tagged,
+        vlan_id=vlan_id,
+        ipv4_address=ipv4_address,
+        ipv4_mask=ipv4_mask,
+        ipv6_address=ipv6_address,
+        ipv6_mask=ipv6_mask,
+        custom_firewall_filters=custom_firewall_filters,
+        v4_bfd_settings=v4_bfd_settings,
+        v6_bfd_settings=v6_bfd_settings,
+    )
+
+    subscription = IXPort.from_other_lifecycle(subscription, SubscriptionLifecycle.PROVISIONING)
+    subscription.description = f"{subscription.product.name} - {gs_id}"
+
+    return {"subscription": subscription}
+
+
+@workflow(
+    "Create IX Port Service",
+    initial_input_form=wrap_create_initial_input_form(initial_input_generator),
+    target=Target.CREATE,
+)
+def create_ix_port() -> StepList:
+    """Create a new IX Port service subscription."""
+    return (
+            begin
+            >> create_subscription
+            >> store_process_subscription(Target.CREATE)
+            >> initialize_subscription
+            >> set_status(SubscriptionLifecycle.ACTIVE)
+            >> resync
+            >> done
+    )
-- 
GitLab


From 818a47efdefa80b7c06e2ea51b16883f5f9e0d3f Mon Sep 17 00:00:00 2001
From: Neda Moeini <neda.moeini@geant.org>
Date: Mon, 2 Jun 2025 11:52:18 +0200
Subject: [PATCH 22/24] Add BFD settings form and update IX Port creation WF

---
 ...5_add_commertial_peers_ix_port_private_.py | 39 +++++++++++++++++++
 .../product_blocks/l3_interface_port.py       | 10 ++---
 gso/workflows/ix_port/__init__.py             |  1 +
 gso/workflows/ix_port/create_ix_port.py       | 21 ++++++----
 4 files changed, 59 insertions(+), 12 deletions(-)
 create mode 100644 gso/migrations/versions/2025-06-02_f16525338855_add_commertial_peers_ix_port_private_.py
 create mode 100644 gso/workflows/ix_port/__init__.py

diff --git a/gso/migrations/versions/2025-06-02_f16525338855_add_commertial_peers_ix_port_private_.py b/gso/migrations/versions/2025-06-02_f16525338855_add_commertial_peers_ix_port_private_.py
new file mode 100644
index 000000000..23bfb0b8a
--- /dev/null
+++ b/gso/migrations/versions/2025-06-02_f16525338855_add_commertial_peers_ix_port_private_.py
@@ -0,0 +1,39 @@
+"""Add Commertial peers, IX port, private peer port and transit proivder port WFs.
+
+Revision ID: f16525338855
+Revises: fb88e4914b47
+Create Date: 2025-06-02 11:36:48.053232
+
+"""
+import sqlalchemy as sa
+from alembic import op
+
+# revision identifiers, used by Alembic.
+revision = 'f16525338855'
+down_revision = 'fb88e4914b47'
+branch_labels = None
+depends_on = None
+
+
+from orchestrator.migrations.helpers import create_workflow, delete_workflow
+
+new_workflows = [
+    {
+        "name": "create_ix_port",
+        "target": "CREATE",
+        "description": "Create IX Port Service",
+        "product_type": "IXPort"
+    }
+]
+
+
+def upgrade() -> None:
+    conn = op.get_bind()
+    for workflow in new_workflows:
+        create_workflow(conn, workflow)
+
+
+def downgrade() -> None:
+    conn = op.get_bind()
+    for workflow in new_workflows:
+        delete_workflow(conn, workflow["name"])
diff --git a/gso/products/product_blocks/l3_interface_port.py b/gso/products/product_blocks/l3_interface_port.py
index 8712c4720..4b3914512 100644
--- a/gso/products/product_blocks/l3_interface_port.py
+++ b/gso/products/product_blocks/l3_interface_port.py
@@ -22,11 +22,11 @@ class L3InterfacePortBlockInactive(
     ipv4_mask: IPv4Netmask | None = None
     ipv6_address: IPv6AddressType | None = None
     ipv6_mask: IPv6Netmask | None = None
-    custom_firewall_filters: bool
-    gs_id: str
-    edge_port: EdgePortBlockInactive
-    v4_bfd_settings: BFDSettings
-    v6_bfd_settings: BFDSettings
+    custom_firewall_filters: bool | None = None
+    gs_id: str | None = None
+    edge_port: EdgePortBlockInactive | None = None
+    v4_bfd_settings: BFDSettings | None = None
+    v6_bfd_settings: BFDSettings | None = None
 
 
 class L3InterfacePortBlockProvisioning(
diff --git a/gso/workflows/ix_port/__init__.py b/gso/workflows/ix_port/__init__.py
new file mode 100644
index 000000000..85bbeef04
--- /dev/null
+++ b/gso/workflows/ix_port/__init__.py
@@ -0,0 +1 @@
+"""All workflows that can be executed on IX Port."""
diff --git a/gso/workflows/ix_port/create_ix_port.py b/gso/workflows/ix_port/create_ix_port.py
index d5fbccd39..b30e1f680 100644
--- a/gso/workflows/ix_port/create_ix_port.py
+++ b/gso/workflows/ix_port/create_ix_port.py
@@ -8,7 +8,7 @@ from orchestrator.types import SubscriptionLifecycle
 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
-from pydantic import ConfigDict, Field
+from pydantic import ConfigDict, Field, BaseModel
 from pydantic_forms.types import FormGenerator, State, UUIDstr
 from pydantic_forms.validators import Divider
 
@@ -35,6 +35,13 @@ def initial_input_generator(product_name: str) -> FormGenerator:
         partner: partner_choice()
     initial_user_input = yield InitialIXPortForm
 
+    class BFDSettingsForm(BaseModel):
+        bfd_enabled: bool = False
+        bfd_interval_rx: int | None = Field(default=None, examples=["BFD RX defaults"])
+        bfd_interval_tx: int | None = None
+        bfd_multiplier: int | None = None
+
+
     class ConfigureIXPortForm(SubmitFormPage):
         model_config = ConfigDict(title=f"{product_name} - Configure IX Port")
 
@@ -51,8 +58,8 @@ def initial_input_generator(product_name: str) -> FormGenerator:
         ipv6_mask: IPv6Netmask | None = None
         custom_firewall_filters: bool = False
         divider2: Divider = Field(None, exclude=True)
-        v4_bfd_settings: BFDSettings
-        v6_bfd_settings: BFDSettings
+        v4_bfd_settings: BFDSettingsForm
+        v6_bfd_settings: BFDSettingsForm
 
     config_input = yield ConfigureIXPortForm
 
@@ -78,8 +85,8 @@ def initialize_subscription(
         ipv6_address: IPv6AddressType | None,
         ipv6_mask: IPv6Netmask | None,
         custom_firewall_filters: bool,
-        v4_bfd_settings: BFDSettings,
-        v6_bfd_settings: BFDSettings,
+        v4_bfd_settings: dict,
+        v6_bfd_settings: dict,
 ) -> State:
     """Initialize the IXPort subscription object."""
 
@@ -94,8 +101,8 @@ def initialize_subscription(
         ipv6_address=ipv6_address,
         ipv6_mask=ipv6_mask,
         custom_firewall_filters=custom_firewall_filters,
-        v4_bfd_settings=v4_bfd_settings,
-        v6_bfd_settings=v6_bfd_settings,
+        v4_bfd_settings=BFDSettings.new(subscription_id=uuid4(), **(v4_bfd_settings)),
+        v6_bfd_settings=BFDSettings.new(subscription_id=uuid4(), **(v6_bfd_settings)),
     )
 
     subscription = IXPort.from_other_lifecycle(subscription, SubscriptionLifecycle.PROVISIONING)
-- 
GitLab


From 335854cf25f9844af1c001de3580dd91f031c268 Mon Sep 17 00:00:00 2001
From: Aleksandr Kurbatov <ak@geant.org>
Date: Tue, 3 Jun 2025 15:54:18 +0100
Subject: [PATCH 23/24] Remove BFD settings from l3_interface_port model

---
 gso/products/product_blocks/l3_interface_port.py | 10 +---------
 1 file changed, 1 insertion(+), 9 deletions(-)

diff --git a/gso/products/product_blocks/l3_interface_port.py b/gso/products/product_blocks/l3_interface_port.py
index 4b3914512..3375e63f2 100644
--- a/gso/products/product_blocks/l3_interface_port.py
+++ b/gso/products/product_blocks/l3_interface_port.py
@@ -4,9 +4,7 @@ from orchestrator.domain.base import ProductBlockModel
 from orchestrator.types import SubscriptionLifecycle
 
 from gso.products.product_blocks.edge_port import EdgePortBlock, EdgePortBlockInactive, EdgePortBlockProvisioning
-from gso.products.product_blocks.service_binding_port import (
-    BFDSettings,
-)
+
 from gso.utils.types.ip_address import IPv4AddressType, IPv4Netmask, IPv6AddressType, IPv6Netmask
 from gso.utils.types.virtual_identifiers import VLAN_ID
 
@@ -25,8 +23,6 @@ class L3InterfacePortBlockInactive(
     custom_firewall_filters: bool | None = None
     gs_id: str | None = None
     edge_port: EdgePortBlockInactive | None = None
-    v4_bfd_settings: BFDSettings | None = None
-    v6_bfd_settings: BFDSettings | None = None
 
 
 class L3InterfacePortBlockProvisioning(
@@ -43,8 +39,6 @@ class L3InterfacePortBlockProvisioning(
     custom_firewall_filters: bool
     gs_id: str
     edge_port: EdgePortBlockProvisioning
-    v4_bfd_settings: BFDSettings
-    v6_bfd_settings: BFDSettings
 
 
 class L3InterfacePortBlock(
@@ -61,5 +55,3 @@ class L3InterfacePortBlock(
     custom_firewall_filters: bool
     gs_id: str
     edge_port: EdgePortBlock
-    v4_bfd_settings: BFDSettings
-    v6_bfd_settings: BFDSettings
-- 
GitLab


From c718ca642e6252c8bd2ba67fcd0d042a42868179 Mon Sep 17 00:00:00 2001
From: Aleksandr Kurbatov <ak@geant.org>
Date: Tue, 3 Jun 2025 15:54:48 +0100
Subject: [PATCH 24/24] Update create_ix_port WF

---
 gso/workflows/ix_port/create_ix_port.py | 37 +++++++++++++++++++------
 1 file changed, 28 insertions(+), 9 deletions(-)

diff --git a/gso/workflows/ix_port/create_ix_port.py b/gso/workflows/ix_port/create_ix_port.py
index b30e1f680..02ddde656 100644
--- a/gso/workflows/ix_port/create_ix_port.py
+++ b/gso/workflows/ix_port/create_ix_port.py
@@ -1,6 +1,9 @@
 """Create IX Port Service Workflow"""
+import re
+from typing import Any
 from uuid import uuid4
 
+from gso.services.lso_client import LSOState
 from orchestrator import step, workflow
 from orchestrator.forms import FormPage, SubmitFormPage
 from orchestrator.targets import Target
@@ -12,10 +15,10 @@ from pydantic import ConfigDict, Field, BaseModel
 from pydantic_forms.types import FormGenerator, State, UUIDstr
 from pydantic_forms.validators import Divider
 
-from gso.products.product_blocks.service_binding_port import BFDSettings
 from gso.products.product_types.edge_port import EdgePort
 from gso.products.product_types.ix_port import IXPort, IXPortInactive
-from gso.services.partners import get_partner_by_name
+from gso.services.lso_client import LSOState, lso_interaction
+from gso.services.partners import get_partner_by_id, get_partner_by_name
 from gso.services.subscriptions import generate_unique_id
 from gso.utils.helpers import active_edge_port_selector, partner_choice
 from gso.utils.types.ip_address import IPv4AddressType, IPv4Netmask, IPv6AddressType, IPv6Netmask
@@ -58,8 +61,6 @@ def initial_input_generator(product_name: str) -> FormGenerator:
         ipv6_mask: IPv6Netmask | None = None
         custom_firewall_filters: bool = False
         divider2: Divider = Field(None, exclude=True)
-        v4_bfd_settings: BFDSettingsForm
-        v6_bfd_settings: BFDSettingsForm
 
     config_input = yield ConfigureIXPortForm
 
@@ -85,8 +86,6 @@ def initialize_subscription(
         ipv6_address: IPv6AddressType | None,
         ipv6_mask: IPv6Netmask | None,
         custom_firewall_filters: bool,
-        v4_bfd_settings: dict,
-        v6_bfd_settings: dict,
 ) -> State:
     """Initialize the IXPort subscription object."""
 
@@ -101,14 +100,33 @@ def initialize_subscription(
         ipv6_address=ipv6_address,
         ipv6_mask=ipv6_mask,
         custom_firewall_filters=custom_firewall_filters,
-        v4_bfd_settings=BFDSettings.new(subscription_id=uuid4(), **(v4_bfd_settings)),
-        v6_bfd_settings=BFDSettings.new(subscription_id=uuid4(), **(v6_bfd_settings)),
     )
 
     subscription = IXPort.from_other_lifecycle(subscription, SubscriptionLifecycle.PROVISIONING)
     subscription.description = f"{subscription.product.name} - {gs_id}"
+    partner_name = get_partner_by_id(subscription.customer_id).name
 
-    return {"subscription": subscription}
+    return {"subscription": subscription, "partner_name": partner_name}
+
+
+@step("[DRY RUN] Deploy IX port")
+def deploy_l3_port_dry(
+        subscription: dict[str, Any], process_id: UUIDstr, tt_number: str, partner_name: str
+) -> LSOState:
+    """Perform a dry run of deploying L3 interface."""
+    extra_vars = {
+        "subscription": subscription,
+        "partner_name": partner_name,
+        "dry_run": True,
+        "verb": "deploy",
+        "object": "l3_interface",
+    }
+
+    return {
+        "playbook_name": "gap_ansible/playbooks/l3_interface.yaml",
+        "inventory": {"all": {"hosts": subscription["ix_port"]["l3_interface"]["edge_port"]["node"]["router_fqdn"]}},
+        "extra_vars": extra_vars,
+    }
 
 
 @workflow(
@@ -123,6 +141,7 @@ def create_ix_port() -> StepList:
             >> create_subscription
             >> store_process_subscription(Target.CREATE)
             >> initialize_subscription
+            >> lso_interaction(deploy_l3_port_dry)
             >> set_status(SubscriptionLifecycle.ACTIVE)
             >> resync
             >> done
-- 
GitLab