diff --git a/Changelog.md b/Changelog.md
index bd167f8ed8c18eef0ab1af670a9b84cae3cac900..0073e89a7b1fc69886aa7c47e5794ab58ffaa9a0 100644
--- a/Changelog.md
+++ b/Changelog.md
@@ -1,6 +1,13 @@
 # Changelog
 
-## [2.5] - 2024-11-21
+## [2.26] - 2024-11-27
+- Rename the `NRENL3CoreService` to `L3CoreService`
+- To the existing Layer 3 core services, add LHCOne, Copernicus and IAS-GWS
+- Add Layer 2 circuit services (GÉANT+ and Azure ExpressRoute)
+- Add a field to BGP sessions for storing prefix limits
+- Update L3 Core service to allow for BFD settings in both a BGP session and the Service Binding Port
+
+## [2.25] - 2024-11-21
 - Updated a trunk description when it is migrated.
 - Made moodi steps indifferent about failure
 - Added a router to Kentik when creating with a PE role
diff --git a/docs/source/module/workflows/l2_circuit/nren_l3_core_service/create_imported_layer_2_circuit.rst b/docs/source/module/workflows/l2_circuit/nren_l3_core_service/create_imported_layer_2_circuit.rst
new file mode 100644
index 0000000000000000000000000000000000000000..c8683dd28a8653080494be20c86f8a658305d826
--- /dev/null
+++ b/docs/source/module/workflows/l2_circuit/nren_l3_core_service/create_imported_layer_2_circuit.rst
@@ -0,0 +1,6 @@
+``gso.workflows.l2_circuit.create_imported_layer_2_circuit``
+============================================================
+
+.. automodule:: gso.workflows.l2_circuit.create_imported_layer_2_circuit
+   :members:
+   :show-inheritance:
diff --git a/docs/source/module/workflows/l2_circuit/nren_l3_core_service/create_layer_2_circuit.rst b/docs/source/module/workflows/l2_circuit/nren_l3_core_service/create_layer_2_circuit.rst
new file mode 100644
index 0000000000000000000000000000000000000000..c19b27a1fef15935a4190414dae474ef45eccdb6
--- /dev/null
+++ b/docs/source/module/workflows/l2_circuit/nren_l3_core_service/create_layer_2_circuit.rst
@@ -0,0 +1,6 @@
+``gso.workflows.l2_circuit.create_layer_2_circuit``
+===================================================
+
+.. automodule:: gso.workflows.l2_circuit.create_layer_2_circuit
+   :members:
+   :show-inheritance:
diff --git a/docs/source/module/workflows/l2_circuit/nren_l3_core_service/import_layer_2_circuit.rst b/docs/source/module/workflows/l2_circuit/nren_l3_core_service/import_layer_2_circuit.rst
new file mode 100644
index 0000000000000000000000000000000000000000..10231456b09c062f5fec98b06aaa9a7d22db84ee
--- /dev/null
+++ b/docs/source/module/workflows/l2_circuit/nren_l3_core_service/import_layer_2_circuit.rst
@@ -0,0 +1,6 @@
+``gso.workflows.l2_circuit.import_layer_2_circuit``
+===================================================
+
+.. automodule:: gso.workflows.l2_circuit.import_layer_2_circuit
+   :members:
+   :show-inheritance:
diff --git a/docs/source/module/workflows/l2_circuit/nren_l3_core_service/index.rst b/docs/source/module/workflows/l2_circuit/nren_l3_core_service/index.rst
new file mode 100644
index 0000000000000000000000000000000000000000..5e3d52fb1aff1fcf798b52fe09f3d58fab38aa30
--- /dev/null
+++ b/docs/source/module/workflows/l2_circuit/nren_l3_core_service/index.rst
@@ -0,0 +1,20 @@
+``gso.workflows.L2_circuit
+==========================
+
+.. automodule:: gso.workflows.L2_circuit
+   :members:
+   :show-inheritance:
+
+Submodules
+----------
+
+.. toctree::
+   :maxdepth: 2
+   :titlesonly:
+
+   create_layer_2_circuit
+   modify_layer_2_circuit
+   terminate_layer_2_circuit
+   create_imported_layer_2_circuit
+   import_layer_2_circuit
+
diff --git a/docs/source/module/workflows/l2_circuit/nren_l3_core_service/modify_layer_2_circuit.rst b/docs/source/module/workflows/l2_circuit/nren_l3_core_service/modify_layer_2_circuit.rst
new file mode 100644
index 0000000000000000000000000000000000000000..f46d037adb145b1d6efe177f365e582ea743e3c6
--- /dev/null
+++ b/docs/source/module/workflows/l2_circuit/nren_l3_core_service/modify_layer_2_circuit.rst
@@ -0,0 +1,6 @@
+``gso.workflows.l2_circuit.modify_layer_2_circuit``
+===================================================
+
+.. automodule:: gso.workflows.l2_circuit.modify_layer_2_circuit
+   :members:
+   :show-inheritance:
diff --git a/gso/cli/imports.py b/gso/cli/imports.py
index cd2e5998dd39ea965f1c476357c1738afeaae372..bca9ccd4bc49daeb873bc6c696deec077fda6892 100644
--- a/gso/cli/imports.py
+++ b/gso/cli/imports.py
@@ -13,7 +13,7 @@ import yaml
 from orchestrator.db import db
 from orchestrator.services.processes import start_process
 from orchestrator.types import SubscriptionLifecycle, UUIDstr
-from pydantic import BaseModel, ValidationError, field_validator, model_validator
+from pydantic import BaseModel, NonNegativeInt, ValidationError, field_validator, model_validator
 from sqlalchemy.exc import SQLAlchemyError
 
 from gso.db.models import PartnerTable
@@ -21,10 +21,11 @@ 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.layer_2_circuit import Layer2CircuitType
 from gso.products.product_blocks.router import RouterRole
-from gso.products.product_blocks.service_binding_port import VLAN_ID
 from gso.products.product_blocks.switch import SwitchModel
