From c9cbd9fa09a2710673b5dfe8d075cb609bb1670a Mon Sep 17 00:00:00 2001
From: "Georgios P. Katsikas" <gkatsikas@ubitech.eu>
Date: Sat, 22 Mar 2025 08:09:29 +0000
Subject: [PATCH] fix: refactoring of p4 fabric tna testing suite

---
 .../service/service_handlers/__init__.py      |   4 +-
 .../{p4_l1 => p4_dummy_l1}/__init__.py        |   0
 .../p4_dummy_l1_service_handler.py}           |   4 +-
 .../README.md                                 |  40 ++--
 .../__init__.py                               |   0
 .../descriptors/sbi-rules-insert-acl.json     |   0
 .../descriptors/sbi-rules-insert-int-b1.json  |   0
 .../descriptors/sbi-rules-insert-int-b2.json  |   0
 .../descriptors/sbi-rules-insert-int-b3.json  |   0
 .../sbi-rules-insert-routing-corp.json        |   0
 .../sbi-rules-insert-routing-edge.json        |   0
 .../descriptors/sbi-rules-remove.json         |   0
 .../descriptors/topology.json                 |   0
 .../p4src/README.md                           |   0
 .../p4src/_pp.p4                              |   0
 .../p4src/bmv2.json                           |   0
 .../p4src/p4info.txt                          |   0
 .../run_test_01_bootstrap.sh                  |   2 +-
 ...n_test_02a_sbi_provision_int_l2_l3_acl.sh} |   2 +-
 ..._test_02b_sbi_deprovision_int_l2_l3_acl.sh |  17 ++
 .../run_test_07_cleanup.sh}                   |   2 +-
 .../run_test_08_purge.sh}                     |   2 +-
 .../setup.sh                                  |   4 +-
 .../test_functional_sbi_rules_deprovision.py  |   2 +-
 .../test_functional_sbi_rules_provision.py    |   2 +-
 .../tests-setup}/test_functional_bootstrap.py |   2 +-
 .../tests-setup}/test_functional_cleanup.py   |   5 +-
 .../tests-setup/test_functional_purge.py      |  81 +++++++++
 .../topology/README.md                        |   0
 .../topology/p4-switch-conf-common.sh         |   0
 .../topology/p4-switch-setup.sh               |   0
 .../topology/p4-switch-tear-down.sh           |   0
 ...witch-three-port-chassis-config-phy.pb.txt |   0
 .../topology/run-stratum.sh                   |   0
 src/tests/p4-int-routing-acl/test_common.py   | 116 ------------
 src/tests/tools/test_tools_p4.py              | 172 ++++++++++++++++++
 36 files changed, 309 insertions(+), 148 deletions(-)
 rename src/service/service/service_handlers/{p4_l1 => p4_dummy_l1}/__init__.py (100%)
 rename src/service/service/service_handlers/{p4_l1/p4_l1_service_handler.py => p4_dummy_l1/p4_dummy_l1_service_handler.py} (99%)
 rename src/tests/{p4-int-routing-acl => p4-fabric-tna}/README.md (73%)
 rename src/tests/{p4-int-routing-acl => p4-fabric-tna}/__init__.py (100%)
 rename src/tests/{p4-int-routing-acl => p4-fabric-tna}/descriptors/sbi-rules-insert-acl.json (100%)
 rename src/tests/{p4-int-routing-acl => p4-fabric-tna}/descriptors/sbi-rules-insert-int-b1.json (100%)
 rename src/tests/{p4-int-routing-acl => p4-fabric-tna}/descriptors/sbi-rules-insert-int-b2.json (100%)
 rename src/tests/{p4-int-routing-acl => p4-fabric-tna}/descriptors/sbi-rules-insert-int-b3.json (100%)
 rename src/tests/{p4-int-routing-acl => p4-fabric-tna}/descriptors/sbi-rules-insert-routing-corp.json (100%)
 rename src/tests/{p4-int-routing-acl => p4-fabric-tna}/descriptors/sbi-rules-insert-routing-edge.json (100%)
 rename src/tests/{p4-int-routing-acl => p4-fabric-tna}/descriptors/sbi-rules-remove.json (100%)
 rename src/tests/{p4-int-routing-acl => p4-fabric-tna}/descriptors/topology.json (100%)
 rename src/tests/{p4-int-routing-acl => p4-fabric-tna}/p4src/README.md (100%)
 rename src/tests/{p4-int-routing-acl => p4-fabric-tna}/p4src/_pp.p4 (100%)
 rename src/tests/{p4-int-routing-acl => p4-fabric-tna}/p4src/bmv2.json (100%)
 rename src/tests/{p4-int-routing-acl => p4-fabric-tna}/p4src/p4info.txt (100%)
 rename src/tests/{p4-int-routing-acl => p4-fabric-tna}/run_test_01_bootstrap.sh (89%)
 rename src/tests/{p4-int-routing-acl/run_test_03_sbi_rules_deprovision.sh => p4-fabric-tna/run_test_02a_sbi_provision_int_l2_l3_acl.sh} (86%)
 create mode 100755 src/tests/p4-fabric-tna/run_test_02b_sbi_deprovision_int_l2_l3_acl.sh
 rename src/tests/{p4-int-routing-acl/run_test_02_sbi_rules_provision.sh => p4-fabric-tna/run_test_07_cleanup.sh} (87%)
 rename src/tests/{p4-int-routing-acl/run_test_06_cleanup.sh => p4-fabric-tna/run_test_08_purge.sh} (88%)
 rename src/tests/{p4-int-routing-acl => p4-fabric-tna}/setup.sh (80%)
 rename src/tests/{p4-int-routing-acl => p4-fabric-tna/tests-sbi}/test_functional_sbi_rules_deprovision.py (99%)
 rename src/tests/{p4-int-routing-acl => p4-fabric-tna/tests-sbi}/test_functional_sbi_rules_provision.py (99%)
 rename src/tests/{p4-int-routing-acl => p4-fabric-tna/tests-setup}/test_functional_bootstrap.py (98%)
 rename src/tests/{p4-int-routing-acl => p4-fabric-tna/tests-setup}/test_functional_cleanup.py (94%)
 create mode 100644 src/tests/p4-fabric-tna/tests-setup/test_functional_purge.py
 rename src/tests/{p4-int-routing-acl => p4-fabric-tna}/topology/README.md (100%)
 rename src/tests/{p4-int-routing-acl => p4-fabric-tna}/topology/p4-switch-conf-common.sh (100%)
 rename src/tests/{p4-int-routing-acl => p4-fabric-tna}/topology/p4-switch-setup.sh (100%)
 rename src/tests/{p4-int-routing-acl => p4-fabric-tna}/topology/p4-switch-tear-down.sh (100%)
 rename src/tests/{p4-int-routing-acl => p4-fabric-tna}/topology/p4-switch-three-port-chassis-config-phy.pb.txt (100%)
 rename src/tests/{p4-int-routing-acl => p4-fabric-tna}/topology/run-stratum.sh (100%)
 delete mode 100644 src/tests/p4-int-routing-acl/test_common.py
 create mode 100644 src/tests/tools/test_tools_p4.py

