From 247c178deb6dd05809b83b5b5df157300c7845f9 Mon Sep 17 00:00:00 2001
From: Karel van Klink <karel.vanklink@geant.org>
Date: Tue, 1 Oct 2024 10:26:41 +0200
Subject: [PATCH] Rework LSO interaction

Automatically skip a playbook execution if the inventory is empty to prevent hangs
---
 gso/services/lso_client.py                    |  75 +++-
 gso/utils/workflow_steps.py                   |  72 ++--
 gso/workflows/iptrunk/create_iptrunk.py       | 150 +++----
 gso/workflows/iptrunk/deploy_twamp.py         |  68 +--
 gso/workflows/iptrunk/migrate_iptrunk.py      | 395 +++++++++---------
 gso/workflows/iptrunk/modify_isis_metric.py   |  58 ++-
 .../iptrunk/modify_trunk_interface.py         | 123 +++---
 gso/workflows/iptrunk/terminate_iptrunk.py    |  48 ++-
 gso/workflows/iptrunk/validate_iptrunk.py     |  87 ++--
 gso/workflows/router/promote_p_to_pe.py       | 319 ++++++--------
 gso/workflows/router/terminate_router.py      | 113 +++--
 gso/workflows/router/update_ibgp_mesh.py      |  69 ++-
 gso/workflows/router/validate_router.py       |  34 +-
 test/workflows/iptrunk/test_create_iptrunk.py |   6 +-
 test/workflows/iptrunk/test_deploy_twamp.py   |   2 +-
 .../iptrunk/test_modify_isis_metric.py        |   2 +-
 .../iptrunk/test_terminate_iptrunk.py         |  12 +-
 .../iptrunk/test_validate_iptrunk.py          |   4 +-
 test/workflows/router/test_create_router.py   |   4 +-
 test/workflows/router/test_promote_p_to_pe.py |   7 +-
 .../workflows/router/test_terminate_router.py |   6 +
 .../workflows/router/test_update_ibgp_mesh.py |   2 +-
 test/workflows/router/test_validate_router.py |   2 +-
 23 files changed, 824 insertions(+), 834 deletions(-)

diff --git a/gso/services/lso_client.py b/gso/services/lso_client.py
index 77c22647..2215a7f5 100644
--- a/gso/services/lso_client.py
+++ b/gso/services/lso_client.py
@@ -12,7 +12,7 @@ from orchestrator import step
 from orchestrator.config.assignee import Assignee
 from orchestrator.types import State
 from orchestrator.utils.errors import ProcessFailureError
-from orchestrator.workflow import Step, StepList, begin, callback_step, inputstep
+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
@@ -48,11 +48,9 @@ def _send_request(parameters: dict, callback_route: str) -> None:
     request.raise_for_status()
 
 
