diff --git a/gso/migrations/versions/2025-03-27_3541c7e57284_add_l2circuit_migration_workflow.py b/gso/migrations/versions/2025-03-27_3541c7e57284_add_l2circuit_migration_workflow.py
new file mode 100644
index 0000000000000000000000000000000000000000..0dfcd2cbee3f627b93c2d1d38247cd3a2ca72699
--- /dev/null
+++ b/gso/migrations/versions/2025-03-27_3541c7e57284_add_l2circuit_migration_workflow.py
@@ -0,0 +1,39 @@
+"""Add L2Circuit migration workflow.
+
+Revision ID: 3541c7e57284
+Revises: b14f71db2b58
+Create Date: 2025-03-27 15:35:39.351436
+
+"""
+import sqlalchemy as sa
+from alembic import op
+
+# revision identifiers, used by Alembic.
+revision = '3541c7e57284'
+down_revision = 'b14f71db2b58'
+branch_labels = None
+depends_on = None
+
+
+from orchestrator.migrations.helpers import create_workflow, delete_workflow
+
+new_workflows = [
+    {
+        "name": "migrate_layer_2_circuit",
+        "target": "MODIFY",
+        "description": "Migrate Layer 2 Circuit",
+        "product_type": "Layer2Circuit"
+    }
+]
+
+
+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/translations/en-GB.json b/gso/translations/en-GB.json
index b1a54748dbd43be1fc98ca3145b467e097f9f14a..02ea8449fd9eefba694548940ab8b83a6a61a85d 100644
--- a/gso/translations/en-GB.json
+++ b/gso/translations/en-GB.json
@@ -114,6 +114,7 @@
         "import_switch": "NOT FOR HUMANS -- Finalize import into a Switch",
         "migrate_edge_port": "Migrate Edge Port",
         "migrate_iptrunk": "Migrate IP Trunk",
+        "migrate_layer_2_circuit": "Migrate Layer 2 Circuit",
         "migrate_l3_core_service": "Migrate L3 Core Service",
         "modify_connection_strategy": "Modify connection strategy",
         "modify_edge_port": "Modify Edge Port",
diff --git a/gso/utils/helpers.py b/gso/utils/helpers.py
index 15b9fcd42b193127797d06f7e771519efc3ff191..c20e8a4e780daff69688fa1b83b7cf57787ce99a 100644
--- a/gso/utils/helpers.py
+++ b/gso/utils/helpers.py
@@ -3,7 +3,7 @@
 import random
 import re
 from ipaddress import IPv4Network, IPv6Network
-from typing import TYPE_CHECKING
+from typing import TYPE_CHECKING, TypeAlias, cast
 from uuid import UUID
 
 from orchestrator.types import SubscriptionLifecycle
@@ -11,6 +11,7 @@ from pydantic_forms.types import UUIDstr
 from pydantic_forms.validators import Choice
 
 from gso import settings
+from gso.products.product_blocks.layer_2_circuit import Layer2CircuitType
 from gso.products.product_blocks.router import RouterRole
 from gso.products.product_types.router import Router
 from gso.services.netbox_client import NetboxClient
@@ -267,15 +268,18 @@ def active_switch_selector() -> Choice:
     return Choice("Select a switch", zip(switch_subscriptions.keys(), switch_subscriptions.items(), strict=True))  # type: ignore[arg-type]
 
 
-def active_edge_port_selector(*, partner_id: UUIDstr | None = None) -> Choice:
+def active_edge_port_selector(*, partner_id: UUIDstr | None = None) -> TypeAlias:
     """Generate a dropdown selector for choosing an active Edge Port in an input form."""
     edge_ports = get_active_edge_port_subscriptions(partner_id=partner_id)
 
     options = {str(edge_port.subscription_id): edge_port.description for edge_port in edge_ports}
 