diff --git a/src/service/service/service_handlers/__init__.py b/src/service/service/service_handlers/__init__.py
index ccf821b2f..bddc8ee6e 100644
--- a/src/service/service/service_handlers/__init__.py
+++ b/src/service/service/service_handlers/__init__.py
@@ -25,7 +25,7 @@ from .l3nm_ietf_actn.L3NMIetfActnServiceHandler import L3NMIetfActnServiceHandle
 from .l3nm_nce.L3NMNCEServiceHandler import L3NMNCEServiceHandler
 from .l3slice_ietfslice.L3SliceIETFSliceServiceHandler import L3NMSliceIETFSliceServiceHandler
 from .microwave.MicrowaveServiceHandler import MicrowaveServiceHandler
-from .p4_l1.p4_l1_service_handler import P4L1ServiceHandler
+from .p4_dummy_l1.p4_dummy_l1_service_handler import P4DummyL1ServiceHandler
 from .tapi_tapi.TapiServiceHandler import TapiServiceHandler
 from .tapi_xr.TapiXrServiceHandler import TapiXrServiceHandler
 from .e2e_orch.E2EOrchestratorServiceHandler import E2EOrchestratorServiceHandler
@@ -105,7 +105,7 @@ SERVICE_HANDLERS = [
             FilterFieldEnum.DEVICE_DRIVER : [DeviceDriverEnum.DEVICEDRIVER_IETF_NETWORK_TOPOLOGY, DeviceDriverEnum.DEVICEDRIVER_ONF_TR_532],
         }
     ]),
