diff --git a/scripts/run_test_microwave_device.sh b/scripts/run_test_microwave_device.sh
new file mode 100755
index 0000000000000000000000000000000000000000..34317b56484108d8ef83ef1c1eb74fbc31bfc25c
--- /dev/null
+++ b/scripts/run_test_microwave_device.sh
@@ -0,0 +1,28 @@
+#!/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
+
+# Run unitary tests and analyze coverage of code at same time
+
+# Useful flags for pytest:
+#-o log_cli=true -o log_file=device.log -o log_file_level=DEBUG
+
+coverage run --rcfile=$RCFILE --append -m pytest -s --log-level=INFO --verbose \
+    device/tests/test_unitary_microwave.py
diff --git a/src/common/DeviceTypes.py b/src/common/DeviceTypes.py
index 44f8e3981157d2b77e5450c34efa54ab66a39362..bf871a2d5afa6a73f1c9dd39431c64a7f31bbd7e 100644
--- a/src/common/DeviceTypes.py
+++ b/src/common/DeviceTypes.py
@@ -17,6 +17,7 @@ from enum import Enum
 class DeviceTypeEnum(Enum):
     EMULATED_OPTICAL_LINE_SYSTEM = 'emu-optical-line-system'
     EMULATED_PACKET_ROUTER       = 'emu-packet-router'
+    MICROVAWE_RADIO_SYSTEM       = 'microwave-radio-system'
     OPTICAL_ROADM                = 'optical-roadm'
     OPTICAL_TRANDPONDER          = 'optical-trandponder'
     OPTICAL_LINE_SYSTEM          = 'optical-line-system'
diff --git a/src/common/tools/object_factory/Device.py b/src/common/tools/object_factory/Device.py
index ae065e9c03127bf26b4e15710431cc1cfad67208..c4ef8173cb3b366097f963b6ed3d1d344e97ba11 100644
--- a/src/common/tools/object_factory/Device.py
+++ b/src/common/tools/object_factory/Device.py
@@ -32,6 +32,10 @@ DEVICE_PR_DRIVERS   = [DeviceDriverEnum.DEVICEDRIVER_OPENCONFIG]
 DEVICE_TAPI_TYPE    = DeviceTypeEnum.OPTICAL_LINE_SYSTEM.value
 DEVICE_TAPI_DRIVERS = [DeviceDriverEnum.DEVICEDRIVER_TRANSPORT_API]
 
+# check which enum type and value assign to microwave device
+DEVICE_MICROWAVE_TYPE    = DeviceTypeEnum.MICROVAWE_RADIO_SYSTEM.value
+DEVICE_MICROWAVE_DRIVERS = [DeviceDriverEnum.DEVICEDRIVER_IETF_NETWORK_TOPOLOGY]
+
 DEVICE_P4_TYPE      = DeviceTypeEnum.P4_SWITCH.value
 DEVICE_P4_DRIVERS   = [DeviceDriverEnum.DEVICEDRIVER_P4]
 