-    return Choice(
-        "Select an Edge Port",
-        zip(options.keys(), options.items(), strict=True),  # type: ignore[arg-type]
+    return cast(
+        type[Choice],
+        Choice.__call__(
+            "Select an Edge Port",
+            zip(options.keys(), options.items(), strict=True),
+        ),
     )
 
 
@@ -301,14 +305,17 @@ def validate_edge_port_number_of_members_based_on_lacp(*, number_of_members: int
         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'.
+def generate_unique_vc_id(l2c_type: str, max_attempts: int = 100) -> VC_ID | None:
+    """Generate a unique 8-digit VC_ID.
 
-    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.
+    This function attempts to generate a ``VC_ID`` based on their circuit type,
+    and ensures uniqueness before returning it. A maximum attempt limit is
+    set to prevent an infinite loop in case the ID space is saturated.
+    The range used for generating a ``VC_ID`` depends on the circuit type:
+    ``Ethernet`` and ``VLAN`` type circuits get their IDs from different ranges.
 
     Args:
+        l2c_type: type of l2circuit.
         max_attempts: The maximum number of attempts to generate a unique ID.
 
     Returns:
@@ -317,7 +324,9 @@ def generate_unique_vc_id(max_attempts: int = 100) -> VC_ID | None:
 
     def create_vc_id() -> str:
         """Generate an 8-digit VC_ID starting with '11'."""
-        return f"11{random.randint(100000, 999999)}"  # noqa: S311
+        if l2c_type == Layer2CircuitType.ETHERNET:
+            return str(random.randint(30001, 39999))
+        return str(random.randint(6000, 6999))
 
     for _ in range(max_attempts):
         vc_id = create_vc_id()
diff --git a/gso/utils/workflow_steps.py b/gso/utils/workflow_steps.py
index 6c90f051cb19b312a1706db411a6084b5be1b5fe..50f754a96a6b8ba263ce24ee49ae67d5f1f125f6 100644
--- a/gso/utils/workflow_steps.py
+++ b/gso/utils/workflow_steps.py
@@ -50,11 +50,7 @@ def _deploy_base_config(
 
 
 def _update_sdp_mesh(
-    subscription: dict[str, Any],
-    tt_number: str,
-    process_id: UUIDstr,
-    *,
-    dry_run: bool,
+    subscription: dict[str, Any], tt_number: str, process_id: UUIDstr, *, dry_run: bool, verb: str
 ) -> LSOState:
     inventory = generate_inventory_for_routers(
         router_role=RouterRole.PE, router_vendor=Vendor.NOKIA, exclude_routers=[subscription["router"]["router_fqdn"]]
@@ -65,7 +61,7 @@ def _update_sdp_mesh(
         "subscription": subscription,
         "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",
+        "verb": verb,
         "pe_router_list": {
             subscription["router"]["router_fqdn"]: {
                 "lo4": str(subscription["router"]["router_lo_ipv4_address"]),
@@ -93,7 +89,7 @@ def _update_sdp_single_pe(
         "subscription": subscription,
         "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",
+        "verb": "add_pe_to_sdp_mesh",
         "pe_router_list": generate_inventory_for_routers(
             router_role=RouterRole.PE,
             exclude_routers=[subscription["router"]["router_fqdn"]],
@@ -288,15 +284,27 @@ def add_pe_mesh_to_pe_real(subscription: dict[str, Any], tt_number: str, process
 
 
 @step("[DRY RUN] Include the PE into SDP mesh on other Nokia PEs")
-def update_sdp_mesh_dry(subscription: dict[str, Any], tt_number: str, process_id: UUIDstr) -> State:
+def add_pe_to_sdp_mesh_dry(subscription: dict[str, Any], tt_number: str, process_id: UUIDstr) -> State:
+    """Perform a dry run of including new PE router in SDP mesh on other NOKIA PE routers."""
+    return _update_sdp_mesh(subscription, tt_number, process_id, dry_run=True, verb="add_pe_to_sdp_mesh")
+
+
+@step("[FOR REAL] Include the PE into SDP mesh on other Nokia PEs")
+def add_pe_to_sdp_mesh_real(subscription: dict[str, Any], tt_number: str, process_id: UUIDstr) -> State:
+    """Perform a real run of including new PE router in SDP mesh on other NOKIA PE routers."""
+    return _update_sdp_mesh(subscription, tt_number, process_id, dry_run=False, verb="add_pe_to_sdp_mesh")
+
+
+@step("[DRY RUN] Include the PE into SDP mesh on other Nokia PEs")
+def remove_pe_from_sdp_mesh_dry(subscription: dict[str, Any], tt_number: str, process_id: UUIDstr) -> State:
     """Perform a dry run of including new PE router in SDP mesh on other NOKIA PE routers."""
-    return _update_sdp_mesh(subscription, tt_number, process_id, dry_run=True)
+    return _update_sdp_mesh(subscription, tt_number, process_id, dry_run=True, verb="remove_pe_from_sdp_mesh")
 
 
 @step("[FOR REAL] Include the PE into SDP mesh on other Nokia PEs")
-def update_sdp_mesh_real(subscription: dict[str, Any], tt_number: str, process_id: UUIDstr) -> State:
+def remove_pe_from_sdp_mesh_real(subscription: dict[str, Any], tt_number: str, process_id: UUIDstr) -> State:
     """Perform a real run of including new PE router in SDP mesh on other NOKIA PE routers."""
-    return _update_sdp_mesh(subscription, tt_number, process_id, dry_run=False)
+    return _update_sdp_mesh(subscription, tt_number, process_id, dry_run=False, verb="remove_pe_from_sdp_mesh")
 
 
 @step("[DRY RUN] Configure SDP on the PE to all other Nokia PEs")
diff --git a/gso/workflows/__init__.py b/gso/workflows/__init__.py
index 0f1abab7165f2db22138765e51591705cf04ae55..e977c62c25e434852c999d91cd0b2239a3da3e6b 100644
--- a/gso/workflows/__init__.py
+++ b/gso/workflows/__init__.py
@@ -132,6 +132,7 @@ LazyWorkflowInstance("gso.workflows.l3_core_service.validate_prefix_list", "vali
 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.migrate_layer_2_circuit", "migrate_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/migrate_edge_port.py b/gso/workflows/edge_port/migrate_edge_port.py
index afd2d6d0f46e9dd6a81f9fe1e290af0ff3b217d0..ecdabd6f3460343249e98b59bd87557514ebdeb5 100644
--- a/gso/workflows/edge_port/migrate_edge_port.py
+++ b/gso/workflows/edge_port/migrate_edge_port.py
@@ -292,7 +292,7 @@ def migrate_l3_core_services_to_new_node(subscription_id: UUIDstr, tt_number: TT
                     },
                 ],
             ],
-            countdown=random.choice([2, 3, 4, 5]),  # noqa: S311
+            countdown=random.choice([2, 3, 4, 5]),
         )
 
     return {"l3_core_services": l3_core_services}
@@ -319,7 +319,7 @@ def migrate_l2_circuits_to_new_node(subscription_id: UUIDstr, tt_number: TTNumbe
                     },
                 ],
             ],
-            countdown=random.choice([2, 3, 4, 5]),  # noqa: S311
+            countdown=random.choice([2, 3, 4, 5]),
         )
 
     return {"layer2_circuits": layer2_circuits}
diff --git a/gso/workflows/l2_circuit/create_layer_2_circuit.py b/gso/workflows/l2_circuit/create_layer_2_circuit.py
index 5138aef1a167fe90a39c96bc0abaf557aa9ec875..ce70f215fa60ee80cc9a7a4f0240bec8729a370e 100644
--- a/gso/workflows/l2_circuit/create_layer_2_circuit.py
+++ b/gso/workflows/l2_circuit/create_layer_2_circuit.py
@@ -18,6 +18,7 @@ from gso.products.product_blocks.layer_2_circuit import Layer2CircuitSideBlockIn
 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.lso_client import lso_interaction
 from gso.services.partners import get_partner_by_name
 from gso.services.subscriptions import generate_unique_id
 from gso.utils.helpers import active_edge_port_selector, generate_unique_vc_id, partner_choice
@@ -25,6 +26,11 @@ 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
+from gso.workflows.l2_circuit.shared_steps import (
+    extract_partner_name_from_edge_port,
+    provision_l2circuit_dry,
+    provision_l2circuit_real,
+)
 
 
 def initial_input_generator(product_name: str) -> FormGenerator:
@@ -125,8 +131,10 @@ def initialize_subscription(
         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.virtual_circuit_id = generate_unique_vc_id(
+        l2c_type=subscription.layer_2_circuit.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
@@ -136,8 +144,9 @@ def initialize_subscription(
     subscription.description = f"{subscription.product.name} - {subscription.layer_2_circuit.virtual_circuit_id}"
 
     subscription = Layer2Circuit.from_other_lifecycle(subscription, SubscriptionLifecycle.PROVISIONING)
+    fqdn_list = [side.sbp.edge_port.node.router_fqdn for side in subscription.layer_2_circuit.layer_2_circuit_sides]
 
-    return {"subscription": subscription}
+    return {"subscription": subscription, "fqdn_list": fqdn_list}
 
 
 @workflow(
@@ -152,6 +161,9 @@ def create_layer_2_circuit() -> StepList:
         >> create_subscription
         >> store_process_subscription(Target.CREATE)
         >> initialize_subscription
+        >> extract_partner_name_from_edge_port
+        >> lso_interaction(provision_l2circuit_dry)
+        >> lso_interaction(provision_l2circuit_real)
         >> set_status(SubscriptionLifecycle.ACTIVE)
         >> resync
         >> done
diff --git a/gso/workflows/l2_circuit/migrate_layer2_circuit.py b/gso/workflows/l2_circuit/migrate_layer2_circuit.py
deleted file mode 100644
index e7d4d9b294cce4bd0ab5d8b2b0926fa28567a967..0000000000000000000000000000000000000000
--- a/gso/workflows/l2_circuit/migrate_layer2_circuit.py
+++ /dev/null
@@ -1,20 +0,0 @@
-"""This workflow migrates an L2 Core Service to a new Edge Port.
-
-It can be triggered by an operator or automatically by the system during Edge Port migration which is a separate
-workflow.
-
-System-triggered migration:
-When the system migrates an Edge Port, it runs the workflow automatically. The source and destination Edge Ports are
-set to the same values. Then here migration only applies the configuration to the router and fill the drift between
-core DB as source of truth and the actual network since the intent of network has changed in the previous workflow
-even though the L2 Circuit Service is not changed.
-
-Operator-triggered migration:
-When an operator initiates the workflow, they are required to specify both the source and destination EdgePorts.
-During the migration process, the system updates the related edge_port reference to replace the source
-EdgePort with the destination EdgePort and applies the necessary configuration changes to the router.
-
-Important Note:
-Since an L2 Circuit Service has multiple side, the workflow must be run separately for each side to fully
-migrate the service.
-"""
diff --git a/gso/workflows/l2_circuit/migrate_layer_2_circuit.py b/gso/workflows/l2_circuit/migrate_layer_2_circuit.py
new file mode 100644
index 0000000000000000000000000000000000000000..70a91e5aaaf24c274ca510bb9121e3b2ac37a882
--- /dev/null
+++ b/gso/workflows/l2_circuit/migrate_layer_2_circuit.py
@@ -0,0 +1,152 @@
+"""This workflow migrates an L2 Core Service to a new Edge Port.
+
+It can be triggered by an operator or automatically by the system during Edge Port migration which is a separate
+workflow.
+
+System-triggered migration:
+When the system migrates an Edge Port, it runs the workflow automatically. The source and destination Edge Ports are
+set to the same values. Then here migration only applies the configuration to the router and fill the drift between
+core DB as source of truth and the actual network since the intent of network has changed in the previous workflow
+even though the L2 Circuit Service is not changed.
+
+Operator-triggered migration:
+When an operator initiates the workflow, they are required to specify both the source and destination EdgePorts.
+During the migration process, the system updates the related edge_port reference to replace the source
+EdgePort with the destination EdgePort and applies the necessary configuration changes to the router.
+
+!!! info
+    Since an L2 Circuit Service has two sides, the workflow must be run separately for each side to fully
+    migrate the service.
+"""
+
+from typing import TypeAlias, cast
+
+from orchestrator import step, workflow
+from orchestrator.forms import FormPage, SubmitFormPage
+from orchestrator.forms.validators import Choice, Label
+from orchestrator.targets import Target
+from orchestrator.utils.errors import ProcessFailureError
+from orchestrator.workflow import StepList, begin, conditional, done
+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.types import FormGenerator, State, UUIDstr
+from pydantic_forms.validators import Divider
+
+from gso.products.product_types.edge_port import EdgePort
+from gso.products.product_types.layer_2_circuit import Layer2Circuit
+from gso.services.lso_client import lso_interaction
+from gso.services.partners import get_partner_by_id
+from gso.utils.helpers import active_edge_port_selector, generate_unique_vc_id
+from gso.utils.types.tt_number import TTNumber
+from gso.workflows.l2_circuit.shared_steps import (
+    extract_partner_name_from_edge_port,
+    generate_fqdn_list,
+    provision_l2circuit_dry,
+    provision_l2circuit_real,
+    terminate_l2circuit_dry,
+    terminate_l2circuit_real,
+)
+
+
+def input_form_generator(subscription_id: UUIDstr) -> FormGenerator:
+    """Generate an input form for migrating a Layer 2 Circuit."""
+    subscription = Layer2Circuit.from_subscription(subscription_id)
+
+    def circuit_side_selector() -> TypeAlias:
+        sides_dict = {
+            str(side.sbp.edge_port.owner_subscription_id): EdgePort.from_subscription(
+                side.sbp.edge_port.owner_subscription_id
+            ).description
+            for side in subscription.layer_2_circuit.layer_2_circuit_sides
+        }
+        return cast(
+            type[Choice],
+            Choice.__call__("Select one side of the circuit", zip(sides_dict.keys(), sides_dict.items(), strict=True)),
+        )
+
+    class MigrateL2CircuitForm(FormPage):
+        model_config = ConfigDict(title="Migrating Layer 2 Circuit")
+
+        tt_number: TTNumber
+        replace_side: circuit_side_selector()  # type: ignore[valid-type]
+        divider: Divider = Field(None, exclude=True)
+
+        label_a: Label = Field("Are we migrating to a different site?", exclude=True)
+        migrate_to_different_site: bool = False
+
+        label_b: Label = Field("Execute Ansible playbooks on the OLD side of the circuit?", exclude=True)
+        run_old_side_ansible: bool = True
+        label_c: Label = Field("Execute Ansible playbooks on the NEW side of the circuit?", exclude=True)
+        run_new_side_ansible: bool = True
+
+    initial_user_input = yield MigrateL2CircuitForm
+    replace_side_partner = get_partner_by_id(EdgePort.from_subscription(initial_user_input.replace_side).customer_id)
+
+    class SelectNewEdgePortForm(SubmitFormPage):
+        model_config = ConfigDict(title="Migrating Layer 2 Circuit")
+
+        new_edge_port: active_edge_port_selector(partner_id=replace_side_partner.partner_id)  # type: ignore[valid-type]
+
+    user_input = yield SelectNewEdgePortForm
+
+    return {
+        "tt_number": initial_user_input.tt_number,
+        "run_old_side_ansible": initial_user_input.run_old_side_ansible,
+        "run_new_side_ansible": initial_user_input.run_new_side_ansible,
+        "subscription": subscription,
+        "subscription_id": subscription_id,
+        "old_edge_port": initial_user_input.replace_side,
+        "new_edge_port": user_input.new_edge_port,
+    }
+
+
+@step("Update subscription model")
+def update_subscription_model(subscription: Layer2Circuit, old_edge_port: UUIDstr, new_edge_port: UUIDstr) -> State:
+    """Replace the old Edge Port with the newly selected one in the subscription model."""
+    replace_index = (
+        0
+        if str(subscription.layer_2_circuit.layer_2_circuit_sides[0].sbp.edge_port.owner_subscription_id)
+        == old_edge_port
+        else 1
+    )
+    subscription.layer_2_circuit.layer_2_circuit_sides[replace_index].sbp.edge_port = EdgePort.from_subscription(
+        new_edge_port
+    ).edge_port
+
+    vc_id = generate_unique_vc_id(l2c_type=subscription.layer_2_circuit.layer_2_circuit_type)
+    if not vc_id:
+        msg = "Failed to generate unique Virtual Circuit ID."
+        raise ProcessFailureError(msg)
+
+    subscription.layer_2_circuit.virtual_circuit_id = vc_id
+
+    return {"subscription": subscription}
+
+
+@workflow(
+    "Migrate Layer 2 Circuit",
+    initial_input_form=wrap_modify_initial_input_form(input_form_generator),
+    target=Target.MODIFY,
+)
+def migrate_layer_2_circuit() -> StepList:
+    """Migrate a Layer 2 Circuit."""
+    run_old_side_ansible = conditional(lambda state: state["run_old_side_ansible"])
+    run_new_side_ansible = conditional(lambda state: state["run_new_side_ansible"])
+
+    return (
+        begin
+        >> store_process_subscription(Target.MODIFY)
+        >> unsync
+        >> run_old_side_ansible(generate_fqdn_list)
+        >> run_old_side_ansible(extract_partner_name_from_edge_port)
+        >> run_old_side_ansible(lso_interaction(terminate_l2circuit_dry))
+        >> run_old_side_ansible(lso_interaction(terminate_l2circuit_real))
+        >> update_subscription_model
+        >> run_new_side_ansible(generate_fqdn_list)
+        >> run_new_side_ansible(extract_partner_name_from_edge_port)
+        >> run_new_side_ansible(lso_interaction(provision_l2circuit_dry))
+        >> run_new_side_ansible(lso_interaction(provision_l2circuit_real))
+        >> resync
+        >> done
+    )
diff --git a/gso/workflows/l2_circuit/modify_layer_2_circuit.py b/gso/workflows/l2_circuit/modify_layer_2_circuit.py
index 91c791359b99194e9e089e7a0823b3b857aa37af..ae879037c0700a515ca7d2507fbad6eba7d83715 100644
--- a/gso/workflows/l2_circuit/modify_layer_2_circuit.py
+++ b/gso/workflows/l2_circuit/modify_layer_2_circuit.py
@@ -3,7 +3,7 @@
 from orchestrator import begin, done, workflow
 from orchestrator.forms import FormPage, SubmitFormPage
 from orchestrator.targets import Target
-from orchestrator.workflow import StepList, step
+from orchestrator.workflow import StepList, conditional, step
 from orchestrator.workflows.steps import resync, store_process_subscription, unsync
 from orchestrator.workflows.utils import wrap_modify_initial_input_form
 from pydantic import BaseModel, ConfigDict, Field
@@ -13,10 +13,17 @@ 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.lso_client import lso_interaction
 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
+from gso.workflows.l2_circuit.shared_steps import (
+    extract_partner_name_from_edge_port,
+    generate_fqdn_list,
+    provision_l2circuit_dry,
+    provision_l2circuit_real,
+)
 
 
 def initial_input_form_generator(subscription_id: UUIDstr) -> FormGenerator:
@@ -36,6 +43,9 @@ def initial_input_form_generator(subscription_id: UUIDstr) -> FormGenerator:
         policer_enabled: bool = subscription.layer_2_circuit.policer_enabled
         custom_service_name: str | None = subscription.layer_2_circuit.custom_service_name
 
+        label: Label = Field("Should this workflow execute Ansible playbooks on routers?", exclude=True)
+        run_ansible_steps: bool = True
+
     layer_2_circuit_input = yield ModifyL2CircuitForm
 
     class ModifyLayer2CircuitServiceSidesPage(SubmitFormPage):
@@ -127,11 +137,17 @@ def modify_layer_2_circuit_subscription(
 )
 def modify_layer_2_circuit() -> StepList:
     """Modify an existing Layer 2 Circuit service subscription."""
+    run_ansible_steps = conditional(lambda state: state["run_ansible_steps"])
+
     return (
         begin
         >> store_process_subscription(Target.MODIFY)
         >> unsync
+        >> run_ansible_steps(generate_fqdn_list)
+        >> run_ansible_steps(extract_partner_name_from_edge_port)
         >> modify_layer_2_circuit_subscription
+        >> run_ansible_steps(lso_interaction(provision_l2circuit_dry))
+        >> run_ansible_steps(lso_interaction(provision_l2circuit_real))
         >> resync
         >> done
     )
diff --git a/gso/workflows/l2_circuit/shared_steps.py b/gso/workflows/l2_circuit/shared_steps.py
new file mode 100644
index 0000000000000000000000000000000000000000..48b6429b6405afdfc1f46fcbed48f4dfe4a99c3c
--- /dev/null
+++ b/gso/workflows/l2_circuit/shared_steps.py
@@ -0,0 +1,121 @@
+"""Workflow steps that are used in multiple Layer 2 Circuit workflows."""
+
+from copy import deepcopy
+from typing import Any
+
+from orchestrator import step
+from pydantic_forms.types import State, UUIDstr
+
+from gso.products.product_types.edge_port import EdgePort
+from gso.products.product_types.layer_2_circuit import Layer2Circuit
+from gso.services.lso_client import LSOState
+from gso.services.partners import get_partner_by_id
+from gso.utils.types.tt_number import TTNumber
+
+
+@step("Generate FQDN list")
+def generate_fqdn_list(subscription: Layer2Circuit) -> State:
+    """Generate the list of FQDNs that this workflow should target.
+
+    This list will consist of two elements, one for each far end of the circuit.
+    """
+    return {
+        "fqdn_list": [
+            side.sbp.edge_port.node.router_fqdn for side in subscription.layer_2_circuit.layer_2_circuit_sides
+        ]
+    }
+
+
+@step("Expand subscription dictionary")
+def extract_partner_name_from_edge_port(subscription: dict[str, Any]) -> State:
+    """Expand a subscription model of a Layer 2 Circuit.
+
+    This method will include the name of each Edge Port's partner to be used in Ansible playbooks.
+    """
+    modified_subscription = deepcopy(subscription)
+    for side in modified_subscription["layer_2_circuit"]["layer_2_circuit_sides"]:
+        side["sbp"]["edge_port"]["partner_name"] = get_partner_by_id(
+            EdgePort.from_subscription(side["sbp"]["edge_port"]["owner_subscription_id"]).customer_id
+        ).name
+
+    return {"modified_subscription": modified_subscription}
+
+
+@step("[DRY RUN] Deploy L2circuit")
+def provision_l2circuit_dry(
+    modified_subscription: dict[str, Any], process_id: UUIDstr, tt_number: str, fqdn_list: list[str]
+) -> LSOState:
+    """Perform a dry run of deploying a Layer 2 Circuit."""
+    extra_vars = {
+        "subscription": modified_subscription,
+        "dry_run": True,
+        "verb": "deploy",
+        "commit_comment": f"GSO_PROCESS_ID: {process_id} - TT_NUMBER: {tt_number} - "
+        f"Deploy config for {modified_subscription["description"]}",
+    }
+
+    return {
+        "playbook_name": "gap_ansible/playbooks/l2circuit.yaml",
+        "inventory": {"all": {"hosts": dict.fromkeys(fqdn_list)}},
+        "extra_vars": extra_vars,
+    }
+
+
+@step("[REAL RUN] Deploy L2circuit")
+def provision_l2circuit_real(
+    modified_subscription: dict[str, Any], process_id: UUIDstr, tt_number: str, fqdn_list: list[str]
+) -> LSOState:
+    """Perform a dry run of deploying a Layer 2 Circuit."""
+    extra_vars = {
+        "subscription": modified_subscription,
+        "dry_run": False,
+        "verb": "deploy",
+        "commit_comment": f"GSO_PROCESS_ID: {process_id} - TT_NUMBER: {tt_number} - "
+        f"Deploy config for {modified_subscription["description"]}",
+    }
+
+    return {
+        "playbook_name": "gap_ansible/playbooks/l2circuit.yaml",
+        "inventory": {"all": {"hosts": dict.fromkeys(fqdn_list)}},
+        "extra_vars": extra_vars,
+    }
+
+
+@step("[DRY RUN] Remove old config")
+def terminate_l2circuit_dry(
+    process_id: UUIDstr, tt_number: TTNumber, modified_subscription: dict[str, Any], fqdn_list: list[str]
+) -> LSOState:
+    """Perform a dry run of removing old configuration of a Layer 2 Circuit."""
+    extra_vars = {
+        "subscription": modified_subscription,
+        "dry_run": True,
+        "verb": "terminate",
+        "commit_comment": f"GSO_PROCESS_ID: {process_id} - TT_NUMBER: {tt_number} - "
+        f"Remove config for {modified_subscription["description"]}",
+    }
+
+    return {
+        "playbook_name": "gap_ansible/playbooks/l2circuit.yaml",
+        "inventory": {"all": {"hosts": dict.fromkeys(fqdn_list)}},
+        "extra_vars": extra_vars,
+    }
+
+
+@step("[FOR REAL] Remove old config")
+def terminate_l2circuit_real(
+    process_id: UUIDstr, tt_number: TTNumber, modified_subscription: dict[str, Any], fqdn_list: list[str]
+) -> LSOState:
+    """Remove old configuration of a Layer 2 Circuit."""
+    extra_vars = {
+        "subscription": modified_subscription,
+        "dry_run": False,
+        "verb": "terminate",
+        "commit_comment": f"GSO_PROCESS_ID: {process_id} - TT_NUMBER: {tt_number} - "
+        f"Remove config for {modified_subscription["description"]}",
+    }
+
+    return {
+        "playbook_name": "gap_ansible/playbooks/l2circuit.yaml",
+        "inventory": {"all": {"hosts": dict.fromkeys(fqdn_list)}},
+        "extra_vars": extra_vars,
+    }
diff --git a/gso/workflows/l2_circuit/terminate_layer_2_circuit.py b/gso/workflows/l2_circuit/terminate_layer_2_circuit.py
index e8773a678ea5ce19c0dddef11e19d036f622460e..bab755eb824c8d92f0ed1e91d815eaf6a9df1fb4 100644
--- a/gso/workflows/l2_circuit/terminate_layer_2_circuit.py
+++ b/gso/workflows/l2_circuit/terminate_layer_2_circuit.py
@@ -4,13 +4,22 @@ from orchestrator import begin, workflow
 from orchestrator.forms import SubmitFormPage
 from orchestrator.targets import Target
 from orchestrator.types import SubscriptionLifecycle
-from orchestrator.workflow import StepList, done
+from orchestrator.workflow import StepList, conditional, 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 import Field
 from pydantic_forms.types import FormGenerator, UUIDstr
+from pydantic_forms.validators import Label
 
 from gso.products.product_types.layer_2_circuit import Layer2Circuit
+from gso.services.lso_client import lso_interaction
 from gso.utils.types.tt_number import TTNumber
+from gso.workflows.l2_circuit.shared_steps import (
+    extract_partner_name_from_edge_port,
+    generate_fqdn_list,
+    terminate_l2circuit_dry,
+    terminate_l2circuit_real,
+)
 
 
 def _input_form_generator(subscription_id: UUIDstr) -> FormGenerator:
@@ -19,8 +28,11 @@ def _input_form_generator(subscription_id: UUIDstr) -> FormGenerator:
     class TerminateForm(SubmitFormPage):
         tt_number: TTNumber
 
-    yield TerminateForm
-    return {"subscription": layer_2_circuit}
+        label: Label = Field("Should this workflow run Ansible playbooks to remove configuration from routers?")
+        run_ansible_steps: bool = True
+
+    user_input = yield TerminateForm
+    return {"subscription": layer_2_circuit} | user_input.model_dump()
 
 
 @workflow(
@@ -30,10 +42,16 @@ def _input_form_generator(subscription_id: UUIDstr) -> FormGenerator:
 )
 def terminate_layer_2_circuit() -> StepList:
     """Terminate a Layer 2 Circuit subscription."""
+    run_ansible_steps = conditional(lambda state: state["run_ansible_steps"])
+
     return (
         begin
         >> store_process_subscription(Target.TERMINATE)
         >> unsync
+        >> run_ansible_steps(generate_fqdn_list)
+        >> run_ansible_steps(extract_partner_name_from_edge_port)
+        >> run_ansible_steps(lso_interaction(terminate_l2circuit_dry))
+        >> run_ansible_steps(lso_interaction(terminate_l2circuit_real))
         >> set_status(SubscriptionLifecycle.TERMINATED)
         >> resync
         >> done
diff --git a/gso/workflows/l3_core_service/validate_prefix_list.py b/gso/workflows/l3_core_service/validate_prefix_list.py
index 9104f04a35500d98ab72bd4d856407a6e9cd6481..9c9b6cd4321e07d78ce98524487d880f8b1cfb54 100644
--- a/gso/workflows/l3_core_service/validate_prefix_list.py
+++ b/gso/workflows/l3_core_service/validate_prefix_list.py
@@ -114,6 +114,7 @@ def validate_prefix_list() -> StepList:
     prefix_list_should_be_validated = conditional(
         lambda state: state["subscription"]["l3_core_service_type"] == L3CoreServiceType.GEANT_IP
     )
+    fqdn_list_is_empty = conditional(lambda state: state["ap_fqdn_list"] == [])
     prefix_list_has_drifted = conditional(lambda state: bool(state["prefix_list_drift"]))
 
     redeploy_prefix_list_steps = (
@@ -134,6 +135,7 @@ def validate_prefix_list() -> StepList:
         begin
         >> store_process_subscription(Target.SYSTEM)
         >> build_fqdn_list
+        >> fqdn_list_is_empty(done)
         >> prefix_list_should_be_validated(prefix_list_validation_steps)
         >> done
     )
diff --git a/gso/workflows/router/promote_p_to_pe.py b/gso/workflows/router/promote_p_to_pe.py
index ce961ed10581017bc7a592031fd36c1197c8cad7..25d4363360ace0cee93d5895089992021a6e90ec 100644
--- a/gso/workflows/router/promote_p_to_pe.py
+++ b/gso/workflows/router/promote_p_to_pe.py
@@ -30,11 +30,11 @@ from gso.utils.workflow_steps import (
     add_pe_to_all_p_real,
     add_pe_to_pe_mesh_dry,
     add_pe_to_pe_mesh_real,
+    add_pe_to_sdp_mesh_dry,
+    add_pe_to_sdp_mesh_real,
     check_l3_services,
     check_pe_ibgp,
     create_kentik_device,
-    update_sdp_mesh_dry,
-    update_sdp_mesh_real,
 )
 
 
@@ -308,8 +308,8 @@ def promote_p_to_pe() -> StepList:
         >> lso_interaction(deploy_routing_instances_dry)
         >> lso_interaction(deploy_routing_instances_real)
         >> lso_interaction(check_l3_services)
-        >> lso_interaction(update_sdp_mesh_dry)
-        >> lso_interaction(update_sdp_mesh_real)
+        >> lso_interaction(add_pe_to_sdp_mesh_dry)
+        >> lso_interaction(add_pe_to_sdp_mesh_real)
         >> lso_interaction(add_all_p_to_pe_dry)
         >> lso_interaction(add_all_p_to_pe_real)
         >> lso_interaction(add_pe_to_all_p_dry)
diff --git a/gso/workflows/router/terminate_router.py b/gso/workflows/router/terminate_router.py
index b5464513ef0d5cb0574d826ca65883ea68e8e03f..4599c6f099555b79a9a5a6a5f1229f4878dd8013 100644
--- a/gso/workflows/router/terminate_router.py
+++ b/gso/workflows/router/terminate_router.py
@@ -49,6 +49,10 @@ from gso.settings import load_oss_params
 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 (
+    remove_pe_from_sdp_mesh_dry,
+    remove_pe_from_sdp_mesh_real,
+)
 
 logger = logging.getLogger(__name__)
 
@@ -67,9 +71,11 @@ def initial_input_form_generator(subscription_id: UUIDstr) -> FormGenerator:
 
         tt_number: TTNumber
         termination_label: Label = "Please confirm whether configuration should get removed from the router."
-        remove_configuration: bool = True
+        remove_configuration: bool = False
         update_ibgp_mesh_label: Label = "Please confirm whether the iBGP mesh should get updated."
         update_ibgp_mesh: bool = True
+        update_sdp_mesh_label: Label = "Please confirm whether the SDP mesh should get updated."
+        update_sdp_mesh: bool = True
 
     user_input = yield TerminateForm
     return user_input.model_dump() | {
@@ -332,6 +338,7 @@ def terminate_router() -> StepList:
     """
     run_config_steps = conditional(lambda state: state["remove_configuration"])
     update_ibgp_mesh = conditional(lambda state: state["update_ibgp_mesh"])
+    update_sdp_mesh = conditional(lambda state: state["update_sdp_mesh"])
     router_is_nokia = conditional(lambda state: state["router_is_nokia"])
     router_is_pe = conditional(lambda state: state["router_role"] == RouterRole.PE)
     router_is_p = conditional(lambda state: state["router_role"] == RouterRole.P)
@@ -346,6 +353,8 @@ def terminate_router() -> StepList:
         >> update_ibgp_mesh(router_is_pe(lso_interaction(remove_pe_from_all_pe_real)))
         >> update_ibgp_mesh(router_is_pe(lso_interaction(remove_pe_from_all_p_dry)))
         >> update_ibgp_mesh(router_is_pe(lso_interaction(remove_pe_from_all_p_real)))
+        >> update_sdp_mesh(router_is_pe(lso_interaction(remove_pe_from_sdp_mesh_dry)))
+        >> update_sdp_mesh(router_is_pe(lso_interaction(remove_pe_from_sdp_mesh_real)))
         >> deprovision_loopback_ips
         >> run_config_steps(lso_interaction(remove_config_from_router_dry))
         >> run_config_steps(lso_interaction(remove_config_from_router_real))
diff --git a/gso/workflows/router/update_ibgp_mesh.py b/gso/workflows/router/update_ibgp_mesh.py
index 2c8c9db00fbc54649a8182f236e48648a899e359..d04a50946f6a7567335e800adb9698e1683bc926 100644
--- a/gso/workflows/router/update_ibgp_mesh.py
+++ b/gso/workflows/router/update_ibgp_mesh.py
@@ -48,10 +48,10 @@ from gso.utils.workflow_steps import (
     add_pe_to_all_p_real,
     add_pe_to_pe_mesh_dry,
     add_pe_to_pe_mesh_real,
+    add_pe_to_sdp_mesh_dry,
+    add_pe_to_sdp_mesh_real,
     check_l3_services,
     check_pe_ibgp,
-    update_sdp_mesh_dry,
-    update_sdp_mesh_real,
     update_sdp_single_pe_dry,
     update_sdp_single_pe_real,
 )
@@ -251,8 +251,8 @@ def update_ibgp_mesh() -> StepList:
         >> router_is_pe(lso_interaction(add_pe_to_all_p_real))
         >> router_is_pe(lso_interaction(update_sdp_single_pe_dry))
         >> router_is_pe(lso_interaction(update_sdp_single_pe_real))
-        >> router_is_pe(lso_interaction(update_sdp_mesh_dry))
-        >> router_is_pe(lso_interaction(update_sdp_mesh_real))
+        >> router_is_pe(lso_interaction(add_pe_to_sdp_mesh_dry))
+        >> router_is_pe(lso_interaction(add_pe_to_sdp_mesh_real))
         >> router_is_pe(lso_interaction(check_pe_ibgp))
         >> router_is_pe(lso_interaction(check_l3_services))
         >> add_device_to_librenms
diff --git a/pyproject.toml b/pyproject.toml
index 6fb91172edfe7f78200037184ba51e1f77cad78f..34e477ffd71d29f1bd31b5d2ea0282e99d0ed05a 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -46,6 +46,7 @@ ignore = [
     "PLR0913",
     "PLR0904",
     "PLW1514",
+    "S311",
 ]
 select = [
     "A",
diff --git a/setup.py b/setup.py
index f0cf0320874b1d1fb4e6fbedea918c2dad9f9e16..fb8eab0f1e24782746e85d98d123c871fb79fc2f 100644
--- a/setup.py
+++ b/setup.py
@@ -4,7 +4,7 @@ from setuptools import find_packages, setup
 
 setup(
     name="geant-service-orchestrator",
-    version="2.45",
+    version="2.46",
     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 bc882410526e73dde39025e5da0adb91bd8881a5..26ab3bdd918da528e4c2f1564a8ced089caf803c 100644
--- a/test/cli/test_imports.py
+++ b/test/cli/test_imports.py
@@ -405,11 +405,12 @@ def l3_core_service_data(temp_file, faker, partner_factory, edge_port_subscripti
 @pytest.fixture()
 def layer_2_circuit_data(temp_file, faker, partner_factory, edge_port_subscription_factory):
     def _layer_2_circuit_data(**kwargs):
+        circuit_type = Layer2CircuitType.VLAN
         layer_2_circuit_input_data = {
             "partner": partner_factory()["name"],
             "service_type": Layer2CircuitServiceType.GEANT_PLUS,
             "gs_id": faker.imported_gs_id(),
-            "vc_id": generate_unique_vc_id(),
+            "vc_id": generate_unique_vc_id(circuit_type),
             "layer_2_circuit_side_a": {
                 "edge_port": str(edge_port_subscription_factory().subscription_id),
                 "vlan_id": faker.vlan_id(),
@@ -418,7 +419,7 @@ def layer_2_circuit_data(temp_file, faker, partner_factory, edge_port_subscripti
                 "edge_port": str(edge_port_subscription_factory().subscription_id),
                 "vlan_id": faker.vlan_id(),
             },
-            "layer_2_circuit_type": Layer2CircuitType.VLAN,
+            "layer_2_circuit_type": circuit_type,
             "vlan_range_lower_bound": faker.vlan_id(),
             "vlan_range_upper_bound": faker.vlan_id(),
             "policer_enabled": False,
diff --git a/test/fixtures/edge_port_fixtures.py b/test/fixtures/edge_port_fixtures.py
index ad6da994a083b5746107d381d831de6dc1a291ff..7ec1e0d9357e1e6174116c409f4e400a4ad78b95 100644
--- a/test/fixtures/edge_port_fixtures.py
+++ b/test/fixtures/edge_port_fixtures.py
@@ -16,7 +16,7 @@ from gso.utils.types.interfaces import PhysicalPortCapacity
 
 
 @pytest.fixture()
-def edge_port_subscription_factory(faker, partner_factory, router_subscription_factory):
+def edge_port_subscription_factory(faker, geant_partner, router_subscription_factory):
     def subscription_create(
         description=None,
         partner: dict | None = None,
@@ -38,7 +38,8 @@ def edge_port_subscription_factory(faker, partner_factory, router_subscription_f
         ignore_if_down=False,
         is_imported=False,
     ) -> SubscriptionModel:
-        partner = partner or partner_factory()
+        #  Use default partner if none defined
+        partner = partner or geant_partner
         node = node or router_subscription_factory(vendor=Vendor.NOKIA).router
 
         if is_imported:
diff --git a/test/fixtures/l3_core_service_fixtures.py b/test/fixtures/l3_core_service_fixtures.py
index bffe03dae1e11dfe8e06e5b2cd4bd02543cc4627..fc72ca3998ccec8c03e893f4178c452fbabf6e6f 100644
--- a/test/fixtures/l3_core_service_fixtures.py
+++ b/test/fixtures/l3_core_service_fixtures.py
@@ -133,7 +133,7 @@ def access_port_factory(faker, service_binding_port_factory):
     ):
         return AccessPort.new(
             subscription_id=uuid4(),
-            ap_type=ap_type or random.choice(list(APType)),  # noqa: S311
+            ap_type=ap_type or random.choice(list(APType)),
             sbp=service_binding_port or service_binding_port_factory(edge_port=edge_port, partner=partner),
         )
 
diff --git a/test/fixtures/layer_2_circuit_fixtures.py b/test/fixtures/layer_2_circuit_fixtures.py
index f00b3375ce9da225e07e0259991019aa137066fc..584df642c94b7f010f0d219fa95498a5aafc58e1 100644
--- a/test/fixtures/layer_2_circuit_fixtures.py
+++ b/test/fixtures/layer_2_circuit_fixtures.py
@@ -96,22 +96,16 @@ def layer_2_circuit_subscription_factory(faker, geant_partner, edge_port_subscri
             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.virtual_circuit_id = generate_unique_vc_id(layer_2_circuit_type)
         subscription.layer_2_circuit.layer_2_circuit_type = layer_2_circuit_type
         if layer_2_circuit_type == Layer2CircuitType.VLAN:
             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} - {subscription.layer_2_circuit.virtual_circuit_id}"
         )
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
index 49e19bc7c693e3a1f9d5be14cbab6898a439f18b..84d894d98c25784406b86d032575991676acda74 100644
--- a/test/workflows/l2_circuit/test_create_imported_layer_2_circuit.py
+++ b/test/workflows/l2_circuit/test_create_imported_layer_2_circuit.py
@@ -17,15 +17,16 @@ def test_create_imported_layer_2_circuit_success(
     edge_port_a = str(edge_port_subscription_factory(partner=partner).subscription_id)
     edge_port_b = str(edge_port_subscription_factory(partner=partner).subscription_id)
     policer_enabled = faker.boolean()
+    circuit_type = Layer2CircuitType.VLAN
     creation_form_input_data = [
         {
             "service_type": layer_2_service_type,
             "partner": partner["name"],
-            "layer_2_circuit_type": Layer2CircuitType.VLAN,
+            "layer_2_circuit_type": circuit_type,
             "policer_enabled": policer_enabled,
             "vlan_range_lower_bound": faker.vlan_id(),
             "vlan_range_upper_bound": faker.vlan_id(),
-            "vc_id": generate_unique_vc_id(),
+            "vc_id": generate_unique_vc_id(circuit_type),
             "policer_bandwidth": faker.bandwidth() if policer_enabled else None,
             "policer_burst_rate": faker.bandwidth() if policer_enabled else None,
             "gs_id": faker.imported_gs_id(),
diff --git a/test/workflows/l2_circuit/test_create_layer_2_circuit.py b/test/workflows/l2_circuit/test_create_layer_2_circuit.py
index ecec066a438b591d14b138bf16a3be93a4b1735d..4d282f6f73f5f2df113a069c75a2f3126773c2d6 100644
--- a/test/workflows/l2_circuit/test_create_layer_2_circuit.py
+++ b/test/workflows/l2_circuit/test_create_layer_2_circuit.py
@@ -1,10 +1,12 @@
+from unittest.mock import patch
+
 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 LAYER_2_CIRCUIT_SERVICE_TYPES, Layer2Circuit
 from gso.services.subscriptions import get_product_id_by_name
-from test.workflows import assert_complete, extract_state, run_workflow
+from test.workflows import assert_complete, assert_lso_interaction_success, extract_state, run_workflow
 
 
 def generate_layer_2_circuit_input(
@@ -58,16 +60,23 @@ def layer_2_circuit_ethernet_input(
 
 @pytest.mark.parametrize("layer_2_circuit_service_type", LAYER_2_CIRCUIT_SERVICE_TYPES)
 @pytest.mark.workflow()
+@patch("gso.services.lso_client._send_request")
 def test_create_layer_2_circuit_success(
+    mock_lso_interaction,
     layer_2_circuit_service_type,
     layer_2_circuit_input,
     faker,
     partner_factory,
 ):
-    result, _, _ = run_workflow("create_layer_2_circuit", layer_2_circuit_input)
+    result, process_stat, step_log = run_workflow("create_layer_2_circuit", layer_2_circuit_input)
+
+    for _ in range(2):
+        result, step_log = assert_lso_interaction_success(result, process_stat, step_log)
+
     assert_complete(result)
     state = extract_state(result)
     subscription = Layer2Circuit.from_subscription(state["subscription_id"])
+    assert mock_lso_interaction.call_count == 2
     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
@@ -104,16 +113,23 @@ def test_create_layer_2_circuit_success(
 
 @pytest.mark.parametrize("layer_2_circuit_service_type", LAYER_2_CIRCUIT_SERVICE_TYPES)
 @pytest.mark.workflow()
+@patch("gso.services.lso_client._send_request")
 def test_create_layer_2_circuit_with_ethernet_type(
+    mock_lso_interaction,
     layer_2_circuit_service_type,
     layer_2_circuit_ethernet_input,
     faker,
     partner_factory,
 ):
-    result, _, _ = run_workflow("create_layer_2_circuit", layer_2_circuit_ethernet_input)
+    result, process_stat, step_log = run_workflow("create_layer_2_circuit", layer_2_circuit_ethernet_input)
+
+    for _ in range(2):
+        result, step_log = assert_lso_interaction_success(result, process_stat, step_log)
+
     assert_complete(result)
     state = extract_state(result)
     subscription = Layer2Circuit.from_subscription(state["subscription_id"])
+    assert mock_lso_interaction.call_count == 2
     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
diff --git a/test/workflows/l2_circuit/test_migrate_layer_2_circuit.py b/test/workflows/l2_circuit/test_migrate_layer_2_circuit.py
new file mode 100644
index 0000000000000000000000000000000000000000..9c016cd28a5abbbd1a3d9cfd8572eeb78c5fedff
--- /dev/null
+++ b/test/workflows/l2_circuit/test_migrate_layer_2_circuit.py
@@ -0,0 +1,72 @@
+from unittest.mock import patch
+
+import pytest
+
+from gso.products.product_types.edge_port import EdgePort
+from gso.products.product_types.layer_2_circuit import LAYER_2_CIRCUIT_SERVICE_TYPES, Layer2Circuit
+from gso.products.product_types.router import Router
+from test.workflows import assert_complete, assert_lso_interaction_success, extract_state, run_workflow
+
+
+@pytest.mark.workflow()
+@pytest.mark.parametrize("layer_2_circuit_service_type", LAYER_2_CIRCUIT_SERVICE_TYPES)
+@pytest.mark.parametrize("run_old_side_ansible", [False, True])
+@pytest.mark.parametrize("run_new_side_ansible", [False, True])
+@patch("gso.services.lso_client._send_request")
+def test_migrate_layer_2_circuit(
+    mock_lso_interaction,
+    run_new_side_ansible,
+    run_old_side_ansible,
+    layer_2_circuit_service_type,
+    layer_2_circuit_subscription_factory,
+    edge_port_subscription_factory,
+    faker,
+):
+    subscription = layer_2_circuit_subscription_factory(layer_2_circuit_service_type=layer_2_circuit_service_type)
+    side_b_router = Router.from_subscription(
+        subscription.layer_2_circuit.layer_2_circuit_sides[1].sbp.edge_port.node.owner_subscription_id
+    )
+    old_edge_port = EdgePort.from_subscription(
+        subscription.layer_2_circuit.layer_2_circuit_sides[1].sbp.edge_port.owner_subscription_id
+    )
+    new_edge_port = edge_port_subscription_factory(node=side_b_router.router)
+
+    initial_layer_2_circuit_data = [
+        {"subscription_id": subscription.subscription_id},
+        {
+            "tt_number": faker.tt_number(),
+            "replace_side": old_edge_port.subscription_id,
+            "migrate_to_different_site": False,
+            "run_old_side_ansible": run_old_side_ansible,
+            "run_new_side_ansible": run_new_side_ansible,
+        },
+        {"new_edge_port": new_edge_port.subscription_id},
+    ]
+
+    result, process_stat, step_log = run_workflow("migrate_layer_2_circuit", initial_layer_2_circuit_data)
+
+    lso_step_count = 0
+    lso_step_count += 2 if run_old_side_ansible else 0
+    lso_step_count += 2 if run_new_side_ansible else 0
+
+    for _ in range(lso_step_count):
+        result, step_log = assert_lso_interaction_success(result, process_stat, step_log)
+
+    assert_complete(result)
+
+    state = extract_state(result)
+    subscription_id = state["subscription_id"]
+    subscription = Layer2Circuit.from_subscription(subscription_id)
+    assert subscription.status == "active"
+    assert mock_lso_interaction.call_count == lso_step_count
+
+    replaced_edge_port = subscription.layer_2_circuit.layer_2_circuit_sides[1].sbp.edge_port
+    assert replaced_edge_port.model_dump(exclude="edge_port_ae_members") == new_edge_port.edge_port.model_dump(
+        exclude="edge_port_ae_members"
+    )
+    assert replaced_edge_port.edge_port_ae_members[0].model_dump(
+        exclude="owner_subscription_id"
+    ) == new_edge_port.edge_port.edge_port_ae_members[0].model_dump(exclude="owner_subscription_id")
+    assert replaced_edge_port.edge_port_ae_members[1].model_dump(
+        exclude="owner_subscription_id"
+    ) == new_edge_port.edge_port.edge_port_ae_members[1].model_dump(exclude="owner_subscription_id")
diff --git a/test/workflows/l2_circuit/test_modify_layer_2_circuit.py b/test/workflows/l2_circuit/test_modify_layer_2_circuit.py
index cc6f773c85c8cb3ececb9e86d240fa5fd5924c44..e4f4dcce33efeabe2dac11c54a41b014577ab0de 100644
--- a/test/workflows/l2_circuit/test_modify_layer_2_circuit.py
+++ b/test/workflows/l2_circuit/test_modify_layer_2_circuit.py
@@ -1,15 +1,21 @@
+from unittest.mock import patch
+
 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 LAYER_2_CIRCUIT_SERVICE_TYPES, Layer2Circuit
-from test.workflows import assert_complete, extract_state, run_workflow
+from test.workflows import assert_complete, assert_lso_interaction_success, extract_state, run_workflow
 
 
 @pytest.mark.parametrize("layer_2_circuit_service_type", LAYER_2_CIRCUIT_SERVICE_TYPES)
+@pytest.mark.parametrize("run_ansible_steps", [False, True])
 @pytest.mark.workflow()
+@patch("gso.services.lso_client._send_request")
 def test_modify_layer_2_circuit_change_policer_bandwidth(
+    mock_lso_interaction,
     layer_2_circuit_service_type,
+    run_ansible_steps,
     layer_2_circuit_subscription_factory,
     faker,
     partner_factory,
@@ -22,6 +28,7 @@ def test_modify_layer_2_circuit_change_policer_bandwidth(
             "layer_2_circuit_type": Layer2CircuitType.VLAN,
             "policer_enabled": False,
             "custom_service_name": faker.sentence(),
+            "run_ansible_steps": run_ansible_steps,
         },
         {
             "vlan_range_lower_bound": subscription.layer_2_circuit.vlan_range_lower_bound,
@@ -32,9 +39,16 @@ def test_modify_layer_2_circuit_change_policer_bandwidth(
             "layer_2_circuit_side_b": {},
         },
     ]
-    result, _, _ = run_workflow("modify_layer_2_circuit", input_form_data)
+    result, process_stat, step_log = run_workflow("modify_layer_2_circuit", input_form_data)
+
+    lso_step_count = 2 if run_ansible_steps else 0
+
+    for _ in range(lso_step_count):
+        result, step_log = assert_lso_interaction_success(result, process_stat, step_log)
+
     subscription = Layer2Circuit.from_subscription(str(subscription.subscription_id))
     assert_complete(result)
+    assert mock_lso_interaction.call_count == lso_step_count
     assert subscription.status == SubscriptionLifecycle.ACTIVE
     assert subscription.layer_2_circuit.policer_enabled is False
     assert subscription.layer_2_circuit.bandwidth is None
@@ -54,9 +68,13 @@ def test_modify_layer_2_circuit_change_policer_bandwidth(
 
 
 @pytest.mark.parametrize("layer_2_circuit_service_type", LAYER_2_CIRCUIT_SERVICE_TYPES)
+@pytest.mark.parametrize("run_ansible_steps", [False, True])
 @pytest.mark.workflow()
+@patch("gso.services.lso_client._send_request")
 def test_modify_layer_2_circuit_change_circuit_type(
+    mock_lso_interaction,
     layer_2_circuit_service_type,
+    run_ansible_steps,
     layer_2_circuit_subscription_factory,
     faker,
     partner_factory,
@@ -67,6 +85,7 @@ def test_modify_layer_2_circuit_change_circuit_type(
         {
             "tt_number": faker.tt_number(),
             "layer_2_circuit_type": Layer2CircuitType.ETHERNET,
+            "run_ansible_steps": run_ansible_steps,
         },
         {
             "vlan_range_lower_bound": None,
@@ -76,10 +95,17 @@ def test_modify_layer_2_circuit_change_circuit_type(
             "layer_2_circuit_side_b": {"vlan_id": faker.vlan_id()},
         },
     ]
-    result, _, _ = run_workflow("modify_layer_2_circuit", input_form_data)
+    result, process_stat, step_log = run_workflow("modify_layer_2_circuit", input_form_data)
+
+    lso_step_count = 2 if run_ansible_steps else 0
+
+    for _ in range(lso_step_count):
+        result, step_log = assert_lso_interaction_success(result, process_stat, step_log)
+
     assert_complete(result)
     state = extract_state(result)
     subscription = Layer2Circuit.from_subscription(state["subscription_id"])
+    assert mock_lso_interaction.call_count == lso_step_count
     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
diff --git a/test/workflows/l2_circuit/test_terminate_layer_2_circuit.py b/test/workflows/l2_circuit/test_terminate_layer_2_circuit.py
index 750c7b06c06984229940dca59bfaa0751a085d17..55deedededc9c3c43ef3ca56d6145508cbf7f5d4 100644
--- a/test/workflows/l2_circuit/test_terminate_layer_2_circuit.py
+++ b/test/workflows/l2_circuit/test_terminate_layer_2_circuit.py
@@ -1,20 +1,36 @@
+from unittest.mock import patch
+
 import pytest
 
 from gso.products.product_types.layer_2_circuit import LAYER_2_CIRCUIT_SERVICE_TYPES, Layer2Circuit
-from test.workflows import assert_complete, extract_state, run_workflow
+from test.workflows import assert_complete, assert_lso_interaction_success, extract_state, run_workflow
 
 
 @pytest.mark.workflow()
 @pytest.mark.parametrize("layer_2_circuit_service_type", LAYER_2_CIRCUIT_SERVICE_TYPES)
-def test_terminate_layer_2_circuit(layer_2_circuit_service_type, layer_2_circuit_subscription_factory, faker):
+@pytest.mark.parametrize("run_ansible_steps", [True, False])
+@patch("gso.services.lso_client._send_request")
+def test_terminate_layer_2_circuit(
+    mock_lso_interaction, layer_2_circuit_service_type, run_ansible_steps, layer_2_circuit_subscription_factory, faker
+):
     subscription_id = str(
         layer_2_circuit_subscription_factory(layer_2_circuit_service_type=layer_2_circuit_service_type).subscription_id
     )
-    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)
+    initial_layer_2_circuit_data = [
+        {"subscription_id": subscription_id},
+        {"tt_number": faker.tt_number(), "run_ansible_steps": run_ansible_steps},
+    ]
+    result, process_stat, step_log = run_workflow("terminate_layer_2_circuit", initial_layer_2_circuit_data)
+
+    lso_step_count = 2 if run_ansible_steps else 0
+
+    for _ in range(lso_step_count):
+        result, step_log = assert_lso_interaction_success(result, process_stat, step_log)
+
     assert_complete(result)
 
     state = extract_state(result)
     subscription_id = state["subscription_id"]
     subscription = Layer2Circuit.from_subscription(subscription_id)
     assert subscription.status == "terminated"
+    assert mock_lso_interaction.call_count == lso_step_count
diff --git a/test/workflows/l3_core_service/test_validate_prefix_list.py b/test/workflows/l3_core_service/test_validate_prefix_list.py
index c4fe2ed976d1e89d555838a2fb54e8e90b0c586a..5ec52d8e58a29f9dd6f7c05a587a5ae83cf63a01 100644
--- a/test/workflows/l3_core_service/test_validate_prefix_list.py
+++ b/test/workflows/l3_core_service/test_validate_prefix_list.py
@@ -3,7 +3,7 @@ from unittest.mock import patch
 import pytest
 
 from gso.products.product_types.l3_core_service import L3_CORE_SERVICE_TYPES, L3CoreService, L3CoreServiceType
-from gso.utils.shared_enums import Vendor
+from gso.utils.shared_enums import APType, Vendor
 from test import USER_CONFIRM_EMPTY_FORM
 from test.workflows import (
     assert_complete,
@@ -41,9 +41,6 @@ def test_validate_prefix_list_success(
     subscription = L3CoreService.from_subscription(subscription_id)
     assert subscription.status == "active"
     assert subscription.insync is True
-    # Verify the subscription has no Juniper devices
-    for ap in subscription.l3_core_service.ap_list:
-        assert ap.sbp.edge_port.node.vendor != Vendor.JUNIPER
     # Verify the number of LSO interactions
     assert mock_lso_interaction.call_count == (1 if should_run_validation else 0)
 
@@ -103,3 +100,57 @@ def test_validate_prefix_list_without_diff(mock_lso_interaction, l3_core_service
     assert subscription.insync is True
     # Verify the number of LSO interactions
     assert mock_lso_interaction.call_count == 1  # Only validation is performed
+
+
+@pytest.mark.workflow()
+def test_validate_prefix_skip_on_juniper(
+    l3_core_service_subscription_factory,
+    access_port_factory,
+    service_binding_port_factory,
+    edge_port_subscription_factory,
+    router_subscription_factory,
+    faker,
+):
+    """Test case where all APs are Juniper, and the workflow is effectively skipped."""
+    ap_list = [
+        access_port_factory(
+            ap_type=APType.PRIMARY,
+            service_binding_port=service_binding_port_factory(
+                edge_port=edge_port_subscription_factory(node=router_subscription_factory(vendor=Vendor.JUNIPER).router)
+            ),
+        ),
+        access_port_factory(
+            ap_type=APType.BACKUP,
+            service_binding_port=service_binding_port_factory(
+                edge_port=edge_port_subscription_factory(node=router_subscription_factory(vendor=Vendor.JUNIPER).router)
+            ),
+        ),
+        access_port_factory(
+            ap_type=APType.BACKUP,
+            service_binding_port=service_binding_port_factory(
+                edge_port=edge_port_subscription_factory(node=router_subscription_factory(vendor=Vendor.JUNIPER).router)
+            ),
+        ),
+        access_port_factory(
+            ap_type=APType.BACKUP,
+            service_binding_port=service_binding_port_factory(
+                edge_port=edge_port_subscription_factory(node=router_subscription_factory(vendor=Vendor.JUNIPER).router)
+            ),
+        ),
+    ]
+    subscription_id = str(
+        l3_core_service_subscription_factory(
+            l3_core_service_type=L3CoreServiceType.GEANT_IP, ap_list=ap_list
+        ).subscription_id
+    )
+    initial_l3_core_service_data = [{"subscription_id": subscription_id}]
+    # Run the workflow and extract results
+    #  Assert workflow completion since it is skipped if it is on a Juniper
+    result, _, _ = run_workflow("validate_prefix_list", initial_l3_core_service_data)
+    assert_complete(result)
+    # Extract the state and validate subscription attributes
+    state = extract_state(result)
+    subscription_id = state["subscription_id"]
+    subscription = L3CoreService.from_subscription(subscription_id)
+    assert subscription.status == "active"
+    assert subscription.insync is True
diff --git a/test/workflows/router/test_terminate_router.py b/test/workflows/router/test_terminate_router.py
index 6611bc0dc7574f729052f295194273afe3073b24..a760472efae75808a1a613d031eafb1494e1e18f 100644
--- a/test/workflows/router/test_terminate_router.py
+++ b/test/workflows/router/test_terminate_router.py
@@ -11,6 +11,7 @@ from test.workflows import assert_complete, assert_lso_interaction_success, extr
 @pytest.mark.workflow()
 @pytest.mark.parametrize("remove_configuration", [True, False])
 @pytest.mark.parametrize("update_ibgp_mesh", [True, False])
+@pytest.mark.parametrize("update_sdp_mesh", [True, False])
 @patch("gso.services.lso_client._send_request")
 @patch("gso.workflows.router.terminate_router.NetboxClient.delete_device")
 @patch("gso.workflows.router.terminate_router.infoblox.delete_host_by_ip")
@@ -24,6 +25,7 @@ def test_terminate_pe_router_full_success(
     mock_execute_playbook,
     remove_configuration,
     update_ibgp_mesh,
+    update_sdp_mesh,
     router_subscription_factory,
     faker,
 ):
@@ -36,12 +38,15 @@ def test_terminate_pe_router_full_success(
         "tt_number": faker.tt_number(),
         "remove_configuration": remove_configuration,
         "update_ibgp_mesh": update_ibgp_mesh,
+        "update_sdp_mesh": update_sdp_mesh,
     }
     lso_interaction_count = 0
     if remove_configuration:
         lso_interaction_count += 2
     if update_ibgp_mesh:
         lso_interaction_count += 4
+    if update_sdp_mesh:
+        lso_interaction_count += 2
     mock_kentik_client.return_value = MockedKentikClient
     #  Run workflow
     initial_router_data = [{"subscription_id": product_id}, router_termination_input_form_data]