-    (P4L1ServiceHandler, [
+    (P4DummyL1ServiceHandler, [
         {
             FilterFieldEnum.SERVICE_TYPE: ServiceTypeEnum.SERVICETYPE_L1NM,
             FilterFieldEnum.DEVICE_DRIVER: DeviceDriverEnum.DEVICEDRIVER_P4,
diff --git a/src/service/service/service_handlers/p4_l1/__init__.py b/src/service/service/service_handlers/p4_dummy_l1/__init__.py
similarity index 100%
rename from src/service/service/service_handlers/p4_l1/__init__.py
rename to src/service/service/service_handlers/p4_dummy_l1/__init__.py
diff --git a/src/service/service/service_handlers/p4_l1/p4_l1_service_handler.py b/src/service/service/service_handlers/p4_dummy_l1/p4_dummy_l1_service_handler.py
similarity index 99%
rename from src/service/service/service_handlers/p4_l1/p4_l1_service_handler.py
rename to src/service/service/service_handlers/p4_dummy_l1/p4_dummy_l1_service_handler.py
index b1ff9c600..6e9141caf 100644
--- a/src/service/service/service_handlers/p4_l1/p4_l1_service_handler.py
+++ b/src/service/service/service_handlers/p4_dummy_l1/p4_dummy_l1_service_handler.py
@@ -29,7 +29,7 @@ from service.service.task_scheduler.TaskExecutor import TaskExecutor
 
 LOGGER = logging.getLogger(__name__)
 
-METRICS_POOL = MetricsPool('Service', 'Handler', labels={'handler': 'p4_l1'})
+METRICS_POOL = MetricsPool('Service', 'Handler', labels={'handler': 'p4_dummy_l1'})
 
 def create_rule_set(endpoint_a, endpoint_b):
     return json_config_rule_set(
@@ -83,7 +83,7 @@ def find_names(uuid_a, uuid_b, device_endpoints):
 
     return (endpoint_a, endpoint_b)
 
-class P4L1ServiceHandler(_ServiceHandler):
+class P4DummyL1ServiceHandler(_ServiceHandler):
     def __init__(   # pylint: disable=super-init-not-called
         self, service : Service, task_executor : TaskExecutor, **settings
     ) -> None:
diff --git a/src/tests/p4-int-routing-acl/README.md b/src/tests/p4-fabric-tna/README.md
similarity index 73%
rename from src/tests/p4-int-routing-acl/README.md
rename to src/tests/p4-fabric-tna/README.md
index fa935e1b2..fc49276a9 100644
--- a/src/tests/p4-int-routing-acl/README.md
+++ b/src/tests/p4-fabric-tna/README.md
@@ -1,6 +1,7 @@
 # Tests for P4 routing, ACL, and In-Band Network Telemetry functions
 
-This directory contains the necessary scripts and configurations to run tests atop a Stratum-based P4 whitebox that performs a set of network functions, including routing, access control list (ACL), and In-Band Network Telemetry (INT).
+This directory contains the necessary scripts and configurations to run tests atop a Stratum-based P4 whitebox that performs a set of network functions, including forwarding (L2), routing (L3), L3/L4 access control list (ACL), and In-Band Network Telemetry (INT).
+The P4 data plane is based on ONF's SD-Fabric implementation, titled [fabric-tna](https://github.com/stratum/fabric-tna)
 
 ## Prerequisites
 
@@ -16,6 +17,7 @@ pip3 install grpcio-tools
 ```
 
 The versions used for this test are:
+
 - `protobuf` 3.20.3
 - `grpclib` 0.4.4
 - `grpcio` 1.47.5
@@ -29,11 +31,11 @@ First we copy it to /usr/local/bin/:
 ```
 
 Then, we include this path to the PYTHONPATH:
+
 ```shell
 export PYTHONPATH="${PYTHONPATH}:/usr/local/bin/protoc-gen-grpclib_python"
 ```
 
-
 You need to build and deploy a software-based Stratum switch, before being able to use TFS to control it.
 To do so, follow the instructions in the `./topology` folder.
 
@@ -41,7 +43,7 @@ To do so, follow the instructions in the `./topology` folder.
 
 To conduct this test, follow the steps below.
 
-### TFS re-deploy
+### Deploy TFS
 
 ```shell
 cd ~/tfs-ctrl/
@@ -67,7 +69,7 @@ The `./setup.sh` script copies from this directory. If you need to change the P4
 
 ## Tests
 
-The following tests are implemented.
+A set of tests is implemented, each focusing on different aspects of TFS.
 For each of these tests, an auxiliary bash script allows to run it with less typing.
 
 |                 Test                 |              Bash Runner           |                Purpose             |
@@ -80,7 +82,7 @@ For each of these tests, an auxiliary bash script allows to run it with less typ
 
 Each of the tests above is described in detail below.
 
-### Step 1: Copy the necessary P4 artifacts into the TFS SBI service pod
+### Step 0: Copy the necessary P4 artifacts into the TFS SBI service pod
 
 The setup script copies the necessary artifacts to the SBI service pod.
 It should be run just once, after a fresh install of TFS.
@@ -89,34 +91,33 @@ If you `deploy/all.sh` again, you need to repeat this step.
 ```shell
 cd ~/tfs-ctrl/
 source my_deploy.sh && source tfs_runtime_env_vars.sh
-bash src/tests/p4-int-routing-acl/setup.sh
+bash src/tests/p4-fabric-tna/setup.sh
 ```
 
-### Step 2: Bootstrap topology
+### Step 1: Bootstrap topology
 
 The bootstrap script registers the context, topology, links, and devices to TFS.
 
 ```shell
 cd ~/tfs-ctrl/
-bash src/tests/p4-int-routing-acl/run_test_01_bootstrap.sh
+bash src/tests/p4-fabric-tna/run_test_01_bootstrap.sh
 ```
 
-### Step 3: Provision rules via the SBI API
+### Step 2: Manage L2, L3, ACL, and INT via the SBI API (rules)
 
-Implement routing, ACL, and INT functions by installing P4 rules to the Stratum switch via the TFS SBI API.
+Implement forwarding, routing, ACL, and INT network functions by installing P4 rules to the Stratum switch via the TFS SBI API.
+In this test, these rules are installed in batches, as the switch cannot digest all these rules at once.
 
 ```shell
 cd ~/tfs-ctrl/
-bash src/tests/p4-int-routing-acl/run_test_02_rules_provision.sh
+bash src/tests/p4-fabric-tna/run_test_02a_sbi_provision_int_l2_l3_acl.sh
 ```
 
-### Step 4: Deprovision rules via the SBI API
-
-Deprovision the routing, ACL, and INT network functions by removing the previously installed P4 rules (via the TFS SBI API) from the Stratum switch.
+Deprovision forwarding, routing, ACL, and INT network functions by removing the previously installed P4 rules (via the TFS SBI API) from the Stratum switch.
 
 ```shell
 cd ~/tfs-ctrl/
-bash src/tests/p4-int-routing-acl/run_test_03_rules_deprovision.sh
+bash src/tests/p4-fabric-tna/run_test_02b_sbi_deprovision_int_l2_l3_acl.sh
 ```
 
 ### Step 4: Deprovision topology
@@ -125,5 +126,12 @@ Delete all the objects (context, topology, links, devices) from TFS:
 
 ```shell
 cd ~/tfs-ctrl/
-bash src/tests/p4-int-routing-acl/run_test_04_cleanup.sh
+bash src/tests/p4-fabric-tna/run_test_07_cleanup.sh
+```
+
+Alternatively, a purge test is implemented; this test removes services, links, devices, topology, and context in this order.
+
+```shell
+cd ~/tfs-ctrl/
+bash src/tests/p4-fabric-tna/run_test_08_purge.sh
 ```
diff --git a/src/tests/p4-int-routing-acl/__init__.py b/src/tests/p4-fabric-tna/__init__.py
similarity index 100%
rename from src/tests/p4-int-routing-acl/__init__.py
rename to src/tests/p4-fabric-tna/__init__.py
diff --git a/src/tests/p4-int-routing-acl/descriptors/sbi-rules-insert-acl.json b/src/tests/p4-fabric-tna/descriptors/sbi-rules-insert-acl.json
similarity index 100%
rename from src/tests/p4-int-routing-acl/descriptors/sbi-rules-insert-acl.json
rename to src/tests/p4-fabric-tna/descriptors/sbi-rules-insert-acl.json
diff --git a/src/tests/p4-int-routing-acl/descriptors/sbi-rules-insert-int-b1.json b/src/tests/p4-fabric-tna/descriptors/sbi-rules-insert-int-b1.json
similarity index 100%
rename from src/tests/p4-int-routing-acl/descriptors/sbi-rules-insert-int-b1.json
rename to src/tests/p4-fabric-tna/descriptors/sbi-rules-insert-int-b1.json
diff --git a/src/tests/p4-int-routing-acl/descriptors/sbi-rules-insert-int-b2.json b/src/tests/p4-fabric-tna/descriptors/sbi-rules-insert-int-b2.json
similarity index 100%
rename from src/tests/p4-int-routing-acl/descriptors/sbi-rules-insert-int-b2.json
rename to src/tests/p4-fabric-tna/descriptors/sbi-rules-insert-int-b2.json
diff --git a/src/tests/p4-int-routing-acl/descriptors/sbi-rules-insert-int-b3.json b/src/tests/p4-fabric-tna/descriptors/sbi-rules-insert-int-b3.json
similarity index 100%
rename from src/tests/p4-int-routing-acl/descriptors/sbi-rules-insert-int-b3.json
rename to src/tests/p4-fabric-tna/descriptors/sbi-rules-insert-int-b3.json
diff --git a/src/tests/p4-int-routing-acl/descriptors/sbi-rules-insert-routing-corp.json b/src/tests/p4-fabric-tna/descriptors/sbi-rules-insert-routing-corp.json
similarity index 100%
rename from src/tests/p4-int-routing-acl/descriptors/sbi-rules-insert-routing-corp.json
rename to src/tests/p4-fabric-tna/descriptors/sbi-rules-insert-routing-corp.json
diff --git a/src/tests/p4-int-routing-acl/descriptors/sbi-rules-insert-routing-edge.json b/src/tests/p4-fabric-tna/descriptors/sbi-rules-insert-routing-edge.json
similarity index 100%
rename from src/tests/p4-int-routing-acl/descriptors/sbi-rules-insert-routing-edge.json
rename to src/tests/p4-fabric-tna/descriptors/sbi-rules-insert-routing-edge.json
diff --git a/src/tests/p4-int-routing-acl/descriptors/sbi-rules-remove.json b/src/tests/p4-fabric-tna/descriptors/sbi-rules-remove.json
similarity index 100%
rename from src/tests/p4-int-routing-acl/descriptors/sbi-rules-remove.json
rename to src/tests/p4-fabric-tna/descriptors/sbi-rules-remove.json
diff --git a/src/tests/p4-int-routing-acl/descriptors/topology.json b/src/tests/p4-fabric-tna/descriptors/topology.json
similarity index 100%
rename from src/tests/p4-int-routing-acl/descriptors/topology.json
rename to src/tests/p4-fabric-tna/descriptors/topology.json
diff --git a/src/tests/p4-int-routing-acl/p4src/README.md b/src/tests/p4-fabric-tna/p4src/README.md
similarity index 100%
rename from src/tests/p4-int-routing-acl/p4src/README.md
rename to src/tests/p4-fabric-tna/p4src/README.md
diff --git a/src/tests/p4-int-routing-acl/p4src/_pp.p4 b/src/tests/p4-fabric-tna/p4src/_pp.p4
similarity index 100%
rename from src/tests/p4-int-routing-acl/p4src/_pp.p4
rename to src/tests/p4-fabric-tna/p4src/_pp.p4
diff --git a/src/tests/p4-int-routing-acl/p4src/bmv2.json b/src/tests/p4-fabric-tna/p4src/bmv2.json
similarity index 100%
rename from src/tests/p4-int-routing-acl/p4src/bmv2.json
rename to src/tests/p4-fabric-tna/p4src/bmv2.json
diff --git a/src/tests/p4-int-routing-acl/p4src/p4info.txt b/src/tests/p4-fabric-tna/p4src/p4info.txt
similarity index 100%
rename from src/tests/p4-int-routing-acl/p4src/p4info.txt
rename to src/tests/p4-fabric-tna/p4src/p4info.txt
diff --git a/src/tests/p4-int-routing-acl/run_test_01_bootstrap.sh b/src/tests/p4-fabric-tna/run_test_01_bootstrap.sh
similarity index 89%
rename from src/tests/p4-int-routing-acl/run_test_01_bootstrap.sh
rename to src/tests/p4-fabric-tna/run_test_01_bootstrap.sh
index 76469ca55..81d87476e 100755
--- a/src/tests/p4-int-routing-acl/run_test_01_bootstrap.sh
+++ b/src/tests/p4-fabric-tna/run_test_01_bootstrap.sh
@@ -18,4 +18,4 @@
 # - tfs_runtime_env_vars.sh
 
 source tfs_runtime_env_vars.sh
-python3 -m pytest --verbose src/tests/p4-int-routing-acl/test_functional_bootstrap.py
+python3 -m pytest --verbose src/tests/p4-fabric-tna/tests-setup/test_functional_bootstrap.py
diff --git a/src/tests/p4-int-routing-acl/run_test_03_sbi_rules_deprovision.sh b/src/tests/p4-fabric-tna/run_test_02a_sbi_provision_int_l2_l3_acl.sh
similarity index 86%
rename from src/tests/p4-int-routing-acl/run_test_03_sbi_rules_deprovision.sh
rename to src/tests/p4-fabric-tna/run_test_02a_sbi_provision_int_l2_l3_acl.sh
index 4032c01df..a3ab51c22 100755
--- a/src/tests/p4-int-routing-acl/run_test_03_sbi_rules_deprovision.sh
+++ b/src/tests/p4-fabric-tna/run_test_02a_sbi_provision_int_l2_l3_acl.sh
@@ -14,4 +14,4 @@
 # limitations under the License.
 
 source tfs_runtime_env_vars.sh
-python3 -m pytest --verbose src/tests/p4-int-routing-acl/test_functional_sbi_rules_deprovision.py
+python3 -m pytest --verbose src/tests/p4-fabric-tna/tests-sbi/test_functional_sbi_rules_provision.py
diff --git a/src/tests/p4-fabric-tna/run_test_02b_sbi_deprovision_int_l2_l3_acl.sh b/src/tests/p4-fabric-tna/run_test_02b_sbi_deprovision_int_l2_l3_acl.sh
new file mode 100755
index 000000000..49a686638
--- /dev/null
+++ b/src/tests/p4-fabric-tna/run_test_02b_sbi_deprovision_int_l2_l3_acl.sh
@@ -0,0 +1,17 @@
+#!/bin/bash
+# Copyright 2022-2024 ETSI SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/)
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+source tfs_runtime_env_vars.sh
+python3 -m pytest --verbose src/tests/p4-fabric-tna/tests-sbi/test_functional_sbi_rules_deprovision.py
diff --git a/src/tests/p4-int-routing-acl/run_test_02_sbi_rules_provision.sh b/src/tests/p4-fabric-tna/run_test_07_cleanup.sh
similarity index 87%
rename from src/tests/p4-int-routing-acl/run_test_02_sbi_rules_provision.sh
rename to src/tests/p4-fabric-tna/run_test_07_cleanup.sh
index 7c485d401..c7b829168 100755
--- a/src/tests/p4-int-routing-acl/run_test_02_sbi_rules_provision.sh
+++ b/src/tests/p4-fabric-tna/run_test_07_cleanup.sh
@@ -14,4 +14,4 @@
 # limitations under the License.
 
 source tfs_runtime_env_vars.sh
-python3 -m pytest --verbose src/tests/p4-int-routing-acl/test_functional_sbi_rules_provision.py
+python3 -m pytest --verbose src/tests/p4-fabric-tna/tests-setup/test_functional_cleanup.py
diff --git a/src/tests/p4-int-routing-acl/run_test_06_cleanup.sh b/src/tests/p4-fabric-tna/run_test_08_purge.sh
similarity index 88%
rename from src/tests/p4-int-routing-acl/run_test_06_cleanup.sh
rename to src/tests/p4-fabric-tna/run_test_08_purge.sh
index 917a2af2d..9aef079f1 100755
--- a/src/tests/p4-int-routing-acl/run_test_06_cleanup.sh
+++ b/src/tests/p4-fabric-tna/run_test_08_purge.sh
@@ -14,4 +14,4 @@
 # limitations under the License.
 
 source tfs_runtime_env_vars.sh
-python3 -m pytest --verbose src/tests/p4-int-routing-acl/test_functional_cleanup.py
+python3 -m pytest --verbose src/tests/p4-fabric-tna/tests-setup/test_functional_purge.py
diff --git a/src/tests/p4-int-routing-acl/setup.sh b/src/tests/p4-fabric-tna/setup.sh
similarity index 80%
rename from src/tests/p4-int-routing-acl/setup.sh
rename to src/tests/p4-fabric-tna/setup.sh
index c77164276..9418ac3d7 100755
--- a/src/tests/p4-int-routing-acl/setup.sh
+++ b/src/tests/p4-fabric-tna/setup.sh
@@ -18,5 +18,5 @@ export POD_NAME=$(kubectl get pods -n=tfs | grep device | awk '{print $1}')
 
 kubectl exec ${POD_NAME} -n=tfs -c=server -- mkdir -p /root/p4
 
-kubectl cp src/tests/p4-int-routing-acl/p4src/p4info.txt tfs/${POD_NAME}:/root/p4 -c=server
-kubectl cp src/tests/p4-int-routing-acl/p4src/bmv2.json tfs/${POD_NAME}:/root/p4 -c=server
+kubectl cp src/tests/p4-fabric-tna/p4src/p4info.txt tfs/${POD_NAME}:/root/p4 -c=server
+kubectl cp src/tests/p4-fabric-tna/p4src/bmv2.json tfs/${POD_NAME}:/root/p4 -c=server
diff --git a/src/tests/p4-int-routing-acl/test_functional_sbi_rules_deprovision.py b/src/tests/p4-fabric-tna/tests-sbi/test_functional_sbi_rules_deprovision.py
similarity index 99%
rename from src/tests/p4-int-routing-acl/test_functional_sbi_rules_deprovision.py
rename to src/tests/p4-fabric-tna/tests-sbi/test_functional_sbi_rules_deprovision.py
index 2d54ae908..6d5f7dfd2 100644
--- a/src/tests/p4-int-routing-acl/test_functional_sbi_rules_deprovision.py
+++ b/src/tests/p4-fabric-tna/tests-sbi/test_functional_sbi_rules_deprovision.py
@@ -18,7 +18,7 @@ from common.tools.grpc.Tools import grpc_message_to_json_string
 from context.client.ContextClient import ContextClient
 from device.client.DeviceClient import DeviceClient
 from tests.Fixtures import context_client, device_client   # pylint: disable=unused-import
-from test_common import *
+from tests.tools.test_tools_p4 import *
 
 LOGGER = logging.getLogger(__name__)
 LOGGER.setLevel(logging.DEBUG)
diff --git a/src/tests/p4-int-routing-acl/test_functional_sbi_rules_provision.py b/src/tests/p4-fabric-tna/tests-sbi/test_functional_sbi_rules_provision.py
similarity index 99%
rename from src/tests/p4-int-routing-acl/test_functional_sbi_rules_provision.py
rename to src/tests/p4-fabric-tna/tests-sbi/test_functional_sbi_rules_provision.py
index 86a82d212..49d9aba4d 100644
--- a/src/tests/p4-int-routing-acl/test_functional_sbi_rules_provision.py
+++ b/src/tests/p4-fabric-tna/tests-sbi/test_functional_sbi_rules_provision.py
@@ -18,7 +18,7 @@ from common.tools.grpc.Tools import grpc_message_to_json_string
 from context.client.ContextClient import ContextClient
 from device.client.DeviceClient import DeviceClient
 from tests.Fixtures import context_client, device_client # pylint: disable=unused-import
-from test_common import *
+from tests.tools.test_tools_p4 import *
 
 LOGGER = logging.getLogger(__name__)
 LOGGER.setLevel(logging.DEBUG)
diff --git a/src/tests/p4-int-routing-acl/test_functional_bootstrap.py b/src/tests/p4-fabric-tna/tests-setup/test_functional_bootstrap.py
similarity index 98%
rename from src/tests/p4-int-routing-acl/test_functional_bootstrap.py
rename to src/tests/p4-fabric-tna/tests-setup/test_functional_bootstrap.py
index b5b72cc33..2f9130ad0 100644
--- a/src/tests/p4-int-routing-acl/test_functional_bootstrap.py
+++ b/src/tests/p4-fabric-tna/tests-setup/test_functional_bootstrap.py
@@ -19,7 +19,7 @@ from common.tools.descriptor.Loader import DescriptorLoader, \
 from context.client.ContextClient import ContextClient
 from device.client.DeviceClient import DeviceClient
 from tests.Fixtures import context_client, device_client # pylint: disable=unused-import
-from test_common import *
+from tests.tools.test_tools_p4 import *
 
 LOGGER = logging.getLogger(__name__)
 LOGGER.setLevel(logging.DEBUG)
diff --git a/src/tests/p4-int-routing-acl/test_functional_cleanup.py b/src/tests/p4-fabric-tna/tests-setup/test_functional_cleanup.py
similarity index 94%
rename from src/tests/p4-int-routing-acl/test_functional_cleanup.py
rename to src/tests/p4-fabric-tna/tests-setup/test_functional_cleanup.py
index fc29be24d..4d98c9e05 100644
--- a/src/tests/p4-int-routing-acl/test_functional_cleanup.py
+++ b/src/tests/p4-fabric-tna/tests-setup/test_functional_cleanup.py
@@ -12,13 +12,12 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-import logging, os
-from common.Constants import DEFAULT_CONTEXT_NAME
+import logging
 from common.tools.descriptor.Loader import DescriptorLoader, validate_empty_scenario
 from context.client.ContextClient import ContextClient
 from device.client.DeviceClient import DeviceClient
 from tests.Fixtures import context_client, device_client # pylint: disable=unused-import
-from test_common import *
+from tests.tools.test_tools_p4 import *
 
 LOGGER = logging.getLogger(__name__)
 LOGGER.setLevel(logging.DEBUG)
diff --git a/src/tests/p4-fabric-tna/tests-setup/test_functional_purge.py b/src/tests/p4-fabric-tna/tests-setup/test_functional_purge.py
new file mode 100644
index 000000000..ba37fbd89
--- /dev/null
+++ b/src/tests/p4-fabric-tna/tests-setup/test_functional_purge.py
@@ -0,0 +1,81 @@
+# Copyright 2022-2024 ETSI SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/)
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import logging
+from common.proto.context_pb2 import ServiceId, DeviceId, LinkId, ServiceStatusEnum, ServiceTypeEnum
+from common.tools.grpc.Tools import grpc_message_to_json_string
+from common.tools.object_factory.Service import json_service_id
+from context.client.ContextClient import ContextClient
+from device.client.DeviceClient import DeviceClient
+from service.client.ServiceClient import ServiceClient
+from tests.Fixtures import context_client, device_client, service_client # pylint: disable=unused-import
+from tests.tools.test_tools_p4 import *
+
+LOGGER = logging.getLogger(__name__)
+LOGGER.setLevel(logging.DEBUG)
+
+def test_clean_services(
+    context_client : ContextClient,  # pylint: disable=redefined-outer-name
+    service_client : ServiceClient  # pylint: disable=redefined-outer-name
+) -> None:
+    response = context_client.ListServices(ADMIN_CONTEXT_ID)
+    LOGGER.warning('Services[{:d}] = {:s}'.format(len(response.services), grpc_message_to_json_string(response)))
+    
+    for service in response.services:
+        service_id = service.service_id
+        assert service_id
+
+        service_uuid = service_id.service_uuid.uuid
+        context_uuid = service_id.context_id.context_uuid.uuid
+        assert service.service_status.service_status == ServiceStatusEnum.SERVICESTATUS_ACTIVE
+        assert service.service_type == ServiceTypeEnum.SERVICETYPE_INT
+
+        # Delete service
+        service_client.DeleteService(ServiceId(**json_service_id(service_uuid, json_context_id(context_uuid))))
+
+def test_clean_links(
+    context_client : ContextClient,  # pylint: disable=redefined-outer-name
+) -> None:
+    response = context_client.ListLinks(ADMIN_CONTEXT_ID)
+    
+    for link in response.links:
+        link_id = link.link_id
+
+        # Delete link
+        context_client.RemoveLink(LinkId(**link_id))
+
+def test_clean_devices(
+    context_client : ContextClient,  # pylint: disable=redefined-outer-name
+    device_client : DeviceClient   # pylint: disable=redefined-outer-name
+) -> None:
+    response = context_client.ListDevices(ADMIN_CONTEXT_ID)
+    LOGGER.warning('Devices[{:d}] = {:s}'.format(len(response.devices), grpc_message_to_json_string(response)))
+    
+    for device in response.devices:
+        device_id = device.device_id
+
+        # Delete device
+        device_client.DeleteDevice(DeviceId(**device_id))
+
+def test_clean_context(
+    context_client : ContextClient  # pylint: disable=redefined-outer-name
+) -> None:
+    # Verify the scenario has no services/slices
+    response = context_client.ListTopologies(ADMIN_CONTEXT_ID)
+
+    for topology in response.topologies:
+        topology_id = topology.topology_id
+        response = context_client.RemoveTopology(topology_id)
+
+    response = context_client.RemoveContext(ADMIN_CONTEXT_ID)
diff --git a/src/tests/p4-int-routing-acl/topology/README.md b/src/tests/p4-fabric-tna/topology/README.md
similarity index 100%
rename from src/tests/p4-int-routing-acl/topology/README.md
rename to src/tests/p4-fabric-tna/topology/README.md
diff --git a/src/tests/p4-int-routing-acl/topology/p4-switch-conf-common.sh b/src/tests/p4-fabric-tna/topology/p4-switch-conf-common.sh
similarity index 100%
rename from src/tests/p4-int-routing-acl/topology/p4-switch-conf-common.sh
rename to src/tests/p4-fabric-tna/topology/p4-switch-conf-common.sh
diff --git a/src/tests/p4-int-routing-acl/topology/p4-switch-setup.sh b/src/tests/p4-fabric-tna/topology/p4-switch-setup.sh
similarity index 100%
rename from src/tests/p4-int-routing-acl/topology/p4-switch-setup.sh
rename to src/tests/p4-fabric-tna/topology/p4-switch-setup.sh
diff --git a/src/tests/p4-int-routing-acl/topology/p4-switch-tear-down.sh b/src/tests/p4-fabric-tna/topology/p4-switch-tear-down.sh
similarity index 100%
rename from src/tests/p4-int-routing-acl/topology/p4-switch-tear-down.sh
rename to src/tests/p4-fabric-tna/topology/p4-switch-tear-down.sh
diff --git a/src/tests/p4-int-routing-acl/topology/p4-switch-three-port-chassis-config-phy.pb.txt b/src/tests/p4-fabric-tna/topology/p4-switch-three-port-chassis-config-phy.pb.txt
similarity index 100%
rename from src/tests/p4-int-routing-acl/topology/p4-switch-three-port-chassis-config-phy.pb.txt
rename to src/tests/p4-fabric-tna/topology/p4-switch-three-port-chassis-config-phy.pb.txt
diff --git a/src/tests/p4-int-routing-acl/topology/run-stratum.sh b/src/tests/p4-fabric-tna/topology/run-stratum.sh
similarity index 100%
rename from src/tests/p4-int-routing-acl/topology/run-stratum.sh
rename to src/tests/p4-fabric-tna/topology/run-stratum.sh
diff --git a/src/tests/p4-int-routing-acl/test_common.py b/src/tests/p4-int-routing-acl/test_common.py
deleted file mode 100644
index f2d00f1dc..000000000
--- a/src/tests/p4-int-routing-acl/test_common.py
+++ /dev/null
@@ -1,116 +0,0 @@
-# Copyright 2022-2024 ETSI SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/)
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-import os
-from common.Constants import DEFAULT_CONTEXT_NAME
-from common.proto.context_pb2 import ContextId, DeviceOperationalStatusEnum
-from common.tools.object_factory.Context import json_context_id
-
-# Context info
-CONTEXT_NAME_P4 = DEFAULT_CONTEXT_NAME
-ADMIN_CONTEXT_ID = ContextId(**json_context_id(CONTEXT_NAME_P4))
-
-# Device and rule cardinality variables
-DEV_NB = 4
-CONNECTION_RULES = 3
-ENDPOINT_RULES = 3
-DATAPLANE_RULES_NB_INT_B1 = 5
-DATAPLANE_RULES_NB_INT_B2 = 6
-DATAPLANE_RULES_NB_INT_B3 = 8
-DATAPLANE_RULES_NB_RT_EDGE = 7
-DATAPLANE_RULES_NB_RT_CORP = 7
-DATAPLANE_RULES_NB_ACL = 1
-DATAPLANE_RULES_NB_TOT = \
-    DATAPLANE_RULES_NB_INT_B1 +\
-    DATAPLANE_RULES_NB_INT_B2 +\
-    DATAPLANE_RULES_NB_INT_B3 +\
-    DATAPLANE_RULES_NB_RT_EDGE +\
-    DATAPLANE_RULES_NB_RT_CORP +\
-    DATAPLANE_RULES_NB_ACL
-
-# Service-related variables
-SVC_NB = 1
-NO_SERVICES = 0
-NO_SLICES = 0
-
-# Topology descriptor
-DESC_TOPO = os.path.join(
-    os.path.dirname(
-        os.path.abspath(__file__)
-    ),
-    'descriptors', 'topology.json'
-)
-
-# SBI rule insertion descriptors
-# The switch cannot digest all rules at once, hence we insert in batches
-DESC_FILE_RULES_INSERT_INT_B1 = os.path.join(
-    os.path.dirname(
-        os.path.abspath(__file__)
-    ),
-    'descriptors', 'sbi-rules-insert-int-b1.json'
-)
-DESC_FILE_RULES_INSERT_INT_B2 = os.path.join(
-    os.path.dirname(
-        os.path.abspath(__file__)
-    ),
-    'descriptors', 'sbi-rules-insert-int-b2.json'
-)
-DESC_FILE_RULES_INSERT_INT_B3 = os.path.join(
-    os.path.dirname(
-        os.path.abspath(__file__)
-    ),
-    'descriptors', 'sbi-rules-insert-int-b3.json'
-)
-DESC_FILE_RULES_INSERT_ROUTING_EDGE = os.path.join(
-    os.path.dirname(
-        os.path.abspath(__file__)
-    ),
-    'descriptors', 'sbi-rules-insert-routing-edge.json'
-)
-DESC_FILE_RULES_INSERT_ROUTING_CORP = os.path.join(
-    os.path.dirname(
-        os.path.abspath(__file__)
-    ),
-    'descriptors', 'sbi-rules-insert-routing-corp.json'
-)
-DESC_FILE_RULES_INSERT_ACL = os.path.join(
-    os.path.dirname(
-        os.path.abspath(__file__)
-    ),
-    'descriptors', 'sbi-rules-insert-acl.json'
-)
-
-# SBI rule deletion descriptor
-DESC_FILE_RULES_DELETE_ALL = os.path.join(
-    os.path.dirname(
-        os.path.abspath(__file__)
-    ),
-    'descriptors', 'sbi-rules-remove.json'
-)
-
-def verify_number_of_rules(devices, desired_rules_nb):
-    # Iterate all devices
-    for device in devices:
-        # Skip non-P4 devices
-        if device.device_type != "p4-switch": continue
-
-        # We want the device to be active
-        assert \
-            device.device_operational_status == DeviceOperationalStatusEnum.DEVICEOPERATIONALSTATUS_ENABLED
-
-        # Get the configuration rules of this device
-        config_rules = device.device_config.config_rules
-
-        # Expected rule cardinality
-        assert len(config_rules) == desired_rules_nb
diff --git a/src/tests/tools/test_tools_p4.py b/src/tests/tools/test_tools_p4.py
new file mode 100644
index 000000000..4557fef61
--- /dev/null
+++ b/src/tests/tools/test_tools_p4.py
@@ -0,0 +1,172 @@
+# Copyright 2022-2024 ETSI SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/)
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import os
+from common.Constants import DEFAULT_CONTEXT_NAME
+from common.proto.context_pb2 import ContextId, DeviceOperationalStatusEnum,\
+    DeviceDriverEnum, ServiceTypeEnum, ServiceStatusEnum
+from common.tools.object_factory.Context import json_context_id
+
+# Context info
+CONTEXT_NAME_P4 = DEFAULT_CONTEXT_NAME
+ADMIN_CONTEXT_ID = ContextId(**json_context_id(CONTEXT_NAME_P4))
+
+# Device and rule cardinality variables
+DEV_NB = 4
+P4_DEV_NB = 1
+CONNECTION_RULES = 3
+ENDPOINT_RULES = 3
+INT_RULES = 19
+L2_RULES = 8
+L3_RULES = 8
+ACL_RULES = 1
+
+DATAPLANE_RULES_NB_INT_B1 = 5
+DATAPLANE_RULES_NB_INT_B2 = 6
+DATAPLANE_RULES_NB_INT_B3 = 8
+DATAPLANE_RULES_NB_RT_EDGE = 7
+DATAPLANE_RULES_NB_RT_CORP = 7
+DATAPLANE_RULES_NB_ACL = 1
+DATAPLANE_RULES_NB_TOT = \
+    DATAPLANE_RULES_NB_INT_B1 +\
+    DATAPLANE_RULES_NB_INT_B2 +\
+    DATAPLANE_RULES_NB_INT_B3 +\
+    DATAPLANE_RULES_NB_RT_EDGE +\
+    DATAPLANE_RULES_NB_RT_CORP +\
+    DATAPLANE_RULES_NB_ACL
+
+# Service-related variables
+SVC_NB = 1
+NO_SERVICES = 0
+NO_SLICES = 0
+
+TEST_PATH = os.path.join(
+    os.path.dirname(os.path.dirname(
+        os.path.abspath(__file__)
+    )) + '/p4-fabric-tna/descriptors')
+assert os.path.exists(TEST_PATH), "Invalid path to P4 SD-Fabric tests"
+
+# Topology descriptor
+DESC_TOPO = os.path.join(TEST_PATH, 'topology.json')
+assert os.path.exists(DESC_TOPO), "Invalid path to the SD-Fabric topology descriptor"
+
+# SBI descriptors
+# The switch cannot digest all rules at once, hence we insert in batches
+DESC_FILE_RULES_INSERT_INT_B1 = os.path.join(TEST_PATH, 'sbi-rules-insert-int-b1.json')
+assert os.path.exists(DESC_FILE_RULES_INSERT_INT_B1),\
+    "Invalid path to the SD-Fabric INT SBI descriptor (batch #1)"
+
+DESC_FILE_RULES_INSERT_INT_B2 = os.path.join(TEST_PATH, 'sbi-rules-insert-int-b2.json')
+assert os.path.exists(DESC_FILE_RULES_INSERT_INT_B2),\
+    "Invalid path to the SD-Fabric INT SBI descriptor (batch #2)"
+
+DESC_FILE_RULES_INSERT_INT_B3 = os.path.join(TEST_PATH, 'sbi-rules-insert-int-b3.json')
+assert os.path.exists(DESC_FILE_RULES_INSERT_INT_B3),\
+    "Invalid path to the SD-Fabric INT SBI descriptor (batch #3)"
+
+DESC_FILE_RULES_INSERT_ROUTING_EDGE = os.path.join(TEST_PATH, 'sbi-rules-insert-routing-edge.json')
+assert os.path.exists(DESC_FILE_RULES_INSERT_ROUTING_EDGE),\
+    "Invalid path to the SD-Fabric routing SBI descriptor (domain1-side)"
+
+DESC_FILE_RULES_INSERT_ROUTING_CORP = os.path.join(TEST_PATH, 'sbi-rules-insert-routing-corp.json')
+assert os.path.exists(DESC_FILE_RULES_INSERT_ROUTING_CORP),\
+    "Invalid path to the SD-Fabric routing SBI descriptor (domain2-side)"
+
+DESC_FILE_RULES_INSERT_ACL = os.path.join(TEST_PATH, 'sbi-rules-insert-acl.json')
+assert os.path.exists(DESC_FILE_RULES_INSERT_ACL),\
+    "Invalid path to the SD-Fabric ACL SBI descriptor"
+
+DESC_FILE_RULES_DELETE_ALL = os.path.join(TEST_PATH, 'sbi-rules-remove.json')
+assert os.path.exists(DESC_FILE_RULES_DELETE_ALL),\
+    "Invalid path to the SD-Fabric rule removal SBI descriptor"
+
+# Service descriptors
+DESC_FILE_SERVICE_CREATE_INT = os.path.join(TEST_PATH, 'service-create-int.json')
+assert os.path.exists(DESC_FILE_SERVICE_CREATE_INT),\
+    "Invalid path to the SD-Fabric INT service descriptor"
+
+DESC_FILE_SERVICE_CREATE_L2 = os.path.join(TEST_PATH, 'service-create-l2.json')
+assert os.path.exists(DESC_FILE_SERVICE_CREATE_L2),\
+    "Invalid path to the SD-Fabric L2 service descriptor"
+
+DESC_FILE_SERVICE_CREATE_L3 = os.path.join(TEST_PATH, 'service-create-l3.json')
+assert os.path.exists(DESC_FILE_SERVICE_CREATE_L3),\
+    "Invalid path to the SD-Fabric L3 service descriptor"
+
+DESC_FILE_SERVICE_CREATE_ACL = os.path.join(TEST_PATH, 'service-create-acl.json')
+assert os.path.exists(DESC_FILE_SERVICE_CREATE_ACL),\
+    "Invalid path to the SD-Fabric ACL service descriptor"
+
+def identify_number_of_p4_devices(devices) -> int:
+    p4_dev_no = 0
+
+    # Iterate all devices
+    for device in devices:
+        # Skip non-P4 devices
+        if not DeviceDriverEnum.DEVICEDRIVER_P4 in device.device_drivers: continue
+
+        p4_dev_no += 1
+    
+    return p4_dev_no
+
+def get_number_of_rules(devices) -> int:
+    total_rules_no = 0
+
+    # Iterate all devices
+    for device in devices:
+        # Skip non-P4 devices
+        if not DeviceDriverEnum.DEVICEDRIVER_P4 in device.device_drivers: continue
+
+        # We want the device to be active
+        assert device.device_operational_status == \
+            DeviceOperationalStatusEnum.DEVICEOPERATIONALSTATUS_ENABLED
+
+        # Get the configuration rules of this device
+        config_rules = device.device_config.config_rules
+
+        # Expected rule cardinality
+        total_rules_no += len(config_rules)
+    
+    return total_rules_no
+
+def verify_number_of_rules(devices, desired_rules_nb : int) -> None:
+    # Iterate all devices
+    for device in devices:
+        # Skip non-P4 devices
+        if not DeviceDriverEnum.DEVICEDRIVER_P4 in device.device_drivers: continue
+
+        # We want the device to be active
+        assert device.device_operational_status == \
+            DeviceOperationalStatusEnum.DEVICEOPERATIONALSTATUS_ENABLED
+
+        # Get the configuration rules of this device
+        config_rules = device.device_config.config_rules
+
+        # Expected rule cardinality
+        assert len(config_rules) == desired_rules_nb
+
+def verify_active_service_type(services, target_service_type : ServiceTypeEnum) -> bool: # type: ignore
+    # Iterate all services
+    for service in services:
+        # Ignore services of other types
+        if service.service_type != target_service_type:
+            continue
+
+        service_id = service.service_id
+        assert service_id
+        assert service.service_status.service_status == ServiceStatusEnum.SERVICESTATUS_ACTIVE
+        assert service.service_config
+        return True
+    
+    return False
-- 
GitLab