diff --git a/.pylintrc b/.pylintrc
deleted file mode 100644
index d5b831302d169f0ce8adbc8835d2909eee7fd51b..0000000000000000000000000000000000000000
--- a/.pylintrc
+++ /dev/null
@@ -1,9 +0,0 @@
-[MAIN]
-extension-pkg-whitelist=pydantic
-
-[MISCELLANEOUS]
-
-# List of note tags to take in consideration, separated by a comma.
-# Note that it does not contain TODO, only the default FIXME and XXX
-notes=FIXME,
-      XXX
diff --git a/Changelog.md b/Changelog.md
index b0b8daefa2b5c71193b8c366899efee496dc60a2..371dcf4f9723110ce3b7f9d8da4bfb293a815e4b 100644
--- a/Changelog.md
+++ b/Changelog.md
@@ -1,5 +1,9 @@
 # Changelog
 
+## [2.22] - 2024-10-31
+- Added EdgePort, IAS and GEANT IP products and required workflows
+- Refactored pydantic models for maintainability
+
 ## [2.21] - 2024-10-22
 - Fix inventory structure
 - Fix the `pe_router_list` in update_sdp_single_pe
@@ -23,12 +27,6 @@
 ## [2.18] - 2024-10-01
 -  Use solo pool for Celery workers
 
-## [2.17] - 2024-09-30
-- NOTHING IS HERE (JENKINS ISSUE)
-
-## [2.16] - 2024-09-30
-- NOTHING IS HERE (JENKINS ISSUE)
-
 ## [2.15] - 2024-09-30
 - Show current license usage when updating Kentik license of a router
 - Fix the bug of clearing all the AE members and creating new objects instead of updating it.
diff --git a/build-docs.sh b/build-docs.sh
index 34eadabbe43f22cac9e4abd40c3fadf1c3d872a3..f68d5ad6dfa32f42e18bfdcee90a8351d6c5e8fd 100755
--- a/build-docs.sh
+++ b/build-docs.sh
@@ -3,6 +3,7 @@ set -o errexit
 set -o nounset
 
 export OSS_PARAMS_FILENAME=../gso/oss-params-example.json
+export TESTING=true
 
 pip install sphinx_rtd_theme sphinxcontrib-jquery
 
diff --git a/docs/source/glossary.rst b/docs/source/glossary.rst
index 6ac6193af5f17e003f5f84eba351c981e6824a2d..59b699e5f3bffd1a16fc523d6e08e3c635b1de05 100644
--- a/docs/source/glossary.rst
+++ b/docs/source/glossary.rst
@@ -9,6 +9,9 @@ Glossary of terms
   API
     Application Programming Interface
 
+  BFD
+    Bi-directional Forwarding Detection
+
   BGP
     Border Gateway Protocol: a path vector routing protocol described in
     `RFC 4271 <https://datatracker.ietf.org/doc/html/rfc4271>`_.
@@ -54,18 +57,27 @@ Glossary of terms
   JSON
     JavaScript Object Notation
 
+  LACP
+    Link Aggregation Control Protocol
+
   LAG
     Link Aggregation: a bundle of multiple network connections.
 
   LAN
     Local Area Network
 
+  LLDP
+    Link Layer Discovery Protocol
+
   LSO
     Lightweight Service Orchestrator
 
   NET
     Network Entity Title: used for :term:`ISIS` routing.
 
+  NREN
+    National Research and Education Network
+
   OIDC
     OpenID Connect
 
@@ -78,6 +90,14 @@ Glossary of terms
   OSS
     Operational Support Systems
 
+  SBP
+    Service Binding Point, a logical construct used in the orchestrator to attach a partner subscription to a physical
+    (set of) ports.
+
+  SDP
+    Service Demarcation Point: A logical construct used for modeling partner subscriptions. It models the link between
+    the physical and the service domains.
+
   SNMP
     Simple Network Management Protocol: a protocol that's used for gathering data, widely used for network management
     and monitoring.
@@ -91,5 +111,8 @@ Glossary of terms
   VLAN
     Virtual LAN
 
+  WAN
+    Wide Area Network
+
   WFO
     `Workflow Orchestrator <https://workfloworchestrator.org/>`_
diff --git a/docs/source/module/products/product_blocks/bgp_session.rst b/docs/source/module/products/product_blocks/bgp_session.rst
new file mode 100644
index 0000000000000000000000000000000000000000..5d3fe341ee92e31dd323d06bcd00206c1120c438
--- /dev/null
+++ b/docs/source/module/products/product_blocks/bgp_session.rst
@@ -0,0 +1,6 @@
+``gso.products.product_blocks.bgp_session``
+===========================================
+
+.. automodule:: gso.products.product_blocks.bgp_session
+   :members:
+   :show-inheritance:
diff --git a/docs/source/module/products/product_blocks/edge_port.rst b/docs/source/module/products/product_blocks/edge_port.rst
new file mode 100644
index 0000000000000000000000000000000000000000..4d6dbf1f35248da7ea43b3f570fff7f2fced4a2d
--- /dev/null
+++ b/docs/source/module/products/product_blocks/edge_port.rst
@@ -0,0 +1,6 @@
+``gso.products.product_blocks.edge_port``
+=========================================
+
+.. automodule:: gso.products.product_blocks.edge_port
+   :members:
+   :show-inheritance:
diff --git a/docs/source/module/products/product_blocks/index.rst b/docs/source/module/products/product_blocks/index.rst
index da2852b9e7b85abfc06c2ca1906b71663d538d45..f7f2c86785d04b746ad14c7750a51f8b0b3b030f 100644
--- a/docs/source/module/products/product_blocks/index.rst
+++ b/docs/source/module/products/product_blocks/index.rst
@@ -14,13 +14,16 @@ Submodules
 .. toctree::
    :maxdepth: 1
 
-   super_pop_switch
-   office_router
+   bgp_session
+   edge_port
    iptrunk
+   lan_switch_interconnect
+   nren_l3_core_service
+   office_router
+   opengear
+   pop_vlan
    router
+   service_binding_port
    site
+   super_pop_switch
    switch
-   lan_switch_interconnect
-   pop_vlan
-   opengear
-
diff --git a/docs/source/module/products/product_blocks/nren_l3_core_service.rst b/docs/source/module/products/product_blocks/nren_l3_core_service.rst
new file mode 100644
index 0000000000000000000000000000000000000000..53e849d98c7b948468f21fb21280431a670274a2
--- /dev/null
+++ b/docs/source/module/products/product_blocks/nren_l3_core_service.rst
@@ -0,0 +1,6 @@
+``gso.products.product_blocks.nren_l3_core_service``
+====================================================
+
+.. automodule:: gso.products.product_blocks.nren_l3_core_service
+   :members:
+   :show-inheritance:
diff --git a/docs/source/module/products/product_blocks/service_binding_port.rst b/docs/source/module/products/product_blocks/service_binding_port.rst
new file mode 100644
index 0000000000000000000000000000000000000000..a7036dc21ef3191fda321f70362d08e89b49ffae
--- /dev/null
+++ b/docs/source/module/products/product_blocks/service_binding_port.rst
@@ -0,0 +1,6 @@
+``gso.products.product_blocks.service_binding_port``
+====================================================
+
+.. automodule:: gso.products.product_blocks.service_binding_port
+   :members:
+   :show-inheritance:
diff --git a/docs/source/module/products/product_types/edge_port.rst b/docs/source/module/products/product_types/edge_port.rst
new file mode 100644
index 0000000000000000000000000000000000000000..ae7140595640e1d605f10b486df98d939a32aa8a
--- /dev/null
+++ b/docs/source/module/products/product_types/edge_port.rst
@@ -0,0 +1,6 @@
+``gso.products.product_types.edge_port``
+========================================
+
+.. automodule:: gso.products.product_types.edge_port
+   :members:
+   :show-inheritance:
diff --git a/docs/source/module/products/product_types/index.rst b/docs/source/module/products/product_types/index.rst
index 06c706298b1badf546a36c7c84cfb47966c96ce2..70f882d3632166aa86d3b46f85c33493c043da0f 100644
--- a/docs/source/module/products/product_types/index.rst
+++ b/docs/source/module/products/product_types/index.rst
@@ -14,12 +14,14 @@ Submodules
 .. toctree::
    :maxdepth: 1
 
-   super_pop_switch
-   office_router
+   edge_port
    iptrunk
+   lan_switch_interconnect
+   nren_l3_core_service
+   office_router
+   opengear
+   pop_vlan
    router
    site
+   super_pop_switch
    switch
-   lan_switch_interconnect
-   pop_vlan
-   opengear
diff --git a/docs/source/module/products/product_types/nren_l3_core_service.rst b/docs/source/module/products/product_types/nren_l3_core_service.rst
new file mode 100644
index 0000000000000000000000000000000000000000..4440d033bb1f7e439b51eaba6429d7d2f5f63883
--- /dev/null
+++ b/docs/source/module/products/product_types/nren_l3_core_service.rst
@@ -0,0 +1,6 @@
+``gso.products.product_types.nren_l3_core_service``
+===================================================
+
+.. automodule:: gso.products.product_types.nren_l3_core_service
+   :members:
+   :show-inheritance:
diff --git a/docs/source/module/utils/index.rst b/docs/source/module/utils/index.rst
index 52f992ba035f17541d1e5314844ee59490a883bf..7289690736f520a7b462cce3c7446c282a805613 100644
--- a/docs/source/module/utils/index.rst
+++ b/docs/source/module/utils/index.rst
@@ -5,6 +5,14 @@
    :members:
    :show-inheritance:
 
+Subpackages
+-----------
+
+.. toctree::
+   :maxdepth: 1
+
+   types/index
+
 Submodules
 ----------
 
diff --git a/docs/source/module/utils/types.rst b/docs/source/module/utils/types.rst
deleted file mode 100644
index c70c8dd0c61a4fa29cc2f123ec4d0643ab7bdf5d..0000000000000000000000000000000000000000
--- a/docs/source/module/utils/types.rst
+++ /dev/null
@@ -1,6 +0,0 @@
-``gso.utils.types``
-===================
-
-.. automodule:: gso.utils.types
-   :members:
-   :show-inheritance:
diff --git a/docs/source/module/utils/types/base_site.rst b/docs/source/module/utils/types/base_site.rst
new file mode 100644
index 0000000000000000000000000000000000000000..ac952086ae62069f8a4cd3f0fc9e757e9035d231
--- /dev/null
+++ b/docs/source/module/utils/types/base_site.rst
@@ -0,0 +1,6 @@
+``gso.utils.types.base_site``
+=============================
+
+.. automodule:: gso.utils.types.base_site
+   :members:
+   :show-inheritance:
diff --git a/docs/source/module/utils/types/coordinates.rst b/docs/source/module/utils/types/coordinates.rst
new file mode 100644
index 0000000000000000000000000000000000000000..85c52ba10681782a6ce049a231c8a21b2a0aede3
--- /dev/null
+++ b/docs/source/module/utils/types/coordinates.rst
@@ -0,0 +1,6 @@
+``gso.utils.types.coordinates``
+===============================
+
+.. automodule:: gso.utils.types.coordinates
+   :members:
+   :show-inheritance:
diff --git a/docs/source/module/utils/types/country_code.rst b/docs/source/module/utils/types/country_code.rst
new file mode 100644
index 0000000000000000000000000000000000000000..130b77afb0254aa77fabde7f2f5f5801e27adfc4
--- /dev/null
+++ b/docs/source/module/utils/types/country_code.rst
@@ -0,0 +1,6 @@
+``gso.utils.types.country_code``
+================================
+
+.. automodule:: gso.utils.types.country_code
+   :members:
+   :show-inheritance:
diff --git a/docs/source/module/utils/types/index.rst b/docs/source/module/utils/types/index.rst
new file mode 100644
index 0000000000000000000000000000000000000000..2dcf23ec96f2d068448c3d3f3aadaf1d3ec3b805
--- /dev/null
+++ b/docs/source/module/utils/types/index.rst
@@ -0,0 +1,24 @@
+``gso.utils.types``
+===================
+
+.. automodule:: gso.utils.types
+   :members:
+   :show-inheritance:
+
+
+Submodules
+----------
+
+.. toctree::
+   :maxdepth: 2
+   :titlesonly:
+
+   base_site
+   coordinates
+   country_code
+   interfaces
+   ip_address
+   netbox_router
+   site_name
+   tt_number
+   unique_field
diff --git a/docs/source/module/utils/types/interfaces.rst b/docs/source/module/utils/types/interfaces.rst
new file mode 100644
index 0000000000000000000000000000000000000000..95c9a3dacf770c3963f431cb2d40a612d4b1043f
--- /dev/null
+++ b/docs/source/module/utils/types/interfaces.rst
@@ -0,0 +1,6 @@
+``gso.utils.types.interfaces``
+==============================
+
+.. automodule:: gso.utils.types.interfaces
+   :members:
+   :show-inheritance:
diff --git a/docs/source/module/utils/types/ip_address.rst b/docs/source/module/utils/types/ip_address.rst
new file mode 100644
index 0000000000000000000000000000000000000000..68858af762bd0559a88e2fcacf88edbd9f4bd1a3
--- /dev/null
+++ b/docs/source/module/utils/types/ip_address.rst
@@ -0,0 +1,6 @@
+``gso.utils.types.ip_address``
+==============================
+
+.. automodule:: gso.utils.types.ip_address
+   :members:
+   :show-inheritance:
diff --git a/docs/source/module/utils/types/netbox_router.rst b/docs/source/module/utils/types/netbox_router.rst
new file mode 100644
index 0000000000000000000000000000000000000000..1d35d235e221b31a8b09e5c0a0dcef5cfc342fcc
--- /dev/null
+++ b/docs/source/module/utils/types/netbox_router.rst
@@ -0,0 +1,6 @@
+``gso.utils.types.netbox_router``
+=================================
+
+.. automodule:: gso.utils.types.netbox_router
+   :members:
+   :show-inheritance:
diff --git a/docs/source/module/utils/types/site_name.rst b/docs/source/module/utils/types/site_name.rst
new file mode 100644
index 0000000000000000000000000000000000000000..df2934a6d0449a9129615b97e5584fd687c528b3
--- /dev/null
+++ b/docs/source/module/utils/types/site_name.rst
@@ -0,0 +1,6 @@
+``gso.utils.types.site_name``
+=============================
+
+.. automodule:: gso.utils.types.site_name
+   :members:
+   :show-inheritance:
diff --git a/docs/source/module/utils/types/tt_number.rst b/docs/source/module/utils/types/tt_number.rst
new file mode 100644
index 0000000000000000000000000000000000000000..43410a3536f9fa879411e512140328293543fffa
--- /dev/null
+++ b/docs/source/module/utils/types/tt_number.rst
@@ -0,0 +1,6 @@
+``gso.utils.types.tt_number``
+=============================
+
+.. automodule:: gso.utils.types.tt_number
+   :members:
+   :show-inheritance:
diff --git a/docs/source/module/utils/types/unique_field.rst b/docs/source/module/utils/types/unique_field.rst
new file mode 100644
index 0000000000000000000000000000000000000000..3a74a0d1faea700768b41be6fd3dfe67f2d4df58
--- /dev/null
+++ b/docs/source/module/utils/types/unique_field.rst
@@ -0,0 +1,6 @@
+``gso.utils.types.unique_field``
+================================
+
+.. automodule:: gso.utils.types.unique_field
+   :members:
+   :show-inheritance:
diff --git a/docs/source/module/workflows/edge_port/create_edge_port.rst b/docs/source/module/workflows/edge_port/create_edge_port.rst
new file mode 100644
index 0000000000000000000000000000000000000000..7cedc807890765ce40f92e07d4830d4fa0be0f50
--- /dev/null
+++ b/docs/source/module/workflows/edge_port/create_edge_port.rst
@@ -0,0 +1,6 @@
+``gso.workflows.edge_port.create_edge_port``
+============================================
+
+.. automodule:: gso.workflows.edge_port.create_edge_port
+   :members:
+   :show-inheritance:
diff --git a/docs/source/module/workflows/edge_port/create_imported_edge_port.rst b/docs/source/module/workflows/edge_port/create_imported_edge_port.rst
new file mode 100644
index 0000000000000000000000000000000000000000..38b8d7de0ef42d82366b6ce3c363b23db8af0ee5
--- /dev/null
+++ b/docs/source/module/workflows/edge_port/create_imported_edge_port.rst
@@ -0,0 +1,6 @@
+``gso.workflows.edge_port.create_imported_edge_port``
+=====================================================
+
+.. automodule:: gso.workflows.edge_port.create_imported_edge_port
+   :members:
+   :show-inheritance:
diff --git a/docs/source/module/workflows/edge_port/import_edge_port.rst b/docs/source/module/workflows/edge_port/import_edge_port.rst
new file mode 100644
index 0000000000000000000000000000000000000000..31d54a7071095f4378701bc6bfbe380e1edb305d
--- /dev/null
+++ b/docs/source/module/workflows/edge_port/import_edge_port.rst
@@ -0,0 +1,6 @@
+``gso.workflows.edge_port.import_edge_port``
+============================================
+
+.. automodule:: gso.workflows.edge_port.import_edge_port
+   :members:
+   :show-inheritance:
diff --git a/docs/source/module/workflows/edge_port/index.rst b/docs/source/module/workflows/edge_port/index.rst
new file mode 100644
index 0000000000000000000000000000000000000000..51d11ff47730c1ff9932cb33498aa3cdbffc468d
--- /dev/null
+++ b/docs/source/module/workflows/edge_port/index.rst
@@ -0,0 +1,20 @@
+``gso.workflows.edge_port``
+===========================
+
+.. automodule:: gso.workflows.edge_port
+   :members:
+   :show-inheritance:
+
+Submodules
+----------
+
+.. toctree::
+   :maxdepth: 2
+   :titlesonly:
+
+   create_edge_port
+   modify_edge_port
+   terminate_edge_port
+   validate_edge_port
+   import_edge_port
+   create_imported_edge_port
diff --git a/docs/source/module/workflows/edge_port/modify_edge_port.rst b/docs/source/module/workflows/edge_port/modify_edge_port.rst
new file mode 100644
index 0000000000000000000000000000000000000000..88d0133603cfb5055c0d35aef1f5e14a9e848310
--- /dev/null
+++ b/docs/source/module/workflows/edge_port/modify_edge_port.rst
@@ -0,0 +1,6 @@
+``gso.workflows.edge_port.modify_edge_port``
+============================================
+
+.. automodule:: gso.workflows.edge_port.modify_edge_port
+   :members:
+   :show-inheritance:
diff --git a/docs/source/module/workflows/edge_port/terminate_edge_port.rst b/docs/source/module/workflows/edge_port/terminate_edge_port.rst
new file mode 100644
index 0000000000000000000000000000000000000000..4613d361d6576b6b89c665f201b1eecee161f813
--- /dev/null
+++ b/docs/source/module/workflows/edge_port/terminate_edge_port.rst
@@ -0,0 +1,6 @@
+``gso.workflows.edge_port.terminate_edge_port``
+===============================================
+
+.. automodule:: gso.workflows.edge_port.terminate_edge_port
+   :members:
+   :show-inheritance:
diff --git a/docs/source/module/workflows/edge_port/validate_edge_port.rst b/docs/source/module/workflows/edge_port/validate_edge_port.rst
new file mode 100644
index 0000000000000000000000000000000000000000..a80e2eca96440d312587bea46966a2abf76bc4c2
--- /dev/null
+++ b/docs/source/module/workflows/edge_port/validate_edge_port.rst
@@ -0,0 +1,6 @@
+``gso.workflows.edge_port.validate_edge_port``
+==============================================
+
+.. automodule:: gso.workflows.edge_port.validate_edge_port
+   :members:
+   :show-inheritance:
diff --git a/docs/source/module/workflows/index.rst b/docs/source/module/workflows/index.rst
index 3ee41bad0534418b8417ef5cd037795a9681628d..774573c9e67c1a069175fc5aa7e39b74c2283a32 100644
--- a/docs/source/module/workflows/index.rst
+++ b/docs/source/module/workflows/index.rst
@@ -12,7 +12,9 @@ Subpackages
    :maxdepth: 2
    :titlesonly:
 
+   edge_port/index
    iptrunk/index
+   nren_l3_core_service/index
    office_router/index
    opengear/index
    router/index
diff --git a/docs/source/module/workflows/nren_l3_core_service/create_imported_nren_l3_core_service.rst b/docs/source/module/workflows/nren_l3_core_service/create_imported_nren_l3_core_service.rst
new file mode 100644
index 0000000000000000000000000000000000000000..249f9b824661d6ed6773b3695c6084b5aa3348be
--- /dev/null
+++ b/docs/source/module/workflows/nren_l3_core_service/create_imported_nren_l3_core_service.rst
@@ -0,0 +1,6 @@
+``gso.workflows.nren_l3_core_service.create_imported_nren_l3_core_service``
+===========================================================================
+
+.. automodule:: gso.workflows.nren_l3_core_service.create_imported_nren_l3_core_service
+   :members:
+   :show-inheritance:
diff --git a/docs/source/module/workflows/nren_l3_core_service/create_nren_l3_core_service.rst b/docs/source/module/workflows/nren_l3_core_service/create_nren_l3_core_service.rst
new file mode 100644
index 0000000000000000000000000000000000000000..e1418b4c915bd2e0c43130f9a344090ee0b4472a
--- /dev/null
+++ b/docs/source/module/workflows/nren_l3_core_service/create_nren_l3_core_service.rst
@@ -0,0 +1,6 @@
+``gso.workflows.nren_l3_core_service.create_nren_l3_core_service``
+==================================================================
+
+.. automodule:: gso.workflows.nren_l3_core_service.create_nren_l3_core_service
+   :members:
+   :show-inheritance:
diff --git a/docs/source/module/workflows/nren_l3_core_service/import_nren_l3_core_service.rst b/docs/source/module/workflows/nren_l3_core_service/import_nren_l3_core_service.rst
new file mode 100644
index 0000000000000000000000000000000000000000..fdb67a1e8c8b2e65774149001da612d34d5f312f
--- /dev/null
+++ b/docs/source/module/workflows/nren_l3_core_service/import_nren_l3_core_service.rst
@@ -0,0 +1,6 @@
+``gso.workflows.nren_l3_core_service.import_nren_l3_core_service``
+==================================================================
+
+.. automodule:: gso.workflows.nren_l3_core_service.import_nren_l3_core_service
+   :members:
+   :show-inheritance:
diff --git a/docs/source/module/workflows/nren_l3_core_service/index.rst b/docs/source/module/workflows/nren_l3_core_service/index.rst
new file mode 100644
index 0000000000000000000000000000000000000000..aa55ff08c8e565e7304ff1c4082f9d3e0b4caae3
--- /dev/null
+++ b/docs/source/module/workflows/nren_l3_core_service/index.rst
@@ -0,0 +1,19 @@
+``gso.workflows.nren_l3_core_service``
+======================================
+
+.. automodule:: gso.workflows.nren_l3_core_service
+   :members:
+   :show-inheritance:
+
+Submodules
+----------
+
+.. toctree::
+   :maxdepth: 2
+   :titlesonly:
+
+   create_nren_l3_core_service
+   create_imported_nren_l3_core_service
+   import_nren_l3_core_service
+   migrate_nren_l3_core_service
+   modify_nren_l3_core_service
diff --git a/docs/source/module/workflows/nren_l3_core_service/migrate_nren_l3_core_service.rst b/docs/source/module/workflows/nren_l3_core_service/migrate_nren_l3_core_service.rst
new file mode 100644
index 0000000000000000000000000000000000000000..9a550c827f43ee3e06ddcd88440897592c83eff5
--- /dev/null
+++ b/docs/source/module/workflows/nren_l3_core_service/migrate_nren_l3_core_service.rst
@@ -0,0 +1,6 @@
+``gso.workflows.nren_l3_core_service.migrate_nren_l3_core_service``
+===================================================================
+
+.. automodule:: gso.workflows.nren_l3_core_service.migrate_nren_l3_core_service
+   :members:
+   :show-inheritance:
diff --git a/docs/source/module/workflows/nren_l3_core_service/modify_nren_l3_core_service.rst b/docs/source/module/workflows/nren_l3_core_service/modify_nren_l3_core_service.rst
new file mode 100644
index 0000000000000000000000000000000000000000..bd33ee2260284b4ec67883e6bb6bc17af4bdbfe5
--- /dev/null
+++ b/docs/source/module/workflows/nren_l3_core_service/modify_nren_l3_core_service.rst
@@ -0,0 +1,6 @@
+``gso.workflows.nren_l3_core_service.modify_nren_l3_core_service``
+==================================================================
+
+.. automodule:: gso.workflows.nren_l3_core_service.modify_nren_l3_core_service
+   :members:
+   :show-inheritance:
diff --git a/docs/vale/.vale.ini b/docs/vale/.vale.ini
index 76b4b40c4795fa870bdefc65e758f0476f747d5a..063fe579321aa2a2ee6d0613b7e9b35a8803c180 100644
--- a/docs/vale/.vale.ini
+++ b/docs/vale/.vale.ini
@@ -25,6 +25,7 @@ custom.Contractions = YES
 ; Using a "regular" - instead of an en dash is totally fine
 Microsoft.Negative = NO
 Microsoft.RangeFormat = NO
+Microsoft.We = suggestion
 
 TokenIgnores = (:term:`\S+`), (:param \S+(?: \S+)?:), (:type \S+:), (:return \S+:), (:rtype: \S+), (:class:`\S+`)
 
diff --git a/docs/vale/styles/config/vocabularies/geant-jargon/accept.txt b/docs/vale/styles/config/vocabularies/geant-jargon/accept.txt
index 9d05c0923f215fd59a4b0729ffec2b91301d8e93..6509f0d5bdb354bcd9c17eab9cf79fb900653ee1 100644
--- a/docs/vale/styles/config/vocabularies/geant-jargon/accept.txt
+++ b/docs/vale/styles/config/vocabularies/geant-jargon/accept.txt
@@ -31,3 +31,7 @@ OPA
 OIDC
 HTTPBearer
 Kentik
+UTC
+EARL
+SURF
+[Ee]nsure
diff --git a/docs/vale/styles/custom/Contractions.yml b/docs/vale/styles/custom/Contractions.yml
index 9c2b94a52ad8840de5539565d9e6d4d288cdd838..ecba3923af1c6e99fbacc60afbfbdcfc862c275d 100644
--- a/docs/vale/styles/custom/Contractions.yml
+++ b/docs/vale/styles/custom/Contractions.yml
@@ -7,7 +7,6 @@ swap:
   can't: cannot
   couldn't: could not
   didn't: did not
-  don't: do not
   doesn't: does not
   hasn't: has not
   haven't: have not
diff --git a/gso/cli/imports.py b/gso/cli/imports.py
index 33a4636a0b5010d57a7f3c06053e468bb37e191b..f1553d222d34c974584c45ef15cff56dfa55c371 100644
--- a/gso/cli/imports.py
+++ b/gso/cli/imports.py
@@ -18,8 +18,12 @@ from sqlalchemy.exc import SQLAlchemyError
 
 from gso.db.models import PartnerTable
 from gso.products import ProductType
+from gso.products.product_blocks.bgp_session import IPFamily
+from gso.products.product_blocks.edge_port import EdgePortType, EncapsulationType
 from gso.products.product_blocks.iptrunk import IptrunkType
 from gso.products.product_blocks.router import RouterRole
+from gso.products.product_blocks.service_binding_port import VLAN_ID
+from gso.products.product_types.nren_l3_core_service import NRENL3CoreServiceType
 from gso.services.partners import (
     PartnerEmail,
     PartnerName,
@@ -27,14 +31,15 @@ from gso.services.partners import (
     get_partner_by_name,
 )
 from gso.services.subscriptions import (
+    get_active_edge_port_subscriptions,
     get_active_router_subscriptions,
     get_active_subscriptions_by_field_and_value,
     get_subscriptions,
 )
-from gso.utils.shared_enums import Vendor
+from gso.utils.shared_enums import SBPType, Vendor
 from gso.utils.types.base_site import BaseSiteValidatorModel
 from gso.utils.types.interfaces import LAGMember, LAGMemberList, PhysicalPortCapacity
-from gso.utils.types.ip_address import IPv4AddressType, IPv6AddressType, PortNumber
+from gso.utils.types.ip_address import IPAddress, IPv4AddressType, IPV4Netmask, IPv6AddressType, IPV6Netmask, PortNumber
 
 app: typer.Typer = typer.Typer()
 
@@ -161,6 +166,118 @@ class OpenGearImportModel(BaseModel):
     opengear_wan_gateway: IPv4AddressType
 
 
+class EdgePortImportModel(BaseModel):
+    """Required fields for importing an existing :class:`gso.products.product_types.edge_port`."""
+
+    node: str
+    service_type: EdgePortType
+    speed: PhysicalPortCapacity
+    encapsulation: EncapsulationType
+    name: str
+    minimum_links: int
+    geant_ga_id: str | None
+    mac_address: str | None
+    partner: str
+    enable_lacp: bool
+    ignore_if_down: bool
+    ae_members: LAGMemberList[LAGMember]
+    description: str | None = None
+
+    @field_validator("partner")
+    def check_if_partner_exists(cls, value: str) -> str:
+        """Validate that the partner exists."""
+        try:
+            get_partner_by_name(value)
+        except PartnerNotFoundError as e:
+            msg = f"Partner {value} not found"
+            raise ValueError(msg) from e
+
+        return value
+
+    @field_validator("node")
+    def validate_node(cls, value: str) -> str:
+        """Check if the node is an active PE router in :term:`GSO`."""
+        pe_routers = {
+            str(router.subscription_id)
+            for router in get_active_subscriptions_by_field_and_value("router_role", RouterRole.PE)
+        }
+        if value not in pe_routers:
+            msg = f"Router {value} not found"
+            raise ValueError(msg)
+
+        return value
+
+    @model_validator(mode="after")
+    def check_members(self) -> Self:
+        """Amount of :term:`LAG` members has to match and meet the minimum requirement."""
+        if len(self.ae_members) < self.minimum_links:
+            msg = f"Number of members should be at least {self.minimum_links} (edge_port_minimum_links)"
+            raise ValueError(msg)
+        return self
+
+
+class NRENL3CoreServiceImportModel(BaseModel):
+    """Import :term:`NREN` L3 Core Service model."""
+
+    class BaseBGPPeer(BaseModel):
+        """Base BGP Peer model."""
+
+        bfd_enabled: bool = False
+        bfd_interval: int | None = None
+        bfd_multiplier: int | None = None
+        has_custom_policies: bool = False
+        authentication_key: str
+        multipath_enabled: bool = False
+        send_default_route: bool = False
+        is_passive: bool = False
+        peer_address: IPAddress
+        families: list[IPFamily]
+        is_multi_hop: bool
+        rtbh_enabled: bool  # whether Remote Triggered Blackhole is enabled
+
+    class ServiceBindingPort(BaseModel):
+        """Service Binding model."""
+
+        edge_port: str
+        ap_type: str
+        geant_sid: str
+        sbp_type: SBPType = SBPType.L3
+        is_tagged: bool = False
+        vlan_id: VLAN_ID
+        custom_firewall_filters: bool = False
+        ipv4_address: IPv4AddressType
+        ipv4_mask: IPV4Netmask
+        ipv6_address: IPv6AddressType
+        ipv6_mask: IPV6Netmask
+        is_multi_hop: bool = True
+        bgp_peers: list["NRENL3CoreServiceImportModel.BaseBGPPeer"]
+
+    partner: str
+    service_binding_ports: list[ServiceBindingPort]
+
+    @field_validator("partner")
+    def check_if_partner_exists(cls, value: str) -> str:
+        """Validate that the partner exists."""
+        try:
+            get_partner_by_name(value)
+        except PartnerNotFoundError as e:
+            msg = f"Partner {value} not found"
+            raise ValueError(msg) from e
+
+        return value
+
+    @field_validator("service_binding_ports")
+    def validate_node(cls, value: list[ServiceBindingPort]) -> list[ServiceBindingPort]:
+        """Check if the Service Binding Ports are valid."""
+        edge_ports = [str(subscription["subscription_id"]) for subscription in get_active_edge_port_subscriptions()]
+        for sbp in value:
+            if sbp.edge_port not in edge_ports:
+                msg = f"Edge Port {sbp.edge_port} not found"
+                raise ValueError(msg)
+
+        return value
+
+
 T = TypeVar(
     "T",
     SiteImportModel,
@@ -169,6 +286,8 @@ T = TypeVar(
     SuperPopSwitchImportModel,
     OfficeRouterImportModel,
     OpenGearImportModel,
+    EdgePortImportModel,
+    NRENL3CoreServiceImportModel,
 )
 
 common_filepath_option = typer.Option(
@@ -219,7 +338,7 @@ def _generic_import_product(
     successfully_imported_data = []
     data = _read_data(file_path)
     for details in data:
-        details["partner"] = "GEANT"
+        details["partner"] = details.get("partner", "GEANT")
         typer.echo(f"Creating imported {name_key}: {details[name_key]}")
         try:
             initial_data = import_model(**details)
@@ -297,6 +416,38 @@ def import_opengear(filepath: str = common_filepath_option) -> None:
     )
 
 
+@app.command()
+def import_edge_port(filepath: str = common_filepath_option) -> None:
+    """Import Edge Port into GSO."""
+    successfully_imported_data = []
+    data = _read_data(Path(filepath))
+    for edge_port in data:
+        typer.echo(f"Importing Edge Port {edge_port["name"]} on {edge_port["node"]}. ")
+        try:
+            edge_port["node"] = _get_router_subscription_id(edge_port["node"])
+            initial_data = EdgePortImportModel(**edge_port)
+            start_process("create_imported_edge_port", [initial_data.model_dump()])
+            successfully_imported_data.append(edge_port["name"])
+            typer.echo(f"Successfully imported Edge Port {edge_port["name"]} on {edge_port["node"]}.")
+        except ValidationError as e:
+            typer.echo(f"Validation error: {e}")
+
+    typer.echo("Waiting for the dust to settle before moving on the importing new products...")
+    time.sleep(1)
+
+    edge_port_ids = get_subscriptions(
+        [ProductType.IMPORTED_EDGE_PORT], lifecycles=[SubscriptionLifecycle.ACTIVE], includes=["subscription_id"]
+    )
+    for subscription_id in edge_port_ids:
+        typer.echo(f"Migrating Edge Port {subscription_id}")
+        start_process("import_edge_port", [subscription_id])
+
+    if successfully_imported_data:
+        typer.echo("Successfully imported Edge Ports:")
+        for item in successfully_imported_data:
+            typer.echo(f"- {item}")
+
+
 @app.command()
 def import_iptrunks(filepath: str = common_filepath_option) -> None:
     """Import IP trunks into GSO."""
@@ -390,3 +541,43 @@ def import_partners(file_path: str = typer.Argument(..., help="Path to the CSV f
         typer.echo(f"Failed to import partners: {e}")
     finally:
         db.session.close()
+
+
+@app.command()
+def import_nren_l3_core_service(filepath: str = common_filepath_option) -> None:
+    """Import :term:`NREN` L3 Core Services into :term:`GSO`."""
+    successfully_imported_data = []
+    nren_l3_core_service_list = _read_data(Path(filepath))
+
+    for nren_l3_core_service in nren_l3_core_service_list:
+        partner = nren_l3_core_service["partner"]
+        service_type = NRENL3CoreServiceType(nren_l3_core_service["service_type"])
+        typer.echo(f"Creating imported {service_type} for {partner}")
+
+        try:
+            initial_data = NRENL3CoreServiceImportModel(**nren_l3_core_service)
+            start_process("create_imported_nren_l3_core_service", [initial_data.model_dump()])
+            edge_ports = [sbp["edge_port"] for sbp in nren_l3_core_service["service_binding_ports"]]
+            successfully_imported_data.append(edge_ports)
+            typer.echo(f"Successfully created imported {service_type} for {partner}")
+        except ValidationError as e:
+            typer.echo(f"Validation error: {e}")
+
+    typer.echo("Waiting for the dust to settle before importing new products...")
+    time.sleep(1)
+
+    # Migrate new products from imported to "full" counterpart.
+    imported_products = get_subscriptions(
+        product_types=[ProductType.IMPORTED_GEANT_IP, ProductType.IMPORTED_IAS],
+        lifecycles=[SubscriptionLifecycle.ACTIVE],
+        includes=["subscription_id"],
+    )
+
+    for subscription_id in imported_products:
+        typer.echo(f"Importing {subscription_id}")
+        start_process("import_nren_l3_core_service", [subscription_id])
+
+    if successfully_imported_data:
+        typer.echo("Successfully created imported NREN L3 Core Services:")
+        for item in successfully_imported_data:
+            typer.echo(f"- {item}")
diff --git a/gso/migrations/versions/2024-10-17_7412c5b7ebe4_add_import_edgeport_and_l3_core_service_.py b/gso/migrations/versions/2024-10-17_7412c5b7ebe4_add_import_edgeport_and_l3_core_service_.py
new file mode 100644
index 0000000000000000000000000000000000000000..d3739209234f72cdbb8f54acb1d7e90de18a1d1c
--- /dev/null
+++ b/gso/migrations/versions/2024-10-17_7412c5b7ebe4_add_import_edgeport_and_l3_core_service_.py
@@ -0,0 +1,99 @@
+"""Add Import EdgePort and L3 Core Service workflows..
+
+Revision ID: 7412c5b7ebe4
+Revises: e1659d366925
+Create Date: 2024-10-17 13:10:38.551706
+
+"""
+import sqlalchemy as sa
+from alembic import op
+
+# revision identifiers, used by Alembic.
+revision = '7412c5b7ebe4'
+down_revision = 'e1659d366925'
+branch_labels = None
+depends_on = None
+
+
+from orchestrator.migrations.helpers import create_workflow, delete_workflow
+
+new_workflows = [
+    {
+        "name": "create_edge_port",
+        "target": "CREATE",
+        "description": "Create Edge Port",
+        "product_type": "EdgePort"
+    },
+    {
+        "name": "modify_edge_port",
+        "target": "MODIFY",
+        "description": "Modify Edge Port",
+        "product_type": "EdgePort"
+    },
+    {
+        "name": "terminate_edge_port",
+        "target": "TERMINATE",
+        "description": "Terminate Edge Port",
+        "product_type": "EdgePort"
+    },
+    {
+        "name": "validate_edge_port",
+        "target": "SYSTEM",
+        "description": "Validate Edge Port Configuration",
+        "product_type": "EdgePort"
+    },
+    {
+        "name": "create_imported_edge_port",
+        "target": "CREATE",
+        "description": "Import Edge Port",
+        "product_type": "ImportedEdgePort"
+    },
+    {
+        "name": "import_edge_port",
+        "target": "MODIFY",
+        "description": "Import Edge Port",
+        "product_type": "ImportedEdgePort"
+    },
+    {
+        "name": "create_nren_l3_core_service",
+        "target": "CREATE",
+        "description": "Create NREN L3 Core Service",
+        "product_type": "NRENL3CoreService"
+    },
+    {
+        "name": "modify_nren_l3_core_service",
+        "target": "MODIFY",
+        "description": "Modify NREN L3 Core Service",
+        "product_type": "NRENL3CoreService"
+    },
+    {
+        "name": "create_imported_nren_l3_core_service",
+        "target": "CREATE",
+        "description": "Create imported NREN L3 Core Service",
+        "product_type": "ImportedNRENL3CoreService"
+    },
+    {
+        "name": "import_nren_l3_core_service",
+        "target": "MODIFY",
+        "description": "Import NREN L3 Core Service",
+        "product_type": "ImportedNRENL3CoreService"
+    },
+    {
+        "name": "migrate_nren_l3_core_service",
+        "target": "MODIFY",
+        "description": "Migrate NREN L3 Core Service",
+        "product_type": "NRENL3CoreService"
+    }
+]
+
+
+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/migrations/versions/2024-10-17_e1659d366925_add_edge_port_and_l3_core_service_.py b/gso/migrations/versions/2024-10-17_e1659d366925_add_edge_port_and_l3_core_service_.py
new file mode 100644
index 0000000000000000000000000000000000000000..d033c263d61f6d483c44230e00e199f0cd5501a5
--- /dev/null
+++ b/gso/migrations/versions/2024-10-17_e1659d366925_add_edge_port_and_l3_core_service_.py
@@ -0,0 +1,536 @@
+"""Add Edge Port and L3 Core Service domain models.
+
+Revision ID: e1659d366925
+Revises: 51c819b28101
+Create Date: 2024-10-17 11:33:38.103939
+
+"""
+import sqlalchemy as sa
+from alembic import op
+
+# revision identifiers, used by Alembic.
+revision = 'e1659d366925'
+down_revision = '51c819b28101'
+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 ('Edge Port', 'Edge Port where a partner service terminates', 'EdgePort', 'EP', 'active') RETURNING products.product_id
+    """))
+    conn.execute(sa.text("""
+INSERT INTO products (name, description, product_type, tag, status) VALUES ('Imported Edge Port', 'A pre-existing Edge Port that is imported into the service database', 'ImportedEdgePort', 'IMP_EP', 'active') RETURNING products.product_id
+    """))
+    conn.execute(sa.text("""
+INSERT INTO products (name, description, product_type, tag, status) VALUES ('GÉANT IP', 'A GÉANT IP subscription for R&E access', 'NRENL3CoreService', 'G_IP', 'active') RETURNING products.product_id
+    """))
+    conn.execute(sa.text("""
+INSERT INTO products (name, description, product_type, tag, status) VALUES ('Imported GÉANT IP', 'A pre-existing GÉANT IP subscription that is imported into the service database', 'ImportedNRENL3CoreService', 'IMP_G_IP', 'active') RETURNING products.product_id
+    """))
+    conn.execute(sa.text("""
+INSERT INTO products (name, description, product_type, tag, status) VALUES ('IAS', 'An Internet Access Service for general internet access', 'NRENL3CoreService', 'IAS', 'active') RETURNING products.product_id
+    """))
+    conn.execute(sa.text("""
+INSERT INTO products (name, description, product_type, tag, status) VALUES ('Imported IAS', 'A pre-existing Internet Access Service that is imported into the service database', 'ImportedNRENL3CoreService', 'IMP_IAS', 'active') RETURNING products.product_id
+    """))
+    conn.execute(sa.text("""
+INSERT INTO fixed_inputs (name, value, product_id) VALUES ('nren_l3_core_service_type', 'IAS', (SELECT products.product_id FROM products WHERE products.name IN ('IAS'))), ('nren_l3_core_service_type', 'IMPORTED IAS', (SELECT products.product_id FROM products WHERE products.name IN ('Imported IAS'))), ('nren_l3_core_service_type', 'GÉANT IP', (SELECT products.product_id FROM products WHERE products.name IN ('GÉANT IP'))), ('nren_l3_core_service_type', 'IMPORTED GÉANT IP', (SELECT products.product_id FROM products WHERE products.name IN ('Imported GÉANT IP')))
+    """))
+    conn.execute(sa.text("""
+INSERT INTO product_blocks (name, description, tag, status) VALUES ('EdgePortBlock', 'The product block with all attributes of an Edge Port', 'EP_BLOCK', 'active') RETURNING product_blocks.product_block_id
+    """))
+    conn.execute(sa.text("""
+INSERT INTO product_blocks (name, description, tag, status) VALUES ('EdgePortAEMemberBlock', 'A physical interface member of an Edge Port', 'EP_AE_BLOCK', 'active') RETURNING product_blocks.product_block_id
+    """))
+    conn.execute(sa.text("""
+INSERT INTO product_blocks (name, description, tag, status) VALUES ('NRENL3CoreServiceBlock', 'The product block with all attributes of an NREN L3 Core Service', 'NREN_L3_CORE_BLOCK', 'active') RETURNING product_blocks.product_block_id
+    """))
+    conn.execute(sa.text("""
+INSERT INTO product_blocks (name, description, tag, status) VALUES ('NRENAccessPort', 'An NREN Access Port', 'NREN_AP_BLOCK', 'active') RETURNING product_blocks.product_block_id
+    """))
+    conn.execute(sa.text("""
+INSERT INTO product_blocks (name, description, tag, status) VALUES ('ServiceBindingPort', 'A Service Binding Port', 'SBP_BLOCK', 'active') RETURNING product_blocks.product_block_id
+    """))
+    conn.execute(sa.text("""
+INSERT INTO product_blocks (name, description, tag, status) VALUES ('BGPSession', 'A BGP session', 'BGP_BLOCK', 'active') RETURNING product_blocks.product_block_id
+    """))
+    conn.execute(sa.text("""
+INSERT INTO resource_types (resource_type, description) VALUES ('send_default_route', 'This product sends a default route') RETURNING resource_types.resource_type_id
+    """))
+    conn.execute(sa.text("""
+INSERT INTO resource_types (resource_type, description) VALUES ('has_custom_policies', 'This has custom policies enabled') RETURNING resource_types.resource_type_id
+    """))
+    conn.execute(sa.text("""
+INSERT INTO resource_types (resource_type, description) VALUES ('is_passive', 'This product is passive') RETURNING resource_types.resource_type_id
+    """))
+    conn.execute(sa.text("""
+INSERT INTO resource_types (resource_type, description) VALUES ('bfd_enabled', 'This product has BFD enabled') RETURNING resource_types.resource_type_id
+    """))
+    conn.execute(sa.text("""
+INSERT INTO resource_types (resource_type, description) VALUES ('enable_lacp', 'This product has LACP enabled') RETURNING resource_types.resource_type_id
+    """))
+    conn.execute(sa.text("""
+INSERT INTO resource_types (resource_type, description) VALUES ('edge_port_type', 'Type of Edge Port') RETURNING resource_types.resource_type_id
+    """))
+    conn.execute(sa.text("""
+INSERT INTO resource_types (resource_type, description) VALUES ('geant_ga_id', 'GÉANT GA service ID') RETURNING resource_types.resource_type_id
+    """))
+    conn.execute(sa.text("""
+INSERT INTO resource_types (resource_type, description) VALUES ('bfd_multiplier', 'BFD multiplier') RETURNING resource_types.resource_type_id
+    """))
+    conn.execute(sa.text("""
+INSERT INTO resource_types (resource_type, description) VALUES ('ipv6_address', 'IPv6 Address') RETURNING resource_types.resource_type_id
+    """))
+    conn.execute(sa.text("""
+INSERT INTO resource_types (resource_type, description) VALUES ('edge_port_name', 'The Edge Port name') RETURNING resource_types.resource_type_id
+    """))
+    conn.execute(sa.text("""
+INSERT INTO resource_types (resource_type, description) VALUES ('encapsulation', 'Encapsulation method') RETURNING resource_types.resource_type_id
+    """))
+    conn.execute(sa.text("""
+INSERT INTO resource_types (resource_type, description) VALUES ('ignore_if_down', 'Ignore if this interface goes down') RETURNING resource_types.resource_type_id
+    """))
+    conn.execute(sa.text("""
+INSERT INTO resource_types (resource_type, description) VALUES ('bfd_interval', 'BFD interval') RETURNING resource_types.resource_type_id
+    """))
+    conn.execute(sa.text("""
+INSERT INTO resource_types (resource_type, description) VALUES ('rtbh_enabled', 'This product has RTBH enabled') RETURNING resource_types.resource_type_id
+    """))
+    conn.execute(sa.text("""
+INSERT INTO resource_types (resource_type, description) VALUES ('custom_firewall_filters', 'This product has custom firewall filters enabled') RETURNING resource_types.resource_type_id
+    """))
+    conn.execute(sa.text("""
+INSERT INTO resource_types (resource_type, description) VALUES ('ap_type', 'Access Port type') RETURNING resource_types.resource_type_id
+    """))
+    conn.execute(sa.text("""
+INSERT INTO resource_types (resource_type, description) VALUES ('is_multi_hop', 'This product is multi-hop') RETURNING resource_types.resource_type_id
+    """))
+    conn.execute(sa.text("""
+INSERT INTO resource_types (resource_type, description) VALUES ('edge_port_description', 'Description of an Edge Port') RETURNING resource_types.resource_type_id
+    """))
+    conn.execute(sa.text("""
+INSERT INTO resource_types (resource_type, description) VALUES ('is_tagged', 'This product is tagged') RETURNING resource_types.resource_type_id
+    """))
+    conn.execute(sa.text("""
+INSERT INTO resource_types (resource_type, description) VALUES ('mac_address', 'A MAC address') RETURNING resource_types.resource_type_id
+    """))
+    conn.execute(sa.text("""
+INSERT INTO resource_types (resource_type, description) VALUES ('geant_sid', 'GEANT SID') RETURNING resource_types.resource_type_id
+    """))
+    conn.execute(sa.text("""
+INSERT INTO resource_types (resource_type, description) VALUES ('ipv4_address', 'IPV4 Address') RETURNING resource_types.resource_type_id
+    """))
+    conn.execute(sa.text("""
+INSERT INTO resource_types (resource_type, description) VALUES ('peer_address', 'Peer address') RETURNING resource_types.resource_type_id
+    """))
+    conn.execute(sa.text("""
+INSERT INTO resource_types (resource_type, description) VALUES ('sbp_type', 'Type of Service Binding Port') RETURNING resource_types.resource_type_id
+    """))
+    conn.execute(sa.text("""
+INSERT INTO resource_types (resource_type, description) VALUES ('ipv6_mask', 'IPV6 subnet mask') RETURNING resource_types.resource_type_id
+    """))
+    conn.execute(sa.text("""
+INSERT INTO resource_types (resource_type, description) VALUES ('authentication_key', 'Authentication key') RETURNING resource_types.resource_type_id
+    """))
+    conn.execute(sa.text("""
+INSERT INTO resource_types (resource_type, description) VALUES ('multipath_enabled', 'Does this have multipath enabled') RETURNING resource_types.resource_type_id
+    """))
+    conn.execute(sa.text("""
+INSERT INTO resource_types (resource_type, description) VALUES ('ipv4_mask', 'IPV4 netmask') RETURNING resource_types.resource_type_id
+    """))
+    conn.execute(sa.text("""
+INSERT INTO resource_types (resource_type, description) VALUES ('families', 'IP families, can be v4 v6 and UNICAST or MULTICAST') RETURNING resource_types.resource_type_id
+    """))
+    conn.execute(sa.text("""
+INSERT INTO resource_types (resource_type, description) VALUES ('member_speed', 'The member speed') 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 ('Edge Port')), (SELECT product_blocks.product_block_id FROM product_blocks WHERE product_blocks.name IN ('EdgePortBlock'))), ((SELECT products.product_id FROM products WHERE products.name IN ('Imported Edge Port')), (SELECT product_blocks.product_block_id FROM product_blocks WHERE product_blocks.name IN ('EdgePortBlock')))
+    """))
+    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 ('IAS')), (SELECT product_blocks.product_block_id FROM product_blocks WHERE product_blocks.name IN ('NRENL3CoreServiceBlock'))), ((SELECT products.product_id FROM products WHERE products.name IN ('Imported IAS')), (SELECT product_blocks.product_block_id FROM product_blocks WHERE product_blocks.name IN ('NRENL3CoreServiceBlock'))), ((SELECT products.product_id FROM products WHERE products.name IN ('GÉANT IP')), (SELECT product_blocks.product_block_id FROM product_blocks WHERE product_blocks.name IN ('NRENL3CoreServiceBlock'))), ((SELECT products.product_id FROM products WHERE products.name IN ('Imported GÉANT IP')), (SELECT product_blocks.product_block_id FROM product_blocks WHERE product_blocks.name IN ('NRENL3CoreServiceBlock')))
+    """))
+    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 ('EdgePortBlock')), (SELECT product_blocks.product_block_id FROM product_blocks WHERE product_blocks.name IN ('RouterBlock')))
+    """))
+    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 ('EdgePortBlock')), (SELECT product_blocks.product_block_id FROM product_blocks WHERE product_blocks.name IN ('EdgePortAEMemberBlock')))
+    """))
+    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 ('NRENL3CoreServiceBlock')), (SELECT product_blocks.product_block_id FROM product_blocks WHERE product_blocks.name IN ('NRENAccessPort')))
+    """))
+    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 ('NRENAccessPort')), (SELECT product_blocks.product_block_id FROM product_blocks WHERE product_blocks.name IN ('ServiceBindingPort')))
+    """))
+    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 ('ServiceBindingPort')), (SELECT product_blocks.product_block_id FROM product_blocks WHERE product_blocks.name IN ('BGPSession')))
+    """))
+    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 ('ServiceBindingPort')), (SELECT product_blocks.product_block_id FROM product_blocks WHERE product_blocks.name IN ('EdgePortBlock')))
+    """))
+    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 ('EdgePortBlock')), (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('edge_port_name')))
+    """))
+    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 ('EdgePortBlock')), (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('edge_port_description')))
+    """))
+    conn.execute(sa.text("""
+INSERT INTO product_block_resource_types (product_block_id, resource_type_id) VALUES ((SELECT product_blocks.product_block_id FROM product_blocks WHERE product_blocks.name IN ('EdgePortBlock')), (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('enable_lacp')))
+    """))
+    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 ('EdgePortBlock')), (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('encapsulation')))
+    """))
+    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 ('EdgePortBlock')), (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('mac_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 ('EdgePortBlock')), (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('member_speed')))
+    """))
+    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 ('EdgePortBlock')), (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('minimum_links')))
+    """))
+    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 ('EdgePortBlock')), (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('edge_port_type')))
+    """))
+    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 ('EdgePortBlock')), (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('ignore_if_down')))
+    """))
+    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 ('EdgePortBlock')), (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('geant_ga_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 ('EdgePortAEMemberBlock')), (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('interface_name')))
+    """))
+    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 ('EdgePortAEMemberBlock')), (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('interface_description')))
+    """))
+    conn.execute(sa.text("""
+INSERT INTO product_block_resource_types (product_block_id, resource_type_id) VALUES ((SELECT product_blocks.product_block_id FROM product_blocks WHERE product_blocks.name IN ('NRENAccessPort')), (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('ap_type')))
+    """))
+    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 ('ServiceBindingPort')), (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 ('ServiceBindingPort')), (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 ('ServiceBindingPort')), (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('sbp_type')))
+    """))
+    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 ('ServiceBindingPort')), (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 ('ServiceBindingPort')), (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 ('ServiceBindingPort')), (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 ('ServiceBindingPort')), (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 ('ServiceBindingPort')), (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 ('ServiceBindingPort')), (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('geant_sid')))
+    """))
+    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 ('BGPSession')), (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('peer_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 ('BGPSession')), (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('bfd_enabled')))
+    """))
+    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 ('BGPSession')), (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('bfd_interval')))
+    """))
+    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 ('BGPSession')), (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('bfd_multiplier')))
+    """))
+    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 ('BGPSession')), (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('families')))
+    """))
+    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 ('BGPSession')), (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('has_custom_policies')))
+    """))
+    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 ('BGPSession')), (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('authentication_key')))
+    """))
+    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 ('BGPSession')), (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('multipath_enabled')))
+    """))
+    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 ('BGPSession')), (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('send_default_route')))
+    """))
+    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 ('BGPSession')), (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('is_multi_hop')))
+    """))
+    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 ('BGPSession')), (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('is_passive')))
+    """))
+    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 ('BGPSession')), (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('rtbh_enabled')))
+    """))
+
+
+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 ('EdgePortBlock')) AND product_block_resource_types.resource_type_id = (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('edge_port_name'))
+    """))
+    conn.execute(sa.text("""
+DELETE FROM subscription_instance_values USING product_block_resource_types WHERE subscription_instance_values.subscription_instance_id IN (SELECT subscription_instances.subscription_instance_id FROM subscription_instances WHERE subscription_instances.subscription_instance_id IN (SELECT product_blocks.product_block_id FROM product_blocks WHERE product_blocks.name IN ('EdgePortBlock'))) AND product_block_resource_types.resource_type_id = (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('edge_port_name'))
+    """))
+    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 ('EdgePortBlock')) AND product_block_resource_types.resource_type_id = (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('edge_port_description'))
+    """))
+    conn.execute(sa.text("""
+DELETE FROM subscription_instance_values USING product_block_resource_types WHERE subscription_instance_values.subscription_instance_id IN (SELECT subscription_instances.subscription_instance_id FROM subscription_instances WHERE subscription_instances.subscription_instance_id IN (SELECT product_blocks.product_block_id FROM product_blocks WHERE product_blocks.name IN ('EdgePortBlock'))) AND product_block_resource_types.resource_type_id = (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('edge_port_description'))
+    """))
+    conn.execute(sa.text("""
+DELETE FROM product_block_resource_types WHERE product_block_resource_types.product_block_id IN (SELECT product_blocks.product_block_id FROM product_blocks WHERE product_blocks.name IN ('EdgePortBlock')) AND product_block_resource_types.resource_type_id = (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('enable_lacp'))
+    """))
+    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 ('EdgePortBlock'))) AND product_block_resource_types.resource_type_id = (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('enable_lacp'))
+    """))
+    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 ('EdgePortBlock')) AND product_block_resource_types.resource_type_id = (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('encapsulation'))
+    """))
+    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 ('EdgePortBlock'))) AND product_block_resource_types.resource_type_id = (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('encapsulation'))
+    """))
+    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 ('EdgePortBlock')) AND product_block_resource_types.resource_type_id = (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('mac_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 ('EdgePortBlock'))) AND product_block_resource_types.resource_type_id = (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('mac_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 ('EdgePortBlock')) AND product_block_resource_types.resource_type_id = (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('member_speed'))
+    """))
+    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 ('EdgePortBlock'))) AND product_block_resource_types.resource_type_id = (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('member_speed'))
+    """))
+    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 ('EdgePortBlock')) AND product_block_resource_types.resource_type_id = (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('minimum_links'))
+    """))
+    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 ('EdgePortBlock'))) AND product_block_resource_types.resource_type_id = (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('minimum_links'))
+    """))
+    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 ('EdgePortBlock')) AND product_block_resource_types.resource_type_id = (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('edge_port_type'))
+    """))
+    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 ('EdgePortBlock'))) AND product_block_resource_types.resource_type_id = (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('edge_port_type'))
+    """))
+    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 ('EdgePortBlock')) AND product_block_resource_types.resource_type_id = (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('ignore_if_down'))
+    """))
+    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 ('EdgePortBlock'))) AND product_block_resource_types.resource_type_id = (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('ignore_if_down'))
+    """))
+    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 ('EdgePortBlock')) AND product_block_resource_types.resource_type_id = (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('geant_ga_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 ('EdgePortBlock'))) AND product_block_resource_types.resource_type_id = (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('geant_ga_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 ('EdgePortAEMemberBlock')) AND product_block_resource_types.resource_type_id = (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('interface_name'))
+    """))
+    conn.execute(sa.text("""
+DELETE FROM subscription_instance_values USING product_block_resource_types WHERE subscription_instance_values.subscription_instance_id IN (SELECT subscription_instances.subscription_instance_id FROM subscription_instances WHERE subscription_instances.subscription_instance_id IN (SELECT product_blocks.product_block_id FROM product_blocks WHERE product_blocks.name IN ('EdgePortAEMemberBlock'))) AND product_block_resource_types.resource_type_id = (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('interface_name'))
+    """))
+    conn.execute(sa.text("""
+DELETE FROM 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 ('EdgePortAEMemberBlock')) AND product_block_resource_types.resource_type_id = (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('interface_description'))
+    """))
+    conn.execute(sa.text("""
+DELETE FROM subscription_instance_values USING product_block_resource_types WHERE subscription_instance_values.subscription_instance_id IN (SELECT subscription_instances.subscription_instance_id FROM subscription_instances WHERE subscription_instances.subscription_instance_id IN (SELECT product_blocks.product_block_id FROM product_blocks WHERE product_blocks.name IN ('EdgePortAEMemberBlock'))) AND product_block_resource_types.resource_type_id = (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('interface_description'))
+    """))
+    conn.execute(sa.text("""
+DELETE FROM product_block_resource_types WHERE product_block_resource_types.product_block_id IN (SELECT product_blocks.product_block_id FROM product_blocks WHERE product_blocks.name IN ('NRENAccessPort')) AND product_block_resource_types.resource_type_id = (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('ap_type'))
+    """))
+    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 ('NRENAccessPort'))) AND product_block_resource_types.resource_type_id = (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('ap_type'))
+    """))
+    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 ('ServiceBindingPort')) 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 ('ServiceBindingPort'))) 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 ('ServiceBindingPort')) 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 ('ServiceBindingPort'))) 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 ('ServiceBindingPort')) AND product_block_resource_types.resource_type_id = (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('sbp_type'))
+    """))
+    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 ('ServiceBindingPort'))) AND product_block_resource_types.resource_type_id = (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('sbp_type'))
+    """))
+    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 ('ServiceBindingPort')) 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 ('ServiceBindingPort'))) 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 ('ServiceBindingPort')) 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 ('ServiceBindingPort'))) 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 ('ServiceBindingPort')) 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 ('ServiceBindingPort'))) 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 ('ServiceBindingPort')) 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 ('ServiceBindingPort'))) 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 ('ServiceBindingPort')) 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 ('ServiceBindingPort'))) 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 ('ServiceBindingPort')) AND product_block_resource_types.resource_type_id = (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('geant_sid'))
+    """))
+    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 ('ServiceBindingPort'))) AND product_block_resource_types.resource_type_id = (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('geant_sid'))
+    """))
+    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 ('BGPSession')) AND product_block_resource_types.resource_type_id = (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('peer_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 ('BGPSession'))) AND product_block_resource_types.resource_type_id = (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('peer_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 ('BGPSession')) AND product_block_resource_types.resource_type_id = (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('bfd_enabled'))
+    """))
+    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 ('BGPSession'))) AND product_block_resource_types.resource_type_id = (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('bfd_enabled'))
+    """))
+    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 ('BGPSession')) AND product_block_resource_types.resource_type_id = (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('bfd_interval'))
+    """))
+    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 ('BGPSession'))) AND product_block_resource_types.resource_type_id = (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('bfd_interval'))
+    """))
+    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 ('BGPSession')) AND product_block_resource_types.resource_type_id = (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('bfd_multiplier'))
+    """))
+    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 ('BGPSession'))) AND product_block_resource_types.resource_type_id = (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('bfd_multiplier'))
+    """))
+    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 ('BGPSession')) AND product_block_resource_types.resource_type_id = (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('families'))
+    """))
+    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 ('BGPSession'))) AND product_block_resource_types.resource_type_id = (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('families'))
+    """))
+    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 ('BGPSession')) AND product_block_resource_types.resource_type_id = (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('has_custom_policies'))
+    """))
+    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 ('BGPSession'))) AND product_block_resource_types.resource_type_id = (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('has_custom_policies'))
+    """))
+    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 ('BGPSession')) AND product_block_resource_types.resource_type_id = (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('authentication_key'))
+    """))
+    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 ('BGPSession'))) AND product_block_resource_types.resource_type_id = (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('authentication_key'))
+    """))
+    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 ('BGPSession')) AND product_block_resource_types.resource_type_id = (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('multipath_enabled'))
+    """))
+    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 ('BGPSession'))) AND product_block_resource_types.resource_type_id = (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('multipath_enabled'))
+    """))
+    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 ('BGPSession')) AND product_block_resource_types.resource_type_id = (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('send_default_route'))
+    """))
+    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 ('BGPSession'))) AND product_block_resource_types.resource_type_id = (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('send_default_route'))
+    """))
+    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 ('BGPSession')) AND product_block_resource_types.resource_type_id = (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('is_multi_hop'))
+    """))
+    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 ('BGPSession'))) AND product_block_resource_types.resource_type_id = (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('is_multi_hop'))
+    """))
+    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 ('BGPSession')) AND product_block_resource_types.resource_type_id = (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('is_passive'))
+    """))
+    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 ('BGPSession'))) AND product_block_resource_types.resource_type_id = (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('is_passive'))
+    """))
+    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 ('BGPSession')) AND product_block_resource_types.resource_type_id = (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('rtbh_enabled'))
+    """))
+    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 ('BGPSession'))) AND product_block_resource_types.resource_type_id = (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('rtbh_enabled'))
+    """))
+    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 ('send_default_route', 'has_custom_policies', 'is_passive', 'bfd_enabled', 'enable_lacp', 'edge_port_type', 'geant_ga_id', 'bfd_multiplier', 'ipv6_address', 'edge_port_name', 'encapsulation', 'ignore_if_down', 'bfd_interval', 'rtbh_enabled', 'custom_firewall_filters', 'ap_type', 'is_multi_hop', 'edge_port_description', 'is_tagged', 'mac_address', 'geant_sid', 'ipv4_address', 'peer_address', 'sbp_type', 'ipv6_mask', 'authentication_key', 'multipath_enabled', 'ipv4_mask', 'families', 'member_speed'))
+    """))
+    conn.execute(sa.text("""
+DELETE FROM resource_types WHERE resource_types.resource_type IN ('send_default_route', 'has_custom_policies', 'is_passive', 'bfd_enabled', 'enable_lacp', 'edge_port_type', 'geant_ga_id', 'bfd_multiplier', 'ipv6_address', 'edge_port_name', 'encapsulation', 'ignore_if_down', 'bfd_interval', 'rtbh_enabled', 'custom_firewall_filters', 'ap_type', 'is_multi_hop', 'edge_port_description', 'is_tagged', 'mac_address', 'geant_sid', 'ipv4_address', 'peer_address', 'sbp_type', 'ipv6_mask', 'authentication_key', 'multipath_enabled', 'ipv4_mask', 'families', 'member_speed')
+    """))
+    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 ('Edge Port', 'Imported Edge Port')) AND product_product_blocks.product_block_id IN (SELECT product_blocks.product_block_id FROM product_blocks WHERE product_blocks.name IN ('EdgePortBlock'))
+    """))
+    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 ('IAS', 'Imported IAS', 'GÉANT IP', 'Imported GÉANT IP')) AND product_product_blocks.product_block_id IN (SELECT product_blocks.product_block_id FROM product_blocks WHERE product_blocks.name IN ('NRENL3CoreServiceBlock'))
+    """))
+    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 ('EdgePortBlock')) AND product_block_relations.depends_on_id IN (SELECT product_blocks.product_block_id FROM product_blocks WHERE product_blocks.name IN ('RouterBlock'))
+    """))
+    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 ('EdgePortBlock')) AND product_block_relations.depends_on_id IN (SELECT product_blocks.product_block_id FROM product_blocks WHERE product_blocks.name IN ('EdgePortAEMemberBlock'))
+    """))
+    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 ('NRENL3CoreServiceBlock')) AND product_block_relations.depends_on_id IN (SELECT product_blocks.product_block_id FROM product_blocks WHERE product_blocks.name IN ('NRENAccessPort'))
+    """))
+    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 ('NRENAccessPort')) AND product_block_relations.depends_on_id IN (SELECT product_blocks.product_block_id FROM product_blocks WHERE product_blocks.name IN ('ServiceBindingPort'))
+    """))
+    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 ('ServiceBindingPort')) 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 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 ('ServiceBindingPort')) 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 fixed_inputs WHERE fixed_inputs.product_id IN (SELECT products.product_id FROM products WHERE products.name IN ('IAS', 'Imported IAS', 'GÉANT IP', 'Imported GÉANT IP')) AND fixed_inputs.name = 'nren_l3_core_service_type'
+    """))
+    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 ('EdgePortAEMemberBlock', 'EdgePortBlock', 'NRENL3CoreServiceBlock', 'ServiceBindingPort', 'BGPSession', 'NRENAccessPort'))
+    """))
+    conn.execute(sa.text("""
+DELETE FROM product_blocks WHERE product_blocks.name IN ('EdgePortAEMemberBlock', 'EdgePortBlock', 'NRENL3CoreServiceBlock', 'ServiceBindingPort', 'BGPSession', 'NRENAccessPort')
+    """))
+    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 ('IAS', 'GÉANT IP', 'Imported GÉANT IP', 'Edge Port', 'Imported IAS', 'Imported Edge 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 ('IAS', 'GÉANT IP', 'Imported GÉANT IP', 'Edge Port', 'Imported IAS', 'Imported Edge 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 ('IAS', 'GÉANT IP', 'Imported GÉANT IP', 'Edge Port', 'Imported IAS', 'Imported Edge Port')))
+    """))
+    conn.execute(sa.text("""
+DELETE FROM subscriptions WHERE subscriptions.product_id IN (SELECT products.product_id FROM products WHERE products.name IN ('IAS', 'GÉANT IP', 'Imported GÉANT IP', 'Edge Port', 'Imported IAS', 'Imported Edge Port'))
+    """))
+    conn.execute(sa.text("""
+DELETE FROM products WHERE products.name IN ('IAS', 'GÉANT IP', 'Imported GÉANT IP', 'Edge Port', 'Imported IAS', 'Imported Edge Port')
+    """))
diff --git a/gso/products/__init__.py b/gso/products/__init__.py
index 9278fbe752d1fc4614f89c3f60c72ef3021908b7..ad3c2d2a7be2d9c863541d38d950100b0acb59a8 100644
--- a/gso/products/__init__.py
+++ b/gso/products/__init__.py
@@ -8,8 +8,10 @@
 from orchestrator.domain import SUBSCRIPTION_MODEL_REGISTRY
 from pydantic_forms.types import strEnum
 
+from gso.products.product_types.edge_port import EdgePort, ImportedEdgePort
 from gso.products.product_types.iptrunk import ImportedIptrunk, Iptrunk
 from gso.products.product_types.lan_switch_interconnect import LanSwitchInterconnect
+from gso.products.product_types.nren_l3_core_service import ImportedNRENL3CoreService, NRENL3CoreService
 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
@@ -37,6 +39,12 @@ class ProductName(strEnum):
     IMPORTED_OFFICE_ROUTER = "Imported office router"
     OPENGEAR = "Opengear"
     IMPORTED_OPENGEAR = "Imported Opengear"
+    EDGE_PORT = "Edge Port"
+    IMPORTED_EDGE_PORT = "Imported Edge Port"
+    GEANT_IP = "GÉANT IP"
+    IMPORTED_GEANT_IP = "Imported GÉANT IP"
+    IAS = "IAS"
+    IMPORTED_IAS = "Imported IAS"
 
 
 class ProductType(strEnum):
@@ -57,6 +65,12 @@ class ProductType(strEnum):
     IMPORTED_OFFICE_ROUTER = ImportedOfficeRouter.__name__
     OPENGEAR = Opengear.__name__
     IMPORTED_OPENGEAR = Opengear.__name__
+    EDGE_PORT = EdgePort.__name__
+    IMPORTED_EDGE_PORT = ImportedEdgePort.__name__
+    GEANT_IP = NRENL3CoreService.__name__
+    IMPORTED_GEANT_IP = ImportedNRENL3CoreService.__name__
+    IAS = NRENL3CoreService.__name__
+    IMPORTED_IAS = ImportedNRENL3CoreService.__name__
 
 
 SUBSCRIPTION_MODEL_REGISTRY.update(
@@ -76,5 +90,11 @@ SUBSCRIPTION_MODEL_REGISTRY.update(
         ProductName.IMPORTED_OFFICE_ROUTER.value: ImportedOfficeRouter,
         ProductName.OPENGEAR.value: Opengear,
         ProductName.IMPORTED_OPENGEAR.value: ImportedOpengear,
+        ProductName.EDGE_PORT.value: EdgePort,
+        ProductName.IMPORTED_EDGE_PORT.value: ImportedEdgePort,
+        ProductName.GEANT_IP.value: NRENL3CoreService,
+        ProductName.IMPORTED_GEANT_IP.value: ImportedNRENL3CoreService,
+        ProductName.IAS.value: NRENL3CoreService,
+        ProductName.IMPORTED_IAS.value: ImportedNRENL3CoreService,
     },
 )
diff --git a/gso/products/product_blocks/bgp_session.py b/gso/products/product_blocks/bgp_session.py
new file mode 100644
index 0000000000000000000000000000000000000000..65c5f0a0908e6c6750dac9abae25d23290d1367c
--- /dev/null
+++ b/gso/products/product_blocks/bgp_session.py
@@ -0,0 +1,82 @@
+""":term:`BGP` session product block."""
+
+import strawberry
+from orchestrator.domain.base import ProductBlockModel
+from orchestrator.types import SubscriptionLifecycle
+from pydantic import Field
+from pydantic_forms.types import strEnum
+
+from gso.utils.types.ip_address import IPAddress
+
+
+@strawberry.enum
+class IPFamily(strEnum):
+    """Possible IP families of a :term:`BGP` peering."""
+
+    V4UNICAST = "v4unicast"
+    V6UNICAST = "v6unicast"
+    V4MULTICAST = "v4multicast"
+    V6MULTICAST = "v6multicast"
+
+
+class BGPSessionInactive(ProductBlockModel, lifecycle=[SubscriptionLifecycle.INITIAL], product_block_name="BGPSession"):
+    """A :term:`BGP` session that is currently inactive. See :class:`BGPSession`."""
+
+    peer_address: IPAddress | None = None
+    bfd_enabled: bool | None = None
+    bfd_interval: int | None = None
+    bfd_multiplier: int | None = None
+    families: list[IPFamily] = Field(default_factory=list)
+    has_custom_policies: bool | None = None
+    authentication_key: str | None = None
+    multipath_enabled: bool | None = None
+    send_default_route: bool | None = None
+    is_multi_hop: bool = False
+    is_passive: bool = False
+    rtbh_enabled: bool = False
+
+
+class BGPSessionProvisioning(BGPSessionInactive, lifecycle=[SubscriptionLifecycle.PROVISIONING]):
+    """A :term:`BGP` session that is currently being provisioned. See :class:`BGPSession`."""
+
+    peer_address: IPAddress
+    bfd_enabled: bool
+    bfd_interval: int | None = None
+    bfd_multiplier: int | None = None
+    families: list[IPFamily]
+    has_custom_policies: bool
+    authentication_key: str
+    multipath_enabled: bool
+    send_default_route: bool
+    is_multi_hop: bool
+    is_passive: bool
+    rtbh_enabled: bool
+
+
+class BGPSession(BGPSessionProvisioning, lifecycle=[SubscriptionLifecycle.ACTIVE]):
+    """A :term:`BGP` session that is currently deployed in the network."""
+
+    #: The peering address of the session.
+    peer_address: IPAddress
+    #: Whether :term:`BFD` is enabled.
+    bfd_enabled: bool
+    #: The :term:`BFD` interval, if enabled.
+    bfd_interval: int | None = None
+    #: The :term:`BFD` multiplier, if enabled.
+    bfd_multiplier: int | None = None
+    #: The list of IP families enabled for this session.
+    families: list[IPFamily]
+    #: Whether any custom policies exist for this session.
+    has_custom_policies: bool
+    #: The authentication key of the :term:`BGP` session.
+    authentication_key: str
+    #: Whether multi-path is enabled.
+    multipath_enabled: bool
+    #: Whether we send a last resort route.
+    send_default_route: bool
+    #: Whether this session is multi-hop or not. Defaults to no.
+    is_multi_hop: bool
+    #: Whether this is a passive session.
+    is_passive: bool
+    #: Whether Remote Triggered Blackhole is enabled
+    rtbh_enabled: bool
diff --git a/gso/products/product_blocks/edge_port.py b/gso/products/product_blocks/edge_port.py
new file mode 100644
index 0000000000000000000000000000000000000000..3ec09b2a9efbd11616f46bfb246f7611ffedb948
--- /dev/null
+++ b/gso/products/product_blocks/edge_port.py
@@ -0,0 +1,122 @@
+"""Edge port product block.
+
+Edge port sets the boundary between Geant network and an external entity that could also be a different technological
+domain still managed by GEANT. In other words, an Edge port determines where the network ends.
+"""
+
+from orchestrator.domain.base import ProductBlockModel
+from orchestrator.types import SubscriptionLifecycle, strEnum
+
+from gso.products.product_blocks.router import RouterBlock, RouterBlockInactive, RouterBlockProvisioning
+from gso.utils.types.interfaces import LAGMemberList, PhysicalPortCapacity
+
+
+class EncapsulationType(strEnum):
+    """Types of encapsulation for edge ports.
+
+    Null supports a single service on the port.
+    Dot1Q supports multiple services for one customer or services for multiple customers.
+    QinQ expands VLAN space by double-tagging frames.
+    """
+
+    DOT1Q = "dot1q"
+    QINQ = "qinq"
+    NULL = "null"
+
+
+class EdgePortType(strEnum):
+    """Types of edge ports."""
+
+    CUSTOMER = "CUSTOMER"
+    INFRASTRUCTURE = "INFRASTRUCTURE"
+    PRIVATE = "PRIVATE"
+    PUBLIC = "PUBLIC"
+    RE_INTERCONNECT = "RE_INTERCONNECT"
+
+
+class EdgePortAEMemberBlockInactive(
+    ProductBlockModel, lifecycle=[SubscriptionLifecycle.INITIAL], product_block_name="EdgePortAEMemberBlock"
+):
+    """An inactive Edge Port AE interface."""
+
+    interface_name: str | None = None
+    interface_description: str | None = None
+
+
+class EdgePortAEMemberBlockProvisioning(EdgePortAEMemberBlockInactive, lifecycle=[SubscriptionLifecycle.PROVISIONING]):
+    """A provisional Edge Port AE interface."""
+
+    interface_name: str
+    interface_description: str | None = None
+
+
+class EdgePortAEMemberBlock(EdgePortAEMemberBlockProvisioning, lifecycle=[SubscriptionLifecycle.ACTIVE]):
+    """An Edge Port AE interface."""
+
+    interface_name: str
+    interface_description: str | None = None
+
+
+class EdgePortBlockInactive(
+    ProductBlockModel, lifecycle=[SubscriptionLifecycle.INITIAL], product_block_name="EdgePortBlock"
+):
+    """An edge port that's currently inactive. See :class:`EdgePortBlock`."""
+
+    node: RouterBlockInactive | None = None
+    edge_port_name: str | None = None
+    edge_port_description: str | None = None
+    enable_lacp: bool | None = None
+    encapsulation: EncapsulationType = EncapsulationType.DOT1Q
+    mac_address: str | None = None
+    member_speed: PhysicalPortCapacity | None = None
+    minimum_links: int | None = None
+    edge_port_type: EdgePortType | None = None
+    ignore_if_down: bool = False
+    geant_ga_id: str | None = None
+    edge_port_ae_members: LAGMemberList[EdgePortAEMemberBlockInactive]
+
+
+class EdgePortBlockProvisioning(EdgePortBlockInactive, lifecycle=[SubscriptionLifecycle.PROVISIONING]):
+    """An edge port that's being provisioned. See :class:`EdgePortBlock`."""
+
+    node: RouterBlockProvisioning
+    edge_port_name: str
+    edge_port_description: str | None = None
+    enable_lacp: bool
+    encapsulation: EncapsulationType = EncapsulationType.DOT1Q
+    mac_address: str | None = None
+    member_speed: PhysicalPortCapacity
+    minimum_links: int | None = None
+    edge_port_type: EdgePortType
+    ignore_if_down: bool = False
+    geant_ga_id: str | None = None
+    edge_port_ae_members: LAGMemberList[EdgePortAEMemberBlockProvisioning]  # type: ignore[assignment]
+
+
+class EdgePortBlock(EdgePortBlockProvisioning, lifecycle=[SubscriptionLifecycle.ACTIVE]):
+    """An edge port that's currently deployed in the network."""
+
+    #: The router that this edge port is connected to.
+    node: RouterBlock
+    #: The name of the edge port, in our case, corresponds to the name of the :term:`LAG` interface.
+    edge_port_name: str
+    #: A description of the edge port.
+    edge_port_description: str | None = None
+    #: Indicates whether :term:`LACP` is enabled for this edge port.
+    enable_lacp: bool
+    #: The type of encapsulation used on this edge port, by default DOT1Q.
+    encapsulation: EncapsulationType = EncapsulationType.DOT1Q
+    #: The MAC address assigned to this edge port, if applicable.
+    mac_address: str | None = None
+    #: The speed capacity of each member in the physical port.
+    member_speed: PhysicalPortCapacity
+    #: The minimum number of links required for this edge port.
+    minimum_links: int | None = None
+    #: The type of edge port (e.g., customer, private, public).
+    edge_port_type: EdgePortType
+    #: If set to True, the edge port will be ignored if it is down.
+    ignore_if_down: bool = False
+    #: The GEANT GA ID associated with this edge port, if any.
+    geant_ga_id: str | None = None
+    #: A list of :term:`LAG` members associated with this edge port.
+    edge_port_ae_members: LAGMemberList[EdgePortAEMemberBlock]  # type: ignore[assignment]
diff --git a/gso/products/product_blocks/nren_l3_core_service.py b/gso/products/product_blocks/nren_l3_core_service.py
new file mode 100644
index 0000000000000000000000000000000000000000..37eb7616511dc34d887db0ad324dee8b59ec4f96
--- /dev/null
+++ b/gso/products/product_blocks/nren_l3_core_service.py
@@ -0,0 +1,60 @@
+"""Product blocks for :class:`NREN` Layer 3 Core Service products."""
+
+from orchestrator.domain.base import ProductBlockModel
+from orchestrator.types import SubscriptionLifecycle
+from pydantic import Field
+
+from gso.products.product_blocks.service_binding_port import (
+    ServiceBindingPort,
+    ServiceBindingPortInactive,
+    ServiceBindingPortProvisioning,
+)
+from gso.utils.shared_enums import APType
+
+
+class NRENAccessPortInactive(
+    ProductBlockModel, lifecycle=[SubscriptionLifecycle.INITIAL], product_block_name="NRENAccessPort"
+):
+    """An access port for an R&E :term:`NREN` service that is inactive."""
+
+    ap_type: APType | None = None
+    sbp: ServiceBindingPortInactive
+
+
+class NRENAccessPortProvisioning(NRENAccessPortInactive, lifecycle=[SubscriptionLifecycle.PROVISIONING]):
+    """An access port for an R&E :term:`NREN` service that is being provisioned."""
+
+    ap_type: APType
+    sbp: ServiceBindingPortProvisioning
+
+
+class NRENAccessPort(NRENAccessPortProvisioning, lifecycle=[SubscriptionLifecycle.ACTIVE]):
+    """An access port for an R&E :term:`NREN` service."""
+
+    #: The type of Access Port
+    ap_type: APType
+    #: The corresponding :term:`SBP` of this Access Port.
+    sbp: ServiceBindingPort
+
+
+class NRENL3CoreServiceBlockInactive(
+    ProductBlockModel, lifecycle=[SubscriptionLifecycle.INITIAL], product_block_name="NRENL3CoreServiceBlock"
+):
+    """An inactive :term:`NREN` L3 Core service subscription. See :class:`NRENL3CoreServiceBlock`."""
+
+    nren_ap_list: list[NRENAccessPortInactive] = Field(default_factory=list)
+
+
+class NRENL3CoreServiceBlockProvisioning(
+    NRENL3CoreServiceBlockInactive, lifecycle=[SubscriptionLifecycle.PROVISIONING]
+):
+    """A provisioning :term:`NREN` L3 Core Service subscription. See :class:`NRENL3CoreServiceBlock`."""
+
+    nren_ap_list: list[NRENAccessPortProvisioning]  # type: ignore[assignment]
+
+
+class NRENL3CoreServiceBlock(NRENL3CoreServiceBlockProvisioning, lifecycle=[SubscriptionLifecycle.ACTIVE]):
+    """An active :term:`NREN` L3 Core Service subscription block."""
+
+    #: The list of Access Points where this service is present.
+    nren_ap_list: list[NRENAccessPort]  # type: ignore[assignment]
diff --git a/gso/products/product_blocks/opengear.py b/gso/products/product_blocks/opengear.py
index 84ed84b3b11d122f757b3e764202ce5052ca615f..14a4db847deaf6f65bd0d849f51ddabd8debf53d 100644
--- a/gso/products/product_blocks/opengear.py
+++ b/gso/products/product_blocks/opengear.py
@@ -43,9 +43,9 @@ class OpengearBlock(OpengearBlockProvisioning, lifecycle=[SubscriptionLifecycle.
     opengear_hostname: str
     #: The site where the Opengear device is located.
     opengear_site: SiteBlock
-    #: The WAN address of the Opengear device.
+    #: The :term:`WAN` address of the Opengear device.
     opengear_wan_address: ipaddress.IPv4Address
-    #: The WAN netmask of the Opengear device.
+    #: The :term:`WAN` netmask of the Opengear device.
     opengear_wan_netmask: ipaddress.IPv4Address
-    #: The WAN gateway of the Opengear device.
+    #: The :term:`WAN` gateway of the Opengear device.
     opengear_wan_gateway: ipaddress.IPv4Address
diff --git a/gso/products/product_blocks/service_binding_port.py b/gso/products/product_blocks/service_binding_port.py
new file mode 100644
index 0000000000000000000000000000000000000000..540983821f64b86e5e3c11a9118ea8b867bff61d
--- /dev/null
+++ b/gso/products/product_blocks/service_binding_port.py
@@ -0,0 +1,78 @@
+"""Service Binding Port.
+
+A service binding port is used to logically attach an edge port to a customer service using a :term:`VLAN`.
+"""
+
+from typing import Annotated
+
+from orchestrator.domain.base import ProductBlockModel
+from orchestrator.types import SubscriptionLifecycle
+from pydantic import Field
+
+from gso.products.product_blocks.bgp_session import BGPSession, BGPSessionInactive, BGPSessionProvisioning
+from gso.products.product_blocks.edge_port import EdgePortBlock, EdgePortBlockInactive, EdgePortBlockProvisioning
+from gso.utils.shared_enums import SBPType
+from gso.utils.types.ip_address import IPv4AddressType, IPV4Netmask, IPv6AddressType, IPV6Netmask
+
+VLAN_ID = Annotated[int, Field(gt=0, lt=4096)]
+
+
+class ServiceBindingPortInactive(
+    ProductBlockModel, lifecycle=[SubscriptionLifecycle.INITIAL], product_block_name="ServiceBindingPort"
+):
+    """A Service Binding Port that's currently inactive. See :class:`ServiceBindingPort`."""
+
+    is_tagged: bool | None = None
+    vlan_id: VLAN_ID | None = None
+    sbp_type: SBPType | 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 | None = None
+    geant_sid: str | None = None
+    bgp_session_list: list[BGPSessionInactive] = Field(default_factory=list)
+    edge_port: EdgePortBlockInactive | None = None
+
+
+class ServiceBindingPortProvisioning(ServiceBindingPortInactive, lifecycle=[SubscriptionLifecycle.PROVISIONING]):
+    """A Service Binding Port that's currently being provisioned. See :class:`ServiceBindingPort`."""
+
+    is_tagged: bool
+    vlan_id: VLAN_ID | None = None
+    sbp_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
+    geant_sid: str
+    bgp_session_list: list[BGPSessionProvisioning]  # type: ignore[assignment]
+    edge_port: EdgePortBlockProvisioning
+
+
+class ServiceBindingPort(ServiceBindingPortProvisioning, lifecycle=[SubscriptionLifecycle.ACTIVE]):
+    """A service binding port that is actively used in the network."""
+
+    #: Whether this :term:`VLAN` is tagged or not.
+    is_tagged: bool
+    #: The :term:`VLAN` ID.
+    vlan_id: VLAN_ID | None = None
+    #: Is this service binding port layer 2 or 3?
+    sbp_type: SBPType
+    #: If layer 3, IPv4 resources.
+    ipv4_address: IPv4AddressType | None = None
+    #: IPV4 subnet mask.
+    ipv4_mask: IPV4Netmask | None = None
+    #: If layer 3, IPv6 resources.
+    ipv6_address: IPv6AddressType | None = None
+    #: IPV6 subnet mask.
+    ipv6_mask: IPV6Netmask | None = None
+    #: Any custom firewall filters that the partner may require.
+    custom_firewall_filters: bool
+    #: The GÉANT service ID of this binding port.
+    geant_sid: str
+    #: The :term:`BGP` sessions associated with this service binding port.
+    bgp_session_list: list[BGPSession]  # type: ignore[assignment]
+    #: The Edge Port on which this :term:`SBP` resides.
+    edge_port: EdgePortBlock
diff --git a/gso/products/product_types/edge_port.py b/gso/products/product_types/edge_port.py
new file mode 100644
index 0000000000000000000000000000000000000000..417b6047a8d787d852057b43d52692a38569bc32
--- /dev/null
+++ b/gso/products/product_types/edge_port.py
@@ -0,0 +1,42 @@
+"""Product types for Edge Port."""
+
+from orchestrator.domain.base import SubscriptionModel
+from orchestrator.types import SubscriptionLifecycle
+
+from gso.products.product_blocks.edge_port import (
+    EdgePortBlock,
+    EdgePortBlockInactive,
+    EdgePortBlockProvisioning,
+)
+
+
+class EdgePortInactive(SubscriptionModel, is_base=True):
+    """An Edge Port that is inactive."""
+
+    edge_port: EdgePortBlockInactive
+
+
+class EdgePortProvisioning(EdgePortInactive, lifecycle=[SubscriptionLifecycle.PROVISIONING]):
+    """An Edge Port that is being provisioned."""
+
+    edge_port: EdgePortBlockProvisioning
+
+
+class EdgePort(EdgePortProvisioning, lifecycle=[SubscriptionLifecycle.ACTIVE]):
+    """An Edge Port that is active."""
+
+    edge_port: EdgePortBlock
+
+
+class ImportedEdgePortInactive(SubscriptionModel, is_base=True):
+    """An imported, inactive Edge Port."""
+
+    edge_port: EdgePortBlockInactive
+
+
+class ImportedEdgePort(
+    ImportedEdgePortInactive, lifecycle=[SubscriptionLifecycle.PROVISIONING, SubscriptionLifecycle.ACTIVE]
+):
+    """An imported Edge Port that is currently active."""
+
+    edge_port: EdgePortBlock
diff --git a/gso/products/product_types/nren_l3_core_service.py b/gso/products/product_types/nren_l3_core_service.py
new file mode 100644
index 0000000000000000000000000000000000000000..20aa5ab2f766588f466a5100d1d7af69cbb696b0
--- /dev/null
+++ b/gso/products/product_types/nren_l3_core_service.py
@@ -0,0 +1,60 @@
+""":term:`NREN` L3 Core Service product type."""
+
+from orchestrator.domain import SubscriptionModel
+from orchestrator.types import SubscriptionLifecycle
+from pydantic_forms.types import strEnum
+
+from gso.products.product_blocks.nren_l3_core_service import (
+    NRENL3CoreServiceBlock,
+    NRENL3CoreServiceBlockInactive,
+    NRENL3CoreServiceBlockProvisioning,
+)
+
+
+class NRENL3CoreServiceType(strEnum):
+    """Available types of :term:`NREN` Layer 3 Core Services.
+
+    The core services offered include GÉANT IP for R&E access, and the Internet Access Service.
+    """
+
+    GEANT_IP = "GÉANT IP"
+    IMPORTED_GEANT_IP = "IMPORTED GÉANT IP"
+    IAS = "IAS"
+    IMPORTED_IAS = "IMPORTED IAS"
+
+
+class NRENL3CoreServiceInactive(SubscriptionModel, is_base=True):
+    """An inactive :term:`NREN` L3 Core Service subscription."""
+
+    nren_l3_core_service_type: NRENL3CoreServiceType
+    nren_l3_core_service: NRENL3CoreServiceBlockInactive
+
+
+class NRENL3CoreServiceProvisioning(NRENL3CoreServiceInactive, lifecycle=[SubscriptionLifecycle.PROVISIONING]):
+    """A :term:`NREN` L3 Core Service subscription that's being provisioned."""
+
+    nren_l3_core_service_type: NRENL3CoreServiceType
+    nren_l3_core_service: NRENL3CoreServiceBlockProvisioning
+
+
+class NRENL3CoreService(NRENL3CoreServiceProvisioning, lifecycle=[SubscriptionLifecycle.ACTIVE]):
+    """An active :term:`NREN` L3 Core Service subscription."""
+
+    nren_l3_core_service_type: NRENL3CoreServiceType
+    nren_l3_core_service: NRENL3CoreServiceBlock
+
+
+class ImportedNRENL3CoreServiceInactive(SubscriptionModel, is_base=True):
+    """An imported, inactive :term:`NREN` L3 Core Service subscription."""
+
+    nren_l3_core_service_type: NRENL3CoreServiceType
+    nren_l3_core_service: NRENL3CoreServiceBlockInactive
+
+
+class ImportedNRENL3CoreService(
+    ImportedNRENL3CoreServiceInactive, lifecycle=[SubscriptionLifecycle.PROVISIONING, SubscriptionLifecycle.ACTIVE]
+):
+    """An imported :term:`NREN` L3 Core Service subscription."""
+
+    nren_l3_core_service_type: NRENL3CoreServiceType
+    nren_l3_core_service: NRENL3CoreServiceBlock
diff --git a/gso/schedules/validate_subscriptions.py b/gso/schedules/validate_subscriptions.py
index 2e55b05e02e74862c29616e3df9b202f6c31e464..d1d0a70c0690fcfe5c830753f41700659f07030f 100644
--- a/gso/schedules/validate_subscriptions.py
+++ b/gso/schedules/validate_subscriptions.py
@@ -13,7 +13,7 @@ logger = structlog.get_logger(__name__)
 
 
 @celery.task
-@scheduler(CronScheduleConfig(name="Subscriptions Validator", minute="10", hour="0"))
+@scheduler(CronScheduleConfig(name="Subscriptions Validator", minute="10", hour="3"))
 def validate_subscriptions() -> None:
     """Validate all subscriptions using their corresponding validation workflow."""
     subscriptions = get_active_insync_subscriptions()
diff --git a/gso/services/kentik_client.py b/gso/services/kentik_client.py
index 7849adeeec4f6347f2545c5ec76fffaf5f6ee0e1..a958aca58e074ce4de9e32c18849ca9ebf8fc048 100644
--- a/gso/services/kentik_client.py
+++ b/gso/services/kentik_client.py
@@ -99,6 +99,8 @@ class KentikClient:
 
         If the site is not found, return an empty dict.
 
+        .. vale off
+
         :param str site_slug: The name of the site, should be a three-letter slug like COR or POZ.
         """
         sites = self.get_sites()
diff --git a/gso/services/librenms_client.py b/gso/services/librenms_client.py
index b7c21f888ebe281ada67e64edb3e9fd671a0a655..b514f0009c814fcc0e698b07fbb8e86cb27e3150 100644
--- a/gso/services/librenms_client.py
+++ b/gso/services/librenms_client.py
@@ -10,7 +10,7 @@ from requests import HTTPError, Response
 from requests.adapters import HTTPAdapter
 
 from gso.settings import load_oss_params
-from gso.utils.types.snmp import SNMPVersion
+from gso.utils.shared_enums import SNMPVersion
 
 logger = logging.getLogger(__name__)
 
diff --git a/gso/services/netbox_client.py b/gso/services/netbox_client.py
index b79283cc9baf6f887ee22d63f852f711b4e89d16..3b71f49dd54fa643c5a7c465c9cd3deb68d61c35 100644
--- a/gso/services/netbox_client.py
+++ b/gso/services/netbox_client.py
@@ -13,6 +13,7 @@ from gso.settings import load_oss_params
 from gso.utils.device_info import (
     DEFAULT_SITE,
     FEASIBLE_IP_TRUNK_LAG_RANGE,
+    FEASIBLE_SERVICES_LAG_RANGE,
     ROUTER_ROLE,
     TierInfo,
 )
@@ -289,8 +290,8 @@ class NetboxClient:
             interface.lag = None
             interface.save()
 
-    def get_available_lags(self, router_id: UUID) -> list[str]:
-        """Return all available :term:`LAG` not assigned to a device."""
+    def get_available_lags_in_range(self, router_id: UUID, lag_range: range) -> list[str]:
+        """Return all available LAGs within a given range not assigned to a device."""
         router_name = Router.from_subscription(router_id).router.router_fqdn
         device = self.get_device_by_name(router_name)
 
@@ -299,12 +300,20 @@ class NetboxClient:
             interface["name"] for interface in self.netbox.dcim.interfaces.filter(device=device.name, type="lag")
         ]
 
-        # Generate all feasible LAGs
-        all_feasible_lags = [f"lag-{i}" for i in FEASIBLE_IP_TRUNK_LAG_RANGE]
+        # Generate all feasible LAGs in the specified range
+        all_feasible_lags = [f"lag-{i}" for i in lag_range]
 
         # Return available LAGs not assigned to the device
         return [lag for lag in all_feasible_lags if lag not in lag_interface_names]
 
+    def get_available_lags(self, router_id: UUID) -> list[str]:
+        """Return all available :term:`LAG` not assigned to a device."""
+        return self.get_available_lags_in_range(router_id, FEASIBLE_IP_TRUNK_LAG_RANGE)
+
+    def get_available_services_lags(self, router_id: UUID) -> list[str]:
+        """Return all available Edge port LAGs not assigned to a device."""
+        return self.get_available_lags_in_range(router_id, FEASIBLE_SERVICES_LAG_RANGE)
+
     @staticmethod
     def calculate_speed_bits_per_sec(speed: str) -> int:
         """Extract the numeric part from the speed."""
diff --git a/gso/services/subscriptions.py b/gso/services/subscriptions.py
index 33db8fd9c56a9a58391ead9d2e3d3177936002e0..1458f000439fa8bcf936a08b864eb117296caf37 100644
--- a/gso/services/subscriptions.py
+++ b/gso/services/subscriptions.py
@@ -246,6 +246,20 @@ def get_active_site_subscriptions(includes: list[str] | None = None) -> list[Sub
     )
 
 
+def get_active_edge_port_subscriptions(includes: list[str] | None = None) -> list[SubscriptionType]:
+    """Retrieve active Edge Port subscriptions.
+
+    :param includes: The fields to be included in the returned Subscription objects.
+    :type includes: list[str]
+
+    :return: A list of Subscription objects for Edge Ports.
+    :rtype: list[Subscription]
+    """
+    return get_subscriptions(
+        product_types=[ProductType.EDGE_PORT], lifecycles=[SubscriptionLifecycle.ACTIVE], includes=includes
+    )
+
+
 def get_site_by_name(site_name: str) -> Site:
     """Get a site by its name.
 
diff --git a/gso/settings.py b/gso/settings.py
index 0979dcb3a700da9c26fc6f35ebd40356b471ee4c..3596eb1cfcd6855d38c077f10f2babb1bd10ade5 100644
--- a/gso/settings.py
+++ b/gso/settings.py
@@ -9,15 +9,13 @@ import json
 import logging
 import os
 from pathlib import Path
-from typing import Annotated
 
 from orchestrator.types import UUIDstr
-from pydantic import EmailStr, Field
+from pydantic import EmailStr
 from pydantic_forms.types import strEnum
 from pydantic_settings import BaseSettings
-from typing_extensions import Doc
 
-from gso.utils.types.ip_address import PortNumber
+from gso.utils.types.ip_address import IPV4Netmask, IPV6Netmask, PortNumber
 
 logger = logging.getLogger(__name__)
 
@@ -62,16 +60,12 @@ class InfoBloxParams(BaseSettings):
     password: str
 
 
-V4Netmask = Annotated[int, Field(ge=0, le=32), Doc("A valid netmask for an IPv4 network or address.")]
-V6Netmask = Annotated[int, Field(ge=0, le=128), Doc("A valid netmask for an IPv6 network or address.")]
-
-
 class V4NetworkParams(BaseSettings):
     """A set of parameters that describe an IPv4 network in InfoBlox."""
 
     containers: list[ipaddress.IPv4Network]
     networks: list[ipaddress.IPv4Network]
-    mask: V4Netmask
+    mask: IPV4Netmask
 
 
 class V6NetworkParams(BaseSettings):
@@ -79,7 +73,7 @@ class V6NetworkParams(BaseSettings):
 
     containers: list[ipaddress.IPv6Network]
     networks: list[ipaddress.IPv6Network]
-    mask: V6Netmask
+    mask: IPV6Netmask
 
 
 class ServiceNetworkParams(BaseSettings):
diff --git a/gso/translations/en-GB.json b/gso/translations/en-GB.json
index f1ede053f813aefef202d92ffd31b6d48c3908a5..ef3a1bdeae293d10fa74fbb80a550860c3792841 100644
--- a/gso/translations/en-GB.json
+++ b/gso/translations/en-GB.json
@@ -19,6 +19,7 @@
             "router_role": "Router role",
 
             "geant_s_sid": "GÉANT S-SID",
+            "geant_sid": "GÉANT S-SID",
             "iptrunk_description": "IPtrunk description",
             "iptrunk_type": "IPtrunk type",
             "iptrunk_speed": "Capacity per port (in Gbits/s)",
@@ -33,7 +34,13 @@
             "remove_configuration": "Remove configuration from the router",
             "clean_up_ipam": "Clean up related entries in IPAM",
             "restore_isis_metric": "Restore ISIS metric to original value",
-            "confirm_info": "Please verify this form looks correct."
+            "iptrunk_ipv4_network": "IPtrunk IPv4 network",
+            "iptrunk_ipv6_network": "IPtrunk IPv6 network",
+            "isis_metric": "ISIS metric",
+
+            "enable_lacp": "Enable LACP",
+            "mac_address": "MAC address",
+            "geant_ga_id": "GÉANT GA-ID"
         }
     },
     "workflow": {
@@ -44,17 +51,23 @@
         "create_router": "Create Router",
         "create_site": "Create Site",
         "create_switch": "Create Switch",
+        "create_edge_port": "Create Edge Port",
+        "create_nren_l3_core_service": "Create NREN L3 Core Service",
         "deploy_twamp": "Deploy TWAMP",
         "migrate_iptrunk": "Migrate IP Trunk",
+        "migrate_nren_l3_core_service": "Migrate NREN L3 Core Service",
         "modify_isis_metric": "Modify the ISIS metric",
         "modify_site": "Modify Site",
         "modify_trunk_interface": "Modify IP Trunk interface",
         "modify_connection_strategy": "Modify connection strategy",
         "modify_router_kentik_license": "Modify device license in Kentik",
+        "modify_edge_port": "Modify Edge Port",
+        "modify_nren_l3_core_service": "Modify NREN L3 Core Service",
         "terminate_iptrunk": "Terminate IP Trunk",
         "terminate_router": "Terminate Router",
         "terminate_site": "Terminate Site",
         "terminate_switch": "Terminate Switch",
+        "terminate_edge_port": "Terminate Edge Port",
         "redeploy_base_config": "Redeploy base config",
         "update_ibgp_mesh": "Update iBGP mesh",
         "create_imported_site": "NOT FOR HUMANS -- Import existing site",
@@ -63,15 +76,20 @@
         "create_imported_super_pop_switch": "NOT FOR HUMANS -- Import existing super PoP switch",
         "create_imported_office_router": "NOT FOR HUMANS -- Import existing office router",
         "create_imported_opengear": "NOT FOR HUMANS -- Import existing OpenGear",
+        "create_imported_edge_port": "NOT FOR HUMANS -- Import existing Edge Port",
+        "create_imported_nren_l3_core_service": "NOT FOR HUMANS -- Import existing NREN L3 Core Service",
         "import_site": "NOT FOR HUMANS -- Finalize import into a Site product",
         "import_router": "NOT FOR HUMANS -- Finalize import into a Router product",
         "import_iptrunk": "NOT FOR HUMANS -- Finalize import into an IP trunk product",
         "import_office_router": "NOT FOR HUMANS -- Finalize import into an Office router product",
         "import_super_pop_switch": "NOT FOR HUMANS -- Finalize import into a Super PoP switch",
         "import_opengear": "NOT FOR HUMANS -- Finalize import into an OpenGear",
+        "import_edge_port": "NOT FOR HUMANS -- Finalize import into an Edge Port",
+        "import_nren_l3_core_service": "NOT FOR HUMANS -- Finalize import into a NREN L3 Core Service",
         "validate_iptrunk": "Validate IP Trunk configuration",
         "validate_router": "Validate Router configuration",
         "validate_switch": "Validate Switch configuration",
+        "validate_edge_port": "Validate Edge Port",
         "task_validate_geant_products": "Validation task for GEANT products",
         "task_send_email_notifications": "Send email notifications for failed tasks",
         "task_create_partners": "Create partner task",
diff --git a/gso/utils/device_info.py b/gso/utils/device_info.py
index 800d56779ae607a16a50a430a88a024f9cbdeb66..6ee6004c0007590f6cb476c5fca2b57aa4e765d1 100644
--- a/gso/utils/device_info.py
+++ b/gso/utils/device_info.py
@@ -45,8 +45,8 @@ class TierInfo:
         return getattr(self, name)
 
 
-# The range includes values from 1 to 10 (11 is not included)
 FEASIBLE_IP_TRUNK_LAG_RANGE = range(1, 11)
+FEASIBLE_SERVICES_LAG_RANGE = range(20, 51)
 
 # Define default values
 ROUTER_ROLE = {"name": "router", "slug": "router"}
diff --git a/gso/utils/helpers.py b/gso/utils/helpers.py
index 4a78b989d41ddd49e2ee99b5f703a9dd519a7a07..c05517a23beed1b47a5aaf37786af5fe8b8d1eee 100644
--- a/gso/utils/helpers.py
+++ b/gso/utils/helpers.py
@@ -4,6 +4,7 @@ import re
 from typing import TYPE_CHECKING
 from uuid import UUID
 
+from pydantic_forms.types import UUIDstr
 from pydantic_forms.validators import Choice
 
 from gso import settings
@@ -11,6 +12,7 @@ from gso.products.product_blocks.router import RouterRole
 from gso.products.product_types.router import Router
 from gso.services import subscriptions
 from gso.services.netbox_client import NetboxClient
+from gso.services.partners import get_all_partners
 from gso.utils.shared_enums import Vendor
 from gso.utils.types.interfaces import PhysicalPortCapacity
 from gso.utils.types.ip_address import IPv4AddressType
@@ -75,6 +77,18 @@ def available_lags_choices(router_id: UUID) -> Choice | None:
     return Choice("ae iface", zip(side_a_ae_iface_list, side_a_ae_iface_list, strict=True))  # type: ignore[arg-type]
 
 
+def available_service_lags_choices(router_id: UUID) -> Choice | None:
+    """Return a list of available lags for a given router for services.
+
+    For Nokia routers, return a list of available lags.
+    For Juniper routers, return ``None``.
+    """
+    if get_router_vendor(router_id) != Vendor.NOKIA:
+        return None
+    side_a_ae_iface_list = NetboxClient().get_available_services_lags(router_id)
+    return Choice("ae iface", zip(side_a_ae_iface_list, side_a_ae_iface_list, strict=True))  # type: ignore[arg-type]
+
+
 def get_router_vendor(router_id: UUID) -> Vendor:
     """Retrieve the vendor of a router.
 
@@ -177,6 +191,16 @@ def active_router_selector() -> Choice:
     return Choice("Select a router", zip(router_subscriptions.keys(), router_subscriptions.items(), strict=True))  # type: ignore[arg-type]
 
 
+def active_pe_router_selector() -> Choice:
+    """Generate a dropdown selector for choosing an active PE Router in an input form."""
+    routers = {
+        str(router.subscription_id): router.description
+        for router in subscriptions.get_active_subscriptions_by_field_and_value("router_role", RouterRole.PE)
+    }
+
+    return Choice("Select a router", zip(routers.keys(), routers.items(), strict=True))  # type: ignore[arg-type]
+
+
 def active_switch_selector() -> Choice:
     """Generate a dropdown selector for choosing an active Switch in an input form."""
     switch_subscriptions = {
@@ -185,3 +209,42 @@ def active_switch_selector() -> Choice:
     }
 
     return Choice("Select a switch", zip(switch_subscriptions.keys(), switch_subscriptions.items(), strict=True))  # type: ignore[arg-type]
+
+
+def active_edge_port_selector(*, partner_id: UUIDstr | None = None) -> Choice:
+    """Generate a dropdown selector for choosing an active Edge Port in an input form."""
+    edge_port_subscriptions = subscriptions.get_active_edge_port_subscriptions(
+        includes=["subscription_id", "description", "customer_id"]
+    )
+
+    if partner_id:
+        # ``partner_id`` is set, so we will filter accordingly.
+        edge_port_subscriptions = list(
+            filter(lambda subscription: bool(subscription["customer_id"] == partner_id), edge_port_subscriptions)
+        )
+
+    edge_ports = {str(port["subscription_id"]): port["description"] for port in edge_port_subscriptions}
+
+    return Choice(
+        "Select an Edge Port",
+        zip(edge_ports.keys(), edge_ports.items(), strict=True),  # type: ignore[arg-type]
+    )
+
+
+def partner_choice() -> Choice:
+    """Return a Choice object containing a list of available partners."""
+    partners = {partner["partner_id"]: partner["name"] for partner in get_all_partners()}
+
+    return Choice("Select a partner", zip(partners.values(), partners.items(), strict=True))  # type: ignore[arg-type]
+
+
+def validate_edge_port_number_of_members_based_on_lacp(*, number_of_members: int, enable_lacp: bool) -> None:
+    """Validate the number of edge port members based on the :term:`LACP` setting.
+
+    :param number_of_members: The number of members to validate.
+    :param enable_lacp: Whether :term:`LACP` is enabled or not.
+    :raises ValueError: If the number of members is greater than 1 and :term:`LACP` is disabled.
+    """
+    if number_of_members > 1 and not enable_lacp:
+        err_msg = "Number of members must be 1 if LACP is disabled."
+        raise ValueError(err_msg)
diff --git a/gso/utils/shared_enums.py b/gso/utils/shared_enums.py
index 929e5b0bc2c2553c90b36ce8f056c1f22fed74cc..a6050c9bc72de8f7554d41785b41c1e91d38a483 100644
--- a/gso/utils/shared_enums.py
+++ b/gso/utils/shared_enums.py
@@ -1,5 +1,7 @@
 """Shared choices for the different models."""
 
+from enum import StrEnum
+
 from pydantic_forms.types import strEnum
 
 
@@ -15,3 +17,25 @@ class ConnectionStrategy(strEnum):
 
     IN_BAND = "IN BAND"
     OUT_OF_BAND = "OUT OF BAND"
+
+
+class SNMPVersion(StrEnum):
+    """An enumerator for the two relevant versions of :term:`SNMP`: v2c and 3."""
+
+    V2C = "v2c"
+    V3 = "v3"
+
+
+class APType(strEnum):
+    """Enumerator of the types of Access Port."""
+
+    PRIMARY = "PRIMARY"
+    BACKUP = "BACKUP"
+    LOAD_BALANCED = "LOAD_BALANCED"
+
+
+class SBPType(strEnum):
+    """Enumerator for the two allowed types of service binding port: layer 2 or layer 3."""
+
+    L2 = "l2"
+    L3 = "l3"
diff --git a/gso/utils/types/ip_address.py b/gso/utils/types/ip_address.py
index 94cb8beaf498900e4785d3e222a73685f5d35bf6..6186a1f83d6a495aa591e1e96a6a2030794de7fd 100644
--- a/gso/utils/types/ip_address.py
+++ b/gso/utils/types/ip_address.py
@@ -18,13 +18,29 @@ def validate_ipv4_or_ipv6(value: str) -> str:
         return value
 
 
+def validate_ipv4_or_ipv6_network(value: str) -> str:
+    """Validate that a value is a valid IPv4 or IPv6 network."""
+    try:
+        ipaddress.ip_network(value)
+    except ValueError as e:
+        msg = "Enter a valid IPv4 or IPv6 network."
+        raise ValueError(msg) from e
+    else:
+        return value
+
+
 def _str(value: Any) -> str:
     return str(value)
 
 
 IPv4AddressType = Annotated[ipaddress.IPv4Address, PlainSerializer(_str, return_type=str, when_used="always")]
+IPv4NetworkType = Annotated[ipaddress.IPv4Network, PlainSerializer(_str, return_type=str, when_used="always")]
 IPv6AddressType = Annotated[ipaddress.IPv6Address, PlainSerializer(_str, return_type=str, when_used="always")]
+IPv6NetworkType = Annotated[ipaddress.IPv6Network, PlainSerializer(_str, return_type=str, when_used="always")]
 IPAddress = Annotated[str, AfterValidator(validate_ipv4_or_ipv6)]
+IPNetwork = Annotated[str, AfterValidator(validate_ipv4_or_ipv6_network)]
+IPV4Netmask = Annotated[int, Field(ge=0, le=32), Doc("A valid netmask for an IPv4 network or address.")]
+IPV6Netmask = Annotated[int, Field(ge=0, le=128), Doc("A valid netmask for an IPv6 network or address.")]
 PortNumber = Annotated[
     int,
     Field(
diff --git a/gso/utils/types/snmp.py b/gso/utils/types/snmp.py
deleted file mode 100644
index 03581cf970c036db37ea901d8b159d25fc480136..0000000000000000000000000000000000000000
--- a/gso/utils/types/snmp.py
+++ /dev/null
@@ -1,10 +0,0 @@
-"""An enumerator of SNMP version numbers."""
-
-from enum import StrEnum
-
-
-class SNMPVersion(StrEnum):
-    """An enumerator for the two relevant versions of :term:`SNMP`: v2c and 3."""
-
-    V2C = "v2c"
-    V3 = "v3"
diff --git a/gso/utils/types/unique_field.py b/gso/utils/types/unique_field.py
index 32c3d9de43560998dea262b512625de60363de44..87962bd7a2b6c1d08297fb4912c24034ec495aac 100644
--- a/gso/utils/types/unique_field.py
+++ b/gso/utils/types/unique_field.py
@@ -1,21 +1,27 @@
 """An input field that must be unique in the database."""
 
+from functools import partial
 from typing import Annotated, TypeVar
 
 from pydantic import AfterValidator
 from pydantic_core.core_schema import ValidationInfo
+from pydantic_forms.types import UUIDstr
 
 from gso.services import subscriptions
 
 T = TypeVar("T")
 
 
-def validate_field_is_unique(value: T, info: ValidationInfo) -> T:
+def validate_field_is_unique(subscription_id: UUIDstr, value: T, info: ValidationInfo) -> T:
     """Validate that a field is unique."""
-    if len(subscriptions.get_active_subscriptions_by_field_and_value(str(info.field_name), str(value))) > 0:
+    matched_subscriptions = subscriptions.get_active_subscriptions_by_field_and_value(str(info.field_name), str(value))
+    matched_subscriptions = list(
+        filter(lambda match: str(match.subscription_id) != subscription_id, matched_subscriptions)
+    )
+    if len(matched_subscriptions) > 0:
         msg = f"{info.field_name} must be unique"
         raise ValueError(msg)
     return value
 
 
-UniqueField = Annotated[T, AfterValidator(validate_field_is_unique)]
+UniqueField = Annotated[T, AfterValidator(partial(validate_field_is_unique, ""))]
diff --git a/gso/workflows/__init__.py b/gso/workflows/__init__.py
index d94174f2c80a722eb9bc1706e3070f7f49122a0c..5a6b9d082339add5f4890d54b3654228a989d7de 100644
--- a/gso/workflows/__init__.py
+++ b/gso/workflows/__init__.py
@@ -78,3 +78,20 @@ LazyWorkflowInstance("gso.workflows.tasks.create_partners", "task_create_partner
 LazyWorkflowInstance("gso.workflows.tasks.modify_partners", "task_modify_partners")
 LazyWorkflowInstance("gso.workflows.tasks.delete_partners", "task_delete_partners")
 LazyWorkflowInstance("gso.workflows.tasks.clean_old_tasks", "task_clean_old_tasks")
+
+#  Edge port workflows
+LazyWorkflowInstance("gso.workflows.edge_port.create_edge_port", "create_edge_port")
+LazyWorkflowInstance("gso.workflows.edge_port.modify_edge_port", "modify_edge_port")
+LazyWorkflowInstance("gso.workflows.edge_port.terminate_edge_port", "terminate_edge_port")
+LazyWorkflowInstance("gso.workflows.edge_port.validate_edge_port", "validate_edge_port")
+LazyWorkflowInstance("gso.workflows.edge_port.create_imported_edge_port", "create_imported_edge_port")
+LazyWorkflowInstance("gso.workflows.edge_port.import_edge_port", "import_edge_port")
+
+#  NREN L3 Core Service workflows
+LazyWorkflowInstance("gso.workflows.nren_l3_core_service.create_nren_l3_core_service", "create_nren_l3_core_service")
+LazyWorkflowInstance("gso.workflows.nren_l3_core_service.modify_nren_l3_core_service", "modify_nren_l3_core_service")
+LazyWorkflowInstance(
+    "gso.workflows.nren_l3_core_service.create_imported_nren_l3_core_service", "create_imported_nren_l3_core_service"
+)
+LazyWorkflowInstance("gso.workflows.nren_l3_core_service.import_nren_l3_core_service", "import_nren_l3_core_service")
+LazyWorkflowInstance("gso.workflows.nren_l3_core_service.migrate_nren_l3_core_service", "migrate_nren_l3_core_service")
diff --git a/gso/workflows/edge_port/__init__.py b/gso/workflows/edge_port/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..5e496dae50c3245dcde7e606e4e5df87cde77974
--- /dev/null
+++ b/gso/workflows/edge_port/__init__.py
@@ -0,0 +1 @@
+"""All workflows that can be executed on Edge port."""
diff --git a/gso/workflows/edge_port/create_edge_port.py b/gso/workflows/edge_port/create_edge_port.py
new file mode 100644
index 0000000000000000000000000000000000000000..5977206dfaa57ce4bf271c69464e440aa0fdc722
--- /dev/null
+++ b/gso/workflows/edge_port/create_edge_port.py
@@ -0,0 +1,277 @@
+"""A creation workflow for adding a new edge port to the network."""
+
+from typing import Annotated, Any, Self
+from uuid import uuid4
+
+from annotated_types import Len
+from orchestrator import step, workflow
+from orchestrator.forms import FormPage
+from orchestrator.targets import Target
+from orchestrator.types import FormGenerator, State, SubscriptionLifecycle, UUIDstr
+from orchestrator.utils.errors import ProcessFailureError
+from orchestrator.workflow import StepList, begin, done
+from orchestrator.workflows.steps import resync, set_status, store_process_subscription
+from orchestrator.workflows.utils import wrap_create_initial_input_form
+from pydantic import AfterValidator, ConfigDict, model_validator
+from pydantic_forms.validators import validate_unique_list
+from pynetbox.models.dcim import Interfaces
+
+from gso.products.product_blocks.edge_port import EdgePortAEMemberBlockInactive, EdgePortType, EncapsulationType
+from gso.products.product_types.edge_port import EdgePortInactive, EdgePortProvisioning
+from gso.products.product_types.router import Router
+from gso.services.lso_client import LSOState, lso_interaction
+from gso.services.netbox_client import NetboxClient
+from gso.services.partners import get_partner_by_id
+from gso.utils.helpers import (
+    active_pe_router_selector,
+    available_interfaces_choices,
+    available_service_lags_choices,
+    partner_choice,
+    validate_edge_port_number_of_members_based_on_lacp,
+)
+from gso.utils.types.interfaces import LAGMember, PhysicalPortCapacity
+from gso.utils.types.tt_number import TTNumber
+from gso.workflows.shared import create_summary_form
+
+
+def initial_input_form_generator(product_name: str) -> FormGenerator:
+    """Gather information to create a new Edge Port."""
+
+    class CreateEdgePortForm(FormPage):
+        model_config = ConfigDict(title=product_name)
+
+        tt_number: TTNumber
+        node: active_pe_router_selector()  # type: ignore[valid-type]
+        partner: partner_choice()  # type: ignore[valid-type]
+        service_type: EdgePortType
+        enable_lacp: bool = False
+        speed: PhysicalPortCapacity
+        encapsulation: EncapsulationType = EncapsulationType.DOT1Q
+        number_of_members: int
+        minimum_links: int
+        mac_address: str | None = None
+        ignore_if_down: bool = False
+        geant_ga_id: str | None = None
+
+        @model_validator(mode="after")
+        def validate_number_of_members(self) -> Self:
+            validate_edge_port_number_of_members_based_on_lacp(
+                enable_lacp=self.enable_lacp, number_of_members=self.number_of_members
+            )
+            return self
+
+    initial_user_input = yield CreateEdgePortForm
+
+    class EdgePortLAGMember(LAGMember):
+        interface_name: available_interfaces_choices(  # type: ignore[valid-type]
+            initial_user_input.node, initial_user_input.speed
+        )
+
+    lag_ae_members = Annotated[
+        list[EdgePortLAGMember],
+        AfterValidator(validate_unique_list),
+        Len(
+            min_length=initial_user_input.number_of_members,
+            max_length=initial_user_input.number_of_members,
+        ),
+    ]
+
+    class SelectInterfaceForm(FormPage):
+        model_config = ConfigDict(title="Select Interfaces")
+
+        name: available_service_lags_choices(initial_user_input.node)  # type: ignore[valid-type]
+        description: str | None = None
+        ae_members: lag_ae_members
+
+    interface_form_input_data = yield SelectInterfaceForm
+
+    input_forms_data = initial_user_input.model_dump() | interface_form_input_data.model_dump()
+    summary_form_data = input_forms_data | {
+        "node": Router.from_subscription(initial_user_input.node).router.router_fqdn,
+        "partner": get_partner_by_id(initial_user_input.partner).name,
+        "edge_port_ae_members": input_forms_data["ae_members"],
+        "edge_port_name": input_forms_data["name"],
+        "edge_port_description": input_forms_data["description"],
+        "edge_port_type": input_forms_data["service_type"],
+    }
+    summary_fields = [
+        "node",
+        "partner",
+        "edge_port_type",
+        "speed",
+        "encapsulation",
+        "minimum_links",
+        "mac_address",
+        "ignore_if_down",
+        "geant_ga_id",
+        "enable_lacp",
+        "edge_port_name",
+        "edge_port_description",
+        "edge_port_ae_members",
+    ]
+    yield from create_summary_form(summary_form_data, product_name, summary_fields)
+    return input_forms_data
+
+
+@step("Create subscription")
+def create_subscription(product: UUIDstr, partner: UUIDstr) -> State:
+    """Create a new subscription object."""
+    subscription = EdgePortInactive.from_product_id(product, partner)
+
+    return {
+        "subscription": subscription,
+        "subscription_id": subscription.subscription_id,
+    }
+
+
+@step("Initialize subscription")
+def initialize_subscription(
+    subscription: EdgePortInactive,
+    node: UUIDstr,
+    service_type: EdgePortType,
+    speed: PhysicalPortCapacity,
+    encapsulation: EncapsulationType,
+    name: str,
+    minimum_links: int,
+    geant_ga_id: str | None,
+    mac_address: str | None,
+    partner: str,
+    enable_lacp: bool,  # noqa: FBT001
+    ignore_if_down: bool,  # noqa: FBT001
+    ae_members: list[dict[str, Any]],
+    description: str | None = None,
+) -> State:
+    """Initialise the subscription object in the service database."""
+    router = Router.from_subscription(node).router
+    subscription.edge_port.node = router
+    subscription.edge_port.edge_port_type = service_type
+    subscription.edge_port.enable_lacp = enable_lacp
+    subscription.edge_port.member_speed = speed
+    subscription.edge_port.encapsulation = encapsulation
+    subscription.edge_port.edge_port_name = name
+    subscription.edge_port.minimum_links = minimum_links
+    subscription.edge_port.ignore_if_down = ignore_if_down
+    subscription.edge_port.geant_ga_id = geant_ga_id
+    subscription.edge_port.mac_address = mac_address
+    partner_name = get_partner_by_id(partner).name
+    subscription.description = f"Edge Port {name} on {router.router_fqdn}, " f"{partner_name}, {geant_ga_id or ""}"
+    subscription.edge_port.edge_port_description = description
+    for member in ae_members:
+        subscription.edge_port.edge_port_ae_members.append(
+            EdgePortAEMemberBlockInactive.new(subscription_id=uuid4(), **member)
+        )
+    subscription = EdgePortProvisioning.from_other_lifecycle(subscription, SubscriptionLifecycle.PROVISIONING)
+
+    return {"subscription": subscription, "partner_name": partner_name}
+
+
+@step("Reserve interfaces in NetBox")
+def reserve_interfaces_in_netbox(subscription: EdgePortProvisioning) -> State:
+    """Create the :term:`LAG` interfaces in NetBox and attach the lag interfaces to the physical interfaces."""
+    nbclient = NetboxClient()
+    edge_port = subscription.edge_port
+    # Create :term:`LAG` interfaces
+    lag_interface: Interfaces = nbclient.create_interface(
+        iface_name=edge_port.edge_port_name,
+        interface_type="lag",
+        device_name=edge_port.node.router_fqdn,
+        description=str(subscription.subscription_id),
+        enabled=True,
+    )
+    # Attach physical interfaces to :term:`LAG`
+    # Update interface description to subscription ID
+    # Reserve interfaces
+    for interface in edge_port.edge_port_ae_members:
+        nbclient.attach_interface_to_lag(
+            device_name=edge_port.node.router_fqdn,
+            lag_name=lag_interface.name,
+            iface_name=interface.interface_name,
+            description=str(subscription.subscription_id),
+        )
+        nbclient.reserve_interface(
+            device_name=edge_port.node.router_fqdn,
+            iface_name=interface.interface_name,
+        )
+    return {
+        "subscription": subscription,
+    }
+
+
+@step("Allocate interfaces in NetBox")
+def allocate_interfaces_in_netbox(subscription: EdgePortProvisioning) -> None:
+    """Allocate the interfaces in NetBox."""
+    for interface in subscription.edge_port.edge_port_ae_members:
+        fqdn = subscription.edge_port.node.router_fqdn
+        iface_name = interface.interface_name
+        if not fqdn or not iface_name:
+            msg = "FQDN and/or interface name missing in subscription"
+            raise ProcessFailureError(msg, details=subscription.subscription_id)
+
+        NetboxClient().allocate_interface(device_name=fqdn, iface_name=iface_name)
+
+
+@step("[DRY RUN] Create edge port")
+def create_edge_port_dry(
+    subscription: dict[str, Any], tt_number: str, process_id: UUIDstr, partner_name: str
+) -> LSOState:
+    """Create a new edge port in the network as a dry run."""
+    extra_vars = {
+        "dry_run": True,
+        "subscription": subscription,
+        "partner_name": partner_name,
+        "commit_comment": f"GSO_PROCESS_ID: {process_id} - TT_NUMBER: {tt_number} - Create Edge Port",
+        "verb": "create",
+    }
+
+    return {
+        "playbook_name": "gap_ansible/playbooks/edge_port.yaml",
+        "inventory": {"all": {"hosts": {subscription["edge_port"]["node"]["router_fqdn"]: None}}},
+        "extra_vars": extra_vars,
+    }
+
+
+@step("[FOR REAL] Create edge port")
+def create_edge_port_real(
+    subscription: dict[str, Any], tt_number: str, process_id: UUIDstr, partner_name: str
+) -> LSOState:
+    """Create a new edge port in the network for real."""
+    extra_vars = {
+        "dry_run": False,
+        "subscription": subscription,
+        "partner_name": partner_name,
+        "commit_comment": f"GSO_PROCESS_ID: {process_id} - TT_NUMBER: {tt_number} - Create Edge Port",
+        "verb": "create",
+    }
+
+    return {
+        "playbook_name": "gap_ansible/playbooks/edge_port.yaml",
+        "inventory": {"all": {"hosts": {subscription["edge_port"]["node"]["router_fqdn"]: None}}},
+        "extra_vars": extra_vars,
+    }
+
+
+@workflow(
+    "Create Edge Port",
+    initial_input_form=wrap_create_initial_input_form(initial_input_form_generator),
+    target=Target.CREATE,
+)
+def create_edge_port() -> StepList:
+    """Create a new edge port in the network.
+
+    * Create and initialise the subscription object in the service database
+    * Deploy configuration on the new edge port, first as a dry run
+    * allocate :term:`LAG` and :term:`LAG` members in the Netbox.
+    """
+    return (
+        begin
+        >> create_subscription
+        >> store_process_subscription(Target.CREATE)
+        >> initialize_subscription
+        >> reserve_interfaces_in_netbox
+        >> lso_interaction(create_edge_port_dry)
+        >> lso_interaction(create_edge_port_real)
+        >> allocate_interfaces_in_netbox
+        >> set_status(SubscriptionLifecycle.ACTIVE)
+        >> resync
+        >> done
+    )
diff --git a/gso/workflows/edge_port/create_imported_edge_port.py b/gso/workflows/edge_port/create_imported_edge_port.py
new file mode 100644
index 0000000000000000000000000000000000000000..b932175f072c4b03d414777ca9896797e9fdf81f
--- /dev/null
+++ b/gso/workflows/edge_port/create_imported_edge_port.py
@@ -0,0 +1,118 @@
+"""A creation workflow that adds an existing Edge Port to the DB."""
+
+from typing import Annotated, Any
+from uuid import uuid4
+
+from orchestrator import workflow
+from orchestrator.forms import FormPage
+from orchestrator.targets import Target
+from orchestrator.types import FormGenerator, State, SubscriptionLifecycle
+from orchestrator.workflow import StepList, begin, done, step
+from orchestrator.workflows.steps import resync, set_status, store_process_subscription
+from pydantic import AfterValidator, ConfigDict
+from pydantic_forms.types import UUIDstr
+from pydantic_forms.validators import validate_unique_list
+
+from gso.products import ProductName
+from gso.products.product_blocks.edge_port import EdgePortAEMemberBlockInactive, EdgePortType, EncapsulationType
+from gso.products.product_types.edge_port import EdgePortInactive, ImportedEdgePortInactive
+from gso.products.product_types.router import Router
+from gso.services.partners import get_partner_by_name
+from gso.services.subscriptions import get_product_id_by_name
+from gso.utils.helpers import active_pe_router_selector
+from gso.utils.types.interfaces import LAGMember, PhysicalPortCapacity
+
+
+@step("Create subscription")
+def create_subscription(partner: str) -> State:
+    """Create a new subscription object."""
+    partner_id = get_partner_by_name(partner)["partner_id"]
+    product_id = get_product_id_by_name(ProductName.IMPORTED_EDGE_PORT)
+    subscription = ImportedEdgePortInactive.from_product_id(product_id, partner_id)
+
+    return {
+        "subscription": subscription,
+        "subscription_id": subscription.subscription_id,
+    }
+
+
+def initial_input_form_generator() -> FormGenerator:
+    """Generate a form that is filled in using information passed through the :term:`API` endpoint."""
+
+    class ImportEdgePort(FormPage):
+        model_config = ConfigDict(title="Import Router")
+
+        node: active_pe_router_selector()  # type: ignore[valid-type]
+        partner: str
+        service_type: EdgePortType
+        enable_lacp: bool
+        speed: PhysicalPortCapacity
+        encapsulation: EncapsulationType = EncapsulationType.DOT1Q
+        minimum_links: int
+        mac_address: str | None = None
+        ignore_if_down: bool = False
+        geant_ga_id: str | None = None
+        description: str | None = None
+        name: str
+        ae_members: Annotated[list[LAGMember], AfterValidator(validate_unique_list)]
+
+    user_input = yield ImportEdgePort
+
+    return user_input.model_dump()
+
+
+@step("Initialize subscription")
+def initialize_subscription(
+    subscription: EdgePortInactive,
+    node: UUIDstr,
+    service_type: EdgePortType,
+    speed: PhysicalPortCapacity,
+    encapsulation: EncapsulationType,
+    name: str,
+    minimum_links: int,
+    geant_ga_id: str | None,
+    mac_address: str | None,
+    partner: str,
+    enable_lacp: bool,  # noqa: FBT001
+    ignore_if_down: bool,  # noqa: FBT001
+    ae_members: list[dict[str, Any]],
+    description: str | None = None,
+) -> State:
+    """Initialise the subscription object in the service database."""
+    router = Router.from_subscription(node).router
+    subscription.edge_port.node = router
+    subscription.edge_port.edge_port_name = name
+    subscription.edge_port.edge_port_description = description
+    subscription.edge_port.enable_lacp = enable_lacp
+    subscription.edge_port.encapsulation = encapsulation
+    subscription.edge_port.mac_address = mac_address
+    subscription.edge_port.member_speed = speed
+    subscription.edge_port.minimum_links = minimum_links
+    subscription.edge_port.edge_port_type = service_type
+    subscription.edge_port.ignore_if_down = ignore_if_down
+    subscription.edge_port.geant_ga_id = geant_ga_id
+    subscription.description = f"Edge Port {name} on {router.router_fqdn}, {partner}, {geant_ga_id or ""}"
+    for member in ae_members:
+        subscription.edge_port.edge_port_ae_members.append(
+            EdgePortAEMemberBlockInactive.new(subscription_id=uuid4(), **member)
+        )
+
+    return {"subscription": subscription}
+
+
+@workflow(
+    "Import Edge Port",
+    initial_input_form=initial_input_form_generator,
+    target=Target.CREATE,
+)
+def create_imported_edge_port() -> StepList:
+    """Import an Edge Port without provisioning it."""
+    return (
+        begin
+        >> create_subscription
+        >> store_process_subscription(Target.CREATE)
+        >> initialize_subscription
+        >> set_status(SubscriptionLifecycle.ACTIVE)
+        >> resync
+        >> done
+    )
diff --git a/gso/workflows/edge_port/import_edge_port.py b/gso/workflows/edge_port/import_edge_port.py
new file mode 100644
index 0000000000000000000000000000000000000000..3193489a6606eaff8f553e4358f5621352bca613
--- /dev/null
+++ b/gso/workflows/edge_port/import_edge_port.py
@@ -0,0 +1,29 @@
+"""A modification workflow for migrating an ImportedEdgePort to an EdgePort subscription."""
+
+from orchestrator.targets import Target
+from orchestrator.types import State, UUIDstr
+from orchestrator.workflow import StepList, done, init, step, workflow
+from orchestrator.workflows.steps import resync, store_process_subscription, unsync
+from orchestrator.workflows.utils import wrap_modify_initial_input_form
+
+from gso.products import ProductName
+from gso.products.product_types.edge_port import EdgePort, ImportedEdgePort
+from gso.services.subscriptions import get_product_id_by_name
+
+
+@step("Create new Edge Port subscription")
+def import_edge_port_subscription(subscription_id: UUIDstr) -> State:
+    """Take an ImportedEdgePort subscription, and turn it into an EdgePort subscription."""
+    old_edge_port = ImportedEdgePort.from_subscription(subscription_id)
+    new_subscription_id = get_product_id_by_name(ProductName.EDGE_PORT)
+    new_subscription = EdgePort.from_other_product(old_edge_port, new_subscription_id)  # type: ignore[arg-type]
+
+    return {"subscription": new_subscription}
+
+
+@workflow("Import Edge Port", target=Target.MODIFY, initial_input_form=wrap_modify_initial_input_form(None))
+def import_edge_port() -> StepList:
+    """Modify an ImportedEdgePort subscription into an EdgePort subscription to complete the import."""
+    return (
+        init >> store_process_subscription(Target.MODIFY) >> unsync >> import_edge_port_subscription >> resync >> done
+    )
diff --git a/gso/workflows/edge_port/modify_edge_port.py b/gso/workflows/edge_port/modify_edge_port.py
new file mode 100644
index 0000000000000000000000000000000000000000..b2923004e4b74d730006c1f8d0998a9c3da29287
--- /dev/null
+++ b/gso/workflows/edge_port/modify_edge_port.py
@@ -0,0 +1,277 @@
+"""Modify an existing edge port subscription."""
+
+from typing import Annotated, Any, Self
+from uuid import uuid4
+
+from annotated_types import Len
+from orchestrator import workflow
+from orchestrator.forms import FormPage
+from orchestrator.targets import Target
+from orchestrator.workflow import StepList, begin, conditional, done, step
+from orchestrator.workflows.steps import resync, store_process_subscription, unsync
+from orchestrator.workflows.utils import wrap_modify_initial_input_form
+from pydantic import AfterValidator, ConfigDict, model_validator
+from pydantic_forms.types import FormGenerator, State, UUIDstr
+from pydantic_forms.validators import ReadOnlyField, validate_unique_list
+
+from gso.products.product_blocks.edge_port import EdgePortAEMemberBlock, EncapsulationType
+from gso.products.product_types.edge_port import EdgePort
+from gso.services.lso_client import LSOState, lso_interaction
+from gso.services.netbox_client import NetboxClient
+from gso.services.partners import get_partner_by_id
+from gso.utils.helpers import (
+    available_interfaces_choices,
+    available_interfaces_choices_including_current_members,
+    validate_edge_port_number_of_members_based_on_lacp,
+)
+from gso.utils.types.interfaces import LAGMember, PhysicalPortCapacity
+from gso.utils.types.tt_number import TTNumber
+
+
+def initial_input_form_generator(subscription_id: UUIDstr) -> FormGenerator:
+    """Gather input from the operator on what to change about the selected edge port subscription."""
+    subscription = EdgePort.from_subscription(subscription_id)
+
+    class ModifyEdgePortForm(FormPage):
+        model_config = ConfigDict(title="Modify Edge Port")
+
+        tt_number: TTNumber
+        enable_lacp: bool = subscription.edge_port.enable_lacp
+        member_speed: PhysicalPortCapacity = subscription.edge_port.member_speed
+        encapsulation: EncapsulationType = subscription.edge_port.encapsulation
+        number_of_members: int = len(subscription.edge_port.edge_port_ae_members)
+        minimum_links: int | None = subscription.edge_port.minimum_links or None
+        mac_address: str | None = subscription.edge_port.mac_address or None
+        ignore_if_down: bool = subscription.edge_port.ignore_if_down
+        geant_ga_id: str | None = subscription.edge_port.geant_ga_id or None
+
+        @model_validator(mode="after")
+        def validate_number_of_members(self) -> Self:
+            validate_edge_port_number_of_members_based_on_lacp(
+                enable_lacp=self.enable_lacp, number_of_members=self.number_of_members
+            )
+            return self
+
+    user_input = yield ModifyEdgePortForm
+
+    class EdgePortLAGMember(LAGMember):
+        interface_name: (  # type: ignore[valid-type]
+            available_interfaces_choices_including_current_members(
+                subscription.edge_port.node.owner_subscription_id,
+                user_input.member_speed,
+                subscription.edge_port.edge_port_ae_members,
+            )
+            if user_input.member_speed == subscription.edge_port.member_speed
+            else (
+                available_interfaces_choices(subscription.edge_port.node.owner_subscription_id, user_input.member_speed)
+            )
+        )
+
+    lag_ae_members = Annotated[
+        list[EdgePortLAGMember],
+        AfterValidator(validate_unique_list),
+        Len(
+            min_length=user_input.number_of_members,
+            max_length=user_input.number_of_members,
+        ),
+    ]
+
+    current_lag_ae_members = (
+        [
+            EdgePortLAGMember(
+                interface_name=iface.interface_name,
+                interface_description=iface.interface_description,
+            )
+            for iface in subscription.edge_port.edge_port_ae_members
+        ]
+        if user_input.member_speed == subscription.edge_port.member_speed
+        else []
+    )
+
+    class ModifyEdgePortInterfaceForm(FormPage):
+        model_config = ConfigDict(title="Modify Edge Port Interface")
+
+        name: ReadOnlyField(subscription.edge_port.edge_port_name, default_type=str)  # type: ignore[valid-type]
+        description: str | None = subscription.edge_port.edge_port_description or None
+        ae_members: lag_ae_members = current_lag_ae_members
+
+    interface_form_input = yield ModifyEdgePortInterfaceForm
+
+    capacity_has_changed = (
+        user_input.member_speed != subscription.edge_port.member_speed
+        or user_input.number_of_members != len(subscription.edge_port.edge_port_ae_members)
+        or any(
+            old_interface.interface_name
+            not in [new_interface.interface_name for new_interface in interface_form_input.ae_members]
+            for old_interface in subscription.edge_port.edge_port_ae_members
+        )
+        or len(subscription.edge_port.edge_port_ae_members) != len(interface_form_input.ae_members)
+    )
+    return user_input.model_dump() | interface_form_input.model_dump() | {"capacity_has_changed": capacity_has_changed}
+
+
+@step("Modify edge port subscription.")
+def modify_edge_port_subscription(
+    subscription: EdgePort,
+    member_speed: PhysicalPortCapacity,
+    encapsulation: EncapsulationType,
+    minimum_links: int,
+    mac_address: str | None,
+    geant_ga_id: str | None,
+    enable_lacp: bool,  # noqa: FBT001
+    ae_members: list[dict[str, str]],
+    ignore_if_down: bool,  # noqa: FBT001
+    description: str | None = None,
+) -> State:
+    """Modify the edge port subscription with the given parameters."""
+    previous_ae_members = [
+        {
+            "interface_name": member.interface_name,
+            "interface_description": member.interface_description,
+        }
+        for member in subscription.edge_port.edge_port_ae_members
+    ]
+    removed_ae_members = [member for member in previous_ae_members if member not in ae_members]
+    subscription.edge_port.enable_lacp = enable_lacp
+    subscription.edge_port.member_speed = member_speed
+    subscription.edge_port.encapsulation = encapsulation
+    subscription.edge_port.minimum_links = minimum_links
+    subscription.edge_port.mac_address = mac_address
+    subscription.edge_port.ignore_if_down = ignore_if_down
+    subscription.edge_port.geant_ga_id = geant_ga_id
+    subscription.edge_port.edge_port_description = description
+    subscription.description = (
+        f"Edge Port {subscription.edge_port.edge_port_name} on"
+        f" {subscription.edge_port.node.router_fqdn},"
+        f" {get_partner_by_id(subscription.customer_id).name}, {geant_ga_id or ""}"
+    )
+    subscription.edge_port.edge_port_ae_members.clear()
+    partner_name = get_partner_by_id(subscription.customer_id).name
+    for member in ae_members:
+        subscription.edge_port.edge_port_ae_members.append(EdgePortAEMemberBlock.new(subscription_id=uuid4(), **member))
+
+    return {
+        "subscription": subscription,
+        "partner_name": partner_name,
+        "removed_ae_members": removed_ae_members,
+        "previous_ae_members": previous_ae_members,
+    }
+
+
+@step("Update interfaces in NetBox")
+def update_interfaces_in_netbox(
+    subscription: EdgePort, removed_ae_members: list[dict], previous_ae_members: list[dict]
+) -> State:
+    """Update the interfaces in NetBox."""
+    nbclient = NetboxClient()
+    # Free removed interfaces
+    for removed_member in removed_ae_members:
+        nbclient.free_interface(subscription.edge_port.node.router_fqdn, removed_member["interface_name"])
+    # Attach physical interfaces to :term:`LAG`
+    # Update interface description to subscription ID
+    # Reserve interfaces
+    for member in subscription.edge_port.edge_port_ae_members:
+        if any(prev_member["interface_name"] == member.interface_name for prev_member in previous_ae_members):
+            continue
+        nbclient.attach_interface_to_lag(
+            device_name=subscription.edge_port.node.router_fqdn,
+            lag_name=subscription.edge_port.edge_port_name,
+            iface_name=member.interface_name,
+            description=str(subscription.subscription_id),
+        )
+        nbclient.reserve_interface(subscription.edge_port.node.router_fqdn, member.interface_name)
+
+    return {"subscription": subscription}
+
+
+@step("[DRY RUN] Update edge port configuration.")
+def update_edge_port_dry(
+    subscription: dict[str, Any], process_id: UUIDstr, tt_number: str, removed_ae_members: list[dict], partner_name: str
+) -> LSOState:
+    """Perform a dry run of updating the edge port configuration."""
+    extra_vars = {
+        "subscription": subscription,
+        "partner_name": partner_name,
+        "dry_run": True,
+        "verb": "update",
+        "commit_comment": f"GSO_PROCESS_ID: {process_id} - TT_NUMBER: {tt_number} "
+        f"- Update Edge Port {subscription["edge_port"]["edge_port_name"]}"
+        f" on {subscription["edge_port"]["node"]["router_fqdn"]}",
+        "removed_ae_members": removed_ae_members,
+    }
+
+    return {
+        "playbook_name": "gap_ansible/playbooks/edge_port.yaml",
+        "inventory": {"all": {"hosts": {subscription["edge_port"]["node"]["router_fqdn"]: None}}},
+        "extra_vars": extra_vars,
+        "subscription": subscription,
+    }
+
+
+@step("[FOR REAL] Update edge port configuration.")
+def update_edge_port_real(
+    subscription: dict[str, Any], process_id: UUIDstr, tt_number: str, removed_ae_members: list[str], partner_name: str
+) -> LSOState:
+    """Update the edge port configuration."""
+    extra_vars = {
+        "subscription": subscription,
+        "partner_name": partner_name,
+        "dry_run": False,
+        "verb": "update",
+        "commit_comment": f"GSO_PROCESS_ID: {process_id} - TT_NUMBER: {tt_number} "
+        f"- Update Edge Port {subscription["edge_port"]["edge_port_name"]}"
+        f" on {subscription["edge_port"]["node"]["router_fqdn"]}",
+        "removed_ae_members": removed_ae_members,
+    }
+
+    return {
+        "subscription": subscription,
+        "playbook_name": "gap_ansible/playbooks/edge_port.yaml",
+        "inventory": {"all": {"hosts": {subscription["edge_port"]["node"]["router_fqdn"]: None}}},
+        "extra_vars": extra_vars,
+    }
+
+
+@step("Allocate/Deallocate interfaces in NetBox")
+def allocate_interfaces_in_netbox(subscription: EdgePort, previous_ae_members: list[dict]) -> None:
+    """Allocate the new interfaces in NetBox and detach the old ones from the :term:`LAG`."""
+    nbclient = NetboxClient()
+    for member in subscription.edge_port.edge_port_ae_members:
+        if any(member.interface_name == prev_member["interface_name"] for prev_member in previous_ae_members):
+            continue
+        nbclient.allocate_interface(
+            device_name=subscription.edge_port.node.router_fqdn,
+            iface_name=member.interface_name,
+        )
+
+    # detach the old interfaces from lag
+    nbclient.detach_interfaces_from_lag(
+        device_name=subscription.edge_port.node.router_fqdn, lag_name=subscription.edge_port.edge_port_name
+    )
+
+
+@workflow(
+    "Modify Edge Port",
+    initial_input_form=wrap_modify_initial_input_form(initial_input_form_generator),
+    target=Target.MODIFY,
+)
+def modify_edge_port() -> StepList:
+    """Modify a new edge port in the network.
+
+    * Modify the subscription object in the service database
+    * Modify configuration on the new edge port, first as a dry run
+    * Change :term:`LAG` and :term:`LAG` members in the Netbox.
+    """
+    capacity_has_changed = conditional(lambda state: state["capacity_has_changed"])
+    return (
+        begin
+        >> store_process_subscription(Target.MODIFY)
+        >> unsync
+        >> modify_edge_port_subscription
+        >> capacity_has_changed(update_interfaces_in_netbox)
+        >> capacity_has_changed(lso_interaction(update_edge_port_dry))
+        >> capacity_has_changed(lso_interaction(update_edge_port_real))
+        >> capacity_has_changed(allocate_interfaces_in_netbox)
+        >> resync
+        >> done
+    )
diff --git a/gso/workflows/edge_port/terminate_edge_port.py b/gso/workflows/edge_port/terminate_edge_port.py
new file mode 100644
index 0000000000000000000000000000000000000000..72d014c31d05b2bc3325650ff6acdea271eeffd7
--- /dev/null
+++ b/gso/workflows/edge_port/terminate_edge_port.py
@@ -0,0 +1,94 @@
+"""Terminate an edge port in the network."""
+
+from typing import Any
+
+from orchestrator import workflow
+from orchestrator.forms import FormPage
+from orchestrator.targets import Target
+from orchestrator.types import SubscriptionLifecycle
+from orchestrator.workflow import StepList, begin, done, step
+from orchestrator.workflows.steps import resync, set_status, store_process_subscription, unsync
+from orchestrator.workflows.utils import wrap_modify_initial_input_form
+from pydantic_forms.types import FormGenerator, UUIDstr
+
+from gso.products.product_types.edge_port import EdgePort
+from gso.services.lso_client import LSOState, lso_interaction
+from gso.services.netbox_client import NetboxClient
+from gso.utils.types.tt_number import TTNumber
+
+
+def initial_input_form_generator() -> FormGenerator:
+    """Let the operator decide whether to delete configuration on the router, and clear up :term:`IPAM` resources."""
+
+    class TerminateForm(FormPage):
+        tt_number: TTNumber
+
+    user_input = yield TerminateForm
+    return user_input.model_dump()
+
+
+@step("[DRY RUN] Remove Edge Port")
+def remove_edge_port_dry(subscription: dict[str, Any], tt_number: str, process_id: UUIDstr) -> dict[str, Any]:
+    """Remove an edge port from the network."""
+    extra_vars = {
+        "subscription": subscription,
+        "dry_run": True,
+        "verb": "terminate",
+        "commit_comment": f"GSO_PROCESS_ID: {process_id} - TT_NUMBER: {tt_number} - Delete Edge Port",
+    }
+
+    return {
+        "subscription": subscription,
+        "playbook_name": "gap_ansible/playbooks/edge_port.yaml",
+        "inventory": {"all": {"hosts": {subscription["edge_port"]["node"]["router_fqdn"]: None}}},
+        "extra_vars": extra_vars,
+    }
+
+
+@step("[FOR REAL] Remove Edge Port")
+def remove_edge_port_real(subscription: dict[str, Any], tt_number: str, process_id: UUIDstr) -> LSOState:
+    """Remove an edge port from the network."""
+    extra_vars = {
+        "subscription": subscription,
+        "dry_run": False,
+        "verb": "terminate",
+        "commit_comment": f"GSO_PROCESS_ID: {process_id} - TT_NUMBER: {tt_number} - Delete Edge Port",
+    }
+
+    return {
+        "subscription": subscription,
+        "playbook_name": "gap_ansible/playbooks/edge_port.yaml",
+        "inventory": {"all": {"hosts": {subscription["edge_port"]["node"]["router_fqdn"]: None}}},
+        "extra_vars": extra_vars,
+    }
+
+
+@step("Netbox Clean Up")
+def netbox_clean_up(subscription: EdgePort) -> None:
+    """Update Netbox to remove the edge port :term:`LAG` interface and all the :term:`LAG` members."""
+    nbclient = NetboxClient()
+
+    for member in subscription.edge_port.edge_port_ae_members:
+        nbclient.free_interface(subscription.edge_port.node.router_fqdn, member.interface_name)
+
+    nbclient.delete_interface(subscription.edge_port.node.router_fqdn, subscription.edge_port.edge_port_name)
+
+
+@workflow(
+    "Terminate Edge Port",
+    initial_input_form=wrap_modify_initial_input_form(initial_input_form_generator),
+    target=Target.TERMINATE,
+)
+def terminate_edge_port() -> StepList:
+    """Terminate a new edge port in the network."""
+    return (
+        begin
+        >> store_process_subscription(Target.TERMINATE)
+        >> unsync
+        >> lso_interaction(remove_edge_port_dry)
+        >> lso_interaction(remove_edge_port_real)
+        >> netbox_clean_up
+        >> set_status(SubscriptionLifecycle.TERMINATED)
+        >> resync
+        >> done
+    )
diff --git a/gso/workflows/edge_port/validate_edge_port.py b/gso/workflows/edge_port/validate_edge_port.py
new file mode 100644
index 0000000000000000000000000000000000000000..af82afee1bb394e836ba5cc71891c4840881c5c0
--- /dev/null
+++ b/gso/workflows/edge_port/validate_edge_port.py
@@ -0,0 +1,93 @@
+"""Workflow for validating an existing Edge port subscription."""
+
+from typing import Any
+
+from orchestrator.targets import Target
+from orchestrator.types import State, UUIDstr
+from orchestrator.utils.errors import ProcessFailureError
+from orchestrator.workflow import StepList, begin, done, step, workflow
+from orchestrator.workflows.steps import resync, store_process_subscription
+from orchestrator.workflows.utils import wrap_modify_initial_input_form
+
+from gso.products.product_types.edge_port import EdgePort
+from gso.services.lso_client import LSOState, anonymous_lso_interaction
+from gso.services.netbox_client import NetboxClient
+from gso.services.partners import get_partner_by_id
+
+
+@step("Prepare required keys in state")
+def prepare_state(subscription_id: UUIDstr) -> State:
+    """Add required keys to the state for the workflow to run successfully."""
+    edge_port = EdgePort.from_subscription(subscription_id)
+
+    return {"subscription": edge_port}
+
+
+@step("Verify NetBox entries")
+def verify_netbox_entries(subscription: EdgePort) -> None:
+    """Validate required entries for an edge port in NetBox."""
+    nbclient = NetboxClient()
+    netbox_errors = []
+
+    #  Raises en exception when not found.
+    lag = nbclient.get_interface_by_name_and_device(
+        subscription.edge_port.edge_port_name, subscription.edge_port.node.router_fqdn
+    )
+    if lag.description != str(subscription.subscription_id):
+        netbox_errors.append(
+            f"Incorrect description for '{lag}', expected "
+            f"'{subscription.subscription_id}' but got '{lag.description}'"
+        )
+    if not lag.enabled:
+        netbox_errors.append(f"NetBox interface '{lag}' is not enabled.")
+    for member in subscription.edge_port.edge_port_ae_members:
+        interface = nbclient.get_interface_by_name_and_device(
+            member.interface_name, subscription.edge_port.node.router_fqdn
+        )
+        if interface.description != str(subscription.subscription_id):
+            netbox_errors.append(
+                f"Incorrect description for '{member.interface_name}', expected "
+                f"'{subscription.subscription_id}' but got '{interface.description}'"
+            )
+        if not interface.enabled:
+            netbox_errors.append(f"NetBox interface '{member.interface_name}' is not enabled.")
+
+    if netbox_errors:
+        raise ProcessFailureError(message="NetBox misconfiguration(s) found", details=str(netbox_errors))
+
+
+@step("Check base config for drift")
+def verify_base_config(subscription: dict[str, Any]) -> LSOState:
+    """Workflow step for running a playbook that checks whether base config has drifted."""
+    partner_name = get_partner_by_id(subscription["customer_id"]).name
+    return {
+        "playbook_name": "gap_ansible/playbooks/edge_port.yaml",
+        "inventory": {"all": {"hosts": {subscription["edge_port"]["node"]["router_fqdn"]: None}}},
+        "extra_vars": {
+            "dry_run": True,
+            "subscription": subscription,
+            "partner_name": partner_name,
+            "verb": "create",
+            "is_verification_workflow": "true",
+        },
+    }
+
+
+@workflow(
+    "Validate Edge Port Configuration", target=Target.SYSTEM, initial_input_form=wrap_modify_initial_input_form(None)
+)
+def validate_edge_port() -> StepList:
+    """Validate an existing, active Edge port subscription.
+
+    * Check correct configuration of interfaces in NetBox.
+    * Verify create Edge port configuration.
+    """
+    return (
+        begin
+        >> store_process_subscription(Target.SYSTEM)
+        >> prepare_state
+        >> verify_netbox_entries
+        >> anonymous_lso_interaction(verify_base_config)
+        >> resync
+        >> done
+    )
diff --git a/gso/workflows/iptrunk/create_iptrunk.py b/gso/workflows/iptrunk/create_iptrunk.py
index e10401186d3731923dc92d398bc93a0d617465fb..9074af7c3ccfd6ab3777c021a92dbce128c144a3 100644
--- a/gso/workflows/iptrunk/create_iptrunk.py
+++ b/gso/workflows/iptrunk/create_iptrunk.py
@@ -45,6 +45,7 @@ from gso.utils.types.interfaces import JuniperLAGMember, LAGMember, LAGMemberLis
 from gso.utils.types.netbox_router import NetboxEnabledRouter
 from gso.utils.types.tt_number import TTNumber
 from gso.utils.workflow_steps import prompt_sharepoint_checklist_url
+from gso.workflows.shared import create_summary_form
 
 
 def initial_input_form_generator(product_name: str) -> FormGenerator:
@@ -160,8 +161,7 @@ def initial_input_form_generator(product_name: str) -> FormGenerator:
         side_b_ae_members: ae_members_side_b
 
     user_input_side_b = yield CreateIptrunkSideBForm
-
-    return (
+    input_forms_data = (
         initial_user_input.model_dump()
         | verify_minimum_links.model_dump()
         | user_input_router_side_a.model_dump()
@@ -169,6 +169,25 @@ def initial_input_form_generator(product_name: str) -> FormGenerator:
         | user_input_router_side_b.model_dump()
         | user_input_side_b.model_dump()
     )
+    summary_form_data = input_forms_data | {"side_a_node": router_a_fqdn, "side_b_node": router_b_fqdn}
+    summary_fields = [
+        "geant_s_sid",
+        "iptrunk_type",
+        "iptrunk_speed",
+        "iptrunk_description",
+        "iptrunk_minimum_links",
+        "side_a_node",
+        "side_a_ae_iface",
+        "side_a_ae_members",
+        "side_a_ae_geant_a_sid",
+        "side_b_node",
+        "side_b_ae_iface",
+        "side_b_ae_members",
+        "side_b_ae_geant_a_sid",
+    ]
+    yield from create_summary_form(summary_form_data, product_name, summary_fields)
+
+    return input_forms_data
 
 
 @step("Create subscription")
@@ -255,7 +274,7 @@ def dig_all_hosts_v6(new_ipv6_network: str) -> None:
 
 @step("Ping all hosts in the assigned IPv4 network")
 def ping_all_hosts_v4(new_ipv4_network: str) -> None:
-    """Ping all hosts in the IPv4 network to verify they're not in use."""
+    """Ping all hosts in the IPv4 network to verify they are not in use."""
     unavailable_hosts = [host for host in IPv4Network(new_ipv4_network) if ping(str(host), timeout=1)]
 
     if unavailable_hosts:
@@ -265,7 +284,7 @@ def ping_all_hosts_v4(new_ipv4_network: str) -> None:
 
 @step("Ping all hosts in the assigned IPv6 network")
 def ping_all_hosts_v6(new_ipv6_network: str) -> State:
-    """Ping all hosts in the IPv6 network to verify they're not in use."""
+    """Ping all hosts in the IPv6 network to verify they are not in use."""
     unavailable_hosts = [host for host in IPv6Network(new_ipv6_network) if ping(str(host), timeout=1)]
 
     if unavailable_hosts:
diff --git a/gso/workflows/iptrunk/migrate_iptrunk.py b/gso/workflows/iptrunk/migrate_iptrunk.py
index 5a7cafb03dad77270754cdb9da8a1c57ba84182e..43b43e8c454a2dba2b419149f3dceffe59974025 100644
--- a/gso/workflows/iptrunk/migrate_iptrunk.py
+++ b/gso/workflows/iptrunk/migrate_iptrunk.py
@@ -89,7 +89,7 @@ def initial_input_form_generator(subscription_id: UUIDstr) -> FormGenerator:
             ):
                 #  We want to stay on the same site, so all routers that are in different sites get skipped.
                 continue
-            #  If migrate_to_different_site is true, we can add ALL routers to the result map
+            #  If migrate_to_different_site is true, we can add *all* routers to the result map
             routers[str(router_id)] = router["description"]
 
     new_router_enum = Choice("Select a new router", zip(routers.keys(), routers.items(), strict=True))  # type: ignore[arg-type]
@@ -204,7 +204,7 @@ def calculate_old_side_data(subscription: Iptrunk, replace_index: int) -> State:
 
 @step("Check Optical PRE levels on the trunk endpoint")
 def check_ip_trunk_optical_levels_pre(subscription: Iptrunk) -> LSOState:
-    """Check Optical PRE levels on the trunk."""
+    """Check Optical levels on the trunk before migration."""
     extra_vars = {"wfo_ip_trunk_json": json.loads(json_dumps(subscription)), "check": "optical_pre"}
 
     return {
@@ -252,7 +252,7 @@ def check_ip_trunk_optical_levels_post(
 def check_ip_trunk_lldp(
     subscription: Iptrunk, new_node: Router, new_lag_member_interfaces: list[dict], replace_index: int
 ) -> LSOState:
-    """Check LLDP on the new trunk endpoints."""
+    """Check :term:`LLDP` on the new trunk endpoints."""
     extra_vars = {
         "wfo_ip_trunk_json": json.loads(json_dumps(subscription)),
         "new_node": json.loads(json_dumps(new_node)),
@@ -489,7 +489,7 @@ def update_remaining_side_bfd_real(
 
 @step("Check BFD session over trunk")
 def check_ip_trunk_bfd(subscription: Iptrunk, new_node: Router, replace_index: int) -> LSOState:
-    """Check BFD session across the new trunk."""
+    """Check :term:`BFD` session across the new trunk."""
     extra_vars = {
         "wfo_ip_trunk_json": json.loads(json_dumps(subscription)),
         "new_node": json.loads(json_dumps(new_node)),
@@ -830,7 +830,7 @@ def migrate_iptrunk() -> StepList:
     * Deploy a new :term:`ISIS` interface between routers A and C
     * Wait for operator confirmation that :term:`ISIS` is behaving as expected
     * Restore the old :term:`ISIS` metric on the new trunk
-    * Delete the old, disabled configuration on the routers, first as a dry run
+    * Delete the old configuration from the routers, first as a dry run
     * Reflect the changes made in :term:`IPAM`
     * Update the subscription model in the database
     * Update the reserved interfaces in Netbox
diff --git a/gso/workflows/iptrunk/modify_isis_metric.py b/gso/workflows/iptrunk/modify_isis_metric.py
index 7d77510728354e40b9cd8a1424672a9fc499d4b9..45865e39b68f1263f9058edab376e94319ac2830 100644
--- a/gso/workflows/iptrunk/modify_isis_metric.py
+++ b/gso/workflows/iptrunk/modify_isis_metric.py
@@ -13,6 +13,7 @@ from orchestrator.workflows.utils import wrap_modify_initial_input_form
 from gso.products.product_types.iptrunk import Iptrunk
 from gso.services.lso_client import LSOState, lso_interaction
 from gso.utils.types.tt_number import TTNumber
+from gso.workflows.shared import modify_summary_form
 
 
 def initial_input_form_generator(subscription_id: UUIDstr) -> FormGenerator:
@@ -24,8 +25,11 @@ def initial_input_form_generator(subscription_id: UUIDstr) -> FormGenerator:
         isis_metric: int = subscription.iptrunk.iptrunk_isis_metric
 
     user_input = yield ModifyIptrunkForm
-
-    return user_input.model_dump()
+    user_input = user_input.model_dump()
+    yield from modify_summary_form(
+        {"iptrunk_isis_metric": user_input["isis_metric"]}, subscription.iptrunk, ["iptrunk_isis_metric"]
+    )
+    return user_input
 
 
 @step("Update subscription")
diff --git a/gso/workflows/iptrunk/modify_trunk_interface.py b/gso/workflows/iptrunk/modify_trunk_interface.py
index 16de246238a40f40341cf2911b59471cb956df8b..3537f64b30142892b977e87eddc7ac9efa0a718c 100644
--- a/gso/workflows/iptrunk/modify_trunk_interface.py
+++ b/gso/workflows/iptrunk/modify_trunk_interface.py
@@ -205,7 +205,7 @@ def check_ip_trunk_connectivity(subscription: Iptrunk) -> LSOState:
 
 @step("Check LLDP on the trunk endpoints")
 def check_ip_trunk_lldp(subscription: Iptrunk) -> LSOState:
-    """Check LLDP on trunk endpoints."""
+    """Check :term:`LLDP` on trunk endpoints."""
     extra_vars = {"wfo_ip_trunk_json": json.loads(json_dumps(subscription)), "check": "lldp"}
 
     return {
@@ -321,7 +321,7 @@ def provision_ip_trunk_iface_dry(
     }
 
     return {
-        "playbook_name": "gap_ansible/playbooks/gap_ansible/iptrunks.yaml",
+        "playbook_name": "gap_ansible/playbooks/iptrunks.yaml",
         "inventory": {
             "all": {
                 "hosts": {
diff --git a/gso/workflows/iptrunk/validate_iptrunk.py b/gso/workflows/iptrunk/validate_iptrunk.py
index 4ca96f8b6ad70a7e6201174827632b4bad12b0d3..f66612f56c4fcfae5676b13c24f4b755d6b5965b 100644
--- a/gso/workflows/iptrunk/validate_iptrunk.py
+++ b/gso/workflows/iptrunk/validate_iptrunk.py
@@ -210,7 +210,7 @@ def validate_iptrunk() -> StepList:
     * Verify that the :term:`LAG` interfaces are correctly configured in :term:`IPAM`.
     * Check correct configuration of interfaces in NetBox.
     * Verify the configuration on both sides of the trunk is intact.
-    * Check the ISIS metric of the trunk.
+    * Check the :term:`ISIS` metric of the trunk.
     * Verify that TWAMP configuration is correct.
 
     If a trunk has a Juniper router on both sides, it is considered legacy and does not require validation.
diff --git a/gso/workflows/nren_l3_core_service/__init__.py b/gso/workflows/nren_l3_core_service/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..fb652fbe787e3e976db91a9c0cdefa7c897b61f2
--- /dev/null
+++ b/gso/workflows/nren_l3_core_service/__init__.py
@@ -0,0 +1 @@
+""":term:`NREN` layer 3 core service workflows."""
diff --git a/gso/workflows/nren_l3_core_service/create_imported_nren_l3_core_service.py b/gso/workflows/nren_l3_core_service/create_imported_nren_l3_core_service.py
new file mode 100644
index 0000000000000000000000000000000000000000..f5a2e2ea00588c4ea9893f71bbb5772c26c7f0cf
--- /dev/null
+++ b/gso/workflows/nren_l3_core_service/create_imported_nren_l3_core_service.py
@@ -0,0 +1,123 @@
+"""A creation workflow for adding an existing NREN L3 Core Service to the service database."""
+
+from uuid import uuid4
+
+from orchestrator import workflow
+from orchestrator.forms import FormPage
+from orchestrator.targets import Target
+from orchestrator.types import FormGenerator, SubscriptionLifecycle
+from orchestrator.workflow import StepList, begin, done, step
+from orchestrator.workflows.steps import resync, set_status, store_process_subscription
+from pydantic import BaseModel
+from pydantic_forms.types import UUIDstr
+
+from gso.products import ProductName
+from gso.products.product_blocks.bgp_session import BGPSession, IPFamily
+from gso.products.product_blocks.nren_l3_core_service import NRENAccessPortInactive
+from gso.products.product_blocks.service_binding_port import VLAN_ID, ServiceBindingPortInactive
+from gso.products.product_types.edge_port import EdgePort
+from gso.products.product_types.nren_l3_core_service import ImportedNRENL3CoreServiceInactive, NRENL3CoreServiceType
+from gso.services.partners import get_partner_by_name
+from gso.services.subscriptions import get_product_id_by_name
+from gso.utils.shared_enums import SBPType
+from gso.utils.types.ip_address import IPAddress, IPv4AddressType, IPV4Netmask, IPv6AddressType, IPV6Netmask
+
+
+def initial_input_form_generator() -> FormGenerator:
+    """Take all information passed to this workflow by the :term:`API` endpoint that was called."""
+
+    class BaseBGPPeer(BaseModel):
+        bfd_enabled: bool = False
+        bfd_interval: int | None = None
+        bfd_multiplier: int | None = None
+        has_custom_policies: bool = False
+        authentication_key: str
+        multipath_enabled: bool = False
+        send_default_route: bool = False
+        is_passive: bool = False
+        peer_address: IPAddress
+        families: list[IPFamily]
+        is_multi_hop: bool
+        rtbh_enabled: bool
+
+    class ServiceBindingPort(BaseModel):
+        edge_port: UUIDstr
+        ap_type: str
+        geant_sid: str
+        sbp_type: SBPType = SBPType.L3
+        is_tagged: bool = False
+        vlan_id: VLAN_ID
+        custom_firewall_filters: bool = False
+        ipv4_address: IPv4AddressType
+        ipv4_mask: IPV4Netmask
+        ipv6_address: IPv6AddressType
+        ipv6_mask: IPV6Netmask
+        rtbh_enabled: bool = True
+        is_multi_hop: bool = True
+        bgp_peers: list[BaseBGPPeer]
+
+    class ImportNRENL3CoreServiceForm(FormPage):
+        partner: str
+        service_binding_ports: list[ServiceBindingPort]
+        service_type: NRENL3CoreServiceType
+
+    user_input = yield ImportNRENL3CoreServiceForm
+
+    return user_input.model_dump()
+
+
+@step("Create subscription")
+def create_subscription(partner: str, service_type: NRENL3CoreServiceType) -> dict:
+    """Create a new subscription object in the database."""
+    partner_id = get_partner_by_name(partner)["partner_id"]
+    if service_type == NRENL3CoreServiceType.GEANT_IP:
+        product_id = get_product_id_by_name(ProductName.IMPORTED_GEANT_IP)
+    elif service_type == NRENL3CoreServiceType.IAS:
+        product_id = get_product_id_by_name(ProductName.IMPORTED_IAS)
+    subscription = ImportedNRENL3CoreServiceInactive.from_product_id(product_id, partner_id)
+    return {"subscription": subscription, "subscription_id": subscription.subscription_id}
+
+
+@step("Initialize subscription")
+def initialize_subscription(subscription: ImportedNRENL3CoreServiceInactive, service_binding_ports: list) -> dict:
+    """Initialize the subscription with the user input."""
+    for service_binding_port in service_binding_ports:
+        edge_port_subscription = EdgePort.from_subscription(service_binding_port.pop("edge_port"))
+        bgp_peers = service_binding_port.pop("bgp_peers")
+        sbp_bgp_session_list = [BGPSession.new(subscription_id=uuid4(), **session) for session in bgp_peers]
+
+        service_binding_port_subscription = ServiceBindingPortInactive.new(
+            subscription_id=uuid4(),
+            edge_port=edge_port_subscription.edge_port,
+            sbp_bgp_session_list=sbp_bgp_session_list,
+            **service_binding_port,
+        )
+        subscription.nren_l3_core_service.nren_ap_list.append(
+            NRENAccessPortInactive.new(
+                subscription_id=uuid4(),
+                ap_type=service_binding_port["ap_type"],
+                sbp=service_binding_port_subscription,
+            )
+        )
+
+    subscription.description = f"{subscription.product} service"
+
+    return {"subscription": subscription}
+
+
+@workflow(
+    "Create imported NREN L3 Core Service",
+    initial_input_form=initial_input_form_generator,
+    target=Target.CREATE,
+)
+def create_imported_nren_l3_core_service() -> StepList:
+    """Import a GÉANT IP without provisioning it."""
+    return (
+        begin
+        >> create_subscription
+        >> store_process_subscription(Target.CREATE)
+        >> initialize_subscription
+        >> set_status(SubscriptionLifecycle.ACTIVE)
+        >> resync
+        >> done
+    )
diff --git a/gso/workflows/nren_l3_core_service/create_nren_l3_core_service.py b/gso/workflows/nren_l3_core_service/create_nren_l3_core_service.py
new file mode 100644
index 0000000000000000000000000000000000000000..2ac1708d525591a0aa0d9a19b68b5ef89ef9a064
--- /dev/null
+++ b/gso/workflows/nren_l3_core_service/create_nren_l3_core_service.py
@@ -0,0 +1,316 @@
+"""Create a new NREN L3 Core Service subscription including GÉANT IP and IAS."""
+
+from typing import Annotated, Any
+from uuid import uuid4
+
+from orchestrator.forms import FormPage
+from orchestrator.forms.validators import Label
+from orchestrator.targets import Target
+from orchestrator.types import FormGenerator, State, SubscriptionLifecycle, UUIDstr
+from orchestrator.workflow import StepList, begin, done, step, workflow
+from orchestrator.workflows.steps import resync, set_status, store_process_subscription
+from orchestrator.workflows.utils import wrap_create_initial_input_form
+from pydantic import AfterValidator, BaseModel, ConfigDict, Field, computed_field
+from pydantic_forms.validators import Divider
+
+from gso.products.product_blocks.bgp_session import BGPSession, IPFamily
+from gso.products.product_blocks.nren_l3_core_service import NRENAccessPortInactive
+from gso.products.product_blocks.service_binding_port import VLAN_ID, ServiceBindingPortInactive
+from gso.products.product_types.edge_port import EdgePort
+from gso.products.product_types.nren_l3_core_service import NRENL3CoreService, NRENL3CoreServiceInactive
+from gso.services.lso_client import LSOState, lso_interaction
+from gso.utils.helpers import (
+    active_edge_port_selector,
+    partner_choice,
+)
+from gso.utils.shared_enums import APType, SBPType
+from gso.utils.types.ip_address import IPv4AddressType, IPV4Netmask, IPv6AddressType, IPV6Netmask
+from gso.utils.types.tt_number import TTNumber
+
+
+def initial_input_form_generator(product_name: str) -> FormGenerator:
+    """Gather input from the operator to build a new subscription object."""
+
+    class CreateNRENCoreServiceForm(FormPage):
+        model_config = ConfigDict(title=f"{product_name} - Select partner")
+
+        tt_number: TTNumber
+        partner: partner_choice()  # type: ignore[valid-type]
+
+    initial_user_input = yield CreateNRENCoreServiceForm
+
+    class EdgePortSelection(BaseModel):
+        edge_port: active_edge_port_selector(partner_id=initial_user_input.partner)  # type: ignore[valid-type]
+        ap_type: APType
+
+    def validate_edge_ports_are_unique(edge_ports: list[EdgePortSelection]) -> list[EdgePortSelection]:
+        """Verify if interfaces are unique."""
+        port_names = [port.edge_port for port in edge_ports]
+        if len(port_names) != len(set(port_names)):
+            msg = "Edge Ports must be unique."
+            raise ValueError(msg)
+        return edge_ports
+
+    class EdgePortSelectionForm(FormPage):
+        model_config = ConfigDict(title=f"{product_name} - Select Edge Ports")
+        info_label: Label = Field(
+            f"Please select the Edge Ports where this {product_name} service will terminate", exclude=True
+        )
+
+        edge_ports: Annotated[list[EdgePortSelection], AfterValidator(validate_edge_ports_are_unique)]
+
+    selected_edge_ports = yield EdgePortSelectionForm
+    ep_list = selected_edge_ports.edge_ports
+
+    class BaseBGPPeer(BaseModel):
+        bfd_enabled: bool = False
+        bfd_interval: int | None = None
+        bfd_multiplier: int | None = None
+        has_custom_policies: bool = False
+        authentication_key: str
+        multipath_enabled: bool = False
+        send_default_route: bool = False
+        is_passive: bool = False
+
+    class IPv4BGPPeer(BaseBGPPeer):
+        peer_address: IPv4AddressType
+        add_v4_multicast: bool = Field(default=False, exclude=True)
+
+        @computed_field  # type: ignore[misc]
+        @property
+        def families(self) -> list[IPFamily]:
+            return [IPFamily.V4UNICAST, IPFamily.V4MULTICAST] if self.add_v4_multicast else [IPFamily.V4UNICAST]
+
+    class IPv6BGPPeer(BaseBGPPeer):
+        peer_address: IPv6AddressType
+        add_v6_multicast: bool = Field(default=False, exclude=True)
+
+        @computed_field  # type: ignore[misc]
+        @property
+        def families(self) -> list[IPFamily]:
+            return [IPFamily.V6UNICAST, IPFamily.V6MULTICAST] if self.add_v6_multicast else [IPFamily.V6UNICAST]
+
+    binding_port_inputs = []
+    for ep_index, edge_port in enumerate(ep_list):
+
+        class BindingPortsInputForm(FormPage):
+            model_config = ConfigDict(title=f"{product_name} - Configure Edge Ports ({ep_index + 1}/{len(ep_list)})")
+            info_label: Label = Field("Please configure the Service Binding Ports for each Edge Port.", exclude=True)
+            current_ep_label: Label = Field(
+                f"Currently configuring on {EdgePort.from_subscription(edge_port.edge_port).description} "
+                f"(Access Port type: {edge_port.ap_type})",
+                exclude=True,
+            )
+
+            geant_sid: str
+            is_tagged: bool = False
+            vlan_id: VLAN_ID
+            ipv4_address: IPv4AddressType
+            ipv4_mask: IPV4Netmask
+            ipv6_address: IPv6AddressType
+            ipv6_mask: IPV6Netmask
+            custom_firewall_filters: bool = False
+            divider: Divider = Field(None, exclude=True)
+            v4_bgp_peer: IPv4BGPPeer
+            v6_bgp_peer: IPv6BGPPeer
+
+        binding_port_input_form = yield BindingPortsInputForm
+        binding_port_inputs.append(
+            binding_port_input_form.model_dump()
+            | {
+                "bgp_peers": [
+                    binding_port_input_form.v4_bgp_peer.model_dump(),
+                    binding_port_input_form.v6_bgp_peer.model_dump(),
+                ]
+            }
+        )
+
+    return (
+        initial_user_input.model_dump()
+        | selected_edge_ports.model_dump()
+        | {"binding_port_inputs": binding_port_inputs, "product_name": product_name}
+    )
+
+
+@step("Create subscription")
+def create_subscription(product: UUIDstr, partner: str) -> State:
+    """Create a new subscription object in the database."""
+    subscription = NRENL3CoreServiceInactive.from_product_id(product, partner)
+
+    return {"subscription": subscription, "subscription_id": subscription.subscription_id}
+
+
+@step("Initialize subscription")
+def initialize_subscription(
+    subscription: NRENL3CoreServiceInactive, edge_ports: list[dict], binding_port_inputs: list[dict], product_name: str
+) -> State:
+    """Take all user inputs and use them to populate the subscription model."""
+    edge_port_fqdn_list = []
+    for edge_port_input, sbp_input in zip(edge_ports, binding_port_inputs, strict=False):
+        edge_port_subscription = EdgePort.from_subscription(edge_port_input["edge_port"])
+        sbp_bgp_session_list = [
+            BGPSession.new(subscription_id=uuid4(), **session, rtbh_enabled=True, is_multi_hop=True)
+            for session in sbp_input["bgp_peers"]
+        ]
+        service_binding_port = ServiceBindingPortInactive.new(
+            subscription_id=uuid4(),
+            **sbp_input,
+            bgp_session_list=sbp_bgp_session_list,
+            sbp_type=SBPType.L3,
+            edge_port=edge_port_subscription.edge_port,
+        )
+        subscription.nren_l3_core_service.nren_ap_list.append(
+            NRENAccessPortInactive.new(
+                subscription_id=uuid4(),
+                ap_type=edge_port_input["ap_type"],
+                sbp=service_binding_port,
+            )
+        )
+        edge_port_fqdn_list.append(edge_port_subscription.edge_port.node.router_fqdn)
+
+    subscription.description = f"{product_name} service"
+
+    return {"subscription": subscription, "edge_port_fqdn_list": edge_port_fqdn_list}
+
+
+@step("[DRY RUN] Deploy service binding port")
+def provision_sbp_dry(
+    subscription: dict[str, Any], process_id: UUIDstr, tt_number: str, edge_port_fqdn_list: list[str]
+) -> LSOState:
+    """Perform a dry run of deploying Service Binding Ports."""
+    extra_vars = {
+        "subscription": subscription,
+        "dry_run": True,
+        "verb": "deploy",
+        "commit_comment": f"GSO_PROCESS_ID: {process_id} - TT_NUMBER: {tt_number} - "
+        f"Deploy config for {subscription["description"]}",
+    }
+
+    return {
+        "playbook_name": "manage_sbp.yaml",
+        "inventory": {"all": {"hosts": dict.fromkeys(edge_port_fqdn_list)}},
+        "extra_vars": extra_vars,
+    }
+
+
+@step("[FOR REAL] Deploy service binding port")
+def provision_sbp_real(
+    subscription: dict[str, Any], process_id: UUIDstr, tt_number: str, edge_port_fqdn_list: list[str]
+) -> LSOState:
+    """Deploy Service Binding Ports."""
+    extra_vars = {
+        "subscription": subscription,
+        "dry_run": False,
+        "verb": "deploy",
+        "commit_comment": f"GSO_PROCESS_ID: {process_id} - TT_NUMBER: {tt_number} - "
+        f"Deploy config for {subscription["description"]}",
+    }
+
+    return {
+        "playbook_name": "manage_sbp.yaml",
+        "inventory": {"all": {"hosts": dict.fromkeys(edge_port_fqdn_list)}},
+        "extra_vars": extra_vars,
+    }
+
+
+@step("Check service binding port functionality")
+def check_sbp_functionality(subscription: dict[str, Any], edge_port_fqdn_list: list[str]) -> LSOState:
+    """Check functionality of deployed Service Binding Ports."""
+    extra_vars = {"subscription": subscription, "verb": "check"}
+
+    return {
+        "playbook_name": "manage_sbp.yaml",
+        "inventory": {"all": {"hosts": dict.fromkeys(edge_port_fqdn_list)}},
+        "extra_vars": extra_vars,
+    }
+
+
+@step("[DRY RUN] Deploy BGP peers")
+def deploy_bgp_peers_dry(
+    subscription: dict[str, Any], edge_port_fqdn_list: list[str], tt_number: str, process_id: UUIDstr
+) -> LSOState:
+    """Perform a dry run of deploying :term:`BGP` peers."""
+    extra_vars = {
+        "subscription": subscription,
+        "verb": "deploy",
+        "dry_run": True,
+        "commit_comment": f"GSO_PROCESS_ID: {process_id} - TT_NUMBER: {tt_number} - "
+        f"Deploying BGP peers for {subscription["description"]}",
+    }
+
+    return {
+        "playbook_name": "manage_sbp.yaml",
+        "inventory": {"all": {"hosts": dict.fromkeys(edge_port_fqdn_list)}},
+        "extra_vars": extra_vars,
+    }
+
+
+@step("[FOR REAL] Deploy BGP peers")
+def deploy_bgp_peers_real(
+    subscription: dict[str, Any], edge_port_fqdn_list: list[str], tt_number: str, process_id: UUIDstr
+) -> LSOState:
+    """Deploy :term:`BGP` peers."""
+    extra_vars = {
+        "subscription": subscription,
+        "verb": "deploy",
+        "dry_run": False,
+        "commit_comment": f"GSO_PROCESS_ID: {process_id} - TT_NUMBER: {tt_number} - "
+        f"Deploying BGP peers for {subscription["description"]}",
+    }
+
+    return {
+        "playbook_name": "manage_sbp.yaml",
+        "inventory": {"all": {"hosts": dict.fromkeys(edge_port_fqdn_list)}},
+        "extra_vars": extra_vars,
+    }
+
+
+@step("Check BGP peers")
+def check_bgp_peers(subscription: dict[str, Any], edge_port_fqdn_list: list[str]) -> LSOState:
+    """Check correct deployment of :term:`BGP` peers."""
+    extra_vars = {"subscription": subscription, "verb": "check"}
+
+    return {
+        "playbook_name": "manage_sbp.yaml",
+        "inventory": {"all": {"hosts": dict.fromkeys(edge_port_fqdn_list)}},
+        "extra_vars": extra_vars,
+    }
+
+
+@step("Update Infoblox")
+def update_dns_records(subscription: NRENL3CoreService) -> State:
+    """Update :term:`DNS` records in Infoblox."""
+    #  TODO: implement
+    return {"subscription": subscription}
+
+
+@workflow(
+    "Create NREN L3 Core Service",
+    initial_input_form=wrap_create_initial_input_form(initial_input_form_generator),
+    target=Target.CREATE,
+)
+def create_nren_l3_core_service() -> StepList:
+    """Create a new :term:`NREN` L3 Core Service subscription including GÉANT IP and IAS.
+
+    * Create subscription object in the service database
+    * Deploy service binding ports
+    * Deploy :term:`BGP` peers
+    * Update :term:`DNS` records
+    * Set the subscription in a provisioning state in the database
+    """
+    return (
+        begin
+        >> create_subscription
+        >> store_process_subscription(Target.CREATE)
+        >> initialize_subscription
+        >> lso_interaction(provision_sbp_dry)
+        >> lso_interaction(provision_sbp_real)
+        >> lso_interaction(check_sbp_functionality)
+        >> lso_interaction(deploy_bgp_peers_dry)
+        >> lso_interaction(deploy_bgp_peers_real)
+        >> lso_interaction(check_bgp_peers)
+        >> update_dns_records
+        >> set_status(SubscriptionLifecycle.ACTIVE)
+        >> resync
+        >> done
+    )
diff --git a/gso/workflows/nren_l3_core_service/import_nren_l3_core_service.py b/gso/workflows/nren_l3_core_service/import_nren_l3_core_service.py
new file mode 100644
index 0000000000000000000000000000000000000000..3951924612f0c885f1076193d75d208b687e4821
--- /dev/null
+++ b/gso/workflows/nren_l3_core_service/import_nren_l3_core_service.py
@@ -0,0 +1,45 @@
+"""A modification workflow for migrating an ImportedGeantIP to an GeantIP subscription."""
+
+from orchestrator.targets import Target
+from orchestrator.types import State, UUIDstr
+from orchestrator.utils.errors import ProcessFailureError
+from orchestrator.workflow import StepList, done, init, step, workflow
+from orchestrator.workflows.steps import resync, store_process_subscription, unsync
+from orchestrator.workflows.utils import wrap_modify_initial_input_form
+
+from gso.products import ProductName
+from gso.products.product_types.nren_l3_core_service import (
+    ImportedNRENL3CoreService,
+    NRENL3CoreService,
+    NRENL3CoreServiceType,
+)
+from gso.services.subscriptions import get_product_id_by_name
+
+
+@step("Create imported subscription")
+def import_nren_l3_core_service_subscription(subscription_id: UUIDstr) -> State:
+    """Take an imported subscription, and turn it into an :term:`NREN` L3 Core Service subscription."""
+    old_nren_l3_core_service = ImportedNRENL3CoreService.from_subscription(subscription_id)
+    if old_nren_l3_core_service.nren_l3_core_service_type == NRENL3CoreServiceType.IMPORTED_GEANT_IP:
+        new_subscription_id = get_product_id_by_name(ProductName.GEANT_IP)
+    elif old_nren_l3_core_service.nren_l3_core_service_type == NRENL3CoreServiceType.IMPORTED_IAS:
+        new_subscription_id = get_product_id_by_name(ProductName.IAS)
+    else:
+        msg = f"This {old_nren_l3_core_service.nren_l3_core_service_type} is already imported, nothing to do."
+        raise ProcessFailureError(message=msg, details=old_nren_l3_core_service)
+    new_subscription = NRENL3CoreService.from_other_product(old_nren_l3_core_service, new_subscription_id)  # type: ignore[arg-type]
+
+    return {"subscription": new_subscription}
+
+
+@workflow("Import NREN L3 Core Service", target=Target.MODIFY, initial_input_form=wrap_modify_initial_input_form(None))
+def import_nren_l3_core_service() -> StepList:
+    """Modify an imported subscription into an :term:`NREN` L3 Core Service subscription to complete the import."""
+    return (
+        init
+        >> store_process_subscription(Target.MODIFY)
+        >> unsync
+        >> import_nren_l3_core_service_subscription
+        >> resync
+        >> done
+    )
diff --git a/gso/workflows/nren_l3_core_service/migrate_nren_l3_core_service.py b/gso/workflows/nren_l3_core_service/migrate_nren_l3_core_service.py
new file mode 100644
index 0000000000000000000000000000000000000000..5d42bccdad3a97ec0f4dc52b2b1d720ab89192e5
--- /dev/null
+++ b/gso/workflows/nren_l3_core_service/migrate_nren_l3_core_service.py
@@ -0,0 +1,99 @@
+"""A modification workflow that migrates a L3 Core Service to a new set of Edge Ports."""
+
+from typing import Annotated
+
+from annotated_types import Len
+from orchestrator import workflow
+from orchestrator.targets import Target
+from orchestrator.workflow import StepList, begin, done, step
+from orchestrator.workflows.steps import resync, store_process_subscription, unsync
+from orchestrator.workflows.utils import wrap_modify_initial_input_form
+from pydantic import AfterValidator, BaseModel, ConfigDict, Field
+from pydantic_forms.core import FormPage
+from pydantic_forms.types import FormGenerator, State, UUIDstr
+from pydantic_forms.validators import Choice, Divider
+
+from gso.products.product_types.edge_port import EdgePort
+from gso.products.product_types.nren_l3_core_service import NRENL3CoreService
+from gso.services.subscriptions import get_active_edge_port_subscriptions
+from gso.utils.types.tt_number import TTNumber
+
+
+def initial_input_form_generator(subscription_id: UUIDstr) -> FormGenerator:
+    """Gather input from the operator on what new Edge Ports this L3 Core Service should be migrated to."""
+    subscription = NRENL3CoreService.from_subscription(subscription_id)
+    partner_id = subscription.customer_id
+    edge_port_count = len(subscription.nren_l3_core_service.nren_ap_list)
+
+    def _new_edge_port_selector(pid: UUIDstr) -> Choice:
+        existing_ep_name_list = [
+            ap.sbp.edge_port.owner_subscription_id for ap in subscription.nren_l3_core_service.nren_ap_list
+        ]
+        edge_port_subscriptions = list(
+            filter(
+                lambda ep: bool(ep["customer_id"] == pid) and ep["subscription_id"] not in existing_ep_name_list,
+                get_active_edge_port_subscriptions(includes=["subscription_id", "description", "customer_id"]),
+            )
+        )
+
+        edge_ports = {str(port["subscription_id"]): port["description"] for port in edge_port_subscriptions}
+
+        return Choice(
+            "Select an Edge Port",
+            zip(edge_ports.keys(), edge_ports.items(), strict=True),  # type: ignore[arg-type]
+        )
+
+    class NewEdgePortSelection(BaseModel):
+        old_edge_port: str
+        new_edge_port: _new_edge_port_selector(partner_id) | str  # type: ignore[valid-type]
+
+    def _validate_new_edge_ports_are_unique(edge_ports: list[NewEdgePortSelection]) -> list[NewEdgePortSelection]:
+        new_edge_ports = [str(port.new_edge_port) for port in edge_ports]
+        if len(new_edge_ports) != len(set(new_edge_ports)):
+            msg = "New Edge Ports must be unique"
+            raise ValueError(msg)
+        return edge_ports
+
+    class NRENL3CoreServiceEdgePortSelectionForm(FormPage):
+        model_config = ConfigDict(title=f"Migrating {subscription.product.name} to a new set of Edge Ports")
+
+        tt_number: TTNumber
+        divider: Divider = Field(None, exclude=True)
+        edge_port_selection: Annotated[
+            list[NewEdgePortSelection],
+            AfterValidator(_validate_new_edge_ports_are_unique),
+            Len(min_length=edge_port_count, max_length=edge_port_count),
+        ] = [  # noqa: RUF012
+            NewEdgePortSelection(
+                old_edge_port=f"{EdgePort.from_subscription(ap.sbp.edge_port.owner_subscription_id).description} ({
+                    ap.ap_type
+                })",
+                new_edge_port="",
+            )
+            for ap in subscription.nren_l3_core_service.nren_ap_list
+        ]
+
+    ep_user_input = yield NRENL3CoreServiceEdgePortSelectionForm
+
+    return {"subscription_id": subscription_id, "subscription": subscription} | ep_user_input.model_dump()
+
+
+@step("Update subscription model")
+def update_subscription_model(subscription: NRENL3CoreService, edge_port_selection: list[dict]) -> State:
+    """Update the subscription model with the new list of Access Ports."""
+    for index, selected_port in enumerate(edge_port_selection):
+        subscription.nren_l3_core_service.nren_ap_list[index].sbp.edge_port = EdgePort.from_subscription(
+            selected_port["new_edge_port"]
+        ).edge_port
+
+    return {"subscription": subscription}
+
+
+@workflow(
+    "Migrate NREN L3 Core Service",
+    initial_input_form=wrap_modify_initial_input_form(initial_input_form_generator),
+    target=Target.MODIFY,
+)
+def migrate_nren_l3_core_service() -> StepList:
+    """Migrate a :term:`NREN` L3 Core Service to a new set of Edge Ports."""
+    return begin >> store_process_subscription(Target.MODIFY) >> unsync >> update_subscription_model >> resync >> done
diff --git a/gso/workflows/nren_l3_core_service/modify_nren_l3_core_service.py b/gso/workflows/nren_l3_core_service/modify_nren_l3_core_service.py
new file mode 100644
index 0000000000000000000000000000000000000000..4331de2bcabc767d2a53904669fa2e1be7b8d724
--- /dev/null
+++ b/gso/workflows/nren_l3_core_service/modify_nren_l3_core_service.py
@@ -0,0 +1,302 @@
+"""A modification workflow for a :term:`NREN` L3 Core Service subscription."""
+
+from typing import Annotated, Any
+from uuid import uuid4
+
+from orchestrator import begin, conditional, done, step, workflow
+from orchestrator.forms import FormPage
+from orchestrator.targets import Target
+from orchestrator.types import FormGenerator, UUIDstr
+from orchestrator.workflow import StepList
+from orchestrator.workflows.steps import resync, store_process_subscription, unsync
+from orchestrator.workflows.utils import wrap_modify_initial_input_form
+from pydantic import AfterValidator, BaseModel, ConfigDict, Field, computed_field
+from pydantic_forms.types import State
+from pydantic_forms.validators import Divider, Label
+
+from gso.products.product_blocks.bgp_session import BGPSession, IPFamily
+from gso.products.product_blocks.nren_l3_core_service import NRENAccessPort
+from gso.products.product_blocks.service_binding_port import VLAN_ID, ServiceBindingPort
+from gso.products.product_types.edge_port import EdgePort
+from gso.products.product_types.nren_l3_core_service import NRENL3CoreService
+from gso.utils.helpers import active_edge_port_selector
+from gso.utils.shared_enums import APType, SBPType
+from gso.utils.types.ip_address import IPv4AddressType, IPV4Netmask, IPv6AddressType, IPV6Netmask
+
+
+def initial_input_form_generator(subscription_id: UUIDstr) -> FormGenerator:
+    """Get input about added, removed, and modified Access Ports."""
+    subscription = NRENL3CoreService.from_subscription(subscription_id)
+    product_name = subscription.product.name
+
+    class AccessPortSelection(BaseModel):
+        edge_port: active_edge_port_selector(partner_id=subscription.customer_id) | str  # type: ignore[valid-type]
+        ap_type: APType
+
+    def validate_edge_ports_are_unique(access_ports: list[AccessPortSelection]) -> list[AccessPortSelection]:
+        """Verify if interfaces are unique."""
+        edge_ports = [str(port.edge_port) for port in access_ports]
+        if len(edge_ports) != len(set(edge_ports)):
+            msg = "Edge Ports must be unique."
+            raise ValueError(msg)
+        return access_ports
+
+    class ModifyAccessPortsForm(FormPage):
+        model_config = ConfigDict(title=f"Modify {product_name}")
+        access_ports: Annotated[list[AccessPortSelection], AfterValidator(validate_edge_ports_are_unique)] = [  # noqa: RUF012
+            AccessPortSelection(
+                edge_port=str(access_port.sbp.edge_port.owner_subscription_id),
+                ap_type=access_port.ap_type,
+            )
+            for access_port in subscription.nren_l3_core_service.nren_ap_list
+        ]
+
+    access_port_input = yield ModifyAccessPortsForm
+    input_ap_list = access_port_input.access_ports
+    input_ep_list = [str(ap.edge_port) for ap in input_ap_list]
+    existing_ep_list = [
+        str(ap.sbp.edge_port.owner_subscription_id) for ap in subscription.nren_l3_core_service.nren_ap_list
+    ]
+
+    class BaseBGPPeer(BaseModel):
+        bfd_enabled: bool = False
+        bfd_interval: int | None = None
+        bfd_multiplier: int | None = None
+        has_custom_policies: bool = False
+        authentication_key: str
+        multipath_enabled: bool = False
+        send_default_route: bool = False
+        is_passive: bool = False
+
+    class IPv4BGPPeer(BaseBGPPeer):
+        peer_address: IPv4AddressType
+        add_v4_multicast: bool = Field(default=False, exclude=True)
+
+        @computed_field  # type: ignore[misc]
+        @property
+        def families(self) -> list[IPFamily]:
+            return [IPFamily.V4UNICAST, IPFamily.V4MULTICAST] if self.add_v4_multicast else [IPFamily.V4UNICAST]
+
+    class IPv6BGPPeer(BaseBGPPeer):
+        peer_address: IPv6AddressType
+        add_v6_multicast: bool = Field(default=False, exclude=True)
+
+        @computed_field  # type: ignore[misc]
+        @property
+        def families(self) -> list[IPFamily]:
+            return [IPFamily.V6UNICAST, IPFamily.V6MULTICAST] if self.add_v6_multicast else [IPFamily.V6UNICAST]
+
+    #  There are three possible scenarios for Edge Ports. They can be added, removed, or their relevant SBP can be
+    #  modified.
+    removed_ap_list = [
+        access_port.subscription_instance_id
+        for access_port in subscription.nren_l3_core_service.nren_ap_list
+        if str(access_port.sbp.edge_port.owner_subscription_id) not in input_ep_list
+    ]
+    modified_ap_list = [
+        (
+            access_port,
+            next(
+                (
+                    ap.ap_type
+                    for ap in input_ap_list
+                    if str(ap.edge_port) == str(access_port.sbp.edge_port.owner_subscription_id)
+                ),
+                None,
+            ),
+        )
+        for access_port in subscription.nren_l3_core_service.nren_ap_list
+        if str(access_port.sbp.edge_port.owner_subscription_id) in input_ep_list
+    ]
+    added_ap_list = [
+        (ep, next(ap.ap_type for ap in input_ap_list if str(ap.edge_port) == ep))
+        for ep in input_ep_list
+        if ep not in existing_ep_list
+    ]
+
+    #  First, the user can modify existing Edge Ports
+    sbp_inputs = []
+    for access_port_index, ap_entry in enumerate(modified_ap_list):
+        access_port, new_ap_type = ap_entry
+        current_sbp = access_port.sbp
+        v4_peer = next(peer for peer in current_sbp.bgp_session_list if IPFamily.V4UNICAST in peer.families)
+        v6_peer = next(peer for peer in current_sbp.bgp_session_list if IPFamily.V6UNICAST in peer.families)
+
+        class BindingPortModificationForm(FormPage):
+            model_config = ConfigDict(
+                title=f"{product_name} - Modify Edge Port configuration "
+                f"({access_port_index + 1}/{len(input_ap_list)})"
+            )
+            current_ep_label: Label = Field(
+                f"Currently configuring on {access_port.sbp.edge_port.edge_port_description} "
+                f"(Access Port type: {access_port.ap_type})",
+                exclude=True,
+            )
+
+            geant_sid: str = current_sbp.geant_sid
+            is_tagged: bool = current_sbp.is_tagged
+            # The SBP model doesn't require these three fields, but in the case of GÉANT IP OR IAS this will never
+            # occur since it's a layer 3 service. The ignore statements are there to put our type checker at ease.
+            vlan_id: VLAN_ID = current_sbp.vlan_id  # type: ignore[assignment]
+            ipv4_address: IPv4AddressType = current_sbp.ipv4_address  # type: ignore[assignment]
+            ipv4_mask: IPV4Netmask = current_sbp.ipv4_mask  # type: ignore[assignment]
+            ipv6_address: IPv6AddressType = current_sbp.ipv6_address  # type: ignore[assignment]
+            ipv6_mask: IPV6Netmask = current_sbp.ipv6_mask  # type: ignore[assignment]
+            custom_firewall_filters: bool = current_sbp.custom_firewall_filters
+            divider: Divider = Field(None, exclude=True)
+            v4_bgp_peer: IPv4BGPPeer = IPv4BGPPeer(
+                **v4_peer.model_dump(exclude=set("families")),
+                add_v4_multicast=bool(IPFamily.V4MULTICAST in v4_peer.families),
+            )
+            v6_bgp_peer: IPv6BGPPeer = IPv6BGPPeer(
+                **v6_peer.model_dump(exclude=set("families")),
+                add_v6_multicast=bool(IPFamily.V6MULTICAST in v6_peer.families),
+            )
+
+        binding_port_input_form = yield BindingPortModificationForm
+        sbp_inputs.append(
+            binding_port_input_form.model_dump()
+            | {
+                "new_ap_type": new_ap_type,
+                "current_sbp_id": current_sbp.subscription_instance_id,
+            }
+        )
+
+    #  Second, newly added Edge Ports are configured
+    binding_port_inputs = []
+    for ap_index, access_port_tuple in enumerate(added_ap_list):
+        edge_port_id, ap_type = access_port_tuple
+
+        class BindingPortInputForm(FormPage):
+            model_config = ConfigDict(
+                title=f"{product_name} - Configure new Edge Port "
+                f"({len(modified_ap_list) + ap_index + 1}/{len(input_ap_list)})"
+            )
+            info_label: Label = Field(
+                "Please configure the Service Binding Ports for each newly added Edge Port", exclude=True
+            )
+            current_ep_label: Label = Field(
+                f"Currently configuring on {EdgePort.from_subscription(edge_port_id).description} "
+                f"(Access Port type: {ap_type})",
+                exclude=True,
+            )
+
+            geant_sid: str
+            is_tagged: bool = False
+            vlan_id: VLAN_ID
+            ipv4_address: IPv4AddressType
+            ipv6_address: IPv6AddressType
+            custom_firewall_filters: bool = False
+            divider: Divider = Field(None, exclude=True)
+            v4_bgp_peer: IPv4BGPPeer
+            v6_bgp_peer: IPv6BGPPeer
+
+        binding_port_input_form = yield BindingPortInputForm
+        binding_port_inputs.append(
+            binding_port_input_form.model_dump()
+            | {
+                "bgp_peers": [
+                    binding_port_input_form.v4_bgp_peer.model_dump(),
+                    binding_port_input_form.v6_bgp_peer.model_dump(),
+                ],
+                "edge_port_id": edge_port_id,
+                "ap_type": ap_type,
+            }
+        )
+
+    return access_port_input.model_dump() | {
+        "added_service_binding_ports": binding_port_inputs,
+        "removed_access_ports": removed_ap_list,
+        "modified_sbp_list": sbp_inputs,
+    }
+
+
+@step("Clean up removed Edge Ports")
+def remove_old_sbp_blocks(subscription: NRENL3CoreService, removed_access_ports: list[UUIDstr]) -> State:
+    """Remove old :term:`SBP` product blocks from the GÉANT IP subscription."""
+    subscription.nren_l3_core_service.nren_ap_list = [
+        ap
+        for ap in subscription.nren_l3_core_service.nren_ap_list
+        if str(ap.subscription_instance_id) not in removed_access_ports
+    ]
+
+    return {"subscription": subscription}
+
+
+@step("Modify existing Service Binding Ports")
+def modify_existing_sbp_blocks(subscription: NRENL3CoreService, modified_sbp_list: list[dict[str, Any]]) -> State:
+    """Update the subscription model."""
+    for access_port in subscription.nren_l3_core_service.nren_ap_list:
+        current_sbp = access_port.sbp
+        modified_sbp_data = next(
+            sbp for sbp in modified_sbp_list if sbp["current_sbp_id"] == str(current_sbp.subscription_instance_id)
+        )
+
+        v4_peer = next(peer for peer in current_sbp.bgp_session_list if IPFamily.V4UNICAST in peer.families)
+        for attribute in modified_sbp_data["v4_bgp_peer"]:
+            setattr(v4_peer, attribute, modified_sbp_data["v4_bgp_peer"][attribute])
+
+        v6_peer = next(peer for peer in current_sbp.bgp_session_list if IPFamily.V6UNICAST in peer.families)
+        for attribute in modified_sbp_data["v6_bgp_peer"]:
+            setattr(v6_peer, attribute, modified_sbp_data["v6_bgp_peer"][attribute])
+
+        current_sbp.bgp_session_list = [v4_peer, v6_peer]
+        current_sbp.vlan_id = modified_sbp_data["vlan_id"]
+        current_sbp.geant_sid = modified_sbp_data["geant_sid"]
+        current_sbp.is_tagged = modified_sbp_data["is_tagged"]
+        current_sbp.ipv4_address = modified_sbp_data["ipv4_address"]
+        current_sbp.ipv6_address = modified_sbp_data["ipv6_address"]
+        current_sbp.custom_firewall_filters = modified_sbp_data["custom_firewall_filters"]
+        access_port.ap_type = modified_sbp_data["new_ap_type"]
+
+    return {"subscription": subscription}
+
+
+@step("Instantiate new Service Binding Ports")
+def create_new_sbp_blocks(subscription: NRENL3CoreService, added_service_binding_ports: list[dict[str, Any]]) -> State:
+    """Add new two :term:`SBP` to the :term:`NREN` L3 Core Service subscription."""
+    for sbp_input in added_service_binding_ports:
+        edge_port = EdgePort.from_subscription(sbp_input["edge_port_id"])
+        bgp_session_list = [
+            BGPSession.new(subscription_id=uuid4(), **session, rtbh_enabled=True, is_multi_hop=True)
+            for session in sbp_input["bgp_peers"]
+        ]
+        service_binding_port = ServiceBindingPort.new(
+            subscription_id=uuid4(),
+            **sbp_input,
+            bgp_session_list=bgp_session_list,
+            sbp_type=SBPType.L3,
+            edge_port=edge_port.edge_port,
+        )
+        subscription.nren_l3_core_service.nren_ap_list.append(
+            NRENAccessPort.new(
+                subscription_id=uuid4(),
+                ap_type=sbp_input["ap_type"],
+                sbp=service_binding_port,
+            )
+        )
+
+    return {"subscription": subscription}
+
+
+@workflow(
+    "Modify NREN L3 Core Service",
+    initial_input_form=wrap_modify_initial_input_form(initial_input_form_generator),
+    target=Target.MODIFY,
+)
+def modify_nren_l3_core_service() -> StepList:
+    """Modify a NRN L3 Core Service subscription."""
+    access_ports_are_removed = conditional(lambda state: bool(len(state["removed_access_ports"]) > 0))
+    access_ports_are_modified = conditional(lambda state: bool(len(state["modified_sbp_list"]) > 0))
+    access_ports_are_added = conditional(lambda state: bool(len(state["added_service_binding_ports"]) > 0))
+
+    return (
+        begin
+        >> store_process_subscription(Target.MODIFY)
+        >> unsync
+        >> access_ports_are_removed(remove_old_sbp_blocks)
+        >> access_ports_are_modified(modify_existing_sbp_blocks)
+        >> access_ports_are_added(create_new_sbp_blocks)
+        >> resync
+        >> done
+    )
diff --git a/gso/workflows/router/create_router.py b/gso/workflows/router/create_router.py
index 04d38b750f5fa4ca87591f7affe04674208685bf..edc4ec0b7877313f28af8508c3da4833e9161c55 100644
--- a/gso/workflows/router/create_router.py
+++ b/gso/workflows/router/create_router.py
@@ -4,7 +4,7 @@ from typing import Self
 
 from orchestrator.config.assignee import Assignee
 from orchestrator.forms import FormPage
-from orchestrator.forms.validators import Choice, Label
+from orchestrator.forms.validators import Label
 from orchestrator.targets import Target
 from orchestrator.types import FormGenerator, State, SubscriptionLifecycle, UUIDstr
 from orchestrator.utils.errors import ProcessFailureError
@@ -17,13 +17,13 @@ from pydantic_forms.validators import ReadOnlyField
 from gso.products.product_blocks.router import RouterRole
 from gso.products.product_types.router import RouterInactive, RouterProvisioning
 from gso.products.product_types.site import Site
-from gso.services import infoblox, subscriptions
+from gso.services import infoblox
 from gso.services.lso_client import lso_interaction
 from gso.services.netbox_client import NetboxClient
 from gso.services.partners import get_partner_by_name
 from gso.services.sharepoint import SharePointClient
 from gso.settings import load_oss_params
-from gso.utils.helpers import generate_fqdn, iso_from_ipv4
+from gso.utils.helpers import active_site_selector, generate_fqdn, iso_from_ipv4
 from gso.utils.shared_enums import Vendor
 from gso.utils.types.ip_address import PortNumber
 from gso.utils.types.tt_number import TTNumber
@@ -33,15 +33,7 @@ from gso.utils.workflow_steps import (
     prompt_sharepoint_checklist_url,
     run_checks_after_base_config,
 )
-
-
-def _site_selector() -> Choice:
-    site_subscriptions = {}
-    for site in subscriptions.get_active_site_subscriptions(includes=["subscription_id", "description"]):
-        site_subscriptions[str(site["subscription_id"])] = site["description"]
-
-    # noinspection PyTypeChecker
-    return Choice("Select a site", zip(site_subscriptions.keys(), site_subscriptions.items(), strict=True))  # type: ignore[arg-type]
+from gso.workflows.shared import create_summary_form
 
 
 def initial_input_form_generator(product_name: str) -> FormGenerator:
@@ -53,7 +45,7 @@ def initial_input_form_generator(product_name: str) -> FormGenerator:
         tt_number: TTNumber
         partner: ReadOnlyField("GEANT", default_type=str)  # type: ignore[valid-type]
         vendor: Vendor
-        router_site: _site_selector()  # type: ignore[valid-type]
+        router_site: active_site_selector()  # type: ignore[valid-type]
         hostname: str
         ts_port: PortNumber
         router_role: RouterRole
@@ -74,8 +66,11 @@ def initial_input_form_generator(product_name: str) -> FormGenerator:
             return self
 
     user_input = yield CreateRouterForm
+    user_input = user_input.model_dump()
+    summary_fields = ["hostname", "router_site", "vendor", "ts_port", "router_role"]
+    yield from create_summary_form(user_input, product_name, summary_fields)
 
-    return user_input.model_dump()
+    return user_input
 
 
 @step("Create subscription")
diff --git a/gso/workflows/router/modify_connection_strategy.py b/gso/workflows/router/modify_connection_strategy.py
index 65b29b8d1b57851d925ffb89909c215e94889f06..890329d845bfe1c3a18accb318e303734378e667 100644
--- a/gso/workflows/router/modify_connection_strategy.py
+++ b/gso/workflows/router/modify_connection_strategy.py
@@ -34,8 +34,8 @@ def initial_input_form_generator(subscription_id: UUIDstr) -> FormGenerator:
 def update_subscription_model(subscription: Router, connection_strategy: str) -> State:
     """Update the database model to reflect the new connection strategy.
 
-    If the connection strategy is set to IN-BAND, then access_via_ts should be set to False.
-    Conversely, if the connection strategy is set to OUT-OF-BAND, access_via_ts should be set to True.
+    If the connection strategy is set to in-band, then access_via_ts should be set to False.
+    Conversely, if the connection strategy is set to out-of-band, access_via_ts should be set to True.
     """
     subscription.router.router_access_via_ts = connection_strategy == ConnectionStrategy.OUT_OF_BAND
 
diff --git a/gso/workflows/router/promote_p_to_pe.py b/gso/workflows/router/promote_p_to_pe.py
index a82b4823f4374772ef2b5365f3f57ea56fad1017..8bf78ea033cc00a8fe936ab10d0223ae9ee1f9dc 100644
--- a/gso/workflows/router/promote_p_to_pe.py
+++ b/gso/workflows/router/promote_p_to_pe.py
@@ -236,7 +236,7 @@ def deploy_routing_instances_real(subscription: dict[str, Any], tt_number: str,
 
 @step("Remove ISIS overload")
 def remove_isis_overload(subscription: dict[str, Any], tt_number: str, process_id: UUIDstr) -> LSOState:
-    """Remove ISIS overload."""
+    """Remove :term:`ISIS` overload."""
     extra_vars = {
         "dry_run": False,
         "subscription": subscription,
diff --git a/gso/workflows/router/update_ibgp_mesh.py b/gso/workflows/router/update_ibgp_mesh.py
index 298c0878a2f82449173c78d24604e82ae8eb5bb1..fc071ab8fc657cd2f5fb001a165d84175c0a513b 100644
--- a/gso/workflows/router/update_ibgp_mesh.py
+++ b/gso/workflows/router/update_ibgp_mesh.py
@@ -18,7 +18,7 @@ from gso.services import librenms_client
 from gso.services.lso_client import LSOState, lso_interaction
 from gso.services.subscriptions import get_trunks_that_terminate_on_router
 from gso.utils.helpers import generate_inventory_for_active_routers
-from gso.utils.types.snmp import SNMPVersion
+from gso.utils.shared_enums import SNMPVersion
 from gso.utils.types.tt_number import TTNumber
 from gso.utils.workflow_steps import (
     add_all_p_to_pe_dry,
diff --git a/gso/workflows/shared.py b/gso/workflows/shared.py
new file mode 100644
index 0000000000000000000000000000000000000000..8504deba80150c29c48a7f151e281d705b95d04c
--- /dev/null
+++ b/gso/workflows/shared.py
@@ -0,0 +1,40 @@
+"""Shared functions for the workflows."""
+
+from collections.abc import Generator
+from typing import cast
+
+from orchestrator.domain.base import ProductBlockModel
+from orchestrator.forms import FormPage
+from orchestrator.forms.validators import MigrationSummary, migration_summary
+from pydantic import ConfigDict
+
+
+def summary_form(product_name: str, summary_data: dict) -> Generator:
+    """Generate a summary form for the product."""
+
+    class SummaryForm(FormPage):
+        model_config = ConfigDict(title=f"{product_name} summary")
+
+        product_summary: cast(type[MigrationSummary], migration_summary(summary_data))  # type: ignore[valid-type]
+
+    yield SummaryForm
+
+
+def create_summary_form(user_input: dict, product_name: str, fields: list[str]) -> Generator:
+    """Create a summary form for the product."""
+    columns = [[str(user_input[nm]) for nm in fields]]
+    yield from summary_form(product_name, {"labels": fields, "columns": columns})
+
+
+def modify_summary_form(user_input: dict, block: ProductBlockModel, fields: list[str]) -> Generator:
+    """Modify the summary form for the product."""
+    before = [str(getattr(block, nm)) for nm in fields]
+    after = [str(user_input[nm]) for nm in fields]
+    yield from summary_form(
+        block.subscription.product.name,
+        {
+            "labels": fields,
+            "headers": ["Before", "After"],
+            "columns": [before, after],
+        },
+    )
diff --git a/gso/workflows/site/create_site.py b/gso/workflows/site/create_site.py
index eb5409d778c019e41ae82c85376ec48fcf4bdcc6..384dd59f0945885f54bfd5c51b51f477b3a30f69 100644
--- a/gso/workflows/site/create_site.py
+++ b/gso/workflows/site/create_site.py
@@ -15,6 +15,7 @@ from gso.services.partners import get_partner_by_name
 from gso.utils.types.base_site import BaseSiteValidatorModel
 from gso.utils.types.coordinates import LatitudeCoordinate, LongitudeCoordinate
 from gso.utils.types.ip_address import IPAddress
+from gso.workflows.shared import create_summary_form
 
 
 def initial_input_form_generator(product_name: str) -> FormGenerator:
@@ -25,8 +26,23 @@ def initial_input_form_generator(product_name: str) -> FormGenerator:
         partner: ReadOnlyField("GEANT", default_type=str)  # type: ignore[valid-type]
 
     user_input = yield CreateSiteForm
-
-    return user_input.model_dump()
+    user_input = user_input.model_dump()
+    summary_fields = [
+        "site_name",
+        "site_bgp_community_id",
+        "site_internal_id",
+        "site_tier",
+        "site_ts_address",
+        "site_country_code",
+        "site_city",
+        "site_country",
+        "site_latitude",
+        "site_longitude",
+        "partner",
+    ]
+    yield from create_summary_form(user_input, product_name, summary_fields)
+
+    return user_input
 
 
 @step("Create subscription")
diff --git a/gso/workflows/site/modify_site.py b/gso/workflows/site/modify_site.py
index 9c94e55032ae9712856603463b71299cf8e37fe7..a522a714d52ed5db066ff18cbeacc7175ba4a907 100644
--- a/gso/workflows/site/modify_site.py
+++ b/gso/workflows/site/modify_site.py
@@ -1,5 +1,6 @@
 """A modification workflow for a site."""
 
+from functools import partial
 from typing import Annotated
 
 from orchestrator.forms import FormPage
@@ -13,14 +14,15 @@ from orchestrator.workflows.steps import (
     unsync,
 )
 from orchestrator.workflows.utils import wrap_modify_initial_input_form
-from pydantic import ConfigDict
+from pydantic import AfterValidator, ConfigDict
 from pydantic_forms.validators import ReadOnlyField
 
 from gso.products.product_blocks.site import SiteTier
 from gso.products.product_types.site import Site
 from gso.utils.types.coordinates import LatitudeCoordinate, LongitudeCoordinate
 from gso.utils.types.ip_address import IPAddress
-from gso.utils.types.unique_field import UniqueField
+from gso.utils.types.unique_field import validate_field_is_unique
+from gso.workflows.shared import modify_summary_form
 
 
 def initial_input_form_generator(subscription_id: UUIDstr) -> FormGenerator:
@@ -36,14 +38,30 @@ def initial_input_form_generator(subscription_id: UUIDstr) -> FormGenerator:
         site_country_code: ReadOnlyField(subscription.site.site_country_code, default_type=str)  # type: ignore[valid-type]
         site_latitude: LatitudeCoordinate = subscription.site.site_latitude
         site_longitude: LongitudeCoordinate = subscription.site.site_longitude
-        site_bgp_community_id: UniqueField[int] = subscription.site.site_bgp_community_id
-        site_internal_id: UniqueField[int] = subscription.site.site_internal_id
+        site_bgp_community_id: Annotated[int, AfterValidator(partial(validate_field_is_unique, subscription_id))] = (
+            subscription.site.site_bgp_community_id
+        )
+        site_internal_id: Annotated[int, AfterValidator(partial(validate_field_is_unique, subscription_id))] = (
+            subscription.site.site_internal_id
+        )
         site_tier: ReadOnlyField(subscription.site.site_tier, default_type=SiteTier)  # type: ignore[valid-type]
-        site_ts_address: Annotated[IPAddress, UniqueField] | None = subscription.site.site_ts_address
+        site_ts_address: (
+            Annotated[IPAddress, AfterValidator(partial(validate_field_is_unique, subscription_id))] | None
+        ) = subscription.site.site_ts_address
 
     user_input = yield ModifySiteForm
-
-    return user_input.model_dump()
+    user_input = user_input.model_dump()
+    summary_fields = [
+        "site_bgp_community_id",
+        "site_internal_id",
+        "site_ts_address",
+        "site_city",
+        "site_latitude",
+        "site_longitude",
+    ]
+    yield from modify_summary_form(user_input, subscription.site, summary_fields)
+
+    return user_input
 
 
 @step("Modify subscription")
diff --git a/gso/workflows/tasks/delete_partners.py b/gso/workflows/tasks/delete_partners.py
index 6eac752bbb417500d383d43fbc6ce2c3567031d1..8a557c9953079ee4e6723bb89bf60d1bc7a9dfca 100644
--- a/gso/workflows/tasks/delete_partners.py
+++ b/gso/workflows/tasks/delete_partners.py
@@ -7,23 +7,18 @@ from orchestrator.targets import Target
 from orchestrator.types import FormGenerator, State, UUIDstr
 from orchestrator.workflow import StepList, begin, done, step, workflow
 from pydantic import ConfigDict, EmailStr, field_validator
-from pydantic_forms.validators import Choice
 
-from gso.services.partners import delete_partner, get_all_partners, get_partner_by_name
+from gso.services.partners import delete_partner, get_partner_by_name
 from gso.services.subscriptions import get_subscriptions
+from gso.utils.helpers import partner_choice
 
 
 def initial_input_form_generator() -> FormGenerator:
     """Gather input from the user needed for deleting a partner."""
-    partners = {}
-    for partner in get_all_partners():
-        partners[partner["partner_id"]] = partner["name"]
-
-    partner_choice = Choice("Select a partner", zip(partners.values(), partners.items(), strict=True))  # type: ignore[arg-type]
 
     class SelectPartnerForm(FormPage):
         model_config = ConfigDict(title="Delete a Partner")
-        partners: partner_choice  # type: ignore[valid-type]
+        partners: partner_choice()  # type: ignore[valid-type]
 
         @field_validator("partners")
         def validate_partners(cls, value: Enum) -> Enum:
diff --git a/gso/workflows/tasks/modify_partners.py b/gso/workflows/tasks/modify_partners.py
index cb2daddac8d646c87e20d80449024707cb319d21..c557203eede28c35dde9997f74a383002fb3e6fd 100644
--- a/gso/workflows/tasks/modify_partners.py
+++ b/gso/workflows/tasks/modify_partners.py
@@ -5,30 +5,24 @@ from orchestrator.targets import Target
 from orchestrator.types import FormGenerator, State, UUIDstr
 from orchestrator.workflow import StepList, begin, done, step, workflow
 from pydantic import ConfigDict, EmailStr, field_validator
-from pydantic_forms.validators import Choice
 
 from gso.services.partners import (
     ModifiedPartnerSchema,
     edit_partner,
     filter_partners_by_email,
     filter_partners_by_name,
-    get_all_partners,
     get_partner_by_name,
 )
+from gso.utils.helpers import partner_choice
 
 
 def initial_input_form_generator() -> FormGenerator:
     """Gather input from the user needed for modifying a partner."""
-    partners = {}
-    for partner in get_all_partners():
-        partners[partner["partner_id"]] = partner["name"]
-
-    partner_choice = Choice("Select a partner", zip(partners.values(), partners.items(), strict=True))  # type: ignore[arg-type]
 
     class SelectPartnerForm(FormPage):
         model_config = ConfigDict(title="Choose a Partner")
 
-        partners: partner_choice  # type: ignore[valid-type]
+        partners: partner_choice()  # type: ignore[valid-type]
 
     initial_user_input = yield SelectPartnerForm
 
diff --git a/pyproject.toml b/pyproject.toml
index e9979242b773ca0eb5812755a4a240733cca96fa..2ae64df63bb2609ee3196b6d31605957a5b78a0d 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -116,3 +116,6 @@ filterwarnings = [
     "ignore",
     "default:::gso",
 ]
+
+[tool.coverage.run]
+omit = ["gso/migrations/*"]
diff --git a/setup.py b/setup.py
index 511db02051c9e54bf193d9e65ac18a4fe2d73978..3ec7a018f534de6d21b39ba0c786eba2e04042ca 100644
--- a/setup.py
+++ b/setup.py
@@ -4,7 +4,7 @@ from setuptools import find_packages, setup
 
 setup(
     name="geant-service-orchestrator",
-    version="2.21",
+    version="2.22",
     author="GÉANT Orchestration and Automation Team",
     author_email="goat@geant.org",
     description="GÉANT Service Orchestrator",
diff --git a/start-worker.sh b/start-worker.sh
index 3c18dd4422ae6d60e16c78f45aa7189659dd5ca9..92cd6304d95db2a697ca1b77e4e705c5b9350b1a 100755
--- a/start-worker.sh
+++ b/start-worker.sh
@@ -4,4 +4,4 @@ set -o errexit
 set -o nounset
 
 cd /app
-python -m celery -A gso.worker worker --loglevel=info
+python -m celery -A gso.worker worker --loglevel=info --concurrency=1 --pool=solo
diff --git a/test/cli/test_imports.py b/test/cli/test_imports.py
index 983323869339b67329f3824ee7310a495c6c5a24..33ab32dd2acc6b1746322a1694528fa2323ee48d 100644
--- a/test/cli/test_imports.py
+++ b/test/cli/test_imports.py
@@ -5,17 +5,22 @@ from unittest.mock import patch
 import pytest
 
 from gso.cli.imports import (
+    import_edge_port,
     import_iptrunks,
+    import_nren_l3_core_service,
     import_office_routers,
     import_opengear,
     import_routers,
     import_sites,
     import_super_pop_switches,
 )
-from gso.products import Router, Site
+from gso.products.product_blocks.bgp_session import IPFamily
+from gso.products.product_blocks.edge_port import EdgePortType, EncapsulationType
 from gso.products.product_blocks.iptrunk import IptrunkType
 from gso.products.product_blocks.router import RouterRole
 from gso.products.product_blocks.site import SiteTier
+from gso.products.product_types.router import Router
+from gso.products.product_types.site import Site
 from gso.utils.helpers import iso_from_ipv4
 from gso.utils.shared_enums import Vendor
 from gso.utils.types.interfaces import PhysicalPortCapacity
@@ -195,6 +200,138 @@ def opengear_data(temp_file, faker, site_subscription_factory):
     return _opengear_data
 
 
+@pytest.fixture()
+def edge_port_data(temp_file, faker, router_subscription_factory, partner_factory):
+    def _edge_port_data(**kwargs):
+        edge_port_data = {
+            "node": Router.from_subscription(router_subscription_factory(vendor=Vendor.NOKIA)).router.router_fqdn,
+            "service_type": EdgePortType.CUSTOMER,
+            "speed": PhysicalPortCapacity.TEN_GIGABIT_PER_SECOND,
+            "encapsulation": EncapsulationType.DOT1Q,
+            "name": "lag34",
+            "minimum_links": 2,
+            "geant_ga_id": faker.geant_gid(),
+            "mac_address": faker.mac_address(),
+            "partner": partner_factory()["name"],
+            "enable_lacp": True,
+            "ignore_if_down": False,
+            "ae_members": [
+                {
+                    "interface_name": faker.network_interface(),
+                    "interface_description": faker.sentence(),
+                },
+                {
+                    "interface_name": faker.network_interface(),
+                    "interface_description": faker.sentence(),
+                },
+            ],
+            "description": faker.sentence(),
+        }
+        edge_port_data.update(**kwargs)
+
+        temp_file.write_text(json.dumps([edge_port_data]))
+        return {"path": str(temp_file), "data": edge_port_data}
+
+    return _edge_port_data
+
+
+@pytest.fixture()
+def nren_l3_core_service_data(temp_file, faker, partner_factory, edge_port_subscription_factory):
+    def _nren_l3_core_service_data(**kwargs):
+        nren_l3_core_service_data = {
+            "partner": partner_factory()["name"],
+            "service_type": "IMPORTED IAS",
+            "service_binding_ports": [
+                {
+                    "edge_port": edge_port_subscription_factory(),
+                    "ap_type": "PRIMARY",
+                    "geant_sid": faker.geant_sid(),
+                    "vlan_id": faker.vlan_id(),
+                    "ipv4_address": faker.ipv4(),
+                    "ipv4_mask": faker.ipv4_netmask(),
+                    "ipv6_address": faker.ipv6(),
+                    "ipv6_mask": faker.ipv6_netmask(),
+                    "bgp_peers": [
+                        {
+                            "bfd_enabled": True,
+                            "bfd_interval": faker.pyint(),
+                            "bfd_multiplier": faker.pyint(),
+                            "has_custom_policies": True,
+                            "authentication_key": faker.password(),
+                            "multipath_enabled": False,
+                            "send_default_route": True,
+                            "is_passive": True,
+                            "peer_address": faker.ipv4(),
+                            "families": [IPFamily.V4UNICAST, IPFamily.V4MULTICAST],
+                            "is_multi_hop": False,
+                            "rtbh_enabled": True,
+                        },
+                        {
+                            "bfd_enabled": True,
+                            "bfd_interval": faker.pyint(),
+                            "bfd_multiplier": faker.pyint(),
+                            "has_custom_policies": True,
+                            "authentication_key": faker.password(),
+                            "multipath_enabled": False,
+                            "send_default_route": True,
+                            "is_passive": True,
+                            "peer_address": faker.ipv6(),
+                            "families": [IPFamily.V6UNICAST],
+                            "is_multi_hop": False,
+                            "rtbh_enabled": True,
+                        },
+                    ],
+                },
+                {
+                    "edge_port": edge_port_subscription_factory(),
+                    "ap_type": "BACKUP",
+                    "geant_sid": faker.geant_sid(),
+                    "vlan_id": faker.vlan_id(),
+                    "ipv4_address": faker.ipv4(),
+                    "ipv4_mask": faker.ipv4_netmask(),
+                    "ipv6_address": faker.ipv6(),
+                    "ipv6_mask": faker.ipv6_netmask(),
+                    "bgp_peers": [
+                        {
+                            "bfd_enabled": True,
+                            "bfd_interval": faker.pyint(),
+                            "bfd_multiplier": faker.pyint(),
+                            "has_custom_policies": True,
+                            "authentication_key": faker.password(),
+                            "multipath_enabled": False,
+                            "send_default_route": True,
+                            "is_passive": True,
+                            "peer_address": faker.ipv4(),
+                            "families": [IPFamily.V4UNICAST, IPFamily.V4MULTICAST],
+                            "is_multi_hop": False,
+                            "rtbh_enabled": True,
+                        },
+                        {
+                            "bfd_enabled": True,
+                            "bfd_interval": faker.pyint(),
+                            "bfd_multiplier": faker.pyint(),
+                            "has_custom_policies": True,
+                            "authentication_key": faker.password(),
+                            "multipath_enabled": False,
+                            "send_default_route": True,
+                            "is_passive": True,
+                            "peer_address": faker.ipv6(),
+                            "families": [IPFamily.V6UNICAST],
+                            "is_multi_hop": False,
+                            "rtbh_enabled": True,
+                        },
+                    ],
+                },
+            ],
+        }
+        nren_l3_core_service_data.update(**kwargs)
+
+        temp_file.write_text(json.dumps([nren_l3_core_service_data]))
+        return {"path": str(temp_file), "data": nren_l3_core_service_data}
+
+    return _nren_l3_core_service_data
+
+
 ###########
 #  TESTS  #
 ###########
@@ -377,3 +514,128 @@ def test_import_super_pop_switch_success(mock_start_process, mock_sleep, super_p
 def test_import_opengear_success(mock_start_process, opengear_data):
     import_opengear(opengear_data()["path"])
     assert mock_start_process.call_count == 1
+
+
+@patch("gso.cli.imports.time.sleep")
+@patch("gso.cli.imports.start_process")
+def test_import_edge_port_successful(mock_start_process, mock_sleep, edge_port_data):
+    import_edge_port(edge_port_data()["path"])
+    assert mock_start_process.call_count == 1
+
+
+@patch("gso.cli.imports.time.sleep")
+@patch("gso.cli.imports.start_process")
+def test_import_edge_port_with_invalid_router(
+    mock_start_process, mock_sleep, edge_port_data, capfd, router_subscription_factory
+):
+    p_router = router_subscription_factory(vendor=Vendor.NOKIA, router_role=RouterRole.P)
+    broken_data = edge_port_data(node=Router.from_subscription(p_router).router.router_fqdn)
+    import_edge_port(broken_data["path"])
+
+    captured_output, _ = capfd.readouterr()
+    assert f"Router {p_router} not found" in captured_output
+    assert mock_start_process.call_count == 0
+
+
+@patch("gso.cli.imports.time.sleep")
+@patch("gso.cli.imports.start_process")
+def test_import_edge_port_with_invalid_partner(mock_start_process, mock_sleep, edge_port_data, capfd):
+    broken_data = edge_port_data(partner="INVALID")
+    import_edge_port(broken_data["path"])
+
+    captured_output, _ = capfd.readouterr()
+    assert "Partner INVALID not found" in captured_output
+    assert mock_start_process.call_count == 0
+
+
+@patch("gso.cli.imports.time.sleep")
+@patch("gso.cli.imports.start_process")
+def test_import_nren_l3_core_service_success(mock_start_process, mock_sleep, nren_l3_core_service_data, capfd):
+    import_nren_l3_core_service(nren_l3_core_service_data()["path"])
+    assert mock_start_process.call_count == 1
+
+
+@patch("gso.cli.imports.time.sleep")
+@patch("gso.cli.imports.start_process")
+def test_import_nren_l3_core_service_with_invalid_partner(
+    mock_start_process, mock_sleep, nren_l3_core_service_data, capfd
+):
+    broken_data = nren_l3_core_service_data(partner="INVALID")
+    import_nren_l3_core_service(broken_data["path"])
+
+    captured_output, _ = capfd.readouterr()
+    assert "Partner INVALID not found" in captured_output
+    assert mock_start_process.call_count == 0
+
+
+@patch("gso.cli.imports.time.sleep")
+@patch("gso.cli.imports.start_process")
+def test_import_nren_l3_core_service_with_invalid_edge_port(
+    mock_start_process, mock_sleep, faker, nren_l3_core_service_data, edge_port_subscription_factory, capfd
+):
+    fake_uuid = faker.uuid4()
+    broken_data = nren_l3_core_service_data(
+        service_binding_ports=[
+            {
+                "edge_port": fake_uuid,
+                "ap_type": "PRIMARY",
+                "geant_sid": faker.geant_sid(),
+                "vlan_id": faker.vlan_id(),
+                "ipv4_address": faker.ipv4(),
+                "ipv4_mask": faker.ipv4_netmask(),
+                "ipv6_address": faker.ipv6(),
+                "ipv6_mask": faker.ipv6_netmask(),
+                "bgp_peers": [
+                    {
+                        "bfd_enabled": False,
+                        "authentication_key": faker.password(),
+                        "peer_address": faker.ipv4(),
+                        "families": [IPFamily.V4UNICAST],
+                        "is_multi_hop": False,
+                        "rtbh_enabled": True,
+                    },
+                    {
+                        "bfd_enabled": False,
+                        "authentication_key": faker.password(),
+                        "peer_address": faker.ipv6(),
+                        "families": [IPFamily.V6UNICAST],
+                        "is_multi_hop": False,
+                        "rtbh_enabled": True,
+                    },
+                ],
+            },
+            {
+                "edge_port": edge_port_subscription_factory(),
+                "ap_type": "BACKUP",
+                "geant_sid": faker.geant_sid(),
+                "vlan_id": faker.vlan_id(),
+                "ipv4_address": faker.ipv4(),
+                "ipv4_mask": faker.ipv4_netmask(),
+                "ipv6_address": faker.ipv6(),
+                "ipv6_mask": faker.ipv6_netmask(),
+                "bgp_peers": [
+                    {
+                        "bfd_enabled": False,
+                        "authentication_key": faker.password(),
+                        "peer_address": faker.ipv4(),
+                        "families": [IPFamily.V4UNICAST],
+                        "is_multi_hop": False,
+                        "rtbh_enabled": True,
+                    },
+                    {
+                        "bfd_enabled": False,
+                        "authentication_key": faker.password(),
+                        "peer_address": faker.ipv6(),
+                        "families": [IPFamily.V6UNICAST],
+                        "is_multi_hop": False,
+                        "rtbh_enabled": True,
+                    },
+                ],
+            },
+        ]
+    )
+    import_nren_l3_core_service(broken_data["path"])
+
+    captured_output, _ = capfd.readouterr()
+    assert f"Edge Port {fake_uuid} not found" in captured_output
+    assert mock_start_process.call_count == 0
diff --git a/test/conftest.py b/test/conftest.py
index ed4cb631e966caa4149356f0c258c2c164d850e3..981768655dfde2eaf4700853488e2a2c6bedb049 100644
--- a/test/conftest.py
+++ b/test/conftest.py
@@ -36,14 +36,18 @@ from gso.main import init_gso_app
 from gso.services.partners import PartnerSchema, create_partner
 from gso.utils.types.interfaces import LAGMember, LAGMemberList
 from test.fixtures import (  # noqa: F401
+    bgp_session_subscription_factory,
+    edge_port_subscription_factory,
     iptrunk_side_subscription_factory,
     iptrunk_subscription_factory,
+    nren_access_port_factory,
+    nren_l3_core_service_subscription_factory,
     office_router_subscription_factory,
     opengear_subscription_factory,
     router_subscription_factory,
+    service_binding_port_factory,
     site_subscription_factory,
     super_pop_switch_subscription_factory,
-    test_workflow,
 )
 
 logging.getLogger("faker.factory").setLevel(logging.WARNING)
@@ -97,6 +101,12 @@ class FakerProvider(BaseProvider):
 
         return site_name
 
+    def ipv4_netmask(self) -> int:
+        return self.generator.random_int(min=1, max=32)
+
+    def ipv6_netmask(self) -> int:
+        return self.generator.random_int(min=1, max=128)
+
     def network_interface(self) -> str:
         return self.generator.numerify("ge-@#/@#/@#")
 
@@ -115,6 +125,9 @@ class FakerProvider(BaseProvider):
             for i in range(iface_amount)
         ]
 
+    def vlan_id(self) -> int:
+        return self.generator.random_int(min=1, max=4095)
+
 
 @pytest.fixture(scope="session")
 def faker() -> Faker:
@@ -265,15 +278,15 @@ def test_client(fastapi_app):
 
 
 @pytest.fixture(scope="session")
-def partner_factory():
+def partner_factory(faker):
     def _create_partner(
-        name: str,
-        email: str,
+        name: str | None = None,
+        email: str | None = None,
     ) -> dict:
         return create_partner(
             PartnerSchema(
-                name=name,
-                email=email,
+                name=name or faker.company(),
+                email=email or faker.email(),
             )
         )
 
diff --git a/test/fixtures.py b/test/fixtures.py
index 6c3928555854b130deea0cf30fc95d3afaed62cd..e2b905c1736879af3087c31d09bc3a605130e43b 100644
--- a/test/fixtures.py
+++ b/test/fixtures.py
@@ -1,4 +1,3 @@
-import ipaddress
 from collections.abc import Generator
 from typing import Any
 from uuid import uuid4
@@ -6,462 +5,15 @@ from uuid import uuid4
 import pytest
 from orchestrator import step, workflow
 from orchestrator.config.assignee import Assignee
-from orchestrator.db import (
-    ProductTable,
-    SubscriptionInstanceTable,
-    SubscriptionInstanceValueTable,
-    SubscriptionTable,
-    db,
-)
-from orchestrator.domain import SubscriptionModel
-from orchestrator.types import SubscriptionLifecycle, UUIDstr
-from orchestrator.utils.datetime import nowtz
+from orchestrator.types import UUIDstr
 from orchestrator.workflow import done, init, inputstep
 from pydantic_forms.core import FormPage
-from pydantic_forms.types import FormGenerator, SubscriptionMapping
+from pydantic_forms.types import FormGenerator
 from pydantic_forms.validators import Choice
 
-from gso.products import ProductName
-from gso.products.product_blocks.iptrunk import (
-    IptrunkInterfaceBlock,
-    IptrunkSideBlock,
-    IptrunkType,
-)
-from gso.products.product_blocks.router import RouterRole
-from gso.products.product_blocks.site import SiteTier
-from gso.products.product_types.iptrunk import ImportedIptrunkInactive, IptrunkInactive
-from gso.products.product_types.office_router import ImportedOfficeRouterInactive, OfficeRouterInactive
-from gso.products.product_types.opengear import ImportedOpengearInactive, OpengearInactive
-from gso.products.product_types.router import ImportedRouterInactive, Router, RouterInactive
-from gso.products.product_types.site import ImportedSiteInactive, Site, SiteInactive
-from gso.products.product_types.super_pop_switch import ImportedSuperPopSwitchInactive, SuperPopSwitchInactive
-from gso.services import subscriptions
-from gso.utils.helpers import iso_from_ipv4
-from gso.utils.shared_enums import Vendor
-from gso.utils.types.interfaces import PhysicalPortCapacity
-from gso.utils.types.ip_address import IPv4AddressType, IPv6AddressType
 from test.workflows import WorkflowInstanceForTests
 
 
-@pytest.fixture()
-def site_subscription_factory(faker, geant_partner):
-    def subscription_create(
-        description=None,
-        start_date="2023-05-24T00:00:00+00:00",
-        site_name=None,
-        site_city=None,
-        site_country=None,
-        site_country_code=None,
-        site_latitude=None,
-        site_longitude=None,
-        site_bgp_community_id=None,
-        site_internal_id=None,
-        site_tier=SiteTier.TIER1,
-        site_ts_address=None,
-        status: SubscriptionLifecycle | None = None,
-        partner: dict | None = None,
-        *,
-        is_imported: bool | None = True,
-    ) -> UUIDstr:
-        if partner is None:
-            partner = geant_partner
-
-        description = description or "Site Subscription"
-        site_name = site_name or faker.site_name()
-        site_city = site_city or faker.city()
-        site_country = site_country or faker.country()
-        site_country_code = site_country_code or faker.country_code()
-        site_latitude = site_latitude or str(faker.latitude())
-        site_longitude = site_longitude or str(faker.longitude())
-        site_bgp_community_id = site_bgp_community_id or faker.pyint()
-        site_internal_id = site_internal_id or faker.pyint()
-        site_ts_address = site_ts_address or faker.ipv4()
-
-        if is_imported:
-            product_id = subscriptions.get_product_id_by_name(ProductName.SITE)
-            site_subscription = SiteInactive.from_product_id(product_id, customer_id=partner["partner_id"], insync=True)
-        else:
-            product_id = subscriptions.get_product_id_by_name(ProductName.IMPORTED_SITE)
-            site_subscription = ImportedSiteInactive.from_product_id(
-                product_id, customer_id=partner["partner_id"], insync=True
-            )
-
-        site_subscription.site.site_city = site_city
-        site_subscription.site.site_name = site_name
-        site_subscription.site.site_country = site_country
-        site_subscription.site.site_country_code = site_country_code
-        site_subscription.site.site_latitude = site_latitude
-        site_subscription.site.site_longitude = site_longitude
-        site_subscription.site.site_bgp_community_id = site_bgp_community_id
-        site_subscription.site.site_internal_id = site_internal_id
-        site_subscription.site.site_tier = site_tier
-        site_subscription.site.site_ts_address = site_ts_address
-
-        site_subscription = SubscriptionModel.from_other_lifecycle(site_subscription, SubscriptionLifecycle.ACTIVE)
-        site_subscription.description = description
-        site_subscription.start_date = start_date
-        if status:
-            site_subscription.status = status
-
-        site_subscription.save()
-        db.session.commit()
-
-        return str(site_subscription.subscription_id)
-
-    return subscription_create
-
-
-@pytest.fixture()
-def router_subscription_factory(site_subscription_factory, faker, geant_partner):
-    def subscription_create(
-        description: str | None = None,
-        start_date: str | None = "2023-05-24T00:00:00+00:00",
-        router_fqdn: str | None = None,
-        router_ts_port: int | None = None,
-        router_lo_ipv4_address: IPv4AddressType | None = None,
-        router_lo_ipv6_address: IPv6AddressType | None = None,
-        router_lo_iso_address: str | None = None,
-        router_role: RouterRole | None = RouterRole.PE,
-        router_site=None,
-        status: SubscriptionLifecycle | None = None,
-        partner: dict | None = None,
-        vendor: Vendor | None = Vendor.NOKIA,
-        *,
-        router_access_via_ts: bool | None = None,
-        is_imported: bool | None = True,
-    ) -> UUIDstr:
-        if partner is None:
-            partner = geant_partner
-        if is_imported:
-            product_id = subscriptions.get_product_id_by_name(ProductName.ROUTER)
-            router_subscription = RouterInactive.from_product_id(
-                product_id, customer_id=partner["partner_id"], insync=True
-            )
-        else:
-            product_id = subscriptions.get_product_id_by_name(ProductName.IMPORTED_ROUTER)
-            router_subscription = ImportedRouterInactive.from_product_id(
-                product_id, customer_id=partner["partner_id"], insync=True
-            )
-
-        router_subscription.router.router_fqdn = router_fqdn or faker.domain_name(levels=4)
-        router_subscription.router.router_ts_port = router_ts_port or faker.port_number(is_user=True)
-        router_subscription.router.router_access_via_ts = router_access_via_ts or faker.boolean()
-        router_subscription.router.router_lo_ipv4_address = router_lo_ipv4_address or ipaddress.IPv4Address(
-            faker.ipv4()
-        )
-        router_subscription.router.router_lo_ipv6_address = router_lo_ipv6_address or ipaddress.IPv6Address(
-            faker.ipv6()
-        )
-        router_subscription.router.router_lo_iso_address = router_lo_iso_address or iso_from_ipv4(faker.ipv4())
-        router_subscription.router.router_role = router_role
-        router_subscription.router.router_site = Site.from_subscription(router_site or site_subscription_factory()).site
-        router_subscription.router.vendor = vendor
-
-        router_subscription = SubscriptionModel.from_other_lifecycle(router_subscription, SubscriptionLifecycle.ACTIVE)
-        router_subscription.insync = True
-        router_subscription.description = description or faker.text(max_nb_chars=30)
-        router_subscription.start_date = start_date
-
-        if status:
-            router_subscription.status = status
-
-        router_subscription.save()
-        db.session.commit()
-
-        return str(router_subscription.subscription_id)
-
-    return subscription_create
-
-
-@pytest.fixture()
-def iptrunk_side_subscription_factory(router_subscription_factory, faker):
-    def subscription_create(
-        iptrunk_side_node=None,
-        iptrunk_side_ae_iface=None,
-        iptrunk_side_ae_geant_a_sid=None,
-        iptrunk_side_ae_members=None,
-        iptrunk_side_ae_members_description=None,
-    ) -> IptrunkSideBlock:
-        iptrunk_side_node_id = iptrunk_side_node or router_subscription_factory()
-        iptrunk_side_node = Router.from_subscription(iptrunk_side_node_id).router
-        iptrunk_side_ae_iface = iptrunk_side_ae_iface or faker.pystr()
-        iptrunk_side_ae_geant_a_sid = iptrunk_side_ae_geant_a_sid or faker.geant_sid()
-        iptrunk_side_ae_members = iptrunk_side_ae_members or [
-            IptrunkInterfaceBlock.new(
-                faker.uuid4(),
-                interface_name=faker.network_interface(),
-                interface_description=faker.sentence(),
-            ),
-            IptrunkInterfaceBlock.new(
-                faker.uuid4(),
-                interface_name=faker.network_interface(),
-                interface_description=faker.sentence(),
-            ),
-        ]
-
-        return IptrunkSideBlock.new(
-            faker.uuid4(),
-            iptrunk_side_node=iptrunk_side_node,
-            iptrunk_side_ae_iface=iptrunk_side_ae_iface,
-            iptrunk_side_ae_geant_a_sid=iptrunk_side_ae_geant_a_sid,
-            iptrunk_side_ae_members=iptrunk_side_ae_members,
-            iptrunk_side_ae_members_description=iptrunk_side_ae_members_description,
-        )
-
-    return subscription_create
-
-
-@pytest.fixture()
-def iptrunk_subscription_factory(iptrunk_side_subscription_factory, faker, geant_partner):
-    def subscription_create(
-        description=None,
-        start_date="2023-05-24T00:00:00+00:00",
-        geant_s_sid=None,
-        iptrunk_description=None,
-        iptrunk_type=IptrunkType.LEASED,
-        iptrunk_speed=PhysicalPortCapacity.ONE_GIGABIT_PER_SECOND,
-        iptrunk_isis_metric=None,
-        iptrunk_ipv4_network=None,
-        iptrunk_ipv6_network=None,
-        iptrunk_sides=None,
-        status: SubscriptionLifecycle | None = None,
-        partner: dict | None = None,
-        *,
-        is_imported: bool | None = True,
-    ) -> UUIDstr:
-        if partner is None:
-            partner = geant_partner
-
-        if is_imported:
-            product_id = subscriptions.get_product_id_by_name(ProductName.IP_TRUNK)
-            iptrunk_subscription = IptrunkInactive.from_product_id(
-                product_id, customer_id=partner["partner_id"], insync=True
-            )
-        else:
-            product_id = subscriptions.get_product_id_by_name(ProductName.IMPORTED_IP_TRUNK)
-            iptrunk_subscription = ImportedIptrunkInactive.from_product_id(
-                product_id, customer_id=partner["partner_id"], insync=True
-            )
-
-        description = description or faker.sentence()
-        geant_s_sid = geant_s_sid or faker.geant_sid()
-        iptrunk_description = iptrunk_description or faker.sentence()
-        iptrunk_isis_metric = iptrunk_isis_metric or faker.pyint()
-        iptrunk_ipv4_network = iptrunk_ipv4_network or faker.ipv4_network(max_subnet=31)
-        iptrunk_ipv6_network = iptrunk_ipv6_network or faker.ipv6_network(max_subnet=126)
-        iptrunk_minimum_links = 1
-        iptrunk_side_a = iptrunk_side_subscription_factory()
-        iptrunk_side_b = iptrunk_side_subscription_factory()
-        iptrunk_sides = iptrunk_sides or [iptrunk_side_a, iptrunk_side_b]
-
-        iptrunk_subscription.iptrunk.geant_s_sid = geant_s_sid
-        iptrunk_subscription.iptrunk.iptrunk_description = iptrunk_description
-        iptrunk_subscription.iptrunk.iptrunk_type = iptrunk_type
-        iptrunk_subscription.iptrunk.iptrunk_speed = iptrunk_speed
-        iptrunk_subscription.iptrunk.iptrunk_minimum_links = iptrunk_minimum_links
-        iptrunk_subscription.iptrunk.iptrunk_isis_metric = iptrunk_isis_metric
-        iptrunk_subscription.iptrunk.iptrunk_ipv4_network = iptrunk_ipv4_network
-        iptrunk_subscription.iptrunk.iptrunk_ipv6_network = iptrunk_ipv6_network
-        iptrunk_subscription.iptrunk.iptrunk_sides = iptrunk_sides
-
-        iptrunk_subscription = SubscriptionModel.from_other_lifecycle(
-            iptrunk_subscription,
-            SubscriptionLifecycle.ACTIVE,
-        )
-
-        if status:
-            iptrunk_subscription.status = status
-
-        iptrunk_subscription.description = description
-        iptrunk_subscription.start_date = start_date
-        iptrunk_subscription.save()
-        db.session.commit()
-
-        return str(iptrunk_subscription.subscription_id)
-
-    return subscription_create
-
-
-@pytest.fixture()
-def office_router_subscription_factory(site_subscription_factory, faker, geant_partner):
-    def subscription_create(
-        description=None,
-        start_date="2023-05-24T00:00:00+00:00",
-        office_router_fqdn=None,
-        office_router_ts_port=None,
-        office_router_lo_ipv4_address=None,
-        office_router_lo_ipv6_address=None,
-        office_router_site=None,
-        status: SubscriptionLifecycle | None = None,
-        partner: dict | None = None,
-        *,
-        is_imported: bool | None = True,
-    ) -> UUIDstr:
-        if partner is None:
-            partner = geant_partner
-
-        description = description or faker.text(max_nb_chars=30)
-        office_router_fqdn = office_router_fqdn or faker.domain_name(levels=4)
-        office_router_ts_port = office_router_ts_port or faker.random_int(min=1, max=49151)
-        office_router_lo_ipv4_address = office_router_lo_ipv4_address or ipaddress.IPv4Address(faker.ipv4())
-        office_router_lo_ipv6_address = office_router_lo_ipv6_address or ipaddress.IPv6Address(faker.ipv6())
-        office_router_site = office_router_site or site_subscription_factory()
-
-        if is_imported:
-            product_id = subscriptions.get_product_id_by_name(ProductName.OFFICE_ROUTER)
-            office_router_subscription = OfficeRouterInactive.from_product_id(
-                product_id, customer_id=partner["partner_id"], insync=True
-            )
-        else:
-            product_id = subscriptions.get_product_id_by_name(ProductName.IMPORTED_OFFICE_ROUTER)
-            office_router_subscription = ImportedOfficeRouterInactive.from_product_id(
-                product_id, customer_id=partner["partner_id"], insync=True
-            )
-
-        office_router_subscription.office_router.office_router_fqdn = office_router_fqdn
-        office_router_subscription.office_router.office_router_ts_port = office_router_ts_port
-        office_router_subscription.office_router.office_router_lo_ipv4_address = office_router_lo_ipv4_address
-        office_router_subscription.office_router.office_router_lo_ipv6_address = office_router_lo_ipv6_address
-        office_router_subscription.office_router.office_router_site = Site.from_subscription(office_router_site).site
-        office_router_subscription.office_router.vendor = Vendor.NOKIA
-
-        office_router_subscription = SubscriptionModel.from_other_lifecycle(
-            office_router_subscription, SubscriptionLifecycle.ACTIVE
-        )
-        office_router_subscription.description = description
-        office_router_subscription.start_date = start_date
-
-        if status:
-            office_router_subscription.status = status
-
-        office_router_subscription.save()
-        db.session.commit()
-
-        return str(office_router_subscription.subscription_id)
-
-    return subscription_create
-
-
-@pytest.fixture()
-def super_pop_switch_subscription_factory(site_subscription_factory, faker, geant_partner):
-    def subscription_create(
-        description=None,
-        start_date="2023-05-24T00:00:00+00:00",
-        super_pop_switch_fqdn=None,
-        super_pop_switch_ts_port=None,
-        super_pop_switch_mgmt_ipv4_address=None,
-        super_pop_switch_site=None,
-        status: SubscriptionLifecycle | None = None,
-        partner: dict | None = None,
-        *,
-        is_imported: bool | None = True,
-    ) -> UUIDstr:
-        if partner is None:
-            partner = geant_partner
-
-        description = description or faker.text(max_nb_chars=30)
-        super_pop_switch_fqdn = super_pop_switch_fqdn or faker.domain_name(levels=4)
-        super_pop_switch_ts_port = super_pop_switch_ts_port or faker.random_int(min=1, max=49151)
-        super_pop_switch_mgmt_ipv4_address = super_pop_switch_mgmt_ipv4_address or ipaddress.IPv4Address(faker.ipv4())
-        super_pop_switch_site = super_pop_switch_site or site_subscription_factory()
-
-        if is_imported:
-            product_id = subscriptions.get_product_id_by_name(ProductName.SUPER_POP_SWITCH)
-            super_pop_switch_subscription = SuperPopSwitchInactive.from_product_id(
-                product_id, customer_id=partner["partner_id"], insync=True
-            )
-        else:
-            product_id = subscriptions.get_product_id_by_name(ProductName.IMPORTED_SUPER_POP_SWITCH)
-            super_pop_switch_subscription = ImportedSuperPopSwitchInactive.from_product_id(
-                product_id, customer_id=partner["partner_id"], insync=True
-            )
-
-        super_pop_switch_subscription.super_pop_switch.super_pop_switch_fqdn = super_pop_switch_fqdn
-        super_pop_switch_subscription.super_pop_switch.super_pop_switch_ts_port = super_pop_switch_ts_port
-        super_pop_switch_subscription.super_pop_switch.super_pop_switch_mgmt_ipv4_address = (
-            super_pop_switch_mgmt_ipv4_address
-        )
-        super_pop_switch_subscription.super_pop_switch.super_pop_switch_site = Site.from_subscription(
-            super_pop_switch_site
-        ).site
-        super_pop_switch_subscription.super_pop_switch.vendor = Vendor.NOKIA
-
-        super_pop_switch_subscription = SubscriptionModel.from_other_lifecycle(
-            super_pop_switch_subscription, SubscriptionLifecycle.ACTIVE
-        )
-        super_pop_switch_subscription.description = description
-        super_pop_switch_subscription.start_date = start_date
-
-        if status:
-            super_pop_switch_subscription.status = status
-
-        super_pop_switch_subscription.save()
-        db.session.commit()
-
-        return str(super_pop_switch_subscription.subscription_id)
-
-    return subscription_create
-
-
-@pytest.fixture()
-def opengear_subscription_factory(site_subscription_factory, faker, geant_partner):
-    def subscription_create(
-        description=None,
-        start_date="2023-05-24T00:00:00+00:00",
-        opengear_site=None,
-        opengear_hostname=None,
-        opengear_wan_address=None,
-        opengear_wan_netmask=None,
-        opengear_wan_gateway=None,
-        status: SubscriptionLifecycle | None = None,
-        partner: dict | None = None,
-        *,
-        is_imported: bool | None = True,
-    ) -> UUIDstr:
-        if partner is None:
-            partner = geant_partner
-
-        description = description or faker.text(max_nb_chars=30)
-        opengear_site = opengear_site or site_subscription_factory()
-        opengear_hostname = opengear_hostname or faker.domain_name(levels=4)
-        opengear_wan_address = opengear_wan_address or faker.ipv4()
-        opengear_wan_netmask = opengear_wan_netmask or faker.ipv4()
-        opengear_wan_gateway = opengear_wan_gateway or faker.ipv4()
-
-        if is_imported:
-            product_id = subscriptions.get_product_id_by_name(ProductName.OPENGEAR)
-            opengear_subscription = OpengearInactive.from_product_id(
-                product_id, customer_id=partner["partner_id"], insync=True
-            )
-        else:
-            product_id = subscriptions.get_product_id_by_name(ProductName.IMPORTED_OPENGEAR)
-            opengear_subscription = ImportedOpengearInactive.from_product_id(
-                product_id, customer_id=partner["partner_id"], insync=True
-            )
-
-        opengear_subscription.opengear.opengear_site = Site.from_subscription(opengear_site).site
-        opengear_subscription.opengear.opengear_hostname = opengear_hostname
-        opengear_subscription.opengear.opengear_wan_address = opengear_wan_address
-        opengear_subscription.opengear.opengear_wan_netmask = opengear_wan_netmask
-        opengear_subscription.opengear.opengear_wan_gateway = opengear_wan_gateway
-
-        opengear_subscription = SubscriptionModel.from_other_lifecycle(
-            opengear_subscription, SubscriptionLifecycle.ACTIVE
-        )
-        opengear_subscription.description = description
-        opengear_subscription.start_date = start_date
-
-        if status:
-            opengear_subscription.status = status
-
-        opengear_subscription.save()
-        db.session.commit()
-
-        return str(opengear_subscription.subscription_id)
-
-    return subscription_create
-
-
 @pytest.fixture()
 def test_workflow(generic_subscription_1: UUIDstr, generic_product_type_1) -> Generator:
     _, generic_product_one = generic_product_type_1
@@ -497,73 +49,3 @@ def test_workflow(generic_subscription_1: UUIDstr, generic_product_type_1) -> Ge
 
     with WorkflowInstanceForTests(workflow_for_testing_processes_py, "workflow_for_testing_processes_py") as wf:
         yield wf
-
-
-def create_subscription_for_mapping(
-    product: ProductTable, mapping: SubscriptionMapping, values: dict[str, Any], **kwargs: Any
-) -> SubscriptionTable:
-    """Create a subscription in the test coredb for the given subscription_mapping and values.
-
-    This function handles optional resource types starting with a ? in the mapping not supplied in the values array.
-
-    Args:
-        product: the ProductTable to create a sub for
-        mapping: the subscription_mapping belonging to that product
-        values: a dictionary of keys from the sub_map and their corresponding test values
-        kwargs: The rest of the arguments
-
-    Returns: The conforming subscription.
-    """
-
-    def build_instance(name, value_mapping):
-        block = product.find_block_by_name(name)
-
-        def build_value(rt, value):
-            resource_type = block.find_resource_type_by_name(rt)
-            return SubscriptionInstanceValueTable(resource_type_id=resource_type.resource_type_id, value=value)
-
-        return SubscriptionInstanceTable(
-            product_block_id=block.product_block_id,
-            values=[
-                build_value(resource_type, values[value_key]) for (resource_type, value_key) in value_mapping.items()
-            ],
-        )
-
-    # recreate the mapping: leave out the ?keys if no value supplied for them
-    mapping = {
-        name: [
-            {
-                **{k: value_map[k] for k in value_map if not value_map[k].startswith("?")},
-                **{
-                    k: value_map[k][1:]
-                    for k in value_map
-                    if value_map[k].startswith("?") and value_map[k][1:] in values
-                },
-            }
-            for value_map in mapping[name]
-        ]
-        for name in mapping
-    }
-
-    instances = [
-        build_instance(name, value_mapping)
-        for (name, value_mappings) in mapping.items()
-        for value_mapping in value_mappings
-    ]
-
-    return create_subscription(instances=instances, product=product, **kwargs)
-
-
-def create_subscription(**kwargs):
-    attrs = {
-        "description": "A subscription.",
-        "customer_id": kwargs.get("customer_id", "85938c4c-0a11-e511-80d0-005056956c1a"),
-        "start_date": nowtz(),
-        "status": "active",
-        "insync": True,
-        **kwargs,
-    }
-    o = SubscriptionTable(**attrs)
-    db.session.add(o)
-    db.session.commit()
-    return o
diff --git a/test/fixtures/__init__.py b/test/fixtures/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..0b69c5fb506e854a84e6e0e908826fcc250ec08e
--- /dev/null
+++ b/test/fixtures/__init__.py
@@ -0,0 +1,28 @@
+from test.fixtures.edge_port_fixtures import edge_port_subscription_factory
+from test.fixtures.iptrunk_fixtures import iptrunk_side_subscription_factory, iptrunk_subscription_factory
+from test.fixtures.nren_l3_core_service_fixtures import (
+    bgp_session_subscription_factory,
+    nren_access_port_factory,
+    nren_l3_core_service_subscription_factory,
+    service_binding_port_factory,
+)
+from test.fixtures.office_router_fixtures import office_router_subscription_factory
+from test.fixtures.opengear_fixtures import opengear_subscription_factory
+from test.fixtures.router_fixtures import router_subscription_factory
+from test.fixtures.site_fixtures import site_subscription_factory
+from test.fixtures.super_pop_switch_fixtures import super_pop_switch_subscription_factory
+
+__all__ = [
+    "bgp_session_subscription_factory",
+    "edge_port_subscription_factory",
+    "iptrunk_side_subscription_factory",
+    "iptrunk_subscription_factory",
+    "nren_access_port_factory",
+    "nren_l3_core_service_subscription_factory",
+    "office_router_subscription_factory",
+    "opengear_subscription_factory",
+    "router_subscription_factory",
+    "service_binding_port_factory",
+    "site_subscription_factory",
+    "super_pop_switch_subscription_factory",
+]
diff --git a/test/fixtures/common_fixtures.py b/test/fixtures/common_fixtures.py
new file mode 100644
index 0000000000000000000000000000000000000000..aeac5a991c3afda7b32c7da7ff9b5e223f630aab
--- /dev/null
+++ b/test/fixtures/common_fixtures.py
@@ -0,0 +1,81 @@
+from typing import Any
+
+from orchestrator.db import (
+    ProductTable,
+    SubscriptionInstanceTable,
+    SubscriptionInstanceValueTable,
+    SubscriptionTable,
+    db,
+)
+from orchestrator.utils.datetime import nowtz
+from pydantic_forms.types import SubscriptionMapping
+
+
+def create_subscription_for_mapping(
+    product: ProductTable, mapping: SubscriptionMapping, values: dict[str, Any], **kwargs: Any
+) -> SubscriptionTable:
+    """Create a subscription in the test coredb for the given subscription_mapping and values.
+
+    This function handles optional resource types starting with a ? in the mapping not supplied in the values array.
+
+    Args:
+        product: the ProductTable to create a sub for
+        mapping: the subscription_mapping belonging to that product
+        values: a dictionary of keys from the sub_map and their corresponding test values
+        kwargs: The rest of the arguments
+
+    Returns: The conforming subscription.
+    """
+
+    def build_instance(name, value_mapping):
+        block = product.find_block_by_name(name)
+
+        def build_value(rt, value):
+            resource_type = block.find_resource_type_by_name(rt)
+            return SubscriptionInstanceValueTable(resource_type_id=resource_type.resource_type_id, value=value)
+
+        return SubscriptionInstanceTable(
+            product_block_id=block.product_block_id,
+            values=[
+                build_value(resource_type, values[value_key]) for (resource_type, value_key) in value_mapping.items()
+            ],
+        )
+
+    # recreate the mapping: leave out the ?keys if no value supplied for them
+    mapping = {
+        name: [
+            {
+                **{k: value_map[k] for k in value_map if not value_map[k].startswith("?")},
+                **{
+                    k: value_map[k][1:]
+                    for k in value_map
+                    if value_map[k].startswith("?") and value_map[k][1:] in values
+                },
+            }
+            for value_map in mapping[name]
+        ]
+        for name in mapping
+    }
+
+    instances = [
+        build_instance(name, value_mapping)
+        for (name, value_mappings) in mapping.items()
+        for value_mapping in value_mappings
+    ]
+
+    return create_subscription(instances=instances, product=product, **kwargs)
+
+
+def create_subscription(**kwargs):
+    attrs = {
+        "description": "A subscription.",
+        "customer_id": kwargs.get("customer_id", "85938c4c-0a11-e511-80d0-005056956c1a"),
+        "start_date": nowtz(),
+        "status": "active",
+        "insync": True,
+        **kwargs,
+    }
+    o = SubscriptionTable(**attrs)
+    db.session.add(o)
+    db.session.commit()
+    return o
diff --git a/test/fixtures/edge_port_fixtures.py b/test/fixtures/edge_port_fixtures.py
new file mode 100644
index 0000000000000000000000000000000000000000..669d42ff90b4e0894ca1d117dde58448daab0903
--- /dev/null
+++ b/test/fixtures/edge_port_fixtures.py
@@ -0,0 +1,93 @@
+import pytest
+from orchestrator.db import db
+from orchestrator.domain import SubscriptionModel
+from orchestrator.types import SubscriptionLifecycle, UUIDstr
+
+from gso.products import ProductName
+from gso.products.product_blocks.edge_port import (
+    EdgePortAEMemberBlock,
+    EdgePortType,
+    EncapsulationType,
+)
+from gso.products.product_types.edge_port import EdgePortInactive, ImportedEdgePortInactive
+from gso.products.product_types.router import Router
+from gso.services import subscriptions
+from gso.utils.shared_enums import Vendor
+from gso.utils.types.interfaces import PhysicalPortCapacity
+
+
+@pytest.fixture()
+def edge_port_subscription_factory(faker, partner_factory, router_subscription_factory):
+    def subscription_create(
+        description=None,
+        partner: dict | None = None,
+        start_date="2023-05-24T00:00:00+00:00",
+        node=None,
+        name=None,
+        edge_port_description=None,
+        encapsulation=EncapsulationType.DOT1Q,
+        mac_address=None,
+        member_speed=PhysicalPortCapacity.HUNDRED_GIGABIT_PER_SECOND,
+        minimum_links=None,
+        edge_port_type=EdgePortType.PUBLIC,
+        geant_ga_id=None,
+        edge_port_ae_members=None,
+        status: SubscriptionLifecycle | None = None,
+        *,
+        enable_lacp=True,
+        ignore_if_down=False,
+        is_imported=True,
+    ) -> UUIDstr:
+        partner = partner or partner_factory()
+        node = Router.from_subscription(router_subscription_factory(vendor=Vendor.NOKIA)).router
+        if is_imported:
+            product_id = subscriptions.get_product_id_by_name(ProductName.EDGE_PORT)
+            edge_port_subscription = EdgePortInactive.from_product_id(
+                product_id, customer_id=partner["partner_id"], insync=True
+            )
+        else:
+            product_id = subscriptions.get_product_id_by_name(ProductName.IMPORTED_EDGE_PORT)
+            edge_port_subscription = ImportedEdgePortInactive.from_product_id(
+                product_id, customer_id=partner["partner_id"], insync=True
+            )
+
+        edge_port_subscription.edge_port.edge_port_description = description or faker.text(max_nb_chars=30)
+        edge_port_subscription.edge_port.geant_ga_id = geant_ga_id or faker.geant_sid()
+        edge_port_subscription.edge_port.node = node or node
+        edge_port_subscription.edge_port.edge_port_name = name or f"lag-{faker.pyint(21, 50)}"
+        edge_port_subscription.edge_port.edge_port_description = edge_port_description or faker.sentence()
+        edge_port_subscription.edge_port.enable_lacp = enable_lacp
+        edge_port_subscription.edge_port.encapsulation = encapsulation
+        edge_port_subscription.edge_port.mac_address = mac_address or faker.mac_address()
+        edge_port_subscription.edge_port.member_speed = member_speed
+        edge_port_subscription.edge_port.minimum_links = minimum_links or faker.pyint(1, 2)
+        edge_port_subscription.edge_port.edge_port_type = edge_port_type
+        edge_port_subscription.edge_port.ignore_if_down = ignore_if_down
+        edge_port_subscription.edge_port.edge_port_ae_members = edge_port_ae_members or [
+            EdgePortAEMemberBlock.new(
+                faker.uuid4(),
+                interface_name="Interface2",
+                interface_description=faker.sentence(),
+            ),
+            EdgePortAEMemberBlock.new(
+                faker.uuid4(),
+                interface_name="Interface3",
+                interface_description=faker.sentence(),
+            ),
+        ]
+        edge_port_subscription = SubscriptionModel.from_other_lifecycle(
+            edge_port_subscription,
+            SubscriptionLifecycle.ACTIVE,
+        )
+
+        if status:
+            edge_port_subscription.status = status
+
+        edge_port_subscription.description = description or faker.text(max_nb_chars=30)
+        edge_port_subscription.start_date = start_date
+        edge_port_subscription.save()
+        db.session.commit()
+
+        return str(edge_port_subscription.subscription_id)
+
+    return subscription_create
diff --git a/test/fixtures/iptrunk_fixtures.py b/test/fixtures/iptrunk_fixtures.py
new file mode 100644
index 0000000000000000000000000000000000000000..70ccdeb547d75ae1fc605b9c1f9e9dfb4bafa6ca
--- /dev/null
+++ b/test/fixtures/iptrunk_fixtures.py
@@ -0,0 +1,125 @@
+import pytest
+from orchestrator.db import db
+from orchestrator.domain import SubscriptionModel
+from orchestrator.types import SubscriptionLifecycle, UUIDstr
+
+from gso.products import ProductName
+from gso.products.product_blocks.iptrunk import (
+    IptrunkInterfaceBlock,
+    IptrunkSideBlock,
+    IptrunkType,
+)
+from gso.products.product_types.iptrunk import ImportedIptrunkInactive, IptrunkInactive
+from gso.products.product_types.router import Router
+from gso.services import subscriptions
+from gso.utils.shared_enums import Vendor
+from gso.utils.types.interfaces import PhysicalPortCapacity
+
+
+@pytest.fixture()
+def iptrunk_side_subscription_factory(router_subscription_factory, faker):
+    def subscription_create(
+        iptrunk_side_node=None,
+        iptrunk_side_ae_iface=None,
+        iptrunk_side_ae_geant_a_sid=None,
+        iptrunk_side_ae_members=None,
+        iptrunk_side_ae_members_description=None,
+    ) -> IptrunkSideBlock:
+        iptrunk_side_node_id = iptrunk_side_node or router_subscription_factory(vendor=Vendor.NOKIA)
+        iptrunk_side_node = Router.from_subscription(iptrunk_side_node_id).router
+        iptrunk_side_ae_iface = iptrunk_side_ae_iface or faker.pystr()
+        iptrunk_side_ae_geant_a_sid = iptrunk_side_ae_geant_a_sid or faker.geant_sid()
+        iptrunk_side_ae_members = iptrunk_side_ae_members or [
+            IptrunkInterfaceBlock.new(
+                faker.uuid4(),
+                interface_name=faker.network_interface(),
+                interface_description=faker.sentence(),
+            ),
+            IptrunkInterfaceBlock.new(
+                faker.uuid4(),
+                interface_name=faker.network_interface(),
+                interface_description=faker.sentence(),
+            ),
+        ]
+
+        return IptrunkSideBlock.new(
+            faker.uuid4(),
+            iptrunk_side_node=iptrunk_side_node,
+            iptrunk_side_ae_iface=iptrunk_side_ae_iface,
+            iptrunk_side_ae_geant_a_sid=iptrunk_side_ae_geant_a_sid,
+            iptrunk_side_ae_members=iptrunk_side_ae_members,
+            iptrunk_side_ae_members_description=iptrunk_side_ae_members_description,
+        )
+
+    return subscription_create
+
+
+@pytest.fixture()
+def iptrunk_subscription_factory(iptrunk_side_subscription_factory, faker, geant_partner):
+    def subscription_create(
+        description=None,
+        start_date="2023-05-24T00:00:00+00:00",
+        geant_s_sid=None,
+        iptrunk_description=None,
+        iptrunk_type=IptrunkType.LEASED,
+        iptrunk_speed=PhysicalPortCapacity.ONE_GIGABIT_PER_SECOND,
+        iptrunk_isis_metric=None,
+        iptrunk_ipv4_network=None,
+        iptrunk_ipv6_network=None,
+        iptrunk_sides=None,
+        status: SubscriptionLifecycle | None = None,
+        partner: dict | None = None,
+        *,
+        is_imported: bool | None = True,
+    ) -> UUIDstr:
+        if partner is None:
+            partner = geant_partner
+
+        if is_imported:
+            product_id = subscriptions.get_product_id_by_name(ProductName.IP_TRUNK)
+            iptrunk_subscription = IptrunkInactive.from_product_id(
+                product_id, customer_id=partner["partner_id"], insync=True
+            )
+        else:
+            product_id = subscriptions.get_product_id_by_name(ProductName.IMPORTED_IP_TRUNK)
+            iptrunk_subscription = ImportedIptrunkInactive.from_product_id(
+                product_id, customer_id=partner["partner_id"], insync=True
+            )
+
+        description = description or faker.sentence()
+        geant_s_sid = geant_s_sid or faker.geant_sid()
+        iptrunk_description = iptrunk_description or faker.sentence()
+        iptrunk_isis_metric = iptrunk_isis_metric or faker.pyint()
+        iptrunk_ipv4_network = iptrunk_ipv4_network or faker.ipv4_network(max_subnet=31)
+        iptrunk_ipv6_network = iptrunk_ipv6_network or faker.ipv6_network(max_subnet=126)
+        iptrunk_minimum_links = 1
+        iptrunk_side_a = iptrunk_side_subscription_factory()
+        iptrunk_side_b = iptrunk_side_subscription_factory()
+        iptrunk_sides = iptrunk_sides or [iptrunk_side_a, iptrunk_side_b]
+
+        iptrunk_subscription.iptrunk.geant_s_sid = geant_s_sid
+        iptrunk_subscription.iptrunk.iptrunk_description = iptrunk_description
+        iptrunk_subscription.iptrunk.iptrunk_type = iptrunk_type
+        iptrunk_subscription.iptrunk.iptrunk_speed = iptrunk_speed
+        iptrunk_subscription.iptrunk.iptrunk_minimum_links = iptrunk_minimum_links
+        iptrunk_subscription.iptrunk.iptrunk_isis_metric = iptrunk_isis_metric
+        iptrunk_subscription.iptrunk.iptrunk_ipv4_network = iptrunk_ipv4_network
+        iptrunk_subscription.iptrunk.iptrunk_ipv6_network = iptrunk_ipv6_network
+        iptrunk_subscription.iptrunk.iptrunk_sides = iptrunk_sides
+
+        iptrunk_subscription = SubscriptionModel.from_other_lifecycle(
+            iptrunk_subscription,
+            SubscriptionLifecycle.ACTIVE,
+        )
+
+        if status:
+            iptrunk_subscription.status = status
+
+        iptrunk_subscription.description = description
+        iptrunk_subscription.start_date = start_date
+        iptrunk_subscription.save()
+        db.session.commit()
+
+        return str(iptrunk_subscription.subscription_id)
+
+    return subscription_create
diff --git a/test/fixtures/nren_l3_core_service_fixtures.py b/test/fixtures/nren_l3_core_service_fixtures.py
new file mode 100644
index 0000000000000000000000000000000000000000..d0fd069a03b4b969e2fd286aaa7f57f5716f10c7
--- /dev/null
+++ b/test/fixtures/nren_l3_core_service_fixtures.py
@@ -0,0 +1,173 @@
+import random
+from uuid import uuid4
+
+import pytest
+from orchestrator.db import db
+from orchestrator.domain import SubscriptionModel
+from orchestrator.types import SubscriptionLifecycle, UUIDstr
+
+from gso.products import ProductName
+from gso.products.product_blocks.bgp_session import BGPSession, IPFamily
+from gso.products.product_blocks.nren_l3_core_service import NRENAccessPort
+from gso.products.product_blocks.service_binding_port import ServiceBindingPort
+from gso.products.product_types.edge_port import EdgePort
+from gso.products.product_types.nren_l3_core_service import (
+    ImportedNRENL3CoreService,
+    NRENL3CoreServiceInactive,
+    NRENL3CoreServiceType,
+)
+from gso.services import subscriptions
+from gso.utils.shared_enums import APType, SBPType
+from gso.utils.types.ip_address import IPAddress
+
+
+@pytest.fixture()
+def bgp_session_subscription_factory(faker):
+    def create_bgp_session(
+        peer_address: IPAddress | None = None,
+        bfd_interval: int = 2,
+        bfd_multiplier: int = 2,
+        families: list[IPFamily] | None = None,
+        authentication_key: str | None = None,
+        *,
+        is_multi_hop: bool = False,
+        has_custom_policies: bool = False,
+        bfd_enabled: bool = True,
+        multipath_enabled: bool | None = True,
+        send_default_route: bool | None = True,
+        is_passive: bool | None = False,
+        rtbh_enabled: bool | None = False,
+    ):
+        return BGPSession.new(
+            subscription_id=uuid4(),
+            peer_address=peer_address or faker.ipv4(),
+            bfd_enabled=bfd_enabled,
+            families=families or [IPFamily.V4UNICAST],
+            has_custom_policies=has_custom_policies,
+            authentication_key=authentication_key or faker.password(),
+            multipath_enabled=multipath_enabled,
+            send_default_route=send_default_route,
+            is_multi_hop=is_multi_hop,
+            bfd_interval=bfd_interval,
+            bfd_multiplier=bfd_multiplier,
+            rtbh_enabled=rtbh_enabled,
+            is_passive=is_passive,
+        )
+
+    return create_bgp_session
+
+
+@pytest.fixture()
+def service_binding_port_factory(faker, bgp_session_subscription_factory, edge_port_subscription_factory):
+    def create_service_binding_port(
+        bgp_session_list: list[BGPSession] | None = None,
+        geant_sid: str | None = None,
+        sbp_type: SBPType = SBPType.L3,
+        ipv4_address: str | None = None,
+        ipv4_mask: int | None = None,
+        ipv6_address: str | None = None,
+        ipv6_mask: int | None = None,
+        vlan_id: int | None = None,
+        edge_port: EdgePort | None = None,
+        *,
+        custom_firewall_filters: bool = False,
+        is_tagged: bool = False,
+    ):
+        return ServiceBindingPort.new(
+            subscription_id=uuid4(),
+            is_tagged=is_tagged,
+            vlan_id=vlan_id or faker.vlan_id(),
+            sbp_type=sbp_type,
+            ipv4_address=ipv4_address or faker.ipv4(),
+            ipv4_mask=ipv4_mask or faker.ipv4_netmask(),
+            ipv6_address=ipv6_address or faker.ipv6(),
+            ipv6_mask=ipv6_mask or faker.ipv6_netmask(),
+            custom_firewall_filters=custom_firewall_filters,
+            geant_sid=geant_sid or faker.geant_sid(),
+            bgp_session_list=bgp_session_list
+            or [
+                bgp_session_subscription_factory(families=[IPFamily.V4UNICAST]),
+                bgp_session_subscription_factory(families=[IPFamily.V6UNICAST], peer_address=faker.ipv6()),
+            ],
+            edge_port=edge_port or EdgePort.from_subscription(edge_port_subscription_factory()).edge_port,
+        )
+
+    return create_service_binding_port
+
+
+@pytest.fixture()
+def nren_access_port_factory(faker, service_binding_port_factory):
+    def create_nren_access_port(
+        nren_ap_type: APType | None = None,
+        service_binding_port: ServiceBindingPort | None = None,
+    ):
+        return NRENAccessPort.new(
+            subscription_id=uuid4(),
+            ap_type=nren_ap_type or random.choice(list(APType)),  # noqa: S311
+            sbp=service_binding_port or service_binding_port_factory(),
+        )
+
+    return create_nren_access_port
+
+
+@pytest.fixture()
+def nren_l3_core_service_subscription_factory(
+    faker,
+    partner_factory,
+    nren_access_port_factory,
+):
+    def create_nren_l3_core_service_subscription(
+        nren_l3_core_service_type: NRENL3CoreServiceType,
+        description=None,
+        partner: dict | None = None,
+        nren_ap_list: list[NRENAccessPort] | None = None,
+        start_date="2023-05-24T00:00:00+00:00",
+        status: SubscriptionLifecycle | None = None,
+    ) -> UUIDstr:
+        partner = partner or partner_factory()
+        match nren_l3_core_service_type:
+            case NRENL3CoreServiceType.GEANT_IP:
+                product_id = subscriptions.get_product_id_by_name(ProductName.GEANT_IP)
+                nren_l3_core_service_subscription = NRENL3CoreServiceInactive.from_product_id(
+                    product_id, customer_id=partner["partner_id"], insync=True
+                )
+            case NRENL3CoreServiceType.IMPORTED_GEANT_IP:
+                product_id = subscriptions.get_product_id_by_name(ProductName.IMPORTED_GEANT_IP)
+                nren_l3_core_service_subscription = ImportedNRENL3CoreService.from_product_id(
+                    product_id, customer_id=partner["partner_id"], insync=True
+                )
+            case NRENL3CoreServiceType.IAS:
+                product_id = subscriptions.get_product_id_by_name(ProductName.IAS)
+                nren_l3_core_service_subscription = NRENL3CoreServiceInactive.from_product_id(
+                    product_id, customer_id=partner["partner_id"], insync=True
+                )
+            case NRENL3CoreServiceType.IMPORTED_IAS:
+                product_id = subscriptions.get_product_id_by_name(ProductName.IMPORTED_IAS)
+                nren_l3_core_service_subscription = ImportedNRENL3CoreService.from_product_id(
+                    product_id, customer_id=partner["partner_id"], insync=True
+                )
+            case _:
+                msg = f"NREN L3 Core Service type not found: {nren_l3_core_service_type}"
+                raise ValueError(msg)
+
+        # Default nren_ap_list creation with primary and backup access ports
+        nren_l3_core_service_subscription.nren_l3_core_service.nren_ap_list = nren_ap_list or [
+            nren_access_port_factory(nren_ap_type=APType.PRIMARY),
+            nren_access_port_factory(nren_ap_type=APType.BACKUP),
+        ]
+
+        # Update subscription with description, start date, and status
+        nren_l3_core_service_subscription = SubscriptionModel.from_other_lifecycle(
+            nren_l3_core_service_subscription,
+            SubscriptionLifecycle.ACTIVE,
+        )
+        nren_l3_core_service_subscription.description = description or faker.sentence()
+        nren_l3_core_service_subscription.start_date = start_date
+        nren_l3_core_service_subscription.status = status or SubscriptionLifecycle.ACTIVE
+        nren_l3_core_service_subscription.save()
+
+        db.session.commit()
+
+        return str(nren_l3_core_service_subscription.subscription_id)
+
+    return create_nren_l3_core_service_subscription
diff --git a/test/fixtures/office_router_fixtures.py b/test/fixtures/office_router_fixtures.py
new file mode 100644
index 0000000000000000000000000000000000000000..0c55ada08ae12b730a491f15eedd42af15e23b6d
--- /dev/null
+++ b/test/fixtures/office_router_fixtures.py
@@ -0,0 +1,72 @@
+import ipaddress
+
+import pytest
+from orchestrator.db import db
+from orchestrator.domain import SubscriptionModel
+from orchestrator.types import SubscriptionLifecycle, UUIDstr
+
+from gso.products import ProductName
+from gso.products.product_types.office_router import ImportedOfficeRouterInactive, OfficeRouterInactive
+from gso.products.product_types.site import Site
+from gso.services import subscriptions
+from gso.utils.shared_enums import Vendor
+
+
+@pytest.fixture()
+def office_router_subscription_factory(site_subscription_factory, faker, geant_partner):
+    def subscription_create(
+        description=None,
+        start_date="2023-05-24T00:00:00+00:00",
+        office_router_fqdn=None,
+        office_router_ts_port=None,
+        office_router_lo_ipv4_address=None,
+        office_router_lo_ipv6_address=None,
+        office_router_site=None,
+        status: SubscriptionLifecycle | None = None,
+        partner: dict | None = None,
+        *,
+        is_imported: bool | None = True,
+    ) -> UUIDstr:
+        if partner is None:
+            partner = geant_partner
+
+        description = description or faker.text(max_nb_chars=30)
+        office_router_fqdn = office_router_fqdn or faker.domain_name(levels=4)
+        office_router_ts_port = office_router_ts_port or faker.random_int(min=1, max=49151)
+        office_router_lo_ipv4_address = office_router_lo_ipv4_address or ipaddress.IPv4Address(faker.ipv4())
+        office_router_lo_ipv6_address = office_router_lo_ipv6_address or ipaddress.IPv6Address(faker.ipv6())
+        office_router_site = office_router_site or site_subscription_factory()
+
+        if is_imported:
+            product_id = subscriptions.get_product_id_by_name(ProductName.OFFICE_ROUTER)
+            office_router_subscription = OfficeRouterInactive.from_product_id(
+                product_id, customer_id=partner["partner_id"], insync=True
+            )
+        else:
+            product_id = subscriptions.get_product_id_by_name(ProductName.IMPORTED_OFFICE_ROUTER)
+            office_router_subscription = ImportedOfficeRouterInactive.from_product_id(
+                product_id, customer_id=partner["partner_id"], insync=True
+            )
+
+        office_router_subscription.office_router.office_router_fqdn = office_router_fqdn
+        office_router_subscription.office_router.office_router_ts_port = office_router_ts_port
+        office_router_subscription.office_router.office_router_lo_ipv4_address = office_router_lo_ipv4_address
+        office_router_subscription.office_router.office_router_lo_ipv6_address = office_router_lo_ipv6_address
+        office_router_subscription.office_router.office_router_site = Site.from_subscription(office_router_site).site
+        office_router_subscription.office_router.vendor = Vendor.NOKIA
+
+        office_router_subscription = SubscriptionModel.from_other_lifecycle(
+            office_router_subscription, SubscriptionLifecycle.ACTIVE
+        )
+        office_router_subscription.description = description
+        office_router_subscription.start_date = start_date
+
+        if status:
+            office_router_subscription.status = status
+
+        office_router_subscription.save()
+        db.session.commit()
+
+        return str(office_router_subscription.subscription_id)
+
+    return subscription_create
diff --git a/test/fixtures/opengear_fixtures.py b/test/fixtures/opengear_fixtures.py
new file mode 100644
index 0000000000000000000000000000000000000000..b6dbff50f0fdc8c786caadddccade778311a83e1
--- /dev/null
+++ b/test/fixtures/opengear_fixtures.py
@@ -0,0 +1,68 @@
+import pytest
+from orchestrator.db import db
+from orchestrator.domain import SubscriptionModel
+from orchestrator.types import SubscriptionLifecycle, UUIDstr
+
+from gso.products import ProductName
+from gso.products.product_types.opengear import ImportedOpengearInactive, OpengearInactive
+from gso.products.product_types.site import Site
+from gso.services import subscriptions
+
+
+@pytest.fixture()
+def opengear_subscription_factory(site_subscription_factory, faker, geant_partner):
+    def subscription_create(
+        description=None,
+        start_date="2023-05-24T00:00:00+00:00",
+        opengear_site=None,
+        opengear_hostname=None,
+        opengear_wan_address=None,
+        opengear_wan_netmask=None,
+        opengear_wan_gateway=None,
+        status: SubscriptionLifecycle | None = None,
+        partner: dict | None = None,
+        *,
+        is_imported: bool | None = True,
+    ) -> UUIDstr:
+        if partner is None:
+            partner = geant_partner
+
+        description = description or faker.text(max_nb_chars=30)
+        opengear_site = opengear_site or site_subscription_factory()
+        opengear_hostname = opengear_hostname or faker.domain_name(levels=4)
+        opengear_wan_address = opengear_wan_address or faker.ipv4()
+        opengear_wan_netmask = opengear_wan_netmask or faker.ipv4()
+        opengear_wan_gateway = opengear_wan_gateway or faker.ipv4()
+
+        if is_imported:
+            product_id = subscriptions.get_product_id_by_name(ProductName.OPENGEAR)
+            opengear_subscription = OpengearInactive.from_product_id(
+                product_id, customer_id=partner["partner_id"], insync=True
+            )
+        else:
+            product_id = subscriptions.get_product_id_by_name(ProductName.IMPORTED_OPENGEAR)
+            opengear_subscription = ImportedOpengearInactive.from_product_id(
+                product_id, customer_id=partner["partner_id"], insync=True
+            )
+
+        opengear_subscription.opengear.opengear_site = Site.from_subscription(opengear_site).site
+        opengear_subscription.opengear.opengear_hostname = opengear_hostname
+        opengear_subscription.opengear.opengear_wan_address = opengear_wan_address
+        opengear_subscription.opengear.opengear_wan_netmask = opengear_wan_netmask
+        opengear_subscription.opengear.opengear_wan_gateway = opengear_wan_gateway
+
+        opengear_subscription = SubscriptionModel.from_other_lifecycle(
+            opengear_subscription, SubscriptionLifecycle.ACTIVE
+        )
+        opengear_subscription.description = description
+        opengear_subscription.start_date = start_date
+
+        if status:
+            opengear_subscription.status = status
+
+        opengear_subscription.save()
+        db.session.commit()
+
+        return str(opengear_subscription.subscription_id)
+
+    return subscription_create
diff --git a/test/fixtures/router_fixtures.py b/test/fixtures/router_fixtures.py
new file mode 100644
index 0000000000000000000000000000000000000000..04c77cede9d10f0d573e217983bd3a1462281d3e
--- /dev/null
+++ b/test/fixtures/router_fixtures.py
@@ -0,0 +1,77 @@
+import ipaddress
+
+import pytest
+from orchestrator.db import db
+from orchestrator.domain import SubscriptionModel
+from orchestrator.types import SubscriptionLifecycle, UUIDstr
+
+from gso.products import ProductName
+from gso.products.product_blocks.router import RouterRole
+from gso.products.product_types.router import ImportedRouterInactive, RouterInactive
+from gso.products.product_types.site import Site
+from gso.services import subscriptions
+from gso.utils.helpers import iso_from_ipv4
+from gso.utils.shared_enums import Vendor
+from gso.utils.types.ip_address import IPv4AddressType, IPv6AddressType
+
+
+@pytest.fixture()
+def router_subscription_factory(site_subscription_factory, faker, geant_partner):
+    def subscription_create(
+        description: str | None = None,
+        start_date: str | None = "2023-05-24T00:00:00+00:00",
+        router_fqdn: str | None = None,
+        router_ts_port: int | None = None,
+        router_lo_ipv4_address: IPv4AddressType | None = None,
+        router_lo_ipv6_address: IPv6AddressType | None = None,
+        router_lo_iso_address: str | None = None,
+        router_role: RouterRole | None = RouterRole.PE,
+        router_site=None,
+        status: SubscriptionLifecycle | None = None,
+        partner: dict | None = None,
+        vendor: Vendor | None = Vendor.NOKIA,
+        *,
+        router_access_via_ts: bool | None = None,
+        is_imported: bool | None = True,
+    ) -> UUIDstr:
+        if partner is None:
+            partner = geant_partner
+        if is_imported:
+            product_id = subscriptions.get_product_id_by_name(ProductName.ROUTER)
+            router_subscription = RouterInactive.from_product_id(
+                product_id, customer_id=partner["partner_id"], insync=True
+            )
+        else:
+            product_id = subscriptions.get_product_id_by_name(ProductName.IMPORTED_ROUTER)
+            router_subscription = ImportedRouterInactive.from_product_id(
+                product_id, customer_id=partner["partner_id"], insync=True
+            )
+
+        router_subscription.router.router_fqdn = router_fqdn or faker.domain_name(levels=4)
+        router_subscription.router.router_ts_port = router_ts_port or faker.port_number(is_user=True)
+        router_subscription.router.router_access_via_ts = router_access_via_ts or faker.boolean()
+        router_subscription.router.router_lo_ipv4_address = router_lo_ipv4_address or ipaddress.IPv4Address(
+            faker.ipv4()
+        )
+        router_subscription.router.router_lo_ipv6_address = router_lo_ipv6_address or ipaddress.IPv6Address(
+            faker.ipv6()
+        )
+        router_subscription.router.router_lo_iso_address = router_lo_iso_address or iso_from_ipv4(faker.ipv4())
+        router_subscription.router.router_role = router_role
+        router_subscription.router.router_site = Site.from_subscription(router_site or site_subscription_factory()).site
+        router_subscription.router.vendor = vendor
+
+        router_subscription = SubscriptionModel.from_other_lifecycle(router_subscription, SubscriptionLifecycle.ACTIVE)
+        router_subscription.insync = True
+        router_subscription.description = description or faker.text(max_nb_chars=30)
+        router_subscription.start_date = start_date
+
+        if status:
+            router_subscription.status = status
+
+        router_subscription.save()
+        db.session.commit()
+
+        return str(router_subscription.subscription_id)
+
+    return subscription_create
diff --git a/test/fixtures/site_fixtures.py b/test/fixtures/site_fixtures.py
new file mode 100644
index 0000000000000000000000000000000000000000..6fa904183f0d8effe142f2c199f180494cbfd7c2
--- /dev/null
+++ b/test/fixtures/site_fixtures.py
@@ -0,0 +1,69 @@
+import pytest
+from orchestrator.db import db
+from orchestrator.domain import SubscriptionModel
+from orchestrator.types import SubscriptionLifecycle, UUIDstr
+
+from gso.products import ProductName
+from gso.products.product_blocks.site import SiteTier
+from gso.products.product_types.site import ImportedSiteInactive, SiteInactive
+from gso.services import subscriptions
+from gso.utils.types.coordinates import LatitudeCoordinate, LongitudeCoordinate
+from gso.utils.types.ip_address import IPAddress
+from gso.utils.types.site_name import SiteName
+
+
+@pytest.fixture()
+def site_subscription_factory(faker, geant_partner):
+    def subscription_create(
+        description: str | None = None,
+        site_name: SiteName | None = None,
+        site_city: str | None = None,
+        site_country: str | None = None,
+        site_country_code: str | None = None,
+        site_latitude: LatitudeCoordinate | None = None,
+        site_longitude: LongitudeCoordinate | None = None,
+        site_bgp_community_id: int | None = None,
+        site_internal_id: int | None = None,
+        site_tier: SiteTier | None = None,
+        site_ts_address: IPAddress | None = None,
+        status: SubscriptionLifecycle | None = None,
+        partner: dict | None = None,
+        start_date="2023-05-24T00:00:00+00:00",
+        *,
+        is_imported: bool | None = True,
+    ) -> UUIDstr:
+        if partner is None:
+            partner = geant_partner
+
+        if is_imported:
+            product_id = subscriptions.get_product_id_by_name(ProductName.SITE)
+            site_subscription = SiteInactive.from_product_id(product_id, customer_id=partner["partner_id"], insync=True)
+        else:
+            product_id = subscriptions.get_product_id_by_name(ProductName.IMPORTED_SITE)
+            site_subscription = ImportedSiteInactive.from_product_id(
+                product_id, customer_id=partner["partner_id"], insync=True
+            )
+
+        site_subscription.site.site_city = site_city or faker.city()
+        site_subscription.site.site_name = site_name or faker.site_name()
+        site_subscription.site.site_country = site_country or faker.country()
+        site_subscription.site.site_country_code = site_country_code or faker.country_code()
+        site_subscription.site.site_latitude = site_latitude or str(faker.latitude())
+        site_subscription.site.site_longitude = site_longitude or str(faker.longitude())
+        site_subscription.site.site_bgp_community_id = site_bgp_community_id or faker.pyint()
+        site_subscription.site.site_internal_id = site_internal_id or faker.pyint()
+        site_subscription.site.site_tier = site_tier or SiteTier.TIER1
+        site_subscription.site.site_ts_address = site_ts_address or faker.ipv4()
+
+        site_subscription = SubscriptionModel.from_other_lifecycle(site_subscription, SubscriptionLifecycle.ACTIVE)
+        site_subscription.description = description or "Site Subscription"
+        site_subscription.start_date = start_date
+        if status:
+            site_subscription.status = status
+
+        site_subscription.save()
+        db.session.commit()
+
+        return str(site_subscription.subscription_id)
+
+    return subscription_create
diff --git a/test/fixtures/super_pop_switch_fixtures.py b/test/fixtures/super_pop_switch_fixtures.py
new file mode 100644
index 0000000000000000000000000000000000000000..5350e2a70b8eead32521ee16e5de095f24585133
--- /dev/null
+++ b/test/fixtures/super_pop_switch_fixtures.py
@@ -0,0 +1,73 @@
+import ipaddress
+
+import pytest
+from orchestrator.db import db
+from orchestrator.domain import SubscriptionModel
+from orchestrator.types import SubscriptionLifecycle, UUIDstr
+
+from gso.products import ProductName
+from gso.products.product_types.site import Site
+from gso.products.product_types.super_pop_switch import ImportedSuperPopSwitchInactive, SuperPopSwitchInactive
+from gso.services import subscriptions
+from gso.utils.shared_enums import Vendor
+
+
+@pytest.fixture()
+def super_pop_switch_subscription_factory(site_subscription_factory, faker, geant_partner):
+    def subscription_create(
+        description=None,
+        start_date="2023-05-24T00:00:00+00:00",
+        super_pop_switch_fqdn=None,
+        super_pop_switch_ts_port=None,
+        super_pop_switch_mgmt_ipv4_address=None,
+        super_pop_switch_site=None,
+        status: SubscriptionLifecycle | None = None,
+        partner: dict | None = None,
+        *,
+        is_imported: bool | None = True,
+    ) -> UUIDstr:
+        if partner is None:
+            partner = geant_partner
+
+        description = description or faker.text(max_nb_chars=30)
+        super_pop_switch_fqdn = super_pop_switch_fqdn or faker.domain_name(levels=4)
+        super_pop_switch_ts_port = super_pop_switch_ts_port or faker.random_int(min=1, max=49151)
+        super_pop_switch_mgmt_ipv4_address = super_pop_switch_mgmt_ipv4_address or ipaddress.IPv4Address(faker.ipv4())
+        super_pop_switch_site = super_pop_switch_site or site_subscription_factory()
+
+        if is_imported:
+            product_id = subscriptions.get_product_id_by_name(ProductName.SUPER_POP_SWITCH)
+            super_pop_switch_subscription = SuperPopSwitchInactive.from_product_id(
+                product_id, customer_id=partner["partner_id"], insync=True
+            )
+        else:
+            product_id = subscriptions.get_product_id_by_name(ProductName.IMPORTED_SUPER_POP_SWITCH)
+            super_pop_switch_subscription = ImportedSuperPopSwitchInactive.from_product_id(
+                product_id, customer_id=partner["partner_id"], insync=True
+            )
+
+        super_pop_switch_subscription.super_pop_switch.super_pop_switch_fqdn = super_pop_switch_fqdn
+        super_pop_switch_subscription.super_pop_switch.super_pop_switch_ts_port = super_pop_switch_ts_port
+        super_pop_switch_subscription.super_pop_switch.super_pop_switch_mgmt_ipv4_address = (
+            super_pop_switch_mgmt_ipv4_address
+        )
+        super_pop_switch_subscription.super_pop_switch.super_pop_switch_site = Site.from_subscription(
+            super_pop_switch_site
+        ).site
+        super_pop_switch_subscription.super_pop_switch.vendor = Vendor.NOKIA
+
+        super_pop_switch_subscription = SubscriptionModel.from_other_lifecycle(
+            super_pop_switch_subscription, SubscriptionLifecycle.ACTIVE
+        )
+        super_pop_switch_subscription.description = description
+        super_pop_switch_subscription.start_date = start_date
+
+        if status:
+            super_pop_switch_subscription.status = status
+
+        super_pop_switch_subscription.save()
+        db.session.commit()
+
+        return str(super_pop_switch_subscription.subscription_id)
+
+    return subscription_create
diff --git a/test/services/conftest.py b/test/services/conftest.py
index 9557b1c5b091e51150ab78cf4b58c62f8018df8e..3467d545d2c571e947c75022c8a574197394e723 100644
--- a/test/services/conftest.py
+++ b/test/services/conftest.py
@@ -12,10 +12,22 @@ class MockedNetboxClient:
     def get_device_by_name(self):
         return self.BaseMockObject(id=1, name="test")
 
+    @staticmethod
+    def get_interface_by_name_and_device(interface_name: str, device_name: str):
+        return {
+            "name": f"{interface_name}",
+            "module": {"display": f"Module{interface_name}"},
+            "description": f"Description{interface_name}-{device_name}",
+        }
+
     @staticmethod
     def get_available_lags() -> list[str]:
         return [f"lag-{lag}" for lag in range(1, 5)]
 
+    @staticmethod
+    def get_available_services_lags() -> list[str]:
+        return [f"lag-{lag}" for lag in range(21, 50)]
+
     @staticmethod
     def get_available_interfaces():
         interfaces = []
diff --git a/test/services/test_librenms_client.py b/test/services/test_librenms_client.py
index c64b5933ba2f4b845fe34c288cd2be34c8655aca..18b922da43913430628ab869f63685f99fc7d0c6 100644
--- a/test/services/test_librenms_client.py
+++ b/test/services/test_librenms_client.py
@@ -5,7 +5,7 @@ import pytest
 from requests import HTTPError
 
 from gso.services.librenms_client import LibreNMSClient
-from gso.utils.types.snmp import SNMPVersion
+from gso.utils.shared_enums import SNMPVersion
 
 
 @pytest.fixture()
diff --git a/test/utils/test_helpers.py b/test/utils/test_helpers.py
index 0df4ced9588e514f2cad00bbc8c22eaa2acdbfb5..880893b628183ff7a131745ca3be7c80164d15fa 100644
--- a/test/utils/test_helpers.py
+++ b/test/utils/test_helpers.py
@@ -3,9 +3,9 @@ from unittest.mock import patch
 import pytest
 from orchestrator.types import SubscriptionLifecycle
 
-from gso.products import Router
 from gso.products.product_blocks.iptrunk import IptrunkInterfaceBlock
 from gso.products.product_blocks.router import RouterRole
+from gso.products.product_types.router import Router
 from gso.utils.helpers import (
     available_interfaces_choices_including_current_members,
     generate_inventory_for_active_routers,
diff --git a/test/workflows/edge_port/__init__.py b/test/workflows/edge_port/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/test/workflows/edge_port/test_create_edge_port.py b/test/workflows/edge_port/test_create_edge_port.py
new file mode 100644
index 0000000000000000000000000000000000000000..ad804c91f5143148098b7cc4fbfc427d99783d29
--- /dev/null
+++ b/test/workflows/edge_port/test_create_edge_port.py
@@ -0,0 +1,129 @@
+from os import PathLike
+from unittest.mock import patch
+
+import pytest
+from pydantic_forms.exceptions import FormValidationError
+
+from gso.products import ProductName
+from gso.products.product_blocks.edge_port import EdgePortType, EncapsulationType
+from gso.products.product_types.edge_port import EdgePort
+from gso.products.product_types.router import Router
+from gso.services.subscriptions import get_product_id_by_name
+from gso.utils.shared_enums import Vendor
+from gso.utils.types.interfaces import PhysicalPortCapacity
+from test.services.conftest import MockedNetboxClient
+from test.workflows import (
+    assert_complete,
+    assert_lso_interaction_success,
+    extract_state,
+    run_workflow,
+)
+
+
+@pytest.fixture()
+def _netbox_client_mock():
+    # Mock NetboxClient methods
+    with (
+        patch("gso.services.netbox_client.NetboxClient.get_device_by_name") as mock_get_device_by_name,
+        patch("gso.services.netbox_client.NetboxClient.get_available_interfaces") as mock_get_available_interfaces,
+        patch("gso.services.netbox_client.NetboxClient.get_available_services_lags") as mock_available_services_lags,
+        patch("gso.services.netbox_client.NetboxClient.create_interface") as mock_create_interface,
+        patch("gso.services.netbox_client.NetboxClient.attach_interface_to_lag") as mock_attach_interface_to_lag,
+        patch("gso.services.netbox_client.NetboxClient.reserve_interface") as mock_reserve_interface,
+        patch("gso.services.netbox_client.NetboxClient.allocate_interface") as mock_allocate_interface,
+    ):
+        mock_get_device_by_name.return_value = MockedNetboxClient().get_device_by_name()
+        mock_get_available_interfaces.return_value = MockedNetboxClient().get_available_interfaces()
+        mock_available_services_lags.return_value = MockedNetboxClient().get_available_services_lags()
+        mock_create_interface.return_value = MockedNetboxClient().create_interface()
+        mock_attach_interface_to_lag.return_value = MockedNetboxClient().attach_interface_to_lag()
+        mock_reserve_interface.return_value = MockedNetboxClient().reserve_interface()
+        mock_allocate_interface.return_value = MockedNetboxClient().allocate_interface()
+
+        yield
+
+
+@pytest.fixture()
+def input_form_wizard_data(request, router_subscription_factory, partner_factory, faker):
+    create_edge_port_step = {
+        "tt_number": faker.tt_number(),
+        "node": router_subscription_factory(vendor=Vendor.NOKIA),
+        "partner": partner_factory(name="GAAR", email=faker.email())["partner_id"],
+        "service_type": EdgePortType.PUBLIC,
+        "geant_ga_id": faker.geant_gid(),
+        "enable_lacp": True,
+        "speed": PhysicalPortCapacity.HUNDRED_GIGABIT_PER_SECOND,
+        "encapsulation": EncapsulationType.DOT1Q,
+        "number_of_members": 2,
+        "minimum_links": 1,
+    }
+    create_edge_port_interface_step = {
+        "name": "lag-21",
+        "description": faker.sentence(),
+        "ae_members": [
+            {
+                "interface_name": f"Interface{interface}",
+                "interface_description": faker.sentence(),
+            }
+            for interface in range(2)
+        ],
+    }
+    summary_view_step = {}
+
+    return [
+        create_edge_port_step,
+        create_edge_port_interface_step,
+        summary_view_step,
+    ]
+
+
+@pytest.mark.workflow()
+@patch("gso.services.lso_client._send_request")
+def test_successful_edge_port_creation(
+    mock_execute_playbook,
+    responses,
+    input_form_wizard_data,
+    faker,
+    _netbox_client_mock,  # noqa: PT019
+    data_config_filename: PathLike,
+    test_client,
+):
+    product_id = get_product_id_by_name(ProductName.EDGE_PORT)
+    initial_data = [{"product": product_id}, *input_form_wizard_data]
+    result, process_stat, step_log = run_workflow("create_edge_port", initial_data)
+
+    for _ in range(2):
+        result, step_log = assert_lso_interaction_success(result, process_stat, step_log)
+
+    assert_complete(result)
+
+    state = extract_state(result)
+    subscription_id = state["subscription_id"]
+    subscription = EdgePort.from_subscription(subscription_id)
+
+    assert subscription.status == "active"
+    ga_id = input_form_wizard_data[0]["geant_ga_id"]
+    router_fqdn = Router.from_subscription(input_form_wizard_data[0]["node"]).router.router_fqdn
+    assert subscription.description == f"Edge Port lag-21 on {router_fqdn}, GAAR, {ga_id}"
+    assert len(subscription.edge_port.edge_port_ae_members) == 2
+    assert mock_execute_playbook.call_count == 2
+
+
+def test_edge_port_creation_with_invalid_input(
+    input_form_wizard_data,
+    faker,
+    _netbox_client_mock,  # noqa: PT019
+    data_config_filename: PathLike,
+    test_client,
+):
+    product_id = get_product_id_by_name(ProductName.EDGE_PORT)
+    # If the number of members is greater than 1 then :term:`LACP` must be enabled.
+    input_form_wizard_data[0]["enable_lacp"] = False
+    initial_data = [{"product": product_id}, *input_form_wizard_data]
+
+    with pytest.raises(FormValidationError) as error:
+        run_workflow("create_edge_port", initial_data)
+
+    error = error.value.errors[0]
+    assert error["msg"] == "Number of members must be 1 if LACP is disabled."
+    assert error["loc"][0] == "__root__"
diff --git a/test/workflows/edge_port/test_create_imported_edge_port.py b/test/workflows/edge_port/test_create_imported_edge_port.py
new file mode 100644
index 0000000000000000000000000000000000000000..c5590fe4f99c03c38c7a62b7129aa91ef8caef9a
--- /dev/null
+++ b/test/workflows/edge_port/test_create_imported_edge_port.py
@@ -0,0 +1,44 @@
+import pytest
+from orchestrator.types import SubscriptionLifecycle
+
+from gso.products.product_blocks.edge_port import EdgePortType, EncapsulationType
+from gso.products.product_types.edge_port import ImportedEdgePort
+from gso.utils.shared_enums import Vendor
+from gso.utils.types.interfaces import PhysicalPortCapacity
+from test.workflows import assert_complete, extract_state, run_workflow
+
+
+@pytest.fixture()
+def imported_edge_port_creation_input_form_data(router_subscription_factory, partner_factory, faker):
+    return {
+        "node": router_subscription_factory(vendor=Vendor.NOKIA),
+        "service_type": EdgePortType.CUSTOMER,
+        "speed": PhysicalPortCapacity.TEN_GIGABIT_PER_SECOND,
+        "encapsulation": EncapsulationType.DOT1Q,
+        "name": "lag34",
+        "minimum_links": 2,
+        "geant_ga_id": faker.geant_gid(),
+        "mac_address": faker.mac_address(),
+        "partner": partner_factory()["name"],
+        "enable_lacp": True,
+        "ignore_if_down": False,
+        "ae_members": [
+            {
+                "interface_name": faker.network_interface(),
+                "interface_description": faker.sentence(),
+            },
+            {
+                "interface_name": faker.network_interface(),
+                "interface_description": faker.sentence(),
+            },
+        ],
+        "description": faker.sentence(),
+    }
+
+
+def test_create_imported_edge_port_success(faker, imported_edge_port_creation_input_form_data):
+    result, _, _ = run_workflow("create_imported_edge_port", [imported_edge_port_creation_input_form_data])
+    state = extract_state(result)
+    subscription = ImportedEdgePort.from_subscription(state["subscription_id"])
+    assert_complete(result)
+    assert subscription.status == SubscriptionLifecycle.ACTIVE
diff --git a/test/workflows/edge_port/test_import_edge_port.py b/test/workflows/edge_port/test_import_edge_port.py
new file mode 100644
index 0000000000000000000000000000000000000000..7fb1fcbdf851c9fb7b73aa30933e508c679b8743
--- /dev/null
+++ b/test/workflows/edge_port/test_import_edge_port.py
@@ -0,0 +1,18 @@
+import pytest
+from orchestrator.types import SubscriptionLifecycle
+
+from gso.products import ProductName
+from gso.products.product_types.edge_port import EdgePort
+from test.workflows import assert_complete, run_workflow
+
+
+@pytest.mark.workflow()
+def test_import_edge_port_success(edge_port_subscription_factory):
+    imported_edge_port = edge_port_subscription_factory(is_imported=False)
+    result, _, _ = run_workflow("import_edge_port", [{"subscription_id": imported_edge_port}])
+    subscription = EdgePort.from_subscription(imported_edge_port)
+
+    assert_complete(result)
+    assert subscription.product.name == ProductName.EDGE_PORT
+    assert subscription.status == SubscriptionLifecycle.ACTIVE
+    assert subscription.insync is True
diff --git a/test/workflows/edge_port/test_modify_edge_port.py b/test/workflows/edge_port/test_modify_edge_port.py
new file mode 100644
index 0000000000000000000000000000000000000000..a7c824283f031c4dcb14cda53a7c8dfbb35527bb
--- /dev/null
+++ b/test/workflows/edge_port/test_modify_edge_port.py
@@ -0,0 +1,168 @@
+from unittest.mock import patch
+
+import pytest
+
+from gso.products.product_types.edge_port import EdgePort
+from gso.utils.types.interfaces import PhysicalPortCapacity
+from test.workflows import (
+    assert_complete,
+    assert_lso_interaction_success,
+    extract_state,
+    run_workflow,
+)
+from test.workflows.iptrunk.test_create_iptrunk import MockedNetboxClient
+
+
+@pytest.fixture()
+def input_form_wizard_data(request, faker, edge_port_subscription_factory, partner_factory):
+    subscription_id = edge_port_subscription_factory()
+
+    return [
+        {"subscription_id": subscription_id},
+        {
+            "tt_number": faker.tt_number(),
+            "geant_ga_id": faker.geant_gid(),
+            "member_speed": PhysicalPortCapacity.FOUR_HUNDRED_GIGABIT_PER_SECOND,
+            "number_of_members": 1,
+        },
+        {
+            "description": faker.sentence(),
+            "ae_members": [
+                {
+                    "interface_name": "Interface1",
+                    "interface_description": faker.sentence(),
+                }
+            ],
+        },
+    ]
+
+
+@pytest.mark.workflow()
+@patch("gso.services.lso_client._send_request")
+@patch("gso.services.netbox_client.NetboxClient.get_available_interfaces")
+@patch("gso.services.netbox_client.NetboxClient.attach_interface_to_lag")
+@patch("gso.services.netbox_client.NetboxClient.reserve_interface")
+@patch("gso.services.netbox_client.NetboxClient.allocate_interface")
+@patch("gso.services.netbox_client.NetboxClient.free_interface")
+@patch("gso.services.netbox_client.NetboxClient.detach_interfaces_from_lag")
+@patch("gso.services.netbox_client.NetboxClient.get_interface_by_name_and_device")
+def test_modify_edge_port_with_changing_capacity(
+    mocked_get_interface_by_name_and_device,
+    mocked_detach_interfaces_from_lag,
+    mocked_free_interface,
+    mocked_allocate_interface,
+    mocked_reserve_interface,
+    mocked_attach_interface_to_lag,
+    mocked_get_available_interfaces,
+    mocked_execute_playbook,
+    input_form_wizard_data,
+    faker,
+    data_config_filename,
+):
+    #  Set up mock return values
+    mocked_netbox = MockedNetboxClient()
+    mocked_get_available_interfaces.return_value = mocked_netbox.get_available_interfaces()
+    mocked_attach_interface_to_lag.return_value = mocked_netbox.attach_interface_to_lag()
+    mocked_reserve_interface.return_value = mocked_netbox.reserve_interface()
+    mocked_allocate_interface.return_value = mocked_netbox.allocate_interface()
+    mocked_free_interface.return_value = mocked_netbox.free_interface()
+    mocked_detach_interfaces_from_lag.return_value = mocked_netbox.detach_interfaces_from_lag()
+    mocked_get_interface_by_name_and_device.side_effect = mocked_netbox.get_interface_by_name_and_device
+
+    #  Run workflow
+    result, process_stat, step_log = run_workflow("modify_edge_port", input_form_wizard_data)
+
+    for _ in range(2):
+        result, step_log = assert_lso_interaction_success(result, process_stat, step_log)
+
+    assert_complete(result)
+    # Validate the final state and subscription data
+    state = extract_state(result)
+    subscription_id = state["subscription_id"]
+    subscription = EdgePort.from_subscription(subscription_id)
+
+    assert subscription.status == "active"
+    assert mocked_execute_playbook.call_count == 2
+
+    # The number of members have been changed from 2 to 1
+    assert mocked_reserve_interface.call_count == 1
+    assert mocked_attach_interface_to_lag.call_count == 1
+    assert mocked_free_interface.call_count == 2
+    assert mocked_detach_interfaces_from_lag.call_count == 1
+    assert subscription.edge_port.geant_ga_id == input_form_wizard_data[1]["geant_ga_id"]
+    assert len(subscription.edge_port.edge_port_ae_members) == 1
+
+
+@pytest.fixture()
+def input_form_wizard_without_changing_capacity(request, faker, edge_port_subscription_factory, partner_factory):
+    subscription_id = edge_port_subscription_factory()
+    subscription = EdgePort.from_subscription(subscription_id)
+
+    return [
+        {"subscription_id": subscription_id},
+        {"tt_number": faker.tt_number(), "geant_ga_id": faker.geant_gid()},
+        {
+            "description": faker.sentence(),
+            "ae_members": [
+                {
+                    "interface_name": interface.interface_name,
+                    "interface_description": interface.interface_description,
+                }
+                for interface in subscription.edge_port.edge_port_ae_members
+            ],
+        },
+    ]
+
+
+@pytest.mark.workflow()
+@patch("gso.services.lso_client._send_request")
+@patch("gso.services.netbox_client.NetboxClient.get_available_interfaces")
+@patch("gso.services.netbox_client.NetboxClient.attach_interface_to_lag")
+@patch("gso.services.netbox_client.NetboxClient.reserve_interface")
+@patch("gso.services.netbox_client.NetboxClient.allocate_interface")
+@patch("gso.services.netbox_client.NetboxClient.free_interface")
+@patch("gso.services.netbox_client.NetboxClient.detach_interfaces_from_lag")
+@patch("gso.services.netbox_client.NetboxClient.get_interface_by_name_and_device")
+def test_modify_edge_port_without_changing_capacity(
+    mocked_get_interface_by_name_and_device,
+    mocked_detach_interfaces_from_lag,
+    mocked_free_interface,
+    mocked_allocate_interface,
+    mocked_reserve_interface,
+    mocked_attach_interface_to_lag,
+    mocked_get_available_interfaces,
+    mocked_execute_playbook,
+    input_form_wizard_without_changing_capacity,
+    faker,
+    data_config_filename,
+):
+    #  Set up mock return values
+    mocked_netbox = MockedNetboxClient()
+    mocked_get_available_interfaces.return_value = mocked_netbox.get_available_interfaces()
+    mocked_attach_interface_to_lag.return_value = mocked_netbox.attach_interface_to_lag()
+    mocked_reserve_interface.return_value = mocked_netbox.reserve_interface()
+    mocked_allocate_interface.return_value = mocked_netbox.allocate_interface()
+    mocked_free_interface.return_value = mocked_netbox.free_interface()
+    mocked_detach_interfaces_from_lag.return_value = mocked_netbox.detach_interfaces_from_lag()
+    mocked_get_interface_by_name_and_device.side_effect = mocked_netbox.get_interface_by_name_and_device
+
+    #  Run workflow
+    result, _, _ = run_workflow("modify_edge_port", input_form_wizard_without_changing_capacity)
+    assert_complete(result)
+
+    state = extract_state(result)
+    subscription_id = state["subscription_id"]
+    subscription = EdgePort.from_subscription(subscription_id)
+
+    assert subscription.status == "active"
+
+    # The capacity has not been changed so the following methods should not be called
+    assert mocked_execute_playbook.call_count == 0
+    assert mocked_reserve_interface.call_count == 0
+    assert mocked_attach_interface_to_lag.call_count == 0
+    assert mocked_free_interface.call_count == 0
+    assert mocked_detach_interfaces_from_lag.call_count == 0
+
+    assert subscription.edge_port.geant_ga_id == input_form_wizard_without_changing_capacity[1]["geant_ga_id"]
+    assert len(subscription.edge_port.edge_port_ae_members) == 2
+    assert subscription.edge_port.edge_port_description == input_form_wizard_without_changing_capacity[2]["description"]
diff --git a/test/workflows/edge_port/test_terminate_edge_port.py b/test/workflows/edge_port/test_terminate_edge_port.py
new file mode 100644
index 0000000000000000000000000000000000000000..b5a16866548235c3f878190d757f9ebe9507b35e
--- /dev/null
+++ b/test/workflows/edge_port/test_terminate_edge_port.py
@@ -0,0 +1,56 @@
+from unittest.mock import patch
+
+import pytest
+
+from gso.products.product_types.edge_port import EdgePort
+from test.services.conftest import MockedNetboxClient
+from test.workflows import (
+    assert_complete,
+    assert_lso_interaction_success,
+    extract_state,
+    run_workflow,
+)
+
+
+@pytest.mark.workflow()
+@patch("gso.services.lso_client._send_request")
+@patch("gso.services.netbox_client.NetboxClient.delete_interface")
+@patch("gso.services.netbox_client.NetboxClient.free_interface")
+def test_successful_edge_port_termination(
+    mocked_free_interface,
+    mocked_delete_interface,
+    mock_execute_playbook,
+    edge_port_subscription_factory,
+    faker,
+    data_config_filename,
+):
+    #  Set up mock return values
+    subscription_id = edge_port_subscription_factory()
+    mocked_netbox = MockedNetboxClient()
+    mocked_delete_interface.return_value = mocked_netbox.delete_interface()
+    mocked_free_interface.return_value = mocked_netbox.free_interface()
+
+    #  Run workflow
+    initial_data = [
+        {"subscription_id": subscription_id},
+        {
+            "tt_number": faker.tt_number(),
+        },
+    ]
+    result, process_stat, step_log = run_workflow("terminate_edge_port", initial_data)
+
+    for _ in range(2):
+        result, step_log = assert_lso_interaction_success(result, process_stat, step_log)
+
+    assert_complete(result)
+
+    # Check NetboxClient calls
+    assert mocked_delete_interface.call_count == 1  # Delete the lag
+    assert mocked_free_interface.call_count == 2  # Free interfaces attached to the lag which is 2
+
+    state = extract_state(result)
+    subscription_id = state["subscription_id"]
+    subscription = EdgePort.from_subscription(subscription_id)
+
+    assert subscription.status == "terminated"
+    assert mock_execute_playbook.call_count == 2
diff --git a/test/workflows/edge_port/test_validate_edge_port.py b/test/workflows/edge_port/test_validate_edge_port.py
new file mode 100644
index 0000000000000000000000000000000000000000..3aff8081a8a4986751997db723cba7c293250c5c
--- /dev/null
+++ b/test/workflows/edge_port/test_validate_edge_port.py
@@ -0,0 +1,62 @@
+from unittest.mock import patch
+
+import pytest
+
+from gso.products.product_types.edge_port import EdgePort
+from test.services.conftest import MockedNetboxClient
+from test.workflows import (
+    assert_complete,
+    assert_lso_success,
+    extract_state,
+    run_workflow,
+)
+
+
+@pytest.mark.workflow()
+@patch("gso.services.lso_client._send_request")
+@patch("gso.services.netbox_client.NetboxClient.get_interface_by_name_and_device")
+def test_validate_edge_port_success(
+    mock_get_interface_by_name_and_device,
+    mock_execute_playbook,
+    edge_port_subscription_factory,
+    faker,
+    data_config_filename,
+):
+    subscription_id = edge_port_subscription_factory()
+    mock_get_interface_by_name_and_device.side_effect = [
+        MockedNetboxClient.BaseMockObject(
+            name="iFace1",
+            module=MockedNetboxClient.BaseMockObject(display="display1"),
+            description=subscription_id,
+            enabled=True,
+        ),
+        MockedNetboxClient.BaseMockObject(
+            name="iFace2",
+            module=MockedNetboxClient.BaseMockObject(display="display2"),
+            description=subscription_id,
+            enabled=True,
+        ),
+        MockedNetboxClient.BaseMockObject(
+            name="Iface3",
+            module=MockedNetboxClient.BaseMockObject(display="display3"),
+            description=subscription_id,
+            enabled=True,
+        ),
+    ]
+
+    #  Run workflow
+    initial_data = [{"subscription_id": subscription_id}]
+    result, process_stat, step_log = run_workflow("validate_edge_port", initial_data)
+
+    state = extract_state(result)
+    subscription_id = state["subscription_id"]
+
+    for _ in range(1):
+        result, step_log = assert_lso_success(result, process_stat, step_log)
+
+    assert_complete(result)
+    subscription = EdgePort.from_subscription(subscription_id)
+    assert subscription.status == "active"
+    assert mock_execute_playbook.call_count == 1
+    # One time for getting the :term:`LAG` and two times for getting the interfaces
+    assert mock_get_interface_by_name_and_device.call_count == 3
diff --git a/test/workflows/iptrunk/test_activate_iptrunk.py b/test/workflows/iptrunk/test_activate_iptrunk.py
index 837f340c6adc77e1aa148760e425429288e4d01f..d1ed9b3347e52f902952dbff1eec2bbc5e1ffaf1 100644
--- a/test/workflows/iptrunk/test_activate_iptrunk.py
+++ b/test/workflows/iptrunk/test_activate_iptrunk.py
@@ -1,6 +1,6 @@
 import pytest
 
-from gso.products import Iptrunk
+from gso.products.product_types.iptrunk import Iptrunk
 from test.workflows import (
     assert_complete,
     assert_suspended,
diff --git a/test/workflows/iptrunk/test_create_iptrunk.py b/test/workflows/iptrunk/test_create_iptrunk.py
index 6be42faed62222a5d899e4f5977a26e813c4f2f7..7a38a234a7e0ba98dea56081b07e0bcc8be21c7e 100644
--- a/test/workflows/iptrunk/test_create_iptrunk.py
+++ b/test/workflows/iptrunk/test_create_iptrunk.py
@@ -4,8 +4,9 @@ from unittest.mock import patch
 import pytest
 from infoblox_client.objects import HostRecord
 
-from gso.products import Iptrunk, ProductName
+from gso.products import ProductName
 from gso.products.product_blocks.iptrunk import IptrunkType
+from gso.products.product_types.iptrunk import Iptrunk
 from gso.services.subscriptions import get_product_id_by_name
 from gso.utils.shared_enums import Vendor
 from gso.utils.types.interfaces import PhysicalPortCapacity
@@ -89,6 +90,7 @@ def input_form_wizard_data(request, router_subscription_factory, faker):
         "side_b_ae_geant_a_sid": faker.geant_sid(),
         "side_b_ae_members": side_b_members,
     }
+    summary_view_step = {}
 
     return [
         create_ip_trunk_step,
@@ -97,6 +99,7 @@ def input_form_wizard_data(request, router_subscription_factory, faker):
         create_ip_trunk_side_a_step,
         create_ip_trunk_side_b_router_name,
         create_ip_trunk_side_b_step,
+        summary_view_step,
     ]
 
 
diff --git a/test/workflows/iptrunk/test_deploy_twamp.py b/test/workflows/iptrunk/test_deploy_twamp.py
index e65feac79a0a1fd9988eff99fe0bf2e55f68899b..1b475140658105d076e6f1d0465d5bd9b5e78281 100644
--- a/test/workflows/iptrunk/test_deploy_twamp.py
+++ b/test/workflows/iptrunk/test_deploy_twamp.py
@@ -2,7 +2,7 @@ from unittest.mock import patch
 
 import pytest
 
-from gso.products import Iptrunk
+from gso.products.product_types.iptrunk import Iptrunk
 from test.workflows import (
     assert_complete,
     assert_lso_interaction_success,
diff --git a/test/workflows/iptrunk/test_import_iptrunk.py b/test/workflows/iptrunk/test_import_iptrunk.py
index 99cdbfd93fc1cd84c7ebf55e7375b1df1d420b97..52a52c329dbad987d3b360315522d0bda827354c 100644
--- a/test/workflows/iptrunk/test_import_iptrunk.py
+++ b/test/workflows/iptrunk/test_import_iptrunk.py
@@ -15,4 +15,4 @@ def test_import_iptrunk_success(iptrunk_subscription_factory):
     assert_complete(result)
     assert subscription.product.name == ProductName.IP_TRUNK
     assert subscription.status == SubscriptionLifecycle.ACTIVE
-    assert subscription.insync
+    assert subscription.insync is True
diff --git a/test/workflows/iptrunk/test_modify_isis_metric.py b/test/workflows/iptrunk/test_modify_isis_metric.py
index 26a9bbd490eb4c73728a2e11defd836bf3701316..f81baac86f23d5abb4e85d84b61ac84edccaeb92 100644
--- a/test/workflows/iptrunk/test_modify_isis_metric.py
+++ b/test/workflows/iptrunk/test_modify_isis_metric.py
@@ -2,7 +2,7 @@ from unittest.mock import patch
 
 import pytest
 
-from gso.products import Iptrunk
+from gso.products.product_types.iptrunk import Iptrunk
 from test.workflows import (
     assert_complete,
     assert_lso_interaction_success,
@@ -26,6 +26,7 @@ def test_iptrunk_modify_isis_metric_success(
     initial_iptrunk_data = [
         {"subscription_id": product_id},
         {"tt_number": faker.tt_number(), "isis_metric": new_isis_metric},
+        {},
     ]
     result, process_stat, step_log = run_workflow("modify_isis_metric", initial_iptrunk_data)
 
diff --git a/test/workflows/iptrunk/test_modify_trunk_interface.py b/test/workflows/iptrunk/test_modify_trunk_interface.py
index 359308cdf880ef7b16a341073ce203410749d15d..77470e79d7865cd68aa3254ae14b67a497171004 100644
--- a/test/workflows/iptrunk/test_modify_trunk_interface.py
+++ b/test/workflows/iptrunk/test_modify_trunk_interface.py
@@ -2,8 +2,8 @@ from unittest.mock import patch
 
 import pytest
 
-from gso.products import Iptrunk
 from gso.products.product_blocks.iptrunk import IptrunkType
+from gso.products.product_types.iptrunk import Iptrunk
 from gso.utils.shared_enums import Vendor
 from gso.utils.types.interfaces import LAGMemberList, PhysicalPortCapacity
 from test.conftest import UseJuniperSide
diff --git a/test/workflows/iptrunk/test_terminate_iptrunk.py b/test/workflows/iptrunk/test_terminate_iptrunk.py
index c745a615ad6c1178703951e1c87247ee0760b5af..83e0324a0f1baa661dbd473bf11d179d17588f95 100644
--- a/test/workflows/iptrunk/test_terminate_iptrunk.py
+++ b/test/workflows/iptrunk/test_terminate_iptrunk.py
@@ -2,8 +2,8 @@ from unittest.mock import patch
 
 import pytest
 
-from gso.products import Iptrunk
 from gso.products.product_blocks.router import RouterRole
+from gso.products.product_types.iptrunk import Iptrunk
 from gso.settings import load_oss_params
 from test.services.conftest import MockedNetboxClient
 from test.workflows import (
diff --git a/test/workflows/nren_l3_core_service/__init__.py b/test/workflows/nren_l3_core_service/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/test/workflows/nren_l3_core_service/test_create_imported_nren_l3_core_service.py b/test/workflows/nren_l3_core_service/test_create_imported_nren_l3_core_service.py
new file mode 100644
index 0000000000000000000000000000000000000000..63aab373915f348d3a8e8e7f7bf161c2e1e5c190
--- /dev/null
+++ b/test/workflows/nren_l3_core_service/test_create_imported_nren_l3_core_service.py
@@ -0,0 +1,68 @@
+import pytest
+from orchestrator.types import SubscriptionLifecycle
+
+from gso.products.product_blocks.bgp_session import IPFamily
+from gso.products.product_types.nren_l3_core_service import ImportedNRENL3CoreService, NRENL3CoreServiceType
+from gso.utils.shared_enums import SBPType
+from test.workflows import assert_complete, extract_state, run_workflow
+
+
+@pytest.mark.parametrize("l3_core_service_type", [NRENL3CoreServiceType.GEANT_IP, NRENL3CoreServiceType.IAS])
+def test_create_imported_nren_l3_core_service_success(
+    faker, partner_factory, edge_port_subscription_factory, l3_core_service_type
+):
+    creation_form_input_data = {
+        "partner": partner_factory()["name"],
+        "service_type": l3_core_service_type,
+        "service_binding_ports": [
+            {
+                "edge_port": edge_port_subscription_factory(),
+                "ap_type": "PRIMARY",
+                "geant_sid": faker.geant_sid(),
+                "sbp_type": SBPType.L3,
+                "is_tagged": faker.boolean(),
+                "vlan_id": faker.vlan_id(),
+                "ipv4_address": faker.ipv4(),
+                "ipv4_mask": faker.ipv4_netmask(),
+                "ipv6_address": faker.ipv6(),
+                "ipv6_mask": faker.ipv6_netmask(),
+                "custom_firewall_filters": faker.boolean(),
+                "bgp_peers": [
+                    {
+                        "bfd_enabled": faker.boolean(),
+                        "bfd_interval": faker.pyint(),
+                        "bfd_multiplier": faker.pyint(),
+                        "has_custom_policies": faker.boolean(),
+                        "authentication_key": faker.password(),
+                        "multipath_enabled": faker.boolean(),
+                        "send_default_route": faker.boolean(),
+                        "is_passive": faker.boolean(),
+                        "peer_address": faker.ipv4(),
+                        "families": [IPFamily.V4UNICAST, IPFamily.V4MULTICAST],
+                        "is_multi_hop": faker.boolean(),
+                        "rtbh_enabled": faker.boolean(),
+                    },
+                    {
+                        "bfd_enabled": faker.boolean(),
+                        "bfd_interval": faker.pyint(),
+                        "bfd_multiplier": faker.pyint(),
+                        "has_custom_policies": faker.boolean(),
+                        "authentication_key": faker.password(),
+                        "multipath_enabled": faker.boolean(),
+                        "send_default_route": faker.boolean(),
+                        "is_passive": faker.boolean(),
+                        "peer_address": faker.ipv6(),
+                        "families": [IPFamily.V6UNICAST],
+                        "is_multi_hop": faker.boolean(),
+                        "rtbh_enabled": faker.boolean(),
+                    },
+                ],
+            }
+        ],
+    }
+
+    result, _, _ = run_workflow("create_imported_nren_l3_core_service", [creation_form_input_data])
+    state = extract_state(result)
+    subscription = ImportedNRENL3CoreService.from_subscription(state["subscription_id"])
+    assert_complete(result)
+    assert subscription.status == SubscriptionLifecycle.ACTIVE
diff --git a/test/workflows/nren_l3_core_service/test_create_nren_l3_core_service.py b/test/workflows/nren_l3_core_service/test_create_nren_l3_core_service.py
new file mode 100644
index 0000000000000000000000000000000000000000..1287bd01c9288d4f577512f8a832f565ed38a01a
--- /dev/null
+++ b/test/workflows/nren_l3_core_service/test_create_nren_l3_core_service.py
@@ -0,0 +1,81 @@
+from unittest.mock import patch
+
+import pytest
+from orchestrator.types import SubscriptionLifecycle
+
+from gso.products import ProductName
+from gso.products.product_types.nren_l3_core_service import NRENL3CoreService
+from gso.services.subscriptions import get_product_id_by_name
+from gso.utils.shared_enums import APType
+from test.workflows import assert_complete, assert_lso_interaction_success, extract_state, run_workflow
+
+
+@pytest.fixture()
+def base_bgp_peer_input(faker):
+    def _base_bgp_peer_input():
+        bfd_enabled = faker.boolean()
+        return {
+            "bfd_enabled": bfd_enabled,
+            "bfd_interval": faker.pyint() if bfd_enabled else None,
+            "bfd_multiplier": faker.pyint() if bfd_enabled else None,
+            "has_custom_policies": faker.boolean(),
+            "authentication_key": faker.password(),
+            "multipath_enabled": faker.boolean(),
+            "send_default_route": faker.boolean(),
+            "is_passive": faker.boolean(),
+        }
+
+    return _base_bgp_peer_input
+
+
+@pytest.mark.parametrize("l3_core_type", [ProductName.GEANT_IP, ProductName.IAS])
+@pytest.mark.workflow()
+@patch("gso.services.lso_client._send_request")
+def test_create_nren_l3_core_service_success(
+    mock_lso_client,
+    l3_core_type,
+    responses,
+    faker,
+    partner_factory,
+    edge_port_subscription_factory,
+    base_bgp_peer_input,
+    data_config_filename,
+):
+    partner = partner_factory()
+    product_id = get_product_id_by_name(l3_core_type)
+    edge_port_a = edge_port_subscription_factory(partner=partner)
+
+    form_input_data = [
+        {"product": product_id},
+        {"tt_number": faker.tt_number(), "partner": partner["partner_id"]},
+        {"edge_ports": [{"edge_port": edge_port_a, "ap_type": APType.PRIMARY}]},
+        {
+            "geant_sid": faker.geant_sid(),
+            "is_tagged": faker.boolean(),
+            "vlan_id": faker.vlan_id(),
+            "ipv4_address": faker.ipv4(),
+            "ipv4_mask": faker.ipv4_netmask(),
+            "ipv6_address": faker.ipv6(),
+            "ipv6_mask": faker.ipv6_netmask(),
+            "custom_firewall_filters": faker.boolean(),
+            "v4_bgp_peer": base_bgp_peer_input() | {"add_v4_multicast": faker.boolean(), "peer_address": faker.ipv4()},
+            "v6_bgp_peer": base_bgp_peer_input() | {"add_v6_multicast": faker.boolean(), "peer_address": faker.ipv6()},
+        },
+    ]
+    lso_interaction_count = 6
+
+    result, process_stat, step_log = run_workflow("create_nren_l3_core_service", form_input_data)
+
+    for _ in range(lso_interaction_count):
+        result, step_log = assert_lso_interaction_success(result, process_stat, step_log)
+
+    assert_complete(result)
+    state = extract_state(result)
+    subscription = NRENL3CoreService.from_subscription(state["subscription_id"])
+    assert mock_lso_client.call_count == lso_interaction_count
+    assert subscription.status == SubscriptionLifecycle.ACTIVE
+    assert len(subscription.nren_l3_core_service.nren_ap_list) == 1
+    assert (
+        str(subscription.nren_l3_core_service.nren_ap_list[0].sbp.edge_port.owner_subscription_id)
+        == form_input_data[2]["edge_ports"][0]["edge_port"]
+    )
diff --git a/test/workflows/nren_l3_core_service/test_import_nren_l3_core_service.py b/test/workflows/nren_l3_core_service/test_import_nren_l3_core_service.py
new file mode 100644
index 0000000000000000000000000000000000000000..13095de6ead5ad679d2ccb9ceb1106d986a79470
--- /dev/null
+++ b/test/workflows/nren_l3_core_service/test_import_nren_l3_core_service.py
@@ -0,0 +1,23 @@
+import pytest
+from orchestrator.types import SubscriptionLifecycle
+
+from gso.products.product_types.nren_l3_core_service import NRENL3CoreService, NRENL3CoreServiceType
+from test.workflows import assert_complete, run_workflow
+
+
+@pytest.mark.parametrize(
+    "l3_core_service_type", [NRENL3CoreServiceType.IMPORTED_GEANT_IP, NRENL3CoreServiceType.IMPORTED_IAS]
+)
+@pytest.mark.workflow()
+def test_import_nren_l3_core_service_success(nren_l3_core_service_subscription_factory, l3_core_service_type):
+    imported_nren_l3_core_service = nren_l3_core_service_subscription_factory(
+        nren_l3_core_service_type=l3_core_service_type
+    )
+    result, _, _ = run_workflow("import_nren_l3_core_service", [{"subscription_id": imported_nren_l3_core_service}])
+    subscription = NRENL3CoreService.from_subscription(imported_nren_l3_core_service)
+
+    assert_complete(result)
+    #  Remove the "IMPORTED_" prefix with ``[9:]``
+    assert subscription.nren_l3_core_service_type == NRENL3CoreServiceType(l3_core_service_type.value[9:])
+    assert subscription.status == SubscriptionLifecycle.ACTIVE
+    assert subscription.insync is True
diff --git a/test/workflows/nren_l3_core_service/test_migrate_nren_l3_core_service.py b/test/workflows/nren_l3_core_service/test_migrate_nren_l3_core_service.py
new file mode 100644
index 0000000000000000000000000000000000000000..a4ad8a9f95a0c834536fae3e76478b65e35264cc
--- /dev/null
+++ b/test/workflows/nren_l3_core_service/test_migrate_nren_l3_core_service.py
@@ -0,0 +1,49 @@
+import pytest
+
+from gso.products.product_types.nren_l3_core_service import NRENL3CoreService, NRENL3CoreServiceType
+from test.workflows import assert_complete, extract_state, run_workflow
+
+
+@pytest.mark.parametrize("l3_core_service_type", [NRENL3CoreServiceType.GEANT_IP, NRENL3CoreServiceType.IAS])
+@pytest.mark.workflow()
+def test_migrate_nren_l3_core_service_success(
+    faker,
+    edge_port_subscription_factory,
+    partner_factory,
+    nren_l3_core_service_subscription_factory,
+    l3_core_service_type,
+):
+    partner = partner_factory()
+    subscription_id = nren_l3_core_service_subscription_factory(
+        partner=partner, nren_l3_core_service_type=l3_core_service_type
+    )
+    new_edge_port_1 = edge_port_subscription_factory(partner=partner)
+    new_edge_port_2 = edge_port_subscription_factory(partner=partner)
+    subscription = NRENL3CoreService.from_subscription(subscription_id)
+
+    form_input_data = [
+        {"subscription_id": subscription_id},
+        {
+            "tt_number": faker.tt_number(),
+            "edge_port_selection": [
+                {
+                    "old_edge_port": subscription.nren_l3_core_service.nren_ap_list[0].sbp.edge_port.description,
+                    "new_edge_port": new_edge_port_1,
+                },
+                {
+                    "old_edge_port": subscription.nren_l3_core_service.nren_ap_list[1].sbp.edge_port.description,
+                    "new_edge_port": new_edge_port_2,
+                },
+            ],
+        },
+    ]
+
+    result, _, _ = run_workflow("migrate_nren_l3_core_service", form_input_data)
+
+    assert_complete(result)
+    state = extract_state(result)
+    subscription = NRENL3CoreService.from_subscription(state["subscription_id"])
+    assert subscription.insync is True
+    assert len(subscription.nren_l3_core_service.nren_ap_list) == 2
+    assert str(subscription.nren_l3_core_service.nren_ap_list[0].sbp.edge_port.owner_subscription_id) == new_edge_port_1
+    assert str(subscription.nren_l3_core_service.nren_ap_list[1].sbp.edge_port.owner_subscription_id) == new_edge_port_2
diff --git a/test/workflows/nren_l3_core_service/test_modify_nren_l3_core_service.py b/test/workflows/nren_l3_core_service/test_modify_nren_l3_core_service.py
new file mode 100644
index 0000000000000000000000000000000000000000..197be36c55baf085b5d624bd9bcee8946c7c157e
--- /dev/null
+++ b/test/workflows/nren_l3_core_service/test_modify_nren_l3_core_service.py
@@ -0,0 +1,264 @@
+import pytest
+
+from gso.products.product_blocks.bgp_session import IPFamily
+from gso.products.product_types.nren_l3_core_service import NRENL3CoreService, NRENL3CoreServiceType
+from gso.utils.shared_enums import APType
+from test.workflows import extract_state, run_workflow
+
+
+@pytest.mark.parametrize("l3_core_service_type", [NRENL3CoreServiceType.GEANT_IP, NRENL3CoreServiceType.IAS])
+@pytest.mark.workflow()
+def test_modify_nren_l3_core_service_remove_edge_port_success(
+    nren_l3_core_service_subscription_factory, l3_core_service_type
+):
+    subscription_id = nren_l3_core_service_subscription_factory(nren_l3_core_service_type=l3_core_service_type)
+    subscription = NRENL3CoreService.from_subscription(subscription_id)
+    access_port = subscription.nren_l3_core_service.nren_ap_list[0]
+    input_form_data = [
+        {"subscription_id": subscription_id},
+        {
+            "access_ports": [
+                {
+                    "edge_port": str(access_port.sbp.edge_port.owner_subscription_id),
+                    "ap_type": APType.LOAD_BALANCED,
+                }
+            ]  # The factory generates a subscription with two Access Ports, this will remove the second one.
+        },
+        {},
+    ]
+
+    result, _, _ = run_workflow("modify_nren_l3_core_service", input_form_data)
+
+    state = extract_state(result)
+    subscription = NRENL3CoreService.from_subscription(state["subscription_id"])
+    assert len(subscription.nren_l3_core_service.nren_ap_list) == 1
+    assert subscription.nren_l3_core_service.nren_ap_list[0].ap_type == APType.LOAD_BALANCED
+
+
+@pytest.mark.parametrize("l3_core_service_type", [NRENL3CoreServiceType.GEANT_IP, NRENL3CoreServiceType.IAS])
+@pytest.mark.workflow()
+def test_modify_nren_l3_core_service_add_new_edge_port_success(
+    nren_l3_core_service_subscription_factory,
+    edge_port_subscription_factory,
+    partner_factory,
+    faker,
+    l3_core_service_type,
+):
+    partner = partner_factory()
+    new_edge_port = edge_port_subscription_factory(partner=partner)
+    subscription_id = nren_l3_core_service_subscription_factory(
+        partner=partner, nren_l3_core_service_type=l3_core_service_type
+    )
+    subscription = NRENL3CoreService.from_subscription(subscription_id)
+    input_form_data = [
+        {"subscription_id": subscription_id},
+        {
+            "access_ports": [
+                {
+                    "edge_port": str(port.sbp.edge_port.owner_subscription_id),
+                    "ap_type": port.ap_type,
+                }
+                for port in subscription.nren_l3_core_service.nren_ap_list
+            ]
+            + [
+                {
+                    "edge_port": str(new_edge_port),
+                    "ap_type": APType.BACKUP,
+                }
+            ]
+        },
+        {},  # The existing SBPs are unchanged
+        {},
+        {  # Adding configuration for the new SBP
+            "geant_sid": faker.geant_sid(),
+            "vlan_id": faker.vlan_id(),
+            "ipv4_address": faker.ipv4(),
+            "ipv6_address": faker.ipv6(),
+            "v4_bgp_peer": {
+                "authentication_key": faker.password(),
+                "peer_address": faker.ipv4(),
+            },
+            "v6_bgp_peer": {
+                "authentication_key": faker.password(),
+                "peer_address": faker.ipv6(),
+            },
+        },
+    ]
+
+    result, _, _ = run_workflow("modify_nren_l3_core_service", input_form_data)
+
+    state = extract_state(result)
+    subscription = NRENL3CoreService.from_subscription(state["subscription_id"])
+    assert len(subscription.nren_l3_core_service.nren_ap_list) == 3
+
+
+@pytest.fixture()
+def sbp_input_form_data(faker):
+    def _generate_form_data():
+        return {
+            "geant_sid": faker.geant_sid(),
+            "is_tagged": True,
+            "vlan_id": faker.vlan_id(),
+            "ipv4_address": faker.ipv4(),
+            "ipv6_address": faker.ipv6(),
+            "custom_firewall_filters": True,
+            "v4_bgp_peer": {
+                "bfd_enabled": True,
+                "bfd_interval": faker.pyint(),
+                "bfd_multiplier": faker.pyint(),
+                "has_custom_policies": True,
+                "authentication_key": faker.password(),
+                "multipath_enabled": True,
+                "send_default_route": True,
+                "is_passive": True,
+                "peer_address": faker.ipv4(),
+                "add_v4_multicast": True,
+            },
+            "v6_bgp_peer": {
+                "bfd_enabled": True,
+                "bfd_interval": faker.pyint(),
+                "bfd_multiplier": faker.pyint(),
+                "has_custom_policies": True,
+                "authentication_key": faker.password(),
+                "multipath_enabled": True,
+                "send_default_route": True,
+                "is_passive": True,
+                "peer_address": faker.ipv6(),
+                "add_v6_multicast": True,
+            },
+        }
+
+    return _generate_form_data
+
+
+@pytest.mark.parametrize("l3_core_service_type", [NRENL3CoreServiceType.GEANT_IP, NRENL3CoreServiceType.IAS])
+@pytest.mark.workflow()
+def test_modify_nren_l3_core_service_modify_edge_port_success(
+    faker, nren_l3_core_service_subscription_factory, l3_core_service_type, sbp_input_form_data
+):
+    subscription_id = nren_l3_core_service_subscription_factory(nren_l3_core_service_type=l3_core_service_type)
+    subscription = NRENL3CoreService.from_subscription(subscription_id)
+    new_sbp_data = [sbp_input_form_data(), sbp_input_form_data()]
+    input_form_data = [
+        {"subscription_id": subscription_id},
+        {
+            "access_ports": [
+                {
+                    "edge_port": str(port.sbp.edge_port.owner_subscription_id),
+                    "ap_type": port.ap_type,
+                }
+                for port in subscription.nren_l3_core_service.nren_ap_list
+            ]
+        },
+        {**new_sbp_data[0]},
+        {**new_sbp_data[1]},
+    ]
+
+    result, _, _ = run_workflow("modify_nren_l3_core_service", input_form_data)
+
+    state = extract_state(result)
+    subscription = NRENL3CoreService.from_subscription(state["subscription_id"])
+    assert len(subscription.nren_l3_core_service.nren_ap_list) == 2
+
+    for i in range(2):
+        assert subscription.nren_l3_core_service.nren_ap_list[i].sbp.geant_sid == new_sbp_data[i]["geant_sid"]
+        assert subscription.nren_l3_core_service.nren_ap_list[i].sbp.is_tagged == new_sbp_data[i]["is_tagged"]
+        assert subscription.nren_l3_core_service.nren_ap_list[i].sbp.vlan_id == new_sbp_data[i]["vlan_id"]
+        assert (
+            str(subscription.nren_l3_core_service.nren_ap_list[i].sbp.ipv4_address) == new_sbp_data[i]["ipv4_address"]
+        )
+        assert (
+            str(subscription.nren_l3_core_service.nren_ap_list[i].sbp.ipv6_address) == new_sbp_data[i]["ipv6_address"]
+        )
+        assert (
+            subscription.nren_l3_core_service.nren_ap_list[i].sbp.custom_firewall_filters
+            == new_sbp_data[i]["custom_firewall_filters"]
+        )
+
+        assert (
+            subscription.nren_l3_core_service.nren_ap_list[i].sbp.bgp_session_list[0].bfd_enabled
+            == new_sbp_data[i]["v4_bgp_peer"]["bfd_enabled"]
+        )
+        assert (
+            subscription.nren_l3_core_service.nren_ap_list[i].sbp.bgp_session_list[0].bfd_interval
+            == new_sbp_data[i]["v4_bgp_peer"]["bfd_interval"]
+        )
+        assert (
+            subscription.nren_l3_core_service.nren_ap_list[i].sbp.bgp_session_list[0].bfd_multiplier
+            == new_sbp_data[i]["v4_bgp_peer"]["bfd_multiplier"]
+        )
+        assert (
+            subscription.nren_l3_core_service.nren_ap_list[i].sbp.bgp_session_list[0].has_custom_policies
+            == new_sbp_data[i]["v4_bgp_peer"]["has_custom_policies"]
+        )
+        assert (
+            subscription.nren_l3_core_service.nren_ap_list[i].sbp.bgp_session_list[0].authentication_key
+            == new_sbp_data[i]["v4_bgp_peer"]["authentication_key"]
+        )
+        assert (
+            subscription.nren_l3_core_service.nren_ap_list[i].sbp.bgp_session_list[0].multipath_enabled
+            == new_sbp_data[i]["v4_bgp_peer"]["multipath_enabled"]
+        )
+        assert (
+            subscription.nren_l3_core_service.nren_ap_list[i].sbp.bgp_session_list[0].send_default_route
+            == new_sbp_data[i]["v4_bgp_peer"]["send_default_route"]
+        )
+        assert (
+            subscription.nren_l3_core_service.nren_ap_list[i].sbp.bgp_session_list[0].is_passive
+            == new_sbp_data[i]["v4_bgp_peer"]["is_passive"]
+        )
+        assert (
+            str(subscription.nren_l3_core_service.nren_ap_list[i].sbp.bgp_session_list[0].peer_address)
+            == new_sbp_data[i]["v4_bgp_peer"]["peer_address"]
+        )
+        assert (
+            bool(
+                IPFamily.V4MULTICAST
+                in subscription.nren_l3_core_service.nren_ap_list[i].sbp.bgp_session_list[0].families
+            )
+            == new_sbp_data[i]["v4_bgp_peer"]["add_v4_multicast"]
+        )
+
+        assert (
+            subscription.nren_l3_core_service.nren_ap_list[i].sbp.bgp_session_list[1].bfd_enabled
+            == new_sbp_data[i]["v6_bgp_peer"]["bfd_enabled"]
+        )
+        assert (
+            subscription.nren_l3_core_service.nren_ap_list[i].sbp.bgp_session_list[1].bfd_interval
+            == new_sbp_data[i]["v6_bgp_peer"]["bfd_interval"]
+        )
+        assert (
+            subscription.nren_l3_core_service.nren_ap_list[i].sbp.bgp_session_list[1].bfd_multiplier
+            == new_sbp_data[i]["v6_bgp_peer"]["bfd_multiplier"]
+        )
+        assert (
+            subscription.nren_l3_core_service.nren_ap_list[i].sbp.bgp_session_list[1].has_custom_policies
+            == new_sbp_data[i]["v6_bgp_peer"]["has_custom_policies"]
+        )
+        assert (
+            subscription.nren_l3_core_service.nren_ap_list[i].sbp.bgp_session_list[1].authentication_key
+            == new_sbp_data[i]["v6_bgp_peer"]["authentication_key"]
+        )
+        assert (
+            subscription.nren_l3_core_service.nren_ap_list[i].sbp.bgp_session_list[1].multipath_enabled
+            == new_sbp_data[i]["v6_bgp_peer"]["multipath_enabled"]
+        )
+        assert (
+            subscription.nren_l3_core_service.nren_ap_list[i].sbp.bgp_session_list[1].send_default_route
+            == new_sbp_data[i]["v6_bgp_peer"]["send_default_route"]
+        )
+        assert (
+            subscription.nren_l3_core_service.nren_ap_list[i].sbp.bgp_session_list[1].is_passive
+            == new_sbp_data[i]["v6_bgp_peer"]["is_passive"]
+        )
+        assert (
+            str(subscription.nren_l3_core_service.nren_ap_list[i].sbp.bgp_session_list[1].peer_address)
+            == new_sbp_data[i]["v6_bgp_peer"]["peer_address"]
+        )
+        assert (
+            bool(
+                IPFamily.V6MULTICAST
+                in subscription.nren_l3_core_service.nren_ap_list[i].sbp.bgp_session_list[1].families
+            )
+            == new_sbp_data[i]["v6_bgp_peer"]["add_v6_multicast"]
+        )
diff --git a/test/workflows/office_router/test_import_office_router.py b/test/workflows/office_router/test_import_office_router.py
index c9894e98eeb5abb69843f849e04c6209daff8983..e86e3ed1978271adb61cc4c0024759434ba9953a 100644
--- a/test/workflows/office_router/test_import_office_router.py
+++ b/test/workflows/office_router/test_import_office_router.py
@@ -15,4 +15,4 @@ def test_import_office_router_success(office_router_subscription_factory):
     assert_complete(result)
     assert subscription.product.name == ProductName.OFFICE_ROUTER
     assert subscription.status == SubscriptionLifecycle.ACTIVE
-    assert subscription.insync
+    assert subscription.insync is True
diff --git a/test/workflows/opengear/test_create_imported_opengear.py b/test/workflows/opengear/test_create_imported_opengear.py
index 0f6d9bf171103c5b5152937b47114a9b666bad5c..9058c602b96ed8fc08d4d9145706b3c688a321b1 100644
--- a/test/workflows/opengear/test_create_imported_opengear.py
+++ b/test/workflows/opengear/test_create_imported_opengear.py
@@ -1,7 +1,8 @@
 import pytest
 from orchestrator.types import SubscriptionLifecycle
 
-from gso.products import ImportedOpengear, ProductName
+from gso.products import ProductName
+from gso.products.product_types.opengear import ImportedOpengear
 from gso.products.product_types.site import Site
 from test.workflows import (
     assert_complete,
diff --git a/test/workflows/opengear/test_import_opengear.py b/test/workflows/opengear/test_import_opengear.py
index 37c7d1718444341b8f645942b0c6baaf24f7e3a6..6ca1da2523951ffc5ff8fe3e296bfd46fed9a76f 100644
--- a/test/workflows/opengear/test_import_opengear.py
+++ b/test/workflows/opengear/test_import_opengear.py
@@ -15,4 +15,4 @@ def test_import_office_router_success(opengear_subscription_factory):
     assert_complete(result)
     assert subscription.product.name == ProductName.OPENGEAR
     assert subscription.status == SubscriptionLifecycle.ACTIVE
-    assert subscription.insync
+    assert subscription.insync is True
diff --git a/test/workflows/router/test_activate_router.py b/test/workflows/router/test_activate_router.py
index da0c24a6b3826fef0269bb54e348bff8d13be62a..c118dcaf4d1e4cb1efac97d50b3c8b7870c47593 100644
--- a/test/workflows/router/test_activate_router.py
+++ b/test/workflows/router/test_activate_router.py
@@ -1,6 +1,6 @@
 import pytest
 
-from gso.products import Router
+from gso.products.product_types.router import Router
 from test.workflows import (
     assert_complete,
     assert_suspended,
diff --git a/test/workflows/router/test_create_router.py b/test/workflows/router/test_create_router.py
index a6bf456f3ef249d52817b2381c9cc3fcd40a5c86..f9fb37fcfa38159a5d92c1aca8a9bd0aea2c5051 100644
--- a/test/workflows/router/test_create_router.py
+++ b/test/workflows/router/test_create_router.py
@@ -3,9 +3,10 @@ from unittest.mock import patch
 import pytest
 from infoblox_client import objects
 
-from gso.products import ProductName, Site
+from gso.products import ProductName
 from gso.products.product_blocks.router import RouterRole
 from gso.products.product_types.router import Router
+from gso.products.product_types.site import Site
 from gso.services.subscriptions import get_product_id_by_name
 from gso.utils.shared_enums import Vendor
 from test import USER_CONFIRM_EMPTY_FORM
@@ -67,7 +68,7 @@ def test_create_nokia_router_success(
     mock_sharepoint_client.return_value = MockedSharePointClient
 
     #  Run workflow
-    initial_router_data = [{"product": product_id}, router_creation_input_form_data]
+    initial_router_data = [{"product": product_id}, router_creation_input_form_data, {}]
     result, process_stat, step_log = run_workflow("create_router", initial_router_data)
 
     state = extract_state(result)
@@ -175,7 +176,7 @@ def test_create_nokia_router_lso_failure(
 
     #  Run workflow
     product_id = get_product_id_by_name(ProductName.ROUTER)
-    initial_router_data = [{"product": product_id}, router_creation_input_form_data]
+    initial_router_data = [{"product": product_id}, router_creation_input_form_data, {}]
     result, process_stat, step_log = run_workflow("create_router", initial_router_data)
 
     result, step_log = assert_lso_interaction_success(result, process_stat, step_log)
diff --git a/test/workflows/router/test_import_router.py b/test/workflows/router/test_import_router.py
index 3c06b3385b501f031b90ca9769da038295becbb8..2bab9ac22a03bb9a0ec5f60da14fb56403668033 100644
--- a/test/workflows/router/test_import_router.py
+++ b/test/workflows/router/test_import_router.py
@@ -15,4 +15,4 @@ def test_import_site_success(router_subscription_factory):
     assert_complete(result)
     assert subscription.product.name == ProductName.ROUTER
     assert subscription.status == SubscriptionLifecycle.ACTIVE
-    assert subscription.insync
+    assert subscription.insync is True
diff --git a/test/workflows/router/test_modify_connection_stratey.py b/test/workflows/router/test_modify_connection_strategy.py
similarity index 94%
rename from test/workflows/router/test_modify_connection_stratey.py
rename to test/workflows/router/test_modify_connection_strategy.py
index e460f2e9b5f36627908434f0e9b6af6a019a4667..fd3a01357a9fbb7558a38a0797d6ff55d23eeeb9 100644
--- a/test/workflows/router/test_modify_connection_stratey.py
+++ b/test/workflows/router/test_modify_connection_strategy.py
@@ -1,6 +1,6 @@
 import pytest
 
-from gso.products import Router
+from gso.products.product_types.router import Router
 from gso.utils.shared_enums import ConnectionStrategy
 from test.workflows import assert_complete, run_workflow
 
diff --git a/test/workflows/router/test_redeploy_base_config.py b/test/workflows/router/test_redeploy_base_config.py
index c9697cb73cfda6ec60699ee547badbbbb4c0dfd3..2023ec04e005cab469e45251061de6138b563c8d 100644
--- a/test/workflows/router/test_redeploy_base_config.py
+++ b/test/workflows/router/test_redeploy_base_config.py
@@ -2,7 +2,7 @@ from unittest.mock import patch
 
 import pytest
 
-from gso.products import Router
+from gso.products.product_types.router import Router
 from test.workflows import (
     assert_complete,
     assert_lso_interaction_success,
diff --git a/test/workflows/router/test_terminate_router.py b/test/workflows/router/test_terminate_router.py
index a182cc744ab35a093b84cdea0e884a5fb93e84f0..d2dca96f7492c67f2396fb8935801670798b43b4 100644
--- a/test/workflows/router/test_terminate_router.py
+++ b/test/workflows/router/test_terminate_router.py
@@ -2,8 +2,8 @@ from unittest.mock import patch
 
 import pytest
 
-from gso.products import Router
 from gso.products.product_blocks.router import RouterRole
+from gso.products.product_types.router import Router
 from test.services.conftest import MockedKentikClient
 from test.workflows import assert_complete, assert_lso_interaction_success, extract_state, run_workflow
 
diff --git a/test/workflows/router/test_update_ibgp_mesh.py b/test/workflows/router/test_update_ibgp_mesh.py
index 023cd2cb5e03bc0420635830e1e6e54c5bb066b8..693bfd671160abe62d35a6340a08c8220c31a59f 100644
--- a/test/workflows/router/test_update_ibgp_mesh.py
+++ b/test/workflows/router/test_update_ibgp_mesh.py
@@ -5,8 +5,8 @@ from orchestrator.types import SubscriptionLifecycle
 from orchestrator.workflow import StepStatus
 from pydantic_forms.exceptions import FormValidationError
 
-from gso.products import Iptrunk
 from gso.products.product_blocks.router import RouterRole
+from gso.products.product_types.iptrunk import Iptrunk
 from test import USER_CONFIRM_EMPTY_FORM
 from test.workflows import (
     assert_lso_interaction_success,
diff --git a/test/workflows/site/test_create_site.py b/test/workflows/site/test_create_site.py
index 7642debea0c2739b1a5845db4404f35c5205884a..112e078f39c78a1545ada8d14be18dbd61da7858 100644
--- a/test/workflows/site/test_create_site.py
+++ b/test/workflows/site/test_create_site.py
@@ -25,6 +25,7 @@ def test_create_site(responses, faker):
             "site_tier": SiteTier.TIER1,
             "site_ts_address": faker.ipv4(),
         },
+        {},
     ]
     result, _, _ = run_workflow("create_site", initial_site_data)
     assert_complete(result)
@@ -66,6 +67,7 @@ def test_site_name_is_incorrect(responses, faker):
             "site_ts_address": faker.ipv4(),
             "partner": "GEANT",
         },
+        {},
     ]
 
     with pytest.raises(FormValidationError, match=expected_exception_msg):
diff --git a/test/workflows/site/test_import_site.py b/test/workflows/site/test_import_site.py
index ac476c107498f342e2e206e42d6457c966fd6732..f706a4737b5c2747693529ab084646c47848d32e 100644
--- a/test/workflows/site/test_import_site.py
+++ b/test/workflows/site/test_import_site.py
@@ -15,4 +15,4 @@ def test_import_site_success(site_subscription_factory):
     assert_complete(result)
     assert subscription.product.name == ProductName.SITE
     assert subscription.status == SubscriptionLifecycle.ACTIVE
-    assert subscription.insync
+    assert subscription.insync is True
diff --git a/test/workflows/site/test_modify_site.py b/test/workflows/site/test_modify_site.py
index 0db1a50fcd9f7880aeb574fa465afc08e832e808..fb45a9576e6917e66af9370dab3a69435bdf20db 100644
--- a/test/workflows/site/test_modify_site.py
+++ b/test/workflows/site/test_modify_site.py
@@ -6,15 +6,16 @@ from test.workflows import assert_complete, extract_state, run_workflow
 
 
 @pytest.mark.workflow()
-def test_modify_site(responses, site_subscription_factory):
+def test_modify_site(responses, site_subscription_factory, faker):
     subscription_id = site_subscription_factory()
     initial_site_data = [
         {"subscription_id": subscription_id},
         {
-            "site_bgp_community_id": 10,
-            "site_internal_id": 20,
-            "site_ts_address": "127.0.0.1",
+            "site_bgp_community_id": faker.pyint(),
+            "site_internal_id": faker.pyint(),
+            "site_ts_address": faker.ipv4(),
         },
+        {},
     ]
     result, _, _ = run_workflow("modify_site", initial_site_data)
     assert_complete(result)
@@ -28,16 +29,57 @@ def test_modify_site(responses, site_subscription_factory):
 
 
 @pytest.mark.workflow()
-def test_modify_site_with_invalid_data(responses, site_subscription_factory):
-    subscription_a = Site.from_subscription(site_subscription_factory())
-    subscription_b = Site.from_subscription(site_subscription_factory())
+def test_modify_site_with_duplicate_bgp_community_id(faker, site_subscription_factory):
+    duplicate_bgp_community_id = faker.pyint()
+
+    site_subscription_factory(site_bgp_community_id=duplicate_bgp_community_id)
+    subscription_b = site_subscription_factory()
 
     initial_site_data = [
-        {"subscription_id": subscription_b.subscription_id},
+        {"subscription_id": subscription_b},
         {
-            "site_bgp_community_id": subscription_a.site.site_bgp_community_id,
+            "site_bgp_community_id": duplicate_bgp_community_id,
         },
+        {},
     ]
 
     with pytest.raises(FormValidationError, match="site_bgp_community_id must be unique"):
         run_workflow("modify_site", initial_site_data)
+
+
+@pytest.mark.workflow()
+def test_modify_site_with_duplicate_internal_id(faker, site_subscription_factory):
+    duplicate_internal_id = faker.pyint()
+
+    site_subscription_factory(site_internal_id=duplicate_internal_id)
+    subscription_b = site_subscription_factory()
+
+    initial_site_data = [
+        {"subscription_id": subscription_b},
+        {
+            "site_internal_id": duplicate_internal_id,
+        },
+        {},
+    ]
+
+    with pytest.raises(FormValidationError, match="site_internal_id must be unique"):
+        run_workflow("modify_site", initial_site_data)
+
+
+@pytest.mark.workflow()
+def test_modify_site_with_duplicate_ts_address(faker, site_subscription_factory):
+    duplicate_ts_address = faker.ipv4()
+
+    site_subscription_factory(site_ts_address=duplicate_ts_address)
+    subscription_b = site_subscription_factory()
+
+    initial_site_data = [
+        {"subscription_id": subscription_b},
+        {
+            "site_ts_address": duplicate_ts_address,
+        },
+        {},
+    ]
+
+    with pytest.raises(FormValidationError, match="site_ts_address must be unique"):
+        run_workflow("modify_site", initial_site_data)
diff --git a/test/workflows/super_pop_switch/test_import_super_pop_switch.py b/test/workflows/super_pop_switch/test_import_super_pop_switch.py
index 4d20774c7d91470f6210da9e19d15632794bbf7c..2961b53988122eec82bcb4ccbde63f85acbfb864 100644
--- a/test/workflows/super_pop_switch/test_import_super_pop_switch.py
+++ b/test/workflows/super_pop_switch/test_import_super_pop_switch.py
@@ -15,4 +15,4 @@ def test_import_super_pop_switch_success(super_pop_switch_subscription_factory):
     assert_complete(result)
     assert subscription.product.name == ProductName.SUPER_POP_SWITCH
     assert subscription.status == SubscriptionLifecycle.ACTIVE
-    assert subscription.insync
+    assert subscription.insync is True
diff --git a/test/workflows/tasks/test_delete_partners.py b/test/workflows/tasks/test_delete_partners.py
index b0964bdfc93be71624a8f947876cae956a4810b2..3fb36c8415016939ac22cad68f2e3590d5c9399f 100644
--- a/test/workflows/tasks/test_delete_partners.py
+++ b/test/workflows/tasks/test_delete_partners.py
@@ -6,7 +6,7 @@ from pydantic_forms.exceptions import FormValidationError
 from sqlalchemy import select
 
 from gso.services.partners import filter_partners_by_name
-from test.fixtures import create_subscription_for_mapping
+from test.fixtures.common_fixtures import create_subscription_for_mapping
 from test.workflows import assert_complete, run_workflow
 
 CORRECT_SUBSCRIPTION = str(uuid4())
diff --git a/tox.ini b/tox.ini
index a6d04258b1967deeb79007b5ba99e27d12559547..b5cb43cbef816cce1a45cc953c6d6f7aa7441b8c 100644
--- a/tox.ini
+++ b/tox.ini
@@ -20,7 +20,7 @@ commands =
     ruff check --respect-gitignore --preview .
     ruff format --respect-gitignore --preview --check .
     mypy .
-    sh -c 'if [ $SKIP_ALL_TESTS = 1 ]; then echo "Skipping coverage report"; else pytest --cov=gso --cov-report=xml --cov-report=html --cov-fail-under=85 -n auto {posargs}; fi'
+    sh -c 'if [ $SKIP_ALL_TESTS = 1 ]; then echo "Skipping coverage report"; else pytest --cov=gso --cov-report=xml --cov-report=html --cov-fail-under=90 -n auto {posargs}; fi'
 
 allowlist_externals =
     sh