diff --git a/oeccpsc22 b/oeccpsc22
new file mode 120000
index 0000000000000000000000000000000000000000..4f55befad3e8730c8b7eb1a4cf2fbc7600d1878b
--- /dev/null
+++ b/oeccpsc22
@@ -0,0 +1 @@
+src/tests/oeccpsc22/
\ No newline at end of file
diff --git a/src/tests/.gitlab-ci.yml b/src/tests/.gitlab-ci.yml
index e663b09ec1d79a14f76d37a4ac906e534667ac26..6fcac64803e92b238eb1a63ce92814e67e3138ab 100644
--- a/src/tests/.gitlab-ci.yml
+++ b/src/tests/.gitlab-ci.yml
@@ -14,4 +14,5 @@
 
 # include the individual .gitlab-ci.yml of each integration test
 include:
-  - local: '/src/tests/ofc22_bootstrap_monitor_l3vpn/.gitlab-ci.yml'
+  - local: '/src/tests/ofc22/.gitlab-ci.yml'
+  - local: '/src/tests/oeccpsc22/.gitlab-ci.yml'
diff --git a/src/tests/oeccpsc22/README.md b/src/tests/oeccpsc22/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..42e0228a52bdf9dfc21bc0358b78fb98677ed458
--- /dev/null
+++ b/src/tests/oeccpsc22/README.md
@@ -0,0 +1,8 @@
+# OECC/PSC'22 Paper - Interdomain slices
+This functional test reproduces the experiment in paper "... paper title ..." presented at OECC/PSC'22 conference
+[OECC/PSC'22](... demo link ...).
+
+## Functional test folder
+This functional test can be found in folder `./src/tests/oeccpsc22/`. A convenience alias `./oeccpsc22/` pointing to that folder has been defined.
+
+# TO BE WRITTEN
diff --git a/src/tests/oeccpsc22/__init__.py b/src/tests/oeccpsc22/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..70a33251242c51f49140e596b8208a19dd5245f7
--- /dev/null
+++ b/src/tests/oeccpsc22/__init__.py
@@ -0,0 +1,14 @@
+# Copyright 2021-2023 H2020 TeraFlow (https://www.teraflow-h2020.eu/)
+#
+# 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.
+
diff --git a/src/tests/oeccpsc22/deploy_in_kubernetes.sh b/src/tests/oeccpsc22/deploy_in_kubernetes.sh
new file mode 100755
index 0000000000000000000000000000000000000000..f40257f341c006e24e24ed961f5d1a0367ef5a11
--- /dev/null
+++ b/src/tests/oeccpsc22/deploy_in_kubernetes.sh
@@ -0,0 +1,33 @@
+#!/bin/bash
+# Copyright 2021-2023 H2020 TeraFlow (https://www.teraflow-h2020.eu/)
+#
+# 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.
+
+
+# OECC/PSC 22 deployment settings
+
+export REGISTRY_IMAGE=""
+export COMPONENTS="context device service compute monitoring interdomain webui" # slice
+export IMAGE_TAG="oeccpsc22"
+export K8S_HOSTNAME="kubernetes-master"
+#export GRAFANA_PASSWORD="admin123+"
+
+# Deploy TeraFlow instance 1
+export K8S_NAMESPACE="oeccpsc22-1"
+export EXTRA_MANIFESTS="./oeccpsc22/expose_services_teraflow_1.yaml"
+./deploy_in_kubernetes.sh
+
+# Deploy TeraFlow instance 2
+export K8S_NAMESPACE="oeccpsc22-2"
+export EXTRA_MANIFESTS="./oeccpsc22/expose_services_teraflow_2.yaml"
+./deploy_in_kubernetes.sh
diff --git a/src/tests/oeccpsc22/expose_services_teraflow_1.yaml b/src/tests/oeccpsc22/expose_services_teraflow_1.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..6d2f0bed72633608a2a0cd80235e7ce4b3d723c5
--- /dev/null
+++ b/src/tests/oeccpsc22/expose_services_teraflow_1.yaml
@@ -0,0 +1,101 @@
+# Copyright 2021-2023 H2020 TeraFlow (https://www.teraflow-h2020.eu/)
+#
+# 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.
+
+apiVersion: v1
+kind: Service
+metadata:
+  name: remote-teraflow
+spec:
+  type: ExternalName
+  externalName: interdomainservice.oeccpsc22-2.svc.cluster.local
+  ports:
+  - name: grpc
+    protocol: TCP
+    port: 10010
+---
+apiVersion: v1
+kind: Service
+metadata:
+  name: contextservice-public
+  labels:
+    app: contextservice
+spec:
+  type: NodePort
+  selector:
+    app: contextservice
+  ports:
+  - name: grpc
+    protocol: TCP
+    port: 1010
+    targetPort: 1010
+    nodePort: 30111
+  - name: redis
+    protocol: TCP
+    port: 6379
+    targetPort: 6379
+    nodePort: 30631
+---
+apiVersion: v1
+kind: Service
+metadata:
+  name: deviceservice-public
+  labels:
+    app: deviceservice
+spec:
+  type: NodePort
+  selector:
+    app: deviceservice
+  ports:
+  - name: grpc
+    protocol: TCP
+    port: 2020
+    targetPort: 2020
+    nodePort: 30221
+---
+apiVersion: v1
+kind: Service
+metadata:
+  name: computeservice-public
+spec:
+  type: NodePort
+  selector:
+    app: computeservice
+  ports:
+  - name: http
+    protocol: TCP
+    port: 8080
+    targetPort: 8080
+    nodePort: 30881
+---
+apiVersion: v1
+kind: Service
+metadata:
+  name: webuiservice-public
+  labels:
+    app: webuiservice
+spec:
+  type: NodePort
+  selector:
+    app: webuiservice
+  ports:
+  - name: http
+    protocol: TCP
+    port: 8004
+    targetPort: 8004
+    nodePort: 30801
+  - name: grafana
+    protocol: TCP
+    port: 3000
+    targetPort: 3000
+    nodePort: 30301
diff --git a/src/tests/oeccpsc22/expose_services_teraflow_2.yaml b/src/tests/oeccpsc22/expose_services_teraflow_2.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..32974848ea91560d9fbfe511cae99010a7471a5d
--- /dev/null
+++ b/src/tests/oeccpsc22/expose_services_teraflow_2.yaml
@@ -0,0 +1,101 @@
+# Copyright 2021-2023 H2020 TeraFlow (https://www.teraflow-h2020.eu/)
+#
+# 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.
+
+apiVersion: v1
+kind: Service
+metadata:
+  name: remote-teraflow
+spec:
+  type: ExternalName
+  externalName: interdomainservice.oeccpsc22-2.svc.cluster.local
+  ports:
+  - name: grpc
+    protocol: TCP
+    port: 10010
+---
+apiVersion: v1
+kind: Service
+metadata:
+  name: contextservice-public
+  labels:
+    app: contextservice
+spec:
+  type: NodePort
+  selector:
+    app: contextservice
+  ports:
+  - name: grpc
+    protocol: TCP
+    port: 1010
+    targetPort: 1010
+    nodePort: 30112
+  - name: redis
+    protocol: TCP
+    port: 6379
+    targetPort: 6379
+    nodePort: 30632
+---
+apiVersion: v1
+kind: Service
+metadata:
+  name: deviceservice-public
+  labels:
+    app: deviceservice
+spec:
+  type: NodePort
+  selector:
+    app: deviceservice
+  ports:
+  - name: grpc
+    protocol: TCP
+    port: 2020
+    targetPort: 2020
+    nodePort: 30222
+---
+apiVersion: v1
+kind: Service
+metadata:
+  name: computeservice-public
+spec:
+  type: NodePort
+  selector:
+    app: computeservice
+  ports:
+  - name: http
+    protocol: TCP
+    port: 8080
+    targetPort: 8080
+    nodePort: 30882
+---
+apiVersion: v1
+kind: Service
+metadata:
+  name: webuiservice-public
+  labels:
+    app: webuiservice
+spec:
+  type: NodePort
+  selector:
+    app: webuiservice
+  ports:
+  - name: http
+    protocol: TCP
+    port: 8004
+    targetPort: 8004
+    nodePort: 30802
+  - name: grafana
+    protocol: TCP
+    port: 3000
+    targetPort: 3000
+    nodePort: 30302
diff --git a/src/tests/oeccpsc22/run_test_01_bootstrap.sh b/src/tests/oeccpsc22/run_test_01_bootstrap.sh
new file mode 100755
index 0000000000000000000000000000000000000000..7b816984a17f7f5a30ce8eaafc6d831c615ce3e0
--- /dev/null
+++ b/src/tests/oeccpsc22/run_test_01_bootstrap.sh
@@ -0,0 +1,56 @@
+#!/bin/bash
+# Copyright 2021-2023 H2020 TeraFlow (https://www.teraflow-h2020.eu/)
+#
+# 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.
+
+
+PROJECTDIR=`pwd`
+
+cd $PROJECTDIR/src
+RCFILE=$PROJECTDIR/coverage/.coveragerc
+COVERAGEFILE=$PROJECTDIR/coverage/.coverage
+
+# Configure the correct folder on the .coveragerc file
+cat $PROJECTDIR/coverage/.coveragerc.template | sed s+~/teraflow/controller+$PROJECTDIR+g > $RCFILE
+
+# Destroy old coverage file
+rm -f $COVERAGEFILE
+
+# Set the name of the Kubernetes namespace and hostname to use.
+K8S_NAMESPACE_D1="oeccpsc22-1"
+K8S_NAMESPACE_D2="oeccpsc22-2"
+# K8S_HOSTNAME="kubernetes-master"
+# dynamically gets the name of the K8s master node
+K8S_HOSTNAME=`kubectl get nodes --selector=node-role.kubernetes.io/master | tr -s " " | cut -f1 -d" " | sed -n '2 p'`
+
+# Flush Context database
+kubectl --namespace $K8S_NAMESPACE_D1 exec -it deployment/contextservice --container redis -- redis-cli FLUSHALL
+kubectl --namespace $K8S_NAMESPACE_D2 exec -it deployment/contextservice --container redis -- redis-cli FLUSHALL
+
+export D1_CONTEXTSERVICE_SERVICE_HOST=$(kubectl get node $K8S_HOSTNAME -o 'jsonpath={.status.addresses[?(@.type=="InternalIP")].address}')
+export D1_CONTEXTSERVICE_SERVICE_PORT_GRPC=$(kubectl get service contextservice-public --namespace $K8S_NAMESPACE_D1 -o 'jsonpath={.spec.ports[?(@.port==1010)].nodePort}')
+export D1_DEVICESERVICE_SERVICE_HOST=$(kubectl get node $K8S_HOSTNAME -o 'jsonpath={.status.addresses[?(@.type=="InternalIP")].address}')
+export D1_DEVICESERVICE_SERVICE_PORT_GRPC=$(kubectl get service deviceservice-public --namespace $K8S_NAMESPACE_D1 -o 'jsonpath={.spec.ports[?(@.port==2020)].nodePort}')
+
+export D2_CONTEXTSERVICE_SERVICE_HOST=$(kubectl get node $K8S_HOSTNAME -o 'jsonpath={.status.addresses[?(@.type=="InternalIP")].address}')
+export D2_CONTEXTSERVICE_SERVICE_PORT_GRPC=$(kubectl get service contextservice-public --namespace $K8S_NAMESPACE_D2 -o 'jsonpath={.spec.ports[?(@.port==1010)].nodePort}')
+export D2_DEVICESERVICE_SERVICE_HOST=$(kubectl get node $K8S_HOSTNAME -o 'jsonpath={.status.addresses[?(@.type=="InternalIP")].address}')
+export D2_DEVICESERVICE_SERVICE_PORT_GRPC=$(kubectl get service deviceservice-public --namespace $K8S_NAMESPACE_D2 -o 'jsonpath={.spec.ports[?(@.port==2020)].nodePort}')
+
+# Useful flags for pytest:
+#-o log_cli=true -o log_file=device.log -o log_file_level=DEBUG
+
+# Run functional test and analyze coverage of code at same time
+
+coverage run --rcfile=$RCFILE --append -m pytest --log-level=INFO --verbose \
+    tests/oeccpsc22/tests/test_functional_bootstrap.py
diff --git a/src/tests/oeccpsc22/run_test_02_create_service.sh b/src/tests/oeccpsc22/run_test_02_create_service.sh
new file mode 100755
index 0000000000000000000000000000000000000000..c01f64741fe54e4e5a889080f4e680660435c093
--- /dev/null
+++ b/src/tests/oeccpsc22/run_test_02_create_service.sh
@@ -0,0 +1,41 @@
+#!/bin/bash
+# Copyright 2021-2023 H2020 TeraFlow (https://www.teraflow-h2020.eu/)
+#
+# 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.
+
+
+PROJECTDIR=`pwd`
+
+cd $PROJECTDIR/src
+RCFILE=$PROJECTDIR/coverage/.coveragerc
+COVERAGEFILE=$PROJECTDIR/coverage/.coverage
+
+# Set the name of the Kubernetes namespace and hostname to use.
+K8S_NAMESPACE="oeccpsc22"
+# dynamically gets the name of the K8s master node
+K8S_HOSTNAME=`kubectl get nodes --selector=node-role.kubernetes.io/master | tr -s " " | cut -f1 -d" " | sed -n '2 p'`
+
+export CONTEXTSERVICE_SERVICE_HOST=$(kubectl get node $K8S_HOSTNAME -o 'jsonpath={.status.addresses[?(@.type=="InternalIP")].address}')
+export CONTEXTSERVICE_SERVICE_PORT_GRPC=$(kubectl get service contextservice-public --namespace $K8S_NAMESPACE -o 'jsonpath={.spec.ports[?(@.port==1010)].nodePort}')
+export DEVICESERVICE_SERVICE_HOST=$(kubectl get node $K8S_HOSTNAME -o 'jsonpath={.status.addresses[?(@.type=="InternalIP")].address}')
+export DEVICESERVICE_SERVICE_PORT_GRPC=$(kubectl get service deviceservice-public --namespace $K8S_NAMESPACE -o 'jsonpath={.spec.ports[?(@.port==2020)].nodePort}')
+export COMPUTESERVICE_SERVICE_HOST=$(kubectl get node $K8S_HOSTNAME -o 'jsonpath={.status.addresses[?(@.type=="InternalIP")].address}')
+export COMPUTESERVICE_SERVICE_PORT_HTTP=$(kubectl get service computeservice-public --namespace $K8S_NAMESPACE -o 'jsonpath={.spec.ports[?(@.port==8080)].nodePort}')
+
+# Useful flags for pytest:
+#-o log_cli=true -o log_file=device.log -o log_file_level=DEBUG
+
+# Run functional test and analyze coverage of code at same time
+
+coverage run --rcfile=$RCFILE --append -m pytest --log-level=INFO --verbose \
+    tests/oeccpsc22/tests/test_functional_create_service.py
diff --git a/src/tests/oeccpsc22/run_test_03_delete_service.sh b/src/tests/oeccpsc22/run_test_03_delete_service.sh
new file mode 100755
index 0000000000000000000000000000000000000000..1782a143bdf6b3257ae8dbf75d94803dcde2275c
--- /dev/null
+++ b/src/tests/oeccpsc22/run_test_03_delete_service.sh
@@ -0,0 +1,41 @@
+#!/bin/bash
+# Copyright 2021-2023 H2020 TeraFlow (https://www.teraflow-h2020.eu/)
+#
+# 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.
+
+
+PROJECTDIR=`pwd`
+
+cd $PROJECTDIR/src
+RCFILE=$PROJECTDIR/coverage/.coveragerc
+COVERAGEFILE=$PROJECTDIR/coverage/.coverage
+
+# Set the name of the Kubernetes namespace and hostname to use.
+K8S_NAMESPACE="oeccpsc22"
+# dynamically gets the name of the K8s master node
+K8S_HOSTNAME=`kubectl get nodes --selector=node-role.kubernetes.io/master | tr -s " " | cut -f1 -d" " | sed -n '2 p'`
+
+export CONTEXTSERVICE_SERVICE_HOST=$(kubectl get node $K8S_HOSTNAME -o 'jsonpath={.status.addresses[?(@.type=="InternalIP")].address}')
+export CONTEXTSERVICE_SERVICE_PORT_GRPC=$(kubectl get service contextservice-public --namespace $K8S_NAMESPACE -o 'jsonpath={.spec.ports[?(@.port==1010)].nodePort}')
+export DEVICESERVICE_SERVICE_HOST=$(kubectl get node $K8S_HOSTNAME -o 'jsonpath={.status.addresses[?(@.type=="InternalIP")].address}')
+export DEVICESERVICE_SERVICE_PORT_GRPC=$(kubectl get service deviceservice-public --namespace $K8S_NAMESPACE -o 'jsonpath={.spec.ports[?(@.port==2020)].nodePort}')
+export COMPUTESERVICE_SERVICE_HOST=$(kubectl get node $K8S_HOSTNAME -o 'jsonpath={.status.addresses[?(@.type=="InternalIP")].address}')
+export COMPUTESERVICE_SERVICE_PORT_HTTP=$(kubectl get service computeservice-public --namespace $K8S_NAMESPACE -o 'jsonpath={.spec.ports[?(@.port==8080)].nodePort}')
+
+# Useful flags for pytest:
+#-o log_cli=true -o log_file=device.log -o log_file_level=DEBUG
+
+# Run functional test and analyze coverage of code at same time
+
+coverage run --rcfile=$RCFILE --append -m pytest --log-level=INFO --verbose \
+    tests/oeccpsc22/tests/test_functional_delete_service.py
diff --git a/src/tests/oeccpsc22/run_test_04_cleanup.sh b/src/tests/oeccpsc22/run_test_04_cleanup.sh
new file mode 100755
index 0000000000000000000000000000000000000000..14b4024be28dba06add413fb775b0a6ed090af74
--- /dev/null
+++ b/src/tests/oeccpsc22/run_test_04_cleanup.sh
@@ -0,0 +1,41 @@
+#!/bin/bash
+# Copyright 2021-2023 H2020 TeraFlow (https://www.teraflow-h2020.eu/)
+#
+# 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.
+
+
+PROJECTDIR=`pwd`
+
+cd $PROJECTDIR/src
+RCFILE=$PROJECTDIR/coverage/.coveragerc
+COVERAGEFILE=$PROJECTDIR/coverage/.coverage
+
+# Set the name of the Kubernetes namespace and hostname to use.
+K8S_NAMESPACE="oeccpsc22"
+# dynamically gets the name of the K8s master node
+K8S_HOSTNAME=`kubectl get nodes --selector=node-role.kubernetes.io/master | tr -s " " | cut -f1 -d" " | sed -n '2 p'`
+
+export CONTEXTSERVICE_SERVICE_HOST=$(kubectl get node $K8S_HOSTNAME -o 'jsonpath={.status.addresses[?(@.type=="InternalIP")].address}')
+export CONTEXTSERVICE_SERVICE_PORT_GRPC=$(kubectl get service contextservice-public --namespace $K8S_NAMESPACE -o 'jsonpath={.spec.ports[?(@.port==1010)].nodePort}')
+export DEVICESERVICE_SERVICE_HOST=$(kubectl get node $K8S_HOSTNAME -o 'jsonpath={.status.addresses[?(@.type=="InternalIP")].address}')
+export DEVICESERVICE_SERVICE_PORT_GRPC=$(kubectl get service deviceservice-public --namespace $K8S_NAMESPACE -o 'jsonpath={.spec.ports[?(@.port==2020)].nodePort}')
+export COMPUTESERVICE_SERVICE_HOST=$(kubectl get node $K8S_HOSTNAME -o 'jsonpath={.status.addresses[?(@.type=="InternalIP")].address}')
+export COMPUTESERVICE_SERVICE_PORT_HTTP=$(kubectl get service computeservice-public --namespace $K8S_NAMESPACE -o 'jsonpath={.spec.ports[?(@.port==8080)].nodePort}')
+
+# Useful flags for pytest:
+#-o log_cli=true -o log_file=device.log -o log_file_level=DEBUG
+
+# Run functional test and analyze coverage of code at same time
+
+coverage run --rcfile=$RCFILE --append -m pytest --log-level=INFO --verbose \
+    tests/oeccpsc22/tests/test_functional_cleanup.py
diff --git a/src/tests/oeccpsc22/show_deploy.sh b/src/tests/oeccpsc22/show_deploy.sh
new file mode 100755
index 0000000000000000000000000000000000000000..90d6914890cfd37db37ed3b3ea8266372c067c20
--- /dev/null
+++ b/src/tests/oeccpsc22/show_deploy.sh
@@ -0,0 +1,26 @@
+#!/bin/bash
+# Copyright 2021-2023 H2020 TeraFlow (https://www.teraflow-h2020.eu/)
+#
+# 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.
+
+# Deploy TeraFlow instance 1
+printf "TeraFlow Instance 1:\n--------------------\n"
+export K8S_NAMESPACE="oeccpsc22-1"
+kubectl --namespace $K8S_NAMESPACE get all
+
+printf "\n\n"
+
+# Deploy TeraFlow instance 2
+printf "TeraFlow Instance 2:\n--------------------\n"
+export K8S_NAMESPACE="oeccpsc22-2"
+kubectl --namespace $K8S_NAMESPACE get all
diff --git a/src/tests/oeccpsc22/tests/.gitignore b/src/tests/oeccpsc22/tests/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..76cb708d1b532c9b69166e55f36bcb912fd5e370
--- /dev/null
+++ b/src/tests/oeccpsc22/tests/.gitignore
@@ -0,0 +1,2 @@
+# Add here your files containing confidential testbed details such as IP addresses, ports, usernames, passwords, etc.
+Credentials.py
diff --git a/src/tests/oeccpsc22/tests/Objects_Domain_1.py b/src/tests/oeccpsc22/tests/Objects_Domain_1.py
new file mode 100644
index 0000000000000000000000000000000000000000..af353c6e9ca3c863387e5d72dbef005fcb516a5a
--- /dev/null
+++ b/src/tests/oeccpsc22/tests/Objects_Domain_1.py
@@ -0,0 +1,121 @@
+# Copyright 2021-2023 H2020 TeraFlow (https://www.teraflow-h2020.eu/)
+#
+# 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.
+
+from common.Constants import DEFAULT_CONTEXT_UUID, DEFAULT_TOPOLOGY_UUID
+from common.tools.object_factory.Context import json_context, json_context_id
+from common.tools.object_factory.Device import (
+    json_device_emulated_connect_rules, json_device_emulated_packet_router_disabled, json_device_id)
+from common.tools.object_factory.Link import json_link, json_link_id
+from common.tools.object_factory.Topology import json_topology, json_topology_id
+from .Tools import get_link_uuid, json_endpoint_ids
+
+# ----- Context --------------------------------------------------------------------------------------------------------
+D1_CONTEXT_ID = json_context_id(DEFAULT_CONTEXT_UUID)
+D1_CONTEXT    = json_context(DEFAULT_CONTEXT_UUID)
+
+# ----- Topology -------------------------------------------------------------------------------------------------------
+D1_TOPOLOGY_ID = json_topology_id(DEFAULT_TOPOLOGY_UUID, context_id=D1_CONTEXT_ID)
+D1_TOPOLOGY    = json_topology(DEFAULT_TOPOLOGY_UUID, context_id=D1_CONTEXT_ID)
+
+# ----- Devices --------------------------------------------------------------------------------------------------------
+# Assume all devices have the same architecture of endpoints
+DEVICE_ENDPOINT_DEFS = [
+    # Trunk ports
+    ('1/1', '25Gbps', []), ('1/2', '25Gbps', []), ('1/3', '25Gbps', []), ('1/4', '25Gbps', []),
+    # Inter-domain ports
+    ('2/1', '100Gbps', []), ('2/2', '100Gbps', []),
+    # Access ports
+    ('3/1', '10Gbps', []), ('3/2', '10Gbps', []), ('3/3', '10Gbps', []), ('3/4', '10Gbps', []),
+    ('3/5', '10Gbps', []), ('3/6', '10Gbps', []), ('3/7', '10Gbps', []), ('3/8', '10Gbps', []),
+]
+
+DEVICE_D1R1_UUID          = 'D1-R1'
+DEVICE_D1R1_ID            = json_device_id(DEVICE_D1R1_UUID)
+DEVICE_D1R1               = json_device_emulated_packet_router_disabled(DEVICE_D1R1_UUID)
+DEVICE_D1R1_CONNECT_RULES = json_device_emulated_connect_rules(DEVICE_ENDPOINT_DEFS)
+
+DEVICE_D1R2_UUID          = 'D1-R2'
+DEVICE_D1R2_ID            = json_device_id(DEVICE_D1R2_UUID)
+DEVICE_D1R2               = json_device_emulated_packet_router_disabled(DEVICE_D1R2_UUID)
+DEVICE_D1R2_CONNECT_RULES = json_device_emulated_connect_rules(DEVICE_ENDPOINT_DEFS)
+
+DEVICE_D1R3_UUID          = 'D1-R3'
+DEVICE_D1R3_ID            = json_device_id(DEVICE_D1R3_UUID)
+DEVICE_D1R3               = json_device_emulated_packet_router_disabled(DEVICE_D1R3_UUID)
+DEVICE_D1R3_CONNECT_RULES = json_device_emulated_connect_rules(DEVICE_ENDPOINT_DEFS)
+
+DEVICE_D1R4_UUID          = 'D1-R4'
+DEVICE_D1R4_ID            = json_device_id(DEVICE_D1R4_UUID)
+DEVICE_D1R4               = json_device_emulated_packet_router_disabled(DEVICE_D1R4_UUID)
+DEVICE_D1R4_CONNECT_RULES = json_device_emulated_connect_rules(DEVICE_ENDPOINT_DEFS)
+
+# Virtual devices on remote domains
+DEVICE_D2R1_UUID          = 'D2-R1'
+DEVICE_D2R1_ID            = json_device_id(DEVICE_D2R1_UUID)
+DEVICE_D2R1               = json_device_emulated_packet_router_disabled(DEVICE_D2R1_UUID)
+DEVICE_D2R1_CONNECT_RULES = json_device_emulated_connect_rules(DEVICE_ENDPOINT_DEFS)
+
+ENDPOINT_IDS = {}
+ENDPOINT_IDS.update(json_endpoint_ids(DEVICE_D1R1_ID, DEVICE_ENDPOINT_DEFS))
+ENDPOINT_IDS.update(json_endpoint_ids(DEVICE_D1R2_ID, DEVICE_ENDPOINT_DEFS))
+ENDPOINT_IDS.update(json_endpoint_ids(DEVICE_D1R3_ID, DEVICE_ENDPOINT_DEFS))
+ENDPOINT_IDS.update(json_endpoint_ids(DEVICE_D1R4_ID, DEVICE_ENDPOINT_DEFS))
+ENDPOINT_IDS.update(json_endpoint_ids(DEVICE_D2R1_ID, DEVICE_ENDPOINT_DEFS))
+
+
+# ----- Links ----------------------------------------------------------------------------------------------------------
+# Intra-domain links
+LINK_D1R1_D1R2_UUID = get_link_uuid(ENDPOINT_IDS[DEVICE_D1R1_UUID]['1/2'], ENDPOINT_IDS[DEVICE_D1R2_UUID]['1/1'])
+LINK_D1R1_D1R2_ID   = json_link_id(LINK_D1R1_D1R2_UUID)
+LINK_D1R1_D1R2      = json_link(LINK_D1R1_D1R2_UUID, [
+    ENDPOINT_IDS[DEVICE_D1R1_UUID]['1/2'], ENDPOINT_IDS[DEVICE_D1R2_UUID]['1/1']])
+
+LINK_D1R2_D1R3_UUID = get_link_uuid(ENDPOINT_IDS[DEVICE_D1R2_UUID]['1/2'], ENDPOINT_IDS[DEVICE_D1R3_UUID]['1/1'])
+LINK_D1R2_D1R3_ID   = json_link_id(LINK_D1R2_D1R3_UUID)
+LINK_D1R2_D1R3      = json_link(LINK_D1R2_D1R3_UUID, [
+    ENDPOINT_IDS[DEVICE_D1R2_UUID]['1/2'], ENDPOINT_IDS[DEVICE_D1R3_UUID]['1/1']])
+
+LINK_D1R3_D1R4_UUID = get_link_uuid(ENDPOINT_IDS[DEVICE_D1R3_UUID]['1/2'], ENDPOINT_IDS[DEVICE_D1R4_UUID]['1/1'])
+LINK_D1R3_D1R4_ID   = json_link_id(LINK_D1R3_D1R4_UUID)
+LINK_D1R3_D1R4      = json_link(LINK_D1R3_D1R4_UUID, [
+    ENDPOINT_IDS[DEVICE_D1R3_UUID]['1/2'], ENDPOINT_IDS[DEVICE_D1R4_UUID]['1/1']])
+
+LINK_D1R4_D1R1_UUID = get_link_uuid(ENDPOINT_IDS[DEVICE_D1R4_UUID]['1/2'], ENDPOINT_IDS[DEVICE_D1R1_UUID]['1/1'])
+LINK_D1R4_D1R1_ID   = json_link_id(LINK_D1R4_D1R1_UUID)
+LINK_D1R4_D1R1      = json_link(LINK_D1R4_D1R1_UUID, [
+    ENDPOINT_IDS[DEVICE_D1R4_UUID]['1/2'], ENDPOINT_IDS[DEVICE_D1R1_UUID]['1/1']])
+
+# Inter-domain links
+LINK_D1R4_D2R1_UUID = get_link_uuid(ENDPOINT_IDS[DEVICE_D1R4_UUID]['2/1'], ENDPOINT_IDS[DEVICE_D2R1_UUID]['2/1'])
+LINK_D1R4_D2R1_ID   = json_link_id(LINK_D1R4_D2R1_UUID)
+LINK_D1R4_D2R1      = json_link(LINK_D1R4_D2R1_UUID, [
+    ENDPOINT_IDS[DEVICE_D1R4_UUID]['2/1'], ENDPOINT_IDS[DEVICE_D2R1_UUID]['2/1']])
+
+# ----- Object Collections ---------------------------------------------------------------------------------------------
+
+D1_CONTEXTS = [D1_CONTEXT]
+D1_TOPOLOGIES = [D1_TOPOLOGY]
+
+D1_DEVICES = [
+    (DEVICE_D1R1, DEVICE_D1R1_CONNECT_RULES),
+    (DEVICE_D1R2, DEVICE_D1R2_CONNECT_RULES),
+    (DEVICE_D1R3, DEVICE_D1R3_CONNECT_RULES),
+    (DEVICE_D1R4, DEVICE_D1R4_CONNECT_RULES),
+    (DEVICE_D2R1, DEVICE_D2R1_CONNECT_RULES),
+]
+
+D1_LINKS = [
+    LINK_D1R1_D1R2, LINK_D1R2_D1R3, LINK_D1R3_D1R4, LINK_D1R4_D1R1,
+    LINK_D1R4_D2R1,
+]
diff --git a/src/tests/oeccpsc22/tests/Objects_Domain_2.py b/src/tests/oeccpsc22/tests/Objects_Domain_2.py
new file mode 100644
index 0000000000000000000000000000000000000000..f7798925096f9fa6b49c81c7a6e628afac23bed8
--- /dev/null
+++ b/src/tests/oeccpsc22/tests/Objects_Domain_2.py
@@ -0,0 +1,121 @@
+# Copyright 2021-2023 H2020 TeraFlow (https://www.teraflow-h2020.eu/)
+#
+# 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.
+
+from common.Constants import DEFAULT_CONTEXT_UUID, DEFAULT_TOPOLOGY_UUID
+from common.tools.object_factory.Context import json_context, json_context_id
+from common.tools.object_factory.Device import (
+    json_device_emulated_connect_rules, json_device_emulated_packet_router_disabled, json_device_id)
+from common.tools.object_factory.Link import json_link, json_link_id
+from common.tools.object_factory.Topology import json_topology, json_topology_id
+from .Tools import get_link_uuid, json_endpoint_ids
+
+# ----- Context --------------------------------------------------------------------------------------------------------
+D2_CONTEXT_ID = json_context_id(DEFAULT_CONTEXT_UUID)
+D2_CONTEXT    = json_context(DEFAULT_CONTEXT_UUID)
+
+# ----- Topology -------------------------------------------------------------------------------------------------------
+D2_TOPOLOGY_ID = json_topology_id(DEFAULT_TOPOLOGY_UUID, context_id=D2_CONTEXT_ID)
+D2_TOPOLOGY    = json_topology(DEFAULT_TOPOLOGY_UUID, context_id=D2_CONTEXT_ID)
+
+# ----- Devices --------------------------------------------------------------------------------------------------------
+# Assume all devices have the same architecture of endpoints
+DEVICE_ENDPOINT_DEFS = [
+    # Trunk ports
+    ('1/1', '25Gbps', []), ('1/2', '25Gbps', []), ('1/3', '25Gbps', []), ('1/4', '25Gbps', []),
+    # Inter-domain ports
+    ('2/1', '100Gbps', []), ('2/2', '100Gbps', []),
+    # Access ports
+    ('3/1', '10Gbps', []), ('3/2', '10Gbps', []), ('3/3', '10Gbps', []), ('3/4', '10Gbps', []),
+    ('3/5', '10Gbps', []), ('3/6', '10Gbps', []), ('3/7', '10Gbps', []), ('3/8', '10Gbps', []),
+]
+
+DEVICE_D2R1_UUID          = 'D2-R1'
+DEVICE_D2R1_ID            = json_device_id(DEVICE_D2R1_UUID)
+DEVICE_D2R1               = json_device_emulated_packet_router_disabled(DEVICE_D2R1_UUID)
+DEVICE_D2R1_CONNECT_RULES = json_device_emulated_connect_rules(DEVICE_ENDPOINT_DEFS)
+
+DEVICE_D2R2_UUID          = 'D2-R2'
+DEVICE_D2R2_ID            = json_device_id(DEVICE_D2R2_UUID)
+DEVICE_D2R2               = json_device_emulated_packet_router_disabled(DEVICE_D2R2_UUID)
+DEVICE_D2R2_CONNECT_RULES = json_device_emulated_connect_rules(DEVICE_ENDPOINT_DEFS)
+
+DEVICE_D2R3_UUID          = 'D2-R3'
+DEVICE_D2R3_ID            = json_device_id(DEVICE_D2R3_UUID)
+DEVICE_D2R3               = json_device_emulated_packet_router_disabled(DEVICE_D2R3_UUID)
+DEVICE_D2R3_CONNECT_RULES = json_device_emulated_connect_rules(DEVICE_ENDPOINT_DEFS)
+
+DEVICE_D2R4_UUID          = 'D2-R4'
+DEVICE_D2R4_ID            = json_device_id(DEVICE_D2R4_UUID)
+DEVICE_D2R4               = json_device_emulated_packet_router_disabled(DEVICE_D2R4_UUID)
+DEVICE_D2R4_CONNECT_RULES = json_device_emulated_connect_rules(DEVICE_ENDPOINT_DEFS)
+
+# Virtual devices on remote domains
+DEVICE_D1R1_UUID          = 'D1-R1'
+DEVICE_D1R1_ID            = json_device_id(DEVICE_D1R1_UUID)
+DEVICE_D1R1               = json_device_emulated_packet_router_disabled(DEVICE_D1R1_UUID)
+DEVICE_D1R1_CONNECT_RULES = json_device_emulated_connect_rules(DEVICE_ENDPOINT_DEFS)
+
+ENDPOINT_IDS = {}
+ENDPOINT_IDS.update(json_endpoint_ids(DEVICE_D2R1_ID, DEVICE_ENDPOINT_DEFS))
+ENDPOINT_IDS.update(json_endpoint_ids(DEVICE_D2R2_ID, DEVICE_ENDPOINT_DEFS))
+ENDPOINT_IDS.update(json_endpoint_ids(DEVICE_D2R3_ID, DEVICE_ENDPOINT_DEFS))
+ENDPOINT_IDS.update(json_endpoint_ids(DEVICE_D2R4_ID, DEVICE_ENDPOINT_DEFS))
+ENDPOINT_IDS.update(json_endpoint_ids(DEVICE_D1R1_ID, DEVICE_ENDPOINT_DEFS))
+
+
+# ----- Links ----------------------------------------------------------------------------------------------------------
+# Intra-domain links
+LINK_D2R1_D2R2_UUID = get_link_uuid(ENDPOINT_IDS[DEVICE_D2R1_UUID]['1/2'], ENDPOINT_IDS[DEVICE_D2R2_UUID]['1/1'])
+LINK_D2R1_D2R2_ID   = json_link_id(LINK_D2R1_D2R2_UUID)
+LINK_D2R1_D2R2      = json_link(LINK_D2R1_D2R2_UUID, [
+    ENDPOINT_IDS[DEVICE_D2R1_UUID]['1/2'], ENDPOINT_IDS[DEVICE_D2R2_UUID]['1/1']])
+
+LINK_D2R2_D2R3_UUID = get_link_uuid(ENDPOINT_IDS[DEVICE_D2R2_UUID]['1/2'], ENDPOINT_IDS[DEVICE_D2R3_UUID]['1/1'])
+LINK_D2R2_D2R3_ID   = json_link_id(LINK_D2R2_D2R3_UUID)
+LINK_D2R2_D2R3      = json_link(LINK_D2R2_D2R3_UUID, [
+    ENDPOINT_IDS[DEVICE_D2R2_UUID]['1/2'], ENDPOINT_IDS[DEVICE_D2R3_UUID]['1/1']])
+
+LINK_D2R3_D2R4_UUID = get_link_uuid(ENDPOINT_IDS[DEVICE_D2R3_UUID]['1/2'], ENDPOINT_IDS[DEVICE_D2R4_UUID]['1/1'])
+LINK_D2R3_D2R4_ID   = json_link_id(LINK_D2R3_D2R4_UUID)
+LINK_D2R3_D2R4      = json_link(LINK_D2R3_D2R4_UUID, [
+    ENDPOINT_IDS[DEVICE_D2R3_UUID]['1/2'], ENDPOINT_IDS[DEVICE_D2R4_UUID]['1/1']])
+
+LINK_D2R4_D2R1_UUID = get_link_uuid(ENDPOINT_IDS[DEVICE_D2R4_UUID]['1/2'], ENDPOINT_IDS[DEVICE_D2R1_UUID]['1/1'])
+LINK_D2R4_D2R1_ID   = json_link_id(LINK_D2R4_D2R1_UUID)
+LINK_D2R4_D2R1      = json_link(LINK_D2R4_D2R1_UUID, [
+    ENDPOINT_IDS[DEVICE_D2R4_UUID]['1/2'], ENDPOINT_IDS[DEVICE_D2R1_UUID]['1/1']])
+
+# Inter-domain links
+LINK_D2R4_D1R1_UUID = get_link_uuid(ENDPOINT_IDS[DEVICE_D2R4_UUID]['2/1'], ENDPOINT_IDS[DEVICE_D1R1_UUID]['2/1'])
+LINK_D2R4_D1R1_ID   = json_link_id(LINK_D2R4_D1R1_UUID)
+LINK_D2R4_D1R1      = json_link(LINK_D2R4_D1R1_UUID, [
+    ENDPOINT_IDS[DEVICE_D2R4_UUID]['2/1'], ENDPOINT_IDS[DEVICE_D1R1_UUID]['2/1']])
+
+# ----- Object Collections ---------------------------------------------------------------------------------------------
+
+D2_CONTEXTS = [D2_CONTEXT]
+D2_TOPOLOGIES = [D2_TOPOLOGY]
+
+D2_DEVICES = [
+    (DEVICE_D2R1, DEVICE_D2R1_CONNECT_RULES),
+    (DEVICE_D2R2, DEVICE_D2R2_CONNECT_RULES),
+    (DEVICE_D2R3, DEVICE_D2R3_CONNECT_RULES),
+    (DEVICE_D2R4, DEVICE_D2R4_CONNECT_RULES),
+    (DEVICE_D1R1, DEVICE_D1R1_CONNECT_RULES),
+]
+
+D2_LINKS = [
+    LINK_D2R1_D2R2, LINK_D2R2_D2R3, LINK_D2R3_D2R4, LINK_D2R4_D2R1,
+    LINK_D2R4_D1R1,
+]
diff --git a/src/tests/oeccpsc22/tests/Objects_Service.py b/src/tests/oeccpsc22/tests/Objects_Service.py
new file mode 100644
index 0000000000000000000000000000000000000000..b9ec2a691e03381ff54dc08053063cad87cdeb3b
--- /dev/null
+++ b/src/tests/oeccpsc22/tests/Objects_Service.py
@@ -0,0 +1,35 @@
+
+
+# ----- WIM Service Settings -------------------------------------------------------------------------------------------
+WIM_SEP_R1_ID          = compose_service_endpoint_id(ENDPOINT_ID_R1_13_1_2)
+WIM_SEP_R1_ROUTER_ID   = '10.10.10.1'
+WIM_SEP_R1_ROUTER_DIST = '65000:111'
+WIM_SEP_R1_SITE_ID     = '1'
+WIM_SEP_R1_BEARER      = compose_bearer(ENDPOINT_ID_R1_13_1_2, WIM_SEP_R1_ROUTER_ID, WIM_SEP_R1_ROUTER_DIST)
+WIM_SRV_R1_VLAN_ID     = 400
+
+WIM_SEP_R3_ID          = compose_service_endpoint_id(ENDPOINT_ID_R3_13_1_2)
+WIM_SEP_R3_ROUTER_ID   = '20.20.20.1'
+WIM_SEP_R3_ROUTER_DIST = '65000:222'
+WIM_SEP_R3_SITE_ID     = '2'
+WIM_SEP_R3_BEARER      = compose_bearer(ENDPOINT_ID_R3_13_1_2, WIM_SEP_R3_ROUTER_ID, WIM_SEP_R3_ROUTER_DIST)
+WIM_SRV_R3_VLAN_ID     = 500
+
+WIM_USERNAME = 'admin'
+WIM_PASSWORD = 'admin'
+
+WIM_MAPPING  = [
+    {'device-id': DEVICE_R1_UUID, 'service_endpoint_id': WIM_SEP_R1_ID,
+     'service_mapping_info': {'bearer': {'bearer-reference': WIM_SEP_R1_BEARER}, 'site-id': WIM_SEP_R1_SITE_ID}},
+    {'device-id': DEVICE_R3_UUID, 'service_endpoint_id': WIM_SEP_R3_ID,
+     'service_mapping_info': {'bearer': {'bearer-reference': WIM_SEP_R3_BEARER}, 'site-id': WIM_SEP_R3_SITE_ID}},
+]
+WIM_SERVICE_TYPE = 'ELINE'
+WIM_SERVICE_CONNECTION_POINTS = [
+    {'service_endpoint_id': WIM_SEP_R1_ID,
+        'service_endpoint_encapsulation_type': 'dot1q',
+        'service_endpoint_encapsulation_info': {'vlan': WIM_SRV_R1_VLAN_ID}},
+    {'service_endpoint_id': WIM_SEP_R3_ID,
+        'service_endpoint_encapsulation_type': 'dot1q',
+        'service_endpoint_encapsulation_info': {'vlan': WIM_SRV_R3_VLAN_ID}},
+]
diff --git a/src/tests/oeccpsc22/tests/Tools.py b/src/tests/oeccpsc22/tests/Tools.py
new file mode 100644
index 0000000000000000000000000000000000000000..a782b6bb3e541e4331f5f95164e69def5640f556
--- /dev/null
+++ b/src/tests/oeccpsc22/tests/Tools.py
@@ -0,0 +1,25 @@
+from typing import Dict, List, Tuple
+from common.tools.object_factory.EndPoint import json_endpoint_id
+
+def json_endpoint_ids(device_id : Dict, endpoint_descriptors : List[Tuple[str, str, List[int]]]):
+    return {
+        device_id['device_uuid']['uuid']: {
+            ep_uuid: json_endpoint_id(device_id, ep_uuid, topology_id=None)
+            for ep_uuid, _, _ in endpoint_descriptors
+        }
+    }
+
+def get_link_uuid(a_endpoint_id : Dict, z_endpoint_id : Dict) -> str:
+    return '{:s}/{:s}=={:s}/{:s}'.format(
+        a_endpoint_id['device_id']['device_uuid']['uuid'], a_endpoint_id['endpoint_uuid']['uuid'],
+        a_endpoint_id['device_id']['device_uuid']['uuid'], z_endpoint_id['endpoint_uuid']['uuid'])
+
+def compose_service_endpoint_id(endpoint_id):
+    device_uuid = endpoint_id['device_id']['device_uuid']['uuid']
+    endpoint_uuid = endpoint_id['endpoint_uuid']['uuid']
+    return ':'.join([device_uuid, endpoint_uuid])
+
+def compose_bearer(endpoint_id):
+    device_uuid = endpoint_id['device_id']['device_uuid']['uuid']
+    endpoint_uuid = endpoint_id['endpoint_uuid']['uuid']
+    return ':'.join([device_uuid, endpoint_uuid])
diff --git a/src/tests/oeccpsc22/tests/__init__.py b/src/tests/oeccpsc22/tests/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..70a33251242c51f49140e596b8208a19dd5245f7
--- /dev/null
+++ b/src/tests/oeccpsc22/tests/__init__.py
@@ -0,0 +1,14 @@
+# Copyright 2021-2023 H2020 TeraFlow (https://www.teraflow-h2020.eu/)
+#
+# 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.
+
diff --git a/src/tests/oeccpsc22/tests/test_functional_bootstrap.py b/src/tests/oeccpsc22/tests/test_functional_bootstrap.py
new file mode 100644
index 0000000000000000000000000000000000000000..b09b558cd96bf794b26dadc99913100bb0ca9de1
--- /dev/null
+++ b/src/tests/oeccpsc22/tests/test_functional_bootstrap.py
@@ -0,0 +1,208 @@
+# Copyright 2021-2023 H2020 TeraFlow (https://www.teraflow-h2020.eu/)
+#
+# 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 copy, logging, pytest
+from common.Settings import get_setting
+from common.tests.EventTools import EVENT_CREATE, check_events
+from common.tools.object_factory.Device import json_device_id
+from common.tools.object_factory.Link import json_link_id
+from context.client.ContextClient import ContextClient
+from context.client.EventsCollector import EventsCollector
+from context.proto.context_pb2 import Context, ContextId, Device, Empty, Link, Topology
+from device.client.DeviceClient import DeviceClient
+from .Objects_Domain_1 import D1_CONTEXT_ID, D1_CONTEXTS, D1_DEVICES, D1_LINKS, D1_TOPOLOGIES
+from .Objects_Domain_2 import D2_CONTEXT_ID, D2_CONTEXTS, D2_DEVICES, D2_LINKS, D2_TOPOLOGIES
+
+LOGGER = logging.getLogger(__name__)
+LOGGER.setLevel(logging.DEBUG)
+
+@pytest.fixture(scope='session')
+def d1_context_client():
+    _client = ContextClient(
+        get_setting('D1_CONTEXTSERVICE_SERVICE_HOST'), get_setting('D1_CONTEXTSERVICE_SERVICE_PORT_GRPC'))
+    yield _client
+    _client.close()
+
+@pytest.fixture(scope='session')
+def d1_device_client():
+    _client = DeviceClient(
+        get_setting('D1_DEVICESERVICE_SERVICE_HOST'), get_setting('D1_DEVICESERVICE_SERVICE_PORT_GRPC'))
+    yield _client
+    _client.close()
+
+@pytest.fixture(scope='session')
+def d2_context_client():
+    _client = ContextClient(
+        get_setting('D2_CONTEXTSERVICE_SERVICE_HOST'), get_setting('D2_CONTEXTSERVICE_SERVICE_PORT_GRPC'))
+    yield _client
+    _client.close()
+
+@pytest.fixture(scope='session')
+def d2_device_client():
+    _client = DeviceClient(
+        get_setting('D2_DEVICESERVICE_SERVICE_HOST'), get_setting('D2_DEVICESERVICE_SERVICE_PORT_GRPC'))
+    yield _client
+    _client.close()
+
+
+def test_scenario_empty(
+    d1_context_client : ContextClient,  # pylint: disable=redefined-outer-name
+    d2_context_client : ContextClient): # pylint: disable=redefined-outer-name
+
+    def per_domain(context_client):
+        response = context_client.ListContexts(Empty())
+        assert len(response.contexts) == 0
+
+        response = context_client.ListDevices(Empty())
+        assert len(response.devices) == 0
+
+        response = context_client.ListLinks(Empty())
+        assert len(response.links) == 0
+
+    # ----- List entities - Ensure database is empty -------------------------------------------------------------------
+    per_domain(d1_context_client)
+    per_domain(d2_context_client)
+
+
+def test_prepare_scenario(
+    d1_context_client : ContextClient,  # pylint: disable=redefined-outer-name
+    d2_context_client : ContextClient): # pylint: disable=redefined-outer-name
+
+    def per_domain(contexts, topologies, context_client):
+        for context in contexts:
+            context_uuid = context['context_id']['context_uuid']['uuid']
+            LOGGER.info('Adding Context {:s}'.format(context_uuid))
+            response = context_client.SetContext(Context(**context))
+            assert response.context_uuid.uuid == context_uuid
+
+        for topology in topologies:
+            context_uuid = topology['topology_id']['context_id']['context_uuid']['uuid']
+            topology_uuid = topology['topology_id']['topology_uuid']['uuid']
+            LOGGER.info('Adding Topology {:s}/{:s}'.format(context_uuid, topology_uuid))
+            response = context_client.SetTopology(Topology(**topology))
+            assert response.context_id.context_uuid.uuid == context_uuid
+            assert response.topology_uuid.uuid == topology_uuid
+
+    # ----- Create Contexts and Topologies -----------------------------------------------------------------------------
+    per_domain(D1_CONTEXTS, D1_TOPOLOGIES, d1_context_client)
+    per_domain(D2_CONTEXTS, D2_TOPOLOGIES, d2_context_client)
+
+
+def test_scenario_ready(
+    d1_context_client : ContextClient,  # pylint: disable=redefined-outer-name
+    d2_context_client : ContextClient): # pylint: disable=redefined-outer-name
+
+    def per_domain(contexts, topologies, context_id, context_client):
+        response = context_client.ListContexts(Empty())
+        assert len(response.contexts) == len(contexts)
+
+        response = context_client.ListTopologies(ContextId(**context_id))
+        assert len(response.topologies) == len(topologies)
+
+        response = context_client.ListDevices(Empty())
+        assert len(response.devices) == 0
+
+        response = context_client.ListLinks(Empty())
+        assert len(response.links) == 0
+
+        response = context_client.ListServices(ContextId(**context_id))
+        assert len(response.services) == 0
+
+    # ----- List entities - Ensure scenario is ready -------------------------------------------------------------------
+    per_domain(D1_CONTEXTS, D1_TOPOLOGIES, D1_CONTEXT_ID, d1_context_client)
+    per_domain(D2_CONTEXTS, D2_TOPOLOGIES, D2_CONTEXT_ID, d2_context_client)
+
+
+def test_devices_bootstraping(
+    d1_device_client : DeviceClient,    # pylint: disable=redefined-outer-name
+    d2_device_client : DeviceClient):   # pylint: disable=redefined-outer-name
+
+    def per_domain(devices, device_client):
+        for device, connect_rules in devices:
+            device_uuid = device['device_id']['device_uuid']['uuid']
+            LOGGER.info('Adding Device {:s}'.format(device_uuid))
+            device_with_connect_rules = copy.deepcopy(device)
+            device_with_connect_rules['device_config']['config_rules'].extend(connect_rules)
+            response = device_client.AddDevice(Device(**device_with_connect_rules))
+            assert response.device_uuid.uuid == device_uuid
+
+    # ----- Create Devices and Validate Collected Events ---------------------------------------------------------------
+    per_domain(D1_DEVICES, d1_device_client)
+    per_domain(D2_DEVICES, d2_device_client)
+
+
+def test_devices_bootstrapped(
+    d1_context_client : ContextClient,  # pylint: disable=redefined-outer-name
+    d2_context_client : ContextClient): # pylint: disable=redefined-outer-name
+
+    def per_domain(contexts, topologies, devices, context_id, context_client):
+        response = context_client.ListContexts(Empty())
+        assert len(response.contexts) == len(contexts)
+
+        response = context_client.ListTopologies(ContextId(**context_id))
+        assert len(response.topologies) == len(topologies)
+
+        response = context_client.ListDevices(Empty())
+        assert len(response.devices) == len(devices)
+
+        response = context_client.ListLinks(Empty())
+        assert len(response.links) == 0
+
+        response = context_client.ListServices(ContextId(**context_id))
+        assert len(response.services) == 0
+
+    # ----- List entities - Ensure bevices are created -----------------------------------------------------------------
+    per_domain(D1_CONTEXTS, D1_TOPOLOGIES, D1_DEVICES, D1_CONTEXT_ID, d1_context_client)
+    per_domain(D2_CONTEXTS, D2_TOPOLOGIES, D2_DEVICES, D2_CONTEXT_ID, d2_context_client)
+
+
+def test_links_creation(
+    d1_context_client : ContextClient,  # pylint: disable=redefined-outer-name
+    d2_context_client : ContextClient): # pylint: disable=redefined-outer-name
+
+    def per_domain(links, context_client):
+        for link in links:
+            link_uuid = link['link_id']['link_uuid']['uuid']
+            LOGGER.info('Adding Link {:s}'.format(link_uuid))
+            response = context_client.SetLink(Link(**link))
+            assert response.link_uuid.uuid == link_uuid
+
+    # ----- Create Links and Validate Collected Events -----------------------------------------------------------------
+    per_domain(D1_LINKS, d1_context_client)
+    per_domain(D2_LINKS, d2_context_client)
+
+
+def test_links_created(
+    d1_context_client : ContextClient,  # pylint: disable=redefined-outer-name
+    d2_context_client : ContextClient): # pylint: disable=redefined-outer-name
+
+    def per_domain(contexts, topologies, devices, links, context_id, context_client):
+        response = context_client.ListContexts(Empty())
+        assert len(response.contexts) == len(contexts)
+
+        response = context_client.ListTopologies(ContextId(**context_id))
+        assert len(response.topologies) == len(topologies)
+
+        response = context_client.ListDevices(Empty())
+        assert len(response.devices) == len(devices)
+
+        response = context_client.ListLinks(Empty())
+        assert len(response.links) == len(links)
+
+        response = context_client.ListServices(ContextId(**context_id))
+        assert len(response.services) == 0
+
+    # ----- List entities - Ensure links are created -------------------------------------------------------------------
+    per_domain(D1_CONTEXTS, D1_TOPOLOGIES, D1_DEVICES, D1_LINKS, D1_CONTEXT_ID, d1_context_client)
+    per_domain(D2_CONTEXTS, D2_TOPOLOGIES, D2_DEVICES, D2_LINKS, D2_CONTEXT_ID, d2_context_client)
diff --git a/src/tests/oeccpsc22/tests/test_functional_cleanup.py b/src/tests/oeccpsc22/tests/test_functional_cleanup.py
new file mode 100644
index 0000000000000000000000000000000000000000..eb78a585079e3ee757a836433bf23423a3ad899d
--- /dev/null
+++ b/src/tests/oeccpsc22/tests/test_functional_cleanup.py
@@ -0,0 +1,123 @@
+# Copyright 2021-2023 H2020 TeraFlow (https://www.teraflow-h2020.eu/)
+#
+# 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, pytest
+from common.Settings import get_setting
+from common.tests.EventTools import EVENT_REMOVE, check_events
+from common.tools.object_factory.Context import json_context_id
+from common.tools.object_factory.Device import json_device_id
+from common.tools.object_factory.Link import json_link_id
+from common.tools.object_factory.Topology import json_topology_id
+from context.client.ContextClient import ContextClient
+from context.client.EventsCollector import EventsCollector
+from context.proto.context_pb2 import ContextId, DeviceId, Empty, LinkId, TopologyId
+from device.client.DeviceClient import DeviceClient
+from .Objects import CONTEXT_ID, CONTEXTS, DEVICES, LINKS, TOPOLOGIES
+
+LOGGER = logging.getLogger(__name__)
+LOGGER.setLevel(logging.DEBUG)
+
+
+@pytest.fixture(scope='session')
+def context_client():
+    _client = ContextClient(get_setting('CONTEXTSERVICE_SERVICE_HOST'), get_setting('CONTEXTSERVICE_SERVICE_PORT_GRPC'))
+    yield _client
+    _client.close()
+
+
+@pytest.fixture(scope='session')
+def device_client():
+    _client = DeviceClient(get_setting('DEVICESERVICE_SERVICE_HOST'), get_setting('DEVICESERVICE_SERVICE_PORT_GRPC'))
+    yield _client
+    _client.close()
+
+
+def test_services_removed(context_client : ContextClient):  # pylint: disable=redefined-outer-name
+    # ----- List entities - Ensure service is removed ------------------------------------------------------------------
+    response = context_client.ListContexts(Empty())
+    assert len(response.contexts) == len(CONTEXTS)
+
+    response = context_client.ListTopologies(ContextId(**CONTEXT_ID))
+    assert len(response.topologies) == len(TOPOLOGIES)
+
+    response = context_client.ListDevices(Empty())
+    assert len(response.devices) == len(DEVICES)
+
+    response = context_client.ListLinks(Empty())
+    assert len(response.links) == len(LINKS)
+
+    response = context_client.ListServices(ContextId(**CONTEXT_ID))
+    assert len(response.services) == 0
+
+
+def test_scenario_cleanup(
+    context_client : ContextClient, device_client : DeviceClient):  # pylint: disable=redefined-outer-name
+
+    # ----- Start the EventsCollector ----------------------------------------------------------------------------------
+    events_collector = EventsCollector(context_client)
+    events_collector.start()
+
+    expected_events = []
+
+    # ----- Delete Links and Validate Collected Events -----------------------------------------------------------------
+    for link in LINKS:
+        link_id = link['link_id']
+        link_uuid = link_id['link_uuid']['uuid']
+        LOGGER.info('Deleting Link {:s}'.format(link_uuid))
+        context_client.RemoveLink(LinkId(**link_id))
+        expected_events.append(('LinkEvent', EVENT_REMOVE, json_link_id(link_uuid)))
+
+    # ----- Delete Devices and Validate Collected Events ---------------------------------------------------------------
+    for device, _ in DEVICES:
+        device_id = device['device_id']
+        device_uuid = device_id['device_uuid']['uuid']
+        LOGGER.info('Deleting Device {:s}'.format(device_uuid))
+        device_client.DeleteDevice(DeviceId(**device_id))
+        expected_events.append(('DeviceEvent', EVENT_REMOVE, json_device_id(device_uuid)))
+
+    # ----- Delete Topologies and Validate Collected Events ------------------------------------------------------------
+    for topology in TOPOLOGIES:
+        topology_id = topology['topology_id']
+        context_uuid = topology_id['context_id']['context_uuid']['uuid']
+        topology_uuid = topology_id['topology_uuid']['uuid']
+        LOGGER.info('Deleting Topology {:s}/{:s}'.format(context_uuid, topology_uuid))
+        context_client.RemoveTopology(TopologyId(**topology_id))
+        context_id = json_context_id(context_uuid)
+        expected_events.append(('TopologyEvent', EVENT_REMOVE, json_topology_id(topology_uuid, context_id=context_id)))
+
+    # ----- Delete Contexts and Validate Collected Events --------------------------------------------------------------
+    for context in CONTEXTS:
+        context_id = context['context_id']
+        context_uuid = context_id['context_uuid']['uuid']
+        LOGGER.info('Deleting Context {:s}'.format(context_uuid))
+        context_client.RemoveContext(ContextId(**context_id))
+        expected_events.append(('ContextEvent', EVENT_REMOVE, json_context_id(context_uuid)))
+
+    # ----- Validate Collected Events ----------------------------------------------------------------------------------
+    check_events(events_collector, expected_events)
+
+    # ----- Stop the EventsCollector -----------------------------------------------------------------------------------
+    events_collector.stop()
+
+
+def test_scenario_empty_again(context_client : ContextClient):  # pylint: disable=redefined-outer-name
+    # ----- List entities - Ensure database is empty again -------------------------------------------------------------
+    response = context_client.ListContexts(Empty())
+    assert len(response.contexts) == 0
+
+    response = context_client.ListDevices(Empty())
+    assert len(response.devices) == 0
+
+    response = context_client.ListLinks(Empty())
+    assert len(response.links) == 0
diff --git a/src/tests/oeccpsc22/tests/test_functional_create_service.py b/src/tests/oeccpsc22/tests/test_functional_create_service.py
new file mode 100644
index 0000000000000000000000000000000000000000..f3389fdbfce4e9262ffddbad876bb86f9b300551
--- /dev/null
+++ b/src/tests/oeccpsc22/tests/test_functional_create_service.py
@@ -0,0 +1,129 @@
+# Copyright 2021-2023 H2020 TeraFlow (https://www.teraflow-h2020.eu/)
+#
+# 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, pytest
+from common.DeviceTypes import DeviceTypeEnum
+from common.Settings import get_setting
+from common.tests.EventTools import EVENT_CREATE, EVENT_UPDATE, check_events
+from common.tools.object_factory.Connection import json_connection_id
+from common.tools.object_factory.Device import json_device_id
+from common.tools.object_factory.Service import json_service_id
+from common.tools.grpc.Tools import grpc_message_to_json_string
+from compute.tests.mock_osm.MockOSM import MockOSM
+from context.client.ContextClient import ContextClient
+from context.client.EventsCollector import EventsCollector
+from context.proto.context_pb2 import ContextId, Empty
+from .Objects import (
+    CONTEXT_ID, CONTEXTS, DEVICE_O1_UUID, DEVICE_R1_UUID, DEVICE_R3_UUID, DEVICES, LINKS, TOPOLOGIES,
+    WIM_MAPPING, WIM_PASSWORD, WIM_SERVICE_CONNECTION_POINTS, WIM_SERVICE_TYPE, WIM_USERNAME)
+
+LOGGER = logging.getLogger(__name__)
+LOGGER.setLevel(logging.DEBUG)
+
+DEVTYPE_EMU_PR  = DeviceTypeEnum.EMULATED_PACKET_ROUTER.value
+DEVTYPE_EMU_OLS = DeviceTypeEnum.EMULATED_OPTICAL_LINE_SYSTEM.value
+
+
+@pytest.fixture(scope='session')
+def context_client():
+    _client = ContextClient(get_setting('CONTEXTSERVICE_SERVICE_HOST'), get_setting('CONTEXTSERVICE_SERVICE_PORT_GRPC'))
+    yield _client
+    _client.close()
+
+
+@pytest.fixture(scope='session')
+def osm_wim():
+    wim_url = 'http://{:s}:{:s}'.format(
+        get_setting('COMPUTESERVICE_SERVICE_HOST'), str(get_setting('COMPUTESERVICE_SERVICE_PORT_HTTP')))
+    return MockOSM(wim_url, WIM_MAPPING, WIM_USERNAME, WIM_PASSWORD)
+
+
+def test_scenario_is_correct(context_client : ContextClient):  # pylint: disable=redefined-outer-name
+    # ----- List entities - Ensure links are created -------------------------------------------------------------------
+    response = context_client.ListContexts(Empty())
+    assert len(response.contexts) == len(CONTEXTS)
+
+    response = context_client.ListTopologies(ContextId(**CONTEXT_ID))
+    assert len(response.topologies) == len(TOPOLOGIES)
+
+    response = context_client.ListDevices(Empty())
+    assert len(response.devices) == len(DEVICES)
+
+    response = context_client.ListLinks(Empty())
+    assert len(response.links) == len(LINKS)
+
+    response = context_client.ListServices(ContextId(**CONTEXT_ID))
+    assert len(response.services) == 0
+
+
+def test_service_creation(context_client : ContextClient, osm_wim : MockOSM): # pylint: disable=redefined-outer-name
+    # ----- Start the EventsCollector ----------------------------------------------------------------------------------
+    events_collector = EventsCollector(context_client, log_events_received=True)
+    events_collector.start()
+
+    # ----- Create Service ---------------------------------------------------------------------------------------------
+    service_uuid = osm_wim.create_connectivity_service(WIM_SERVICE_TYPE, WIM_SERVICE_CONNECTION_POINTS)
+    osm_wim.get_connectivity_service_status(service_uuid)
+
+    # ----- Validate collected events ----------------------------------------------------------------------------------
+
+    packet_connection_uuid = '{:s}:{:s}'.format(service_uuid, DEVTYPE_EMU_PR)
+    optical_connection_uuid = '{:s}:optical:{:s}'.format(service_uuid, DEVTYPE_EMU_OLS)
+    optical_service_uuid = '{:s}:optical'.format(service_uuid)
+
+    expected_events = [
+        # Create packet service and add first endpoint
+        ('ServiceEvent',    EVENT_CREATE, json_service_id(service_uuid, context_id=CONTEXT_ID)),
+        ('ServiceEvent',    EVENT_UPDATE, json_service_id(service_uuid, context_id=CONTEXT_ID)),
+
+        # Configure OLS controller, create optical service, create optical connection
+        ('DeviceEvent',     EVENT_UPDATE, json_device_id(DEVICE_O1_UUID)),
+        ('ServiceEvent',    EVENT_CREATE, json_service_id(optical_service_uuid, context_id=CONTEXT_ID)),
+        ('ConnectionEvent', EVENT_CREATE, json_connection_id(optical_connection_uuid)),
+
+        # Configure endpoint packet devices, add second endpoint to service, create connection
+        ('DeviceEvent',     EVENT_UPDATE, json_device_id(DEVICE_R1_UUID)),
+        ('DeviceEvent',     EVENT_UPDATE, json_device_id(DEVICE_R3_UUID)),
+        ('ServiceEvent',    EVENT_UPDATE, json_service_id(service_uuid, context_id=CONTEXT_ID)),
+        ('ConnectionEvent', EVENT_CREATE, json_connection_id(packet_connection_uuid)),
+    ]
+    check_events(events_collector, expected_events)
+
+    # ----- Stop the EventsCollector -----------------------------------------------------------------------------------
+    events_collector.stop()
+
+
+def test_scenario_service_created(context_client : ContextClient):  # pylint: disable=redefined-outer-name
+    # ----- List entities - Ensure service is created ------------------------------------------------------------------
+    response = context_client.ListContexts(Empty())
+    assert len(response.contexts) == len(CONTEXTS)
+
+    response = context_client.ListTopologies(ContextId(**CONTEXT_ID))
+    assert len(response.topologies) == len(TOPOLOGIES)
+
+    response = context_client.ListDevices(Empty())
+    assert len(response.devices) == len(DEVICES)
+
+    response = context_client.ListLinks(Empty())
+    assert len(response.links) == len(LINKS)
+
+    response = context_client.ListServices(ContextId(**CONTEXT_ID))
+    LOGGER.info('Services[{:d}] = {:s}'.format(len(response.services), grpc_message_to_json_string(response)))
+    assert len(response.services) == 2 # L3NM + TAPI
+    for service in response.services:
+        service_id = service.service_id
+        response = context_client.ListConnections(service_id)
+        LOGGER.info('  ServiceId[{:s}] => Connections[{:d}] = {:s}'.format(
+            grpc_message_to_json_string(service_id), len(response.connections), grpc_message_to_json_string(response)))
+        assert len(response.connections) == 1 # one connection per service
diff --git a/src/tests/oeccpsc22/tests/test_functional_delete_service.py b/src/tests/oeccpsc22/tests/test_functional_delete_service.py
new file mode 100644
index 0000000000000000000000000000000000000000..51e91a5967e1696fa2fdfe7dd06d2efb46642248
--- /dev/null
+++ b/src/tests/oeccpsc22/tests/test_functional_delete_service.py
@@ -0,0 +1,134 @@
+# Copyright 2021-2023 H2020 TeraFlow (https://www.teraflow-h2020.eu/)
+#
+# 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, pytest
+from common.DeviceTypes import DeviceTypeEnum
+from common.Settings import get_setting
+from common.tests.EventTools import EVENT_REMOVE, EVENT_UPDATE, check_events
+from common.tools.object_factory.Connection import json_connection_id
+from common.tools.object_factory.Device import json_device_id
+from common.tools.object_factory.Service import json_service_id
+from common.tools.grpc.Tools import grpc_message_to_json_string
+from compute.tests.mock_osm.MockOSM import MockOSM
+from context.client.ContextClient import ContextClient
+from context.client.EventsCollector import EventsCollector
+from context.proto.context_pb2 import ContextId, Empty
+from .Objects import (
+    CONTEXT_ID, CONTEXTS, DEVICE_O1_UUID, DEVICE_R1_UUID, DEVICE_R3_UUID, DEVICES, LINKS, TOPOLOGIES, WIM_MAPPING,
+    WIM_PASSWORD, WIM_USERNAME)
+
+
+LOGGER = logging.getLogger(__name__)
+LOGGER.setLevel(logging.DEBUG)
+
+DEVTYPE_EMU_PR  = DeviceTypeEnum.EMULATED_PACKET_ROUTER.value
+DEVTYPE_EMU_OLS = DeviceTypeEnum.EMULATED_OPTICAL_LINE_SYSTEM.value
+
+
+@pytest.fixture(scope='session')
+def context_client():
+    _client = ContextClient(get_setting('CONTEXTSERVICE_SERVICE_HOST'), get_setting('CONTEXTSERVICE_SERVICE_PORT_GRPC'))
+    yield _client
+    _client.close()
+
+
+@pytest.fixture(scope='session')
+def osm_wim():
+    wim_url = 'http://{:s}:{:s}'.format(
+        get_setting('COMPUTESERVICE_SERVICE_HOST'), str(get_setting('COMPUTESERVICE_SERVICE_PORT_HTTP')))
+    return MockOSM(wim_url, WIM_MAPPING, WIM_USERNAME, WIM_PASSWORD)
+
+
+def test_scenario_is_correct(context_client : ContextClient):  # pylint: disable=redefined-outer-name
+    # ----- List entities - Ensure service is created ------------------------------------------------------------------
+    response = context_client.ListContexts(Empty())
+    assert len(response.contexts) == len(CONTEXTS)
+
+    response = context_client.ListTopologies(ContextId(**CONTEXT_ID))
+    assert len(response.topologies) == len(TOPOLOGIES)
+
+    response = context_client.ListDevices(Empty())
+    assert len(response.devices) == len(DEVICES)
+
+    response = context_client.ListLinks(Empty())
+    assert len(response.links) == len(LINKS)
+
+    response = context_client.ListServices(ContextId(**CONTEXT_ID))
+    LOGGER.info('Services[{:d}] = {:s}'.format(len(response.services), grpc_message_to_json_string(response)))
+    assert len(response.services) == 2 # L3NM + TAPI
+    for service in response.services:
+        service_id = service.service_id
+        response = context_client.ListConnections(service_id)
+        LOGGER.info('  ServiceId[{:s}] => Connections[{:d}] = {:s}'.format(
+            grpc_message_to_json_string(service_id), len(response.connections), grpc_message_to_json_string(response)))
+        assert len(response.connections) == 1 # one connection per service
+
+
+def test_service_removal(context_client : ContextClient, osm_wim : MockOSM): # pylint: disable=redefined-outer-name
+    # ----- Start the EventsCollector ----------------------------------------------------------------------------------
+    events_collector = EventsCollector(context_client, log_events_received=True)
+    events_collector.start()
+
+    # ----- Delete Service ---------------------------------------------------------------------------------------------
+    response = context_client.ListServiceIds(ContextId(**CONTEXT_ID))
+    LOGGER.info('Services[{:d}] = {:s}'.format(len(response.service_ids), grpc_message_to_json_string(response)))
+    assert len(response.service_ids) == 2 # L3NM + TAPI
+    service_uuids = set()
+    for service_id in response.service_ids:
+        service_uuid = service_id.service_uuid.uuid
+        if service_uuid.endswith(':optical'): continue
+        service_uuids.add(service_uuid)
+        osm_wim.conn_info[service_uuid] = {}
+
+    assert len(service_uuids) == 1  # assume a single service has been created
+    service_uuid = set(service_uuids).pop()
+
+    osm_wim.delete_connectivity_service(service_uuid)
+
+    # ----- Validate collected events ----------------------------------------------------------------------------------
+    packet_connection_uuid = '{:s}:{:s}'.format(service_uuid, DEVTYPE_EMU_PR)
+    optical_connection_uuid = '{:s}:optical:{:s}'.format(service_uuid, DEVTYPE_EMU_OLS)
+    optical_service_uuid = '{:s}:optical'.format(service_uuid)
+
+    expected_events = [
+        ('ConnectionEvent', EVENT_REMOVE, json_connection_id(packet_connection_uuid)),
+        ('DeviceEvent',     EVENT_UPDATE, json_device_id(DEVICE_R1_UUID)),
+        ('DeviceEvent',     EVENT_UPDATE, json_device_id(DEVICE_R3_UUID)),
+        ('ServiceEvent',    EVENT_REMOVE, json_service_id(service_uuid, context_id=CONTEXT_ID)),
+        ('ConnectionEvent', EVENT_REMOVE, json_connection_id(optical_connection_uuid)),
+        ('DeviceEvent',     EVENT_UPDATE, json_device_id(DEVICE_O1_UUID)),
+        ('ServiceEvent',    EVENT_REMOVE, json_service_id(optical_service_uuid, context_id=CONTEXT_ID)),
+    ]
+    check_events(events_collector, expected_events)
+
+    # ----- Stop the EventsCollector -----------------------------------------------------------------------------------
+    events_collector.stop()
+
+
+def test_services_removed(context_client : ContextClient):  # pylint: disable=redefined-outer-name
+    # ----- List entities - Ensure service is removed ------------------------------------------------------------------
+    response = context_client.ListContexts(Empty())
+    assert len(response.contexts) == len(CONTEXTS)
+
+    response = context_client.ListTopologies(ContextId(**CONTEXT_ID))
+    assert len(response.topologies) == len(TOPOLOGIES)
+
+    response = context_client.ListDevices(Empty())
+    assert len(response.devices) == len(DEVICES)
+
+    response = context_client.ListLinks(Empty())
+    assert len(response.links) == len(LINKS)
+
+    response = context_client.ListServices(ContextId(**CONTEXT_ID))
+    assert len(response.services) == 0