@@ -81,6 +85,13 @@ def json_device_tapi_disabled(
     return json_device(
         device_uuid, DEVICE_TAPI_TYPE, DEVICE_DISABLED, endpoints=endpoints, config_rules=config_rules, drivers=drivers)
 
+def json_device_microwave_disabled(
+        device_uuid : str, endpoints : List[Dict] = [], config_rules : List[Dict] = [],
+        drivers : List[Dict] = DEVICE_MICROWAVE_DRIVERS
+    ):
+    return json_device(
+        device_uuid, DEVICE_MICROWAVE_TYPE, DEVICE_DISABLED, endpoints=endpoints, config_rules=config_rules, drivers=drivers)
+
 def json_device_p4_disabled(
         device_uuid : str, endpoints : List[Dict] = [], config_rules : List[Dict] = [],
         drivers : List[Dict] = DEVICE_P4_DRIVERS
diff --git a/src/device/requirements.in b/src/device/requirements.in
index dde08cf195ecb503c7a10f75a2df9ca9f1a0c651..d1af1e219f548a275078d31e2f489c07eb54ddd1 100644
--- a/src/device/requirements.in
+++ b/src/device/requirements.in
@@ -3,6 +3,7 @@ APScheduler==3.8.1
 fastcache==1.1.0
 grpcio==1.43.0
 grpcio-health-checking==1.43.0
+grpcio-tools==1.43.0
 Jinja2==3.0.3
 netconf-client==2.0.0 #1.7.3
 p4runtime==1.3.0
diff --git a/src/device/service/drivers/__init__.py b/src/device/service/drivers/__init__.py
index 7479d43641b1ecd0803b3c790f51256a03e2c7fa..664b52821224f4d53b17cb5e10e44461ac7b75f4 100644
--- a/src/device/service/drivers/__init__.py
+++ b/src/device/service/drivers/__init__.py
@@ -18,6 +18,7 @@ from .emulated.EmulatedDriver import EmulatedDriver
 from .openconfig.OpenConfigDriver import OpenConfigDriver
 from .transport_api.TransportApiDriver import TransportApiDriver
 from .p4.p4_driver import P4Driver
+from .microwave.IETFApiDriver import IETFApiDriver
 
 DRIVERS = [
     (EmulatedDriver, [
@@ -51,4 +52,10 @@ DRIVERS = [
             FilterFieldEnum.DRIVER     : ORM_DeviceDriverEnum.P4,
         }
     ]),
+    (IETFApiDriver, [
+        {
+            FilterFieldEnum.DEVICE_TYPE: DeviceTypeEnum.MICROVAWE_RADIO_SYSTEM,
+            FilterFieldEnum.DRIVER     : ORM_DeviceDriverEnum.IETF_NETWORK_TOPOLOGY,
+        }
+    ]),
 ]
diff --git a/src/device/service/drivers/microwave/IETFApiDriver.py b/src/device/service/drivers/microwave/IETFApiDriver.py
new file mode 100644
index 0000000000000000000000000000000000000000..714a149b6459656cc62c4945da3b2e8d33d0a4e0
--- /dev/null
+++ b/src/device/service/drivers/microwave/IETFApiDriver.py
@@ -0,0 +1,112 @@
+# 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, requests, threading
+from typing import Any, Iterator, List, Optional, Tuple, Union
+from common.type_checkers.Checkers import chk_string, chk_type
+from device.service.driver_api._Driver import _Driver
+from . import ALL_RESOURCE_KEYS
+from .Tools import create_connectivity_service, find_key, config_getter, delete_connectivity_service
+
+LOGGER = logging.getLogger(__name__)
+
+class IETFApiDriver(_Driver):
+    def __init__(self, address: str, port: int, **settings) -> None:    # pylint: disable=super-init-not-called
+        self.__lock = threading.Lock()
+        self.__started = threading.Event()
+        self.__terminate = threading.Event()
+        self.__tapi_root = 'https://' + address + ':' + str(port)
+        self.__timeout = int(settings.get('timeout', 120))
+
+    def Connect(self) -> bool:
+        url = self.__tapi_root + '/nmswebs/restconf/data/ietf-network:networks'
+        with self.__lock:
+            if self.__started.is_set(): return True
+            try:
+                requests.get(url, timeout=self.__timeout, verify=False)
+            except requests.exceptions.Timeout:
+                LOGGER.exception('Timeout connecting {:s}'.format(str(self.__tapi_root)))
+                return False
+            except Exception:  # pylint: disable=broad-except
+                LOGGER.exception('Exception connecting {:s}'.format(str(self.__tapi_root)))
+                return False
+            else:
+                self.__started.set()
+                return True
+
+    def Disconnect(self) -> bool:
+        with self.__lock:
+            self.__terminate.set()
+            return True
+
+    def GetInitialConfig(self) -> List[Tuple[str, Any]]:
+        with self.__lock:
+            return []
+
+    def GetConfig(self, resource_keys : List[str] = []) -> List[Tuple[str, Union[Any, None, Exception]]]:
+        chk_type('resources', resource_keys, list)
+        results = []
+        with self.__lock:
+            if len(resource_keys) == 0: resource_keys = ALL_RESOURCE_KEYS
+            for i, resource_key in enumerate(resource_keys):
+                str_resource_name = 'resource_key[#{:d}]'.format(i)
+                chk_string(str_resource_name, resource_key, allow_empty=False)
+                results.extend(config_getter(self.__tapi_root, resource_key, self.__timeout))
+        return results
+
+
+    def SetConfig(self, resources: List[Tuple[str, Any]]) -> List[Union[bool, Exception]]:
+        results = []
+        if len(resources) == 0:
+            return results
+        with self.__lock:
+            for resource in resources:
+                LOGGER.info('resource = {:s}'.format(str(resource)))
+
+                node_id_src = find_key(resource, 'node_id_src')
+                tp_id_src = find_key(resource, 'tp_id_src')
+                node_id_dst = find_key(resource, 'node_id_dst')
+                tp_id_dst = find_key(resource, 'tp_id_dst')
+                vlan_id = find_key(resource, 'vlan_id')
+                uuid = find_key(resource, 'uuid')
+
+                data = create_connectivity_service(
+                    self.__tapi_root, self.__timeout, uuid, node_id_src, tp_id_src, node_id_dst, tp_id_dst, vlan_id)
+                results.extend(data)
+        return results
+
+
+    def DeleteConfig(self, resources: List[Tuple[str, Any]]) -> List[Union[bool, Exception]]:
+        results = []
+        if len(resources) == 0: return results
+        with self.__lock:
+            for resource in resources:
+                LOGGER.info('resource = {:s}'.format(str(resource)))
+                uuid = find_key(resource, 'uuid')
+                results.extend(delete_connectivity_service(self.__tapi_root, self.__timeout, uuid))
+        return results
+
+    def SubscribeState(self, subscriptions : List[Tuple[str, float, float]]) -> List[Union[bool, Exception]]:
+        # TODO: TAPI does not support monitoring by now
+        return [False for _ in subscriptions]
+
+    def UnsubscribeState(self, subscriptions : List[Tuple[str, float, float]]) -> List[Union[bool, Exception]]:
+        # TODO: TAPI does not support monitoring by now
+        return [False for _ in subscriptions]
+
+    def GetState(
+        self, blocking=False, terminate : Optional[threading.Event] = None
+    ) -> Iterator[Tuple[float, str, Any]]:
+        # TODO: TAPI does not support monitoring by now
+        return []
diff --git a/src/device/service/drivers/microwave/Tools.py b/src/device/service/drivers/microwave/Tools.py
new file mode 100644
index 0000000000000000000000000000000000000000..6398b6867b887abb7d0ea69d4d6d66588a21522f
--- /dev/null
+++ b/src/device/service/drivers/microwave/Tools.py
@@ -0,0 +1,177 @@
+# 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 json, logging, requests
+from device.service.driver_api._Driver import RESOURCE_ENDPOINTS
+
+LOGGER = logging.getLogger(__name__)
+
+
+def find_key(resource, key):
+    return json.loads(resource[1])[key]
+
+# this function exports only the endpoints which are not already involved in a microwave physical link
+def isAnExportableEndpoint(node, termination_point_id, links):
+    # for each link we check if the endpoint (termination_point_id) is already used by an existing link
+    for link in links:
+        src = link['source']
+        dest = link['destination']
+        if dest['dest-node'] == node and dest['dest-tp'] == termination_point_id:
+            return False
+        elif src['source-node'] == node and src['source-tp'] == termination_point_id:
+            return False
+
+    return True
+
+def config_getter(root_url, resource_key, timeout):
+    # getting endpoints
+    url = '{:s}/nmswebs/restconf/data/ietf-network:networks/network=SIAE-ETH-TOPOLOGY?fields=ietf-network-topology:link(link-id;destination(dest-node;dest-tp);source(source-node;source-tp));node(node-id;ietf-network-topology:termination-point(tp-id;ietf-te-topology:te/name)))'.format(root_url)
+    result = []
+    try:
+        response = requests.get(url, timeout=timeout, verify=False)
+    except requests.exceptions.Timeout:
+        LOGGER.exception('Timeout connecting {:s}'.format(url))
+    except Exception as e:  # pylint: disable=broad-except
+        LOGGER.exception('Exception retrieving {:s}'.format(resource_key))
+        result.append((resource_key, e))
+    else:
+        context = json.loads(response.content)
+    
+
+        if resource_key == RESOURCE_ENDPOINTS:
+            if not (context.get('ietf-network:network') is None):
+                network_instance = context['ietf-network:network']
+                if not (network_instance.get('ietf-network-topology:link') is None):
+                    links = network_instance['ietf-network-topology:link']  
+
+                    for sip in network_instance['node']:
+                        tp = sip['ietf-network-topology:termination-point']
+                        node_id = sip['node-id']
+                        for te in tp:
+                            tp_id = te['tp-id']
+                            if isAnExportableEndpoint(node_id, tp_id, links):
+                                result.append(('/endpoints/endpoint[{:s}:{:s}]'.format(node_id,tp_id), {'uuid': tp_id, 'type': te['ietf-te-topology:te']['name']}))
+
+    # getting created services 
+    url = '{:s}/nmswebs/restconf/data/ietf-eth-tran-service:etht-svc'.format(root_url)
+    try:
+        response = requests.get(url, timeout=timeout, verify=False)
+    except requests.exceptions.Timeout:
+        LOGGER.exception('Timeout connecting {:s}'.format(url))
+    except Exception as e:  # pylint: disable=broad-except
+        LOGGER.exception('Exception retrieving {:s}'.format(resource_key))
+        result.append((resource_key, e))
+    else:
+        context = json.loads(response.content)
+    
+
+        if resource_key == RESOURCE_ENDPOINTS:
+            if not (context.get('ietf-eth-tran-service:etht-svc') is None):
+                etht_service = context['ietf-eth-tran-service:etht-svc']
+                if not (etht_service.get('etht-svc-instances') is None):
+                    service_instances = etht_service['etht-svc-instances'] 
+            
+                    for service in service_instances:
+                        service_name = service['etht-svc-name']
+                        result.append(('/services/service[{:s}]'.format(service_name), {'uuid': service_name, 'type': service['etht-svc-type']}))
+            
+    return result
+
+
+def create_connectivity_service(
+    root_url, timeout, uuid, node_id_src, tp_id_src, node_id_dst, tp_id_dst, vlan_id):
+
+    url = '{:s}/nmswebs/restconf/data/ietf-eth-tran-service:etht-svc'.format(root_url)
+    headers = {'content-type': 'application/json'}
+    data = {}
+    
+    data = {
+            "etht-svc-instances": 
+            [
+	            {
+		            "etht-svc-name": uuid,
+		            "etht-svc-type": "ietf-eth-tran-types:p2p-svc",
+		            "etht-svc-end-points": 
+                    [
+		                {
+			                "etht-svc-access-points": 
+                            [
+			                    {       
+			                        "access-node-id": node_id_src,
+			                        "access-ltp-id": tp_id_src,
+			                        "access-point-id": "1"
+			                    }
+			                ],
+		                    "outer-tag": 
+                                {
+			                        "vlan-value": vlan_id,
+			                        "tag-type": "ietf-eth-tran-types:classify-c-vlan"
+		                        },
+		                    "etht-svc-end-point-name": node_id_src+':'+str(tp_id_src),
+		                    "service-classification-type": "ietf-eth-tran-types:vlan-classification"
+		                },
+		                {
+		                    "etht-svc-access-points": 
+                            [
+		                        {
+			                        "access-node-id": node_id_dst,
+			                        "access-ltp-id": tp_id_dst,
+			                        "access-point-id": "2"
+		                        }
+		                    ],
+		                    "outer-tag": 
+                                {
+			                        "vlan-value": vlan_id,
+			                        "tag-type": "ietf-eth-tran-types:classify-c-vlan"
+		                        },
+		                    "etht-svc-end-point-name": node_id_dst+':'+str(tp_id_dst),
+		                    "service-classification-type": "ietf-eth-tran-types:vlan-classification"
+		                }
+		            ]	
+	            }
+	        ]       
+           }
+
+    
+    results = []
+    try:
+        LOGGER.info('Connectivity service {:s}: {:s}'.format(str(uuid), str(data)))
+        response = requests.post(url=url, data=json.dumps(data), timeout=timeout, headers=headers, verify=False)
+        LOGGER.info('Microwave Driver response: {:s}'.format(str(response)))
+    except Exception as e:  # pylint: disable=broad-except
+        LOGGER.exception('Exception creating ConnectivityService(uuid={:s}, data={:s})'.format(str(uuid), str(data)))
+        results.append(e)
+    else:
+        if response.status_code != 201:
+            msg = 'Could not create ConnectivityService(uuid={:s}, data={:s}). status_code={:s} reply={:s}'
+            LOGGER.error(msg.format(str(uuid), str(data), str(response.status_code), str(response)))
+        results.append(response.status_code == 201)
+    return results
+
+
+def delete_connectivity_service(root_url, timeout, uuid):
+    url = '{:s}/nmswebs/restconf/data/ietf-eth-tran-service:etht-svc/etht-svc-instances={:s}'
+    url = url.format(root_url, uuid)
+    results = []
+    try:
+        response = requests.delete(url=url, timeout=timeout, verify=False)
+    except Exception as e:  # pylint: disable=broad-except
+        LOGGER.exception('Exception deleting ConnectivityService(uuid={:s})'.format(str(uuid)))
+        results.append(e)
+    else:
+        if response.status_code != 201:
+            msg = 'Could not delete ConnectivityService(uuid={:s}). status_code={:s} reply={:s}'
+            LOGGER.error(msg.format(str(uuid), str(response.status_code), str(response)))
+        results.append(response.status_code == 202)
+    return results
diff --git a/src/device/service/drivers/microwave/__init__.py b/src/device/service/drivers/microwave/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..925746998061f4e05c468133dfacaaa0414551c8
--- /dev/null
+++ b/src/device/service/drivers/microwave/__init__.py
@@ -0,0 +1,27 @@
+# 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 device.service.driver_api._Driver import RESOURCE_ENDPOINTS, RESOURCE_INTERFACES, RESOURCE_NETWORK_INSTANCES
+
+ALL_RESOURCE_KEYS = [
+    RESOURCE_ENDPOINTS,
+    RESOURCE_INTERFACES,
+    RESOURCE_NETWORK_INSTANCES,
+]
+
+RESOURCE_KEY_MAPPINGS = {
+    RESOURCE_ENDPOINTS        : 'component',
+    RESOURCE_INTERFACES       : 'interface',
+    RESOURCE_NETWORK_INSTANCES: 'network_instance',
+}
diff --git a/src/device/tests/Device_Microwave_Template.py b/src/device/tests/Device_Microwave_Template.py
new file mode 100644
index 0000000000000000000000000000000000000000..1f2d57a1dfffae8e5947eedfbe00487ad2960733
--- /dev/null
+++ b/src/device/tests/Device_Microwave_Template.py
@@ -0,0 +1,46 @@
+# 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.tools.object_factory.ConfigRule import json_config_rule_delete, json_config_rule_set
+from common.tools.object_factory.Device import (
+    json_device_connect_rules, json_device_id, json_device_microwave_disabled)
+
+DEVICE_MICROWAVE_UUID    = 'DEVICE-MICROWAVE'     # populate 'device-uuid' of the MICROWAVE SMDC server
+DEVICE_MICROWAVE_ADDRESS = '127.0.0.1'       # populate 'address' of the MICROWAVE SMDC server
+DEVICE_MICROWAVE_PORT    = 8443                   # populate 'port' of the MICROWAVE SMDC server
+DEVICE_MICROWAVE_TIMEOUT = 120                    # populate 'timeout' of the MICROWAVE SMDC server
+
+DEVICE_MICROWAVE_ID = json_device_id(DEVICE_MICROWAVE_UUID)
+DEVICE_MICROWAVE    = json_device_microwave_disabled(DEVICE_MICROWAVE_UUID)
+
+DEVICE_MICROWAVE_CONNECT_RULES = json_device_connect_rules(DEVICE_MICROWAVE_ADDRESS, DEVICE_MICROWAVE_PORT, {
+    'timeout' : DEVICE_MICROWAVE_TIMEOUT,
+})
+
+DEVICE_MICROWAVE_CONFIG_RULES = [
+    json_config_rule_set('/services/service[service_uuid]', {
+        'uuid'                      : 'service-uuid',       # populate 'service_name of the service to test
+        'node_id_src'               : '172.26.60.243',      # populate 'node_id_src' of the service to test
+        'tp_id_src'                 : 9,                    # populate 'tp_id_src' of the service to test
+        'node_id_dst'               : '172.26.60.244',      # populate 'node_id_dst' of the service to test
+        'tp_id_dst'                 : 9,                    # populate 'tp_id_dst' of the service to test
+        'vlan_id'                   : 121,                  # populate 'vlan_id' of the service to test
+    })
+]
+
+DEVICE_MICROWAVE_DECONFIG_RULES = [
+    json_config_rule_delete('/services/service[service-uuid]', {
+        'uuid': 'service-uuid'                              # populate 'service_name' of the service to test
+    })
+]
diff --git a/src/device/tests/test_unitary_microwave.py b/src/device/tests/test_unitary_microwave.py
new file mode 100644
index 0000000000000000000000000000000000000000..8718d99fb9d9d430e49ecd44edc60e5a2e9be339
--- /dev/null
+++ b/src/device/tests/test_unitary_microwave.py
@@ -0,0 +1,316 @@
+# 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 calendar, copy, dateutil.parser, grpc, json, logging, operator, os, pytest, queue, time
+from datetime import datetime, timezone
+from typing import Tuple
+from common.orm.Database import Database
+from common.orm.Factory import get_database_backend, BackendEnum as DatabaseBackendEnum
+from common.message_broker.Factory import get_messagebroker_backend, BackendEnum as MessageBrokerBackendEnum
+from common.message_broker.MessageBroker import MessageBroker
+from common.tools.grpc.Tools import grpc_message_to_json_string
+from common.tools.object_factory.EndPoint import json_endpoint, json_endpoint_id
+from context.Config import (
+    GRPC_SERVICE_PORT as CONTEXT_GRPC_SERVICE_PORT, GRPC_MAX_WORKERS as CONTEXT_GRPC_MAX_WORKERS,
+    GRPC_GRACE_PERIOD as CONTEXT_GRPC_GRACE_PERIOD)
+from context.client.ContextClient import ContextClient
+from context.proto.context_pb2 import DeviceId, DeviceOperationalStatusEnum
+from context.service.grpc_server.ContextService import ContextService
+from device.Config import (
+    GRPC_SERVICE_PORT as DEVICE_GRPC_SERVICE_PORT, GRPC_MAX_WORKERS as DEVICE_GRPC_MAX_WORKERS,
+    GRPC_GRACE_PERIOD as DEVICE_GRPC_GRACE_PERIOD)
+from device.client.DeviceClient import DeviceClient
+from device.proto.context_pb2 import ConfigActionEnum, Context, Device, Topology
+from device.proto.device_pb2 import MonitoringSettings
+from device.proto.kpi_sample_types_pb2 import KpiSampleType
+from device.service.DeviceService import DeviceService
+from device.service.driver_api._Driver import _Driver
+from device.service.driver_api.DriverFactory import DriverFactory
+from device.service.driver_api.DriverInstanceCache import DriverInstanceCache
+from device.service.drivers import DRIVERS
+from device.tests.MockMonitoringService import MockMonitoringService
+from monitoring.Config import (
+    GRPC_SERVICE_PORT as MONITORING_GRPC_SERVICE_PORT, GRPC_MAX_WORKERS as MONITORING_GRPC_MAX_WORKERS,
+    GRPC_GRACE_PERIOD as MONITORING_GRPC_GRACE_PERIOD)
+from monitoring.client.monitoring_client import MonitoringClient
+from .CommonObjects import CONTEXT, TOPOLOGY
+
+from .Device_Emulated import (
+    DEVICE_EMU, DEVICE_EMU_CONFIG_ADDRESSES, DEVICE_EMU_CONFIG_ENDPOINTS, DEVICE_EMU_CONNECT_RULES,
+    DEVICE_EMU_DECONFIG_ADDRESSES, DEVICE_EMU_DECONFIG_ENDPOINTS, DEVICE_EMU_EP_DESCS, DEVICE_EMU_ENDPOINTS_COOKED,
+    DEVICE_EMU_ID, DEVICE_EMU_RECONFIG_ADDRESSES, DEVICE_EMU_UUID)
+ENABLE_EMULATED = True
+
+try:
+    from .Device_OpenConfig_Infinera1 import(
+    #from .Device_OpenConfig_Infinera2 import(
+        DEVICE_OC, DEVICE_OC_CONFIG_RULES, DEVICE_OC_DECONFIG_RULES, DEVICE_OC_CONNECT_RULES, DEVICE_OC_ID,
+        DEVICE_OC_UUID)
+    ENABLE_OPENCONFIG = True
+except ImportError:
+    ENABLE_OPENCONFIG = False
+
+try:
+    from .Device_Transport_Api_CTTC import (
+        DEVICE_TAPI, DEVICE_TAPI_CONNECT_RULES, DEVICE_TAPI_UUID, DEVICE_TAPI_ID, DEVICE_TAPI_CONFIG_RULES,
+        DEVICE_TAPI_DECONFIG_RULES)
+    ENABLE_TAPI = True
+except ImportError:
+    ENABLE_TAPI = False
+
+try:
+    from .Device_Microwave_Template import (
+        DEVICE_MICROWAVE, DEVICE_MICROWAVE_CONNECT_RULES, DEVICE_MICROWAVE_UUID, DEVICE_MICROWAVE_ID, DEVICE_MICROWAVE_CONFIG_RULES,
+        DEVICE_MICROWAVE_DECONFIG_RULES)
+    ENABLE_MICROWAVE = True
+except ImportError as error:
+    ENABLE_MICROWAVE = False
+    print(error.__class__.__name__ + ": " + error.message)
+
+from .mock_p4runtime_service import MockP4RuntimeService
+try:
+    from .device_p4 import(
+        DEVICE_P4, DEVICE_P4_ID, DEVICE_P4_UUID, DEVICE_P4_NAME, DEVICE_P4_ADDRESS, DEVICE_P4_PORT, DEVICE_P4_WORKERS,
+        DEVICE_P4_GRACE_PERIOD, DEVICE_P4_CONNECT_RULES, DEVICE_P4_CONFIG_RULES)
+    ENABLE_P4 = True
+except ImportError:
+    ENABLE_P4 = False
+
+#ENABLE_EMULATED   = False # set to False to disable tests of Emulated devices
+#ENABLE_OPENCONFIG = False # set to False to disable tests of OpenConfig devices
+#ENABLE_TAPI       = False # set to False to disable tests of TAPI devices
+#ENABLE_P4         = False # set to False to disable tests of P4 devices
+
+ENABLE_OPENCONFIG_CONFIGURE   = True
+ENABLE_OPENCONFIG_MONITOR     = True
+ENABLE_OPENCONFIG_DECONFIGURE = True
+
+
+logging.getLogger('apscheduler.executors.default').setLevel(logging.WARNING)
+logging.getLogger('apscheduler.scheduler').setLevel(logging.WARNING)
+logging.getLogger('monitoring-client').setLevel(logging.WARNING)
+
+LOGGER = logging.getLogger(__name__)
+LOGGER.setLevel(logging.DEBUG)
+
+CONTEXT_GRPC_SERVICE_PORT = 10000 + CONTEXT_GRPC_SERVICE_PORT # avoid privileged ports
+DEVICE_GRPC_SERVICE_PORT = 10000 + DEVICE_GRPC_SERVICE_PORT # avoid privileged ports
+MONITORING_GRPC_SERVICE_PORT = 10000 + MONITORING_GRPC_SERVICE_PORT # avoid privileged ports
+
+DEFAULT_REDIS_SERVICE_HOST = '127.0.0.1'
+DEFAULT_REDIS_SERVICE_PORT = 6379
+DEFAULT_REDIS_DATABASE_ID  = 0
+
+REDIS_CONFIG = {
+    'REDIS_SERVICE_HOST': os.environ.get('REDIS_SERVICE_HOST', DEFAULT_REDIS_SERVICE_HOST),
+    'REDIS_SERVICE_PORT': os.environ.get('REDIS_SERVICE_PORT', DEFAULT_REDIS_SERVICE_PORT),
+    'REDIS_DATABASE_ID' : os.environ.get('REDIS_DATABASE_ID',  DEFAULT_REDIS_DATABASE_ID ),
+}
+
+SCENARIOS = [
+    ('all_inmemory', DatabaseBackendEnum.INMEMORY, {},           MessageBrokerBackendEnum.INMEMORY, {}          ),
+    #('all_redis',    DatabaseBackendEnum.REDIS,    REDIS_CONFIG, MessageBrokerBackendEnum.REDIS,    REDIS_CONFIG),
+]
+
+@pytest.fixture(scope='session', ids=[str(scenario[0]) for scenario in SCENARIOS], params=SCENARIOS)
+def context_db_mb(request) -> Tuple[Database, MessageBroker]:
+    name,db_backend,db_settings,mb_backend,mb_settings = request.param
+    msg = 'Running scenario {:s} db_backend={:s}, db_settings={:s}, mb_backend={:s}, mb_settings={:s}...'
+    LOGGER.info(msg.format(str(name), str(db_backend.value), str(db_settings), str(mb_backend.value), str(mb_settings)))
+    _database = Database(get_database_backend(backend=db_backend, **db_settings))
+    _message_broker = MessageBroker(get_messagebroker_backend(backend=mb_backend, **mb_settings))
+    yield _database, _message_broker
+    _message_broker.terminate()
+
+@pytest.fixture(scope='session')
+def context_service(context_db_mb : Tuple[Database, MessageBroker]): # pylint: disable=redefined-outer-name
+    _service = ContextService(
+        context_db_mb[0], context_db_mb[1], port=CONTEXT_GRPC_SERVICE_PORT, max_workers=CONTEXT_GRPC_MAX_WORKERS,
+        grace_period=CONTEXT_GRPC_GRACE_PERIOD)
+    _service.start()
+    yield _service
+    _service.stop()
+
+@pytest.fixture(scope='session')
+def context_client(context_service : ContextService): # pylint: disable=redefined-outer-name
+    _client = ContextClient(address='127.0.0.1', port=CONTEXT_GRPC_SERVICE_PORT)
+    yield _client
+    _client.close()
+
+@pytest.fixture(scope='session')
+def monitoring_service():
+    _service = MockMonitoringService(port=MONITORING_GRPC_SERVICE_PORT, max_workers=MONITORING_GRPC_MAX_WORKERS,
+        grace_period=MONITORING_GRPC_GRACE_PERIOD)
+    _service.start()
+    yield _service
+    _service.stop()
+
+@pytest.fixture(scope='session')
+def monitoring_client(monitoring_service : MockMonitoringService): # pylint: disable=redefined-outer-name
+    _client = MonitoringClient(server='127.0.0.1', port=MONITORING_GRPC_SERVICE_PORT)
+    #yield _client
+    #_client.close()
+    return _client
+
+@pytest.fixture(scope='session')
+def device_service(
+    context_client : ContextClient,         # pylint: disable=redefined-outer-name
+    monitoring_client : MonitoringClient):  # pylint: disable=redefined-outer-name
+
+    _driver_factory = DriverFactory(DRIVERS)
+    _driver_instance_cache = DriverInstanceCache(_driver_factory)
+    _service = DeviceService(
+        context_client, monitoring_client, _driver_instance_cache, port=DEVICE_GRPC_SERVICE_PORT,
+        max_workers=DEVICE_GRPC_MAX_WORKERS, grace_period=DEVICE_GRPC_GRACE_PERIOD)
+    _service.start()
+    yield _service
+    _service.stop()
+
+@pytest.fixture(scope='session')
+def device_client(device_service : DeviceService): # pylint: disable=redefined-outer-name
+    _client = DeviceClient(address='127.0.0.1', port=DEVICE_GRPC_SERVICE_PORT)
+    yield _client
+    _client.close()
+
+@pytest.fixture(scope='session')
+def p4runtime_service():
+    _service = MockP4RuntimeService(
+        address=DEVICE_P4_ADDRESS, port=DEVICE_P4_PORT,
+        max_workers=DEVICE_P4_WORKERS,
+        grace_period=DEVICE_P4_GRACE_PERIOD)
+    _service.start()
+    yield _service
+    _service.stop()
+
+
+def test_prepare_environment(
+    context_client : ContextClient,     # pylint: disable=redefined-outer-name
+    device_client : DeviceClient,       # pylint: disable=redefined-outer-name
+    device_service : DeviceService):    # pylint: disable=redefined-outer-name
+
+    context_client.SetContext(Context(**CONTEXT))
+    context_client.SetTopology(Topology(**TOPOLOGY))
+
+
+
+
+# ----- Test Device Driver Microwave ------------------------------------------------
+
+def test_device_microwave_add_correct(
+    device_client: DeviceClient,        # pylint: disable=redefined-outer-name
+    device_service: DeviceService):     # pylint: disable=redefined-outer-name
+
+    if not ENABLE_MICROWAVE: pytest.skip('Skipping test: No MICROWAVE device has been configured')
+    
+    DEVICE_MICROWAVE_WITH_CONNECT_RULES = copy.deepcopy(DEVICE_MICROWAVE)
+    DEVICE_MICROWAVE_WITH_CONNECT_RULES['device_config']['config_rules'].extend(DEVICE_MICROWAVE_CONNECT_RULES)
+    device_client.AddDevice(Device(**DEVICE_MICROWAVE_WITH_CONNECT_RULES))
+    driver: _Driver = device_service.driver_instance_cache.get(DEVICE_MICROWAVE_UUID)
+    assert driver is not None
+
+
+def test_device_microwave_get(
+    context_client: ContextClient,      # pylint: disable=redefined-outer-name
+    device_client: DeviceClient):       # pylint: disable=redefined-outer-name
+
+    if not ENABLE_MICROWAVE: pytest.skip('Skipping test: No MICROWAVE device has been configured')
+
+    initial_config = device_client.GetInitialConfig(DeviceId(**DEVICE_MICROWAVE_ID))
+    LOGGER.info('initial_config = {:s}'.format(grpc_message_to_json_string(initial_config)))
+
+    device_data = context_client.GetDevice(DeviceId(**DEVICE_MICROWAVE_ID))
+    LOGGER.info('device_data = {:s}'.format(grpc_message_to_json_string(device_data)))
+
+
+def test_device_microwave_configure(
+    context_client: ContextClient,      # pylint: disable=redefined-outer-name
+    device_client: DeviceClient,        # pylint: disable=redefined-outer-name
+    device_service: DeviceService):     # pylint: disable=redefined-outer-name
+
+    if not ENABLE_MICROWAVE: pytest.skip('Skipping test: No MICROWAVE device has been configured')
+
+    driver : _Driver = device_service.driver_instance_cache.get(DEVICE_MICROWAVE_UUID)
+    assert driver is not None
+
+    # Requires to retrieve data from device; might be slow. Uncomment only when needed and test does not pass directly.
+    #driver_config = sorted(driver.GetConfig(), key=operator.itemgetter(0))
+    #LOGGER.info('driver_config = {:s}'.format(str(driver_config)))
+
+    DEVICE_MICROWAVE_WITH_CONFIG_RULES = copy.deepcopy(DEVICE_MICROWAVE)
+    DEVICE_MICROWAVE_WITH_CONFIG_RULES['device_config']['config_rules'].extend(DEVICE_MICROWAVE_CONFIG_RULES)
+    device_client.ConfigureDevice(Device(**DEVICE_MICROWAVE_WITH_CONFIG_RULES))
+
+    # Requires to retrieve data from device; might be slow. Uncomment only when needed and test does not pass directly.
+    #driver_config = sorted(driver.GetConfig(), key=operator.itemgetter(0))
+    #LOGGER.info('driver_config = {:s}'.format(str(driver_config)))
+
+    device_data = context_client.GetDevice(DeviceId(**DEVICE_MICROWAVE_ID))
+    config_rules = [
+        (ConfigActionEnum.Name(config_rule.action), config_rule.resource_key, config_rule.resource_value)
+        for config_rule in device_data.device_config.config_rules
+    ]
+    LOGGER.info('device_data.device_config.config_rules = \n{:s}'.format(
+        '\n'.join(['{:s} {:s} = {:s}'.format(*config_rule) for config_rule in config_rules])))
+    for config_rule in DEVICE_MICROWAVE_CONFIG_RULES:
+        #import pdb;
+        #pdb. set_trace()
+        config_rule = (
+            ConfigActionEnum.Name(config_rule['action']), config_rule['resource_key'], config_rule['resource_value'])
+        assert config_rule in config_rules
+
+
+def test_device_microwave_deconfigure(
+    context_client: ContextClient,      # pylint: disable=redefined-outer-name
+    device_client: DeviceClient,        # pylint: disable=redefined-outer-name
+    device_service: DeviceService):     # pylint: disable=redefined-outer-name
+
+    if not ENABLE_MICROWAVE: pytest.skip('Skipping test: No MICROWAVE device has been configured')
+
+    driver: _Driver = device_service.driver_instance_cache.get(DEVICE_MICROWAVE_UUID)
+    assert driver is not None
+
+    # Requires to retrieve data from device; might be slow. Uncomment only when needed and test does not pass directly.
+    #driver_config = sorted(driver.GetConfig(), key=operator.itemgetter(0))
+    #LOGGER.info('driver_config = {:s}'.format(str(driver_config)))
+
+    DEVICE_MICROWAVE_WITH_DECONFIG_RULES = copy.deepcopy(DEVICE_MICROWAVE)
+    DEVICE_MICROWAVE_WITH_DECONFIG_RULES['device_config']['config_rules'].extend(DEVICE_MICROWAVE_DECONFIG_RULES)
+    device_client.ConfigureDevice(Device(**DEVICE_MICROWAVE_WITH_DECONFIG_RULES))
+
+    # Requires to retrieve data from device; might be slow. Uncomment only when needed and test does not pass directly.
+    #driver_config = sorted(driver.GetConfig(), key=operator.itemgetter(0))
+    #LOGGER.info('driver_config = {:s}'.format(str(driver_config)))
+
+    device_data = context_client.GetDevice(DeviceId(**DEVICE_MICROWAVE_ID))
+    config_rules = [
+        (ConfigActionEnum.Name(config_rule.action), config_rule.resource_key, config_rule.resource_value)
+        for config_rule in device_data.device_config.config_rules
+    ]
+    LOGGER.info('device_data.device_config.config_rules = \n{:s}'.format(
+        '\n'.join(['{:s} {:s} = {:s}'.format(*config_rule) for config_rule in config_rules])))
+    for config_rule in DEVICE_MICROWAVE_DECONFIG_RULES:
+        action_set = ConfigActionEnum.Name(ConfigActionEnum.CONFIGACTION_SET)
+        config_rule = (action_set, config_rule['resource_key'], config_rule['resource_value'])
+        assert config_rule not in config_rules
+
+
+def test_device_microwave_delete(
+    device_client : DeviceClient,       # pylint: disable=redefined-outer-name
+    device_service : DeviceService):    # pylint: disable=redefined-outer-name
+
+    if not ENABLE_MICROWAVE: pytest.skip('Skipping test: No MICROWAVE device has been configured')
+
+    device_client.DeleteDevice(DeviceId(**DEVICE_MICROWAVE_ID))
+    driver : _Driver = device_service.driver_instance_cache.get(DEVICE_MICROWAVE_UUID, {})
+    assert driver is None
\ No newline at end of file