-from gso.products.product_types.nren_l3_core_service import NRENL3CoreServiceType
+from gso.products.product_types.edge_port import EdgePort
+from gso.products.product_types.layer_2_circuit import Layer2CircuitServiceType
 from gso.services.partners import (
     PartnerEmail,
     PartnerName,
@@ -39,7 +40,7 @@ from gso.services.subscriptions import (
 )
 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.interfaces import BandwidthString, LAGMember, LAGMemberList, PhysicalPortCapacity
 from gso.utils.types.ip_address import (
     AddressSpace,
     IPAddress,
@@ -50,6 +51,7 @@ from gso.utils.types.ip_address import (
     IPV6Netmask,
     PortNumber,
 )
+from gso.utils.types.virtual_identifiers import VC_ID, VLAN_ID
 
 app: typer.Typer = typer.Typer()
 
@@ -236,17 +238,15 @@ class EdgePortImportModel(BaseModel):
         return self
 
 
-class NRENL3CoreServiceImportModel(BaseModel):
-    """Import :term:`NREN` L3 Core Service model."""
+class L3CoreServiceImportModel(BaseModel):
+    """Import 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
+        authentication_key: str | None
         multipath_enabled: bool = False
         send_default_route: bool = False
         is_passive: bool = False
@@ -254,6 +254,15 @@ class NRENL3CoreServiceImportModel(BaseModel):
         families: list[IPFamily]
         is_multi_hop: bool
         rtbh_enabled: bool  # whether Remote Triggered Blackhole is enabled
+        prefix_limit: NonNegativeInt | None = None
+
+    class BFDSettingsModel(BaseModel):
+        """BFD Settings model."""
+
+        bfd_enabled: bool = False
+        bfd_interval_rx: int | None = None
+        bfd_interval_tx: int | None = None
+        bfd_multiplier: int | None = None
 
     class ServiceBindingPort(BaseModel):
         """Service Binding model."""
@@ -270,7 +279,9 @@ class NRENL3CoreServiceImportModel(BaseModel):
         ipv6_address: IPv6AddressType
         ipv6_mask: IPV6Netmask
         is_multi_hop: bool = True
-        bgp_peers: list["NRENL3CoreServiceImportModel.BaseBGPPeer"]
+        bgp_peers: list["L3CoreServiceImportModel.BaseBGPPeer"]
+        v4_bfd_settings: "L3CoreServiceImportModel.BFDSettingsModel"
+        v6_bfd_settings: "L3CoreServiceImportModel.BFDSettingsModel"
 
     partner: str
     service_binding_ports: list[ServiceBindingPort]
@@ -327,6 +338,49 @@ class LanSwitchInterconnectImportModel(BaseModel):
     switch_side: LanSwitchInterconnectSwitchSideImportModel
 
 
+class Layer2CircuitServiceImportModel(BaseModel):
+    """Import Layer 2 Circuit Service model."""
+
+    class ServiceBindingPortInput(BaseModel):
+        """Service Binding Port model."""
+
+        edge_port: UUIDstr
+        vlan_id: VLAN_ID
+
+    service_type: Layer2CircuitServiceType
+    partner: str
+    geant_sid: str
+    vc_id: VC_ID
+    layer_2_circuit_side_a: ServiceBindingPortInput
+    layer_2_circuit_side_b: ServiceBindingPortInput
+    layer_2_circuit_type: Layer2CircuitType
+    vlan_range_lower_bound: VLAN_ID | None = None
+    vlan_range_upper_bound: VLAN_ID | None = None
+    policer_enabled: bool = False
+    policer_bandwidth: BandwidthString | None = None
+    policer_burst_rate: BandwidthString | 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
+
+    @model_validator(mode="after")
+    def check_if_edge_ports_exist(self) -> Self:
+        """Check if the edge ports exist."""
+        for side in [self.layer_2_circuit_side_a, self.layer_2_circuit_side_b]:
+            if not EdgePort.from_subscription(side.edge_port):
+                msg = f"Edge Port {side.edge_port} not found"
+                raise ValueError(msg)
+        return self
+
+
 T = TypeVar(
     "T",
     SiteImportModel,
@@ -337,8 +391,9 @@ T = TypeVar(
     OfficeRouterImportModel,
     OpenGearImportModel,
     EdgePortImportModel,
-    NRENL3CoreServiceImportModel,
+    L3CoreServiceImportModel,
     LanSwitchInterconnectImportModel,
+    Layer2CircuitServiceImportModel,
 )
 
 common_filepath_option = typer.Option(
@@ -601,20 +656,20 @@ def import_partners(file_path: str = typer.Argument(..., help="Path to the CSV f
 
 
 @app.command()
-def import_nren_l3_core_service(filepath: str = common_filepath_option) -> None:
-    """Import :term:`NREN` L3 Core Services into :term:`GSO`."""
+def import_l3_core_service(filepath: str = common_filepath_option) -> None:
+    """Import L3 Core Services into :term:`GSO`."""
     successfully_imported_data = []
-    nren_l3_core_service_list = _read_data(Path(filepath))
+    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"])
+    for l3_core_service in l3_core_service_list:
+        partner = l3_core_service["partner"]
+        service_type = 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"]]
+            initial_data = L3CoreServiceImportModel(**l3_core_service)
+            start_process("create_imported_l3_core_service", [initial_data.model_dump()])
+            edge_ports = [sbp["edge_port"] for sbp in 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:
@@ -625,17 +680,22 @@ def import_nren_l3_core_service(filepath: str = common_filepath_option) -> None:
 
     # Migrate new products from imported to "full" counterpart.
     imported_products = get_subscriptions(
-        product_types=[ProductType.IMPORTED_GEANT_IP, ProductType.IMPORTED_IAS],
+        product_types=[
+            ProductType.IMPORTED_GEANT_IP,
+            ProductType.IMPORTED_IAS,
+            ProductType.IMPORTED_LHCONE,
+            ProductType.IMPORTED_COPERNICUS,
+        ],
         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])
+        start_process("import_l3_core_service", [subscription_id])
 
     if successfully_imported_data:
-        typer.echo("Successfully created imported NREN L3 Core Services:")
+        typer.echo("Successfully created imported L3 Core Services:")
         for item in successfully_imported_data:
             typer.echo(f"- {item}")
 
@@ -650,3 +710,44 @@ def import_lan_switch_interconnect(filepath: str = common_filepath_option) -> No
         "lan_switch_interconnect_description",
         LanSwitchInterconnectImportModel,
     )
+
+
+@app.command()
+def import_layer_2_circuit_service(filepath: str = common_filepath_option) -> None:
+    """Import Layer 2 Circuit services into GSO."""
+    successfully_imported_data = []
+    layer_2_circuit_service_list = _read_data(Path(filepath))
+
+    for layer_2_circuit_service in layer_2_circuit_service_list:
+        partner = layer_2_circuit_service["partner"]
+        service_type = Layer2CircuitServiceType(layer_2_circuit_service["service_type"])
+        typer.echo(f"Creating imported {service_type} for {partner}")
+
+        try:
+            initial_data = Layer2CircuitServiceImportModel(**layer_2_circuit_service)
+            start_process("create_imported_layer_2_circuit", [initial_data.model_dump()])
+            successfully_imported_data.append(initial_data.vc_id)
+            typer.echo(
+                f"Successfully created imported {service_type} with virtual circuit ID {initial_data.vc_id}"
+                f" 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_EXPRESSROUTE, ProductType.IMPORTED_GEANT_PLUS],
+        lifecycles=[SubscriptionLifecycle.ACTIVE],
+        includes=["subscription_id"],
+    )
+
+    for subscription_id in imported_products:
+        typer.echo(f"Importing {subscription_id}")
+        start_process("import_layer_2_circuit", [subscription_id])
+
+    if successfully_imported_data:
+        typer.echo("Successfully created imported Layer 2 Circuit services:")
+        for item in successfully_imported_data:
+            typer.echo(f"- {item}")
diff --git a/gso/migrations/versions/2024-10-29_5132c463214d_add_layer_2_cricuits_services_domain_.py b/gso/migrations/versions/2024-10-29_5132c463214d_add_layer_2_cricuits_services_domain_.py
new file mode 100644
index 0000000000000000000000000000000000000000..42bc0a1eb83f6aa3b61ebada2bc64048c301d798
--- /dev/null
+++ b/gso/migrations/versions/2024-10-29_5132c463214d_add_layer_2_cricuits_services_domain_.py
@@ -0,0 +1,182 @@
+"""Add Layer 2 Cricuits services domain models..
+
+Revision ID: 5132c463214d
+Revises: 0e7e7d749617
+Create Date: 2024-10-29 13:34:39.234303
+
+"""
+import sqlalchemy as sa
+from alembic import op
+
+# revision identifiers, used by Alembic.
+revision = '5132c463214d'
+down_revision = '0e7e7d749617'
+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 ('GÉANT Plus', 'GÉANT Plus', 'Layer2Circuit', 'G_PLUS', 'active') RETURNING products.product_id
+    """))
+    conn.execute(sa.text("""
+INSERT INTO products (name, description, product_type, tag, status) VALUES ('Imported GÉANT Plus', 'Imported GEANT Plus', 'ImportedLayer2Circuit', 'IMP_G_PLUS', 'active') RETURNING products.product_id
+    """))
+    conn.execute(sa.text("""
+INSERT INTO products (name, description, product_type, tag, status) VALUES ('Azure ExpressRoute', 'Azure ExpressRoute product', 'Layer2Circuit', 'ER', 'active') RETURNING products.product_id
+    """))
+    conn.execute(sa.text("""
+INSERT INTO products (name, description, product_type, tag, status) VALUES ('Imported Azure ExpressRoute', 'Imported Azure ExpressRoute', 'ImportedLayer2Circuit', 'IMP_ER', 'active') RETURNING products.product_id
+    """))
+    conn.execute(sa.text("""
+INSERT INTO fixed_inputs (name, value, product_id) VALUES ('layer_2_circuit_service_type', 'Azure ExpressRoute', (SELECT products.product_id FROM products WHERE products.name IN ('Azure ExpressRoute'))), ('layer_2_circuit_service_type', 'Imported Azure ExpressRoute', (SELECT products.product_id FROM products WHERE products.name IN ('Imported Azure ExpressRoute'))), ('layer_2_circuit_service_type', 'Imported GÉANT Plus', (SELECT products.product_id FROM products WHERE products.name IN ('Imported GÉANT Plus'))), ('layer_2_circuit_service_type', 'GÉANT Plus', (SELECT products.product_id FROM products WHERE products.name IN ('GÉANT Plus')))
+    """))
+    conn.execute(sa.text("""
+INSERT INTO product_blocks (name, description, tag, status) VALUES ('Layer2CircuitBlock', 'Layer 2 Circuit product block', 'L2_C_BLOCK', 'active') RETURNING product_blocks.product_block_id
+    """))
+    conn.execute(sa.text("""
+INSERT INTO product_blocks (name, description, tag, status) VALUES ('Layer2CircuitSideBlock', 'Layer 2 Circuit side product block', 'L2_C_SIDE_BLOCK', 'active') RETURNING product_blocks.product_block_id
+    """))
+    conn.execute(sa.text("""
+INSERT INTO resource_types (resource_type, description) VALUES ('policer_enabled', 'Whether this Layer 2 Circuit is policed.') RETURNING resource_types.resource_type_id
+    """))
+    conn.execute(sa.text("""
+INSERT INTO resource_types (resource_type, description) VALUES ('bandwidth', 'If policed, the bandwidth of the policer is stored.') RETURNING resource_types.resource_type_id
+    """))
+    conn.execute(sa.text("""
+INSERT INTO resource_types (resource_type, description) VALUES ('vlan_range_upper_bound', 'VLAN upper bound range') RETURNING resource_types.resource_type_id
+    """))
+    conn.execute(sa.text("""
+INSERT INTO resource_types (resource_type, description) VALUES ('vlan_range_lower_bound', 'VLAN lower bound range') RETURNING resource_types.resource_type_id
+    """))
+    conn.execute(sa.text("""
+INSERT INTO resource_types (resource_type, description) VALUES ('virtual_circuit_id', 'Virtual Circuit ID of this Layer 2 Circuit.') RETURNING resource_types.resource_type_id
+    """))
+    conn.execute(sa.text("""
+INSERT INTO resource_types (resource_type, description) VALUES ('layer_2_circuit_type', 'The type of circuit, can be tagged or untagged.') 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 ('Azure ExpressRoute')), (SELECT product_blocks.product_block_id FROM product_blocks WHERE product_blocks.name IN ('Layer2CircuitBlock'))), ((SELECT products.product_id FROM products WHERE products.name IN ('Imported Azure ExpressRoute')), (SELECT product_blocks.product_block_id FROM product_blocks WHERE product_blocks.name IN ('Layer2CircuitBlock'))), ((SELECT products.product_id FROM products WHERE products.name IN ('Imported GÉANT Plus')), (SELECT product_blocks.product_block_id FROM product_blocks WHERE product_blocks.name IN ('Layer2CircuitBlock'))), ((SELECT products.product_id FROM products WHERE products.name IN ('GÉANT Plus')), (SELECT product_blocks.product_block_id FROM product_blocks WHERE product_blocks.name IN ('Layer2CircuitBlock')))
+    """))
+    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 ('Layer2CircuitBlock')), (SELECT product_blocks.product_block_id FROM product_blocks WHERE product_blocks.name IN ('Layer2CircuitSideBlock')))
+    """))
+    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 ('Layer2CircuitSideBlock')), (SELECT product_blocks.product_block_id FROM product_blocks WHERE product_blocks.name IN ('ServiceBindingPort')))
+    """))
+    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 ('Layer2CircuitBlock')), (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('virtual_circuit_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 ('Layer2CircuitBlock')), (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('layer_2_circuit_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 ('Layer2CircuitBlock')), (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('vlan_range_lower_bound')))
+    """))
+    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 ('Layer2CircuitBlock')), (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('vlan_range_upper_bound')))
+    """))
+    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 ('Layer2CircuitBlock')), (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('policer_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 ('Layer2CircuitBlock')), (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('bandwidth')))
+    """))
+    conn.execute(sa.text("""
+INSERT INTO resource_types (resource_type, description) VALUES ('policer_burst_rate', 'Max burst rate of a policer') RETURNING resource_types.resource_type_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 ('Layer2CircuitBlock')), (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('policer_burst_rate')))
+    """))
+
+
+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 ('Layer2CircuitBlock')) AND product_block_resource_types.resource_type_id = (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('policer_burst_rate'))
+            """))
+    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 ('Layer2CircuitBlock'))) AND product_block_resource_types.resource_type_id = (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('policer_burst_rate'))
+            """))
+    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 ('policer_burst_rate'))
+            """))
+    conn.execute(sa.text("""
+DELETE FROM resource_types WHERE resource_types.resource_type IN ('policer_burst_rate')
+            """))
+    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 ('Layer2CircuitBlock')) AND product_block_resource_types.resource_type_id = (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('virtual_circuit_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 ('Layer2CircuitBlock'))) AND product_block_resource_types.resource_type_id = (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('virtual_circuit_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 ('Layer2CircuitBlock')) AND product_block_resource_types.resource_type_id = (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('layer_2_circuit_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 ('Layer2CircuitBlock'))) AND product_block_resource_types.resource_type_id = (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('layer_2_circuit_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 ('Layer2CircuitBlock')) AND product_block_resource_types.resource_type_id = (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('vlan_range_lower_bound'))
+    """))
+    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 ('Layer2CircuitBlock'))) AND product_block_resource_types.resource_type_id = (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('vlan_range_lower_bound'))
+    """))
+    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 ('Layer2CircuitBlock')) AND product_block_resource_types.resource_type_id = (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('vlan_range_upper_bound'))
+    """))
+    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 ('Layer2CircuitBlock'))) AND product_block_resource_types.resource_type_id = (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('vlan_range_upper_bound'))
+    """))
+    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 ('Layer2CircuitBlock')) AND product_block_resource_types.resource_type_id = (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('policer_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 ('Layer2CircuitBlock'))) AND product_block_resource_types.resource_type_id = (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('policer_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 ('Layer2CircuitBlock')) AND product_block_resource_types.resource_type_id = (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('bandwidth'))
+    """))
+    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 ('Layer2CircuitBlock'))) AND product_block_resource_types.resource_type_id = (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('bandwidth'))
+    """))
+    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 ('policer_enabled', 'bandwidth', 'vlan_range_upper_bound', 'vlan_range_lower_bound', 'virtual_circuit_id', 'layer_2_circuit_type'))
+    """))
+    conn.execute(sa.text("""
+DELETE FROM resource_types WHERE resource_types.resource_type IN ('policer_enabled', 'bandwidth', 'vlan_range_upper_bound', 'vlan_range_lower_bound', 'virtual_circuit_id', 'layer_2_circuit_type')
+    """))
+    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 ('Azure ExpressRoute', 'Imported Azure ExpressRoute', 'Imported GÉANT Plus', 'GÉANT Plus')) AND product_product_blocks.product_block_id IN (SELECT product_blocks.product_block_id FROM product_blocks WHERE product_blocks.name IN ('Layer2CircuitBlock'))
+    """))
+    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 ('Layer2CircuitBlock')) AND product_block_relations.depends_on_id IN (SELECT product_blocks.product_block_id FROM product_blocks WHERE product_blocks.name IN ('Layer2CircuitSideBlock'))
+    """))
+    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 ('Layer2CircuitSideBlock')) 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 fixed_inputs WHERE fixed_inputs.product_id IN (SELECT products.product_id FROM products WHERE products.name IN ('Azure ExpressRoute', 'Imported Azure ExpressRoute', 'Imported GÉANT Plus', 'GÉANT Plus')) AND fixed_inputs.name = 'layer_2_circuit_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 ('Layer2CircuitSideBlock', 'Layer2CircuitBlock'))
+    """))
+    conn.execute(sa.text("""
+DELETE FROM product_blocks WHERE product_blocks.name IN ('Layer2CircuitSideBlock', 'Layer2CircuitBlock')
+    """))
+    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 ('Azure ExpressRoute', 'Imported Azure ExpressRoute', 'Imported GÉANT Plus', 'GÉANT Plus'))))
+    """))
+    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 ('Azure ExpressRoute', 'Imported Azure ExpressRoute', 'Imported GÉANT Plus', 'GÉANT Plus')))
+    """))
+    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 ('Azure ExpressRoute', 'Imported Azure ExpressRoute', 'Imported GÉANT Plus', 'GÉANT Plus')))
+    """))
+    conn.execute(sa.text("""
+DELETE FROM subscriptions WHERE subscriptions.product_id IN (SELECT products.product_id FROM products WHERE products.name IN ('Azure ExpressRoute', 'Imported Azure ExpressRoute', 'Imported GÉANT Plus', 'GÉANT Plus'))
+    """))
+    conn.execute(sa.text("""
+DELETE FROM products WHERE products.name IN ('Azure ExpressRoute', 'Imported Azure ExpressRoute', 'Imported GÉANT Plus', 'GÉANT Plus')
+    """))
diff --git a/gso/migrations/versions/2024-10-29_72a4f7aa499d_add_l2circuit_workflows.py b/gso/migrations/versions/2024-10-29_72a4f7aa499d_add_l2circuit_workflows.py
new file mode 100644
index 0000000000000000000000000000000000000000..2b6ef3b961046aae7db1f5d50c270191185e0c95
--- /dev/null
+++ b/gso/migrations/versions/2024-10-29_72a4f7aa499d_add_l2circuit_workflows.py
@@ -0,0 +1,63 @@
+"""Add L2Circuit workflows..
+
+Revision ID: 72a4f7aa499d
+Revises: 5132c463214d
+Create Date: 2024-10-29 14:04:29.807253
+
+"""
+import sqlalchemy as sa
+from alembic import op
+
+# revision identifiers, used by Alembic.
+revision = '72a4f7aa499d'
+down_revision = '5132c463214d'
+branch_labels = None
+depends_on = None
+
+
+from orchestrator.migrations.helpers import create_workflow, delete_workflow
+
+new_workflows = [
+    {
+        "name": "create_layer_2_circuit",
+        "target": "CREATE",
+        "description": "Create Layer 2 Circuit Service",
+        "product_type": "Layer2Circuit"
+    },
+    {
+        "name": "modify_layer_2_circuit",
+        "target": "MODIFY",
+        "description": "Modify Layer 2 Circuit Service",
+        "product_type": "Layer2Circuit"
+    },
+    {
+        "name": "terminate_layer_2_circuit",
+        "target": "TERMINATE",
+        "description": "Terminate Layer 2 Circuit Service",
+        "product_type": "Layer2Circuit"
+    },
+    {
+        "name": "create_imported_layer_2_circuit",
+        "target": "CREATE",
+        "description": "Create imported Layer 2 Circuit",
+        "product_type": "ImportedLayer2Circuit"
+    },
+    {
+        "name": "import_layer_2_circuit",
+        "target": "MODIFY",
+        "description": "Import Layer 2 Circuit",
+        "product_type": "ImportedLayer2Circuit"
+    },
+]
+
+
+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-11-18_f6ceec1af371_split_bfd_settings_off_into_separate_.py b/gso/migrations/versions/2024-11-18_f6ceec1af371_split_bfd_settings_off_into_separate_.py
new file mode 100644
index 0000000000000000000000000000000000000000..d1d6cfffeeaa8b7d364e799794024665616a2a88
--- /dev/null
+++ b/gso/migrations/versions/2024-11-18_f6ceec1af371_split_bfd_settings_off_into_separate_.py
@@ -0,0 +1,116 @@
+"""Split BFD settings off into separate product block..
+
+Revision ID: f6ceec1af371
+Revises: 72a4f7aa499d
+Create Date: 2024-11-18 09:40:50.214908
+
+"""
+import sqlalchemy as sa
+from alembic import op
+
+# revision identifiers, used by Alembic.
+revision = 'f6ceec1af371'
+down_revision = '72a4f7aa499d'
+branch_labels = None
+depends_on = None
+
+
+def upgrade() -> 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 ('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 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 ('bfd_interval'))
+    """))
+    conn.execute(sa.text("""
+DELETE FROM resource_types WHERE resource_types.resource_type IN ('bfd_interval')
+    """))
+    conn.execute(sa.text("""
+INSERT INTO product_blocks (name, description, tag, status) VALUES ('BFDSettings', 'A set of settings for BFD', 'BFD_SETTINGS', 'active') RETURNING product_blocks.product_block_id
+    """))
+    conn.execute(sa.text("""
+INSERT INTO resource_types (resource_type, description) VALUES ('bfd_interval_tx', 'the interval TX') RETURNING resource_types.resource_type_id
+    """))
+    conn.execute(sa.text("""
+INSERT INTO resource_types (resource_type, description) VALUES ('bfd_interval_rx', 'the interval RX') RETURNING resource_types.resource_type_id
+    """))
+    conn.execute(sa.text("""
+INSERT INTO resource_types (resource_type, description) VALUES ('ip_type', 'if it''s IPV4 or IPV6') RETURNING resource_types.resource_type_id
+    """))
+    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 ('BFDSettings')))
+    """))
+    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 ('ip_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 ('BFDSettings')), (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 ('BFDSettings')), (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('bfd_interval_tx')))
+    """))
+    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 ('BFDSettings')), (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('bfd_interval_rx')))
+    """))
+    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 ('BFDSettings')), (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('bfd_multiplier')))
+    """))
+
+
+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 ('BGPSession')) AND product_block_resource_types.resource_type_id = (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('ip_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 ('BGPSession'))) AND product_block_resource_types.resource_type_id = (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('ip_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 ('BFDSettings')) 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 ('BFDSettings'))) 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 ('BFDSettings')) 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_tx'))
+    """))
+    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 ('BFDSettings'))) 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_tx'))
+    """))
+    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 ('BFDSettings')) 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_rx'))
+    """))
+    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 ('BFDSettings'))) 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_rx'))
+    """))
+    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 ('BFDSettings')) 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 ('BFDSettings'))) 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 WHERE subscription_instance_values.resource_type_id IN (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('bfd_interval_tx', 'bfd_interval_rx', 'ip_type'))
+    """))
+    conn.execute(sa.text("""
+DELETE FROM resource_types WHERE resource_types.resource_type IN ('bfd_interval_tx', 'bfd_interval_rx', 'ip_type')
+    """))
+    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 ('BFDSettings'))
+    """))
+    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 ('BFDSettings'))
+    """))
+    conn.execute(sa.text("""
+DELETE FROM product_blocks WHERE product_blocks.name IN ('BFDSettings')
+    """))
diff --git a/gso/migrations/versions/2024-11-25_2746f861a765_rename_nrenl3core_service_to_l3core_.py b/gso/migrations/versions/2024-11-25_2746f861a765_rename_nrenl3core_service_to_l3core_.py
new file mode 100644
index 0000000000000000000000000000000000000000..0655d9a44b1bae0605d3426b9c2ef3592032d20b
--- /dev/null
+++ b/gso/migrations/versions/2024-11-25_2746f861a765_rename_nrenl3core_service_to_l3core_.py
@@ -0,0 +1,213 @@
+"""Rename NRENL3Core service to L3Core service and add LHCOne, GWS, and Copernicus.
+
+Revision ID: 2746f861a765
+Revises: f6ceec1af371
+Create Date: 2024-11-25 09:47:08.419689
+
+"""
+import sqlalchemy as sa
+from alembic import op
+
+# revision identifiers, used by Alembic.
+revision = '2746f861a765'
+down_revision = 'f6ceec1af371'
+branch_labels = None
+depends_on = None
+
+from orchestrator.migrations.helpers import create_workflow, delete_workflow
+
+new_workflows = [
+    {
+        "name": "create_l3_core_service",
+        "target": "CREATE",
+        "description": "Create L3 Core Service",
+        "product_type": "L3CoreService"
+    },
+    {
+        "name": "modify_l3_core_service",
+        "target": "MODIFY",
+        "description": "Modify L3 Core Service",
+        "product_type": "L3CoreService"
+    },
+    {
+        "name": "create_imported_l3_core_service",
+        "target": "CREATE",
+        "description": "Create imported L3 Core Service",
+        "product_type": "ImportedL3CoreService"
+    },
+    {
+        "name": "import_l3_core_service",
+        "target": "MODIFY",
+        "description": "Import L3 Core Service",
+        "product_type": "ImportedL3CoreService"
+    },
+    {
+        "name": "migrate_l3_core_service",
+        "target": "MODIFY",
+        "description": "Migrate L3 Core Service",
+        "product_type": "L3CoreService"
+    }
+]
+
+old_workflows = [
+    {
+        "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 old_workflows:
+        delete_workflow(conn, workflow["name"])
+    conn.execute(sa.text("""
+UPDATE fixed_inputs SET name='l3_core_service_type' WHERE fixed_inputs.product_id = (SELECT products.product_id FROM products WHERE products.name IN ('GÉANT IP')) AND fixed_inputs.name = 'nren_l3_core_service_type'
+    """))
+    conn.execute(sa.text("""
+UPDATE fixed_inputs SET name='l3_core_service_type' WHERE fixed_inputs.product_id = (SELECT products.product_id FROM products WHERE products.name IN ('Imported GÉANT IP')) AND fixed_inputs.name = 'nren_l3_core_service_type'
+    """))
+    conn.execute(sa.text("""
+UPDATE fixed_inputs SET name='l3_core_service_type' WHERE fixed_inputs.product_id = (SELECT products.product_id FROM products WHERE products.name IN ('IAS')) AND fixed_inputs.name = 'nren_l3_core_service_type'
+    """))
+    conn.execute(sa.text("""
+UPDATE fixed_inputs SET name='l3_core_service_type' WHERE fixed_inputs.product_id = (SELECT products.product_id FROM products WHERE products.name IN ('Imported IAS')) AND fixed_inputs.name = 'nren_l3_core_service_type'
+    """))
+    conn.execute(sa.text("""
+UPDATE products SET product_type='L3CoreService' WHERE product_type='NRENL3CoreService'
+    """))
+    conn.execute(sa.text("""
+UPDATE products SET product_type='ImportedL3CoreService' WHERE product_type='ImportedNRENL3CoreService'
+        """))
+    conn.execute(sa.text("""
+INSERT INTO products (name, description, product_type, tag, status) VALUES ('LHCOne', 'LHCOne', 'L3CoreService', 'LHC', 'active') RETURNING products.product_id
+    """))
+    conn.execute(sa.text("""
+INSERT INTO products (name, description, product_type, tag, status) VALUES ('Imported LHCOne', 'Imported LHCOne', 'ImportedL3CoreService', 'IMP_LHC', 'active') RETURNING products.product_id
+    """))
+    conn.execute(sa.text("""
+INSERT INTO products (name, description, product_type, tag, status) VALUES ('Copernicus', 'Copernicus', 'L3CoreService', 'COP', 'active') RETURNING products.product_id
+    """))
+    conn.execute(sa.text("""
+INSERT INTO products (name, description, product_type, tag, status) VALUES ('Imported Copernicus', 'Imported Copernicus', 'ImportedL3CoreService', 'IMP_COP', 'active') RETURNING products.product_id
+    """))
+    conn.execute(sa.text("""
+INSERT INTO products (name, description, product_type, tag, status) VALUES ('GWS', 'GWS', 'L3CoreService', 'GWS', 'active') RETURNING products.product_id
+        """))
+    conn.execute(sa.text("""
+INSERT INTO products (name, description, product_type, tag, status) VALUES ('Imported GWS', 'Imported GWS', 'ImportedL3CoreService', 'IMP_GWS', 'active') RETURNING products.product_id
+        """))
+    conn.execute(sa.text("""
+INSERT INTO fixed_inputs (name, value, product_id) VALUES ('l3_core_service_type', 'LHCONE', (SELECT products.product_id FROM products WHERE products.name IN ('LHCOne'))), ('l3_core_service_type', 'IMPORTED COPERNICUS', (SELECT products.product_id FROM products WHERE products.name IN ('Imported Copernicus'))), ('l3_core_service_type', 'COPERNICUS', (SELECT products.product_id FROM products WHERE products.name IN ('Copernicus'))), ('l3_core_service_type', 'IMPORTED LHCONE', (SELECT products.product_id FROM products WHERE products.name IN ('Imported LHCOne'))), ('l3_core_service_type', 'IMPORTED GWS', (SELECT products.product_id FROM products WHERE products.name IN ('Imported GWS'))), ('l3_core_service_type', 'GWS', (SELECT products.product_id FROM products WHERE products.name IN ('GWS')))
+    """))
+    conn.execute(sa.text("""
+INSERT INTO product_blocks (name, description, tag, status) VALUES ('L3CoreServiceBlock', 'A Core network service on Layer 3', 'L3CORE', 'active') RETURNING product_blocks.product_block_id
+    """))
+    conn.execute(sa.text("""
+INSERT INTO product_blocks (name, description, tag, status) VALUES ('AccessPort', 'An Access Port where a Layer 3 service terminates', 'AP', 'active') RETURNING product_blocks.product_block_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 ('Imported IAS')), (SELECT product_blocks.product_block_id FROM product_blocks WHERE product_blocks.name IN ('L3CoreServiceBlock'))), ((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 ('L3CoreServiceBlock'))), ((SELECT products.product_id FROM products WHERE products.name IN ('LHCOne')), (SELECT product_blocks.product_block_id FROM product_blocks WHERE product_blocks.name IN ('L3CoreServiceBlock'))), ((SELECT products.product_id FROM products WHERE products.name IN ('Imported LHCOne')), (SELECT product_blocks.product_block_id FROM product_blocks WHERE product_blocks.name IN ('L3CoreServiceBlock'))), ((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 ('L3CoreServiceBlock'))), ((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 ('L3CoreServiceBlock'))), ((SELECT products.product_id FROM products WHERE products.name IN ('Copernicus')), (SELECT product_blocks.product_block_id FROM product_blocks WHERE product_blocks.name IN ('L3CoreServiceBlock'))), ((SELECT products.product_id FROM products WHERE products.name IN ('Imported Copernicus')), (SELECT product_blocks.product_block_id FROM product_blocks WHERE product_blocks.name IN ('L3CoreServiceBlock'))),((SELECT products.product_id FROM products WHERE products.name IN ('GWS')), (SELECT product_blocks.product_block_id FROM product_blocks WHERE product_blocks.name IN ('L3CoreServiceBlock'))), ((SELECT products.product_id FROM products WHERE products.name IN ('Imported GWS')), (SELECT product_blocks.product_block_id FROM product_blocks WHERE product_blocks.name IN ('L3CoreServiceBlock')))
+    """))
+    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 ('AccessPort')), (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 ('L3CoreServiceBlock')), (SELECT product_blocks.product_block_id FROM product_blocks WHERE product_blocks.name IN ('AccessPort')))
+    """))
+    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 ('AccessPort')), (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('ap_type')))
+    """))
+    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"])
+    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 ('AccessPort')) 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 ('AccessPort'))) 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_product_blocks WHERE product_product_blocks.product_id IN (SELECT products.product_id FROM products WHERE products.name IN ('Imported IAS', 'GÉANT IP', 'LHCOne', 'Imported LHCOne', 'IAS', 'Imported GÉANT IP', 'Copernicus', 'Imported Copernicus', 'GWS', 'Imported GWS')) AND product_product_blocks.product_block_id IN (SELECT product_blocks.product_block_id FROM product_blocks WHERE product_blocks.name IN ('L3CoreServiceBlock'))
+    """))
+    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 ('AccessPort')) 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 ('L3CoreServiceBlock')) AND product_block_relations.depends_on_id IN (SELECT product_blocks.product_block_id FROM product_blocks WHERE product_blocks.name IN ('AccessPort'))
+    """))
+    conn.execute(sa.text("""
+DELETE FROM fixed_inputs WHERE fixed_inputs.product_id IN (SELECT products.product_id FROM products WHERE products.name IN ('LHCOne', 'Imported Copernicus', 'Copernicus', 'Imported LHCOne', 'GWS', 'Imported GWS')) AND fixed_inputs.name = '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 ('AccessPort', 'L3CoreServiceBlock'))
+    """))
+    conn.execute(sa.text("""
+DELETE FROM product_blocks WHERE product_blocks.name IN ('AccessPort', 'L3CoreServiceBlock')
+    """))
+    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 ('LHCOne', 'Imported Copernicus', 'Copernicus', 'Imported LHCOne', 'GWS', 'Imported GWS'))))
+    """))
+    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 ('LHCOne', 'Imported Copernicus', 'Copernicus', 'Imported LHCOne', 'GWS', 'Imported GWS')))
+    """))
+    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 ('LHCOne', 'Imported Copernicus', 'Copernicus', 'Imported LHCOne', 'GWS', 'Imported GWS')))
+    """))
+    conn.execute(sa.text("""
+DELETE FROM subscriptions WHERE subscriptions.product_id IN (SELECT products.product_id FROM products WHERE products.name IN ('LHCOne', 'Imported Copernicus', 'Copernicus', 'Imported LHCOne', 'GWS', 'Imported GWS'))
+    """))
+    conn.execute(sa.text("""
+DELETE FROM products WHERE products.name IN ('LHCOne', 'Imported Copernicus', 'Copernicus', 'Imported LHCOne', 'GWS', 'Imported GWS')
+    """))
+    conn.execute(sa.text("""
+UPDATE products SET product_type='NRENL3CoreService' WHERE product_type='L3CoreService'
+    """))
+    conn.execute(sa.text("""
+UPDATE products SET product_type='ImportedNRENL3CoreService' WHERE product_type='ImportedL3CoreService'
+        """))
+    conn.execute(sa.text("""
+UPDATE fixed_inputs SET name='nren_l3_core_service_type' WHERE fixed_inputs.product_id = (SELECT products.product_id FROM products WHERE products.name IN ('GÉANT IP')) AND fixed_inputs.name = 'l3_core_service_type'
+    """))
+    conn.execute(sa.text("""
+UPDATE fixed_inputs SET name='nren_l3_core_service_type' WHERE fixed_inputs.product_id = (SELECT products.product_id FROM products WHERE products.name IN ('Imported GÉANT IP')) AND fixed_inputs.name = 'l3_core_service_type'
+    """))
+    conn.execute(sa.text("""
+UPDATE fixed_inputs SET name='nren_l3_core_service_type' WHERE fixed_inputs.product_id = (SELECT products.product_id FROM products WHERE products.name IN ('IAS')) AND fixed_inputs.name = 'l3_core_service_type'
+    """))
+    conn.execute(sa.text("""
+UPDATE fixed_inputs SET name='nren_l3_core_service_type' WHERE fixed_inputs.product_id = (SELECT products.product_id FROM products WHERE products.name IN ('Imported IAS')) AND fixed_inputs.name = 'l3_core_service_type'
+    """))
+    for workflow in old_workflows:
+        create_workflow(conn, workflow)
diff --git a/gso/migrations/versions/2024-11-27_543afff041f9_add_prefix_limit_to_bgpsession.py b/gso/migrations/versions/2024-11-27_543afff041f9_add_prefix_limit_to_bgpsession.py
new file mode 100644
index 0000000000000000000000000000000000000000..a239b880512a0f68dc2f52c399533abf8b9da470
--- /dev/null
+++ b/gso/migrations/versions/2024-11-27_543afff041f9_add_prefix_limit_to_bgpsession.py
@@ -0,0 +1,41 @@
+"""Add prefix limit to BGPSession.
+
+Revision ID: 543afff041f9
+Revises: 2746f861a765
+Create Date: 2024-11-27 10:34:29.855749
+
+"""
+import sqlalchemy as sa
+from alembic import op
+
+# revision identifiers, used by Alembic.
+revision = '543afff041f9'
+down_revision = '2746f861a765'
+branch_labels = None
+depends_on = None
+
+
+def upgrade() -> None:
+    conn = op.get_bind()
+    conn.execute(sa.text("""
+INSERT INTO resource_types (resource_type, description) VALUES ('prefix_limit', 'Prefix limit for a BGP session') RETURNING resource_types.resource_type_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 ('BGPSession')), (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('prefix_limit')))
+    """))
+
+
+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 ('BGPSession')) AND product_block_resource_types.resource_type_id = (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('prefix_limit'))
+    """))
+    conn.execute(sa.text("""
+DELETE FROM subscription_instance_values USING product_block_resource_types WHERE subscription_instance_values.subscription_instance_id IN (SELECT subscription_instances.subscription_instance_id FROM subscription_instances WHERE subscription_instances.subscription_instance_id IN (SELECT product_blocks.product_block_id FROM product_blocks WHERE product_blocks.name IN ('BGPSession'))) AND product_block_resource_types.resource_type_id = (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('prefix_limit'))
+    """))
+    conn.execute(sa.text("""
+DELETE FROM subscription_instance_values WHERE subscription_instance_values.resource_type_id IN (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('prefix_limit'))
+    """))
+    conn.execute(sa.text("""
+DELETE FROM resource_types WHERE resource_types.resource_type IN ('prefix_limit')
+    """))
diff --git a/gso/products/__init__.py b/gso/products/__init__.py
index 457a86ed3601e66ffb728f5ac00fdc3937907612..4b472c9f8a4f186bca00aea8bf70507981c0b7da 100644
--- a/gso/products/__init__.py
+++ b/gso/products/__init__.py
@@ -10,8 +10,9 @@ 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.l3_core_service import ImportedL3CoreService, L3CoreService
 from gso.products.product_types.lan_switch_interconnect import ImportedLanSwitchInterconnect, LanSwitchInterconnect
-from gso.products.product_types.nren_l3_core_service import ImportedNRENL3CoreService, NRENL3CoreService
+from gso.products.product_types.layer_2_circuit import ImportedLayer2Circuit, Layer2Circuit, Layer2CircuitServiceType
 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
@@ -47,6 +48,16 @@ class ProductName(strEnum):
     IMPORTED_GEANT_IP = "Imported GÉANT IP"
     IAS = "IAS"
     IMPORTED_IAS = "Imported IAS"
+    GWS = "GWS"
+    IMPORTED_GWS = "Imported GWS"
+    LHCONE = "LHCOne"
+    IMPORTED_LHCONE = "Imported LHCOne"
+    COPERNICUS = "Copernicus"
+    IMPORTED_COPERNICUS = "Imported Copernicus"
+    GEANT_PLUS = Layer2CircuitServiceType.GEANT_PLUS
+    IMPORTED_GEANT_PLUS = Layer2CircuitServiceType.IMPORTED_GEANT_PLUS
+    EXPRESSROUTE = Layer2CircuitServiceType.EXPRESSROUTE
+    IMPORTED_EXPRESSROUTE = Layer2CircuitServiceType.IMPORTED_EXPRESSROUTE
 
 
 class ProductType(strEnum):
@@ -71,10 +82,20 @@ class ProductType(strEnum):
     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__
+    GEANT_IP = L3CoreService.__name__
+    IMPORTED_GEANT_IP = ImportedL3CoreService.__name__
+    IAS = L3CoreService.__name__
+    IMPORTED_IAS = ImportedL3CoreService.__name__
+    GWS = L3CoreService.__name__
+    IMPORTED_GWS = ImportedL3CoreService.__name__
+    LHCONE = L3CoreService.__name__
+    IMPORTED_LHCONE = ImportedL3CoreService.__name__
+    COPERNICUS = L3CoreService.__name__
+    IMPORTED_COPERNICUS = ImportedL3CoreService.__name__
+    GEANT_PLUS = Layer2Circuit.__name__
+    IMPORTED_GEANT_PLUS = ImportedLayer2Circuit.__name__
+    EXPRESSROUTE = Layer2Circuit.__name__
+    IMPORTED_EXPRESSROUTE = ImportedLayer2Circuit.__name__
 
 
 SUBSCRIPTION_MODEL_REGISTRY.update(
@@ -98,9 +119,21 @@ SUBSCRIPTION_MODEL_REGISTRY.update(
         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,
+        ProductName.GEANT_IP.value: L3CoreService,
+        ProductName.IMPORTED_GEANT_IP.value: ImportedL3CoreService,
+        ProductName.IAS.value: L3CoreService,
+        ProductName.IMPORTED_IAS.value: ImportedL3CoreService,
+        ProductName.GWS.value: L3CoreService,
+        ProductName.IMPORTED_GWS.value: ImportedL3CoreService,
+        ProductName.LHCONE.value: L3CoreService,
+        ProductName.IMPORTED_LHCONE.value: ImportedL3CoreService,
+        ProductName.COPERNICUS.value: L3CoreService,
+        ProductName.IMPORTED_COPERNICUS.value: ImportedL3CoreService,
+        ProductName.GEANT_PLUS.value: Layer2Circuit,
+        ProductName.IMPORTED_GEANT_PLUS.value: ImportedLayer2Circuit,
+        ProductName.EXPRESSROUTE.value: Layer2Circuit,
+        ProductName.IMPORTED_EXPRESSROUTE.value: ImportedLayer2Circuit,
     },
 )
+
+__all__ = ["ProductName", "ProductType"]
diff --git a/gso/products/product_blocks/bgp_session.py b/gso/products/product_blocks/bgp_session.py
index 65c5f0a0908e6c6750dac9abae25d23290d1367c..2a57eb69b1ea5d200f2ac96ad6f8169f203093ef 100644
--- a/gso/products/product_blocks/bgp_session.py
+++ b/gso/products/product_blocks/bgp_session.py
@@ -3,7 +3,7 @@
 import strawberry
 from orchestrator.domain.base import ProductBlockModel
 from orchestrator.types import SubscriptionLifecycle
-from pydantic import Field
+from pydantic import Field, NonNegativeInt
 from pydantic_forms.types import strEnum
 
 from gso.utils.types.ip_address import IPAddress
@@ -13,19 +13,24 @@ from gso.utils.types.ip_address import IPAddress
 class IPFamily(strEnum):
     """Possible IP families of a :term:`BGP` peering."""
 
-    V4UNICAST = "v4unicast"
-    V6UNICAST = "v6unicast"
-    V4MULTICAST = "v4multicast"
-    V6MULTICAST = "v6multicast"
+    V4UNICAST = "ipv4"
+    V6UNICAST = "ipv6"
+    V4MULTICAST = "mcast-ipv4"
+    V6MULTICAST = "mcast-ipv6"
+
+
+@strawberry.enum
+class IPTypes(strEnum):
+    """Possible IP types of a :term:`BGP` peering."""
+
+    IPV4 = "ipv4"
+    IPV6 = "ipv6"
 
 
 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
@@ -34,23 +39,26 @@ class BGPSessionInactive(ProductBlockModel, lifecycle=[SubscriptionLifecycle.INI
     is_multi_hop: bool = False
     is_passive: bool = False
     rtbh_enabled: bool = False
+    bfd_enabled: bool = False
+    ip_type: IPTypes | None = None
+    prefix_limit: NonNegativeInt | None = None
 
 
 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
+    authentication_key: str | None
     multipath_enabled: bool
     send_default_route: bool
     is_multi_hop: bool
     is_passive: bool
     rtbh_enabled: bool
+    bfd_enabled: bool
+    ip_type: IPTypes
+    prefix_limit: NonNegativeInt | None
 
 
 class BGPSession(BGPSessionProvisioning, lifecycle=[SubscriptionLifecycle.ACTIVE]):
@@ -58,18 +66,12 @@ class BGPSession(BGPSessionProvisioning, lifecycle=[SubscriptionLifecycle.ACTIVE
 
     #: 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
+    authentication_key: str | None
     #: Whether multi-path is enabled.
     multipath_enabled: bool
     #: Whether we send a last resort route.
@@ -80,3 +82,9 @@ class BGPSession(BGPSessionProvisioning, lifecycle=[SubscriptionLifecycle.ACTIVE
     is_passive: bool
     #: Whether Remote Triggered Blackhole is enabled
     rtbh_enabled: bool
+    #: Settings for :term:`BFD`.
+    bfd_enabled: bool
+    #: The IP type of the session.
+    ip_type: IPTypes
+    #: A prefix limit, if required
+    prefix_limit: NonNegativeInt | None
diff --git a/gso/products/product_blocks/l3_core_service.py b/gso/products/product_blocks/l3_core_service.py
new file mode 100644
index 0000000000000000000000000000000000000000..add0724bee631819c1049b37be3b8132e91e8b8b
--- /dev/null
+++ b/gso/products/product_blocks/l3_core_service.py
@@ -0,0 +1,56 @@
+"""Product blocks for 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 AccessPortInactive(ProductBlockModel, lifecycle=[SubscriptionLifecycle.INITIAL], product_block_name="AccessPort"):
+    """An access port for an R&E service that is inactive."""
+
+    ap_type: APType | None = None
+    sbp: ServiceBindingPortInactive
+
+
+class AccessPortProvisioning(AccessPortInactive, lifecycle=[SubscriptionLifecycle.PROVISIONING]):
+    """An access port for an R&E service that is being provisioned."""
+
+    ap_type: APType
+    sbp: ServiceBindingPortProvisioning
+
+
+class AccessPort(AccessPortProvisioning, lifecycle=[SubscriptionLifecycle.ACTIVE]):
+    """An access port for an R&E service."""
+
+    #: The type of Access Port
+    ap_type: APType
+    #: The corresponding :term:`SBP` of this Access Port.
+    sbp: ServiceBindingPort
+
+
+class L3CoreServiceBlockInactive(
+    ProductBlockModel, lifecycle=[SubscriptionLifecycle.INITIAL], product_block_name="L3CoreServiceBlock"
+):
+    """An inactive L3 Core service subscription. See :class:`L3CoreServiceBlock`."""
+
+    ap_list: list[AccessPortInactive] = Field(default_factory=list)
+
+
+class L3CoreServiceBlockProvisioning(L3CoreServiceBlockInactive, lifecycle=[SubscriptionLifecycle.PROVISIONING]):
+    """A provisioning L3 Core Service subscription. See :class:`L3CoreServiceBlock`."""
+
+    ap_list: list[AccessPortProvisioning]  # type: ignore[assignment]
+
+
+class L3CoreServiceBlock(L3CoreServiceBlockProvisioning, lifecycle=[SubscriptionLifecycle.ACTIVE]):
+    """An active L3 Core Service subscription block."""
+
+    #: The list of Access Points where this service is present.
+    ap_list: list[AccessPort]  # type: ignore[assignment]
diff --git a/gso/products/product_blocks/layer_2_circuit.py b/gso/products/product_blocks/layer_2_circuit.py
new file mode 100644
index 0000000000000000000000000000000000000000..261caa48669fd604c4182a5bb878a39b24647301
--- /dev/null
+++ b/gso/products/product_blocks/layer_2_circuit.py
@@ -0,0 +1,115 @@
+"""Layer 2 Circuit product block."""
+
+from collections.abc import Sequence
+from typing import Annotated, TypeVar
+
+from annotated_types import Len
+from orchestrator.domain.base import ProductBlockModel
+from orchestrator.types import SubscriptionLifecycle
+from pydantic import AfterValidator
+from pydantic_forms.types import strEnum
+from pydantic_forms.validators import validate_unique_list
+from typing_extensions import Doc
+
+from gso.products.product_blocks.service_binding_port import (
+    ServiceBindingPort,
+    ServiceBindingPortInactive,
+    ServiceBindingPortProvisioning,
+)
+from gso.utils.types.interfaces import BandwidthString
+from gso.utils.types.virtual_identifiers import VC_ID, VLAN_ID
+
+
+class Layer2CircuitSideBlockInactive(
+    ProductBlockModel,
+    lifecycle=[SubscriptionLifecycle.INITIAL],
+    product_block_name="Layer2CircuitSideBlock",
+):
+    """One inactive side of a Layer 2 Circuit."""
+
+    sbp: ServiceBindingPortInactive
+
+
+class Layer2CircuitSideBlockProvisioning(
+    Layer2CircuitSideBlockInactive, lifecycle=[SubscriptionLifecycle.PROVISIONING]
+):
+    """One provisioning side of a Layer 2 Circuit."""
+
+    sbp: ServiceBindingPortProvisioning
+
+
+class Layer2CircuitSideBlock(Layer2CircuitSideBlockProvisioning, lifecycle=[SubscriptionLifecycle.ACTIVE]):
+    """One side of a Layer 2 Circuit."""
+
+    sbp: ServiceBindingPort
+
+
+Layer2CircuitSideBlockType = TypeVar(
+    "Layer2CircuitSideBlockType",
+    "Layer2CircuitSideBlockInactive",
+    "Layer2CircuitSideBlockProvisioning",
+    "Layer2CircuitSideBlock",
+)
+
+Layer2CircuitSides = Annotated[
+    Sequence[Layer2CircuitSideBlockType],
+    AfterValidator(validate_unique_list),
+    Len(min_length=2, max_length=2),
+    Doc("A list of two Layer 2 Circuit sides."),
+]
+
+
+class Layer2CircuitType(strEnum):
+    """The two types of Layer 2 Circuit."""
+
+    TAGGED = "TAGGED"
+    UNTAGGED = "UNTAGGED"
+
+
+class Layer2CircuitBlockInactive(
+    ProductBlockModel, lifecycle=[SubscriptionLifecycle.INITIAL], product_block_name="Layer2CircuitBlock"
+):
+    """An inactive Layer 2 Circuit, see :class:`Layer2CircuitBlock`."""
+
+    layer_2_circuit_sides: Layer2CircuitSides[Layer2CircuitSideBlockInactive]
+    virtual_circuit_id: VC_ID | None = None
+    layer_2_circuit_type: Layer2CircuitType | None = None
+    vlan_range_lower_bound: VLAN_ID | None = None
+    vlan_range_upper_bound: VLAN_ID | None = None
+    policer_enabled: bool | None = None
+    policer_burst_rate: BandwidthString | None = None
+    bandwidth: BandwidthString | None = None
+
+
+class Layer2CircuitBlockProvisioning(Layer2CircuitBlockInactive, lifecycle=[SubscriptionLifecycle.PROVISIONING]):
+    """A provisioning Layer 2 Circuit, see :class:`Layer2CircuitBlock`."""
+
+    layer_2_circuit_sides: Layer2CircuitSides[Layer2CircuitSideBlockProvisioning]
+    virtual_circuit_id: VC_ID
+    layer_2_circuit_type: Layer2CircuitType
+    vlan_range_lower_bound: VLAN_ID | None
+    vlan_range_upper_bound: VLAN_ID | None
+    policer_enabled: bool
+    policer_burst_rate: BandwidthString | None
+    bandwidth: BandwidthString | None
+
+
+class Layer2CircuitBlock(Layer2CircuitBlockProvisioning, lifecycle=[SubscriptionLifecycle.ACTIVE]):
+    """An active Layer 2 Circuit."""
+
+    #: The two sides that the Layer 2 Circuit is connected to.
+    layer_2_circuit_sides: Layer2CircuitSides[Layer2CircuitSideBlock]
+    #: Virtual Circuit ID of this Layer 2 Circuit.
+    virtual_circuit_id: VC_ID
+    #: The type of circuit, can be tagged or untagged.
+    layer_2_circuit_type: Layer2CircuitType
+    #: If tagged, the lower and upper bounds will set the :term:`VLAN` range.
+    vlan_range_lower_bound: VLAN_ID | None
+    #: Lower and Upper bounds are including.
+    vlan_range_upper_bound: VLAN_ID | None
+    #: Whether this Layer 2 Circuit is policed.
+    policer_enabled: bool
+    #: If policed, the burst rate of the policer
+    policer_burst_rate: BandwidthString | None
+    #: If policed, the bandwidth of the policer is stored.
+    bandwidth: BandwidthString | None
diff --git a/gso/products/product_blocks/nren_l3_core_service.py b/gso/products/product_blocks/nren_l3_core_service.py
deleted file mode 100644
index 37eb7616511dc34d887db0ad324dee8b59ec4f96..0000000000000000000000000000000000000000
--- a/gso/products/product_blocks/nren_l3_core_service.py
+++ /dev/null
@@ -1,60 +0,0 @@
-"""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/service_binding_port.py b/gso/products/product_blocks/service_binding_port.py
index 540983821f64b86e5e3c11a9118ea8b867bff61d..2c9cd2ce37f5029c2393bccbccb786cc0b84a6b1 100644
--- a/gso/products/product_blocks/service_binding_port.py
+++ b/gso/products/product_blocks/service_binding_port.py
@@ -3,18 +3,52 @@
 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.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
+from gso.utils.types.virtual_identifiers import VLAN_ID
+
+
+class BFDSettingsInactive(
+    ProductBlockModel, lifecycle=[SubscriptionLifecycle.INITIAL], product_block_name="BFDSettings"
+):
+    """Settings for :term:`BFD`, see :class:`BFDSettings`."""
+
+    bfd_enabled: bool = False
+    bfd_interval_rx: int | None = None
+    bfd_interval_tx: int | None = None
+    bfd_multiplier: int | None = None
+
+
+class BFDSettingsProvisioning(BFDSettingsInactive, lifecycle=[SubscriptionLifecycle.PROVISIONING]):
+    """Settings for :term:`BFD`, see :class:`BFDSettings`."""
+
+    bfd_enabled: bool
+    bfd_interval_rx: int | None = None
+    bfd_interval_tx: int | None = None
+    bfd_multiplier: int | None
+
+
+class BFDSettings(BFDSettingsProvisioning, lifecycle=[SubscriptionLifecycle.ACTIVE]):
+    """A set of settings for :term:`BFD`."""
 
-VLAN_ID = Annotated[int, Field(gt=0, lt=4096)]
+    #: Whether :term:`BFD` is enabled.
+    bfd_enabled: bool
+    #: The :term:`BFD` interval RX, if enabled.
+    bfd_interval_rx: int | None
+    #: The :term:`BFD` interval TX, if enabled.
+    bfd_interval_tx: int | None
+    #: The :term:`BFD` multiplier, if enabled.
+    bfd_multiplier: int | None
 
 
 class ServiceBindingPortInactive(
@@ -33,6 +67,8 @@ class ServiceBindingPortInactive(
     geant_sid: str | None = None
     bgp_session_list: list[BGPSessionInactive] = Field(default_factory=list)
     edge_port: EdgePortBlockInactive | None = None
+    v4_bfd_settings: BFDSettingsInactive
+    v6_bfd_settings: BFDSettingsInactive
 
 
 class ServiceBindingPortProvisioning(ServiceBindingPortInactive, lifecycle=[SubscriptionLifecycle.PROVISIONING]):
@@ -49,6 +85,8 @@ class ServiceBindingPortProvisioning(ServiceBindingPortInactive, lifecycle=[Subs
     geant_sid: str
     bgp_session_list: list[BGPSessionProvisioning]  # type: ignore[assignment]
     edge_port: EdgePortBlockProvisioning
+    v4_bfd_settings: BFDSettingsProvisioning
+    v6_bfd_settings: BFDSettingsProvisioning
 
 
 class ServiceBindingPort(ServiceBindingPortProvisioning, lifecycle=[SubscriptionLifecycle.ACTIVE]):
@@ -76,3 +114,7 @@ class ServiceBindingPort(ServiceBindingPortProvisioning, lifecycle=[Subscription
     bgp_session_list: list[BGPSession]  # type: ignore[assignment]
     #: The Edge Port on which this :term:`SBP` resides.
     edge_port: EdgePortBlock
+    #: :term:`BFD` settings for IPv4
+    v4_bfd_settings: BFDSettings
+    #: :term:`BFD` settings for IPv6
+    v6_bfd_settings: BFDSettings
diff --git a/gso/products/product_types/l3_core_service.py b/gso/products/product_types/l3_core_service.py
new file mode 100644
index 0000000000000000000000000000000000000000..8d4c96d5110965ec0fb412551f316666478ee122
--- /dev/null
+++ b/gso/products/product_types/l3_core_service.py
@@ -0,0 +1,66 @@
+"""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.l3_core_service import (
+    L3CoreServiceBlock,
+    L3CoreServiceBlockInactive,
+    L3CoreServiceBlockProvisioning,
+)
+
+
+class L3CoreServiceType(strEnum):
+    """Available types of 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"
+    GWS = "GWS"
+    IMPORTED_GWS = "IMPORTED GWS"
+    LHCONE = "LHCONE"
+    IMPORTED_LHCONE = "IMPORTED LHCONE"
+    COPERNICUS = "COPERNICUS"
+    IMPORTED_COPERNICUS = "IMPORTED COPERNICUS"
+
+
+class L3CoreServiceInactive(SubscriptionModel, is_base=True):
+    """An inactive L3 Core Service subscription."""
+
+    l3_core_service_type: L3CoreServiceType
+    l3_core_service: L3CoreServiceBlockInactive
+
+
+class L3CoreServiceProvisioning(L3CoreServiceInactive, lifecycle=[SubscriptionLifecycle.PROVISIONING]):
+    """A L3 Core Service subscription that's being provisioned."""
+
+    l3_core_service_type: L3CoreServiceType
+    l3_core_service: L3CoreServiceBlockProvisioning
+
+
+class L3CoreService(L3CoreServiceProvisioning, lifecycle=[SubscriptionLifecycle.ACTIVE]):
+    """An active L3 Core Service subscription."""
+
+    l3_core_service_type: L3CoreServiceType
+    l3_core_service: L3CoreServiceBlock
+
+
+class ImportedL3CoreServiceInactive(SubscriptionModel, is_base=True):
+    """An imported, inactive L3 Core Service subscription."""
+
+    l3_core_service_type: L3CoreServiceType
+    l3_core_service: L3CoreServiceBlockInactive
+
+
+class ImportedL3CoreService(
+    ImportedL3CoreServiceInactive, lifecycle=[SubscriptionLifecycle.PROVISIONING, SubscriptionLifecycle.ACTIVE]
+):
+    """An imported L3 Core Service subscription."""
+
+    l3_core_service_type: L3CoreServiceType
+    l3_core_service: L3CoreServiceBlock
diff --git a/gso/products/product_types/layer_2_circuit.py b/gso/products/product_types/layer_2_circuit.py
new file mode 100644
index 0000000000000000000000000000000000000000..9839a8853013901ee55151f0dc4210a4eb86c8ce
--- /dev/null
+++ b/gso/products/product_types/layer_2_circuit.py
@@ -0,0 +1,57 @@
+"""Product type for a Layer 2 circuit."""
+
+from orchestrator.domain.base import SubscriptionModel
+from orchestrator.types import SubscriptionLifecycle
+from pydantic_forms.types import strEnum
+
+from gso.products.product_blocks.layer_2_circuit import (
+    Layer2CircuitBlock,
+    Layer2CircuitBlockInactive,
+    Layer2CircuitBlockProvisioning,
+)
+
+
+class Layer2CircuitServiceType(strEnum):
+    """Available types of Layer 2 Circuit services."""
+
+    GEANT_PLUS = "GÉANT Plus"
+    IMPORTED_GEANT_PLUS = "Imported GÉANT Plus"
+    EXPRESSROUTE = "Azure ExpressRoute"
+    IMPORTED_EXPRESSROUTE = "Imported Azure ExpressRoute"
+
+
+class Layer2CircuitInactive(SubscriptionModel, is_base=True):
+    """An inactive Layer 2 Circuit."""
+
+    layer_2_circuit_service_type: Layer2CircuitServiceType
+    layer_2_circuit: Layer2CircuitBlockInactive
+
+
+class Layer2CircuitProvisioning(Layer2CircuitInactive, lifecycle=[SubscriptionLifecycle.PROVISIONING]):
+    """A Layer 2 Circuit that is provisioning."""
+
+    layer_2_circuit_service_type: Layer2CircuitServiceType
+    layer_2_circuit: Layer2CircuitBlockProvisioning
+
+
+class Layer2Circuit(Layer2CircuitProvisioning, lifecycle=[SubscriptionLifecycle.ACTIVE]):
+    """An active Layer 2 Circuit."""
+
+    layer_2_circuit_service_type: Layer2CircuitServiceType
+    layer_2_circuit: Layer2CircuitBlock
+
+
+class ImportedLayer2CircuitInactive(SubscriptionModel, is_base=True):
+    """An imported, inactive Layer 2 Circuit."""
+
+    layer_2_circuit_service_type: Layer2CircuitServiceType
+    layer_2_circuit: Layer2CircuitBlockInactive
+
+
+class ImportedLayer2Circuit(
+    ImportedLayer2CircuitInactive, lifecycle=[SubscriptionLifecycle.PROVISIONING, SubscriptionLifecycle.ACTIVE]
+):
+    """An imported Layer 2 Circuit."""
+
+    layer_2_circuit_service_type: Layer2CircuitServiceType
+    layer_2_circuit: Layer2CircuitBlock
diff --git a/gso/products/product_types/nren_l3_core_service.py b/gso/products/product_types/nren_l3_core_service.py
deleted file mode 100644
index 20aa5ab2f766588f466a5100d1d7af69cbb696b0..0000000000000000000000000000000000000000
--- a/gso/products/product_types/nren_l3_core_service.py
+++ /dev/null
@@ -1,60 +0,0 @@
-""":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/services/lso_client.py b/gso/services/lso_client.py
index cae5dd9c28d55d3b166f7250513ad7a5d7946cba..f501ed7023a52d418dedf0c88200ada8096b92f8 100644
--- a/gso/services/lso_client.py
+++ b/gso/services/lso_client.py
@@ -10,11 +10,11 @@ from typing import Any, Literal, TypedDict
 import requests
 from orchestrator import step
 from orchestrator.config.assignee import Assignee
+from orchestrator.forms import FormPage
 from orchestrator.types import State
 from orchestrator.utils.errors import ProcessFailureError
 from orchestrator.workflow import Step, StepList, begin, callback_step, conditional, inputstep
 from pydantic import ConfigDict
-from pydantic_forms.core import FormPage
 from pydantic_forms.types import FormGenerator
 from pydantic_forms.validators import Label, LongText, ReadOnlyField
 
diff --git a/gso/services/subscriptions.py b/gso/services/subscriptions.py
index 1458f000439fa8bcf936a08b864eb117296caf37..b2c2561d8cb4a213b172aea7ed89cc12aed8bd1c 100644
--- a/gso/services/subscriptions.py
+++ b/gso/services/subscriptions.py
@@ -287,3 +287,25 @@ def get_all_active_sites() -> list[dict[str, Any]]:
         }
         for subscription in get_active_site_subscriptions(includes=["subscription_id"])
     ]
+
+
+def is_virtual_circuit_id_available(virtual_circuit_id: str) -> bool:
+    """Check if the given virtual circuit ID is unique in the database.
+
+    This function verifies if the specified virtual circuit ID is not already
+    present in the core database.
+
+    :param virtual_circuit_id: The virtual circuit ID to check.
+    :type virtual_circuit_id: str
+    :return: True if the virtual circuit ID is unique (not found), False if it exists.
+    :rtype: bool
+    """
+    exists = (
+        ResourceTypeTable.query.join(SubscriptionInstanceValueTable)
+        .filter(
+            ResourceTypeTable.resource_type == "virtual_circuit_id",
+            SubscriptionInstanceValueTable.value == virtual_circuit_id,
+        )
+        .scalar()
+    )
+    return exists is None
diff --git a/gso/translations/en-GB.json b/gso/translations/en-GB.json
index 1858d04ece8f39a1bd1f83de8994e6c71633ba9a..a871e3394a98a57c90dccf4db79d206ed47d2027 100644
--- a/gso/translations/en-GB.json
+++ b/gso/translations/en-GB.json
@@ -52,18 +52,18 @@
         "create_site": "Create Site",
         "create_switch": "Create Switch",
         "create_edge_port": "Create Edge Port",
-        "create_nren_l3_core_service": "Create NREN L3 Core Service",
+        "create_l3_core_service": "Create L3 Core Service",
         "create_lan_switch_interconnect": "Create LAN Switch Interconnect",
         "deploy_twamp": "Deploy TWAMP",
         "migrate_iptrunk": "Migrate IP Trunk",
-        "migrate_nren_l3_core_service": "Migrate NREN L3 Core Service",
+        "migrate_l3_core_service": "Migrate 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",
+        "modify_l3_core_service": "Modify L3 Core Service",
         "terminate_iptrunk": "Terminate IP Trunk",
         "terminate_router": "Terminate Router",
         "terminate_site": "Terminate Site",
@@ -79,7 +79,7 @@
         "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",
+        "create_imported_l3_core_service": "NOT FOR HUMANS -- Import existing L3 Core Service",
         "create_imported_switch": "NOT FOR HUMANS -- Import existing Switch",
         "create_imported_lan_switch_interconnect": "NOT FOR HUMANS -- Import existing LAN Switch Interconnect",
         "import_site": "NOT FOR HUMANS -- Finalize import into a Site product",
@@ -89,7 +89,7 @@
         "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",
+        "import_l3_core_service": "NOT FOR HUMANS -- Finalize import into an L3 Core Service",
         "import_switch": "NOT FOR HUMANS -- Finalize import into a Switch",
         "import_lan_switch_interconnect": "NOT FOR HUMANS -- Finalize import into a LAN Switch Interconnect",
         "validate_iptrunk": "Validate IP Trunk configuration",
@@ -103,6 +103,11 @@
         "task_modify_partners": "Modify partner task",
         "task_delete_partners": "Delete partner task",
         "task_clean_old_tasks": "Remove old cleanup tasks",
-        "promote_p_to_pe": "Promote P to PE"
+        "promote_p_to_pe": "Promote P to PE",
+        "create_layer_2_circuit": "Create Layer 2 Circuit",
+        "modify_layer_2_circuit": "Modify Layer 2 Circuit",
+        "terminate_layer_2_circuit": "Terminate Layer 2 Circuit",
+        "create_imported_layer_2_circuit": "NOT FOR HUMANS -- Import existing Layer 2 Circuit",
+        "import_layer_2_circuit": "NOT FOR HUMANS -- Finalize import into a Layer 2 Circuit product"
     }
 }
diff --git a/gso/utils/helpers.py b/gso/utils/helpers.py
index c05517a23beed1b47a5aaf37786af5fe8b8d1eee..5485bf2000e8dbd14fef41243b3966b26c13d11f 100644
--- a/gso/utils/helpers.py
+++ b/gso/utils/helpers.py
@@ -1,9 +1,11 @@
 """Helper methods that are used across :term:`GSO`."""
 
+import random
 import re
 from typing import TYPE_CHECKING
 from uuid import UUID
 
+from orchestrator.types import SubscriptionLifecycle
 from pydantic_forms.types import UUIDstr
 from pydantic_forms.validators import Choice
 
@@ -13,9 +15,11 @@ 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.services.subscriptions import is_virtual_circuit_id_available
 from gso.utils.shared_enums import Vendor
 from gso.utils.types.interfaces import PhysicalPortCapacity
 from gso.utils.types.ip_address import IPv4AddressType
+from gso.utils.types.virtual_identifiers import VC_ID
 
 if TYPE_CHECKING:
     from gso.products.product_blocks.iptrunk import IptrunkInterfaceBlock
@@ -119,10 +123,12 @@ def generate_fqdn(hostname: str, site_name: str, country_code: str) -> str:
     return f"{hostname}.{site_name.lower()}.{country_code.lower()}{oss.IPAM.LO.domain_name}"
 
 
-def generate_inventory_for_active_routers(
+def generate_inventory_for_routers(
     router_role: RouterRole,
     exclude_routers: list[str] | None = None,
     router_vendor: Vendor | None = None,
+    *,
+    include_provisioning_routers: bool = True,
 ) -> dict:
     """Generate an Ansible-compatible inventory for executing playbooks.
 