-def execute_playbook(
-    playbook_name: str,
-    callback_route: str,
-    inventory: dict[str, Any] | str,
-    extra_vars: dict[str, Any],
+@step("Execute Ansible playbook")
+def _execute_playbook(
+    playbook_name: str, callback_route: str, inventory: dict[str, Any], extra_vars: dict[str, Any]
 ) -> None:
     """Execute a playbook remotely through the provisioning proxy.
 
@@ -71,7 +69,8 @@ def execute_playbook(
                     },
                     "host2.local": {
                         "key": "value"
-                    }
+                    },
+                    "host3.local": None
                 }
             }
         }
@@ -141,7 +140,38 @@ def _show_results(state: State) -> FormGenerator:
 
 @step("Clean up keys from state")
 def _clean_state() -> State:
-    return {"__remove_keys": ["run_results", "lso_result_title", "lso_result_extra_label", "callback_result"]}
+    return {
+        "__remove_keys": [
+            "run_results",
+            "lso_result_title",
+            "lso_result_extra_label",
+            "callback_result",
+            "playbook_name",
+            "callback_route",
+            "inventory",
+            "extra_vars",
+        ]
+    }
+
+
+def _inventory_is_set(state: State) -> bool:
+    """Validate whether the passed Ansible inventory is empty.
+
+    If the inventory is empty, which can happen in select cases, there should be no playbook run. This conditional will
+    prevent from calling out to :term:`LSO` with an empty playbook, which would cause the Ansible runner process to
+    hang. This in turn will result in a workflow step that is never called back to.
+    """
+    if "inventory" not in state:
+        msg = "Missing Ansible inventory for playbook."
+        raise ProcessFailureError(msg, details="Key 'inventory' not found in state.")
+    if "all" not in state["inventory"] or "hosts" not in state["inventory"]["all"]:
+        msg = "Malformed Ansible inventory found in state."
+        raise ProcessFailureError(msg, details="Ansible inventory must be in YAML form, not string.")
+
+    return state["inventory"]["all"]["hosts"]
+
+
+_inventory_is_not_empty = conditional(_inventory_is_set)
 
 
 def lso_interaction(provisioning_step: Step) -> StepList:
@@ -162,9 +192,15 @@ def lso_interaction(provisioning_step: Step) -> StepList:
     """
     return (
         begin
-        >> callback_step(name=provisioning_step.name, action_step=provisioning_step, validate_step=_evaluate_results)
-        >> step("Inject result title")(lambda: {"lso_result_title": provisioning_step.name})
-        >> _show_results
+        >> provisioning_step
+        >> _inventory_is_not_empty(
+            begin
+            >> callback_step(
+                name="Running Ansible playbook", action_step=_execute_playbook, validate_step=_evaluate_results
+            )
+            >> step("Inject result title")(lambda: {"lso_result_title": provisioning_step.name})
+            >> _show_results
+        )
         >> _clean_state
     )
 
@@ -187,9 +223,15 @@ def indifferent_lso_interaction(provisioning_step: Step) -> StepList:
     """
     return (
         begin
-        >> callback_step(name=provisioning_step.name, action_step=provisioning_step, validate_step=_ignore_results)
-        >> step("Inject result title")(lambda: {"lso_result_title": provisioning_step.name})
-        >> _show_results
+        >> provisioning_step
+        >> _inventory_is_not_empty(
+            begin
+            >> callback_step(
+                name="Running Ansible playbook", action_step=_execute_playbook, validate_step=_ignore_results
+            )
+            >> step("Inject result title")(lambda: {"lso_result_title": provisioning_step.name})
+            >> _show_results
+        )
         >> _clean_state
     )
 
@@ -207,6 +249,9 @@ def anonymous_lso_interaction(provisioning_step: Step, validation_step: Step = _
     """
     return (
         begin
-        >> callback_step(name=provisioning_step.name, action_step=provisioning_step, validate_step=validation_step)
+        >> provisioning_step
+        >> _inventory_is_not_empty(
+            callback_step(name="Running Ansible playbook", action_step=_execute_playbook, validate_step=validation_step)
+        )
         >> _clean_state
     )
diff --git a/gso/utils/workflow_steps.py b/gso/utils/workflow_steps.py
index 93891456..5a714e1b 100644
--- a/gso/utils/workflow_steps.py
+++ b/gso/utils/workflow_steps.py
@@ -13,20 +13,16 @@ from pydantic_forms.types import FormGenerator
 from pydantic_forms.validators import Label
 
 from gso.products.product_types.iptrunk import Iptrunk
-from gso.services import lso_client
 from gso.settings import load_oss_params
 
 
 def _deploy_base_config(
     subscription: dict[str, Any],
     tt_number: str,
-    callback_route: str,
     process_id: UUIDstr,
     *,
     dry_run: bool,
-) -> None:
-    inventory = subscription["router"]["router_fqdn"]
-
+) -> State:
     extra_vars = {
         "wfo_router_json": subscription,
         "dry_run": dry_run,
@@ -34,42 +30,27 @@ def _deploy_base_config(
         "commit_comment": f"GSO_PROCESS_ID: {process_id} - TT_NUMBER: {tt_number} - Deploy base config",
     }
 
-    lso_client.execute_playbook(
-        playbook_name="base_config.yaml",
-        callback_route=callback_route,
-        inventory=inventory,
-        extra_vars=extra_vars,
-    )
+    return {
+        "playbook_name": "base_config.yaml",
+        "inventory": {"all": {"hosts": {subscription["router"]["router_fqdn"]: None}}},
+        "extra_vars": extra_vars,
+    }
 
 
 @step("[DRY RUN] Deploy base config")
-def deploy_base_config_dry(
-    subscription: dict[str, Any],
-    tt_number: str,
-    callback_route: str,
-    process_id: UUIDstr,
-) -> State:
+def deploy_base_config_dry(subscription: dict[str, Any], tt_number: str, process_id: UUIDstr) -> State:
     """Perform a dry run of provisioning base config on a router."""
-    _deploy_base_config(subscription, tt_number, callback_route, process_id, dry_run=True)
-
-    return {"subscription": subscription}
+    return _deploy_base_config(subscription, tt_number, process_id, dry_run=True)
 
 
 @step("[FOR REAL] Deploy base config")
-def deploy_base_config_real(
-    subscription: dict[str, Any],
-    tt_number: str,
-    callback_route: str,
-    process_id: UUIDstr,
-) -> State:
+def deploy_base_config_real(subscription: dict[str, Any], tt_number: str, process_id: UUIDstr) -> State:
     """Deploy base config on a router using the provisioning proxy."""
-    _deploy_base_config(subscription, tt_number, callback_route, process_id, dry_run=False)
-
-    return {"subscription": subscription}
+    return _deploy_base_config(subscription, tt_number, process_id, dry_run=False)
 
 
 @step("[FOR REAL] Set ISIS metric to very high value")
-def set_isis_to_max(subscription: Iptrunk, process_id: UUIDstr, callback_route: str, tt_number: str) -> State:
+def set_isis_to_max(subscription: Iptrunk, process_id: UUIDstr, tt_number: str) -> State:
     """Workflow step for setting the :term:`ISIS` metric to an arbitrarily high value to drain a link."""
     old_isis_metric = subscription.iptrunk.iptrunk_isis_metric
     params = load_oss_params()
@@ -83,29 +64,30 @@ def set_isis_to_max(subscription: Iptrunk, process_id: UUIDstr, callback_route:
         f"{subscription.iptrunk.geant_s_sid}",
     }
 
-    lso_client.execute_playbook(
-        playbook_name="iptrunks.yaml",
-        callback_route=callback_route,
-        inventory=f"{subscription.iptrunk.iptrunk_sides[0].iptrunk_side_node.router_fqdn}\n"
-        f"{subscription.iptrunk.iptrunk_sides[1].iptrunk_side_node.router_fqdn}\n",
-        extra_vars=extra_vars,
-    )
-
     return {
         "subscription": subscription,
+        "playbook_name": "iptrunks.yaml",
+        "inventory": {
+            "all": {
+                "hosts": {
+                    subscription.iptrunk.iptrunk_sides[0].iptrunk_side_node.router_fqdn: None,
+                    subscription.iptrunk.iptrunk_sides[1].iptrunk_side_node.router_fqdn: None,
+                }
+            }
+        },
+        "extra_vars": extra_vars,
         "old_isis_metric": old_isis_metric,
     }
 
 
 @step("Run show commands after base config install")
-def run_checks_after_base_config(subscription: dict[str, Any], callback_route: str) -> None:
+def run_checks_after_base_config(subscription: dict[str, Any]) -> State:
     """Workflow step for running show commands after installing base config."""
-    lso_client.execute_playbook(
-        playbook_name="base_config_checks.yaml",
-        callback_route=callback_route,
-        inventory=subscription["router"]["router_fqdn"],
-        extra_vars={"wfo_router_json": subscription},
-    )
+    return {
+        "playbook_name": "base_config_checks.yaml",
+        "inventory": {"all": {"hosts": {subscription["router"]["router_fqdn"]}}},
+        "extra_vars": {"wfo_router_json": subscription},
+    }
 
 
 @inputstep("Prompt for new SharePoint checklist", assignee=Assignee.SYSTEM)
diff --git a/gso/workflows/iptrunk/create_iptrunk.py b/gso/workflows/iptrunk/create_iptrunk.py
index 56907a6c..8bb7c993 100644
--- a/gso/workflows/iptrunk/create_iptrunk.py
+++ b/gso/workflows/iptrunk/create_iptrunk.py
@@ -28,7 +28,7 @@ from gso.products.product_blocks.iptrunk import (
 from gso.products.product_types.iptrunk import Iptrunk, IptrunkInactive, IptrunkProvisioning
 from gso.products.product_types.router import Router
 from gso.services import infoblox, subscriptions
-from gso.services.lso_client import execute_playbook, lso_interaction
+from gso.services.lso_client import lso_interaction
 from gso.services.netbox_client import NetboxClient
 from gso.services.partners import get_partner_by_name
 from gso.services.sharepoint import SharePointClient
@@ -325,12 +325,7 @@ def initialize_subscription(
 
 
 @step("[DRY RUN] Provision IP trunk interface")
-def provision_ip_trunk_iface_dry(
-    subscription: IptrunkInactive,
-    callback_route: str,
-    process_id: UUIDstr,
-    tt_number: str,
-) -> State:
+def provision_ip_trunk_iface_dry(subscription: IptrunkInactive, process_id: UUIDstr, tt_number: str) -> State:
     """Perform a dry run of deploying configuration on both sides of the trunk."""
     extra_vars = {
         "wfo_trunk_json": json.loads(json_dumps(subscription)),
@@ -341,24 +336,22 @@ def provision_ip_trunk_iface_dry(
         f"{subscription.iptrunk.geant_s_sid}",
     }
 
-    execute_playbook(
-        playbook_name="iptrunks.yaml",
-        callback_route=callback_route,
-        inventory=f"{subscription.iptrunk.iptrunk_sides[0].iptrunk_side_node.router_fqdn}\n"
-        f"{subscription.iptrunk.iptrunk_sides[1].iptrunk_side_node.router_fqdn}\n",
-        extra_vars=extra_vars,
-    )
-
-    return {"subscription": subscription}
+    return {
+        "playbook_name": "iptrunks.yaml",
+        "inventory": {
+            "all": {
+                "hosts": {
+                    subscription.iptrunk.iptrunk_sides[0].iptrunk_side_node.router_fqdn: None,
+                    subscription.iptrunk.iptrunk_sides[1].iptrunk_side_node.router_fqdn: None,
+                }
+            }
+        },
+        "extra_vars": extra_vars,
+    }
 
 
 @step("[FOR REAL] Provision IP trunk interface")
-def provision_ip_trunk_iface_real(
-    subscription: IptrunkInactive,
-    callback_route: str,
-    process_id: UUIDstr,
-    tt_number: str,
-) -> State:
+def provision_ip_trunk_iface_real(subscription: IptrunkInactive, process_id: UUIDstr, tt_number: str) -> State:
     """Deploy IP trunk configuration on both sides."""
     extra_vars = {
         "wfo_trunk_json": json.loads(json_dumps(subscription)),
@@ -369,42 +362,34 @@ def provision_ip_trunk_iface_real(
         f"{subscription.iptrunk.geant_s_sid}",
     }
 
-    execute_playbook(
-        playbook_name="iptrunks.yaml",
-        callback_route=callback_route,
-        inventory=f"{subscription.iptrunk.iptrunk_sides[0].iptrunk_side_node.router_fqdn}\n"
-        f"{subscription.iptrunk.iptrunk_sides[1].iptrunk_side_node.router_fqdn}\n",
-        extra_vars=extra_vars,
-    )
-
-    return {"subscription": subscription}
+    return {
+        "playbook_name": "iptrunks.yaml",
+        "inventory": {
+            "all": {
+                "hosts": {
+                    subscription.iptrunk.iptrunk_sides[0].iptrunk_side_node.router_fqdn: None,
+                    subscription.iptrunk.iptrunk_sides[1].iptrunk_side_node.router_fqdn: None,
+                }
+            }
+        },
+        "extra_vars": extra_vars,
+    }
 
 
 @step("Check IP connectivity of the trunk")
-def check_ip_trunk_connectivity(
-    subscription: IptrunkInactive,
-    callback_route: str,
-) -> State:
+def check_ip_trunk_connectivity(subscription: IptrunkInactive) -> State:
     """Check successful connectivity across the new trunk."""
     extra_vars = {"wfo_ip_trunk_json": json.loads(json_dumps(subscription)), "check": "ping"}
 
-    execute_playbook(
-        playbook_name="iptrunks_checks.yaml",
-        callback_route=callback_route,
-        inventory=subscription.iptrunk.iptrunk_sides[0].iptrunk_side_node.router_fqdn,  # type: ignore[arg-type]
-        extra_vars=extra_vars,
-    )
-
-    return {"subscription": subscription}
+    return {
+        "playbook_name": "iptrunks_checks.yaml",
+        "inventory": {"all": {"hosts": {subscription.iptrunk.iptrunk_sides[0].iptrunk_side_node.router_fqdn: None}}},
+        "extra_vars": extra_vars,
+    }
 
 
 @step("[DRY RUN] Provision IP trunk ISIS interface")
-def provision_ip_trunk_isis_iface_dry(
-    subscription: IptrunkInactive,
-    callback_route: str,
-    process_id: UUIDstr,
-    tt_number: str,
-) -> State:
+def provision_ip_trunk_isis_iface_dry(subscription: IptrunkInactive, process_id: UUIDstr, tt_number: str) -> State:
     """Perform a dry run of deploying :term:`ISIS` configuration."""
     extra_vars = {
         "wfo_trunk_json": json.loads(json_dumps(subscription)),
@@ -415,24 +400,22 @@ def provision_ip_trunk_isis_iface_dry(
         f"{subscription.iptrunk.geant_s_sid}",
     }
 
-    execute_playbook(
-        playbook_name="iptrunks.yaml",
-        callback_route=callback_route,
-        inventory=f"{subscription.iptrunk.iptrunk_sides[0].iptrunk_side_node.router_fqdn}\n"
-        f"{subscription.iptrunk.iptrunk_sides[1].iptrunk_side_node.router_fqdn}\n",
-        extra_vars=extra_vars,
-    )
-
-    return {"subscription": subscription}
+    return {
+        "playbook_name": "iptrunks.yaml",
+        "inventory": {
+            "all": {
+                "hosts": {
+                    subscription.iptrunk.iptrunk_sides[0].iptrunk_side_node.router_fqdn: None,
+                    subscription.iptrunk.iptrunk_sides[1].iptrunk_side_node.router_fqdn: None,
+                }
+            }
+        },
+        "extra_vars": extra_vars,
+    }
 
 
 @step("[FOR REAL] Provision IP trunk ISIS interface")
-def provision_ip_trunk_isis_iface_real(
-    subscription: IptrunkInactive,
-    callback_route: str,
-    process_id: UUIDstr,
-    tt_number: str,
-) -> State:
+def provision_ip_trunk_isis_iface_real(subscription: IptrunkInactive, process_id: UUIDstr, tt_number: str) -> State:
     """Deploy :term:`ISIS` configuration on both sides."""
     extra_vars = {
         "wfo_trunk_json": json.loads(json_dumps(subscription)),
@@ -443,33 +426,30 @@ def provision_ip_trunk_isis_iface_real(
         f"{subscription.iptrunk.geant_s_sid}",
     }
 
-    execute_playbook(
-        playbook_name="iptrunks.yaml",
-        callback_route=callback_route,
-        inventory=f"{subscription.iptrunk.iptrunk_sides[0].iptrunk_side_node.router_fqdn}\n"
-        f"{subscription.iptrunk.iptrunk_sides[1].iptrunk_side_node.router_fqdn}\n",
-        extra_vars=extra_vars,
-    )
-
-    return {"subscription": subscription}
+    return {
+        "playbook_name": "iptrunks.yaml",
+        "inventory": {
+            "all": {
+                "hosts": {
+                    subscription.iptrunk.iptrunk_sides[0].iptrunk_side_node.router_fqdn: None,
+                    subscription.iptrunk.iptrunk_sides[1].iptrunk_side_node.router_fqdn: None,
+                }
+            }
+        },
+        "extra_vars": extra_vars,
+    }
 
 
 @step("Check ISIS adjacency")
-def check_ip_trunk_isis(
-    subscription: IptrunkInactive,
-    callback_route: str,
-) -> State:
+def check_ip_trunk_isis(subscription: IptrunkInactive) -> State:
     """Run an Ansible playbook to confirm :term:`ISIS` adjacency."""
     extra_vars = {"wfo_ip_trunk_json": json.loads(json_dumps(subscription)), "check": "isis"}
 
-    execute_playbook(
-        playbook_name="iptrunks_checks.yaml",
-        callback_route=callback_route,
-        inventory=subscription.iptrunk.iptrunk_sides[0].iptrunk_side_node.router_fqdn,  # type: ignore[arg-type]
-        extra_vars=extra_vars,
-    )
-
-    return {"subscription": subscription}
+    return {
+        "playbook_name": "iptrunks_checks.yaml",
+        "inventory": {"all": {"hosts": {subscription.iptrunk.iptrunk_sides[0].iptrunk_side_node.router_fqdn: None}}},
+        "extra_vars": extra_vars,
+    }
 
 
 @step("Register DNS records for both sides of the trunk")
diff --git a/gso/workflows/iptrunk/deploy_twamp.py b/gso/workflows/iptrunk/deploy_twamp.py
index 11dce53b..36d1644b 100644
--- a/gso/workflows/iptrunk/deploy_twamp.py
+++ b/gso/workflows/iptrunk/deploy_twamp.py
@@ -12,7 +12,7 @@ from orchestrator.workflows.steps import resync, store_process_subscription, uns
 from orchestrator.workflows.utils import wrap_modify_initial_input_form
 
 from gso.products.product_types.iptrunk import Iptrunk
-from gso.services.lso_client import execute_playbook, lso_interaction
+from gso.services.lso_client import lso_interaction
 from gso.utils.types.tt_number import TTNumber
 
 
@@ -33,7 +33,7 @@ def _initial_input_form_generator(subscription_id: UUIDstr) -> FormGenerator:
 
 
 @step("[DRY RUN] Deploy TWAMP on both sides")
-def deploy_twamp_dry(subscription: Iptrunk, process_id: UUIDstr, callback_route: str, tt_number: str) -> State:
+def deploy_twamp_dry(subscription: Iptrunk, process_id: UUIDstr, tt_number: str) -> State:
     """Perform a dry run of deploying the :term:`TWAMP` session."""
     extra_vars = {
         "subscription": json.loads(json_dumps(subscription)),
@@ -43,18 +43,22 @@ def deploy_twamp_dry(subscription: Iptrunk, process_id: UUIDstr, callback_route:
         "commit_comment": f"GSO_PROCESS_ID: {process_id} - TT_NUMBER: {tt_number} - Deploy TWAMP",
     }
 
-    inventory = (
-        f"{subscription.iptrunk.iptrunk_sides[0].iptrunk_side_node.router_fqdn}"
-        f"\n{subscription.iptrunk.iptrunk_sides[1].iptrunk_side_node.router_fqdn}"
-    )
-
-    execute_playbook("deploy_twamp.yaml", callback_route, inventory, extra_vars)
-
-    return {"subscription": subscription}
+    return {
+        "playbook_name": "deploy_twamp.yaml",
+        "inventory": {
+            "all": {
+                "hosts": {
+                    subscription.iptrunk.iptrunk_sides[0].iptrunk_side_node.router_fqdn: None,
+                    subscription.iptrunk.iptrunk_sides[1].iptrunk_side_node.router_fqdn: None,
+                }
+            }
+        },
+        "extra_vars": extra_vars,
+    }
 
 
 @step("[FOR REAL] Deploy TWAMP on both sides")
-def deploy_twamp_real(subscription: Iptrunk, process_id: UUIDstr, callback_route: str, tt_number: str) -> State:
+def deploy_twamp_real(subscription: Iptrunk, process_id: UUIDstr, tt_number: str) -> State:
     """Deploy the :term:`TWAMP` session."""
     extra_vars = {
         "subscription": json.loads(json_dumps(subscription)),
@@ -64,32 +68,40 @@ def deploy_twamp_real(subscription: Iptrunk, process_id: UUIDstr, callback_route
         "commit_comment": f"GSO_PROCESS_ID: {process_id} - TT_NUMBER: {tt_number} - Deploy TWAMP",
     }
 
-    inventory = (
-        f"{subscription.iptrunk.iptrunk_sides[0].iptrunk_side_node.router_fqdn}"
-        f"\n{subscription.iptrunk.iptrunk_sides[1].iptrunk_side_node.router_fqdn}"
-    )
-
-    execute_playbook("deploy_twamp.yaml", callback_route, inventory, extra_vars)
-
-    return {"subscription": subscription}
+    return {
+        "playbook_name": "deploy_twamp.yaml",
+        "inventory": {
+            "all": {
+                "hosts": {
+                    subscription.iptrunk.iptrunk_sides[0].iptrunk_side_node.router_fqdn: None,
+                    subscription.iptrunk.iptrunk_sides[1].iptrunk_side_node.router_fqdn: None,
+                }
+            }
+        },
+        "extra_vars": extra_vars,
+    }
 
 
 @step("Check TWAMP status on both sides")
-def check_twamp_status(subscription: Iptrunk, callback_route: str) -> State:
+def check_twamp_status(subscription: Iptrunk) -> State:
     """Check TWAMP session."""
     extra_vars = {
         "subscription": json.loads(json_dumps(subscription)),
         "verb": "check_twamp",
     }
 
-    inventory = (
-        f"{subscription.iptrunk.iptrunk_sides[0].iptrunk_side_node.router_fqdn}"
-        f"\n{subscription.iptrunk.iptrunk_sides[1].iptrunk_side_node.router_fqdn}"
-    )
-
-    execute_playbook("deploy_twamp.yaml", callback_route, inventory, extra_vars)
-
-    return {"subscription": subscription}
+    return {
+        "playbook_name": "deploy_twamp.yaml",
+        "inventory": {
+            "all": {
+                "hosts": {
+                    subscription.iptrunk.iptrunk_sides[0].iptrunk_side_node.router_fqdn: None,
+                    subscription.iptrunk.iptrunk_sides[1].iptrunk_side_node.router_fqdn: None,
+                }
+            }
+        },
+        "extra_vars": extra_vars,
+    }
 
 
 @workflow(
diff --git a/gso/workflows/iptrunk/migrate_iptrunk.py b/gso/workflows/iptrunk/migrate_iptrunk.py
index c7021875..0077db4e 100644
--- a/gso/workflows/iptrunk/migrate_iptrunk.py
+++ b/gso/workflows/iptrunk/migrate_iptrunk.py
@@ -28,7 +28,7 @@ from gso.products.product_blocks.iptrunk import IptrunkInterfaceBlock, IptrunkTy
 from gso.products.product_types.iptrunk import Iptrunk
 from gso.products.product_types.router import Router
 from gso.services import infoblox
-from gso.services.lso_client import execute_playbook, lso_interaction
+from gso.services.lso_client import lso_interaction
 from gso.services.netbox_client import NetboxClient
 from gso.services.sharepoint import SharePointClient
 from gso.services.subscriptions import get_active_router_subscriptions
@@ -203,28 +203,27 @@ def calculate_old_side_data(subscription: Iptrunk, replace_index: int) -> State:
 
 
 @step("Check Optical PRE levels on the trunk endpoint")
-def check_ip_trunk_optical_levels_pre(subscription: Iptrunk, callback_route: str) -> State:
+def check_ip_trunk_optical_levels_pre(subscription: Iptrunk) -> State:
     """Check Optical PRE levels on the trunk."""
     extra_vars = {"wfo_ip_trunk_json": json.loads(json_dumps(subscription)), "check": "optical_pre"}
 
-    execute_playbook(
-        playbook_name="iptrunks_checks.yaml",
-        callback_route=callback_route,
-        inventory=f"{subscription.iptrunk.iptrunk_sides[0].iptrunk_side_node.router_fqdn}\n"
-        f"{subscription.iptrunk.iptrunk_sides[1].iptrunk_side_node.router_fqdn}\n",
-        extra_vars=extra_vars,
-    )
-
-    return {"subscription": subscription}
+    return {
+        "playbook_name": "iptrunks_checks.yaml",
+        "inventory": {
+            "all": {
+                "hosts": {
+                    subscription.iptrunk.iptrunk_sides[0].iptrunk_side_node.router_fqdn: None,
+                    subscription.iptrunk.iptrunk_sides[1].iptrunk_side_node.router_fqdn: None,
+                }
+            }
+        },
+        "extra_vars": extra_vars,
+    }
 
 
 @step("Check Optical POST levels on the trunk endpoint")
 def check_ip_trunk_optical_levels_post(
-    subscription: Iptrunk,
-    callback_route: str,
-    new_node: Router,
-    new_lag_member_interfaces: list[dict],
-    replace_index: int,
+    subscription: Iptrunk, new_node: Router, new_lag_member_interfaces: list[dict], replace_index: int
 ) -> State:
     """Check Optical POST levels on the trunk."""
     extra_vars = {
@@ -235,24 +234,23 @@ def check_ip_trunk_optical_levels_post(
         "check": "optical_post",
     }
 
-    execute_playbook(
-        playbook_name="iptrunks_checks.yaml",
-        callback_route=callback_route,
-        inventory=f"{subscription.iptrunk.iptrunk_sides[1 - replace_index].iptrunk_side_node.router_fqdn}\n"
-        f"{new_node.router.router_fqdn}\n",
-        extra_vars=extra_vars,
-    )
-
-    return {"subscription": subscription}
+    return {
+        "playbook_name": "iptrunks_checks.yaml",
+        "inventory": {
+            "all": {
+                "hosts": {
+                    subscription.iptrunk.iptrunk_sides[1 - replace_index].iptrunk_side_node.router_fqdn: None,
+                    new_node.router.router_fqdn: None,
+                }
+            }
+        },
+        "extra_vars": extra_vars,
+    }
 
 
 @step("Check LLDP on the trunk endpoints")
 def check_ip_trunk_lldp(
-    subscription: Iptrunk,
-    callback_route: str,
-    new_node: Router,
-    new_lag_member_interfaces: list[dict],
-    replace_index: int,
+    subscription: Iptrunk, new_node: Router, new_lag_member_interfaces: list[dict], replace_index: int
 ) -> State:
     """Check LLDP on the new trunk endpoints."""
     extra_vars = {
@@ -263,21 +261,23 @@ def check_ip_trunk_lldp(
         "check": "lldp",
     }
 
-    execute_playbook(
-        playbook_name="iptrunks_checks.yaml",
-        callback_route=callback_route,
-        inventory=f"{subscription.iptrunk.iptrunk_sides[1 - replace_index].iptrunk_side_node.router_fqdn}\n"
-        f"{new_node.router.router_fqdn}\n",
-        extra_vars=extra_vars,
-    )
-
-    return {"subscription": subscription}
+    return {
+        "playbook_name": "iptrunks_checks.yaml",
+        "inventory": {
+            "all": {
+                "hosts": {
+                    subscription.iptrunk.iptrunk_sides[1 - replace_index].iptrunk_side_node.router_fqdn: None,
+                    new_node.router.router_fqdn: None,
+                }
+            }
+        },
+        "extra_vars": extra_vars,
+    }
 
 
 @step("[DRY RUN] Disable configuration on old router")
 def disable_old_config_dry(
     subscription: Iptrunk,
-    callback_route: str,
     new_node: Router,
     new_lag_interface: str,
     new_lag_member_interfaces: list[dict],
@@ -299,22 +299,24 @@ def disable_old_config_dry(
         f"- Deploy config for {subscription.iptrunk.geant_s_sid}",
     }
 
-    execute_playbook(
-        playbook_name="iptrunks_migration.yaml",
-        callback_route=callback_route,
-        inventory=f"{subscription.iptrunk.iptrunk_sides[0].iptrunk_side_node.router_fqdn}\n"
-        f"{subscription.iptrunk.iptrunk_sides[1].iptrunk_side_node.router_fqdn}\n"
-        f"{new_node.router.router_fqdn}\n",
-        extra_vars=extra_vars,
-    )
-
-    return {"subscription": subscription}
+    return {
+        "playbook_name": "iptrunks_migration.yaml",
+        "inventory": {
+            "all": {
+                "hosts": {
+                    subscription.iptrunk.iptrunk_sides[0].iptrunk_side_node.router_fqdn: None,
+                    subscription.iptrunk.iptrunk_sides[1].iptrunk_side_node.router_fqdn: None,
+                    new_node.router.router_fqdn: None,
+                }
+            }
+        },
+        "extra_vars": extra_vars,
+    }
 
 
 @step("[FOR REAL] Disable configuration on old router")
 def disable_old_config_real(
     subscription: Iptrunk,
-    callback_route: str,
     new_node: Router,
     new_lag_interface: str,
     new_lag_member_interfaces: list[dict],
@@ -336,24 +338,24 @@ def disable_old_config_real(
         f"- Deploy config for {subscription.iptrunk.geant_s_sid}",
     }
 
-    execute_playbook(
-        playbook_name="iptrunks_migration.yaml",
-        callback_route=callback_route,
-        inventory=f"{subscription.iptrunk.iptrunk_sides[0].iptrunk_side_node.router_fqdn}\n"
-        f"{subscription.iptrunk.iptrunk_sides[1].iptrunk_side_node.router_fqdn}\n"
-        f"{new_node.router.router_fqdn}\n",
-        extra_vars=extra_vars,
-    )
-
     return {
-        "subscription": subscription,
+        "playbook_name": "iptrunks_migration.yaml",
+        "inventory": {
+            "all": {
+                "hosts": {
+                    subscription.iptrunk.iptrunk_sides[0].iptrunk_side_node.router_fqdn: None,
+                    subscription.iptrunk.iptrunk_sides[1].iptrunk_side_node.router_fqdn: None,
+                    new_node.router.router_fqdn: None,
+                }
+            }
+        },
+        "extra_vars": extra_vars,
     }
 
 
 @step("[DRY RUN] Deploy configuration on new router")
 def deploy_new_config_dry(
     subscription: Iptrunk,
-    callback_route: str,
     new_node: Router,
     new_lag_interface: str,
     new_lag_member_interfaces: list[dict],
@@ -375,22 +377,24 @@ def deploy_new_config_dry(
         f"- Deploy config for {subscription.iptrunk.geant_s_sid}",
     }
 
-    execute_playbook(
-        playbook_name="iptrunks_migration.yaml",
-        callback_route=callback_route,
-        inventory=f"{subscription.iptrunk.iptrunk_sides[0].iptrunk_side_node.router_fqdn}\n"
-        f"{subscription.iptrunk.iptrunk_sides[1].iptrunk_side_node.router_fqdn}\n"
-        f"{new_node.router.router_fqdn}\n",
-        extra_vars=extra_vars,
-    )
-
-    return {"subscription": subscription}
+    return {
+        "playbook_name": "iptrunks_migration.yaml",
+        "inventory": {
+            "all": {
+                "hosts": {
+                    subscription.iptrunk.iptrunk_sides[0].iptrunk_side_node.router_fqdn: None,
+                    subscription.iptrunk.iptrunk_sides[1].iptrunk_side_node.router_fqdn: None,
+                    new_node.router.router_fqdn: None,
+                }
+            }
+        },
+        "extra_vars": extra_vars,
+    }
 
 
 @step("[FOR REAL] Deploy configuration on new router")
 def deploy_new_config_real(
     subscription: Iptrunk,
-    callback_route: str,
     new_node: Router,
     new_lag_interface: str,
     new_lag_member_interfaces: list[dict],
@@ -412,26 +416,24 @@ def deploy_new_config_real(
         f"- Deploy config for {subscription.iptrunk.geant_s_sid}",
     }
 
-    execute_playbook(
-        playbook_name="iptrunks_migration.yaml",
-        callback_route=callback_route,
-        inventory=f"{subscription.iptrunk.iptrunk_sides[0].iptrunk_side_node.router_fqdn}\n"
-        f"{subscription.iptrunk.iptrunk_sides[1].iptrunk_side_node.router_fqdn}\n"
-        f"{new_node.router.router_fqdn}\n",
-        extra_vars=extra_vars,
-    )
-
-    return {"subscription": subscription}
+    return {
+        "playbook_name": "iptrunks_migration.yaml",
+        "inventory": {
+            "all": {
+                "hosts": {
+                    subscription.iptrunk.iptrunk_sides[0].iptrunk_side_node.router_fqdn: None,
+                    subscription.iptrunk.iptrunk_sides[1].iptrunk_side_node.router_fqdn: None,
+                    new_node.router.router_fqdn: None,
+                }
+            }
+        },
+        "extra_vars": extra_vars,
+    }
 
 
 @step("[DRY RUN] Update BFD on the remaining side")
 def update_remaining_side_bfd_dry(
-    subscription: Iptrunk,
-    callback_route: str,
-    new_node: Router,
-    replace_index: int,
-    process_id: UUIDstr,
-    tt_number: str,
+    subscription: Iptrunk, new_node: Router, replace_index: int, process_id: UUIDstr, tt_number: str
 ) -> State:
     """Perform a dry run of updating configuration on the remaining router."""
     extra_vars = {
@@ -444,24 +446,22 @@ def update_remaining_side_bfd_dry(
         "commit_comment": f"GSO_PROCESS_ID: {process_id} - TT_NUMBER: {tt_number} " f"- Update BFD config.",
     }
 
-    execute_playbook(
-        playbook_name="iptrunks_migration.yaml",
-        callback_route=callback_route,
-        inventory=subscription.iptrunk.iptrunk_sides[1 - replace_index].iptrunk_side_node.router_fqdn,
-        extra_vars=extra_vars,
-    )
-
-    return {"subscription": subscription}
+    return {
+        "playbook_name": "iptrunks_migration.yaml",
+        "inventory": {
+            "all": {
+                "hosts": {
+                    subscription.iptrunk.iptrunk_sides[1 - replace_index].iptrunk_side_node.router_fqdn: None,
+                }
+            }
+        },
+        "extra_vars": extra_vars,
+    }
 
 
 @step("[FOR REAL] Update BFD on the remaining side")
 def update_remaining_side_bfd_real(
-    subscription: Iptrunk,
-    callback_route: str,
-    new_node: Router,
-    replace_index: int,
-    process_id: UUIDstr,
-    tt_number: str,
+    subscription: Iptrunk, new_node: Router, replace_index: int, process_id: UUIDstr, tt_number: str
 ) -> State:
     """Update configuration on the remaining router."""
     extra_vars = {
@@ -474,23 +474,21 @@ def update_remaining_side_bfd_real(
         "commit_comment": f"GSO_PROCESS_ID: {process_id} - TT_NUMBER: {tt_number} " f"- Update BFD config.",
     }
 
-    execute_playbook(
-        playbook_name="iptrunks_migration.yaml",
-        callback_route=callback_route,
-        inventory=subscription.iptrunk.iptrunk_sides[1 - replace_index].iptrunk_side_node.router_fqdn,
-        extra_vars=extra_vars,
-    )
-
-    return {"subscription": subscription}
+    return {
+        "playbook_name": "iptrunks_migration.yaml",
+        "inventory": {
+            "all": {
+                "hosts": {
+                    subscription.iptrunk.iptrunk_sides[1 - replace_index].iptrunk_side_node.router_fqdn: None,
+                }
+            }
+        },
+        "extra_vars": extra_vars,
+    }
 
 
 @step("Check BFD session over trunk")
-def check_ip_trunk_bfd(
-    subscription: Iptrunk,
-    callback_route: str,
-    new_node: Router,
-    replace_index: int,
-) -> State:
+def check_ip_trunk_bfd(subscription: Iptrunk, new_node: Router, replace_index: int) -> State:
     """Check BFD session across the new trunk."""
     extra_vars = {
         "wfo_ip_trunk_json": json.loads(json_dumps(subscription)),
@@ -498,14 +496,17 @@ def check_ip_trunk_bfd(
         "check": "bfd",
     }
 
-    execute_playbook(
-        playbook_name="iptrunks_checks.yaml",
-        callback_route=callback_route,
-        inventory=subscription.iptrunk.iptrunk_sides[1 - replace_index].iptrunk_side_node.router_fqdn,
-        extra_vars=extra_vars,
-    )
-
-    return {"subscription": subscription}
+    return {
+        "playbook_name": "iptrunks_checks.yaml",
+        "inventory": {
+            "all": {
+                "hosts": {
+                    subscription.iptrunk.iptrunk_sides[1 - replace_index].iptrunk_side_node.router_fqdn: None,
+                }
+            }
+        },
+        "extra_vars": extra_vars,
+    }
 
 
 @inputstep("Wait for confirmation", assignee=Assignee.SYSTEM)
@@ -523,28 +524,26 @@ def confirm_continue_move_fiber() -> FormGenerator:
 
 
 @step("Check IP connectivity of the trunk")
-def check_ip_trunk_connectivity(
-    subscription: Iptrunk,
-    callback_route: str,
-    replace_index: int,
-) -> State:
+def check_ip_trunk_connectivity(subscription: Iptrunk, replace_index: int) -> State:
     """Check successful connectivity across the new trunk."""
     extra_vars = {"wfo_ip_trunk_json": json.loads(json_dumps(subscription)), "check": "ping"}
 
-    execute_playbook(
-        playbook_name="iptrunks_checks.yaml",
-        callback_route=callback_route,
-        inventory=subscription.iptrunk.iptrunk_sides[1 - replace_index].iptrunk_side_node.router_fqdn,
-        extra_vars=extra_vars,
-    )
-
-    return {"subscription": subscription}
+    return {
+        "playbook_name": "iptrunks_checks.yaml",
+        "inventory": {
+            "all": {
+                "hosts": {
+                    subscription.iptrunk.iptrunk_sides[1 - replace_index].iptrunk_side_node.router_fqdn: None,
+                }
+            }
+        },
+        "extra_vars": extra_vars,
+    }
 
 
 @step("[FOR REAL] Deploy ISIS configuration on new router")
 def deploy_new_isis(
     subscription: Iptrunk,
-    callback_route: str,
     new_node: Router,
     new_lag_interface: str,
     new_lag_member_interfaces: list[dict],
@@ -566,35 +565,37 @@ def deploy_new_isis(
         f"- Deploy config for {subscription.iptrunk.geant_s_sid}",
     }
 
-    execute_playbook(
-        playbook_name="iptrunks_migration.yaml",
-        callback_route=callback_route,
-        inventory=f"{subscription.iptrunk.iptrunk_sides[0].iptrunk_side_node.router_fqdn}\n"
-        f"{subscription.iptrunk.iptrunk_sides[1].iptrunk_side_node.router_fqdn}\n"
-        f"{new_node.router.router_fqdn}\n",
-        extra_vars=extra_vars,
-    )
-
-    return {"subscription": subscription}
+    return {
+        "playbook_name": "iptrunks_migration.yaml",
+        "inventory": {
+            "all": {
+                "hosts": {
+                    subscription.iptrunk.iptrunk_sides[0].iptrunk_side_node.router_fqdn: None,
+                    subscription.iptrunk.iptrunk_sides[1].iptrunk_side_node.router_fqdn: None,
+                    new_node.router.router_fqdn: None,
+                }
+            }
+        },
+        "extra_vars": extra_vars,
+    }
 
 
 @step("Check ISIS adjacency")
-def check_ip_trunk_isis(
-    subscription: Iptrunk,
-    callback_route: str,
-    replace_index: int,
-) -> State:
+def check_ip_trunk_isis(subscription: Iptrunk, replace_index: int) -> State:
     """Run an Ansible playbook to confirm :term:`ISIS` adjacency."""
     extra_vars = {"wfo_ip_trunk_json": json.loads(json_dumps(subscription)), "check": "isis"}
 
-    execute_playbook(
-        playbook_name="iptrunks_checks.yaml",
-        callback_route=callback_route,
-        inventory=subscription.iptrunk.iptrunk_sides[1 - replace_index].iptrunk_side_node.router_fqdn,
-        extra_vars=extra_vars,
-    )
-
-    return {"subscription": subscription}
+    return {
+        "playbook_name": "iptrunks_checks.yaml",
+        "inventory": {
+            "all": {
+                "hosts": {
+                    subscription.iptrunk.iptrunk_sides[1 - replace_index].iptrunk_side_node.router_fqdn: None,
+                }
+            }
+        },
+        "extra_vars": extra_vars,
+    }
 
 
 @inputstep("Wait for confirmation", assignee=Assignee.SYSTEM)
@@ -615,7 +616,6 @@ def confirm_continue_restore_isis() -> FormGenerator:
 def restore_isis_metric(
     subscription: Iptrunk,
     process_id: UUIDstr,
-    callback_route: str,
     tt_number: str,
     old_isis_metric: int,
 ) -> State:
@@ -630,21 +630,23 @@ def restore_isis_metric(
         f"{subscription.iptrunk.geant_s_sid}",
     }
 
-    execute_playbook(
-        playbook_name="iptrunks.yaml",
-        callback_route=callback_route,
-        inventory=f"{subscription.iptrunk.iptrunk_sides[0].iptrunk_side_node.router_fqdn}\n"
-        f"{subscription.iptrunk.iptrunk_sides[1].iptrunk_side_node.router_fqdn}\n",
-        extra_vars=extra_vars,
-    )
-
-    return {"subscription": subscription}
+    return {
+        "playbook_name": "iptrunks.yaml",
+        "inventory": {
+            "all": {
+                "hosts": {
+                    subscription.iptrunk.iptrunk_sides[0].iptrunk_side_node.router_fqdn: None,
+                    subscription.iptrunk.iptrunk_sides[1].iptrunk_side_node.router_fqdn: None,
+                }
+            }
+        },
+        "extra_vars": extra_vars,
+    }
 
 
 @step("[DRY RUN] Delete configuration on old router")
 def delete_old_config_dry(
     subscription: Iptrunk,
-    callback_route: str,
     new_node: Router,
     new_lag_interface: str,
     new_lag_member_interfaces: list[dict],
@@ -666,22 +668,24 @@ def delete_old_config_dry(
         f"- Deploy config for {subscription.iptrunk.geant_s_sid}",
     }
 
-    execute_playbook(
-        playbook_name="iptrunks_migration.yaml",
-        callback_route=callback_route,
-        inventory=f"{subscription.iptrunk.iptrunk_sides[0].iptrunk_side_node.router_fqdn}\n"
-        f"{subscription.iptrunk.iptrunk_sides[1].iptrunk_side_node.router_fqdn}\n"
-        f"{new_node.router.router_fqdn}\n",
-        extra_vars=extra_vars,
-    )
-
-    return {"subscription": subscription}
+    return {
+        "playbook_name": "iptrunks_migration.yaml",
+        "inventory": {
+            "all": {
+                "hosts": {
+                    subscription.iptrunk.iptrunk_sides[0].iptrunk_side_node.router_fqdn: None,
+                    subscription.iptrunk.iptrunk_sides[1].iptrunk_side_node.router_fqdn: None,
+                    new_node.router.router_fqdn: None,
+                }
+            }
+        },
+        "extra_vars": extra_vars,
+    }
 
 
 @step("[FOR REAL] Delete configuration on old router")
 def delete_old_config_real(
     subscription: Iptrunk,
-    callback_route: str,
     new_node: Router,
     new_lag_interface: str,
     new_lag_member_interfaces: list[dict],
@@ -703,16 +707,19 @@ def delete_old_config_real(
         f"- Deploy config for {subscription.iptrunk.geant_s_sid}",
     }
 
-    execute_playbook(
-        playbook_name="iptrunks_migration.yaml",
-        callback_route=callback_route,
-        inventory=f"{subscription.iptrunk.iptrunk_sides[0].iptrunk_side_node.router_fqdn}\n"
-        f"{subscription.iptrunk.iptrunk_sides[1].iptrunk_side_node.router_fqdn}\n"
-        f"{new_node.router.router_fqdn}\n",
-        extra_vars=extra_vars,
-    )
-
-    return {"subscription": subscription}
+    return {
+        "playbook_name": "iptrunks_migration.yaml",
+        "inventory": {
+            "all": {
+                "hosts": {
+                    subscription.iptrunk.iptrunk_sides[0].iptrunk_side_node.router_fqdn: None,
+                    subscription.iptrunk.iptrunk_sides[1].iptrunk_side_node.router_fqdn: None,
+                    new_node.router.router_fqdn: None,
+                }
+            }
+        },
+        "extra_vars": extra_vars,
+    }
 
 
 @step("Update IP records in IPAM")
diff --git a/gso/workflows/iptrunk/modify_isis_metric.py b/gso/workflows/iptrunk/modify_isis_metric.py
index da14d078..daf24ec7 100644
--- a/gso/workflows/iptrunk/modify_isis_metric.py
+++ b/gso/workflows/iptrunk/modify_isis_metric.py
@@ -11,7 +11,7 @@ from orchestrator.workflows.steps import resync, store_process_subscription, uns
 from orchestrator.workflows.utils import wrap_modify_initial_input_form
 
 from gso.products.product_types.iptrunk import Iptrunk
-from gso.services.lso_client import execute_playbook, lso_interaction
+from gso.services.lso_client import lso_interaction
 from gso.utils.types.tt_number import TTNumber
 
 
@@ -37,12 +37,7 @@ def modify_iptrunk_subscription(subscription: Iptrunk, isis_metric: int) -> Stat
 
 
 @step("[DRY RUN] Provision IP trunk ISIS interface")
-def provision_ip_trunk_isis_iface_dry(
-    subscription: Iptrunk,
-    process_id: UUIDstr,
-    callback_route: str,
-    tt_number: str,
-) -> State:
+def provision_ip_trunk_isis_iface_dry(subscription: Iptrunk, process_id: UUIDstr, tt_number: str) -> State:
     """Perform a dry run of deploying the new :term:`ISIS` metric on both sides of the trunk."""
     extra_vars = {
         "wfo_trunk_json": json.loads(json_dumps(subscription)),
@@ -53,24 +48,22 @@ def provision_ip_trunk_isis_iface_dry(
         f"{subscription.iptrunk.geant_s_sid}",
     }
 
-    execute_playbook(
-        playbook_name="iptrunks.yaml",
-        callback_route=callback_route,
-        inventory=f"{subscription.iptrunk.iptrunk_sides[0].iptrunk_side_node.router_fqdn}\n"
-        f"{subscription.iptrunk.iptrunk_sides[1].iptrunk_side_node.router_fqdn}\n",
-        extra_vars=extra_vars,
-    )
-
-    return {"subscription": subscription}
+    return {
+        "playbook_name": "iptrunks.yaml",
+        "inventory": {
+            "all": {
+                "hosts": {
+                    subscription.iptrunk.iptrunk_sides[0].iptrunk_side_node.router_fqdn: None,
+                    subscription.iptrunk.iptrunk_sides[1].iptrunk_side_node.router_fqdn: None,
+                }
+            }
+        },
+        "extra_vars": extra_vars,
+    }
 
 
 @step("[FOR REAL] Provision IP trunk ISIS interface")
-def provision_ip_trunk_isis_iface_real(
-    subscription: Iptrunk,
-    process_id: UUIDstr,
-    callback_route: str,
-    tt_number: str,
-) -> State:
+def provision_ip_trunk_isis_iface_real(subscription: Iptrunk, process_id: UUIDstr, tt_number: str) -> State:
     """Deploy the new :term:`ISIS` metric on both sides of the trunk."""
     extra_vars = {
         "wfo_trunk_json": json.loads(json_dumps(subscription)),
@@ -81,15 +74,18 @@ def provision_ip_trunk_isis_iface_real(
         f"{subscription.iptrunk.geant_s_sid}",
     }
 
-    execute_playbook(
-        playbook_name="iptrunks.yaml",
-        callback_route=callback_route,
-        inventory=f"{subscription.iptrunk.iptrunk_sides[0].iptrunk_side_node.router_fqdn}\n"
-        f"{subscription.iptrunk.iptrunk_sides[1].iptrunk_side_node.router_fqdn}\n",
-        extra_vars=extra_vars,
-    )
-
-    return {"subscription": subscription}
+    return {
+        "playbook_name": "iptrunks.yaml",
+        "inventory": {
+            "all": {
+                "hosts": {
+                    subscription.iptrunk.iptrunk_sides[0].iptrunk_side_node.router_fqdn: None,
+                    subscription.iptrunk.iptrunk_sides[1].iptrunk_side_node.router_fqdn: None,
+                }
+            }
+        },
+        "extra_vars": extra_vars,
+    }
 
 
 @workflow(
diff --git a/gso/workflows/iptrunk/modify_trunk_interface.py b/gso/workflows/iptrunk/modify_trunk_interface.py
index e947fd52..350ea64a 100644
--- a/gso/workflows/iptrunk/modify_trunk_interface.py
+++ b/gso/workflows/iptrunk/modify_trunk_interface.py
@@ -21,7 +21,7 @@ from gso.products.product_blocks.iptrunk import (
     IptrunkType,
 )
 from gso.products.product_types.iptrunk import Iptrunk
-from gso.services.lso_client import execute_playbook, lso_interaction
+from gso.services.lso_client import lso_interaction
 from gso.services.netbox_client import NetboxClient
 from gso.utils.helpers import (
     available_interfaces_choices,
@@ -186,34 +186,40 @@ def determine_change_in_capacity(
 
 
 @step("Check IP connectivity of the trunk")
-def check_ip_trunk_connectivity(subscription: Iptrunk, callback_route: str) -> State:
+def check_ip_trunk_connectivity(subscription: Iptrunk) -> State:
     """Check successful connectivity across a trunk."""
     extra_vars = {"wfo_ip_trunk_json": json.loads(json_dumps(subscription)), "check": "ping"}
 
-    execute_playbook(
-        playbook_name="iptrunks_checks.yaml",
-        callback_route=callback_route,
-        inventory=subscription.iptrunk.iptrunk_sides[0].iptrunk_side_node.router_fqdn,
-        extra_vars=extra_vars,
-    )
-
-    return {"subscription": subscription}
+    return {
+        "playbook_name": "iptrunks_checks.yaml",
+        "inventory": {
+            "all": {
+                "hosts": {
+                    subscription.iptrunk.iptrunk_sides[0].iptrunk_side_node.router_fqdn: None,
+                }
+            }
+        },
+        "extra_vars": extra_vars,
+    }
 
 
 @step("Check LLDP on the trunk endpoints")
-def check_ip_trunk_lldp(subscription: Iptrunk, callback_route: str) -> State:
+def check_ip_trunk_lldp(subscription: Iptrunk) -> State:
     """Check LLDP on trunk endpoints."""
     extra_vars = {"wfo_ip_trunk_json": json.loads(json_dumps(subscription)), "check": "lldp"}
 
-    execute_playbook(
-        playbook_name="iptrunks_checks.yaml",
-        callback_route=callback_route,
-        inventory=f"{subscription.iptrunk.iptrunk_sides[0].iptrunk_side_node.router_fqdn}\n"
-        f"{subscription.iptrunk.iptrunk_sides[1].iptrunk_side_node.router_fqdn}\n",
-        extra_vars=extra_vars,
-    )
-
-    return {"subscription": subscription}
+    return {
+        "playbook_name": "iptrunks_checks.yaml",
+        "inventory": {
+            "all": {
+                "hosts": {
+                    subscription.iptrunk.iptrunk_sides[0].iptrunk_side_node.router_fqdn: None,
+                    subscription.iptrunk.iptrunk_sides[1].iptrunk_side_node.router_fqdn: None,
+                }
+            }
+        },
+        "extra_vars": extra_vars,
+    }
 
 
 def update_side_members(subscription: Iptrunk, side_index: int, new_members: list[dict]) -> None:
@@ -301,11 +307,7 @@ def modify_iptrunk_subscription(
 
 @step("[DRY RUN] Provision IP trunk interface")
 def provision_ip_trunk_iface_dry(
-    subscription: Iptrunk,
-    process_id: UUIDstr,
-    callback_route: str,
-    tt_number: str,
-    removed_ae_members: list[str],
+    subscription: Iptrunk, process_id: UUIDstr, tt_number: str, removed_ae_members: list[str]
 ) -> State:
     """Perform a dry run of deploying the updated IP trunk."""
     extra_vars = {
@@ -318,24 +320,23 @@ def provision_ip_trunk_iface_dry(
         f"{subscription.iptrunk.geant_s_sid}",
     }
 
-    execute_playbook(
-        playbook_name="iptrunks.yaml",
-        callback_route=callback_route,
-        inventory=f"{subscription.iptrunk.iptrunk_sides[0].iptrunk_side_node.router_fqdn}\n"
-        f"{subscription.iptrunk.iptrunk_sides[1].iptrunk_side_node.router_fqdn}\n",
-        extra_vars=extra_vars,
-    )
-
-    return {"subscription": subscription}
+    return {
+        "playbook_name": "iptrunks.yaml",
+        "inventory": {
+            "all": {
+                "hosts": {
+                    subscription.iptrunk.iptrunk_sides[0].iptrunk_side_node.router_fqdn: None,
+                    subscription.iptrunk.iptrunk_sides[1].iptrunk_side_node.router_fqdn: None,
+                }
+            }
+        },
+        "extra_vars": extra_vars,
+    }
 
 
 @step("[FOR REAL] Provision IP trunk interface")
 def provision_ip_trunk_iface_real(
-    subscription: Iptrunk,
-    process_id: UUIDstr,
-    callback_route: str,
-    tt_number: str,
-    removed_ae_members: list[str],
+    subscription: Iptrunk, process_id: UUIDstr, tt_number: str, removed_ae_members: list[str]
 ) -> State:
     """Provision the new IP trunk with updated interfaces."""
     extra_vars = {
@@ -348,15 +349,18 @@ def provision_ip_trunk_iface_real(
         f"{subscription.iptrunk.geant_s_sid}",
     }
 
-    execute_playbook(
-        playbook_name="iptrunks.yaml",
-        callback_route=callback_route,
-        inventory=f"{subscription.iptrunk.iptrunk_sides[0].iptrunk_side_node.router_fqdn}\n"
-        f"{subscription.iptrunk.iptrunk_sides[1].iptrunk_side_node.router_fqdn}\n",
-        extra_vars=extra_vars,
-    )
-
-    return {"subscription": subscription}
+    return {
+        "playbook_name": "iptrunks.yaml",
+        "inventory": {
+            "all": {
+                "hosts": {
+                    subscription.iptrunk.iptrunk_sides[0].iptrunk_side_node.router_fqdn: None,
+                    subscription.iptrunk.iptrunk_sides[1].iptrunk_side_node.router_fqdn: None,
+                }
+            }
+        },
+        "extra_vars": extra_vars,
+    }
 
 
 def _netbox_update_interfaces(
@@ -455,19 +459,22 @@ def allocate_interfaces_in_netbox_side_b(subscription: Iptrunk, previous_ae_memb
 
 
 @step("Check Optical POST levels on the trunk endpoint")
-def check_ip_trunk_optical_levels_post(subscription: Iptrunk, callback_route: str) -> State:
+def check_ip_trunk_optical_levels_post(subscription: Iptrunk) -> State:
     """Check Optical POST levels on the trunk."""
     extra_vars = {"wfo_ip_trunk_json": json.loads(json_dumps(subscription)), "check": "optical_post"}
 
-    execute_playbook(
-        playbook_name="iptrunks_checks.yaml",
-        callback_route=callback_route,
-        inventory=f"{subscription.iptrunk.iptrunk_sides[0].iptrunk_side_node.router_fqdn}\n"
-        f"{subscription.iptrunk.iptrunk_sides[1].iptrunk_side_node.router_fqdn}\n",
-        extra_vars=extra_vars,
-    )
-
-    return {"subscription": subscription}
+    return {
+        "playbook_name": "iptrunks_checks.yaml",
+        "inventory": {
+            "all": {
+                "hosts": {
+                    subscription.iptrunk.iptrunk_sides[0].iptrunk_side_node.router_fqdn: None,
+                    subscription.iptrunk.iptrunk_sides[1].iptrunk_side_node.router_fqdn: None,
+                }
+            }
+        },
+        "extra_vars": extra_vars,
+    }
 
 
 @workflow(
diff --git a/gso/workflows/iptrunk/terminate_iptrunk.py b/gso/workflows/iptrunk/terminate_iptrunk.py
index ac628d39..9596c6b9 100644
--- a/gso/workflows/iptrunk/terminate_iptrunk.py
+++ b/gso/workflows/iptrunk/terminate_iptrunk.py
@@ -20,7 +20,7 @@ from orchestrator.workflows.utils import wrap_modify_initial_input_form
 from gso.products.product_blocks.iptrunk import IptrunkSideBlock
 from gso.products.product_types.iptrunk import Iptrunk
 from gso.services import infoblox
-from gso.services.lso_client import execute_playbook, lso_interaction
+from gso.services.lso_client import lso_interaction
 from gso.services.netbox_client import NetboxClient
 from gso.utils.helpers import get_router_vendor
 from gso.utils.shared_enums import Vendor
@@ -51,7 +51,7 @@ def initial_input_form_generator(subscription_id: UUIDstr) -> FormGenerator:
 
 
 @step("[DRY RUN] Deprovision IP trunk")
-def deprovision_ip_trunk_dry(subscription: Iptrunk, process_id: UUIDstr, callback_route: str, tt_number: str) -> State:
+def deprovision_ip_trunk_dry(subscription: Iptrunk, process_id: UUIDstr, tt_number: str) -> State:
     """Perform a dry run of deleting configuration from the routers."""
     extra_vars = {
         "wfo_trunk_json": json.loads(json_dumps(subscription)),
@@ -62,19 +62,22 @@ def deprovision_ip_trunk_dry(subscription: Iptrunk, process_id: UUIDstr, callbac
         f"- Remove config for {subscription.iptrunk.geant_s_sid}",
     }
 
-    execute_playbook(
-        playbook_name="iptrunks.yaml",
-        inventory=f"{subscription.iptrunk.iptrunk_sides[0].iptrunk_side_node.router_fqdn}\n"
-        f"{subscription.iptrunk.iptrunk_sides[1].iptrunk_side_node.router_fqdn}\n",
-        extra_vars=extra_vars,
-        callback_route=callback_route,
-    )
-
-    return {"subscription": subscription}
+    return {
+        "playbook_name": "iptrunks.yaml",
+        "inventory": {
+            "all": {
+                "hosts": {
+                    subscription.iptrunk.iptrunk_sides[0].iptrunk_side_node.router_fqdn: None,
+                    subscription.iptrunk.iptrunk_sides[1].iptrunk_side_node.router_fqdn: None,
+                }
+            }
+        },
+        "extra_vars": extra_vars,
+    }
 
 
 @step("[FOR REAL] Deprovision IP trunk")
-def deprovision_ip_trunk_real(subscription: Iptrunk, process_id: UUIDstr, callback_route: str, tt_number: str) -> State:
+def deprovision_ip_trunk_real(subscription: Iptrunk, process_id: UUIDstr, tt_number: str) -> State:
     """Delete configuration from the routers."""
     extra_vars = {
         "wfo_trunk_json": json.loads(json_dumps(subscription)),
@@ -85,15 +88,18 @@ def deprovision_ip_trunk_real(subscription: Iptrunk, process_id: UUIDstr, callba
         f"- Remove config for {subscription.iptrunk.geant_s_sid}",
     }
 
-    execute_playbook(
-        playbook_name="iptrunks.yaml",
-        inventory=f"{subscription.iptrunk.iptrunk_sides[0].iptrunk_side_node.router_fqdn}\n"
-        f"{subscription.iptrunk.iptrunk_sides[1].iptrunk_side_node.router_fqdn}\n",
-        extra_vars=extra_vars,
-        callback_route=callback_route,
-    )
-
-    return {"subscription": subscription}
+    return {
+        "playbook_name": "iptrunks.yaml",
+        "inventory": {
+            "all": {
+                "hosts": {
+                    subscription.iptrunk.iptrunk_sides[0].iptrunk_side_node.router_fqdn: None,
+                    subscription.iptrunk.iptrunk_sides[1].iptrunk_side_node.router_fqdn: None,
+                }
+            }
+        },
+        "extra_vars": extra_vars,
+    }
 
 
 def _free_up_interfaces_from_netbox(side_block: IptrunkSideBlock) -> None:
diff --git a/gso/workflows/iptrunk/validate_iptrunk.py b/gso/workflows/iptrunk/validate_iptrunk.py
index bdfc4d3b..96372192 100644
--- a/gso/workflows/iptrunk/validate_iptrunk.py
+++ b/gso/workflows/iptrunk/validate_iptrunk.py
@@ -8,27 +8,33 @@ from orchestrator.utils.json import json_dumps
 from orchestrator.workflow import StepList, begin, conditional, done, step, workflow
 from orchestrator.workflows.steps import resync, store_process_subscription, unsync
 from orchestrator.workflows.utils import wrap_modify_initial_input_form
+from pydantic_forms.types import State
 
 from gso.products.product_types.iptrunk import Iptrunk
 from gso.services import infoblox
-from gso.services.lso_client import anonymous_lso_interaction, execute_playbook
+from gso.services.lso_client import anonymous_lso_interaction
 from gso.services.netbox_client import NetboxClient
 from gso.utils.helpers import get_router_vendor
 from gso.utils.shared_enums import Vendor
 
 
 @step("Validate IP trunk configuration")
-def validate_router_config(subscription: Iptrunk, callback_route: str) -> None:
+def validate_router_config(subscription: Iptrunk) -> State:
     """Run an Ansible playbook that validates the configuration that is present on an active IP trunk."""
     extra_vars = {"wfo_trunk_json": json.loads(json_dumps(subscription)), "verb": "validate"}
 
-    execute_playbook(
-        playbook_name="base_config.yaml",
-        callback_route=callback_route,
-        inventory=f"{subscription.iptrunk.iptrunk_sides[0].iptrunk_side_node.router_fqdn}\n"
-        f"{subscription.iptrunk.iptrunk_sides[1].iptrunk_side_node.router_fqdn}\n",
-        extra_vars=extra_vars,
-    )
+    return {
+        "playbook_name": "base_config.yaml",
+        "inventory": {
+            "all": {
+                "hosts": {
+                    subscription.iptrunk.iptrunk_sides[0].iptrunk_side_node.router_fqdn: None,
+                    subscription.iptrunk.iptrunk_sides[1].iptrunk_side_node.router_fqdn: None,
+                }
+            }
+        },
+        "extra_vars": extra_vars,
+    }
 
 
 @step("Verify IPAM resources for LAG interfaces")
@@ -129,56 +135,71 @@ def verify_netbox_entries(subscription: Iptrunk) -> None:
 
 
 @step("Verify configuration of IPtrunk")
-def verify_iptrunk_config(subscription: Iptrunk, callback_route: str) -> None:
+def verify_iptrunk_config(subscription: Iptrunk) -> State:
     """Check for configuration drift on the relevant routers."""
-    execute_playbook(
-        playbook_name="iptrunks.yaml",
-        callback_route=callback_route,
-        inventory=f"{subscription.iptrunk.iptrunk_sides[0].iptrunk_side_node.router_fqdn}\n"
-        f"{subscription.iptrunk.iptrunk_sides[1].iptrunk_side_node.router_fqdn}\n",
-        extra_vars={
+    return {
+        "playbook_name": "iptrunks.yaml",
+        "inventory": {
+            "all": {
+                "hosts": {
+                    subscription.iptrunk.iptrunk_sides[0].iptrunk_side_node.router_fqdn: None,
+                    subscription.iptrunk.iptrunk_sides[1].iptrunk_side_node.router_fqdn: None,
+                }
+            }
+        },
+        "extra_vars": {
             "wfo_trunk_json": json.loads(json_dumps(subscription)),
             "verb": "deploy",
             "dry_run": "true",
             "config_object": "trunk_interface",
             "is_verification_workflow": "true",
         },
-    )
+    }
 
 
 @step("Check ISIS configuration")
-def check_ip_trunk_isis(subscription: Iptrunk, callback_route: str) -> None:
+def check_ip_trunk_isis(subscription: Iptrunk) -> State:
     """Run an Ansible playbook to check for any :term:`ISIS` configuration drift."""
-    execute_playbook(
-        playbook_name="iptrunks.yaml",
-        callback_route=callback_route,
-        inventory=f"{subscription.iptrunk.iptrunk_sides[0].iptrunk_side_node.router_fqdn}\n"
-        f"{subscription.iptrunk.iptrunk_sides[1].iptrunk_side_node.router_fqdn}\n",
-        extra_vars={
+    return {
+        "playbook_name": "iptrunks.yaml",
+        "inventory": {
+            "all": {
+                "hosts": {
+                    subscription.iptrunk.iptrunk_sides[0].iptrunk_side_node.router_fqdn: None,
+                    subscription.iptrunk.iptrunk_sides[1].iptrunk_side_node.router_fqdn: None,
+                }
+            }
+        },
+        "extra_vars": {
             "wfo_trunk_json": json.loads(json_dumps(subscription)),
             "verb": "deploy",
             "dry_run": "true",
             "config_object": "isis_interface",
             "is_verification_workflow": "true",
         },
-    )
+    }
 
 
 @step("Verify TWAMP configuration")
-def verify_twamp_config(subscription: Iptrunk, callback_route: str) -> None:
+def verify_twamp_config(subscription: Iptrunk) -> State:
     """Check for configuration drift of TWAMP."""
-    execute_playbook(
-        playbook_name="deploy_twamp.yaml",
-        callback_route=callback_route,
-        inventory=f"{subscription.iptrunk.iptrunk_sides[0].iptrunk_side_node.router_fqdn}\n"
-        f"{subscription.iptrunk.iptrunk_sides[1].iptrunk_side_node.router_fqdn}\n",
-        extra_vars={
+    return {
+        "playbook_name": "deploy_twamp.yaml",
+        "inventory": {
+            "all": {
+                "hosts": {
+                    subscription.iptrunk.iptrunk_sides[0].iptrunk_side_node.router_fqdn: None,
+                    subscription.iptrunk.iptrunk_sides[1].iptrunk_side_node.router_fqdn: None,
+                }
+            }
+        },
+        "extra_vars": {
             "subscription": json.loads(json_dumps(subscription)),
             "verb": "deploy",
             "dry_run": "true",
             "is_verification_workflow": "true",
         },
-    )
+    }
 
 
 @workflow(
diff --git a/gso/workflows/router/promote_p_to_pe.py b/gso/workflows/router/promote_p_to_pe.py
index 0f0b31da..66be5635 100644
--- a/gso/workflows/router/promote_p_to_pe.py
+++ b/gso/workflows/router/promote_p_to_pe.py
@@ -17,7 +17,6 @@ from pydantic import ConfigDict, model_validator
 
 from gso.products.product_blocks.router import RouterRole
 from gso.products.product_types.router import Router
-from gso.services import lso_client
 from gso.services.kentik_client import KentikClient, NewKentikDevice
 from gso.services.lso_client import lso_interaction
 from gso.services.subscriptions import get_all_active_sites
@@ -49,7 +48,7 @@ def initial_input_form_generator(subscription_id: UUIDstr) -> FormGenerator:
 
 
 @step("Evacuate the router by setting isis_overload")
-def set_isis_overload(subscription: dict[str, Any], callback_route: str, tt_number: str, process_id: UUIDstr) -> None:
+def set_isis_overload(subscription: dict[str, Any], tt_number: str, process_id: UUIDstr) -> State:
     """Evacuate the router by setting isis overload."""
     extra_vars = {
         "dry_run": False,
@@ -58,18 +57,15 @@ def set_isis_overload(subscription: dict[str, Any], callback_route: str, tt_numb
         "verb": "set_isis_overload",
     }
 
-    lso_client.execute_playbook(
-        playbook_name="promote_p_to_pe.yaml",
-        callback_route=callback_route,
-        inventory=subscription["router"]["router_fqdn"],
-        extra_vars=extra_vars,
-    )
+    return {
+        "playbook_name": "promote_p_to_pe.yaml",
+        "inventory": {"all": {"hosts": {subscription["router"]["router_fqdn"]: None}}},
+        "extra_vars": extra_vars,
+    }
 
 
 @step("[DRY RUN] Deploy PE base config")
-def deploy_pe_base_config_dry(
-    subscription: dict[str, Any], callback_route: str, tt_number: str, process_id: UUIDstr
-) -> None:
+def deploy_pe_base_config_dry(subscription: dict[str, Any], tt_number: str, process_id: UUIDstr) -> State:
     """Perform a dry run of adding the base config to the router."""
     extra_vars = {
         "dry_run": True,
@@ -80,18 +76,15 @@ def deploy_pe_base_config_dry(
         "geant_sites": json.loads(json_dumps(get_all_active_sites())),
     }
 
-    lso_client.execute_playbook(
-        playbook_name="promote_p_to_pe.yaml",
-        callback_route=callback_route,
-        inventory=subscription["router"]["router_fqdn"],
-        extra_vars=extra_vars,
-    )
+    return {
+        "playbook_name": "promote_p_to_pe.yaml",
+        "inventory": {"all": {"hosts": {subscription["router"]["router_fqdn"]: None}}},
+        "extra_vars": extra_vars,
+    }
 
 
 @step("[FOR REAL] Deploy PE base config")
-def deploy_pe_base_config_real(
-    subscription: dict[str, Any], callback_route: str, tt_number: str, process_id: UUIDstr
-) -> None:
+def deploy_pe_base_config_real(subscription: dict[str, Any], tt_number: str, process_id: UUIDstr) -> State:
     """Perform a real run of adding the base config to the router."""
     extra_vars = {
         "dry_run": False,
@@ -102,12 +95,11 @@ def deploy_pe_base_config_real(
         "geant_sites": json.loads(json_dumps(get_all_active_sites())),
     }
 
-    lso_client.execute_playbook(
-        playbook_name="promote_p_to_pe.yaml",
-        callback_route=callback_route,
-        inventory=subscription["router"]["router_fqdn"],
-        extra_vars=extra_vars,
-    )
+    return {
+        "playbook_name": "promote_p_to_pe.yaml",
+        "inventory": {"all": {"hosts": {subscription["router"]["router_fqdn"]: None}}},
+        "extra_vars": extra_vars,
+    }
 
 
 @inputstep("Prompt EARL insertion", assignee=Assignee.SYSTEM)
@@ -159,7 +151,7 @@ def create_kentik_device(subscription: Router) -> State:
 
 
 @step("[DRY RUN] Include new PE into SDP mesh on other Nokia PEs")
-def update_sdp_mesh_dry(subscription: dict[str, Any], callback_route: str, tt_number: str, process_id: UUIDstr) -> None:
+def update_sdp_mesh_dry(subscription: dict[str, Any], tt_number: str, process_id: UUIDstr) -> State:
     """Perform a dry run for updating the SDP mesh with the new router."""
     extra_vars = {
         "dry_run": True,
@@ -175,18 +167,15 @@ def update_sdp_mesh_dry(subscription: dict[str, Any], callback_route: str, tt_nu
         },
     }
 
-    lso_client.execute_playbook(
-        playbook_name="update_pe_sdp_mesh.yaml",
-        callback_route=callback_route,
-        inventory=generate_inventory_for_active_routers(router_role=RouterRole.PE, router_vendor=Vendor.NOKIA),
-        extra_vars=extra_vars,
-    )
+    return {
+        "playbook_name": "update_pe_sdp_mesh.yaml",
+        "inventory": generate_inventory_for_active_routers(router_role=RouterRole.PE, router_vendor=Vendor.NOKIA),
+        "extra_vars": extra_vars,
+    }
 
 
 @step("[FOR REAL] Include new PE into SDP mesh on other Nokia PEs")
-def update_sdp_mesh_real(
-    subscription: dict[str, Any], callback_route: str, tt_number: str, process_id: UUIDstr
-) -> None:
+def update_sdp_mesh_real(subscription: dict[str, Any], tt_number: str, process_id: UUIDstr) -> State:
     """Update the SDP mesh for L2 circuits(epipes) config on PE NOKIA routers."""
     extra_vars = {
         "dry_run": False,
@@ -202,18 +191,15 @@ def update_sdp_mesh_real(
         },
     }
 
-    lso_client.execute_playbook(
-        playbook_name="update_pe_sdp_mesh.yaml",
-        callback_route=callback_route,
-        inventory=generate_inventory_for_active_routers(router_role=RouterRole.PE, router_vendor=Vendor.NOKIA),
-        extra_vars=extra_vars,
-    )
+    return {
+        "playbook_name": "update_pe_sdp_mesh.yaml",
+        "inventory": generate_inventory_for_active_routers(router_role=RouterRole.PE, router_vendor=Vendor.NOKIA),
+        "extra_vars": extra_vars,
+    }
 
 
 @step("[DRY RUN] Remove P from all PEs")
-def remove_p_from_pe_dry(
-    subscription: dict[str, Any], callback_route: str, tt_number: str, process_id: UUIDstr
-) -> None:
+def remove_p_from_pe_dry(subscription: dict[str, Any], tt_number: str, process_id: UUIDstr) -> State:
     """Perform a dry run of removing the P router from all the PE routers."""
     extra_vars = {
         "dry_run": True,
@@ -223,18 +209,15 @@ def remove_p_from_pe_dry(
         "verb": "remove_p_from_pe",
     }
 
-    lso_client.execute_playbook(
-        playbook_name="update_ibgp_mesh.yaml",
-        callback_route=callback_route,
-        inventory=generate_inventory_for_active_routers(RouterRole.PE),
-        extra_vars=extra_vars,
-    )
+    return {
+        "playbook_name": "update_ibgp_mesh.yaml",
+        "inventory": generate_inventory_for_active_routers(RouterRole.PE),
+        "extra_vars": extra_vars,
+    }
 
 
 @step("[FOR REAL] Remove P from all PEs")
-def remove_p_from_pe_real(
-    subscription: dict[str, Any], callback_route: str, tt_number: str, process_id: UUIDstr
-) -> None:
+def remove_p_from_pe_real(subscription: dict[str, Any], tt_number: str, process_id: UUIDstr) -> State:
     """Remove the P router from all the PE routers."""
     extra_vars = {
         "dry_run": False,
@@ -244,18 +227,15 @@ def remove_p_from_pe_real(
         "verb": "remove_p_from_pe",
     }
 
-    lso_client.execute_playbook(
-        playbook_name="update_ibgp_mesh.yaml",
-        callback_route=callback_route,
-        inventory=generate_inventory_for_active_routers(RouterRole.PE),
-        extra_vars=extra_vars,
-    )
+    return {
+        "playbook_name": "update_ibgp_mesh.yaml",
+        "inventory": generate_inventory_for_active_routers(RouterRole.PE),
+        "extra_vars": extra_vars,
+    }
 
 
 @step("[DRY RUN] Add PE mesh to PE")
-def add_pe_mesh_to_pe_dry(
-    subscription: dict[str, Any], callback_route: str, tt_number: str, process_id: UUIDstr
-) -> None:
+def add_pe_mesh_to_pe_dry(subscription: dict[str, Any], tt_number: str, process_id: UUIDstr) -> State:
     """Perform a dry run of adding list of PE routers into iGEANT/iGEANT6 of promoted router."""
     extra_vars = {
         "dry_run": True,
@@ -266,18 +246,15 @@ def add_pe_mesh_to_pe_dry(
         "pe_router_list": generate_inventory_for_active_routers(RouterRole.PE)["all"]["hosts"],
     }
 
-    lso_client.execute_playbook(
-        playbook_name="update_ibgp_mesh.yaml",
-        callback_route=callback_route,
-        inventory=subscription["router"]["router_fqdn"],
-        extra_vars=extra_vars,
-    )
+    return {
+        "playbook_name": "update_ibgp_mesh.yaml",
+        "inventory": {"all": {"hosts": {subscription["router"]["router_fqdn"]: None}}},
+        "extra_vars": extra_vars,
+    }
 
 
 @step("[FOR REAL] Add PE mesh to PE")
-def add_pe_mesh_to_pe_real(
-    subscription: dict[str, Any], callback_route: str, tt_number: str, process_id: UUIDstr
-) -> None:
+def add_pe_mesh_to_pe_real(subscription: dict[str, Any], tt_number: str, process_id: UUIDstr) -> State:
     """Perform a real run of adding list of PE routers into iGEANT/iGEANT6 of promoted router."""
     extra_vars = {
         "dry_run": False,
@@ -288,18 +265,15 @@ def add_pe_mesh_to_pe_real(
         "pe_router_list": generate_inventory_for_active_routers(RouterRole.PE)["all"]["hosts"],
     }
 
-    lso_client.execute_playbook(
-        playbook_name="update_ibgp_mesh.yaml",
-        callback_route=callback_route,
-        inventory=subscription["router"]["router_fqdn"],
-        extra_vars=extra_vars,
-    )
+    return {
+        "playbook_name": "update_ibgp_mesh.yaml",
+        "inventory": {"all": {"hosts": {subscription["router"]["router_fqdn"]: None}}},
+        "extra_vars": extra_vars,
+    }
 
 
 @step("[DRY RUN] Add PE to PE mesh")
-def add_pe_to_pe_mesh_dry(
-    subscription: dict[str, Any], callback_route: str, tt_number: str, process_id: UUIDstr
-) -> None:
+def add_pe_to_pe_mesh_dry(subscription: dict[str, Any], tt_number: str, process_id: UUIDstr) -> State:
     """Perform a dry run of adding the promoted router to all PE routers in iGEANT/iGEANT6."""
     extra_vars = {
         "dry_run": True,
@@ -309,18 +283,15 @@ def add_pe_to_pe_mesh_dry(
         "verb": "add_pe_to_pe_mesh",
     }
 
-    lso_client.execute_playbook(
-        playbook_name="update_ibgp_mesh.yaml",
-        callback_route=callback_route,
-        inventory=generate_inventory_for_active_routers(RouterRole.PE),
-        extra_vars=extra_vars,
-    )
+    return {
+        "playbook_name": "update_ibgp_mesh.yaml",
+        "inventory": generate_inventory_for_active_routers(RouterRole.PE),
+        "extra_vars": extra_vars,
+    }
 
 
 @step("[FOR REAL] Add PE to PE mesh")
-def add_pe_to_pe_mesh_real(
-    subscription: dict[str, Any], callback_route: str, tt_number: str, process_id: UUIDstr
-) -> None:
+def add_pe_to_pe_mesh_real(subscription: dict[str, Any], tt_number: str, process_id: UUIDstr) -> State:
     """Perform a real run of adding the promoted router to all PE routers in iGEANT/iGEANT6."""
     extra_vars = {
         "dry_run": False,
@@ -330,16 +301,15 @@ def add_pe_to_pe_mesh_real(
         "verb": "add_pe_to_pe_mesh",
     }
 
-    lso_client.execute_playbook(
-        playbook_name="update_ibgp_mesh.yaml",
-        callback_route=callback_route,
-        inventory=generate_inventory_for_active_routers(RouterRole.PE),
-        extra_vars=extra_vars,
-    )
+    return {
+        "playbook_name": "update_ibgp_mesh.yaml",
+        "inventory": generate_inventory_for_active_routers(RouterRole.PE),
+        "extra_vars": extra_vars,
+    }
 
 
 @step("Check iBGP session")
-def check_pe_ibgp(subscription: dict[str, Any], callback_route: str) -> None:
+def check_pe_ibgp(subscription: dict[str, Any]) -> State:
     """Check the iBGP session."""
     extra_vars = {
         "dry_run": False,
@@ -347,18 +317,15 @@ def check_pe_ibgp(subscription: dict[str, Any], callback_route: str) -> None:
         "verb": "check_pe_ibgp",
     }
 
-    lso_client.execute_playbook(
-        playbook_name="check_ibgp.yaml",
-        callback_route=callback_route,
-        inventory=subscription["router"]["router_fqdn"],
-        extra_vars=extra_vars,
-    )
+    return {
+        "playbook_name": "check_ibgp.yaml",
+        "inventory": {"all": {"hosts": {subscription["router"]["router_fqdn"]: None}}},
+        "extra_vars": extra_vars,
+    }
 
 
 @step("[DRY RUN] Deploy routing instances")
-def deploy_routing_instances_dry(
-    subscription: dict[str, Any], callback_route: str, tt_number: str, process_id: UUIDstr
-) -> None:
+def deploy_routing_instances_dry(subscription: dict[str, Any], tt_number: str, process_id: UUIDstr) -> State:
     """Perform a dry run of deploying routing instances."""
     extra_vars = {
         "dry_run": True,
@@ -367,18 +334,15 @@ def deploy_routing_instances_dry(
         "verb": "deploy_routing_instances",
     }
 
-    lso_client.execute_playbook(
-        playbook_name="promote_p_to_pe.yaml",
-        callback_route=callback_route,
-        inventory=subscription["router"]["router_fqdn"],
-        extra_vars=extra_vars,
-    )
+    return {
+        "playbook_name": "promote_p_to_pe.yaml",
+        "inventory": {"all": {"hosts": {subscription["router"]["router_fqdn"]: None}}},
+        "extra_vars": extra_vars,
+    }
 
 
 @step("[FOR REAL] Deploy routing instances")
-def deploy_routing_instances_real(
-    subscription: dict[str, Any], callback_route: str, tt_number: str, process_id: UUIDstr
-) -> None:
+def deploy_routing_instances_real(subscription: dict[str, Any], tt_number: str, process_id: UUIDstr) -> State:
     """Perform a real run of deploying routing instances."""
     extra_vars = {
         "dry_run": False,
@@ -387,16 +351,15 @@ def deploy_routing_instances_real(
         "verb": "deploy_routing_instances",
     }
 
-    lso_client.execute_playbook(
-        playbook_name="promote_p_to_pe.yaml",
-        callback_route=callback_route,
-        inventory=subscription["router"]["router_fqdn"],
-        extra_vars=extra_vars,
-    )
+    return {
+        "playbook_name": "promote_p_to_pe.yaml",
+        "inventory": {"all": {"hosts": {subscription["router"]["router_fqdn"]: None}}},
+        "extra_vars": extra_vars,
+    }
 
 
 @step("Check L3 services")
-def check_l3_services(subscription: dict[str, Any], callback_route: str) -> None:
+def check_l3_services(subscription: dict[str, Any]) -> State:
     """Check L3 services."""
     extra_vars = {
         "dry_run": False,
@@ -404,18 +367,15 @@ def check_l3_services(subscription: dict[str, Any], callback_route: str) -> None
         "verb": "check_base_ris",
     }
 
-    lso_client.execute_playbook(
-        playbook_name="check_l3_services.yaml",
-        callback_route=callback_route,
-        inventory=subscription["router"]["router_fqdn"],
-        extra_vars=extra_vars,
-    )
+    return {
+        "playbook_name": "check_l3_services.yaml",
+        "inventory": {"all": {"hosts": {subscription["router"]["router_fqdn"]: None}}},
+        "extra_vars": extra_vars,
+    }
 
 
 @step("Remove ISIS overload")
-def remove_isis_overload(
-    subscription: dict[str, Any], callback_route: str, tt_number: str, process_id: UUIDstr
-) -> None:
+def remove_isis_overload(subscription: dict[str, Any], tt_number: str, process_id: UUIDstr) -> State:
     """Remove ISIS overload."""
     extra_vars = {
         "dry_run": False,
@@ -424,12 +384,11 @@ def remove_isis_overload(
         "verb": "remove_isis_overload",
     }
 
-    lso_client.execute_playbook(
-        playbook_name="promote_p_to_pe.yaml",
-        callback_route=callback_route,
-        inventory=subscription["router"]["router_fqdn"],
-        extra_vars=extra_vars,
-    )
+    return {
+        "playbook_name": "promote_p_to_pe.yaml",
+        "inventory": {"all": {"hosts": {subscription["router"]["router_fqdn"]: None}}},
+        "extra_vars": extra_vars,
+    }
 
 
 @step("Set router role to PE (Update subscription model)")
@@ -441,7 +400,7 @@ def update_subscription_model(subscription: Router) -> State:
 
 
 @step("[DRY RUN] Add all P to this new PE")
-def add_all_p_to_pe_dry(subscription: dict[str, Any], callback_route: str, tt_number: str, process_id: UUIDstr) -> None:
+def add_all_p_to_pe_dry(subscription: dict[str, Any], tt_number: str, process_id: UUIDstr) -> State:
     """Perform a dry run of adding all P routers to the PE router."""
     extra_vars = {
         "dry_run": True,
@@ -453,18 +412,15 @@ def add_all_p_to_pe_dry(subscription: dict[str, Any], callback_route: str, tt_nu
         )["all"]["hosts"],
     }
 
-    lso_client.execute_playbook(
-        playbook_name="update_ibgp_mesh.yaml",
-        callback_route=callback_route,
-        inventory=subscription["router"]["router_fqdn"],
-        extra_vars=extra_vars,
-    )
+    return {
+        "playbook_name": "update_ibgp_mesh.yaml",
+        "inventory": {"all": {"hosts": {subscription["router"]["router_fqdn"]: None}}},
+        "extra_vars": extra_vars,
+    }
 
 
 @step("[FOR REAL] Add all P to this new PE")
-def add_all_p_to_pe_real(
-    subscription: dict[str, Any], callback_route: str, tt_number: str, process_id: UUIDstr
-) -> None:
+def add_all_p_to_pe_real(subscription: dict[str, Any], tt_number: str, process_id: UUIDstr) -> State:
     """Perform a real run of adding all P routers to the PE router."""
     extra_vars = {
         "dry_run": False,
@@ -476,16 +432,15 @@ def add_all_p_to_pe_real(
         )["all"]["hosts"],
     }
 
-    lso_client.execute_playbook(
-        playbook_name="update_ibgp_mesh.yaml",
-        callback_route=callback_route,
-        inventory=subscription["router"]["router_fqdn"],
-        extra_vars=extra_vars,
-    )
+    return {
+        "playbook_name": "update_ibgp_mesh.yaml",
+        "inventory": {"all": {"hosts": {subscription["router"]["router_fqdn"]: None}}},
+        "extra_vars": extra_vars,
+    }
 
 
 @step("[DRY RUN] Add this new PE to all P")
-def add_pe_to_all_p_dry(subscription: dict[str, Any], callback_route: str, tt_number: str, process_id: UUIDstr) -> None:
+def add_pe_to_all_p_dry(subscription: dict[str, Any], tt_number: str, process_id: UUIDstr) -> State:
     """Perform a dry run of adding promoted router to all PE routers in iGEANT/iGEANT6."""
     extra_vars = {
         "dry_run": True,
@@ -495,20 +450,17 @@ def add_pe_to_all_p_dry(subscription: dict[str, Any], callback_route: str, tt_nu
         "verb": "add_pe_to_all_p",
     }
 
-    lso_client.execute_playbook(
-        playbook_name="update_ibgp_mesh.yaml",
-        callback_route=callback_route,
-        inventory=generate_inventory_for_active_routers(
+    return {
+        "playbook_name": "update_ibgp_mesh.yaml",
+        "inventory": generate_inventory_for_active_routers(
             RouterRole.P, exclude_routers=[subscription["router"]["router_fqdn"]]
         ),
-        extra_vars=extra_vars,
-    )
+        "extra_vars": extra_vars,
+    }
 
 
 @step("[FOR REAL] Add this new PE to all P")
-def add_pe_to_all_p_real(
-    subscription: dict[str, Any], callback_route: str, tt_number: str, process_id: UUIDstr
-) -> None:
+def add_pe_to_all_p_real(subscription: dict[str, Any], tt_number: str, process_id: UUIDstr) -> State:
     """Perform a real run of adding promoted router to all PE routers in iGEANT/iGEANT6."""
     extra_vars = {
         "dry_run": False,
@@ -518,20 +470,17 @@ def add_pe_to_all_p_real(
         "verb": "add_pe_to_all_p",
     }
 
-    lso_client.execute_playbook(
-        playbook_name="update_ibgp_mesh.yaml",
-        callback_route=callback_route,
-        inventory=generate_inventory_for_active_routers(
+    return {
+        "playbook_name": "update_ibgp_mesh.yaml",
+        "inventory": generate_inventory_for_active_routers(
             RouterRole.P, exclude_routers=[subscription["router"]["router_fqdn"]]
         ),
-        extra_vars=extra_vars,
-    )
+        "extra_vars": extra_vars,
+    }
 
 
 @step("[DRY RUN] Delete default routes")
-def delete_default_routes_dry(
-    subscription: dict[str, Any], callback_route: str, tt_number: str, process_id: UUIDstr
-) -> None:
+def delete_default_routes_dry(subscription: dict[str, Any], tt_number: str, process_id: UUIDstr) -> State:
     """Perform a dry run of deleting the default routes."""
     extra_vars = {
         "dry_run": True,
@@ -541,18 +490,15 @@ def delete_default_routes_dry(
         "verb": "delete_default_routes",
     }
 
-    lso_client.execute_playbook(
-        playbook_name="promote_p_to_pe.yaml",
-        callback_route=callback_route,
-        inventory=subscription["router"]["router_fqdn"],
-        extra_vars=extra_vars,
-    )
+    return {
+        "playbook_name": "promote_p_to_pe.yaml",
+        "inventory": {"all": {"hosts": {subscription["router"]["router_fqdn"]: None}}},
+        "extra_vars": extra_vars,
+    }
 
 
 @step("[FOR REAL] Delete default routes")
-def delete_default_routes_real(
-    subscription: dict[str, Any], callback_route: str, tt_number: str, process_id: UUIDstr
-) -> None:
+def delete_default_routes_real(subscription: dict[str, Any], tt_number: str, process_id: UUIDstr) -> State:
     """Perform a real run of deleting the default routes."""
     extra_vars = {
         "dry_run": False,
@@ -562,12 +508,11 @@ def delete_default_routes_real(
         "verb": "delete_default_routes",
     }
 
-    lso_client.execute_playbook(
-        playbook_name="promote_p_to_pe.yaml",
-        callback_route=callback_route,
-        inventory=subscription["router"]["router_fqdn"],
-        extra_vars=extra_vars,
-    )
+    return {
+        "playbook_name": "promote_p_to_pe.yaml",
+        "inventory": {"all": {"hosts": {subscription["router"]["router_fqdn"]: None}}},
+        "extra_vars": extra_vars,
+    }
 
 
 @workflow(
diff --git a/gso/workflows/router/terminate_router.py b/gso/workflows/router/terminate_router.py
index f7caa6c9..421a55c0 100644
--- a/gso/workflows/router/terminate_router.py
+++ b/gso/workflows/router/terminate_router.py
@@ -19,13 +19,14 @@ from orchestrator.workflows.steps import (
     unsync,
 )
 from orchestrator.workflows.utils import wrap_modify_initial_input_form
+from pydantic_forms.types import State
 
 from gso.products.product_blocks.router import RouterRole
 from gso.products.product_types.router import Router
-from gso.services import infoblox, lso_client
+from gso.services import infoblox
 from gso.services.kentik_client import KentikClient
 from gso.services.librenms_client import LibreNMSClient
-from gso.services.lso_client import execute_playbook, lso_interaction
+from gso.services.lso_client import 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
@@ -69,9 +70,7 @@ def deprovision_loopback_ips(subscription: Router) -> dict:
 
 
 @step("[DRY RUN] Remove configuration from router")
-def remove_config_from_router_dry(
-    subscription: Router, callback_route: str, process_id: UUIDstr, tt_number: str
-) -> None:
+def remove_config_from_router_dry(subscription: Router, process_id: UUIDstr, tt_number: str) -> State:
     """Remove configuration from the router, first as a dry run."""
     extra_vars = {
         "wfo_router_json": json.loads(json_dumps(subscription)),
@@ -81,18 +80,15 @@ def remove_config_from_router_dry(
         f"{subscription.router.router_fqdn}",
     }
 
-    execute_playbook(
-        playbook_name="base_config.yaml",
-        callback_route=callback_route,
-        inventory=subscription.router.router_fqdn,
-        extra_vars=extra_vars,
-    )
+    return {
+        "playbook_name": "base_config.yaml",
+        "inventory": {"all": {"hosts": {subscription.router.router_fqdn: None}}},
+        "extra_vars": extra_vars,
+    }
 
 
 @step("[FOR REAL] Remove configuration from router")
-def remove_config_from_router_real(
-    subscription: Router, callback_route: str, process_id: UUIDstr, tt_number: str
-) -> None:
+def remove_config_from_router_real(subscription: Router, process_id: UUIDstr, tt_number: str) -> State:
     """Remove configuration from the router."""
     extra_vars = {
         "wfo_router_json": json.loads(json_dumps(subscription)),
@@ -102,12 +98,11 @@ def remove_config_from_router_real(
         f"{subscription.router.router_fqdn}",
     }
 
-    execute_playbook(
-        playbook_name="base_config.yaml",  # FIX: need to use correct playbook.
-        callback_route=callback_route,
-        inventory=subscription.router.router_fqdn,
-        extra_vars=extra_vars,
-    )
+    return {
+        "playbook_name": "base_config.yaml",
+        "inventory": {"all": {"hosts": {subscription.router.router_fqdn: None}}},
+        "extra_vars": extra_vars,
+    }
 
 
 @step("Remove Device from Netbox")
@@ -118,7 +113,7 @@ def remove_device_from_netbox(subscription: Router) -> dict[str, Router]:
 
 
 @step("[DRY RUN] Remove P router from all the PE routers")
-def remove_p_from_all_pe_dry(subscription: Router, callback_route: str, tt_number: str, process_id: UUIDstr) -> None:
+def remove_p_from_all_pe_dry(subscription: Router, tt_number: str, process_id: UUIDstr) -> State:
     """Perform a dry run of removing the terminated router from all the PE routers."""
     extra_vars = {
         "dry_run": True,
@@ -128,16 +123,15 @@ def remove_p_from_all_pe_dry(subscription: Router, callback_route: str, tt_numbe
         "verb": "remove_p_from_pe",
     }
 
-    lso_client.execute_playbook(
-        playbook_name="update_ibgp_mesh.yaml",
-        callback_route=callback_route,
-        inventory=generate_inventory_for_active_routers(RouterRole.PE),
-        extra_vars=extra_vars,
-    )
+    return {
+        "playbook_name": "update_ibgp_mesh.yaml",
+        "inventory": generate_inventory_for_active_routers(RouterRole.PE),
+        "extra_vars": extra_vars,
+    }
 
 
 @step("[REAL RUN] Remove P router from all the PE routers")
-def remove_p_from_all_pe_real(subscription: Router, callback_route: str, tt_number: str, process_id: UUIDstr) -> None:
+def remove_p_from_all_pe_real(subscription: Router, tt_number: str, process_id: UUIDstr) -> State:
     """Perform a real run of removing the terminated router from all the PE routers."""
     extra_vars = {
         "dry_run": False,
@@ -147,16 +141,15 @@ def remove_p_from_all_pe_real(subscription: Router, callback_route: str, tt_numb
         "verb": "remove_p_from_pe",
     }
 
-    lso_client.execute_playbook(
-        playbook_name="update_ibgp_mesh.yaml",
-        callback_route=callback_route,
-        inventory=generate_inventory_for_active_routers(RouterRole.PE),
-        extra_vars=extra_vars,
-    )
+    return {
+        "playbook_name": "update_ibgp_mesh.yaml",
+        "inventory": generate_inventory_for_active_routers(RouterRole.PE),
+        "extra_vars": extra_vars,
+    }
 
 
 @step("[DRY RUN] Remove PE router from all the PE routers")
-def remove_pe_from_all_pe_dry(subscription: Router, callback_route: str, tt_number: str, process_id: UUIDstr) -> None:
+def remove_pe_from_all_pe_dry(subscription: Router, tt_number: str, process_id: UUIDstr) -> State:
     """Perform a dry run of removing the terminated PE router from the PE router mesh."""
     extra_vars = {
         "dry_run": True,
@@ -166,18 +159,17 @@ def remove_pe_from_all_pe_dry(subscription: Router, callback_route: str, tt_numb
         "verb": "remove_pe_from_pe",
     }
 
-    lso_client.execute_playbook(
-        playbook_name="update_ibgp_mesh.yaml",
-        callback_route=callback_route,
-        inventory=generate_inventory_for_active_routers(
+    return {
+        "playbook_name": "update_ibgp_mesh.yaml",
+        "inventory": generate_inventory_for_active_routers(
             RouterRole.PE, exclude_routers=[subscription.router.router_fqdn]
         ),
-        extra_vars=extra_vars,
-    )
+        "extra_vars": extra_vars,
+    }
 
 
 @step("[REAL RUN] Remove all PE routers from all the PE routers")
-def remove_pe_from_all_pe_real(subscription: Router, callback_route: str, tt_number: str, process_id: UUIDstr) -> None:
+def remove_pe_from_all_pe_real(subscription: Router, tt_number: str, process_id: UUIDstr) -> State:
     """Perform a real run of removing terminated PE router from PE the router mesh."""
     extra_vars = {
         "dry_run": False,
@@ -187,18 +179,17 @@ def remove_pe_from_all_pe_real(subscription: Router, callback_route: str, tt_num
         "verb": "remove_pe_from_pe",
     }
 
-    lso_client.execute_playbook(
-        playbook_name="update_ibgp_mesh.yaml",
-        callback_route=callback_route,
-        inventory=generate_inventory_for_active_routers(
+    return {
+        "playbook_name": "update_ibgp_mesh.yaml",
+        "inventory": generate_inventory_for_active_routers(
             RouterRole.PE, exclude_routers=[subscription.router.router_fqdn]
         ),
-        extra_vars=extra_vars,
-    )
+        "extra_vars": extra_vars,
+    }
 
 
 @step("[DRY RUN] Remove PE router from all the P routers")
-def remove_pe_from_all_p_dry(subscription: Router, callback_route: str, tt_number: str, process_id: UUIDstr) -> None:
+def remove_pe_from_all_p_dry(subscription: Router, tt_number: str, process_id: UUIDstr) -> State:
     """Perform a dry run of removing PE router from all P routers."""
     extra_vars = {
         "dry_run": True,
@@ -208,16 +199,15 @@ def remove_pe_from_all_p_dry(subscription: Router, callback_route: str, tt_numbe
         "verb": "remove_pe_from_p",
     }
 
-    lso_client.execute_playbook(
-        playbook_name="update_ibgp_mesh.yaml",
-        callback_route=callback_route,
-        inventory=generate_inventory_for_active_routers(RouterRole.P),
-        extra_vars=extra_vars,
-    )
+    return {
+        "playbook_name": "update_ibgp_mesh.yaml",
+        "inventory": generate_inventory_for_active_routers(RouterRole.P),
+        "extra_vars": extra_vars,
+    }
 
 
 @step("[REAL RUN] Remove PE router from all P routers")
-def remove_pe_from_all_p_real(subscription: Router, callback_route: str, tt_number: str, process_id: UUIDstr) -> None:
+def remove_pe_from_all_p_real(subscription: Router, tt_number: str, process_id: UUIDstr) -> State:
     """Perform a real run of removing PE router from all P routers."""
     extra_vars = {
         "dry_run": False,
@@ -227,12 +217,11 @@ def remove_pe_from_all_p_real(subscription: Router, callback_route: str, tt_numb
         "verb": "remove_pe_from_p",
     }
 
-    lso_client.execute_playbook(
-        playbook_name="update_ibgp_mesh.yaml",
-        callback_route=callback_route,
-        inventory=generate_inventory_for_active_routers(RouterRole.P),
-        extra_vars=extra_vars,
-    )
+    return {
+        "playbook_name": "update_ibgp_mesh.yaml",
+        "inventory": generate_inventory_for_active_routers(RouterRole.P),
+        "extra_vars": extra_vars,
+    }
 
 
 @step("Remove Device from Librenms")
diff --git a/gso/workflows/router/update_ibgp_mesh.py b/gso/workflows/router/update_ibgp_mesh.py
index a506e625..8f99bb86 100644
--- a/gso/workflows/router/update_ibgp_mesh.py
+++ b/gso/workflows/router/update_ibgp_mesh.py
@@ -14,7 +14,7 @@ from pydantic import ConfigDict, model_validator
 
 from gso.products.product_blocks.router import RouterRole
 from gso.products.product_types.router import Router
-from gso.services import librenms_client, lso_client
+from gso.services import librenms_client
 from gso.services.lso_client import lso_interaction
 from gso.services.subscriptions import get_trunks_that_terminate_on_router
 from gso.utils.helpers import generate_inventory_for_active_routers
@@ -52,7 +52,7 @@ def initial_input_form_generator(subscription_id: UUIDstr) -> FormGenerator:
 
 
 @step("[DRY RUN] Add P router to iBGP mesh")
-def add_p_to_mesh_dry(subscription: dict[str, Any], callback_route: str, tt_number: str, process_id: UUIDstr) -> None:
+def add_p_to_mesh_dry(subscription: dict[str, Any], tt_number: str, process_id: UUIDstr) -> State:
     """Perform a dry run of adding the new P router to the PE router mesh."""
     extra_vars = {
         "dry_run": True,
@@ -61,16 +61,15 @@ def add_p_to_mesh_dry(subscription: dict[str, Any], callback_route: str, tt_numb
         "verb": "add_p_to_pe",
     }
 
-    lso_client.execute_playbook(
-        playbook_name="update_ibgp_mesh.yaml",
-        callback_route=callback_route,
-        inventory=generate_inventory_for_active_routers(RouterRole.PE),
-        extra_vars=extra_vars,
-    )
+    return {
+        "playbook_name": "update_ibgp_mesh.yaml",
+        "inventory": generate_inventory_for_active_routers(RouterRole.PE),
+        "extra_vars": extra_vars,
+    }
 
 
 @step("[FOR REAL] Add P router to iBGP mesh")
-def add_p_to_mesh_real(subscription: dict[str, Any], callback_route: str, tt_number: str, process_id: UUIDstr) -> None:
+def add_p_to_mesh_real(subscription: dict[str, Any], tt_number: str, process_id: UUIDstr) -> State:
     """Add the P router to the mesh of PE routers."""
     extra_vars = {
         "dry_run": False,
@@ -79,16 +78,15 @@ def add_p_to_mesh_real(subscription: dict[str, Any], callback_route: str, tt_num
         "verb": "add_p_to_pe",
     }
 
-    lso_client.execute_playbook(
-        playbook_name="update_ibgp_mesh.yaml",
-        callback_route=callback_route,
-        inventory=generate_inventory_for_active_routers(RouterRole.PE),
-        extra_vars=extra_vars,
-    )
+    return {
+        "playbook_name": "update_ibgp_mesh.yaml",
+        "inventory": generate_inventory_for_active_routers(RouterRole.PE),
+        "extra_vars": extra_vars,
+    }
 
 
 @step("[DRY RUN] Add all PE routers to P router iBGP group")
-def add_all_pe_to_p_dry(subscription: dict[str, Any], callback_route: str) -> None:
+def add_all_pe_to_p_dry(subscription: dict[str, Any]) -> State:
     """Perform a dry run of adding the list of all PE routers to the new P router."""
     extra_vars = {
         "dry_run": True,
@@ -97,18 +95,15 @@ def add_all_pe_to_p_dry(subscription: dict[str, Any], callback_route: str) -> No
         "verb": "add_pe_to_p",
     }
 
-    lso_client.execute_playbook(
-        playbook_name="update_ibgp_mesh.yaml",
-        callback_route=callback_route,
-        inventory=subscription["router"]["router_fqdn"],
-        extra_vars=extra_vars,
-    )
+    return {
+        "playbook_name": "update_ibgp_mesh.yaml",
+        "inventory": {"all": {"hosts": {subscription["router"]["router_fqdn"]: None}}},
+        "extra_vars": extra_vars,
+    }
 
 
 @step("[FOR REAL] Add all PE routers to P router iBGP group")
-def add_all_pe_to_p_real(
-    subscription: dict[str, Any], callback_route: str, tt_number: str, process_id: UUIDstr
-) -> None:
+def add_all_pe_to_p_real(subscription: dict[str, Any], tt_number: str, process_id: UUIDstr) -> State:
     """Add the list of all PE routers to the new P router."""
     extra_vars = {
         "dry_run": False,
@@ -118,23 +113,21 @@ def add_all_pe_to_p_real(
         "verb": "add_pe_to_p",
     }
 
-    lso_client.execute_playbook(
-        playbook_name="update_ibgp_mesh.yaml",
-        callback_route=callback_route,
-        inventory=subscription["router"]["router_fqdn"],
-        extra_vars=extra_vars,
-    )
+    return {
+        "playbook_name": "update_ibgp_mesh.yaml",
+        "inventory": {"all": {"hosts": {subscription["router"]["router_fqdn"]: None}}},
+        "extra_vars": extra_vars,
+    }
 
 
 @step("Verify iBGP session health")
-def check_ibgp_session(subscription: Router, callback_route: str) -> None:
+def check_ibgp_session(subscription: Router) -> State:
     """Run a playbook using the provisioning proxy, to check the health of the new iBGP session."""
-    lso_client.execute_playbook(
-        playbook_name="check_ibgp.yaml",
-        callback_route=callback_route,
-        inventory=subscription.router.router_fqdn,
-        extra_vars={},
-    )
+    return {
+        "playbook_name": "check_ibgp.yaml",
+        "inventory": {"all": {"hosts": {subscription.router.router_fqdn: None}}},
+        "extra_vars": {},
+    }
 
 
 @step("Add the router to LibreNMS")
diff --git a/gso/workflows/router/validate_router.py b/gso/workflows/router/validate_router.py
index aa22b773..82c46fc6 100644
--- a/gso/workflows/router/validate_router.py
+++ b/gso/workflows/router/validate_router.py
@@ -1,11 +1,9 @@
 """Router validation workflow. Used in a nightly schedule."""
 
-import json
 from typing import Any
 
 from orchestrator.targets import Target
 from orchestrator.utils.errors import ProcessFailureError
-from orchestrator.utils.json import json_dumps
 from orchestrator.workflow import StepList, begin, conditional, done, step, workflow
 from orchestrator.workflows.steps import resync, store_process_subscription, unsync
 from orchestrator.workflows.utils import wrap_modify_initial_input_form
@@ -13,10 +11,10 @@ from pydantic_forms.types import State, UUIDstr
 
 from gso.products.product_blocks.router import RouterRole
 from gso.products.product_types.router import Router
-from gso.services import infoblox, lso_client
+from gso.services import infoblox
 from gso.services.kentik_client import KentikClient
 from gso.services.librenms_client import LibreNMSClient
-from gso.services.lso_client import anonymous_lso_interaction, execute_playbook
+from gso.services.lso_client import anonymous_lso_interaction
 from gso.services.netbox_client import NetboxClient
 from gso.utils.helpers import generate_inventory_for_active_routers
 from gso.utils.shared_enums import Vendor
@@ -55,7 +53,7 @@ def check_netbox_entry_exists(subscription: Router) -> None:
 
 
 @step("Verify BGP configuration on P router")
-def verify_p_ibgp(subscription: dict[str, Any], callback_route: str) -> None:
+def verify_p_ibgp(subscription: dict[str, Any]) -> State:
     """Perform a dry run of adding the list of all PE routers to the new P router."""
     extra_vars = {
         "dry_run": True,
@@ -65,12 +63,11 @@ def verify_p_ibgp(subscription: dict[str, Any], callback_route: str) -> None:
         "is_verification_workflow": "true",
     }
 
-    lso_client.execute_playbook(
-        playbook_name="update_ibgp_mesh.yaml",
-        callback_route=callback_route,
-        inventory=subscription["router"]["router_fqdn"],
-        extra_vars=extra_vars,
-    )
+    return {
+        "playbook_name": "update_ibgp_mesh.yaml",
+        "inventory": {"all": {"hosts": {subscription["router"]["router_fqdn"]: None}}},
+        "extra_vars": extra_vars,
+    }
 
 
 @step("Verify correct LibreNMS entry")
@@ -100,19 +97,18 @@ def check_kentik_entry_exists(subscription: Router) -> None:
 
 
 @step("Check base config for drift")
-def verify_base_config(subscription: Router, callback_route: str) -> None:
+def verify_base_config(subscription: dict[str, Any]) -> State:
     """Workflow step for running a playbook that checks whether base config has drifted."""
-    execute_playbook(
-        playbook_name="base_config.yaml",
-        callback_route=callback_route,
-        inventory=subscription.router.router_fqdn,
-        extra_vars={
-            "wfo_router_json": json.loads(json_dumps(subscription)),
+    return {
+        "playbook_name": "base_config.yaml",
+        "inventory": {"all": {"hosts": {subscription["router"]["router_fqdn"]: None}}},
+        "extra_vars": {
+            "wfo_router_json": subscription,
             "verb": "deploy",
             "dry_run": "true",
             "is_verification_workflow": "true",
         },
-    )
+    }
 
 
 @workflow(
diff --git a/test/workflows/iptrunk/test_create_iptrunk.py b/test/workflows/iptrunk/test_create_iptrunk.py
index 0b7e00e6..267cd382 100644
--- a/test/workflows/iptrunk/test_create_iptrunk.py
+++ b/test/workflows/iptrunk/test_create_iptrunk.py
@@ -101,7 +101,7 @@ def input_form_wizard_data(request, juniper_router_subscription_factory, nokia_r
 
 
 @pytest.mark.workflow()
-@patch("gso.workflows.iptrunk.create_iptrunk.execute_playbook")
+@patch("gso.services.lso_client._send_request")
 @patch("gso.workflows.iptrunk.create_iptrunk.infoblox.allocate_v6_network")
 @patch("gso.workflows.iptrunk.create_iptrunk.infoblox.allocate_v4_network")
 @patch("gso.workflows.iptrunk.create_iptrunk.infoblox.create_host_by_ip")
@@ -162,7 +162,7 @@ def test_successful_iptrunk_creation_with_standard_lso_result(
 
 
 @pytest.mark.workflow()
-@patch("gso.workflows.iptrunk.create_iptrunk.execute_playbook")
+@patch("gso.services.lso_client._send_request")
 @patch("gso.workflows.iptrunk.create_iptrunk.infoblox.allocate_v6_network")
 @patch("gso.workflows.iptrunk.create_iptrunk.infoblox.allocate_v4_network")
 @patch("gso.workflows.iptrunk.create_iptrunk.infoblox.find_host_by_ip")
@@ -199,7 +199,7 @@ def test_iptrunk_creation_fails_when_lso_return_code_is_one(
 
 @pytest.mark.parametrize("input_form_wizard_data", [Vendor.JUNIPER], indirect=True)
 @pytest.mark.workflow()
-@patch("gso.workflows.iptrunk.create_iptrunk.execute_playbook")
+@patch("gso.services.lso_client._send_request")
 @patch("gso.workflows.iptrunk.create_iptrunk.infoblox.allocate_v6_network")
 @patch("gso.workflows.iptrunk.create_iptrunk.infoblox.allocate_v4_network")
 @patch("gso.workflows.iptrunk.create_iptrunk.infoblox.create_host_by_ip")
diff --git a/test/workflows/iptrunk/test_deploy_twamp.py b/test/workflows/iptrunk/test_deploy_twamp.py
index 8584cd99..e65feac7 100644
--- a/test/workflows/iptrunk/test_deploy_twamp.py
+++ b/test/workflows/iptrunk/test_deploy_twamp.py
@@ -12,7 +12,7 @@ from test.workflows import (
 
 
 @pytest.mark.workflow()
-@patch("gso.workflows.iptrunk.deploy_twamp.execute_playbook")
+@patch("gso.services.lso_client._send_request")
 def test_iptrunk_deploy_twamp_success(
     mock_execute_playbook,
     iptrunk_subscription_factory,
diff --git a/test/workflows/iptrunk/test_modify_isis_metric.py b/test/workflows/iptrunk/test_modify_isis_metric.py
index 38f4b4e8..26a9bbd4 100644
--- a/test/workflows/iptrunk/test_modify_isis_metric.py
+++ b/test/workflows/iptrunk/test_modify_isis_metric.py
@@ -12,7 +12,7 @@ from test.workflows import (
 
 
 @pytest.mark.workflow()
-@patch("gso.services.lso_client.execute_playbook")
+@patch("gso.services.lso_client._send_request")
 def test_iptrunk_modify_isis_metric_success(
     mock_provision_ip_trunk,
     iptrunk_subscription_factory,
diff --git a/test/workflows/iptrunk/test_terminate_iptrunk.py b/test/workflows/iptrunk/test_terminate_iptrunk.py
index 7fcca946..7319596f 100644
--- a/test/workflows/iptrunk/test_terminate_iptrunk.py
+++ b/test/workflows/iptrunk/test_terminate_iptrunk.py
@@ -3,6 +3,7 @@ from unittest.mock import patch
 import pytest
 
 from gso.products import Iptrunk
+from gso.products.product_blocks.router import RouterRole
 from gso.settings import load_oss_params
 from test.services.conftest import MockedNetboxClient
 from test.workflows import (
@@ -14,8 +15,7 @@ from test.workflows import (
 
 
 @pytest.mark.workflow()
-@patch("gso.workflows.iptrunk.terminate_iptrunk.execute_playbook")
-@patch("gso.utils.workflow_steps.lso_client.execute_playbook")
+@patch("gso.services.lso_client._send_request")
 @patch("gso.workflows.iptrunk.terminate_iptrunk.infoblox.delete_network")
 @patch("gso.services.netbox_client.NetboxClient.delete_interface")
 @patch("gso.services.netbox_client.NetboxClient.free_interface")
@@ -23,17 +23,20 @@ def test_successful_iptrunk_termination(
     mocked_free_interface,
     mocked_delete_interface,
     mock_infoblox_delete_network,
-    mock_set_isis_to_90k,
     mock_execute_playbook,
     iptrunk_subscription_factory,
     faker,
     data_config_filename,
+    nokia_router_subscription_factory,
 ):
     #  Set up mock return values
     product_id = iptrunk_subscription_factory()
     mocked_netbox = MockedNetboxClient()
     mocked_delete_interface.return_value = mocked_netbox.delete_interface()
     mocked_free_interface.return_value = mocked_netbox.free_interface()
+    #  Add two more routers to our fake network
+    nokia_router_subscription_factory(router_role=RouterRole.P)
+    nokia_router_subscription_factory(router_role=RouterRole.PE)
 
     #  Run workflow
     oss_params = load_oss_params()
@@ -60,7 +63,6 @@ def test_successful_iptrunk_termination(
     subscription = Iptrunk.from_subscription(subscription_id)
 
     assert subscription.status == "terminated"
-    assert mock_execute_playbook.call_count == 2
-    assert mock_set_isis_to_90k.call_count == 1
+    assert mock_execute_playbook.call_count == 3
     assert mock_infoblox_delete_network.call_count == 2
     assert subscription.iptrunk.iptrunk_isis_metric == oss_params.GENERAL.isis_high_metric
diff --git a/test/workflows/iptrunk/test_validate_iptrunk.py b/test/workflows/iptrunk/test_validate_iptrunk.py
index 13bcaed9..6b132abc 100644
--- a/test/workflows/iptrunk/test_validate_iptrunk.py
+++ b/test/workflows/iptrunk/test_validate_iptrunk.py
@@ -69,7 +69,7 @@ def _mocked_netbox_client():
 @patch("gso.services.infoblox.find_network_by_cidr")
 @patch("gso.services.infoblox.find_v6_host_by_fqdn")
 @patch("gso.services.infoblox.find_host_by_fqdn")
-@patch("gso.workflows.iptrunk.validate_iptrunk.execute_playbook")
+@patch("gso.services.lso_client._send_request")
 @patch("gso.services.netbox_client.NetboxClient.get_interface_by_name_and_device")
 def test_validate_iptrunk_success(
     mock_get_interface_by_name,
@@ -219,7 +219,7 @@ def test_validate_iptrunk_success(
 @patch("gso.services.infoblox.find_network_by_cidr")
 @patch("gso.services.infoblox.find_v6_host_by_fqdn")
 @patch("gso.services.infoblox.find_host_by_fqdn")
-@patch("gso.workflows.iptrunk.validate_iptrunk.execute_playbook")
+@patch("gso.services.lso_client._send_request")
 @patch("gso.services.netbox_client.NetboxClient.get_interface_by_name_and_device")
 def test_validate_iptrunk_skip_legacy_trunks(
     mock_get_interface_by_name,
diff --git a/test/workflows/router/test_create_router.py b/test/workflows/router/test_create_router.py
index 83a2e533..a6bf456f 100644
--- a/test/workflows/router/test_create_router.py
+++ b/test/workflows/router/test_create_router.py
@@ -36,7 +36,7 @@ def router_creation_input_form_data(site_subscription_factory, faker):
 
 
 @pytest.mark.workflow()
-@patch("gso.utils.workflow_steps.lso_client.execute_playbook")
+@patch("gso.services.lso_client._send_request")
 @patch("gso.workflows.router.create_router.NetboxClient.create_device")
 @patch("gso.workflows.router.create_router.infoblox.hostname_available")
 @patch("gso.workflows.router.create_router.infoblox.find_host_by_fqdn")
@@ -117,7 +117,7 @@ def test_create_nokia_router_success(
 
 
 @pytest.mark.workflow()
-@patch("gso.utils.workflow_steps.lso_client.execute_playbook")
+@patch("gso.services.lso_client._send_request")
 @patch("gso.workflows.router.create_router.NetboxClient.create_device")
 @patch("gso.workflows.router.create_router.infoblox.hostname_available")
 @patch("gso.workflows.router.create_router.infoblox.find_network_by_cidr")
diff --git a/test/workflows/router/test_promote_p_to_pe.py b/test/workflows/router/test_promote_p_to_pe.py
index 3bd7fdca..5e14dcaf 100644
--- a/test/workflows/router/test_promote_p_to_pe.py
+++ b/test/workflows/router/test_promote_p_to_pe.py
@@ -17,7 +17,7 @@ from test.workflows import (
 
 
 @pytest.mark.workflow()
-@patch("gso.workflows.router.promote_p_to_pe.lso_client.execute_playbook")
+@patch("gso.services.lso_client._send_request")
 @patch("gso.workflows.router.promote_p_to_pe.KentikClient")
 def test_promote_p_to_pe_success(
     mock_kentik_client,
@@ -29,6 +29,9 @@ def test_promote_p_to_pe_success(
     """Test the successful promotion of a Nokia P router to a PE router."""
     mock_kentik_client.return_value = MockedKentikClient
     router_id = nokia_router_subscription_factory(router_role=RouterRole.P, status=SubscriptionLifecycle.ACTIVE)
+    #  Add two more routers to our fake network
+    nokia_router_subscription_factory(router_role=RouterRole.P)
+    nokia_router_subscription_factory(router_role=RouterRole.PE)
     input_data = [{"subscription_id": router_id}, {"tt_number": faker.tt_number()}]
     result, process_stat, step_log = run_workflow("promote_p_to_pe", input_data)
     for _ in range(3):
@@ -55,7 +58,7 @@ def test_promote_p_to_pe_juniper_router(juniper_router_subscription_factory, dat
 
 
 @pytest.mark.workflow()
-@patch("gso.workflows.router.promote_p_to_pe.lso_client.execute_playbook")
+@patch("gso.services.lso_client._send_request")
 def test_promote_p_to_pe_nokia_pe_router(
     mock_execute_playbook, nokia_router_subscription_factory, data_config_filename, faker
 ):
diff --git a/test/workflows/router/test_terminate_router.py b/test/workflows/router/test_terminate_router.py
index f2cb638a..a5f4fd3d 100644
--- a/test/workflows/router/test_terminate_router.py
+++ b/test/workflows/router/test_terminate_router.py
@@ -30,6 +30,9 @@ def test_terminate_pe_router_full_success(
 ):
     #  Prepare mock values and expected results
     product_id = nokia_router_subscription_factory()
+    #  Add two more routers to our fake network
+    nokia_router_subscription_factory(router_role=RouterRole.P)
+    nokia_router_subscription_factory(router_role=RouterRole.PE)
     router_termination_input_form_data = {
         "tt_number": faker.tt_number(),
         "remove_configuration": remove_configuration,
@@ -81,6 +84,9 @@ def test_terminate_p_router_full_success(
 ):
     #  Prepare mock values and expected results
     product_id = nokia_router_subscription_factory(router_role=RouterRole.P)
+    #  Add two more routers to our fake network
+    nokia_router_subscription_factory(router_role=RouterRole.P)
+    nokia_router_subscription_factory(router_role=RouterRole.PE)
     router_termination_input_form_data = {
         "tt_number": faker.tt_number(),
         "remove_configuration": remove_configuration,
diff --git a/test/workflows/router/test_update_ibgp_mesh.py b/test/workflows/router/test_update_ibgp_mesh.py
index 2aa0a7b7..b473f2ea 100644
--- a/test/workflows/router/test_update_ibgp_mesh.py
+++ b/test/workflows/router/test_update_ibgp_mesh.py
@@ -19,7 +19,7 @@ from test.workflows import (
 
 @pytest.mark.parametrize("trunk_status", [SubscriptionLifecycle.PROVISIONING, SubscriptionLifecycle.ACTIVE])
 @pytest.mark.workflow()
-@patch("gso.workflows.router.update_ibgp_mesh.lso_client.execute_playbook")
+@patch("gso.services.lso_client._send_request")
 @patch("gso.workflows.router.update_ibgp_mesh.librenms_client.LibreNMSClient.add_device")
 @patch("gso.workflows.router.update_ibgp_mesh.librenms_client.LibreNMSClient.device_exists")
 def test_update_ibgp_mesh_success(
diff --git a/test/workflows/router/test_validate_router.py b/test/workflows/router/test_validate_router.py
index 924577e2..07fc78d7 100644
--- a/test/workflows/router/test_validate_router.py
+++ b/test/workflows/router/test_validate_router.py
@@ -15,7 +15,7 @@ from test.workflows import (
 
 @pytest.mark.workflow()
 @patch("gso.services.infoblox.find_host_by_fqdn")
-@patch("gso.services.lso_client.execute_playbook")
+@patch("gso.services.lso_client._send_request")
 @patch("gso.services.netbox_client.NetboxClient.get_device_by_name")
 @patch("gso.services.librenms_client.LibreNMSClient.validate_device")
 @patch("gso.services.kentik_client.KentikClient")
-- 
GitLab