@@ -131,11 +137,18 @@ def generate_inventory_for_active_routers(
     :param RouterRole router_role: The role of the routers to include in the inventory.
     :param list exclude_routers: List of routers to exclude from the inventory.
     :param Vendor router_vendor: The vendor of the routers to include in the inventory.
+    :param bool include_provisioning_routers: Include routers that are in a ``PROVISIONING`` state.
     :return: A dictionary representing the inventory of active routers.
     :rtype: dict[str, Any]
     """
+    lifecycles = (
+        [SubscriptionLifecycle.PROVISIONING, SubscriptionLifecycle.ACTIVE]
+        if include_provisioning_routers
+        else [SubscriptionLifecycle.ACTIVE]
+    )
     all_routers = [
-        Router.from_subscription(r["subscription_id"]) for r in subscriptions.get_active_router_subscriptions()
+        Router.from_subscription(r["subscription_id"])
+        for r in subscriptions.get_router_subscriptions(lifecycles=lifecycles)
     ]
     exclude_routers = exclude_routers or []
 
@@ -248,3 +261,28 @@ def validate_edge_port_number_of_members_based_on_lacp(*, number_of_members: int
     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)
+
+
+def generate_unique_vc_id(max_attempts: int = 100) -> VC_ID | None:
+    """Generate a unique 8-digit VC_ID starting with '11'.
+
+    This function attempts to generate an 8-digit VC_ID beginning with '11',
+    checking its uniqueness before returning it. A maximum attempt limit is
+    set to prevent infinite loops in case the ID space is saturated.
+
+    :param max_attempts: The maximum number of attempts to generate a unique ID.
+    :type max_attempts: int
+    :return: A unique VC_ID instance if successful, None if no unique ID is found.
+    :rtype: Optional[VC_ID]
+    """
+
+    def create_vc_id() -> str:
+        """Generate an 8-digit VC_ID starting with '11'."""
+        return f"11{random.randint(100000, 999999)}"  # noqa: S311
+
+    for _ in range(max_attempts):
+        vc_id = create_vc_id()
+        if is_virtual_circuit_id_available(vc_id):
+            return VC_ID(vc_id)
+
+    return None
diff --git a/gso/utils/types/interfaces.py b/gso/utils/types/interfaces.py
index 15c91167de822fee116600b71bf0b05dedfd87ae..2251b49e998375a49d9b30c258fda6330f50585c 100644
--- a/gso/utils/types/interfaces.py
+++ b/gso/utils/types/interfaces.py
@@ -100,3 +100,26 @@ class PhysicalPortCapacity(strEnum):
     TEN_GIGABIT_PER_SECOND = "10G"
     HUNDRED_GIGABIT_PER_SECOND = "100G"
     FOUR_HUNDRED_GIGABIT_PER_SECOND = "400G"
+
+
+def bandwidth_string_is_valid(bandwidth_string: str) -> str:
+    """Expect a bandwidth definition to follow the pattern of an int followed by a single letter.
+
+    If this string does not consist of a number followed by a single
+    """
+    msg = f"Expected a network capacity, e.g. 40G or 200M. Got: {bandwidth_string}"
+    if len(bandwidth_string) < 2:  # noqa: PLR2004 not a magic value
+        raise ValueError(msg)
+
+    if bandwidth_string[-1:] not in "K" "M" "G" "T":
+        raise ValueError(msg)
+
+    try:
+        int(bandwidth_string[:-1])  # Try parsing the bandwidth number
+    except ValueError as e:
+        raise ValueError(msg) from e
+
+    return bandwidth_string
+
+
+BandwidthString = Annotated[str, AfterValidator(bandwidth_string_is_valid)]
diff --git a/gso/utils/types/virtual_identifiers.py b/gso/utils/types/virtual_identifiers.py
new file mode 100644
index 0000000000000000000000000000000000000000..2208ca742f298bae64f1217737c673c0fd2131b3
--- /dev/null
+++ b/gso/utils/types/virtual_identifiers.py
@@ -0,0 +1,15 @@
+"""Annotated types for virtual identifiers such as :term:`VLAN` ID or Virtual Circuit ID."""
+
+from typing import Annotated
+
+from pydantic import Field
+from typing_extensions import Doc
+
+VLAN_ID = Annotated[int, Field(gt=0, lt=4096)]
+VC_ID = Annotated[
+    int,
+    Field(gt=0, le=2147483648),
+    Doc(
+        "A Virtual Circuit ID, the upper limit comes from the highest number that a service ID could be in Nokia srOS."
+    ),
+]
diff --git a/gso/utils/workflow_steps.py b/gso/utils/workflow_steps.py
index 9b82b69e1a26a6eb1bd4ff4b7042fec065957915..72e6104a67f611bd921da0e0a159a8cafdc959a2 100644
--- a/gso/utils/workflow_steps.py
+++ b/gso/utils/workflow_steps.py
@@ -5,12 +5,12 @@ from typing import Any
 
 from orchestrator import inputstep, step
 from orchestrator.config.assignee import Assignee
+from orchestrator.forms import FormPage
 from orchestrator.types import State, UUIDstr
 from orchestrator.utils.errors import ProcessFailureError
 from orchestrator.utils.json import json_dumps
 from orchestrator.workflow import StepList, conditional
 from pydantic import ConfigDict
-from pydantic_forms.core import FormPage
 from pydantic_forms.types import FormGenerator
 from pydantic_forms.validators import Label
 
@@ -19,7 +19,7 @@ from gso.products.product_types.iptrunk import Iptrunk
 from gso.services.kentik_client import KentikClient, NewKentikDevice
 from gso.services.lso_client import LSOState, indifferent_lso_interaction
 from gso.settings import load_oss_params
-from gso.utils.helpers import generate_inventory_for_active_routers
+from gso.utils.helpers import generate_inventory_for_routers
 from gso.utils.shared_enums import Vendor
 
 
@@ -51,7 +51,7 @@ def _update_sdp_mesh(
     *,
     dry_run: bool,
 ) -> LSOState:
-    inventory = generate_inventory_for_active_routers(
+    inventory = generate_inventory_for_routers(
         router_role=RouterRole.PE, router_vendor=Vendor.NOKIA, exclude_routers=[subscription["router"]["router_fqdn"]]
     )
 
@@ -89,7 +89,7 @@ def _update_sdp_single_pe(
         "commit_comment": f"GSO_PROCESS_ID: {process_id} - TT_NUMBER: {tt_number} - "
         f"Update the SDP mesh for L2circuits(epipes) config on PE NOKIA routers",
         "verb": "update_sdp_mesh",
-        "pe_router_list": generate_inventory_for_active_routers(
+        "pe_router_list": generate_inventory_for_routers(
             router_role=RouterRole.PE,
             exclude_routers=[subscription["router"]["router_fqdn"]],
         )["all"]["hosts"],
@@ -122,7 +122,7 @@ def _add_pe_mesh_to_pe(
         "commit_comment": f"GSO_PROCESS_ID: {process_id} - TT_NUMBER: {tt_number} - "
         f"Add list of PE routers into iGEANT/iGEANT6 groups of the PE router",
         "verb": "add_pe_mesh_to_pe",
-        "pe_router_list": generate_inventory_for_active_routers(
+        "pe_router_list": generate_inventory_for_routers(
             router_role=RouterRole.PE, exclude_routers=[subscription["router"]["router_fqdn"]]
         )["all"]["hosts"],
     }
@@ -148,7 +148,7 @@ def _add_pe_to_pe_mesh(
     *,
     dry_run: bool,
 ) -> LSOState:
-    inventory = generate_inventory_for_active_routers(
+    inventory = generate_inventory_for_routers(
         router_role=RouterRole.PE, exclude_routers=[subscription["router"]["router_fqdn"]]
     )
     extra_vars = {
@@ -178,7 +178,7 @@ def _add_all_p_to_pe(
         "subscription": subscription,
         "commit_comment": f"GSO_PROCESS_ID: {process_id} - TT_NUMBER: {tt_number} - Add all P-routers to this new PE",
         "verb": "add_all_p_to_pe",
-        "p_router_list": generate_inventory_for_active_routers(
+        "p_router_list": generate_inventory_for_routers(
             router_role=RouterRole.P, exclude_routers=[subscription["router"]["router_fqdn"]]
         )["all"]["hosts"],
     }
@@ -204,7 +204,7 @@ def _add_pe_to_all_p(
     *,
     dry_run: bool,
 ) -> LSOState:
-    inventory = generate_inventory_for_active_routers(
+    inventory = generate_inventory_for_routers(
         router_role=RouterRole.P, exclude_routers=[subscription["router"]["router_fqdn"]]
     )
     extra_vars = {
@@ -417,11 +417,11 @@ def stop_moodi() -> StepList:
     host = load_oss_params().MOODI.host
 
     @step("Stop Moodi")
-    def _stop_moodi() -> LSOState:
+    def _stop_moodi(subscription: dict[str, Any]) -> LSOState:
         return {
             "playbook_name": "moodi_telemetry/playbooks/stop_moodi.yaml",
             "inventory": {"all": {"hosts": {host: None}}},
-            "extra_vars": None,
+            "extra_vars": {"subscription": subscription},
         }
 
     return _is_moodi_enabled(indifferent_lso_interaction(_stop_moodi))
diff --git a/gso/workflows/__init__.py b/gso/workflows/__init__.py
index 362f96ab161f8d15847185ebb8355a6df1a321e8..c5c3e1ef99dbf2ed77dc48620a4f3b0f2c8665ca 100644
--- a/gso/workflows/__init__.py
+++ b/gso/workflows/__init__.py
@@ -116,11 +116,16 @@ LazyWorkflowInstance("gso.workflows.edge_port.validate_edge_port", "validate_edg
 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")
+#  L3 Core Service workflows
+LazyWorkflowInstance("gso.workflows.l3_core_service.create_l3_core_service", "create_l3_core_service")
+LazyWorkflowInstance("gso.workflows.l3_core_service.modify_l3_core_service", "modify_l3_core_service")
+LazyWorkflowInstance("gso.workflows.l3_core_service.create_imported_l3_core_service", "create_imported_l3_core_service")
+LazyWorkflowInstance("gso.workflows.l3_core_service.import_l3_core_service", "import_l3_core_service")
+LazyWorkflowInstance("gso.workflows.l3_core_service.migrate_l3_core_service", "migrate_l3_core_service")
+
+# Layer 2 Circuit workflows
+LazyWorkflowInstance("gso.workflows.l2_circuit.create_layer_2_circuit", "create_layer_2_circuit")
+LazyWorkflowInstance("gso.workflows.l2_circuit.modify_layer_2_circuit", "modify_layer_2_circuit")
+LazyWorkflowInstance("gso.workflows.l2_circuit.terminate_layer_2_circuit", "terminate_layer_2_circuit")
+LazyWorkflowInstance("gso.workflows.l2_circuit.create_imported_layer_2_circuit", "create_imported_layer_2_circuit")
+LazyWorkflowInstance("gso.workflows.l2_circuit.import_layer_2_circuit", "import_layer_2_circuit")
diff --git a/gso/workflows/edge_port/create_imported_edge_port.py b/gso/workflows/edge_port/create_imported_edge_port.py
index b932175f072c4b03d414777ca9896797e9fdf81f..5aa24a28dbd02652adb9f1366e3837e9d296adf1 100644
--- a/gso/workflows/edge_port/create_imported_edge_port.py
+++ b/gso/workflows/edge_port/create_imported_edge_port.py
@@ -15,7 +15,7 @@ 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.edge_port import 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
@@ -30,17 +30,14 @@ def create_subscription(partner: str) -> State:
     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,
-    }
+    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")
+        model_config = ConfigDict(title="Import Edge Port")
 
         node: active_pe_router_selector()  # type: ignore[valid-type]
         partner: str
@@ -63,7 +60,7 @@ def initial_input_form_generator() -> FormGenerator:
 
 @step("Initialize subscription")
 def initialize_subscription(
-    subscription: EdgePortInactive,
+    subscription: ImportedEdgePortInactive,
     node: UUIDstr,
     service_type: EdgePortType,
     speed: PhysicalPortCapacity,
diff --git a/gso/workflows/l2_circuit/__init__.py b/gso/workflows/l2_circuit/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..53538548aafde0121979e0ede455e545ea13a397
--- /dev/null
+++ b/gso/workflows/l2_circuit/__init__.py
@@ -0,0 +1 @@
+"""Workflows for Layer 2 Circuits."""
diff --git a/gso/workflows/l2_circuit/create_imported_layer_2_circuit.py b/gso/workflows/l2_circuit/create_imported_layer_2_circuit.py
new file mode 100644
index 0000000000000000000000000000000000000000..1a94e4ef2b59e9f986d8ed182ea95c393f367a3a
--- /dev/null
+++ b/gso/workflows/l2_circuit/create_imported_layer_2_circuit.py
@@ -0,0 +1,147 @@
+"""A creation workflow that adds an existing Layer 2 Circuit to the database."""
+
+from typing import Any, Self
+from uuid import uuid4
+
+from orchestrator import step, 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
+from orchestrator.workflows.steps import resync, set_status, store_process_subscription
+from pydantic import BaseModel, ConfigDict, model_validator
+from pydantic_forms.types import UUIDstr
+
+from gso.products import ProductName
+from gso.products.product_blocks.layer_2_circuit import Layer2CircuitSideBlockInactive, Layer2CircuitType
+from gso.products.product_blocks.service_binding_port import ServiceBindingPortInactive
+from gso.products.product_types.edge_port import EdgePort
+from gso.products.product_types.layer_2_circuit import (
+    ImportedLayer2CircuitInactive,
+    Layer2CircuitServiceType,
+)
+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.interfaces import BandwidthString
+from gso.utils.types.virtual_identifiers import VC_ID, VLAN_ID
+
+
+def initial_input_form_generator() -> FormGenerator:
+    """Generate a form that can be pre-filled using an :term:`API` endpoint."""
+
+    class ServiceBindingPortInput(BaseModel):
+        edge_port: UUIDstr
+        vlan_id: VLAN_ID
+
+    class ImportLayer2CircuitForm(FormPage):
+        model_config = ConfigDict(title="Import Layer 2 Circuit")
+
+        service_type: Layer2CircuitServiceType
+        partner: str
+        geant_sid: str
+        vc_id: VC_ID
+        layer_2_circuit_side_a: ServiceBindingPortInput
+        layer_2_circuit_side_b: ServiceBindingPortInput
+        layer_2_circuit_type: Layer2CircuitType
+        vlan_range_lower_bound: VLAN_ID | None = None
+        vlan_range_upper_bound: VLAN_ID | None = None
+        policer_enabled: bool = False
+        policer_bandwidth: BandwidthString | None = None
+        policer_burst_rate: BandwidthString | None = None
+
+        @model_validator(mode="after")
+        def tagged_layer_2_circuit_has_vlan_bounds(self) -> Self:
+            """If a Layer 2 Circuit is tagged, it must have a :term:`VLAN` range set."""
+            if self.layer_2_circuit_type == Layer2CircuitType.TAGGED and (
+                self.vlan_range_lower_bound is None or self.vlan_range_upper_bound is None
+            ):
+                msg = (
+                    f"A tagged Layer 2 Circuit must have a VLAN range set. Received lower: "
+                    f"{self.vlan_range_lower_bound}, upper: {self.vlan_range_upper_bound}."
+                )
+                raise ValueError(msg)
+            return self
+
+        @model_validator(mode="after")
+        def policer_bandwidth_required_if_policer_enabled(self) -> Self:
+            """If the policer is enabled, the bandwidth and burst rate must be set."""
+            if self.policer_enabled and (self.policer_bandwidth is None or self.policer_burst_rate is None):
+                msg = (
+                    f"If the policer is enabled, the bandwidth and burst rate must be set. Received bandwidth: "
+                    f"{self.policer_bandwidth}, burst rate: {self.policer_burst_rate}."
+                )
+                raise ValueError(msg)
+            return self
+
+    user_input = yield ImportLayer2CircuitForm
+    return user_input.model_dump()
+
+
+@step("Create subscription")
+def create_subscription(partner: str, service_type: Layer2CircuitServiceType) -> State:
+    """Create a new subscription object."""
+    partner_id = get_partner_by_name(partner)["partner_id"]
+    product_id = get_product_id_by_name(ProductName(service_type))
+    subscription = ImportedLayer2CircuitInactive.from_product_id(product_id, partner_id)
+
+    return {"subscription": subscription, "subscription_id": subscription.subscription_id}
+
+
+@step("Initialize subscription")
+def initialize_subscription(
+    subscription: ImportedLayer2CircuitInactive,
+    geant_sid: str,
+    layer_2_circuit_side_a: dict[str, Any],
+    layer_2_circuit_side_b: dict[str, Any],
+    layer_2_circuit_type: Layer2CircuitType,
+    vlan_range_lower_bound: VLAN_ID | None,
+    vlan_range_upper_bound: VLAN_ID | None,
+    policer_enabled: bool,  # noqa: FBT001
+    policer_bandwidth: BandwidthString | None,
+    policer_burst_rate: BandwidthString | None,
+    vc_id: VC_ID,
+) -> State:
+    """Initialize the subscription object."""
+    layer_2_circuit_sides = []
+    for circuit_side_data in [layer_2_circuit_side_a, layer_2_circuit_side_b]:
+        sbp = ServiceBindingPortInactive.new(
+            uuid4(),
+            edge_port=EdgePort.from_subscription(subscription_id=circuit_side_data["edge_port"]).edge_port,
+            sbp_type=SBPType.L2,
+            vlan_id=circuit_side_data["vlan_id"],
+            geant_sid=geant_sid,
+            is_tagged=layer_2_circuit_type == Layer2CircuitType.TAGGED,
+            custom_firewall_filters=False,
+        )
+        layer2_circuit_side = Layer2CircuitSideBlockInactive.new(uuid4(), sbp=sbp)
+        layer_2_circuit_sides.append(layer2_circuit_side)
+    subscription.layer_2_circuit.layer_2_circuit_sides = layer_2_circuit_sides
+    subscription.layer_2_circuit.virtual_circuit_id = vc_id
+    subscription.layer_2_circuit.layer_2_circuit_type = layer_2_circuit_type
+    subscription.layer_2_circuit.vlan_range_lower_bound = vlan_range_lower_bound
+    subscription.layer_2_circuit.vlan_range_upper_bound = vlan_range_upper_bound
+    subscription.layer_2_circuit.policer_enabled = policer_enabled
+    subscription.layer_2_circuit.bandwidth = policer_bandwidth
+    subscription.layer_2_circuit.policer_burst_rate = policer_burst_rate
+    subscription.description = f"{subscription.product.name} - {subscription.layer_2_circuit.virtual_circuit_id}"
+
+    return {"subscription": subscription}
+
+
+@workflow(
+    "Create imported Layer 2 Circuit",
+    initial_input_form=initial_input_form_generator,
+    target=Target.CREATE,
+)
+def create_imported_layer_2_circuit() -> StepList:
+    """Import a Layer 2 Circuit 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/l2_circuit/create_layer_2_circuit.py b/gso/workflows/l2_circuit/create_layer_2_circuit.py
new file mode 100644
index 0000000000000000000000000000000000000000..0a15fcebc00172a30e223b7c975badbcbd46d144
--- /dev/null
+++ b/gso/workflows/l2_circuit/create_layer_2_circuit.py
@@ -0,0 +1,154 @@
+"""Workflow for creating a new Layer 2 Circuit."""
+
+from typing import Any, Self
+from uuid import uuid4
+
+from orchestrator import step, workflow
+from orchestrator.forms import FormPage
+from orchestrator.targets import Target
+from orchestrator.types import SubscriptionLifecycle
+from orchestrator.workflow import StepList, begin, done
+from orchestrator.workflows.steps import resync, set_status, store_process_subscription
+from orchestrator.workflows.utils import wrap_create_initial_input_form
+from pydantic import BaseModel, ConfigDict, Field, model_validator
+from pydantic_forms.types import FormGenerator, State, UUIDstr
+from pydantic_forms.validators import Divider, Label, ReadOnlyField
+
+from gso.products.product_blocks.layer_2_circuit import Layer2CircuitSideBlockInactive, Layer2CircuitType
+from gso.products.product_blocks.service_binding_port import ServiceBindingPortInactive
+from gso.products.product_types.edge_port import EdgePort
+from gso.products.product_types.layer_2_circuit import Layer2Circuit, Layer2CircuitInactive
+from gso.services.partners import get_partner_by_name
+from gso.utils.helpers import active_edge_port_selector, generate_unique_vc_id, partner_choice
+from gso.utils.shared_enums import SBPType
+from gso.utils.types.interfaces import BandwidthString
+from gso.utils.types.tt_number import TTNumber
+from gso.utils.types.virtual_identifiers import VLAN_ID
+
+
+def initial_input_generator(product_name: str) -> FormGenerator:
+    """Gather input from the operator about a new Layer 2 Circuit subscription."""
+    geant_partner_id = get_partner_by_name("GEANT")["partner_id"]
+
+    class CreateLayer2CircuitServicePage(FormPage):
+        model_config = ConfigDict(title=f"{product_name}")
+
+        tt_number: TTNumber
+        partner: partner_choice() = geant_partner_id  # type: ignore[valid-type]
+        divider: Divider = Field(None, exclude=True)
+
+        layer_2_circuit_type: Layer2CircuitType
+        policer_enabled: bool = False
+
+    initial_user_input = yield CreateLayer2CircuitServicePage
+
+    class Layer2CircuitSideSelection(BaseModel):
+        edge_port: active_edge_port_selector(  # type: ignore[valid-type]
+            partner_id=initial_user_input.partner if initial_user_input.partner != geant_partner_id else None
+        )
+        vlan_id: VLAN_ID
+
+    def _vlan_range_field(*, is_tagged: bool) -> VLAN_ID:
+        """Return the appropriate field type based on whether the circuit is tagged."""
+        return VLAN_ID if is_tagged else ReadOnlyField(None, default_type=int)
+
+    def _policer_field(*, policer_enabled: bool) -> BandwidthString:
+        """Return the appropriate field type based on whether the policer is enabled."""
+        return BandwidthString if policer_enabled else ReadOnlyField(None, default_type=str)
+
+    class Layer2CircuitServiceSidesPage(FormPage):
+        model_config = ConfigDict(title=f"{product_name} - Configure Edge Ports")
+
+        vlan_range_label: Label = Field("Please set a VLAN range, bounds including.", exclude=True)
+        vlan_range_lower_bound: _vlan_range_field(  # type: ignore[valid-type]
+            is_tagged=initial_user_input.layer_2_circuit_type == Layer2CircuitType.TAGGED
+        )
+        vlan_range_upper_bound: _vlan_range_field(  # type: ignore[valid-type]
+            is_tagged=initial_user_input.layer_2_circuit_type == Layer2CircuitType.TAGGED
+        )
+        vlan_divider: Divider = Field(None, exclude=True)
+        policer_bandwidth: _policer_field(policer_enabled=initial_user_input.policer_enabled)  # type: ignore[valid-type]
+        policer_burst_rate: _policer_field(policer_enabled=initial_user_input.policer_enabled)  # type: ignore[valid-type]
+        geant_sid: str
+        layer_2_circuit_side_a: Layer2CircuitSideSelection
+        side_divider: Divider = Field(None, exclude=True)
+        layer_2_circuit_side_b: Layer2CircuitSideSelection
+
+        @model_validator(mode="after")
+        def check_unique_sides(self) -> Self:
+            if self.layer_2_circuit_side_a.edge_port == self.layer_2_circuit_side_b.edge_port:
+                msg = "Both sides of the circuit cannot be connected to the same edge port"
+                raise ValueError(msg)
+            return self
+
+    layer_2_circuit_input = yield Layer2CircuitServiceSidesPage
+
+    return {"product_name": product_name} | initial_user_input.model_dump() | layer_2_circuit_input.model_dump()
+
+
+@step("Create subscription")
+def create_subscription(product: UUIDstr, partner: str) -> State:
+    """Create a new subscription object in the database."""
+    subscription = Layer2CircuitInactive.from_product_id(product, partner)
+
+    return {"subscription": subscription, "subscription_id": subscription.subscription_id}
+
+
+@step("Initialize subscription")
+def initialize_subscription(
+    subscription: Layer2CircuitInactive,
+    layer_2_circuit_side_a: dict[str, Any],
+    layer_2_circuit_side_b: dict[str, Any],
+    layer_2_circuit_type: Layer2CircuitType,
+    vlan_range_lower_bound: VLAN_ID | None,
+    vlan_range_upper_bound: VLAN_ID | None,
+    policer_enabled: bool,  # noqa: FBT001
+    policer_bandwidth: BandwidthString | None,
+    policer_burst_rate: BandwidthString | None,
+    geant_sid: str,
+) -> State:
+    """Build a subscription object from all user input."""
+    layer_2_circuit_sides = []
+    for circuit_side_data in [layer_2_circuit_side_a, layer_2_circuit_side_b]:
+        sbp = ServiceBindingPortInactive.new(
+            uuid4(),
+            edge_port=EdgePort.from_subscription(subscription_id=circuit_side_data["edge_port"]).edge_port,
+            sbp_type=SBPType.L2,
+            vlan_id=circuit_side_data["vlan_id"],
+            geant_sid=geant_sid,
+            is_tagged=layer_2_circuit_type == Layer2CircuitType.TAGGED,
+            custom_firewall_filters=False,
+        )
+        layer2_circuit_side = Layer2CircuitSideBlockInactive.new(uuid4(), sbp=sbp)
+        layer_2_circuit_sides.append(layer2_circuit_side)
+    subscription.layer_2_circuit.layer_2_circuit_sides = layer_2_circuit_sides
+    subscription.layer_2_circuit.virtual_circuit_id = generate_unique_vc_id()
+    subscription.layer_2_circuit.layer_2_circuit_type = layer_2_circuit_type
+    subscription.layer_2_circuit.vlan_range_lower_bound = vlan_range_lower_bound
+    subscription.layer_2_circuit.vlan_range_upper_bound = vlan_range_upper_bound
+    subscription.layer_2_circuit.policer_enabled = policer_enabled
+    subscription.layer_2_circuit.bandwidth = policer_bandwidth
+    subscription.layer_2_circuit.policer_burst_rate = policer_burst_rate
+    subscription.description = f"{subscription.product.name} - {subscription.layer_2_circuit.virtual_circuit_id}"
+
+    subscription = Layer2Circuit.from_other_lifecycle(subscription, SubscriptionLifecycle.PROVISIONING)
+
+    return {"subscription": subscription}
+
+
+@workflow(
+    "Create Layer 2 Circuit Service",
+    initial_input_form=wrap_create_initial_input_form(initial_input_generator),
+    target=Target.CREATE,
+)
+def create_layer_2_circuit() -> StepList:
+    """Create a new Layer 2 Circuit service subscription."""
+    return (
+        begin
+        >> create_subscription
+        >> store_process_subscription(Target.CREATE)
+        >> initialize_subscription
+        >> set_status(SubscriptionLifecycle.ACTIVE)
+        >> resync
+        >> done
+    )
diff --git a/gso/workflows/l2_circuit/import_layer_2_circuit.py b/gso/workflows/l2_circuit/import_layer_2_circuit.py
new file mode 100644
index 0000000000000000000000000000000000000000..01224e86cbdb614eb879dfc2622fdbfb2804d19b
--- /dev/null
+++ b/gso/workflows/l2_circuit/import_layer_2_circuit.py
@@ -0,0 +1,43 @@
+"""A modification workflow for migrating an ImportedLayer2Circuit to an Layer2Circuit 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.layer_2_circuit import ImportedLayer2Circuit, Layer2Circuit, Layer2CircuitServiceType
+from gso.services.subscriptions import get_product_id_by_name
+
+
+@step("Create imported subscription")
+def import_layer_2_circuit_subscription(subscription_id: UUIDstr) -> State:
+    """Take an imported subscription, and turn it into a layer 2 circuit subscription."""
+    old_layer_2_circuit_subscription = ImportedLayer2Circuit.from_subscription(subscription_id)
+    if old_layer_2_circuit_subscription.layer_2_circuit_service_type == Layer2CircuitServiceType.IMPORTED_GEANT_PLUS:
+        new_subscription_id = get_product_id_by_name(ProductName.GEANT_PLUS)
+    elif (
+        old_layer_2_circuit_subscription.layer_2_circuit_service_type == Layer2CircuitServiceType.IMPORTED_EXPRESSROUTE
+    ):
+        new_subscription_id = get_product_id_by_name(ProductName.EXPRESSROUTE)
+    else:
+        msg = f"This {old_layer_2_circuit_subscription.layer_2_circuit_service_type} is already imported."
+        raise ProcessFailureError(message=msg, details=old_layer_2_circuit_subscription)
+    new_subscription = Layer2Circuit.from_other_product(old_layer_2_circuit_subscription, new_subscription_id)  # type: ignore[arg-type]
+
+    return {"subscription": new_subscription}
+
+
+@workflow("Import Layer 2 Circuit", target=Target.MODIFY, initial_input_form=wrap_modify_initial_input_form(None))
+def import_layer_2_circuit() -> StepList:
+    """Modify an imported subscription into a layer 2 circuit subscription to complete the import."""
+    return (
+        init
+        >> store_process_subscription(Target.MODIFY)
+        >> unsync
+        >> import_layer_2_circuit_subscription
+        >> resync
+        >> done
+    )
diff --git a/gso/workflows/l2_circuit/modify_layer_2_circuit.py b/gso/workflows/l2_circuit/modify_layer_2_circuit.py
new file mode 100644
index 0000000000000000000000000000000000000000..0ac2d3919763596fdb34c6f9223c2efccc5694de
--- /dev/null
+++ b/gso/workflows/l2_circuit/modify_layer_2_circuit.py
@@ -0,0 +1,114 @@
+"""A modification workflow for a Layer 2 Circuit subscription."""
+
+from orchestrator import begin, done, workflow
+from orchestrator.forms import FormPage
+from orchestrator.targets import Target
+from orchestrator.types import FormGenerator, UUIDstr
+from orchestrator.workflow import StepList, step
+from orchestrator.workflows.steps import resync, store_process_subscription, unsync
+from orchestrator.workflows.utils import wrap_modify_initial_input_form
+from pydantic import ConfigDict, Field
+from pydantic_forms.validators import Divider, Label, ReadOnlyField
+
+from gso.products.product_blocks.layer_2_circuit import Layer2CircuitType
+from gso.products.product_types.edge_port import EdgePort
+from gso.products.product_types.layer_2_circuit import Layer2Circuit
+from gso.services.partners import get_partner_by_id
+from gso.utils.types.interfaces import BandwidthString
+from gso.utils.types.tt_number import TTNumber
+from gso.utils.types.virtual_identifiers import VLAN_ID
+
+
+def initial_input_form_generator(subscription_id: UUIDstr) -> FormGenerator:
+    """Get input from the operator about the modifications to be made to a Layer 2 Circuit subscription."""
+    subscription = Layer2Circuit.from_subscription(subscription_id)
+    product_name = subscription.product.name
+
+    class ModifyL2CircuitForm(FormPage):
+        model_config = ConfigDict(title=f"Modify {product_name}")
+        tt_number: TTNumber
+        partner: ReadOnlyField(get_partner_by_id(subscription.customer_id).name, default_type=str)  # type: ignore[valid-type]
+        divider: Divider = Field(None, exclude=True)
+
+        layer_2_circuit_type: Layer2CircuitType = subscription.layer_2_circuit.layer_2_circuit_type
+        policer_enabled: bool = subscription.layer_2_circuit.policer_enabled
+
+    layer_2_circuit_input = yield ModifyL2CircuitForm
+
+    class ModifyLayer2CircuitServiceSidesPage(FormPage):
+        model_config = ConfigDict(title=f"{product_name} - Configure Edge Ports")
+
+        vlan_range_label: Label = Field("Please set a VLAN range, bounds including.", exclude=True)
+        if layer_2_circuit_input.layer_2_circuit_type == Layer2CircuitType.TAGGED:
+            vlan_range_lower_bound: VLAN_ID = subscription.layer_2_circuit.vlan_range_lower_bound  # type: ignore[assignment]
+            vlan_range_upper_bound: VLAN_ID = subscription.layer_2_circuit.vlan_range_upper_bound  # type: ignore[assignment]
+        else:
+            vlan_range_lower_bound: ReadOnlyField(None, default_type=int)  # type: ignore[no-redef, valid-type]
+            vlan_range_upper_bound: ReadOnlyField(None, default_type=int)  # type: ignore[no-redef, valid-type]
+
+        vlan_divider: Divider = Field(None, exclude=True)
+        if layer_2_circuit_input.policer_enabled:
+            policer_bandwidth: BandwidthString = subscription.layer_2_circuit.bandwidth  # type: ignore[assignment]
+            policer_burst_rate: BandwidthString = subscription.layer_2_circuit.policer_burst_rate  # type: ignore[assignment]
+        else:
+            policer_bandwidth: ReadOnlyField(None, default_type=str)  # type: ignore[no-redef, valid-type]
+            policer_burst_rate: ReadOnlyField(None, default_type=str)  # type: ignore[no-redef, valid-type]
+        policer_divider: Divider = Field(None, exclude=True)
+
+        layer_2_circuit_side_a: ReadOnlyField(  # type: ignore[valid-type]
+            EdgePort.from_subscription(
+                subscription.layer_2_circuit.layer_2_circuit_sides[0].sbp.edge_port.owner_subscription_id
+            ).description,
+            default_type=str,
+        )
+        side_divider: Divider = Field(None, exclude=True)
+        layer_2_circuit_side_b: ReadOnlyField(  # type: ignore[valid-type]
+            EdgePort.from_subscription(
+                subscription.layer_2_circuit.layer_2_circuit_sides[1].sbp.edge_port.owner_subscription_id
+            ).description,
+            default_type=str,
+        )
+
+    layer_2_circuit_sides = yield ModifyLayer2CircuitServiceSidesPage
+
+    return {"product_name": product_name} | layer_2_circuit_input.model_dump() | layer_2_circuit_sides.model_dump()
+
+
+@step("Update Layer 2 Circuit subscription")
+def modify_layer_2_circuit_subscription(
+    subscription: Layer2Circuit,
+    layer_2_circuit_type: Layer2CircuitType,
+    vlan_range_lower_bound: VLAN_ID | None,
+    vlan_range_upper_bound: VLAN_ID | None,
+    policer_enabled: bool,  # noqa: FBT001
+    policer_bandwidth: BandwidthString | None,
+    policer_burst_rate: BandwidthString | None,
+) -> dict:
+    """Update the Layer 2 Circuit subscription with the new values."""
+    subscription.layer_2_circuit.layer_2_circuit_type = layer_2_circuit_type
+    subscription.layer_2_circuit.vlan_range_lower_bound = vlan_range_lower_bound
+    subscription.layer_2_circuit.vlan_range_upper_bound = vlan_range_upper_bound
+    subscription.layer_2_circuit.policer_enabled = policer_enabled
+    subscription.layer_2_circuit.bandwidth = policer_bandwidth
+    subscription.layer_2_circuit.policer_burst_rate = policer_burst_rate
+    for layer_2_circuit_side in subscription.layer_2_circuit.layer_2_circuit_sides:
+        layer_2_circuit_side.sbp.is_tagged = layer_2_circuit_type == Layer2CircuitType.TAGGED
+
+    return {"subscription": subscription}
+
+
+@workflow(
+    "Modify Layer 2 Circuit Service",
+    initial_input_form=wrap_modify_initial_input_form(initial_input_form_generator),
+    target=Target.MODIFY,
+)
+def modify_layer_2_circuit() -> StepList:
+    """Modify an existing Layer 2 Circuit service subscription."""
+    return (
+        begin
+        >> store_process_subscription(Target.MODIFY)
+        >> unsync
+        >> modify_layer_2_circuit_subscription
+        >> resync
+        >> done
+    )
diff --git a/gso/workflows/l2_circuit/terminate_layer_2_circuit.py b/gso/workflows/l2_circuit/terminate_layer_2_circuit.py
new file mode 100644
index 0000000000000000000000000000000000000000..1652aa02ca5e7d4384716e8dd0f0f0909549fc7d
--- /dev/null
+++ b/gso/workflows/l2_circuit/terminate_layer_2_circuit.py
@@ -0,0 +1,40 @@
+"""Workflow for terminating a Layer 2 Circuit."""
+
+from orchestrator import begin, workflow
+from orchestrator.forms import FormPage
+from orchestrator.targets import Target
+from orchestrator.types import SubscriptionLifecycle, UUIDstr
+from orchestrator.workflow import StepList, done
+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
+
+from gso.products.product_types.layer_2_circuit import Layer2Circuit
+from gso.utils.types.tt_number import TTNumber
+
+
+def _input_form_generator(subscription_id: UUIDstr) -> FormGenerator:
+    layer_2_circuit = Layer2Circuit.from_subscription(subscription_id)
+
+    class TerminateForm(FormPage):
+        tt_number: TTNumber
+
+    yield TerminateForm
+    return {"subscription": layer_2_circuit}
+
+
+@workflow(
+    "Terminate Layer 2 Circuit Service",
+    initial_input_form=wrap_modify_initial_input_form(_input_form_generator),
+    target=Target.TERMINATE,
+)
+def terminate_layer_2_circuit() -> StepList:
+    """Terminate a Layer 2 Circuit subscription."""
+    return (
+        begin
+        >> store_process_subscription(Target.TERMINATE)
+        >> unsync
+        >> set_status(SubscriptionLifecycle.TERMINATED)
+        >> resync
+        >> done
+    )
diff --git a/gso/workflows/l3_core_service/__init__.py b/gso/workflows/l3_core_service/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..6143fba1180f4f37aa0b13a9fa91ebc578c0806a
--- /dev/null
+++ b/gso/workflows/l3_core_service/__init__.py
@@ -0,0 +1 @@
+"""Layer 3 core service workflows."""
diff --git a/gso/workflows/nren_l3_core_service/create_imported_nren_l3_core_service.py b/gso/workflows/l3_core_service/create_imported_l3_core_service.py
similarity index 54%
rename from gso/workflows/nren_l3_core_service/create_imported_nren_l3_core_service.py
rename to gso/workflows/l3_core_service/create_imported_l3_core_service.py
index f5a2e2ea00588c4ea9893f71bbb5772c26c7f0cf..0ff4c30f9664b6d5124d2fb144ca002e8027e7a0 100644
--- a/gso/workflows/nren_l3_core_service/create_imported_nren_l3_core_service.py
+++ b/gso/workflows/l3_core_service/create_imported_l3_core_service.py
@@ -1,4 +1,4 @@
-"""A creation workflow for adding an existing NREN L3 Core Service to the service database."""
+"""A creation workflow for adding an existing L3 Core Service to the service database."""
 
 from uuid import uuid4
 
@@ -6,32 +6,38 @@ from orchestrator import workflow
 from orchestrator.forms import FormPage
 from orchestrator.targets import Target
 from orchestrator.types import FormGenerator, SubscriptionLifecycle
+from orchestrator.utils.errors import ProcessFailureError
 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 import BaseModel, NonNegativeInt
 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_blocks.bgp_session import BGPSession, IPFamily, IPTypes
+from gso.products.product_blocks.l3_core_service import AccessPortInactive
+from gso.products.product_blocks.service_binding_port import BFDSettings, ServiceBindingPortInactive
 from gso.products.product_types.edge_port import EdgePort
-from gso.products.product_types.nren_l3_core_service import ImportedNRENL3CoreServiceInactive, NRENL3CoreServiceType
+from gso.products.product_types.l3_core_service import ImportedL3CoreServiceInactive, L3CoreServiceType
 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
+from gso.utils.types.virtual_identifiers import VLAN_ID
 
 
 def initial_input_form_generator() -> FormGenerator:
     """Take all information passed to this workflow by the :term:`API` endpoint that was called."""
 
-    class BaseBGPPeer(BaseModel):
+    class BFDSettingsModel(BaseModel):
         bfd_enabled: bool = False
-        bfd_interval: int | None = None
+        bfd_interval_rx: int | None = None
+        bfd_interval_tx: int | None = None
         bfd_multiplier: int | None = None
+
+    class BaseBGPPeer(BaseModel):
+        bfd_enabled: bool = False
         has_custom_policies: bool = False
-        authentication_key: str
+        authentication_key: str | None
         multipath_enabled: bool = False
         send_default_route: bool = False
         is_passive: bool = False
@@ -39,6 +45,7 @@ def initial_input_form_generator() -> FormGenerator:
         families: list[IPFamily]
         is_multi_hop: bool
         rtbh_enabled: bool
+        prefix_limit: NonNegativeInt | None = None
 
     class ServiceBindingPort(BaseModel):
         edge_port: UUIDstr
@@ -55,45 +62,67 @@ def initial_input_form_generator() -> FormGenerator:
         rtbh_enabled: bool = True
         is_multi_hop: bool = True
         bgp_peers: list[BaseBGPPeer]
+        v4_bfd_settings: BFDSettingsModel
+        v6_bfd_settings: BFDSettingsModel
 
-    class ImportNRENL3CoreServiceForm(FormPage):
+    class ImportL3CoreServiceForm(FormPage):
         partner: str
         service_binding_ports: list[ServiceBindingPort]
-        service_type: NRENL3CoreServiceType
+        service_type: L3CoreServiceType
 
-    user_input = yield ImportNRENL3CoreServiceForm
+    user_input = yield ImportL3CoreServiceForm
 
     return user_input.model_dump()
 
 
 @step("Create subscription")
-def create_subscription(partner: str, service_type: NRENL3CoreServiceType) -> dict:
+def create_subscription(partner: str, service_type: L3CoreServiceType) -> 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)
+    match service_type:
+        case L3CoreServiceType.GEANT_IP:
+            product_id = get_product_id_by_name(ProductName.IMPORTED_GEANT_IP)
+        case L3CoreServiceType.IAS:
+            product_id = get_product_id_by_name(ProductName.IMPORTED_IAS)
+        case L3CoreServiceType.GWS:
+            product_id = get_product_id_by_name(ProductName.IMPORTED_GWS)
+        case L3CoreServiceType.LHCONE:
+            product_id = get_product_id_by_name(ProductName.IMPORTED_LHCONE)
+        case L3CoreServiceType.COPERNICUS:
+            product_id = get_product_id_by_name(ProductName.IMPORTED_COPERNICUS)
+        case _:
+            msg = "L3 Core service type not defined. Cannot create subscription."
+            raise ProcessFailureError(msg, details=service_type)
+    subscription = ImportedL3CoreServiceInactive.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:
+def initialize_subscription(subscription: ImportedL3CoreServiceInactive, 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]
-
+        sbp_bgp_session_list = [
+            BGPSession.new(
+                subscription_id=uuid4(),
+                ip_type=IPTypes.IPV4
+                if any(family in {IPFamily.V4UNICAST, IPFamily.V4MULTICAST} for family in session["families"])
+                else IPTypes.IPV6,
+                **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,
+            v4_bfd_settings=BFDSettings.new(subscription_id=uuid4(), **(service_binding_port.pop("v4_bfd_settings"))),
+            v6_bfd_settings=BFDSettings.new(subscription_id=uuid4(), **(service_binding_port.pop("v6_bfd_settings"))),
             **service_binding_port,
         )
-        subscription.nren_l3_core_service.nren_ap_list.append(
-            NRENAccessPortInactive.new(
+        subscription.l3_core_service.ap_list.append(
+            AccessPortInactive.new(
                 subscription_id=uuid4(),
                 ap_type=service_binding_port["ap_type"],
                 sbp=service_binding_port_subscription,
@@ -106,11 +135,11 @@ def initialize_subscription(subscription: ImportedNRENL3CoreServiceInactive, ser
 
 
 @workflow(
-    "Create imported NREN L3 Core Service",
+    "Create imported L3 Core Service",
     initial_input_form=initial_input_form_generator,
     target=Target.CREATE,
 )
-def create_imported_nren_l3_core_service() -> StepList:
+def create_imported_l3_core_service() -> StepList:
     """Import a GÉANT IP without provisioning it."""
     return (
         begin
diff --git a/gso/workflows/nren_l3_core_service/create_nren_l3_core_service.py b/gso/workflows/l3_core_service/create_l3_core_service.py
similarity index 55%
rename from gso/workflows/nren_l3_core_service/create_nren_l3_core_service.py
rename to gso/workflows/l3_core_service/create_l3_core_service.py
index 2ac1708d525591a0aa0d9a19b68b5ef89ef9a064..f23a2fcfb2260c42dc930cd6390c05874e89242b 100644
--- a/gso/workflows/nren_l3_core_service/create_nren_l3_core_service.py
+++ b/gso/workflows/l3_core_service/create_l3_core_service.py
@@ -1,6 +1,6 @@
-"""Create a new NREN L3 Core Service subscription including GÉANT IP and IAS."""
+"""Create a new L3 Core Service subscription including GÉANT IP and IAS."""
 
-from typing import Annotated, Any
+from typing import Any
 from uuid import uuid4
 
 from orchestrator.forms import FormPage
@@ -10,15 +10,16 @@ from orchestrator.types import FormGenerator, State, SubscriptionLifecycle, UUID
 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 import BaseModel, ConfigDict, Field, NonNegativeInt, 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_blocks.bgp_session import BGPSession, IPFamily, IPTypes
+from gso.products.product_blocks.l3_core_service import AccessPortInactive
+from gso.products.product_blocks.service_binding_port import BFDSettings, ServiceBindingPortInactive
 from gso.products.product_types.edge_port import EdgePort
-from gso.products.product_types.nren_l3_core_service import NRENL3CoreService, NRENL3CoreServiceInactive
+from gso.products.product_types.l3_core_service import L3CoreService, L3CoreServiceInactive
 from gso.services.lso_client import LSOState, lso_interaction
+from gso.services.partners import get_partner_by_id
 from gso.utils.helpers import (
     active_edge_port_selector,
     partner_choice,
@@ -26,168 +27,183 @@ from gso.utils.helpers import (
 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
+from gso.utils.types.virtual_identifiers import VLAN_ID
 
 
 def initial_input_form_generator(product_name: str) -> FormGenerator:
     """Gather input from the operator to build a new subscription object."""
 
-    class CreateNRENCoreServiceForm(FormPage):
+    class CreateL3CoreServiceForm(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
+    initial_user_input = yield CreateL3CoreServiceForm
 
     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)]
+        edge_port: EdgePortSelection
 
-    selected_edge_ports = yield EdgePortSelectionForm
-    ep_list = selected_edge_ports.edge_ports
+    selected_edge_port = yield EdgePortSelectionForm
 
-    class BaseBGPPeer(BaseModel):
+    class BFDSettingsForm(BaseModel):
         bfd_enabled: bool = False
-        bfd_interval: int | None = None
+        bfd_interval_rx: int | None = Field(default=None, examples=["BFD RX defaults"])
+        bfd_interval_tx: int | None = None
         bfd_multiplier: int | None = None
+
+    class IPv4BGPPeer(BaseModel):
+        peer_address: IPv4AddressType
+        authentication_key: str | None = None
         has_custom_policies: bool = False
-        authentication_key: str
+        bfd_enabled: bool = False
         multipath_enabled: bool = False
-        send_default_route: bool = False
+        prefix_limit: NonNegativeInt | None = None
         is_passive: bool = False
-
-    class IPv4BGPPeer(BaseBGPPeer):
-        peer_address: IPv4AddressType
         add_v4_multicast: bool = Field(default=False, exclude=True)
+        send_default_route: bool = False
 
         @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):
+        @computed_field  # type: ignore[misc]
+        @property
+        def ip_type(self) -> IPTypes:
+            return IPTypes.IPV4
+
+    class IPv6BGPPeer(BaseModel):
         peer_address: IPv6AddressType
+        authentication_key: str | None = None
+        has_custom_policies: bool = False
+        bfd_enabled: bool = False
+        multipath_enabled: bool = False
+        prefix_limit: NonNegativeInt | None = None
+        is_passive: bool = False
         add_v6_multicast: bool = Field(default=False, exclude=True)
+        send_default_route: bool = False
 
         @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(),
-                ]
-            }
+        @computed_field  # type: ignore[misc]
+        @property
+        def ip_type(self) -> IPTypes:
+            return IPTypes.IPV6
+
+    class BindingPortInputForm(FormPage):
+        model_config = ConfigDict(title=f"{product_name} - Configure Edge Port")
+        info_label: Label = Field("Please configure the Service Binding Ports for the Edge Port.", exclude=True)
+        current_ep_label: Label = Field(
+            f"Currently configuring on {EdgePort.from_subscription(selected_edge_port.edge_port.edge_port).description}"
+            f" (Access Port type: {selected_edge_port.edge_port.ap_type})",
+            exclude=True,
         )
 
+        geant_sid: str
+        is_tagged: bool = False
+        vlan_id: VLAN_ID
+        custom_firewall_filters: bool = False
+        divider: Divider = Field(None, exclude=True)
+        v4_label: Label = Field("IPV4 SBP interface params", exclude=True)
+        ipv4_address: IPv4AddressType
+        ipv4_mask: IPV4Netmask
+        v4_bfd_settings: BFDSettingsForm
+        divider2: Divider = Field(None, exclude=True)
+        v6_label: Label = Field("IPV6 SBP interface params", exclude=True)
+        ipv6_address: IPv6AddressType
+        ipv6_mask: IPV6Netmask
+        v6_bfd_settings: BFDSettingsForm
+        divider3: Divider = Field(None, exclude=True)
+        v4_bgp_peer: IPv4BGPPeer
+        v6_bgp_peer: IPv6BGPPeer
+
+    binding_port_input_form = yield BindingPortInputForm
+    binding_port_input = 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}
+        | selected_edge_port.model_dump()
+        | {"binding_port_input": binding_port_input, "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)
+    subscription = L3CoreServiceInactive.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
+    subscription: L3CoreServiceInactive, edge_port: dict, binding_port_input: 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(
+    edge_port_subscription = EdgePort.from_subscription(edge_port["edge_port"])
+    sbp_bgp_session_list = [
+        BGPSession.new(subscription_id=uuid4(), rtbh_enabled=True, is_multi_hop=True, **session)
+        for session in binding_port_input["bgp_peers"]
+    ]
+    service_binding_port = ServiceBindingPortInactive.new(
+        subscription_id=uuid4(),
+        v4_bfd_settings=BFDSettings.new(subscription_id=uuid4(), **(binding_port_input.pop("v4_bfd_settings"))),
+        v6_bfd_settings=BFDSettings.new(subscription_id=uuid4(), **(binding_port_input.pop("v6_bfd_settings"))),
+        **binding_port_input,
+        bgp_session_list=sbp_bgp_session_list,
+        sbp_type=SBPType.L3,
+        edge_port=edge_port_subscription.edge_port,
+    )
+    subscription.l3_core_service.ap_list.append(
+        AccessPortInactive.new(
             subscription_id=uuid4(),
-            **sbp_input,
-            bgp_session_list=sbp_bgp_session_list,
-            sbp_type=SBPType.L3,
-            edge_port=edge_port_subscription.edge_port,
+            ap_type=edge_port["ap_type"],
+            sbp=service_binding_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)
+    )
+    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}
+    partner_name = get_partner_by_id(subscription.customer_id).name
+    return {"subscription": subscription, "edge_port_fqdn_list": edge_port_fqdn_list, "partner_name": partner_name}
 
 
 @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]
+    subscription: dict[str, Any], process_id: UUIDstr, tt_number: str, edge_port_fqdn_list: list[str], partner_name: str
 ) -> LSOState:
     """Perform a dry run of deploying Service Binding Ports."""
     extra_vars = {
         "subscription": subscription,
+        "partner_name": partner_name,
         "dry_run": True,
         "verb": "deploy",
+        "object": "sbp",
         "commit_comment": f"GSO_PROCESS_ID: {process_id} - TT_NUMBER: {tt_number} - "
         f"Deploy config for {subscription["description"]}",
     }
 
     return {
-        "playbook_name": "manage_sbp.yaml",
+        "playbook_name": "gap_ansible/playbooks/l3_core_service.yaml",
         "inventory": {"all": {"hosts": dict.fromkeys(edge_port_fqdn_list)}},
         "extra_vars": extra_vars,
     }
@@ -195,19 +211,21 @@ def provision_sbp_dry(
 
 @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]
+    subscription: dict[str, Any], process_id: UUIDstr, tt_number: str, edge_port_fqdn_list: list[str], partner_name: str
 ) -> LSOState:
     """Deploy Service Binding Ports."""
     extra_vars = {
         "subscription": subscription,
+        "partner_name": partner_name,
         "dry_run": False,
         "verb": "deploy",
+        "object": "sbp",
         "commit_comment": f"GSO_PROCESS_ID: {process_id} - TT_NUMBER: {tt_number} - "
         f"Deploy config for {subscription["description"]}",
     }
 
     return {
-        "playbook_name": "manage_sbp.yaml",
+        "playbook_name": "gap_ansible/playbooks/l3_core_service.yaml",
         "inventory": {"all": {"hosts": dict.fromkeys(edge_port_fqdn_list)}},
         "extra_vars": extra_vars,
     }
@@ -216,10 +234,10 @@ def provision_sbp_real(
 @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"}
+    extra_vars = {"subscription": subscription, "verb": "check", "object": "sbp"}
 
     return {
-        "playbook_name": "manage_sbp.yaml",
+        "playbook_name": "gap_ansible/playbooks/l3_core_service.yaml",
         "inventory": {"all": {"hosts": dict.fromkeys(edge_port_fqdn_list)}},
         "extra_vars": extra_vars,
     }
@@ -227,19 +245,21 @@ def check_sbp_functionality(subscription: dict[str, Any], edge_port_fqdn_list: l
 
 @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
+    subscription: dict[str, Any], edge_port_fqdn_list: list[str], tt_number: str, process_id: UUIDstr, partner_name: str
 ) -> LSOState:
     """Perform a dry run of deploying :term:`BGP` peers."""
     extra_vars = {
         "subscription": subscription,
+        "partner_name": partner_name,
         "verb": "deploy",
+        "object": "bgp",
         "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",
+        "playbook_name": "gap_ansible/playbooks/l3_core_service.yaml",
         "inventory": {"all": {"hosts": dict.fromkeys(edge_port_fqdn_list)}},
         "extra_vars": extra_vars,
     }
@@ -247,19 +267,21 @@ def deploy_bgp_peers_dry(
 
 @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
+    subscription: dict[str, Any], edge_port_fqdn_list: list[str], tt_number: str, process_id: UUIDstr, partner_name: str
 ) -> LSOState:
     """Deploy :term:`BGP` peers."""
     extra_vars = {
         "subscription": subscription,
+        "partner_name": partner_name,
         "verb": "deploy",
+        "object": "bgp",
         "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",
+        "playbook_name": "gap_ansible/playbooks/l3_core_service.yaml",
         "inventory": {"all": {"hosts": dict.fromkeys(edge_port_fqdn_list)}},
         "extra_vars": extra_vars,
     }
@@ -268,29 +290,29 @@ def deploy_bgp_peers_real(
 @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"}
+    extra_vars = {"subscription": subscription, "verb": "check", "object": "bgp"}
 
     return {
-        "playbook_name": "manage_sbp.yaml",
+        "playbook_name": "gap_ansible/playbooks/l3_core_service.yaml",
         "inventory": {"all": {"hosts": dict.fromkeys(edge_port_fqdn_list)}},
         "extra_vars": extra_vars,
     }
 
 
 @step("Update Infoblox")
-def update_dns_records(subscription: NRENL3CoreService) -> State:
+def update_dns_records(subscription: L3CoreService) -> State:
     """Update :term:`DNS` records in Infoblox."""
     #  TODO: implement
     return {"subscription": subscription}
 
 
 @workflow(
-    "Create NREN L3 Core Service",
+    "Create 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.
+def create_l3_core_service() -> StepList:
+    """Create a new L3 Core Service subscription including GÉANT IP and IAS.
 
     * Create subscription object in the service database
     * Deploy service binding ports
diff --git a/gso/workflows/l3_core_service/import_l3_core_service.py b/gso/workflows/l3_core_service/import_l3_core_service.py
new file mode 100644
index 0000000000000000000000000000000000000000..974c379da1073287f11c6b797335f939d8637d5b
--- /dev/null
+++ b/gso/workflows/l3_core_service/import_l3_core_service.py
@@ -0,0 +1,52 @@
+"""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.l3_core_service import (
+    ImportedL3CoreService,
+    L3CoreService,
+    L3CoreServiceType,
+)
+from gso.services.subscriptions import get_product_id_by_name
+
+
+@step("Create imported subscription")
+def import_l3_core_service_subscription(subscription_id: UUIDstr) -> State:
+    """Take an imported subscription, and turn it into an L3 Core Service subscription."""
+    old_l3_core_service = ImportedL3CoreService.from_subscription(subscription_id)
+    match old_l3_core_service.l3_core_service_type:
+        case L3CoreServiceType.IMPORTED_GEANT_IP:
+            new_subscription_id = get_product_id_by_name(ProductName.GEANT_IP)
+        case L3CoreServiceType.IMPORTED_IAS:
+            new_subscription_id = get_product_id_by_name(ProductName.IAS)
+        case L3CoreServiceType.IMPORTED_GWS:
+            new_subscription_id = get_product_id_by_name(ProductName.GWS)
+        case L3CoreServiceType.IMPORTED_LHCONE:
+            new_subscription_id = get_product_id_by_name(ProductName.LHCONE)
+        case L3CoreServiceType.IMPORTED_COPERNICUS:
+            new_subscription_id = get_product_id_by_name(ProductName.COPERNICUS)
+        case _:
+            msg = f"This {old_l3_core_service.l3_core_service_type} is already imported, nothing to do."
+            raise ProcessFailureError(message=msg, details=old_l3_core_service)
+    new_subscription = L3CoreService.from_other_product(old_l3_core_service, new_subscription_id)  # type: ignore[arg-type]
+
+    return {"subscription": new_subscription}
+
+
+@workflow("Import L3 Core Service", target=Target.MODIFY, initial_input_form=wrap_modify_initial_input_form(None))
+def import_l3_core_service() -> StepList:
+    """Modify an imported subscription into an L3 Core Service subscription to complete the import."""
+    return (
+        init
+        >> store_process_subscription(Target.MODIFY)
+        >> unsync
+        >> import_l3_core_service_subscription
+        >> resync
+        >> done
+    )
diff --git a/gso/workflows/nren_l3_core_service/migrate_nren_l3_core_service.py b/gso/workflows/l3_core_service/migrate_l3_core_service.py
similarity index 78%
rename from gso/workflows/nren_l3_core_service/migrate_nren_l3_core_service.py
rename to gso/workflows/l3_core_service/migrate_l3_core_service.py
index 5d42bccdad3a97ec0f4dc52b2b1d720ab89192e5..d8902530da600dafecb5bca62afb120d48c8d424 100644
--- a/gso/workflows/nren_l3_core_service/migrate_nren_l3_core_service.py
+++ b/gso/workflows/l3_core_service/migrate_l3_core_service.py
@@ -4,31 +4,29 @@ from typing import Annotated
 
 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, 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.products.product_types.l3_core_service import L3CoreService
 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)
+    subscription = L3CoreService.from_subscription(subscription_id)
     partner_id = subscription.customer_id
-    edge_port_count = len(subscription.nren_l3_core_service.nren_ap_list)
+    edge_port_count = len(subscription.l3_core_service.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
-        ]
+        existing_ep_name_list = [ap.sbp.edge_port.owner_subscription_id for ap in subscription.l3_core_service.ap_list]
         edge_port_subscriptions = list(
             filter(
                 lambda ep: bool(ep["customer_id"] == pid) and ep["subscription_id"] not in existing_ep_name_list,
@@ -54,7 +52,7 @@ def initial_input_form_generator(subscription_id: UUIDstr) -> FormGenerator:
             raise ValueError(msg)
         return edge_ports
 
-    class NRENL3CoreServiceEdgePortSelectionForm(FormPage):
+    class L3CoreServiceEdgePortSelectionForm(FormPage):
         model_config = ConfigDict(title=f"Migrating {subscription.product.name} to a new set of Edge Ports")
 
         tt_number: TTNumber
@@ -70,19 +68,19 @@ def initial_input_form_generator(subscription_id: UUIDstr) -> FormGenerator:
                 })",
                 new_edge_port="",
             )
-            for ap in subscription.nren_l3_core_service.nren_ap_list
+            for ap in subscription.l3_core_service.ap_list
         ]
 
-    ep_user_input = yield NRENL3CoreServiceEdgePortSelectionForm
+    ep_user_input = yield L3CoreServiceEdgePortSelectionForm
 
     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:
+def update_subscription_model(subscription: L3CoreService, 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(
+        subscription.l3_core_service.ap_list[index].sbp.edge_port = EdgePort.from_subscription(
             selected_port["new_edge_port"]
         ).edge_port
 
@@ -90,10 +88,10 @@ def update_subscription_model(subscription: NRENL3CoreService, edge_port_selecti
 
 
 @workflow(
-    "Migrate NREN L3 Core Service",
+    "Migrate 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."""
+def migrate_l3_core_service() -> StepList:
+    """Migrate a 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/l3_core_service/modify_l3_core_service.py
similarity index 74%
rename from gso/workflows/nren_l3_core_service/modify_nren_l3_core_service.py
rename to gso/workflows/l3_core_service/modify_l3_core_service.py
index 4331de2bcabc767d2a53904669fa2e1be7b8d724..3073b6deae63a456b428119157fd0cbd3b7ea6f8 100644
--- a/gso/workflows/nren_l3_core_service/modify_nren_l3_core_service.py
+++ b/gso/workflows/l3_core_service/modify_l3_core_service.py
@@ -1,4 +1,4 @@
-"""A modification workflow for a :term:`NREN` L3 Core Service subscription."""
+"""A modification workflow for a L3 Core Service subscription."""
 
 from typing import Annotated, Any
 from uuid import uuid4
@@ -10,23 +10,24 @@ 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 import AfterValidator, BaseModel, ConfigDict, Field, NonNegativeInt, 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_blocks.bgp_session import BGPSession, IPFamily, IPTypes
+from gso.products.product_blocks.l3_core_service import AccessPort
+from gso.products.product_blocks.service_binding_port import BFDSettings, ServiceBindingPort
 from gso.products.product_types.edge_port import EdgePort
-from gso.products.product_types.nren_l3_core_service import NRENL3CoreService
+from gso.products.product_types.l3_core_service import L3CoreService
 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
+from gso.utils.types.virtual_identifiers import VLAN_ID
 
 
 def initial_input_form_generator(subscription_id: UUIDstr) -> FormGenerator:
     """Get input about added, removed, and modified Access Ports."""
-    subscription = NRENL3CoreService.from_subscription(subscription_id)
+    subscription = L3CoreService.from_subscription(subscription_id)
     product_name = subscription.product.name
 
     class AccessPortSelection(BaseModel):
@@ -48,49 +49,67 @@ def initial_input_form_generator(subscription_id: UUIDstr) -> FormGenerator:
                 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
+            for access_port in subscription.l3_core_service.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
-    ]
+    existing_ep_list = [str(ap.sbp.edge_port.owner_subscription_id) for ap in subscription.l3_core_service.ap_list]
 
-    class BaseBGPPeer(BaseModel):
+    class BFDInputModel(BaseModel):
         bfd_enabled: bool = False
-        bfd_interval: int | None = None
+        bfd_interval_rx: int | None = None
+        bfd_interval_tx: int | None = None
         bfd_multiplier: int | None = None
+
+    class IPv4BGPPeer(BaseModel):
+        peer_address: IPv4AddressType
+        authentication_key: str | None = None
         has_custom_policies: bool = False
-        authentication_key: str
+        bfd_enabled: bool = False
         multipath_enabled: bool = False
-        send_default_route: bool = False
+        prefix_limit: NonNegativeInt | None = None
         is_passive: bool = False
-
-    class IPv4BGPPeer(BaseBGPPeer):
-        peer_address: IPv4AddressType
         add_v4_multicast: bool = Field(default=False, exclude=True)
+        send_default_route: bool = False
 
         @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):
+        @computed_field  # type: ignore[misc]
+        @property
+        def ip_type(self) -> IPTypes:
+            return IPTypes.IPV4
+
+    class IPv6BGPPeer(BaseModel):
         peer_address: IPv6AddressType
+        authentication_key: str | None = None
+        has_custom_policies: bool = False
+        bfd_enabled: bool = False
+        multipath_enabled: bool = False
+        prefix_limit: NonNegativeInt | None = None
+        is_passive: bool = False
         add_v6_multicast: bool = Field(default=False, exclude=True)
+        send_default_route: bool = False
 
         @computed_field  # type: ignore[misc]
         @property
         def families(self) -> list[IPFamily]:
             return [IPFamily.V6UNICAST, IPFamily.V6MULTICAST] if self.add_v6_multicast else [IPFamily.V6UNICAST]
 
+        @computed_field  # type: ignore[misc]
+        @property
+        def ip_type(self) -> IPTypes:
+            return IPTypes.IPV6
+
     #  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
+        for access_port in subscription.l3_core_service.ap_list
         if str(access_port.sbp.edge_port.owner_subscription_id) not in input_ep_list
     ]
     modified_ap_list = [
@@ -105,7 +124,7 @@ def initial_input_form_generator(subscription_id: UUIDstr) -> FormGenerator:
                 None,
             ),
         )
-        for access_port in subscription.nren_l3_core_service.nren_ap_list
+        for access_port in subscription.l3_core_service.ap_list
         if str(access_port.sbp.edge_port.owner_subscription_id) in input_ep_list
     ]
     added_ap_list = [
@@ -135,7 +154,7 @@ def initial_input_form_generator(subscription_id: UUIDstr) -> FormGenerator:
 
             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
+            # The SBP model doesn't require these five 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]
@@ -143,6 +162,18 @@ def initial_input_form_generator(subscription_id: UUIDstr) -> FormGenerator:
             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
+            v4_bfd_settings: BFDInputModel = BFDInputModel(
+                bfd_enabled=current_sbp.v4_bfd_settings.bfd_enabled,
+                bfd_multiplier=current_sbp.v4_bfd_settings.bfd_multiplier,
+                bfd_interval_rx=current_sbp.v4_bfd_settings.bfd_interval_rx,
+                bfd_interval_tx=current_sbp.v4_bfd_settings.bfd_interval_tx,
+            )
+            v6_bfd_settings: BFDInputModel = BFDInputModel(
+                bfd_enabled=current_sbp.v6_bfd_settings.bfd_enabled,
+                bfd_multiplier=current_sbp.v6_bfd_settings.bfd_multiplier,
+                bfd_interval_rx=current_sbp.v6_bfd_settings.bfd_interval_rx,
+                bfd_interval_tx=current_sbp.v6_bfd_settings.bfd_interval_tx,
+            )
             divider: Divider = Field(None, exclude=True)
             v4_bgp_peer: IPv4BGPPeer = IPv4BGPPeer(
                 **v4_peer.model_dump(exclude=set("families")),
@@ -187,6 +218,8 @@ def initial_input_form_generator(subscription_id: UUIDstr) -> FormGenerator:
             ipv4_address: IPv4AddressType
             ipv6_address: IPv6AddressType
             custom_firewall_filters: bool = False
+            v4_bfd_settings: BFDInputModel
+            v6_bfd_settings: BFDInputModel
             divider: Divider = Field(None, exclude=True)
             v4_bgp_peer: IPv4BGPPeer
             v6_bgp_peer: IPv6BGPPeer
@@ -212,11 +245,11 @@ def initial_input_form_generator(subscription_id: UUIDstr) -> FormGenerator:
 
 
 @step("Clean up removed Edge Ports")
-def remove_old_sbp_blocks(subscription: NRENL3CoreService, removed_access_ports: list[UUIDstr]) -> State:
+def remove_old_sbp_blocks(subscription: L3CoreService, 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 = [
+    subscription.l3_core_service.ap_list = [
         ap
-        for ap in subscription.nren_l3_core_service.nren_ap_list
+        for ap in subscription.l3_core_service.ap_list
         if str(ap.subscription_instance_id) not in removed_access_ports
     ]
 
@@ -224,9 +257,9 @@ def remove_old_sbp_blocks(subscription: NRENL3CoreService, removed_access_ports:
 
 
 @step("Modify existing Service Binding Ports")
-def modify_existing_sbp_blocks(subscription: NRENL3CoreService, modified_sbp_list: list[dict[str, Any]]) -> State:
+def modify_existing_sbp_blocks(subscription: L3CoreService, modified_sbp_list: list[dict[str, Any]]) -> State:
     """Update the subscription model."""
-    for access_port in subscription.nren_l3_core_service.nren_ap_list:
+    for access_port in subscription.l3_core_service.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)
@@ -235,10 +268,14 @@ def modify_existing_sbp_blocks(subscription: NRENL3CoreService, modified_sbp_lis
         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])
+        for attribute in modified_sbp_data["v4_bfd_settings"]:
+            setattr(current_sbp.v4_bfd_settings, attribute, modified_sbp_data["v4_bfd_settings"][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])
+        for attribute in modified_sbp_data["v6_bfd_settings"]:
+            setattr(current_sbp.v6_bfd_settings, attribute, modified_sbp_data["v6_bfd_settings"][attribute])
 
         current_sbp.bgp_session_list = [v4_peer, v6_peer]
         current_sbp.vlan_id = modified_sbp_data["vlan_id"]
@@ -253,23 +290,27 @@ def modify_existing_sbp_blocks(subscription: NRENL3CoreService, modified_sbp_lis
 
 
 @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."""
+def create_new_sbp_blocks(subscription: L3CoreService, added_service_binding_ports: list[dict[str, Any]]) -> State:
+    """Add new two :term:`SBP` to the 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"]
         ]
+        v4_bfd_settings = BFDSettings.new(subscription_id=uuid4(), **sbp_input.pop("v4_bfd_settings"))
+        v6_bfd_settings = BFDSettings.new(subscription_id=uuid4(), **sbp_input.pop("v6_bfd_settings"))
         service_binding_port = ServiceBindingPort.new(
             subscription_id=uuid4(),
             **sbp_input,
+            v4_bfd_settings=v4_bfd_settings,
+            v6_bfd_settings=v6_bfd_settings,
             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.l3_core_service.ap_list.append(
+            AccessPort.new(
                 subscription_id=uuid4(),
                 ap_type=sbp_input["ap_type"],
                 sbp=service_binding_port,
@@ -280,11 +321,11 @@ def create_new_sbp_blocks(subscription: NRENL3CoreService, added_service_binding
 
 
 @workflow(
-    "Modify NREN L3 Core Service",
+    "Modify 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:
+def modify_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))
diff --git a/gso/workflows/nren_l3_core_service/__init__.py b/gso/workflows/nren_l3_core_service/__init__.py
deleted file mode 100644
index fb652fbe787e3e976db91a9c0cdefa7c897b61f2..0000000000000000000000000000000000000000
--- a/gso/workflows/nren_l3_core_service/__init__.py
+++ /dev/null
@@ -1 +0,0 @@
-""":term:`NREN` layer 3 core service workflows."""
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
deleted file mode 100644
index 3951924612f0c885f1076193d75d208b687e4821..0000000000000000000000000000000000000000
--- a/gso/workflows/nren_l3_core_service/import_nren_l3_core_service.py
+++ /dev/null
@@ -1,45 +0,0 @@
-"""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/router/promote_p_to_pe.py b/gso/workflows/router/promote_p_to_pe.py
index 68518265637a9f9072006de336c7c17c0ac41b56..16d1ffcc30c7f81fd9b88b07f26f6786c1d094b0 100644
--- a/gso/workflows/router/promote_p_to_pe.py
+++ b/gso/workflows/router/promote_p_to_pe.py
@@ -18,7 +18,7 @@ from gso.products.product_blocks.router import RouterRole
 from gso.products.product_types.router import Router
 from gso.services.lso_client import LSOState, lso_interaction
 from gso.services.subscriptions import get_all_active_sites
-from gso.utils.helpers import generate_inventory_for_active_routers
+from gso.utils.helpers import generate_inventory_for_routers
 from gso.utils.shared_enums import Vendor
 from gso.utils.types.tt_number import TTNumber
 from gso.utils.workflow_steps import (
@@ -85,7 +85,7 @@ def deploy_pe_base_config_dry(subscription: dict[str, Any], tt_number: str, proc
         "subscription": subscription,
         "commit_comment": f"GSO_PROCESS_ID: {process_id} - TT_NUMBER: {tt_number} - deploy PE base config",
         "verb": "deploy_pe_base_config",
-        "pe_router_list": generate_inventory_for_active_routers(RouterRole.PE)["all"]["hosts"],
+        "pe_router_list": generate_inventory_for_routers(RouterRole.PE)["all"]["hosts"],
         "geant_sites": json.loads(json_dumps(get_all_active_sites())),
     }
 
@@ -104,7 +104,7 @@ def deploy_pe_base_config_real(subscription: dict[str, Any], tt_number: str, pro
         "subscription": subscription,
         "commit_comment": f"GSO_PROCESS_ID: {process_id} - TT_NUMBER: {tt_number} - deploy PE base config",
         "verb": "deploy_pe_base_config",
-        "pe_router_list": generate_inventory_for_active_routers(RouterRole.PE)["all"]["hosts"],
+        "pe_router_list": generate_inventory_for_routers(RouterRole.PE)["all"]["hosts"],
         "geant_sites": json.loads(json_dumps(get_all_active_sites())),
     }
 
@@ -142,7 +142,7 @@ def remove_p_from_pe_dry(subscription: dict[str, Any], tt_number: str, process_i
 
     return {
         "playbook_name": "gap_ansible/playbooks/update_ibgp_mesh.yaml",
-        "inventory": generate_inventory_for_active_routers(RouterRole.PE),
+        "inventory": generate_inventory_for_routers(RouterRole.PE),
         "extra_vars": extra_vars,
     }
 
@@ -160,7 +160,7 @@ def remove_p_from_pe_real(subscription: dict[str, Any], tt_number: str, process_
 
     return {
         "playbook_name": "gap_ansible/playbooks/update_ibgp_mesh.yaml",
-        "inventory": generate_inventory_for_active_routers(RouterRole.PE),
+        "inventory": generate_inventory_for_routers(RouterRole.PE),
         "extra_vars": extra_vars,
     }
 
diff --git a/gso/workflows/router/terminate_router.py b/gso/workflows/router/terminate_router.py
index 9bc48f8ce9943692f8c475b1595d33e9964645f7..56344fc2076791c1dc0934ec2b6f6e659b261a8e 100644
--- a/gso/workflows/router/terminate_router.py
+++ b/gso/workflows/router/terminate_router.py
@@ -28,7 +28,7 @@ from gso.services.librenms_client import LibreNMSClient
 from gso.services.lso_client import LSOState, lso_interaction
 from gso.services.netbox_client import NetboxClient
 from gso.settings import load_oss_params
-from gso.utils.helpers import generate_inventory_for_active_routers
+from gso.utils.helpers import generate_inventory_for_routers
 from gso.utils.shared_enums import Vendor
 from gso.utils.types.tt_number import TTNumber
 
@@ -124,7 +124,7 @@ def remove_p_from_all_pe_dry(subscription: Router, tt_number: str, process_id: U
 
     return {
         "playbook_name": "gap_ansible/playbooks/update_ibgp_mesh.yaml",
-        "inventory": generate_inventory_for_active_routers(RouterRole.PE),
+        "inventory": generate_inventory_for_routers(RouterRole.PE),
         "extra_vars": extra_vars,
     }
 
@@ -142,7 +142,7 @@ def remove_p_from_all_pe_real(subscription: Router, tt_number: str, process_id:
 
     return {
         "playbook_name": "gap_ansible/playbooks/update_ibgp_mesh.yaml",
-        "inventory": generate_inventory_for_active_routers(RouterRole.PE),
+        "inventory": generate_inventory_for_routers(RouterRole.PE),
         "extra_vars": extra_vars,
     }
 
@@ -160,9 +160,7 @@ def remove_pe_from_all_pe_dry(subscription: Router, tt_number: str, process_id:
 
     return {
         "playbook_name": "gap_ansible/playbooks/update_ibgp_mesh.yaml",
-        "inventory": generate_inventory_for_active_routers(
-            RouterRole.PE, exclude_routers=[subscription.router.router_fqdn]
-        ),
+        "inventory": generate_inventory_for_routers(RouterRole.PE, exclude_routers=[subscription.router.router_fqdn]),
         "extra_vars": extra_vars,
     }
 
@@ -180,9 +178,7 @@ def remove_pe_from_all_pe_real(subscription: Router, tt_number: str, process_id:
 
     return {
         "playbook_name": "gap_ansible/playbooks/update_ibgp_mesh.yaml",
-        "inventory": generate_inventory_for_active_routers(
-            RouterRole.PE, exclude_routers=[subscription.router.router_fqdn]
-        ),
+        "inventory": generate_inventory_for_routers(RouterRole.PE, exclude_routers=[subscription.router.router_fqdn]),
         "extra_vars": extra_vars,
     }
 
@@ -200,7 +196,7 @@ def remove_pe_from_all_p_dry(subscription: Router, tt_number: str, process_id: U
 
     return {
         "playbook_name": "gap_ansible/playbooks/update_ibgp_mesh.yaml",
-        "inventory": generate_inventory_for_active_routers(RouterRole.P),
+        "inventory": generate_inventory_for_routers(RouterRole.P),
         "extra_vars": extra_vars,
     }
 
@@ -218,7 +214,7 @@ def remove_pe_from_all_p_real(subscription: Router, tt_number: str, process_id:
 
     return {
         "playbook_name": "gap_ansible/playbooks/update_ibgp_mesh.yaml",
-        "inventory": generate_inventory_for_active_routers(RouterRole.P),
+        "inventory": generate_inventory_for_routers(RouterRole.P),
         "extra_vars": extra_vars,
     }
 
diff --git a/gso/workflows/router/update_ibgp_mesh.py b/gso/workflows/router/update_ibgp_mesh.py
index fc071ab8fc657cd2f5fb001a165d84175c0a513b..7adea41cf5815413248c68a7670dc2840bc21734 100644
--- a/gso/workflows/router/update_ibgp_mesh.py
+++ b/gso/workflows/router/update_ibgp_mesh.py
@@ -17,7 +17,7 @@ from gso.products.product_types.router import Router
 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.helpers import generate_inventory_for_routers
 from gso.utils.shared_enums import SNMPVersion
 from gso.utils.types.tt_number import TTNumber
 from gso.utils.workflow_steps import (
@@ -79,7 +79,7 @@ def add_p_to_mesh_dry(subscription: dict[str, Any], tt_number: str, process_id:
 
     return {
         "playbook_name": "gap_ansible/playbooks/update_ibgp_mesh.yaml",
-        "inventory": generate_inventory_for_active_routers(RouterRole.PE),
+        "inventory": generate_inventory_for_routers(RouterRole.PE),
         "extra_vars": extra_vars,
     }
 
@@ -96,7 +96,7 @@ def add_p_to_mesh_real(subscription: dict[str, Any], tt_number: str, process_id:
 
     return {
         "playbook_name": "gap_ansible/playbooks/update_ibgp_mesh.yaml",
-        "inventory": generate_inventory_for_active_routers(RouterRole.PE),
+        "inventory": generate_inventory_for_routers(RouterRole.PE),
         "extra_vars": extra_vars,
     }
 
@@ -107,7 +107,7 @@ def add_all_pe_to_p_dry(subscription: dict[str, Any]) -> LSOState:
     extra_vars = {
         "dry_run": True,
         "subscription": subscription,
-        "pe_router_list": generate_inventory_for_active_routers(RouterRole.PE),
+        "pe_router_list": generate_inventory_for_routers(RouterRole.PE),
         "verb": "add_pe_to_p",
     }
 
@@ -124,7 +124,7 @@ def add_all_pe_to_p_real(subscription: dict[str, Any], tt_number: str, process_i
     extra_vars = {
         "dry_run": False,
         "subscription": subscription,
-        "pe_router_list": generate_inventory_for_active_routers(RouterRole.PE),
+        "pe_router_list": generate_inventory_for_routers(RouterRole.PE),
         "commit_comment": f"GSO_PROCESS_ID: {process_id} - TT_NUMBER: {tt_number} - Update iBGP mesh",
         "verb": "add_pe_to_p",
     }
diff --git a/gso/workflows/router/validate_router.py b/gso/workflows/router/validate_router.py
index 2403c52f6813ad12517ae2ccf2e32b134ced6d03..125db3171ee52f34029bf6d8b7e01c26034b4212 100644
--- a/gso/workflows/router/validate_router.py
+++ b/gso/workflows/router/validate_router.py
@@ -16,7 +16,7 @@ from gso.services.kentik_client import KentikClient
 from gso.services.librenms_client import LibreNMSClient
 from gso.services.lso_client import LSOState, anonymous_lso_interaction
 from gso.services.netbox_client import NetboxClient
-from gso.utils.helpers import generate_inventory_for_active_routers
+from gso.utils.helpers import generate_inventory_for_routers
 from gso.utils.shared_enums import Vendor
 
 
@@ -52,14 +52,14 @@ def check_netbox_entry_exists(subscription: Router) -> None:
     client.get_device_by_name(subscription.router.router_fqdn)
 
 
-@step("Verify BGP configuration on P router")
+@step("Verify P BGP P-ONLY neighbors")
 def verify_p_ibgp(subscription: dict[str, Any]) -> LSOState:
-    """Perform a dry run of adding the list of all PE routers to the new P router."""
+    """Verify PE neighbors in P-ONLY group on a P router."""
     extra_vars = {
         "dry_run": True,
         "subscription": subscription,
-        "pe_router_list": generate_inventory_for_active_routers(RouterRole.PE)["all"]["hosts"],
-        "verb": "verify_p_ibgp",
+        "pe_router_list": generate_inventory_for_routers(RouterRole.PE)["all"]["hosts"],
+        "verb": "add_pe_to_p",
         "is_verification_workflow": "true",
     }
 
@@ -70,6 +70,60 @@ def verify_p_ibgp(subscription: dict[str, Any]) -> LSOState:
     }
 
 
+@step("Verify PE BGP internal mesh neighbors")
+def verify_pe_mesh_in_pe(subscription: dict[str, Any]) -> LSOState:
+    """Verify PE internal mesh neighbors on a PE router."""
+    extra_vars = {
+        "dry_run": True,
+        "subscription": subscription,
+        "verb": "add_pe_mesh_to_pe",
+        "pe_router_list": generate_inventory_for_routers(
+            router_role=RouterRole.PE, exclude_routers=[subscription["router"]["router_fqdn"]]
+        )["all"]["hosts"],
+        "is_verification_workflow": "true",
+    }
+
+    if not extra_vars["pe_router_list"]:
+        return {
+            "playbook_name": "",
+            "inventory": {"all": {"hosts": {}}},
+            "extra_vars": {},
+        }
+
+    return {
+        "playbook_name": "gap_ansible/playbooks/update_ibgp_mesh.yaml",
+        "inventory": {"all": {"hosts": {subscription["router"]["router_fqdn"]: None}}},
+        "extra_vars": extra_vars,
+    }
+
+
+@step("Verify PE BGP P-ONLY neighbors")
+def verify_all_p_in_pe(subscription: dict[str, Any]) -> LSOState:
+    """Verify P neighbors in P-ONLY group on a PE router."""
+    extra_vars = {
+        "dry_run": True,
+        "subscription": subscription,
+        "verb": "add_all_p_to_pe",
+        "p_router_list": generate_inventory_for_routers(
+            router_role=RouterRole.P, exclude_routers=[subscription["router"]["router_fqdn"]]
+        )["all"]["hosts"],
+        "is_verification_workflow": "true",
+    }
+
+    if not extra_vars["p_router_list"]:
+        return {
+            "playbook_name": "",
+            "inventory": {"all": {"hosts": {}}},
+            "extra_vars": {},
+        }
+
+    return {
+        "playbook_name": "gap_ansible/playbooks/update_ibgp_mesh.yaml",
+        "inventory": {"all": {"hosts": {subscription["router"]["router_fqdn"]: None}}},
+        "extra_vars": extra_vars,
+    }
+
+
 @step("Verify correct LibreNMS entry")
 def check_librenms_entry_exists(subscription: Router) -> None:
     """Validate the LibreNMS entry for a Router.
@@ -125,6 +179,7 @@ def validate_router() -> StepList:
     """
     is_juniper_router = conditional(lambda state: state["subscription"]["router"]["vendor"] == Vendor.JUNIPER)
     is_pe_router = conditional(lambda state: state["subscription"]["router"]["router_role"] == RouterRole.PE)
+    is_p_router = conditional(lambda state: state["subscription"]["router"]["router_role"] == RouterRole.P)
 
     return (
         begin
@@ -137,7 +192,9 @@ def validate_router() -> StepList:
         >> check_librenms_entry_exists
         >> is_pe_router(check_kentik_entry_exists)
         >> anonymous_lso_interaction(verify_base_config)
-        >> anonymous_lso_interaction(verify_p_ibgp)
+        >> is_p_router(anonymous_lso_interaction(verify_p_ibgp))
+        >> is_pe_router(anonymous_lso_interaction(verify_pe_mesh_in_pe))
+        >> is_pe_router(anonymous_lso_interaction(verify_all_p_in_pe))
         >> resync
         >> done
     )
diff --git a/setup.py b/setup.py
index bc1d3bd6177a98ef0ddd71131bbe1b6489297d97..db9de0e1c69a58c958cf8a2b73fbfa3591f07ba9 100644
--- a/setup.py
+++ b/setup.py
@@ -4,7 +4,7 @@ from setuptools import find_packages, setup
 
 setup(
     name="geant-service-orchestrator",
-    version="2.25",
+    version="2.26",
     author="GÉANT Orchestration and Automation Team",
     author_email="goat@geant.org",
     description="GÉANT Service Orchestrator",
diff --git a/test/cli/test_imports.py b/test/cli/test_imports.py
index 62cf1a8d01ed5e0b41dc5f2eb518728f72b546b5..17a1bc13fa3deb71efe359e10c26c82cbb016966 100644
--- a/test/cli/test_imports.py
+++ b/test/cli/test_imports.py
@@ -7,8 +7,9 @@ import pytest
 from gso.cli.imports import (
     import_edge_port,
     import_iptrunks,
+    import_l3_core_service,
     import_lan_switch_interconnect,
-    import_nren_l3_core_service,
+    import_layer_2_circuit_service,
     import_office_routers,
     import_opengear,
     import_routers,
@@ -19,12 +20,14 @@ from gso.cli.imports import (
 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.layer_2_circuit import Layer2CircuitType
 from gso.products.product_blocks.router import RouterRole
 from gso.products.product_blocks.site import SiteTier
 from gso.products.product_blocks.switch import SwitchModel
+from gso.products.product_types.layer_2_circuit import Layer2CircuitServiceType
 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.helpers import generate_unique_vc_id, iso_from_ipv4
 from gso.utils.shared_enums import Vendor
 from gso.utils.types.interfaces import PhysicalPortCapacity
 from gso.utils.types.ip_address import AddressSpace
@@ -293,9 +296,9 @@ def edge_port_data(temp_file, faker, router_subscription_factory, partner_factor
 
 
 @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 = {
+def l3_core_service_data(temp_file, faker, partner_factory, edge_port_subscription_factory):
+    def _l3_core_service_data(**kwargs):
+        l3_core_service_data = {
             "partner": partner_factory()["name"],
             "service_type": "IMPORTED IAS",
             "service_binding_ports": [
@@ -308,11 +311,21 @@ def nren_l3_core_service_data(temp_file, faker, partner_factory, edge_port_subsc
                     "ipv4_mask": faker.ipv4_netmask(),
                     "ipv6_address": faker.ipv6(),
                     "ipv6_mask": faker.ipv6_netmask(),
+                    "v4_bfd_settings": {
+                        "bfd_enabled": True,
+                        "bfd_interval_rx": faker.pyint(),
+                        "bfd_interval_tx": faker.pyint(),
+                        "bfd_multiplier": faker.pyint(),
+                    },
+                    "v6_bfd_settings": {
+                        "bfd_enabled": True,
+                        "bfd_interval_rx": faker.pyint(),
+                        "bfd_interval_tx": faker.pyint(),
+                        "bfd_multiplier": faker.pyint(),
+                    },
                     "bgp_peers": [
                         {
                             "bfd_enabled": True,
-                            "bfd_interval": faker.pyint(),
-                            "bfd_multiplier": faker.pyint(),
                             "has_custom_policies": True,
                             "authentication_key": faker.password(),
                             "multipath_enabled": False,
@@ -325,8 +338,6 @@ def nren_l3_core_service_data(temp_file, faker, partner_factory, edge_port_subsc
                         },
                         {
                             "bfd_enabled": True,
-                            "bfd_interval": faker.pyint(),
-                            "bfd_multiplier": faker.pyint(),
                             "has_custom_policies": True,
                             "authentication_key": faker.password(),
                             "multipath_enabled": False,
@@ -351,8 +362,6 @@ def nren_l3_core_service_data(temp_file, faker, partner_factory, edge_port_subsc
                     "bgp_peers": [
                         {
                             "bfd_enabled": True,
-                            "bfd_interval": faker.pyint(),
-                            "bfd_multiplier": faker.pyint(),
                             "has_custom_policies": True,
                             "authentication_key": faker.password(),
                             "multipath_enabled": False,
@@ -365,8 +374,6 @@ def nren_l3_core_service_data(temp_file, faker, partner_factory, edge_port_subsc
                         },
                         {
                             "bfd_enabled": True,
-                            "bfd_interval": faker.pyint(),
-                            "bfd_multiplier": faker.pyint(),
                             "has_custom_policies": True,
                             "authentication_key": faker.password(),
                             "multipath_enabled": False,
@@ -378,15 +385,57 @@ def nren_l3_core_service_data(temp_file, faker, partner_factory, edge_port_subsc
                             "rtbh_enabled": True,
                         },
                     ],
+                    "v4_bfd_settings": {
+                        "bfd_enabled": True,
+                        "bfd_interval_rx": faker.pyint(),
+                        "bfd_interval_tx": faker.pyint(),
+                        "bfd_multiplier": faker.pyint(),
+                    },
+                    "v6_bfd_settings": {
+                        "bfd_enabled": True,
+                        "bfd_interval_rx": faker.pyint(),
+                        "bfd_interval_tx": faker.pyint(),
+                        "bfd_multiplier": faker.pyint(),
+                    },
                 },
             ],
         }
-        nren_l3_core_service_data.update(**kwargs)
+        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}
+        temp_file.write_text(json.dumps([l3_core_service_data]))
+        return {"path": str(temp_file), "data": l3_core_service_data}
 
-    return _nren_l3_core_service_data
+    return _l3_core_service_data
+
+
+@pytest.fixture()
+def layer_2_circuit_data(temp_file, faker, partner_factory, edge_port_subscription_factory):
+    def _layer_2_circuit_data(**kwargs):
+        layer_2_circuit_input_data = {
+            "partner": partner_factory()["name"],
+            "service_type": Layer2CircuitServiceType.GEANT_PLUS,
+            "geant_sid": faker.geant_sid(),
+            "vc_id": generate_unique_vc_id(),
+            "layer_2_circuit_side_a": {
+                "edge_port": edge_port_subscription_factory(),
+                "vlan_id": faker.vlan_id(),
+            },
+            "layer_2_circuit_side_b": {
+                "edge_port": edge_port_subscription_factory(),
+                "vlan_id": faker.vlan_id(),
+            },
+            "layer_2_circuit_type": Layer2CircuitType.TAGGED,
+            "vlan_range_lower_bound": faker.vlan_id(),
+            "vlan_range_upper_bound": faker.vlan_id(),
+            "policer_enabled": False,
+        }
+        layer_2_circuit_input_data.update(**kwargs)
+
+        temp_file.write_text(json.dumps([layer_2_circuit_input_data]))
+
+        return {"path": str(temp_file), "data": layer_2_circuit_input_data}
+
+    return _layer_2_circuit_data
 
 
 ###########
@@ -621,18 +670,16 @@ def test_import_edge_port_with_invalid_partner(mock_start_process, mock_sleep, e
 
 @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"])
+def test_import_l3_core_service_success(mock_start_process, mock_sleep, l3_core_service_data, capfd):
+    import_l3_core_service(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"])
+def test_import_l3_core_service_with_invalid_partner(mock_start_process, mock_sleep, l3_core_service_data, capfd):
+    broken_data = l3_core_service_data(partner="INVALID")
+    import_l3_core_service(broken_data["path"])
 
     captured_output, _ = capfd.readouterr()
     assert "Partner INVALID not found" in captured_output
@@ -641,11 +688,11 @@ def test_import_nren_l3_core_service_with_invalid_partner(
 
 @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
+def test_import_l3_core_service_with_invalid_edge_port(
+    mock_start_process, mock_sleep, faker, l3_core_service_data, edge_port_subscription_factory, capfd
 ):
     fake_uuid = faker.uuid4()
-    broken_data = nren_l3_core_service_data(
+    broken_data = l3_core_service_data(
         service_binding_ports=[
             {
                 "edge_port": fake_uuid,
@@ -674,6 +721,18 @@ def test_import_nren_l3_core_service_with_invalid_edge_port(
                         "rtbh_enabled": True,
                     },
                 ],
+                "v4_bfd_settings": {
+                    "bfd_enabled": True,
+                    "bfd_interval_rx": faker.pyint(),
+                    "bfd_interval_tx": faker.pyint(),
+                    "bfd_multiplier": faker.pyint(),
+                },
+                "v6_bfd_settings": {
+                    "bfd_enabled": True,
+                    "bfd_interval_rx": faker.pyint(),
+                    "bfd_interval_tx": faker.pyint(),
+                    "bfd_multiplier": faker.pyint(),
+                },
             },
             {
                 "edge_port": edge_port_subscription_factory(),
@@ -702,11 +761,44 @@ def test_import_nren_l3_core_service_with_invalid_edge_port(
                         "rtbh_enabled": True,
                     },
                 ],
+                "v4_bfd_settings": {
+                    "bfd_enabled": True,
+                    "bfd_interval_rx": faker.pyint(),
+                    "bfd_interval_tx": faker.pyint(),
+                    "bfd_multiplier": faker.pyint(),
+                },
+                "v6_bfd_settings": {
+                    "bfd_enabled": True,
+                    "bfd_interval_rx": faker.pyint(),
+                    "bfd_interval_tx": faker.pyint(),
+                    "bfd_multiplier": faker.pyint(),
+                },
             },
         ]
     )
-    import_nren_l3_core_service(broken_data["path"])
+    import_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
+
+
+@patch("gso.cli.imports.time.sleep")
+@patch("gso.cli.imports.start_process")
+def test_import_layer_2_circuit_success(mock_start_process, mock_sleep, layer_2_circuit_data):
+    import_layer_2_circuit_service(layer_2_circuit_data()["path"])
+    assert mock_start_process.call_count == 1
+    assert mock_sleep.call_count == 1
+
+
+@patch("gso.cli.imports.time.sleep")
+@patch("gso.cli.imports.start_process")
+def test_import_layer_2_circuit_with_invalid_partner(
+    mock_start_process, mock_sleep, layer_2_circuit_data, edge_port_subscription_factory, capfd, faker
+):
+    broken_data = layer_2_circuit_data(partner="INVALID")
+    import_layer_2_circuit_service(broken_data["path"])
+
+    captured_output, _ = capfd.readouterr()
+    assert "Partner INVALID not found" in captured_output
+    assert mock_start_process.call_count == 0
diff --git a/test/conftest.py b/test/conftest.py
index 8c8d246c5bd97c16ef7f8db09752c2dca0ffd804..ce44b074025e788e8820ef5a5c8de10b7fb87c8a 100644
--- a/test/conftest.py
+++ b/test/conftest.py
@@ -35,22 +35,7 @@ from urllib3_mock import Responses
 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,
-    lan_switch_interconnect_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,
-    switch_subscription_factory,
-)
+from test.fixtures import *  # noqa: F403
 
 logging.getLogger("faker.factory").setLevel(logging.WARNING)
 
@@ -138,6 +123,11 @@ class FakerProvider(BaseProvider):
     def vlan_id(self) -> int:
         return self.generator.random_int(min=1, max=4095)
 
+    def bandwidth(self) -> str:
+        bandwidth_value = self.generator.random_int(1, 1000)
+        unit = self.generator.random_choices(elements=("K", "M", "G", "T"))[0]
+        return f"{bandwidth_value}{unit}"
+
 
 @pytest.fixture(scope="session")
 def faker() -> Faker:
diff --git a/test/fixtures.py b/test/fixtures.py
index e2b905c1736879af3087c31d09bc3a605130e43b..da10b0d1a777edfd028878ecb197233aa7e072d5 100644
--- a/test/fixtures.py
+++ b/test/fixtures.py
@@ -5,9 +5,9 @@ from uuid import uuid4
 import pytest
 from orchestrator import step, workflow
 from orchestrator.config.assignee import Assignee
+from orchestrator.forms import FormPage
 from orchestrator.types import UUIDstr
 from orchestrator.workflow import done, init, inputstep
-from pydantic_forms.core import FormPage
 from pydantic_forms.types import FormGenerator
 from pydantic_forms.validators import Choice
 
diff --git a/test/fixtures/__init__.py b/test/fixtures/__init__.py
index 3f183fb2e8778f758d7bba96a459d1df29ac717b..8ecda8a79c736087df8c77019e7814ff72c3b2bf 100644
--- a/test/fixtures/__init__.py
+++ b/test/fixtures/__init__.py
@@ -1,12 +1,14 @@
 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.lan_switch_interconnect_fixtures import lan_switch_interconnect_subscription_factory
-from test.fixtures.nren_l3_core_service_fixtures import (
+from test.fixtures.l3_core_service_fixtures import (
+    access_port_factory,
+    bfd_settings_factory,
     bgp_session_subscription_factory,
-    nren_access_port_factory,
-    nren_l3_core_service_subscription_factory,
+    l3_core_service_subscription_factory,
     service_binding_port_factory,
 )
+from test.fixtures.lan_switch_interconnect_fixtures import lan_switch_interconnect_subscription_factory
+from test.fixtures.layer_2_circuit_fixtures import layer_2_circuit_subscription_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
@@ -15,13 +17,15 @@ from test.fixtures.super_pop_switch_fixtures import super_pop_switch_subscriptio
 from test.fixtures.switch_fixtures import switch_subscription_factory
 
 __all__ = [
+    "access_port_factory",
+    "bfd_settings_factory",
     "bgp_session_subscription_factory",
     "edge_port_subscription_factory",
     "iptrunk_side_subscription_factory",
     "iptrunk_subscription_factory",
+    "l3_core_service_subscription_factory",
     "lan_switch_interconnect_subscription_factory",
-    "nren_access_port_factory",
-    "nren_l3_core_service_subscription_factory",
+    "layer_2_circuit_subscription_factory",
     "office_router_subscription_factory",
     "opengear_subscription_factory",
     "router_subscription_factory",
diff --git a/test/fixtures/iptrunk_fixtures.py b/test/fixtures/iptrunk_fixtures.py
index 70ccdeb547d75ae1fc605b9c1f9e9dfb4bafa6ca..5044a2e5904c92023e264e9c5f40cac8a82cb773 100644
--- a/test/fixtures/iptrunk_fixtures.py
+++ b/test/fixtures/iptrunk_fixtures.py
@@ -25,30 +25,27 @@ def iptrunk_side_subscription_factory(router_subscription_factory, faker):
         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,
+            iptrunk_side_node=Router.from_subscription(
+                iptrunk_side_node or router_subscription_factory(vendor=Vendor.NOKIA)
+            ).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(),
+                ),
+            ],
+            iptrunk_side_ae_members_description=iptrunk_side_ae_members_description or faker.sentence(),
         )
 
     return subscription_create
diff --git a/test/fixtures/l3_core_service_fixtures.py b/test/fixtures/l3_core_service_fixtures.py
new file mode 100644
index 0000000000000000000000000000000000000000..da94298e8ac6b62507ed61147c80233afed84059
--- /dev/null
+++ b/test/fixtures/l3_core_service_fixtures.py
@@ -0,0 +1,230 @@
+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 pydantic import NonNegativeInt
+
+from gso.products import ProductName
+from gso.products.product_blocks.bgp_session import BGPSession, IPFamily, IPTypes
+from gso.products.product_blocks.l3_core_service import AccessPort
+from gso.products.product_blocks.service_binding_port import BFDSettings, ServiceBindingPort
+from gso.products.product_types.edge_port import EdgePort
+from gso.products.product_types.l3_core_service import (
+    ImportedL3CoreService,
+    L3CoreServiceInactive,
+    L3CoreServiceType,
+)
+from gso.services import subscriptions
+from gso.utils.shared_enums import APType, SBPType
+from gso.utils.types.ip_address import IPAddress
+
+
+@pytest.fixture()
+def bfd_settings_factory(faker):
+    def create_bfd_settings(
+        bfd_multiplier: int | None = None,
+        bfd_interval: int | None = None,
+        *,
+        bfd_enabled: bool | None = None,
+    ):
+        bfd_enabled = bfd_enabled or faker.boolean()
+        return BFDSettings.new(
+            subscription_id=uuid4(),
+            bfd_enabled=bfd_enabled,
+            bfd_multiplier=bfd_multiplier or (faker.pyint() if bfd_enabled else None),
+            bfd_interval_rx=bfd_interval or (faker.pyint() if bfd_enabled else None),
+            bfd_interval_tx=bfd_interval or (faker.pyint() if bfd_enabled else None),
+        )
+
+    return create_bfd_settings
+
+
+@pytest.fixture()
+def bgp_session_subscription_factory(faker):
+    def create_bgp_session(
+        peer_address: IPAddress | None = None,
+        families: list[IPFamily] | None = None,
+        authentication_key: str | None = None,
+        *,
+        is_multi_hop: bool = False,
+        has_custom_policies: bool = False,
+        multipath_enabled: bool | None = True,
+        prefix_limit: NonNegativeInt | None = None,
+        send_default_route: bool | None = True,
+        is_passive: bool | None = False,
+        rtbh_enabled: bool | None = False,
+        bfd_enabled: bool | None = False,
+        ip_type: IPTypes | None = None,
+    ):
+        return BGPSession.new(
+            subscription_id=uuid4(),
+            peer_address=peer_address or faker.ipv4(),
+            bfd_enabled=bfd_enabled if bfd_enabled else faker.boolean(),
+            families=families or [IPFamily.V4UNICAST],
+            has_custom_policies=has_custom_policies,
+            authentication_key=authentication_key or faker.password(),
+            multipath_enabled=multipath_enabled,
+            prefix_limit=prefix_limit,
+            send_default_route=send_default_route,
+            is_multi_hop=is_multi_hop,
+            rtbh_enabled=rtbh_enabled,
+            is_passive=is_passive,
+            ip_type=ip_type if ip_type else faker.random_element(elements=[IPTypes.IPV4, IPTypes.IPV6]),
+        )
+
+    return create_bgp_session
+
+
+@pytest.fixture()
+def service_binding_port_factory(
+    faker, bgp_session_subscription_factory, edge_port_subscription_factory, bfd_settings_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,
+        v4_bfd_settings: BFDSettings | None = None,
+        v6_bfd_settings: BFDSettings | 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,
+            v4_bfd_settings=v4_bfd_settings or bfd_settings_factory(),
+            v6_bfd_settings=v6_bfd_settings or bfd_settings_factory(),
+        )
+
+    return create_service_binding_port
+
+
+@pytest.fixture()
+def access_port_factory(faker, service_binding_port_factory):
+    def create_access_port(
+        ap_type: APType | None = None,
+        service_binding_port: ServiceBindingPort | None = None,
+    ):
+        return AccessPort.new(
+            subscription_id=uuid4(),
+            ap_type=ap_type or random.choice(list(APType)),  # noqa: S311
+            sbp=service_binding_port or service_binding_port_factory(),
+        )
+
+    return create_access_port
+
+
+@pytest.fixture()
+def l3_core_service_subscription_factory(
+    faker,
+    partner_factory,
+    access_port_factory,
+):
+    def create_l3_core_service_subscription(
+        l3_core_service_type: L3CoreServiceType,
+        description=None,
+        partner: dict | None = None,
+        ap_list: list[AccessPort] | None = None,
+        start_date="2023-05-24T00:00:00+00:00",
+        status: SubscriptionLifecycle | None = None,
+    ) -> UUIDstr:
+        partner = partner or partner_factory()
+        match l3_core_service_type:
+            case L3CoreServiceType.GEANT_IP:
+                product_id = subscriptions.get_product_id_by_name(ProductName.GEANT_IP)
+                l3_core_service_subscription = L3CoreServiceInactive.from_product_id(
+                    product_id, customer_id=partner["partner_id"], insync=True
+                )
+            case L3CoreServiceType.IMPORTED_GEANT_IP:
+                product_id = subscriptions.get_product_id_by_name(ProductName.IMPORTED_GEANT_IP)
+                l3_core_service_subscription = ImportedL3CoreService.from_product_id(
+                    product_id, customer_id=partner["partner_id"], insync=True
+                )
+            case L3CoreServiceType.IAS:
+                product_id = subscriptions.get_product_id_by_name(ProductName.IAS)
+                l3_core_service_subscription = L3CoreServiceInactive.from_product_id(
+                    product_id, customer_id=partner["partner_id"], insync=True
+                )
+            case L3CoreServiceType.IMPORTED_IAS:
+                product_id = subscriptions.get_product_id_by_name(ProductName.IMPORTED_IAS)
+                l3_core_service_subscription = ImportedL3CoreService.from_product_id(
+                    product_id, customer_id=partner["partner_id"], insync=True
+                )
+            case L3CoreServiceType.GWS:
+                product_id = subscriptions.get_product_id_by_name(ProductName.GWS)
+                l3_core_service_subscription = L3CoreServiceInactive.from_product_id(
+                    product_id, customer_id=partner["partner_id"], insync=True
+                )
+            case L3CoreServiceType.IMPORTED_GWS:
+                product_id = subscriptions.get_product_id_by_name(ProductName.IMPORTED_GWS)
+                l3_core_service_subscription = ImportedL3CoreService.from_product_id(
+                    product_id, customer_id=partner["partner_id"], insync=True
+                )
+            case L3CoreServiceType.LHCONE:
+                product_id = subscriptions.get_product_id_by_name(ProductName.LHCONE)
+                l3_core_service_subscription = L3CoreServiceInactive.from_product_id(
+                    product_id, customer_id=partner["partner_id"], insync=True
+                )
+            case L3CoreServiceType.IMPORTED_LHCONE:
+                product_id = subscriptions.get_product_id_by_name(ProductName.IMPORTED_LHCONE)
+                l3_core_service_subscription = ImportedL3CoreService.from_product_id(
+                    product_id, customer_id=partner["partner_id"], insync=True
+                )
+            case L3CoreServiceType.COPERNICUS:
+                product_id = subscriptions.get_product_id_by_name(ProductName.COPERNICUS)
+                l3_core_service_subscription = L3CoreServiceInactive.from_product_id(
+                    product_id, customer_id=partner["partner_id"], insync=True
+                )
+            case L3CoreServiceType.IMPORTED_COPERNICUS:
+                product_id = subscriptions.get_product_id_by_name(ProductName.IMPORTED_COPERNICUS)
+                l3_core_service_subscription = ImportedL3CoreService.from_product_id(
+                    product_id, customer_id=partner["partner_id"], insync=True
+                )
+            case _:
+                msg = f"L3 Core Service type not found: {l3_core_service_type}"
+                raise ValueError(msg)
+
+        # Default ap_list creation with primary and backup access ports
+        l3_core_service_subscription.l3_core_service.ap_list = ap_list or [
+            access_port_factory(ap_type=APType.PRIMARY),
+            access_port_factory(ap_type=APType.BACKUP),
+        ]
+
+        # Update subscription with description, start date, and status
+        l3_core_service_subscription = SubscriptionModel.from_other_lifecycle(
+            l3_core_service_subscription,
+            SubscriptionLifecycle.ACTIVE,
+        )
+        l3_core_service_subscription.description = description or faker.sentence()
+        l3_core_service_subscription.start_date = start_date
+        l3_core_service_subscription.status = status or SubscriptionLifecycle.ACTIVE
+        l3_core_service_subscription.save()
+
+        db.session.commit()
+
+        return str(l3_core_service_subscription.subscription_id)
+
+    return create_l3_core_service_subscription
diff --git a/test/fixtures/layer_2_circuit_fixtures.py b/test/fixtures/layer_2_circuit_fixtures.py
new file mode 100644
index 0000000000000000000000000000000000000000..28fd88aeb26dfb63d827e1f4741a819633f6af5c
--- /dev/null
+++ b/test/fixtures/layer_2_circuit_fixtures.py
@@ -0,0 +1,124 @@
+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.layer_2_circuit import Layer2CircuitSideBlockInactive, Layer2CircuitType
+from gso.products.product_blocks.service_binding_port import ServiceBindingPortInactive
+from gso.products.product_types.edge_port import EdgePort
+from gso.products.product_types.layer_2_circuit import (
+    ImportedLayer2CircuitInactive,
+    Layer2CircuitInactive,
+    Layer2CircuitServiceType,
+)
+from gso.services import subscriptions
+from gso.utils.helpers import generate_unique_vc_id
+from gso.utils.shared_enums import SBPType
+from gso.utils.types.interfaces import BandwidthString
+from gso.utils.types.virtual_identifiers import VLAN_ID
+
+
+@pytest.fixture()
+def layer_2_circuit_subscription_factory(faker, geant_partner, edge_port_subscription_factory):
+    def create_subscription(
+        description: str | None = None,
+        partner: dict | None = None,
+        status: SubscriptionLifecycle | None = None,
+        start_date: str | None = "2024-01-01T10:20:30+01:02",
+        layer_2_circuit_service_type: Layer2CircuitServiceType | None = None,
+        layer_2_circuit_type: Layer2CircuitType = Layer2CircuitType.TAGGED,
+        vlan_range_lower_bound: VLAN_ID | None = None,
+        vlan_range_upper_bound: VLAN_ID | None = None,
+        policer_bandwidth: BandwidthString | None = None,
+        policer_burst_rate: BandwidthString | None = None,
+        layer_2_circuit_side_a_edgeport: UUIDstr | None = None,
+        vlan_id_side_a: VLAN_ID | None = None,
+        layer_2_circuit_side_b_edgeport: UUIDstr | None = None,
+        vlan_id_side_b: VLAN_ID | None = None,
+        geant_sid: str | None = None,
+        *,
+        policer_enabled: bool = False,
+    ) -> UUIDstr:
+        # Assign default partner if none provided
+        if partner is None:
+            partner = geant_partner
+
+        # Select subscription type based on service type
+        match layer_2_circuit_service_type:
+            case Layer2CircuitServiceType.GEANT_PLUS:
+                product_id = subscriptions.get_product_id_by_name(ProductName.GEANT_PLUS)
+                subscription = Layer2CircuitInactive.from_product_id(
+                    product_id, customer_id=partner["partner_id"], insync=True
+                )
+            case Layer2CircuitServiceType.IMPORTED_GEANT_PLUS:
+                product_id = subscriptions.get_product_id_by_name(ProductName.IMPORTED_GEANT_PLUS)
+                subscription = ImportedLayer2CircuitInactive.from_product_id(
+                    product_id, customer_id=partner["partner_id"], insync=True
+                )
+            case Layer2CircuitServiceType.EXPRESSROUTE:
+                product_id = subscriptions.get_product_id_by_name(ProductName.EXPRESSROUTE)
+                subscription = Layer2CircuitInactive.from_product_id(
+                    product_id, customer_id=partner["partner_id"], insync=True
+                )
+            case Layer2CircuitServiceType.IMPORTED_EXPRESSROUTE:
+                product_id = subscriptions.get_product_id_by_name(ProductName.IMPORTED_EXPRESSROUTE)
+                subscription = ImportedLayer2CircuitInactive.from_product_id(
+                    product_id, customer_id=partner["partner_id"], insync=True
+                )
+            case _:
+                err = ValueError(f"Layer 2 Circuit Service type not found: {layer_2_circuit_service_type}")
+                raise ValueError(err)
+
+        layer_2_circuit_sides = []
+        for edge_port, vlan_id in [
+            (layer_2_circuit_side_a_edgeport or edge_port_subscription_factory(), vlan_id_side_a or faker.vlan_id()),
+            (layer_2_circuit_side_b_edgeport or edge_port_subscription_factory(), vlan_id_side_b or faker.vlan_id()),
+        ]:
+            sbp = ServiceBindingPortInactive.new(
+                uuid4(),
+                edge_port=EdgePort.from_subscription(edge_port).edge_port,
+                sbp_type=SBPType.L2,
+                vlan_id=vlan_id,
+                geant_sid=geant_sid or faker.geant_sid(),
+                is_tagged=layer_2_circuit_type == Layer2CircuitType.TAGGED,
+                custom_firewall_filters=False,
+            )
+            layer_2_circuit_side = Layer2CircuitSideBlockInactive.new(uuid4(), sbp=sbp)
+            layer_2_circuit_sides.append(layer_2_circuit_side)
+
+        subscription.layer_2_circuit.layer_2_circuit_sides = layer_2_circuit_sides
+        subscription.layer_2_circuit.virtual_circuit_id = generate_unique_vc_id()
+        subscription.layer_2_circuit.layer_2_circuit_type = layer_2_circuit_type
+        if layer_2_circuit_type == Layer2CircuitType.TAGGED:
+            subscription.layer_2_circuit.vlan_range_lower_bound = vlan_range_lower_bound or faker.vlan_id()
+            subscription.layer_2_circuit.vlan_range_upper_bound = vlan_range_upper_bound or faker.vlan_id()
+        else:
+            subscription.layer_2_circuit.vlan_range_lower_bound = None
+            subscription.layer_2_circuit.vlan_range_upper_bound = None
+
+        subscription.layer_2_circuit.policer_enabled = policer_enabled
+        if policer_enabled:
+            subscription.layer_2_circuit.bandwidth = policer_bandwidth or faker.bandwidth()
+            subscription.layer_2_circuit.policer_burst_rate = policer_burst_rate or faker.bandwidth()
+        else:
+            subscription.layer_2_circuit.bandwidth = None
+            subscription.layer_2_circuit.policer_burst_rate = None
+        subscription.description = description or (
+            f"{subscription.product.name} - " f"{subscription.layer_2_circuit.virtual_circuit_id}"
+        )
+
+        subscription = SubscriptionModel.from_other_lifecycle(subscription, SubscriptionLifecycle.ACTIVE)
+        subscription.insync = True
+        subscription.start_date = start_date
+        if status:
+            subscription.status = status
+
+        subscription.save()
+        db.session.commit()
+
+        return str(subscription.subscription_id)
+
+    return create_subscription
diff --git a/test/fixtures/nren_l3_core_service_fixtures.py b/test/fixtures/nren_l3_core_service_fixtures.py
deleted file mode 100644
index d0fd069a03b4b969e2fd286aaa7f57f5716f10c7..0000000000000000000000000000000000000000
--- a/test/fixtures/nren_l3_core_service_fixtures.py
+++ /dev/null
@@ -1,173 +0,0 @@
-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/utils/test_helpers.py b/test/utils/test_helpers.py
index 880893b628183ff7a131745ca3be7c80164d15fa..e91133780843a15e5b1cba7466f22dbb0700fe99 100644
--- a/test/utils/test_helpers.py
+++ b/test/utils/test_helpers.py
@@ -8,7 +8,7 @@ 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,
+    generate_inventory_for_routers,
 )
 from gso.utils.shared_enums import Vendor
 from gso.utils.types.tt_number import validate_tt_number
@@ -113,7 +113,7 @@ def test_generate_inventory_for_active_routers_with_single_active_router(router_
             }
         }
     }
-    assert generate_inventory_for_active_routers(RouterRole.P) == expected_result
+    assert generate_inventory_for_routers(RouterRole.P) == expected_result
 
 
 def test_generate_inventory_for_active_routers_with_multiple_routers(router_subscription_factory):
@@ -125,9 +125,9 @@ def test_generate_inventory_for_active_routers_with_multiple_routers(router_subs
     router_subscription_factory(status=SubscriptionLifecycle.TERMINATED)
     router_subscription_factory(status=SubscriptionLifecycle.INITIAL)
     #  Test the generation of inventory for multiple active P routers.
-    inventory = generate_inventory_for_active_routers(RouterRole.P)
+    inventory = generate_inventory_for_routers(RouterRole.P)
     assert len(inventory["all"]["hosts"]) == 5
-    inventory = generate_inventory_for_active_routers(RouterRole.PE)
+    inventory = generate_inventory_for_routers(RouterRole.PE)
     assert len(inventory["all"]["hosts"]) == 3
 
 
@@ -137,5 +137,5 @@ def test_generate_inventory_for_active_routers_with_excluded_router(router_subsc
         router_subscription_factory(router_role=RouterRole.P)
     router = router_subscription_factory(router_role=RouterRole.P)
     excluded_routers = [Router.from_subscription(router).router.router_fqdn]
-    inventory = generate_inventory_for_active_routers(RouterRole.P, exclude_routers=excluded_routers)
+    inventory = generate_inventory_for_routers(RouterRole.P, exclude_routers=excluded_routers)
     assert len(inventory["all"]["hosts"]) == 5  # 6 P routers, the last one is excluded, so 5 P routers are left.
diff --git a/test/workflows/nren_l3_core_service/__init__.py b/test/workflows/l2_circuit/__init__.py
similarity index 100%
rename from test/workflows/nren_l3_core_service/__init__.py
rename to test/workflows/l2_circuit/__init__.py
diff --git a/test/workflows/l2_circuit/test_create_imported_layer_2_circuit.py b/test/workflows/l2_circuit/test_create_imported_layer_2_circuit.py
new file mode 100644
index 0000000000000000000000000000000000000000..0e4e857ef32e9d002cd175176fa1ba7a463ce318
--- /dev/null
+++ b/test/workflows/l2_circuit/test_create_imported_layer_2_circuit.py
@@ -0,0 +1,55 @@
+import pytest
+from orchestrator.types import SubscriptionLifecycle
+
+from gso.products.product_blocks.layer_2_circuit import Layer2CircuitType
+from gso.products.product_types.layer_2_circuit import Layer2Circuit, Layer2CircuitServiceType
+from gso.utils.helpers import generate_unique_vc_id
+from test.workflows import assert_complete, extract_state, run_workflow
+
+
+@pytest.mark.parametrize(
+    "layer_2_service_type", [Layer2CircuitServiceType.GEANT_PLUS, Layer2CircuitServiceType.EXPRESSROUTE]
+)
+def test_create_imported_layer_2_circuit_success(
+    faker, partner_factory, edge_port_subscription_factory, layer_2_service_type
+):
+    partner = partner_factory()
+    edge_port_a = edge_port_subscription_factory(partner=partner)
+    edge_port_b = edge_port_subscription_factory(partner=partner)
+    policer_enabled = faker.boolean()
+    creation_form_input_data = [
+        {
+            "service_type": layer_2_service_type,
+            "partner": partner["name"],
+            "layer_2_circuit_type": Layer2CircuitType.TAGGED,
+            "policer_enabled": policer_enabled,
+            "vlan_range_lower_bound": faker.vlan_id(),
+            "vlan_range_upper_bound": faker.vlan_id(),
+            "vc_id": generate_unique_vc_id(),
+            "policer_bandwidth": faker.bandwidth() if policer_enabled else None,
+            "policer_burst_rate": faker.bandwidth() if policer_enabled else None,
+            "geant_sid": faker.geant_sid(),
+            "layer_2_circuit_side_a": {"edge_port": edge_port_a, "vlan_id": faker.vlan_id()},
+            "layer_2_circuit_side_b": {"edge_port": edge_port_b, "vlan_id": faker.vlan_id()},
+        }
+    ]
+
+    result, _, _ = run_workflow("create_imported_layer_2_circuit", creation_form_input_data)
+    state = extract_state(result)
+    assert_complete(result)
+    subscription = Layer2Circuit.from_subscription(state["subscription_id"])
+    assert subscription.status == SubscriptionLifecycle.ACTIVE
+    assert subscription.layer_2_circuit.layer_2_circuit_type == Layer2CircuitType.TAGGED
+    assert subscription.layer_2_circuit.virtual_circuit_id == creation_form_input_data[0]["vc_id"]
+    assert len(subscription.layer_2_circuit.layer_2_circuit_sides) == 2
+    assert subscription.layer_2_circuit.layer_2_circuit_sides[0].sbp.is_tagged is True
+    assert (
+        subscription.layer_2_circuit.layer_2_circuit_sides[0].sbp.geant_sid == creation_form_input_data[0]["geant_sid"]
+    )
+    assert (
+        str(subscription.layer_2_circuit.layer_2_circuit_sides[0].sbp.edge_port.owner_subscription_id)
+        == creation_form_input_data[0]["layer_2_circuit_side_a"]["edge_port"]
+    )
+    assert subscription.layer_2_circuit.policer_enabled == policer_enabled
+    assert subscription.layer_2_circuit.bandwidth == creation_form_input_data[0]["policer_bandwidth"]
+    assert subscription.layer_2_circuit.policer_burst_rate == creation_form_input_data[0]["policer_burst_rate"]
diff --git a/test/workflows/l2_circuit/test_create_layer_2_circuit.py b/test/workflows/l2_circuit/test_create_layer_2_circuit.py
new file mode 100644
index 0000000000000000000000000000000000000000..c0d1b2df6aae5014f24e0fe4531a4d52a5007411
--- /dev/null
+++ b/test/workflows/l2_circuit/test_create_layer_2_circuit.py
@@ -0,0 +1,71 @@
+import pytest
+from orchestrator.types import SubscriptionLifecycle
+
+from gso.products import ProductName
+from gso.products.product_blocks.layer_2_circuit import Layer2CircuitType
+from gso.products.product_types.layer_2_circuit import Layer2Circuit
+from gso.services.subscriptions import get_product_id_by_name
+from test.workflows import assert_complete, extract_state, run_workflow
+
+
+@pytest.fixture()
+def layer_2_circuit_input(faker, partner_factory, edge_port_subscription_factory, layer_2_circuit_service_type):
+    partner = partner_factory()
+    product_id = get_product_id_by_name(layer_2_circuit_service_type)
+    edge_port_a = edge_port_subscription_factory(partner=partner)
+    edge_port_b = edge_port_subscription_factory(partner=partner)
+    policer_enabled = faker.boolean()
+    return [
+        {"product": product_id},
+        {
+            "tt_number": faker.tt_number(),
+            "partner": partner["partner_id"],
+            "layer_2_circuit_type": Layer2CircuitType.TAGGED,
+            "policer_enabled": policer_enabled,
+        },
+        {
+            "vlan_range_lower_bound": faker.vlan_id(),
+            "vlan_range_upper_bound": faker.vlan_id(),
+            "policer_bandwidth": faker.bandwidth() if policer_enabled else None,
+            "policer_burst_rate": faker.bandwidth() if policer_enabled else None,
+            "geant_sid": faker.geant_sid(),
+            "layer_2_circuit_side_a": {"edge_port": edge_port_a, "vlan_id": faker.vlan_id()},
+            "layer_2_circuit_side_b": {"edge_port": edge_port_b, "vlan_id": faker.vlan_id()},
+        },
+    ]
+
+
+@pytest.mark.parametrize("layer_2_circuit_service_type", [ProductName.GEANT_PLUS, ProductName.EXPRESSROUTE])
+@pytest.mark.workflow()
+def test_create_layer_2_circuit_success(
+    layer_2_circuit_service_type,
+    layer_2_circuit_input,
+    faker,
+    partner_factory,
+    data_config_filename,
+):
+    result, _, _ = run_workflow("create_layer_2_circuit", layer_2_circuit_input)
+    assert_complete(result)
+    state = extract_state(result)
+    subscription = Layer2Circuit.from_subscription(state["subscription_id"])
+    assert subscription.status == SubscriptionLifecycle.ACTIVE
+    assert subscription.layer_2_circuit.virtual_circuit_id is not None
+    assert len(subscription.layer_2_circuit.layer_2_circuit_sides) == 2
+    assert (
+        str(subscription.layer_2_circuit.layer_2_circuit_sides[0].sbp.edge_port.owner_subscription_id)
+        == layer_2_circuit_input[2]["layer_2_circuit_side_a"]["edge_port"]
+    )
+    assert subscription.layer_2_circuit.layer_2_circuit_sides[0].sbp.is_tagged is True
+    assert subscription.layer_2_circuit.layer_2_circuit_sides[0].sbp.geant_sid == layer_2_circuit_input[2]["geant_sid"]
+    assert (
+        str(subscription.layer_2_circuit.layer_2_circuit_sides[1].sbp.edge_port.owner_subscription_id)
+        == layer_2_circuit_input[2]["layer_2_circuit_side_b"]["edge_port"]
+    )
+    assert subscription.layer_2_circuit.layer_2_circuit_sides[1].sbp.is_tagged is True
+    assert subscription.layer_2_circuit.layer_2_circuit_sides[1].sbp.geant_sid == layer_2_circuit_input[2]["geant_sid"]
+    assert subscription.layer_2_circuit.layer_2_circuit_type == Layer2CircuitType.TAGGED
+    assert subscription.layer_2_circuit.vlan_range_lower_bound == layer_2_circuit_input[2]["vlan_range_lower_bound"]
+    assert subscription.layer_2_circuit.vlan_range_upper_bound == layer_2_circuit_input[2]["vlan_range_upper_bound"]
+    assert subscription.layer_2_circuit.bandwidth == layer_2_circuit_input[2]["policer_bandwidth"]
+    assert subscription.layer_2_circuit.policer_burst_rate == layer_2_circuit_input[2]["policer_burst_rate"]
+    assert subscription.layer_2_circuit.policer_enabled == layer_2_circuit_input[1]["policer_enabled"]
diff --git a/test/workflows/l2_circuit/test_import_layer_2_circuit.py b/test/workflows/l2_circuit/test_import_layer_2_circuit.py
new file mode 100644
index 0000000000000000000000000000000000000000..ba2009a03f68dd63a0ff3b0e89e51bd3ddda55ac
--- /dev/null
+++ b/test/workflows/l2_circuit/test_import_layer_2_circuit.py
@@ -0,0 +1,24 @@
+import pytest
+from orchestrator.types import SubscriptionLifecycle
+
+from gso.products import ProductName
+from gso.products.product_types.layer_2_circuit import Layer2Circuit
+from test.workflows import assert_complete, run_workflow
+
+
+@pytest.mark.workflow()
+@pytest.mark.parametrize(
+    "layer_2_circuit_service_type", [ProductName.IMPORTED_EXPRESSROUTE, ProductName.IMPORTED_GEANT_PLUS]
+)
+def test_import_layer_2_circuit_success(layer_2_circuit_service_type, layer_2_circuit_subscription_factory):
+    imported_layer_2_circuit = layer_2_circuit_subscription_factory(
+        layer_2_circuit_service_type=layer_2_circuit_service_type
+    )
+    result, _, _ = run_workflow("import_layer_2_circuit", [{"subscription_id": imported_layer_2_circuit}])
+    subscription = Layer2Circuit.from_subscription(imported_layer_2_circuit)
+
+    assert_complete(result)
+    #  Remove the "IMPORTED_" prefix with ``[9:]``
+    assert subscription.product.name == layer_2_circuit_service_type.value[9:]
+    assert subscription.status == SubscriptionLifecycle.ACTIVE
+    assert subscription.insync is True
diff --git a/test/workflows/l2_circuit/test_modify_layer_2_circuit.py b/test/workflows/l2_circuit/test_modify_layer_2_circuit.py
new file mode 100644
index 0000000000000000000000000000000000000000..3879a0fe085f746990baa4198ebb04ee394750db
--- /dev/null
+++ b/test/workflows/l2_circuit/test_modify_layer_2_circuit.py
@@ -0,0 +1,75 @@
+import pytest
+from orchestrator.types import SubscriptionLifecycle
+
+from gso.products import ProductName
+from gso.products.product_blocks.layer_2_circuit import Layer2CircuitType
+from gso.products.product_types.layer_2_circuit import Layer2Circuit
+from test.workflows import assert_complete, extract_state, run_workflow
+
+
+@pytest.mark.parametrize("layer_2_circuit_service_type", [ProductName.GEANT_PLUS, ProductName.EXPRESSROUTE])
+@pytest.mark.workflow()
+def test_modify_layer_2_circuit_change_policer_bandwidth(
+    layer_2_circuit_service_type,
+    layer_2_circuit_subscription_factory,
+    faker,
+    partner_factory,
+    data_config_filename,
+):
+    subscription_id = layer_2_circuit_subscription_factory(layer_2_circuit_service_type=layer_2_circuit_service_type)
+    subscription = Layer2Circuit.from_subscription(subscription_id)
+    input_form_data = [
+        {"subscription_id": subscription_id},
+        {
+            "tt_number": faker.tt_number(),
+            "layer_2_circuit_type": Layer2CircuitType.TAGGED,
+            "policer_enabled": False,
+        },
+        {
+            "vlan_range_lower_bound": subscription.layer_2_circuit.vlan_range_lower_bound,
+            "vlan_range_upper_bound": subscription.layer_2_circuit.vlan_range_upper_bound,
+            "policer_bandwidth": None,
+            "policer_burst_rate": None,
+        },
+    ]
+    result, _, _ = run_workflow("modify_layer_2_circuit", input_form_data)
+    assert_complete(result)
+    assert subscription.status == SubscriptionLifecycle.ACTIVE
+    assert subscription.layer_2_circuit.policer_enabled is False
+    assert subscription.layer_2_circuit.bandwidth is None
+    assert subscription.layer_2_circuit.policer_burst_rate is None
+
+
+@pytest.mark.parametrize("layer_2_circuit_service_type", [ProductName.GEANT_PLUS, ProductName.EXPRESSROUTE])
+@pytest.mark.workflow()
+def test_modify_layer_2_circuit_change_circuit_type(
+    layer_2_circuit_service_type,
+    layer_2_circuit_subscription_factory,
+    faker,
+    partner_factory,
+    data_config_filename,
+):
+    subscription_id = layer_2_circuit_subscription_factory(layer_2_circuit_service_type=layer_2_circuit_service_type)
+    subscription = Layer2Circuit.from_subscription(subscription_id)
+    input_form_data = [
+        {"subscription_id": subscription_id},
+        {
+            "tt_number": faker.tt_number(),
+            "layer_2_circuit_type": Layer2CircuitType.UNTAGGED,
+        },
+        {
+            "vlan_range_lower_bound": None,
+            "vlan_range_upper_bound": None,
+            "policer_bandwidth": subscription.layer_2_circuit.bandwidth,
+        },
+    ]
+    result, _, _ = run_workflow("modify_layer_2_circuit", input_form_data)
+    assert_complete(result)
+    state = extract_state(result)
+    subscription = Layer2Circuit.from_subscription(state["subscription_id"])
+    assert subscription.status == SubscriptionLifecycle.ACTIVE
+    assert subscription.layer_2_circuit.vlan_range_lower_bound is None
+    assert subscription.layer_2_circuit.vlan_range_upper_bound is None
+    assert subscription.layer_2_circuit.layer_2_circuit_type == Layer2CircuitType.UNTAGGED
+    for layer_2_circuit_side in subscription.layer_2_circuit.layer_2_circuit_sides:
+        assert layer_2_circuit_side.sbp.is_tagged is False
diff --git a/test/workflows/l2_circuit/test_terminate_layer_2_circuit.py b/test/workflows/l2_circuit/test_terminate_layer_2_circuit.py
new file mode 100644
index 0000000000000000000000000000000000000000..778582d879d78065921be00b632977af0753ebfa
--- /dev/null
+++ b/test/workflows/l2_circuit/test_terminate_layer_2_circuit.py
@@ -0,0 +1,19 @@
+import pytest
+
+from gso.products import ProductName
+from gso.products.product_types.layer_2_circuit import Layer2Circuit
+from test.workflows import assert_complete, extract_state, run_workflow
+
+
+@pytest.mark.workflow()
+@pytest.mark.parametrize("layer_2_circuit_service_type", [ProductName.GEANT_PLUS, ProductName.EXPRESSROUTE])
+def test_terminate_layer_2_circuit(layer_2_circuit_service_type, layer_2_circuit_subscription_factory, faker):
+    subscription_id = layer_2_circuit_subscription_factory(layer_2_circuit_service_type=layer_2_circuit_service_type)
+    initialt_layer_2_circuit_data = [{"subscription_id": subscription_id}, {"tt_number": faker.tt_number()}]
+    result, _, _ = run_workflow("terminate_layer_2_circuit", initialt_layer_2_circuit_data)
+    assert_complete(result)
+
+    state = extract_state(result)
+    subscription_id = state["subscription_id"]
+    subscription = Layer2Circuit.from_subscription(subscription_id)
+    assert subscription.status == "terminated"
diff --git a/test/workflows/l3_core_service/__init__.py b/test/workflows/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/l3_core_service/test_create_imported_l3_core_service.py
similarity index 70%
rename from test/workflows/nren_l3_core_service/test_create_imported_nren_l3_core_service.py
rename to test/workflows/l3_core_service/test_create_imported_l3_core_service.py
index 63aab373915f348d3a8e8e7f7bf161c2e1e5c190..626c62378746d7b3625f490325cc7d362a6b4de1 100644
--- a/test/workflows/nren_l3_core_service/test_create_imported_nren_l3_core_service.py
+++ b/test/workflows/l3_core_service/test_create_imported_l3_core_service.py
@@ -2,13 +2,22 @@ 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.products.product_types.l3_core_service import ImportedL3CoreService, L3CoreServiceType
 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(
+@pytest.mark.parametrize(
+    "l3_core_service_type",
+    [
+        L3CoreServiceType.GEANT_IP,
+        L3CoreServiceType.IAS,
+        L3CoreServiceType.GWS,
+        L3CoreServiceType.LHCONE,
+        L3CoreServiceType.COPERNICUS,
+    ],
+)
+def test_create_imported_l3_core_service_success(
     faker, partner_factory, edge_port_subscription_factory, l3_core_service_type
 ):
     creation_form_input_data = {
@@ -27,11 +36,21 @@ def test_create_imported_nren_l3_core_service_success(
                 "ipv6_address": faker.ipv6(),
                 "ipv6_mask": faker.ipv6_netmask(),
                 "custom_firewall_filters": faker.boolean(),
+                "v4_bfd_settings": {
+                    "bfd_enabled": True,
+                    "bfd_interval_tx": faker.pyint(),
+                    "bfd_interval_rx": faker.pyint(),
+                    "bfd_multiplier": faker.pyint(),
+                },
+                "v6_bfd_settings": {
+                    "bfd_enabled": True,
+                    "bfd_interval_tx": faker.pyint(),
+                    "bfd_interval_rx": faker.pyint(),
+                    "bfd_multiplier": faker.pyint(),
+                },
                 "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(),
@@ -44,8 +63,6 @@ def test_create_imported_nren_l3_core_service_success(
                     },
                     {
                         "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(),
@@ -61,8 +78,8 @@ def test_create_imported_nren_l3_core_service_success(
         ],
     }
 
-    result, _, _ = run_workflow("create_imported_nren_l3_core_service", [creation_form_input_data])
+    result, _, _ = run_workflow("create_imported_l3_core_service", [creation_form_input_data])
     state = extract_state(result)
-    subscription = ImportedNRENL3CoreService.from_subscription(state["subscription_id"])
+    subscription = ImportedL3CoreService.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/l3_core_service/test_create_l3_core_service.py
similarity index 66%
rename from test/workflows/nren_l3_core_service/test_create_nren_l3_core_service.py
rename to test/workflows/l3_core_service/test_create_l3_core_service.py
index d8eac84e705567a8ef1e5fb0add47457607751f3..149fdf202357a583d52d4010eb7007eb7582413d 100644
--- a/test/workflows/nren_l3_core_service/test_create_nren_l3_core_service.py
+++ b/test/workflows/l3_core_service/test_create_l3_core_service.py
@@ -4,7 +4,7 @@ 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.products.product_types.l3_core_service import L3CoreService
 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
@@ -16,8 +16,6 @@ def base_bgp_peer_input(faker):
         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(),
@@ -28,10 +26,12 @@ def base_bgp_peer_input(faker):
     return _base_bgp_peer_input
 
 
-@pytest.mark.parametrize("l3_core_type", [ProductName.GEANT_IP, ProductName.IAS])
+@pytest.mark.parametrize(
+    "l3_core_type", [ProductName.GEANT_IP, ProductName.IAS, ProductName.GWS, ProductName.LHCONE, ProductName.COPERNICUS]
+)
 @pytest.mark.workflow()
 @patch("gso.services.lso_client._send_request")
-def test_create_nren_l3_core_service_success(
+def test_create_l3_core_service_success(
     mock_lso_client,
     l3_core_type,
     faker,
@@ -47,7 +47,7 @@ def test_create_nren_l3_core_service_success(
     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}]},
+        {"edge_port": {"edge_port": edge_port_a, "ap_type": APType.PRIMARY}},
         {
             "geant_sid": faker.geant_sid(),
             "is_tagged": faker.boolean(),
@@ -57,24 +57,36 @@ def test_create_nren_l3_core_service_success(
             "ipv6_address": faker.ipv6(),
             "ipv6_mask": faker.ipv6_netmask(),
             "custom_firewall_filters": faker.boolean(),
+            "v4_bfd_settings": {
+                "bfd_enabled": True,
+                "bfd_interval_rx": faker.pyint(),
+                "bfd_interval_tx": faker.pyint(),
+                "bfd_multiplier": faker.pyint(),
+            },
+            "v6_bfd_settings": {
+                "bfd_enabled": True,
+                "bfd_interval_rx": faker.pyint(),
+                "bfd_interval_tx": faker.pyint(),
+                "bfd_multiplier": faker.pyint(),
+            },
             "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)
+    result, process_stat, step_log = run_workflow("create_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"])
+    subscription = L3CoreService.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 len(subscription.l3_core_service.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"]
+        str(subscription.l3_core_service.ap_list[0].sbp.edge_port.owner_subscription_id)
+        == form_input_data[2]["edge_port"]["edge_port"]
     )
diff --git a/test/workflows/l3_core_service/test_import_l3_core_service.py b/test/workflows/l3_core_service/test_import_l3_core_service.py
new file mode 100644
index 0000000000000000000000000000000000000000..e3293e6167da1ed02daf053cdde70b4d89d21fe2
--- /dev/null
+++ b/test/workflows/l3_core_service/test_import_l3_core_service.py
@@ -0,0 +1,27 @@
+import pytest
+from orchestrator.types import SubscriptionLifecycle
+
+from gso.products.product_types.l3_core_service import L3CoreService, L3CoreServiceType
+from test.workflows import assert_complete, run_workflow
+
+
+@pytest.mark.parametrize(
+    "l3_core_service_type",
+    [
+        L3CoreServiceType.IMPORTED_GEANT_IP,
+        L3CoreServiceType.IMPORTED_IAS,
+        L3CoreServiceType.IMPORTED_LHCONE,
+        L3CoreServiceType.IMPORTED_COPERNICUS,
+    ],
+)
+@pytest.mark.workflow()
+def test_import_l3_core_service_success(l3_core_service_subscription_factory, l3_core_service_type):
+    imported_l3_core_service = l3_core_service_subscription_factory(l3_core_service_type=l3_core_service_type)
+    result, _, _ = run_workflow("import_l3_core_service", [{"subscription_id": imported_l3_core_service}])
+    subscription = L3CoreService.from_subscription(imported_l3_core_service)
+
+    assert_complete(result)
+    #  Remove the "IMPORTED_" prefix with ``[9:]``
+    assert subscription.l3_core_service_type == L3CoreServiceType(l3_core_service_type.value[9:])
+    assert subscription.status == SubscriptionLifecycle.ACTIVE
+    assert subscription.insync is True
diff --git a/test/workflows/l3_core_service/test_migrate_l3_core_service.py b/test/workflows/l3_core_service/test_migrate_l3_core_service.py
new file mode 100644
index 0000000000000000000000000000000000000000..6109dcca65f26ecec4d29624a4ba4ebe22370323
--- /dev/null
+++ b/test/workflows/l3_core_service/test_migrate_l3_core_service.py
@@ -0,0 +1,56 @@
+import pytest
+
+from gso.products.product_types.l3_core_service import L3CoreService, L3CoreServiceType
+from test.workflows import assert_complete, extract_state, run_workflow
+
+
+@pytest.mark.parametrize(
+    "l3_core_service_type",
+    [
+        L3CoreServiceType.GEANT_IP,
+        L3CoreServiceType.IAS,
+        L3CoreServiceType.GWS,
+        L3CoreServiceType.LHCONE,
+        L3CoreServiceType.COPERNICUS,
+    ],
+)
+@pytest.mark.workflow()
+def test_migrate_l3_core_service_success(
+    faker,
+    edge_port_subscription_factory,
+    partner_factory,
+    l3_core_service_subscription_factory,
+    l3_core_service_type,
+):
+    partner = partner_factory()
+    subscription_id = l3_core_service_subscription_factory(partner=partner, 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 = L3CoreService.from_subscription(subscription_id)
+
+    form_input_data = [
+        {"subscription_id": subscription_id},
+        {
+            "tt_number": faker.tt_number(),
+            "edge_port_selection": [
+                {
+                    "old_edge_port": subscription.l3_core_service.ap_list[0].sbp.edge_port.description,
+                    "new_edge_port": new_edge_port_1,
+                },
+                {
+                    "old_edge_port": subscription.l3_core_service.ap_list[1].sbp.edge_port.description,
+                    "new_edge_port": new_edge_port_2,
+                },
+            ],
+        },
+    ]
+
+    result, _, _ = run_workflow("migrate_l3_core_service", form_input_data)
+
+    assert_complete(result)
+    state = extract_state(result)
+    subscription = L3CoreService.from_subscription(state["subscription_id"])
+    assert subscription.insync is True
+    assert len(subscription.l3_core_service.ap_list) == 2
+    assert str(subscription.l3_core_service.ap_list[0].sbp.edge_port.owner_subscription_id) == new_edge_port_1
+    assert str(subscription.l3_core_service.ap_list[1].sbp.edge_port.owner_subscription_id) == new_edge_port_2
diff --git a/test/workflows/l3_core_service/test_modify_l3_core_service.py b/test/workflows/l3_core_service/test_modify_l3_core_service.py
new file mode 100644
index 0000000000000000000000000000000000000000..67ff888097afbed5db3403a6ca926cdd00dda54e
--- /dev/null
+++ b/test/workflows/l3_core_service/test_modify_l3_core_service.py
@@ -0,0 +1,307 @@
+import pytest
+
+from gso.products.product_blocks.bgp_session import IPFamily
+from gso.products.product_types.l3_core_service import L3CoreService, L3CoreServiceType
+from gso.utils.shared_enums import APType
+from test.workflows import extract_state, run_workflow
+
+
+@pytest.mark.parametrize(
+    "l3_core_service_type",
+    [
+        L3CoreServiceType.GEANT_IP,
+        L3CoreServiceType.IAS,
+        L3CoreServiceType.GWS,
+        L3CoreServiceType.LHCONE,
+        L3CoreServiceType.COPERNICUS,
+    ],
+)
+@pytest.mark.workflow()
+def test_modify_l3_core_service_remove_edge_port_success(l3_core_service_subscription_factory, l3_core_service_type):
+    subscription_id = l3_core_service_subscription_factory(l3_core_service_type=l3_core_service_type)
+    subscription = L3CoreService.from_subscription(subscription_id)
+    access_port = subscription.l3_core_service.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_l3_core_service", input_form_data)
+
+    state = extract_state(result)
+    subscription = L3CoreService.from_subscription(state["subscription_id"])
+    assert len(subscription.l3_core_service.ap_list) == 1
+    assert subscription.l3_core_service.ap_list[0].ap_type == APType.LOAD_BALANCED
+
+
+@pytest.mark.parametrize(
+    "l3_core_service_type",
+    [
+        L3CoreServiceType.GEANT_IP,
+        L3CoreServiceType.IAS,
+        L3CoreServiceType.GWS,
+        L3CoreServiceType.LHCONE,
+        L3CoreServiceType.COPERNICUS,
+    ],
+)
+@pytest.mark.workflow()
+def test_modify_l3_core_service_add_new_edge_port_success(
+    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 = l3_core_service_subscription_factory(partner=partner, l3_core_service_type=l3_core_service_type)
+    subscription = L3CoreService.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.l3_core_service.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(),
+                "bfd_enabled": False,
+                "prefix_limit": 1000,
+            },
+            "v6_bgp_peer": {
+                "authentication_key": faker.password(),
+                "peer_address": faker.ipv6(),
+                "bfd_enabled": False,
+            },
+            "v4_bfd_settings": {"bfd_enabled": False},
+            "v6_bfd_settings": {"bfd_enabled": False},
+        },
+    ]
+
+    result, _, _ = run_workflow("modify_l3_core_service", input_form_data)
+
+    state = extract_state(result)
+    subscription = L3CoreService.from_subscription(state["subscription_id"])
+    assert len(subscription.l3_core_service.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_bfd_settings": {
+                "bfd_enabled": True,
+                "bfd_interval_rx": faker.pyint(),
+                "bfd_interval_tx": faker.pyint(),
+                "bfd_multiplier": faker.pyint(),
+            },
+            "v6_bfd_settings": {
+                "bfd_enabled": True,
+                "bfd_interval_rx": faker.pyint(),
+                "bfd_interval_tx": faker.pyint(),
+                "bfd_multiplier": faker.pyint(),
+            },
+            "v4_bgp_peer": {
+                "bfd_enabled": True,
+                "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,
+                "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,
+                "prefix_limit": 3000,
+            },
+        }
+
+    return _generate_form_data
+
+
+@pytest.mark.parametrize(
+    "l3_core_service_type",
+    [
+        L3CoreServiceType.GEANT_IP,
+        L3CoreServiceType.IAS,
+        L3CoreServiceType.GWS,
+        L3CoreServiceType.LHCONE,
+        L3CoreServiceType.COPERNICUS,
+    ],
+)
+@pytest.mark.workflow()
+def test_modify_l3_core_service_modify_edge_port_success(
+    faker, l3_core_service_subscription_factory, l3_core_service_type, sbp_input_form_data
+):
+    subscription_id = l3_core_service_subscription_factory(l3_core_service_type=l3_core_service_type)
+    subscription = L3CoreService.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.l3_core_service.ap_list
+            ]
+        },
+        {**new_sbp_data[0]},
+        {**new_sbp_data[1]},
+    ]
+
+    result, _, _ = run_workflow("modify_l3_core_service", input_form_data)
+
+    state = extract_state(result)
+    subscription = L3CoreService.from_subscription(state["subscription_id"])
+    assert len(subscription.l3_core_service.ap_list) == 2
+
+    for i in range(2):
+        assert subscription.l3_core_service.ap_list[i].sbp.geant_sid == new_sbp_data[i]["geant_sid"]
+        assert subscription.l3_core_service.ap_list[i].sbp.is_tagged == new_sbp_data[i]["is_tagged"]
+        assert subscription.l3_core_service.ap_list[i].sbp.vlan_id == new_sbp_data[i]["vlan_id"]
+        assert str(subscription.l3_core_service.ap_list[i].sbp.ipv4_address) == new_sbp_data[i]["ipv4_address"]
+        assert str(subscription.l3_core_service.ap_list[i].sbp.ipv6_address) == new_sbp_data[i]["ipv6_address"]
+        assert (
+            subscription.l3_core_service.ap_list[i].sbp.custom_firewall_filters
+            == new_sbp_data[i]["custom_firewall_filters"]
+        )
+
+        assert (
+            subscription.l3_core_service.ap_list[i].sbp.bgp_session_list[0].bfd_enabled
+            == new_sbp_data[i]["v4_bgp_peer"]["bfd_enabled"]
+        )
+        assert (
+            subscription.l3_core_service.ap_list[i].sbp.bgp_session_list[0].has_custom_policies
+            == new_sbp_data[i]["v4_bgp_peer"]["has_custom_policies"]
+        )
+        assert (
+            subscription.l3_core_service.ap_list[i].sbp.bgp_session_list[0].authentication_key
+            == new_sbp_data[i]["v4_bgp_peer"]["authentication_key"]
+        )
+        assert (
+            subscription.l3_core_service.ap_list[i].sbp.bgp_session_list[0].multipath_enabled
+            == new_sbp_data[i]["v4_bgp_peer"]["multipath_enabled"]
+        )
+        assert (
+            subscription.l3_core_service.ap_list[i].sbp.bgp_session_list[0].send_default_route
+            == new_sbp_data[i]["v4_bgp_peer"]["send_default_route"]
+        )
+        assert (
+            subscription.l3_core_service.ap_list[i].sbp.bgp_session_list[0].is_passive
+            == new_sbp_data[i]["v4_bgp_peer"]["is_passive"]
+        )
+        assert (
+            str(subscription.l3_core_service.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.l3_core_service.ap_list[i].sbp.bgp_session_list[0].families)
+            == new_sbp_data[i]["v4_bgp_peer"]["add_v4_multicast"]
+        )
+
+        assert (
+            subscription.l3_core_service.ap_list[i].sbp.bgp_session_list[1].bfd_enabled
+            == new_sbp_data[i]["v6_bgp_peer"]["bfd_enabled"]
+        )
+        assert (
+            subscription.l3_core_service.ap_list[i].sbp.bgp_session_list[1].has_custom_policies
+            == new_sbp_data[i]["v6_bgp_peer"]["has_custom_policies"]
+        )
+        assert (
+            subscription.l3_core_service.ap_list[i].sbp.bgp_session_list[1].authentication_key
+            == new_sbp_data[i]["v6_bgp_peer"]["authentication_key"]
+        )
+        assert (
+            subscription.l3_core_service.ap_list[i].sbp.bgp_session_list[1].multipath_enabled
+            == new_sbp_data[i]["v6_bgp_peer"]["multipath_enabled"]
+        )
+        assert (
+            subscription.l3_core_service.ap_list[i].sbp.bgp_session_list[1].send_default_route
+            == new_sbp_data[i]["v6_bgp_peer"]["send_default_route"]
+        )
+        assert (
+            subscription.l3_core_service.ap_list[i].sbp.bgp_session_list[1].is_passive
+            == new_sbp_data[i]["v6_bgp_peer"]["is_passive"]
+        )
+        assert (
+            str(subscription.l3_core_service.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.l3_core_service.ap_list[i].sbp.bgp_session_list[1].families)
+            == new_sbp_data[i]["v6_bgp_peer"]["add_v6_multicast"]
+        )
+        assert (
+            subscription.l3_core_service.ap_list[i].sbp.v4_bfd_settings.bfd_enabled
+            == new_sbp_data[i]["v4_bfd_settings"]["bfd_enabled"]
+        )
+        assert (
+            subscription.l3_core_service.ap_list[i].sbp.v4_bfd_settings.bfd_interval_rx
+            == new_sbp_data[i]["v4_bfd_settings"]["bfd_interval_rx"]
+        )
+        assert (
+            subscription.l3_core_service.ap_list[i].sbp.v4_bfd_settings.bfd_interval_tx
+            == new_sbp_data[i]["v4_bfd_settings"]["bfd_interval_tx"]
+        )
+        assert (
+            subscription.l3_core_service.ap_list[i].sbp.v4_bfd_settings.bfd_multiplier
+            == new_sbp_data[i]["v4_bfd_settings"]["bfd_multiplier"]
+        )
+        assert (
+            subscription.l3_core_service.ap_list[i].sbp.v6_bfd_settings.bfd_enabled
+            == new_sbp_data[i]["v6_bfd_settings"]["bfd_enabled"]
+        )
+        assert (
+            subscription.l3_core_service.ap_list[i].sbp.v6_bfd_settings.bfd_interval_rx
+            == new_sbp_data[i]["v6_bfd_settings"]["bfd_interval_rx"]
+        )
+        assert (
+            subscription.l3_core_service.ap_list[i].sbp.v6_bfd_settings.bfd_interval_tx
+            == new_sbp_data[i]["v6_bfd_settings"]["bfd_interval_tx"]
+        )
+        assert (
+            subscription.l3_core_service.ap_list[i].sbp.v6_bfd_settings.bfd_multiplier
+            == new_sbp_data[i]["v6_bfd_settings"]["bfd_multiplier"]
+        )
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
deleted file mode 100644
index 13095de6ead5ad679d2ccb9ceb1106d986a79470..0000000000000000000000000000000000000000
--- a/test/workflows/nren_l3_core_service/test_import_nren_l3_core_service.py
+++ /dev/null
@@ -1,23 +0,0 @@
-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
deleted file mode 100644
index a4ad8a9f95a0c834536fae3e76478b65e35264cc..0000000000000000000000000000000000000000
--- a/test/workflows/nren_l3_core_service/test_migrate_nren_l3_core_service.py
+++ /dev/null
@@ -1,49 +0,0 @@
-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
deleted file mode 100644
index 197be36c55baf085b5d624bd9bcee8946c7c157e..0000000000000000000000000000000000000000
--- a/test/workflows/nren_l3_core_service/test_modify_nren_l3_core_service.py
+++ /dev/null
@@ -1,264 +0,0 @@
-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/router/test_validate_router.py b/test/workflows/router/test_validate_router.py
index a72063d916965d62b2048853b720bf9fd21ebf86..1b2e371cebb6cdecd845a4ccc52ee7de285c7afd 100644
--- a/test/workflows/router/test_validate_router.py
+++ b/test/workflows/router/test_validate_router.py
@@ -4,6 +4,7 @@ import pytest
 from infoblox_client import objects
 from orchestrator.types import SubscriptionLifecycle
 
+from gso.products.product_blocks.router import RouterRole
 from gso.products.product_types.router import Router
 from gso.utils.shared_enums import Vendor
 from test.services.conftest import MockedKentikClient
@@ -16,6 +17,7 @@ from test.workflows import (
 
 
 @pytest.mark.parametrize("router_state", [SubscriptionLifecycle.PROVISIONING, SubscriptionLifecycle.ACTIVE])
+@pytest.mark.parametrize("router_role", [RouterRole.P, RouterRole.PE])
 @pytest.mark.workflow()
 @patch("gso.services.infoblox.find_host_by_fqdn")
 @patch("gso.services.lso_client._send_request")
@@ -30,14 +32,15 @@ def test_validate_nokia_router_success(
     mock_find_host_by_fqdn,
     router_subscription_factory,
     faker,
-    data_config_filename,
-    geant_partner,
     router_state,
+    router_role,
 ):
     mock_validate_librenms_device.return_value = None
+    router_subscription_factory(router_role=RouterRole.P)
+    router_subscription_factory(router_role=RouterRole.PE)
     mock_kentik_client.return_value = MockedKentikClient
     #  Run workflow
-    subscription_id = router_subscription_factory(status=router_state)
+    subscription_id = router_subscription_factory(status=router_state, router_role=router_role)
     mock_fqdn = Router.from_subscription(subscription_id).router.router_fqdn
     mock_v4 = faker.ipv4()
     mock_find_host_by_fqdn.return_value = objects.HostRecord(
@@ -62,7 +65,9 @@ def test_validate_nokia_router_success(
     state = extract_state(result)
     subscription_id = state["subscription_id"]
 
-    for _ in range(2):
+    lso_execution_count = 2 if router_role == RouterRole.P else 3
+
+    for _ in range(lso_execution_count):
         result, step_log = assert_lso_success(result, process_stat, step_log)
 
     assert_complete(result)
@@ -71,7 +76,7 @@ def test_validate_nokia_router_success(
     subscription = Router.from_subscription(subscription_id)
 
     assert subscription.status == router_state
-    assert mock_execute_playbook.call_count == 2
+    assert mock_execute_playbook.call_count == lso_execution_count
     assert mock_find_host_by_fqdn.call_count == 1
     assert mock_get_device_by_name.call_count == 1
     assert mock_validate_librenms_device.call_count == 1
@@ -79,12 +84,7 @@ def test_validate_nokia_router_success(
 
 
 @pytest.mark.workflow()
-def test_validate_juniper_router_success(
-    router_subscription_factory,
-    faker,
-    data_config_filename,
-    geant_partner,
-):
+def test_validate_juniper_router_success(router_subscription_factory):
     #  Run workflow
     subscription_id = router_subscription_factory(vendor=Vendor.JUNIPER)