From 48bd04d3247af21f1827f06d96085e9b7ba7b770 Mon Sep 17 00:00:00 2001
From: Lluis Gifre <lluis.gifre@cttc.es>
Date: Wed, 16 Feb 2022 17:50:38 +0000
Subject: [PATCH] Resolved some Device OpenConfigDriver issues

---
 run_tests_locally.sh                          |   3 +
 .../service/DeviceServiceServicerImpl.py      |  50 +--
 src/device/service/driver_api/_Driver.py      |  11 -
 .../drivers/emulated/EmulatedDriver.py        |   5 +-
 .../drivers/openconfig/OpenConfigDriver.py    | 299 +++++++++++-------
 .../openconfig/templates/Interfaces.py        |  86 +++--
 .../openconfig/templates/NetworkInstances.py  |   4 +-
 .../drivers/openconfig/templates/__init__.py  |  19 +-
 .../interface/subinterface/edit_config.xml    |  20 +-
 .../interface/state/counters/get.xml          |   7 +
 .../state/counters/in-octets/get.xml          |   9 +
 .../interface/state/counters/in-pkts/get.xml  |   9 +
 .../state/counters/out-octets/get.xml         |   9 +
 .../interface/state/counters/out-pkts/get.xml |   9 +
 .../network_instance/edit_config.xml          |  11 +-
 .../interface/edit_config.xml                 |   8 +-
 src/device/tests/.gitignore                   |   5 +-
 src/device/tests/test_unitary.py              | 155 ++++++++-
 .../service/piped/0demo/log_tcp_temp_complete |  21 ++
 .../L3NMOpenConfigServiceHandler.py           |  19 +-
 20 files changed, 525 insertions(+), 234 deletions(-)
 create mode 100644 src/device/service/drivers/openconfig/templates/interfaces/interface/state/counters/get.xml
 create mode 100644 src/device/service/drivers/openconfig/templates/interfaces/interface/state/counters/in-octets/get.xml
 create mode 100644 src/device/service/drivers/openconfig/templates/interfaces/interface/state/counters/in-pkts/get.xml
 create mode 100644 src/device/service/drivers/openconfig/templates/interfaces/interface/state/counters/out-octets/get.xml
 create mode 100644 src/device/service/drivers/openconfig/templates/interfaces/interface/state/counters/out-pkts/get.xml

diff --git a/run_tests_locally.sh b/run_tests_locally.sh
index 6e2f3afa2..4acd45251 100755
--- a/run_tests_locally.sh
+++ b/run_tests_locally.sh
@@ -36,6 +36,9 @@ export INFLUXDB_DATABASE=$(kubectl --namespace $K8S_NAMESPACE get secrets influx
 # First destroy old coverage file
 rm -f $COVERAGEFILE
 
+# 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 --log-level=INFO --verbose \
     common/orm/tests/test_unitary.py \
     common/message_broker/tests/test_unitary.py \
diff --git a/src/device/service/DeviceServiceServicerImpl.py b/src/device/service/DeviceServiceServicerImpl.py
index b449b0547..6220c48ee 100644
--- a/src/device/service/DeviceServiceServicerImpl.py
+++ b/src/device/service/DeviceServiceServicerImpl.py
@@ -401,30 +401,32 @@ class DeviceServiceServicerImpl(DeviceServiceServicer):
             # requests.
             #self.monitoring_loops.remove(device_uuid)
 
-        running_config_rules = [
-            (config_rule[0], json.dumps(config_rule[1], sort_keys=True))
-            for config_rule in driver.GetConfig()
-        ]
-        context_config_rules = {
-            config_rule[1]: config_rule[2]
-            for config_rule in get_config_rules(self.database, device_uuid, 'running')
-        }
-
-        # each in context, not in running => delete in context
-        # each in running, not in context => add to context
-        # each in context and in running, context.value != running.value => update in context
-
-        running_config_rules_actions : List[Tuple[ORM_ConfigActionEnum, str, str]] = []
-        for config_rule_key,config_rule_value in running_config_rules:
-            running_config_rules_actions.append((ORM_ConfigActionEnum.SET, config_rule_key, config_rule_value))
-            context_config_rules.pop(config_rule_key, None)
-        for context_rule_key,context_rule_value in context_config_rules.items():
-            running_config_rules_actions.append((ORM_ConfigActionEnum.DELETE, context_rule_key, context_rule_value))
-
-        #msg = '[MonitorDeviceKpi] running_config_rules_action[{:d}]: {:s}'
-        #for i,running_config_rules_action in enumerate(running_config_rules_actions):
-        #    LOGGER.info(msg.format(i, str(running_config_rules_action)))
-        update_config(self.database, device_uuid, 'running', running_config_rules_actions)
+        # Subscriptions are not stored as classical driver config.
+        # TODO: consider adding it somehow in the configuration.
+        # Warning: GetConfig might be very slow in OpenConfig devices
+        #running_config_rules = [
+        #    (config_rule[0], json.dumps(config_rule[1], sort_keys=True))
+        #    for config_rule in driver.GetConfig()
+        #]
+        #context_config_rules = {
+        #    config_rule[1]: config_rule[2]
+        #    for config_rule in get_config_rules(self.database, device_uuid, 'running')
+        #}
+
+        ## each in context, not in running => delete in context
+        ## each in running, not in context => add to context
+        ## each in context and in running, context.value != running.value => update in context
+        #running_config_rules_actions : List[Tuple[ORM_ConfigActionEnum, str, str]] = []
+        #for config_rule_key,config_rule_value in running_config_rules:
+        #    running_config_rules_actions.append((ORM_ConfigActionEnum.SET, config_rule_key, config_rule_value))
+        #    context_config_rules.pop(config_rule_key, None)
+        #for context_rule_key,context_rule_value in context_config_rules.items():
+        #    running_config_rules_actions.append((ORM_ConfigActionEnum.DELETE, context_rule_key, context_rule_value))
+
+        ##msg = '[MonitorDeviceKpi] running_config_rules_action[{:d}]: {:s}'
+        ##for i,running_config_rules_action in enumerate(running_config_rules_actions):
+        ##    LOGGER.info(msg.format(i, str(running_config_rules_action)))
+        #update_config(self.database, device_uuid, 'running', running_config_rules_actions)
 
         sync_device_to_context(db_device, self.context_client)
         return Empty()
diff --git a/src/device/service/driver_api/_Driver.py b/src/device/service/driver_api/_Driver.py
index b52cf6498..b214e6ede 100644
--- a/src/device/service/driver_api/_Driver.py
+++ b/src/device/service/driver_api/_Driver.py
@@ -57,17 +57,6 @@ class _Driver:
         """
         raise NotImplementedError()
 
-    #def GetResource(self, endpoint_uuid : str) -> Optional[str]:
-    #    """ Retrieve the endpoint path for subscriptions.
-    #        Parameters:
-    #            endpoint_uuid : str
-    #                Target endpoint UUID
-    #        Returns:
-    #            resource_path : Optional[str]
-    #                The path of the endpoint, or None if it is not found.
-    #    """
-    #    raise NotImplementedError()
-
     def SetConfig(self, resources : List[Tuple[str, Any]]) -> List[Union[bool, Exception]]:
         """ Create/Update configuration for a list of resources.
             Parameters:
diff --git a/src/device/service/drivers/emulated/EmulatedDriver.py b/src/device/service/drivers/emulated/EmulatedDriver.py
index 1154e4047..99039144e 100644
--- a/src/device/service/drivers/emulated/EmulatedDriver.py
+++ b/src/device/service/drivers/emulated/EmulatedDriver.py
@@ -113,6 +113,7 @@ class EmulatedDriver(_Driver):
         self.__lock = threading.Lock()
         self.__initial = TreeNode('.')
         self.__running = TreeNode('.')
+        self.__subscriptions = TreeNode('.')
 
         endpoints = settings.get('endpoints', [])
         endpoint_resources = []
@@ -286,7 +287,7 @@ class EmulatedDriver(_Driver):
                     end_date=end_date, timezone=pytz.utc)
 
                 subscription_path = resource_path + ['{:.3f}:{:.3f}'.format(sampling_duration, sampling_interval)]
-                set_subnode_value(resolver, self.__running, subscription_path, job)
+                set_subnode_value(resolver, self.__subscriptions, subscription_path, job)
                 results.append(True)
         return results
 
@@ -312,7 +313,7 @@ class EmulatedDriver(_Driver):
                     continue
 
                 subscription_path = resource_path + ['{:.3f}:{:.3f}'.format(sampling_duration, sampling_interval)]
-                subscription_node = get_subnode(resolver, self.__running, subscription_path)
+                subscription_node = get_subnode(resolver, self.__subscriptions, subscription_path)
 
                 # if not found, resource_node is None
                 if subscription_node is None:
diff --git a/src/device/service/drivers/openconfig/OpenConfigDriver.py b/src/device/service/drivers/openconfig/OpenConfigDriver.py
index e2381d81e..5d9717ab3 100644
--- a/src/device/service/drivers/openconfig/OpenConfigDriver.py
+++ b/src/device/service/drivers/openconfig/OpenConfigDriver.py
@@ -1,25 +1,84 @@
-import logging, pytz, queue, threading
-#from datetime import datetime, timedelta
-from typing import Any, List, Tuple, Union
-#from apscheduler.executors.pool import ThreadPoolExecutor
-#from apscheduler.job import Job
-#from apscheduler.jobstores.memory import MemoryJobStore
-#from apscheduler.schedulers.background import BackgroundScheduler
+import anytree, logging, pytz, queue, re, threading
+import lxml.etree as ET
+from datetime import datetime, timedelta
+from typing import Any, Dict, Iterator, List, Optional, Tuple, Union
+from apscheduler.executors.pool import ThreadPoolExecutor
+from apscheduler.job import Job
+from apscheduler.jobstores.memory import MemoryJobStore
+from apscheduler.schedulers.background import BackgroundScheduler
 from netconf_client.connect import connect_ssh
 from netconf_client.ncclient import Manager
-from common.type_checkers.Checkers import chk_length, chk_string, chk_type
+from common.type_checkers.Checkers import chk_length, chk_string, chk_type, chk_float
 from device.service.driver_api.Exceptions import UnsupportedResourceKeyException
 from device.service.driver_api._Driver import _Driver
-from device.service.driver_api.AnyTreeTools import set_subnode_value
+from device.service.driver_api.AnyTreeTools import TreeNode, dump_subtree, get_subnode, set_subnode_value
 from device.service.drivers.openconfig.Tools import xml_pretty_print, xml_to_dict, xml_to_file
 from device.service.drivers.openconfig.templates import ALL_RESOURCE_KEYS, compose_config, get_filter, parse
 
-#logging.getLogger('ncclient.transport.ssh').setLevel(logging.WARNING)
+DEBUG_MODE = False
+#logging.getLogger('ncclient.transport.ssh').setLevel(logging.DEBUG if DEBUG_MODE else logging.WARNING)
+logging.getLogger('apscheduler.executors.default').setLevel(logging.INFO if DEBUG_MODE else logging.ERROR)
+logging.getLogger('apscheduler.scheduler').setLevel(logging.INFO if DEBUG_MODE else logging.ERROR)
+logging.getLogger('monitoring-client').setLevel(logging.INFO if DEBUG_MODE else logging.ERROR)
 
 LOGGER = logging.getLogger(__name__)
 
-#def do_sampling(resource_key : str, out_samples : queue.Queue):
-#    out_samples.put_nowait((datetime.timestamp(datetime.utcnow()), resource_key, random.random()))
+RE_GET_ENDPOINT_FROM_INTERFACE_KEY = re.compile(r'.*interface\[([^\]]+)\].*')
+RE_GET_ENDPOINT_FROM_INTERFACE_XPATH = re.compile(r".*interface\[oci\:name\='([^\]]+)'\].*")
+
+# Collection of samples through NetConf is very slow and each request collects all the data.
+# Populate a cache periodically (when first interface is interrogated).
+# Evict data after some seconds, when data is considered as outdated
+
+SAMPLE_EVICTION_SECONDS = 30.0 # seconds
+SAMPLE_RESOURCE_KEY = 'interfaces/interface/state/counters'
+
+class SamplesCache:
+    def __init__(self) -> None:
+        self.__lock = threading.Lock()
+        self.__timestamp = None
+        self.__samples = {}
+
+    def _refresh_samples(self, netconf_manager : Manager) -> None:
+        with self.__lock:
+            try:
+                now = datetime.timestamp(datetime.utcnow())
+                if self.__timestamp is not None and (now - self.__timestamp) < SAMPLE_EVICTION_SECONDS: return
+                str_filter = get_filter(SAMPLE_RESOURCE_KEY)
+                xml_data = netconf_manager.get(filter=str_filter).data_ele
+                interface_samples = parse(SAMPLE_RESOURCE_KEY, xml_data)
+                for interface,samples in interface_samples:
+                    match = RE_GET_ENDPOINT_FROM_INTERFACE_KEY.match(interface)
+                    if match is None: continue
+                    interface = match.group(1)
+                    self.__samples[interface] = samples
+                self.__timestamp = now
+            except: # pylint: disable=bare-except
+                LOGGER.exception('Error collecting samples')
+
+    def get(self, resource_key : str, netconf_manager : Manager) -> Tuple[float, Dict]:
+        self._refresh_samples(netconf_manager)
+        match = RE_GET_ENDPOINT_FROM_INTERFACE_XPATH.match(resource_key)
+        with self.__lock:
+            if match is None: return self.__timestamp, {}
+            interface = match.group(1)
+            return self.__timestamp, self.__samples.get(interface, {})
+
+def do_sampling(
+    netconf_manager : Manager, samples_cache : SamplesCache, resource_key : str, out_samples : queue.Queue
+) -> None:
+    try:
+        timestamp, samples = samples_cache.get(resource_key, netconf_manager)
+        counter_name = resource_key.split('/')[-1].split(':')[-1]
+        value = samples.get(counter_name)
+        if value is None:
+            LOGGER.warning('[do_sampling] value not found for {:s}'.format(resource_key))
+            return
+        sample = (timestamp, resource_key, value)
+        out_samples.put_nowait(sample)
+    except: # pylint: disable=bare-except
+        LOGGER.exception('Error retrieving samples')
+
 
 class OpenConfigDriver(_Driver):
     def __init__(self, address : str, port : int, **settings) -> None: # pylint: disable=super-init-not-called
@@ -29,16 +88,18 @@ class OpenConfigDriver(_Driver):
         self.__lock = threading.Lock()
         #self.__initial = TreeNode('.')
         #self.__running = TreeNode('.')
+        self.__subscriptions = TreeNode('.')
         self.__started = threading.Event()
         self.__terminate = threading.Event()
         self.__netconf_manager : Manager = None
-        #self.__scheduler = BackgroundScheduler(daemon=True) # scheduler used to emulate sampling events
-        #self.__scheduler.configure(
-        #    jobstores = {'default': MemoryJobStore()},
-        #    executors = {'default': ThreadPoolExecutor(max_workers=1)},
-        #    job_defaults = {'coalesce': False, 'max_instances': 3},
-        #    timezone=pytz.utc)
-        #self.__out_samples = queue.Queue()
+        self.__scheduler = BackgroundScheduler(daemon=True) # scheduler used to emulate sampling events
+        self.__scheduler.configure(
+            jobstores = {'default': MemoryJobStore()},
+            executors = {'default': ThreadPoolExecutor(max_workers=1)},
+            job_defaults = {'coalesce': False, 'max_instances': 3},
+            timezone=pytz.utc)
+        self.__out_samples = queue.Queue()
+        self.__samples_cache = SamplesCache()
 
     def Connect(self) -> bool:
         with self.__lock:
@@ -49,9 +110,9 @@ class OpenConfigDriver(_Driver):
             session = connect_ssh(
                 host=self.__address, port=self.__port, username=username, password=password)
             self.__netconf_manager = Manager(session, timeout=timeout)
-            self.__netconf_manager.set_logger_level(logging.DEBUG)
+            self.__netconf_manager.set_logger_level(logging.DEBUG if DEBUG_MODE else logging.WARNING)
             # Connect triggers activation of sampling events that will be scheduled based on subscriptions
-            #self.__scheduler.start()
+            self.__scheduler.start()
             self.__started.set()
             return True
 
@@ -62,7 +123,7 @@ class OpenConfigDriver(_Driver):
             # If not started, assume it is already disconnected
             if not self.__started.is_set(): return True
             # Disconnect triggers deactivation of sampling events
-            #self.__scheduler.shutdown()
+            self.__scheduler.shutdown()
             self.__netconf_manager.close_session()
             return True
 
@@ -89,28 +150,23 @@ class OpenConfigDriver(_Driver):
                     results.append((resource_key, e)) # if validation fails, store the exception
         return results
 
-    #def GetResource(self, endpoint_uuid : str) -> Optional[str]:
-    #    chk_string('endpoint_uuid', endpoint_uuid)
-    #    return {
-    #        #'key': 'value',
-    #    }.get(endpoint_uuid)
-
     def SetConfig(self, resources : List[Tuple[str, Any]]) -> List[Union[bool, Exception]]:
         chk_type('resources', resources, list)
         if len(resources) == 0: return []
         results = []
+        LOGGER.info('[SetConfig] resources = {:s}'.format(str(resources)))
         with self.__lock:
             for i,resource in enumerate(resources):
                 str_resource_name = 'resources[#{:d}]'.format(i)
                 try:
-                    #LOGGER.info('resource = {:s}'.format(str(resource)))
+                    LOGGER.info('[SetConfig] resource = {:s}'.format(str(resource)))
                     chk_type(str_resource_name, resource, (list, tuple))
                     chk_length(str_resource_name, resource, min_length=2, max_length=2)
                     resource_key,resource_value = resource
                     chk_string(str_resource_name + '.key', resource_key, allow_empty=False)
                     str_config_message = compose_config(resource_key, resource_value)
                     if str_config_message is None: raise UnsupportedResourceKeyException(resource_key)
-                    #LOGGER.info('str_config_message = {:s}'.format(str(str_config_message)))
+                    LOGGER.info('[SetConfig] str_config_message = {:s}'.format(str(str_config_message)))
                     self.__netconf_manager.edit_config(str_config_message, target='running')
                     results.append(True)
                 except Exception as e: # pylint: disable=broad-except
@@ -122,18 +178,19 @@ class OpenConfigDriver(_Driver):
         chk_type('resources', resources, list)
         if len(resources) == 0: return []
         results = []
+        LOGGER.info('[DeleteConfig] resources = {:s}'.format(str(resources)))
         with self.__lock:
             for i,resource in enumerate(resources):
                 str_resource_name = 'resources[#{:d}]'.format(i)
                 try:
-                    #LOGGER.info('resource = {:s}'.format(str(resource)))
+                    LOGGER.info('[DeleteConfig] resource = {:s}'.format(str(resource)))
                     chk_type(str_resource_name, resource, (list, tuple))
                     chk_length(str_resource_name, resource, min_length=2, max_length=2)
                     resource_key,resource_value = resource
                     chk_string(str_resource_name + '.key', resource_key, allow_empty=False)
                     str_config_message = compose_config(resource_key, resource_value, delete=True)
                     if str_config_message is None: raise UnsupportedResourceKeyException(resource_key)
-                    #LOGGER.info('str_config_message = {:s}'.format(str(str_config_message)))
+                    LOGGER.info('[DeleteConfig] str_config_message = {:s}'.format(str(str_config_message)))
                     self.__netconf_manager.edit_config(str_config_message, target='running')
                     results.append(True)
                 except Exception as e: # pylint: disable=broad-except
@@ -141,91 +198,93 @@ class OpenConfigDriver(_Driver):
                     results.append(e) # if validation fails, store the exception
         return results
 
-#    def SubscribeState(self, subscriptions : List[Tuple[str, float, float]]) -> List[Union[bool, Exception]]:
-#        chk_type('subscriptions', subscriptions, list)
-#        if len(subscriptions) == 0: return []
-#        results = []
-#        resolver = anytree.Resolver(pathattr='name')
-#        with self.__lock:
-#            for i,subscription in enumerate(subscriptions):
-#                str_subscription_name = 'subscriptions[#{:d}]'.format(i)
-#                try:
-#                    chk_type(str_subscription_name, subscription, (list, tuple))
-#                    chk_length(str_subscription_name, subscription, min_length=3, max_length=3)
-#                    resource_key,sampling_duration,sampling_interval = subscription
-#                    chk_string(str_subscription_name + '.resource_key', resource_key, allow_empty=False)
-#                    resource_path = resource_key.split('/')
-#                    chk_float(str_subscription_name + '.sampling_duration', sampling_duration, min_value=0)
-#                    chk_float(str_subscription_name + '.sampling_interval', sampling_interval, min_value=0)
-#                except Exception as e: # pylint: disable=broad-except
-#                    LOGGER.exception('Exception validating {:s}: {:s}'.format(str_subscription_name, str(resource_key)))
-#                    results.append(e) # if validation fails, store the exception
-#                    continue
-#
-#                start_date,end_date = None,None
-#                if sampling_duration <= 1.e-12:
-#                    start_date = datetime.utcnow()
-#                    end_date = start_date + timedelta(seconds=sampling_duration)
-#
-#                job_id = 'k={:s}/d={:f}/i={:f}'.format(resource_key, sampling_duration, sampling_interval)
-#                job = self.__scheduler.add_job(
-#                    do_sampling, args=(resource_key, self.__out_samples), kwargs={},
-#                    id=job_id, trigger='interval', seconds=sampling_interval,
-#                    start_date=start_date, end_date=end_date, timezone=pytz.utc)
-#
-#                subscription_path = resource_path + ['{:.3f}:{:.3f}'.format(sampling_duration, sampling_interval)]
-#                set_subnode_value(resolver, self.__running, subscription_path, job)
-#                results.append(True)
-#        return results
-#
-#    def UnsubscribeState(self, subscriptions : List[Tuple[str, float, float]]) -> List[Union[bool, Exception]]:
-#        chk_type('subscriptions', subscriptions, list)
-#        if len(subscriptions) == 0: return []
-#        results = []
-#        resolver = anytree.Resolver(pathattr='name')
-#        with self.__lock:
-#            for i,resource in enumerate(subscriptions):
-#                str_subscription_name = 'resources[#{:d}]'.format(i)
-#                try:
-#                    chk_type(str_subscription_name, resource, (list, tuple))
-#                    chk_length(str_subscription_name, resource, min_length=3, max_length=3)
-#                    resource_key,sampling_duration,sampling_interval = resource
-#                    chk_string(str_subscription_name + '.resource_key', resource_key, allow_empty=False)
-#                    resource_path = resource_key.split('/')
-#                    chk_float(str_subscription_name + '.sampling_duration', sampling_duration, min_value=0)
-#                    chk_float(str_subscription_name + '.sampling_interval', sampling_interval, min_value=0)
-#                except Exception as e: # pylint: disable=broad-except
-#                    LOGGER.exception('Exception validating {:s}: {:s}'.format(str_subscription_name, str(resource_key)))
-#                    results.append(e) # if validation fails, store the exception
-#                    continue
-#
-#                subscription_path = resource_path + ['{:.3f}:{:.3f}'.format(sampling_duration, sampling_interval)]
-#                subscription_node = get_subnode(resolver, self.__running, subscription_path)
-#
-#                # if not found, resource_node is None
-#                if subscription_node is None:
-#                    results.append(False)
-#                    continue
-#
-#                job : Job = getattr(subscription_node, 'value', None)
-#                if job is None or not isinstance(job, Job):
-#                    raise Exception('Malformed subscription node or wrong resource key: {:s}'.format(str(resource)))
-#                job.remove()
-#
-#                parent = subscription_node.parent
-#                children = list(parent.children)
-#                children.remove(subscription_node)
-#                parent.children = tuple(children)
-#
-#                results.append(True)
-#        return results
-#
-#    def GetState(self, blocking=False) -> Iterator[Tuple[str, Any]]:
-#        while not self.__terminate.is_set():
-#            try:
-#                sample = self.__out_samples.get(block=blocking, timeout=0.1)
-#            except queue.Empty:
-#                if blocking: continue
-#                return
-#            if sample is None: continue
-#            yield sample
+    def SubscribeState(self, subscriptions : List[Tuple[str, float, float]]) -> List[Union[bool, Exception]]:
+        chk_type('subscriptions', subscriptions, list)
+        if len(subscriptions) == 0: return []
+        results = []
+        resolver = anytree.Resolver(pathattr='name')
+        with self.__lock:
+            for i,subscription in enumerate(subscriptions):
+                str_subscription_name = 'subscriptions[#{:d}]'.format(i)
+                try:
+                    chk_type(str_subscription_name, subscription, (list, tuple))
+                    chk_length(str_subscription_name, subscription, min_length=3, max_length=3)
+                    resource_key,sampling_duration,sampling_interval = subscription
+                    chk_string(str_subscription_name + '.resource_key', resource_key, allow_empty=False)
+                    resource_path = resource_key.split('/')
+                    chk_float(str_subscription_name + '.sampling_duration', sampling_duration, min_value=0)
+                    chk_float(str_subscription_name + '.sampling_interval', sampling_interval, min_value=0)
+                except Exception as e: # pylint: disable=broad-except
+                    LOGGER.exception('Exception validating {:s}: {:s}'.format(str_subscription_name, str(resource_key)))
+                    results.append(e) # if validation fails, store the exception
+                    continue
+
+                start_date,end_date = None,None
+                if sampling_duration <= 1.e-12:
+                    start_date = datetime.utcnow()
+                    end_date = start_date + timedelta(seconds=sampling_duration)
+
+                job_id = 'k={:s}/d={:f}/i={:f}'.format(resource_key, sampling_duration, sampling_interval)
+                job = self.__scheduler.add_job(
+                    do_sampling, args=(self.__netconf_manager, self.__samples_cache, resource_key, self.__out_samples),
+                    kwargs={}, id=job_id, trigger='interval', seconds=sampling_interval,
+                    start_date=start_date, end_date=end_date, timezone=pytz.utc)
+
+                subscription_path = resource_path + ['{:.3f}:{:.3f}'.format(sampling_duration, sampling_interval)]
+                set_subnode_value(resolver, self.__subscriptions, subscription_path, job)
+                results.append(True)
+        return results
+
+    def UnsubscribeState(self, subscriptions : List[Tuple[str, float, float]]) -> List[Union[bool, Exception]]:
+        chk_type('subscriptions', subscriptions, list)
+        if len(subscriptions) == 0: return []
+        results = []
+        resolver = anytree.Resolver(pathattr='name')
+        with self.__lock:
+            for i,resource in enumerate(subscriptions):
+                str_subscription_name = 'resources[#{:d}]'.format(i)
+                try:
+                    chk_type(str_subscription_name, resource, (list, tuple))
+                    chk_length(str_subscription_name, resource, min_length=3, max_length=3)
+                    resource_key,sampling_duration,sampling_interval = resource
+                    chk_string(str_subscription_name + '.resource_key', resource_key, allow_empty=False)
+                    resource_path = resource_key.split('/')
+                    chk_float(str_subscription_name + '.sampling_duration', sampling_duration, min_value=0)
+                    chk_float(str_subscription_name + '.sampling_interval', sampling_interval, min_value=0)
+                except Exception as e: # pylint: disable=broad-except
+                    LOGGER.exception('Exception validating {:s}: {:s}'.format(str_subscription_name, str(resource_key)))
+                    results.append(e) # if validation fails, store the exception
+                    continue
+
+                subscription_path = resource_path + ['{:.3f}:{:.3f}'.format(sampling_duration, sampling_interval)]
+                subscription_node = get_subnode(resolver, self.__subscriptions, subscription_path)
+
+                # if not found, resource_node is None
+                if subscription_node is None:
+                    results.append(False)
+                    continue
+
+                job : Job = getattr(subscription_node, 'value', None)
+                if job is None or not isinstance(job, Job):
+                    raise Exception('Malformed subscription node or wrong resource key: {:s}'.format(str(resource)))
+                job.remove()
+
+                parent = subscription_node.parent
+                children = list(parent.children)
+                children.remove(subscription_node)
+                parent.children = tuple(children)
+
+                results.append(True)
+        return results
+
+    def GetState(self, blocking=False, terminate : Optional[threading.Event] = None) -> Iterator[Tuple[str, Any]]:
+        while True:
+            if self.__terminate.is_set(): break
+            if terminate is not None and terminate.is_set(): break
+            try:
+                sample = self.__out_samples.get(block=blocking, timeout=0.1)
+            except queue.Empty:
+                if blocking: continue
+                return
+            if sample is None: continue
+            yield sample
diff --git a/src/device/service/drivers/openconfig/templates/Interfaces.py b/src/device/service/drivers/openconfig/templates/Interfaces.py
index 04d03398a..0c7dd2f1f 100644
--- a/src/device/service/drivers/openconfig/templates/Interfaces.py
+++ b/src/device/service/drivers/openconfig/templates/Interfaces.py
@@ -12,7 +12,7 @@ XPATH_IPV4ADDRESSES = ".//ociip:ipv4/ociip:addresses/ociip:address"
 def parse(xml_data : ET.Element) -> List[Tuple[str, Dict[str, Any]]]:
     response = []
     for xml_interface in xml_data.xpath(XPATH_INTERFACES, namespaces=NAMESPACES):
-        LOGGER.info('xml_interface = {:s}'.format(str(ET.tostring(xml_interface))))
+        #LOGGER.info('xml_interface = {:s}'.format(str(ET.tostring(xml_interface))))
 
         interface = {}
 
@@ -24,13 +24,13 @@ def parse(xml_data : ET.Element) -> List[Tuple[str, Dict[str, Any]]]:
         #add_value_from_tag(interface, 'type', interface_type)
 
         interface_mtu = xml_interface.find('oci:config/oci:mtu', namespaces=NAMESPACES)
-        add_value_from_tag(interface, 'mtu',interface_mtu)
+        add_value_from_tag(interface, 'mtu', interface_mtu, cast=int)
 
         interface_description = xml_interface.find('oci:config/oci:description', namespaces=NAMESPACES)
         add_value_from_tag(interface, 'description', interface_description)
 
         for xml_subinterface in xml_interface.xpath(XPATH_SUBINTERFACES, namespaces=NAMESPACES):
-            LOGGER.info('xml_subinterface = {:s}'.format(str(ET.tostring(xml_subinterface))))
+            #LOGGER.info('xml_subinterface = {:s}'.format(str(ET.tostring(xml_subinterface))))
 
             subinterface = {}
 
@@ -38,36 +38,78 @@ def parse(xml_data : ET.Element) -> List[Tuple[str, Dict[str, Any]]]:
 
             subinterface_index = xml_subinterface.find('oci:index', namespaces=NAMESPACES)
             if subinterface_index is None or subinterface_index.text is None: continue
-            add_value_from_tag(subinterface, 'index', subinterface_index)
+            add_value_from_tag(subinterface, 'index', subinterface_index, cast=int)
 
             vlan_id = xml_subinterface.find('ocv:vlan/ocv:config/ocv:vlan-id', namespaces=NAMESPACES)
             add_value_from_tag(subinterface, 'vlan_id', vlan_id, cast=int)
 
+            # TODO: implement support for multiple IP addresses per subinterface
             #ipv4_addresses = []
-            #for xml_ipv4_address in xml_subinterface.xpath(XPATH_IPV4ADDRESSES, namespaces=NAMESPACES):
-            #    LOGGER.info('xml_ipv4_address = {:s}'.format(str(ET.tostring(xml_ipv4_address))))
-            #
-            #    ipv4_address = {}
-            #
-            #    origin = xml_ipv4_address.find('ociip:state/ociip:origin', namespaces=NAMESPACES)
-            #    add_value_from_tag(ipv4_address, 'origin', origin)
-            #
-            #    address = xml_ipv4_address.find('ociip:state/ociip:ip', namespaces=NAMESPACES)
-            #    add_value_from_tag(ipv4_address, 'ip', address)
-            #
-            #    prefix = xml_ipv4_address.find('ociip:state/ociip:prefix-length', namespaces=NAMESPACES)
-            #    add_value_from_tag(ipv4_address, 'prefix_length', prefix)
-            #
-            #    if len(ipv4_address) == 0: continue
-            #    ipv4_addresses.append(ipv4_address)
-            #
+            for xml_ipv4_address in xml_subinterface.xpath(XPATH_IPV4ADDRESSES, namespaces=NAMESPACES):
+                #LOGGER.info('xml_ipv4_address = {:s}'.format(str(ET.tostring(xml_ipv4_address))))
+
+                #ipv4_address = {}
+
+                #origin = xml_ipv4_address.find('ociip:state/ociip:origin', namespaces=NAMESPACES)
+                #add_value_from_tag(ipv4_address, 'origin', origin)
+
+                address = xml_ipv4_address.find('ociip:state/ociip:ip', namespaces=NAMESPACES)
+                #add_value_from_tag(ipv4_address, 'ip', address)
+                add_value_from_tag(subinterface, 'address_ip', address)
+
+                prefix = xml_ipv4_address.find('ociip:state/ociip:prefix-length', namespaces=NAMESPACES)
+                #add_value_from_tag(ipv4_address, 'prefix_length', prefix)
+                add_value_from_tag(subinterface, 'address_prefix', prefix, cast=int)
+
+                #if len(ipv4_address) == 0: continue
+                #ipv4_addresses.append(ipv4_address)
+
             #add_value_from_collection(subinterface, 'ipv4_addresses', ipv4_addresses)
 
             if len(subinterface) == 0: continue
-            resource_key = 'interface[{:s}]/subinterface[{:s}]'.format(interface['name'], subinterface['index'])
+            resource_key = 'interface[{:s}]/subinterface[{:s}]'.format(interface['name'], str(subinterface['index']))
             response.append((resource_key, subinterface))
 
         if len(interface) == 0: continue
         response.append(('interface[{:s}]'.format(interface['name']), interface))
 
     return response
+
+def parse_counters(xml_data : ET.Element) -> List[Tuple[str, Dict[str, Any]]]:
+    response = []
+    for xml_interface in xml_data.xpath(XPATH_INTERFACES, namespaces=NAMESPACES):
+        #LOGGER.info('[parse_counters] xml_interface = {:s}'.format(str(ET.tostring(xml_interface))))
+
+        interface = {}
+
+        interface_name = xml_interface.find('oci:name', namespaces=NAMESPACES)
+        if interface_name is None or interface_name.text is None: continue
+        add_value_from_tag(interface, 'name', interface_name)
+
+        interface_in_pkts = xml_interface.find('oci:state/oci:counters/oci:in-pkts', namespaces=NAMESPACES)
+        add_value_from_tag(interface, 'in-pkts', interface_in_pkts, cast=int)
+
+        interface_in_octets = xml_interface.find('oci:state/oci:counters/oci:in-octets', namespaces=NAMESPACES)
+        add_value_from_tag(interface, 'in-octets', interface_in_octets, cast=int)
+
+        interface_in_errors = xml_interface.find('oci:state/oci:counters/oci:in-errors', namespaces=NAMESPACES)
+        add_value_from_tag(interface, 'in-errors', interface_in_errors, cast=int)
+
+        interface_out_octets = xml_interface.find('oci:state/oci:counters/oci:out-octets', namespaces=NAMESPACES)
+        add_value_from_tag(interface, 'out-octets', interface_out_octets, cast=int)
+
+        interface_out_pkts = xml_interface.find('oci:state/oci:counters/oci:out-pkts', namespaces=NAMESPACES)
+        add_value_from_tag(interface, 'out-pkts', interface_out_pkts, cast=int)
+
+        interface_out_errors = xml_interface.find('oci:state/oci:counters/oci:out-errors', namespaces=NAMESPACES)
+        add_value_from_tag(interface, 'out-errors', interface_out_errors, cast=int)
+
+        interface_out_discards = xml_interface.find('oci:state/oci:counters/oci:out-discards', namespaces=NAMESPACES)
+        add_value_from_tag(interface, 'out-discards', interface_out_discards, cast=int)
+
+        #LOGGER.info('[parse_counters] interface = {:s}'.format(str(interface)))
+
+        if len(interface) == 0: continue
+        response.append(('interface[{:s}]'.format(interface['name']), interface))
+
+    return response
diff --git a/src/device/service/drivers/openconfig/templates/NetworkInstances.py b/src/device/service/drivers/openconfig/templates/NetworkInstances.py
index b842a16a9..ef91e666e 100644
--- a/src/device/service/drivers/openconfig/templates/NetworkInstances.py
+++ b/src/device/service/drivers/openconfig/templates/NetworkInstances.py
@@ -21,8 +21,8 @@ def parse(xml_data : ET.Element) -> List[Tuple[str, Dict[str, Any]]]:
         ni_type = xml_network_instance.find('ocni:config/ocni:type', namespaces=NAMESPACES)
         add_value_from_tag(network_instance, 'type', ni_type)
 
-        ni_router_id = xml_network_instance.find('ocni:config/ocni:router-id', namespaces=NAMESPACES)
-        add_value_from_tag(network_instance, 'router_id', ni_router_id)
+        #ni_router_id = xml_network_instance.find('ocni:config/ocni:router-id', namespaces=NAMESPACES)
+        #add_value_from_tag(network_instance, 'router_id', ni_router_id)
 
         ni_route_dist = xml_network_instance.find('ocni:config/ocni:route-distinguisher', namespaces=NAMESPACES)
         add_value_from_tag(network_instance, 'route_distinguisher', ni_route_dist)
diff --git a/src/device/service/drivers/openconfig/templates/__init__.py b/src/device/service/drivers/openconfig/templates/__init__.py
index c1d145056..818661238 100644
--- a/src/device/service/drivers/openconfig/templates/__init__.py
+++ b/src/device/service/drivers/openconfig/templates/__init__.py
@@ -3,7 +3,7 @@ from typing import Any, Dict
 from jinja2 import Environment, PackageLoader, select_autoescape
 from device.service.driver_api._Driver import RESOURCE_ENDPOINTS, RESOURCE_INTERFACES, RESOURCE_NETWORK_INSTANCES
 from .EndPoints import parse as parse_endpoints
-from .Interfaces import parse as parse_interfaces
+from .Interfaces import parse as parse_interfaces, parse_counters
 from .NetworkInstances import parse as parse_network_instances
 
 ALL_RESOURCE_KEYS = [
@@ -22,20 +22,33 @@ RESOURCE_PARSERS = {
     'component'       : parse_endpoints,
     'interface'       : parse_interfaces,
     'network_instance': parse_network_instances,
+    'interfaces/interface/state/counters': parse_counters,
 }
 
 LOGGER = logging.getLogger(__name__)
 RE_REMOVE_FILTERS = re.compile(r'\[[^\]]+\]')
+RE_REMOVE_FILTERS_2 = re.compile(r'\/[a-z]+:')
 JINJA_ENV = Environment(loader=PackageLoader('device.service.drivers.openconfig'), autoescape=select_autoescape())
 
 def get_filter(resource_key : str):
     resource_key = RESOURCE_KEY_MAPPINGS.get(resource_key, resource_key)
-    template_name = '{:s}/get.xml'.format(RE_REMOVE_FILTERS.sub('', resource_key))
+    resource_key = RE_REMOVE_FILTERS.sub('', resource_key)
+    resource_key = RE_REMOVE_FILTERS_2.sub('/', resource_key)
+    resource_key = resource_key.replace('//', '')
+    template_name = '{:s}/get.xml'.format(resource_key)
     template = JINJA_ENV.get_template(template_name)
     return '<filter>{:s}</filter>'.format(template.render())
 
 def parse(resource_key : str, xml_data : ET.Element):
-    parser = RESOURCE_PARSERS.get(RESOURCE_KEY_MAPPINGS.get(resource_key, resource_key))
+    resource_key = RESOURCE_KEY_MAPPINGS.get(resource_key, resource_key)
+    resource_key = RE_REMOVE_FILTERS.sub('', resource_key)
+    resource_key = RE_REMOVE_FILTERS_2.sub('/', resource_key)
+    resource_key = resource_key.replace('//', '')
+    #resource_key_parts = resource_key.split('/')
+    #if len(resource_key_parts) > 1: resource_key_parts = resource_key_parts[:-1]
+    #resource_key = '/'.join(resource_key_parts)
+    #resource_key = RESOURCE_KEY_MAPPINGS.get(resource_key, resource_key)
+    parser = RESOURCE_PARSERS.get(resource_key)
     if parser is None: return [(resource_key, xml_data)]
     return parser(xml_data)
 
diff --git a/src/device/service/drivers/openconfig/templates/interface/subinterface/edit_config.xml b/src/device/service/drivers/openconfig/templates/interface/subinterface/edit_config.xml
index 6c1d2b06d..d266f819c 100644
--- a/src/device/service/drivers/openconfig/templates/interface/subinterface/edit_config.xml
+++ b/src/device/service/drivers/openconfig/templates/interface/subinterface/edit_config.xml
@@ -4,10 +4,6 @@
         {% if operation is not defined or operation != 'delete' %}
         <config>
             <name>{{name}}</name>
-            {% if 1==0 %}
-            <description>{{description}}</description>
-            <mtu>{{mtu}}</mtu>
-            {% endif %}
         </config>
         {% endif %}
         <subinterfaces>
@@ -16,13 +12,25 @@
                 {% if operation is not defined or operation != 'delete' %}
                 <config>
                     <index>{{index}}</index>
+                    <enabled>true</enabled>
                 </config>
-                {% endif %}
                 <vlan xmlns="http://openconfig.net/yang/vlan">
                     <config>
-                        <vlan-id>{{index}}</vlan-id>
+                        <vlan-id>{{vlan_id}}</vlan-id>
                     </config>
                 </vlan>
+                <ipv4 xmlns="http://openconfig.net/yang/interfaces/ip">
+                    <addresses>
+                        <address>
+                            <ip>{{address_ip}}</ip>
+                            <config>
+                                <ip>{{address_ip}}</ip>
+                                <prefix-length>{{address_prefix}}</prefix-length>
+                            </config>
+                        </address>
+                    </addresses>
+                </ipv4>
+                {% endif %}
             </subinterface>
         </subinterfaces>
     </interface>
diff --git a/src/device/service/drivers/openconfig/templates/interfaces/interface/state/counters/get.xml b/src/device/service/drivers/openconfig/templates/interfaces/interface/state/counters/get.xml
new file mode 100644
index 000000000..90145e25c
--- /dev/null
+++ b/src/device/service/drivers/openconfig/templates/interfaces/interface/state/counters/get.xml
@@ -0,0 +1,7 @@
+<interfaces xmlns="http://openconfig.net/yang/interfaces">
+  <interface>
+    <state>
+      <counters/>
+    </state>
+  </interface>
+</interfaces>
diff --git a/src/device/service/drivers/openconfig/templates/interfaces/interface/state/counters/in-octets/get.xml b/src/device/service/drivers/openconfig/templates/interfaces/interface/state/counters/in-octets/get.xml
new file mode 100644
index 000000000..83da4b223
--- /dev/null
+++ b/src/device/service/drivers/openconfig/templates/interfaces/interface/state/counters/in-octets/get.xml
@@ -0,0 +1,9 @@
+<interfaces xmlns="http://openconfig.net/yang/interfaces">
+  <interface>
+    <state>
+      <counters>
+        <in-octets></in-octets>
+      </counters>
+    </state>
+  </interface>
+</interfaces>
diff --git a/src/device/service/drivers/openconfig/templates/interfaces/interface/state/counters/in-pkts/get.xml b/src/device/service/drivers/openconfig/templates/interfaces/interface/state/counters/in-pkts/get.xml
new file mode 100644
index 000000000..645204d20
--- /dev/null
+++ b/src/device/service/drivers/openconfig/templates/interfaces/interface/state/counters/in-pkts/get.xml
@@ -0,0 +1,9 @@
+<interfaces xmlns="http://openconfig.net/yang/interfaces">
+  <interface>
+    <state>
+      <counters>
+        <in-pkts></in-pkts>
+      </counters>
+    </state>
+  </interface>
+</interfaces>
diff --git a/src/device/service/drivers/openconfig/templates/interfaces/interface/state/counters/out-octets/get.xml b/src/device/service/drivers/openconfig/templates/interfaces/interface/state/counters/out-octets/get.xml
new file mode 100644
index 000000000..28958ec6a
--- /dev/null
+++ b/src/device/service/drivers/openconfig/templates/interfaces/interface/state/counters/out-octets/get.xml
@@ -0,0 +1,9 @@
+<interfaces xmlns="http://openconfig.net/yang/interfaces">
+  <interface>
+    <state>
+      <counters>
+        <out-octets></out-octets>
+      </counters>
+    </state>
+  </interface>
+</interfaces>
diff --git a/src/device/service/drivers/openconfig/templates/interfaces/interface/state/counters/out-pkts/get.xml b/src/device/service/drivers/openconfig/templates/interfaces/interface/state/counters/out-pkts/get.xml
new file mode 100644
index 000000000..52c27410c
--- /dev/null
+++ b/src/device/service/drivers/openconfig/templates/interfaces/interface/state/counters/out-pkts/get.xml
@@ -0,0 +1,9 @@
+<interfaces xmlns="http://openconfig.net/yang/interfaces">
+  <interface>
+    <state>
+      <counters>
+        <out-pkts></out-pkts>
+      </counters>
+    </state>
+  </interface>
+</interfaces>
diff --git a/src/device/service/drivers/openconfig/templates/network_instance/edit_config.xml b/src/device/service/drivers/openconfig/templates/network_instance/edit_config.xml
index 6aa2f0735..1d8ad4480 100644
--- a/src/device/service/drivers/openconfig/templates/network_instance/edit_config.xml
+++ b/src/device/service/drivers/openconfig/templates/network_instance/edit_config.xml
@@ -5,16 +5,7 @@
         <config>
             <name>{{name}}</name>
             <type xmlns:oc-ni-types="http://openconfig.net/yang/network-instance-types">oc-ni-types:{{type}}</type>
-
-            {% if 1==0 %}
-            {% for address_family in address_families %}
-            <enabled-address-families xmlns:oc-types="http://openconfig.net/yang/openconfig-types">oc-types:{{address_family}}</enabled-address-families>
-            {% endfor %}
-            {% else %}
-            <description>PhiTst_1</description>
-            {% endif %}
-
-            <router-id>{{router_id}}</router-id>
+            <description>{{description}}</description>
             <route-distinguisher>{{route_distinguisher}}</route-distinguisher>
             <enabled>true</enabled>
         </config>
diff --git a/src/device/service/drivers/openconfig/templates/network_instance/interface/edit_config.xml b/src/device/service/drivers/openconfig/templates/network_instance/interface/edit_config.xml
index d678d1668..d5c33d31a 100644
--- a/src/device/service/drivers/openconfig/templates/network_instance/interface/edit_config.xml
+++ b/src/device/service/drivers/openconfig/templates/network_instance/interface/edit_config.xml
@@ -7,12 +7,8 @@
                 {% if operation is not defined or operation != 'delete' %}
                 <config>
                     <id>{{id}}</id>
-                    {% if 1==0 %}
-                    <interface>{{id}}</interface>
-                    {% else %}
-                    <interface>13/1/2</interface>
-                    <subinterface>1</subinterface>
-                    {% endif %}
+                    <interface>{{interface}}</interface>
+                    <subinterface>{{subinterface}}</subinterface>
                 </config>
                 {% endif %}
             </interface>
diff --git a/src/device/tests/.gitignore b/src/device/tests/.gitignore
index ddc03384a..b5f6bc13b 100644
--- a/src/device/tests/.gitignore
+++ b/src/device/tests/.gitignore
@@ -1,4 +1,3 @@
 # Add here your files containing confidential testbed details such as IP addresses, ports, usernames, passwords, etc.
-Device_OpenConfig_Infinera.py
-Device_OpenConfig_Infinera_TID.py
-Device_Transport_Api_CTTC.py
+Device_OpenConfig_Infinera*
+Device_Transport_Api*
diff --git a/src/device/tests/test_unitary.py b/src/device/tests/test_unitary.py
index 06f43671b..ab363a76e 100644
--- a/src/device/tests/test_unitary.py
+++ b/src/device/tests/test_unitary.py
@@ -36,9 +36,11 @@ 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_Infinera import(
+    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
@@ -62,9 +64,10 @@ try:
 except ImportError:
     ENABLE_P4 = False
 
-ENABLE_OPENCONFIG = False # uncomment to disable tests of OpenConfig devices
-ENABLE_TAPI       = False # uncomment to disable tests of TAPI devices
-ENABLE_P4         = False # uncomment to disable tests of P4 devices
+#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 (P4 device not available in GitLab)
 
 logging.getLogger('apscheduler.executors.default').setLevel(logging.WARNING)
 logging.getLogger('apscheduler.scheduler').setLevel(logging.WARNING)
@@ -182,6 +185,8 @@ def test_device_emulated_add_error_cases(
     device_client : DeviceClient,       # pylint: disable=redefined-outer-name
     device_service : DeviceService):    # pylint: disable=redefined-outer-name
 
+    if not ENABLE_EMULATED: pytest.skip('Skipping test: No Emulated device has been configured')
+
     with pytest.raises(grpc.RpcError) as e:
         DEVICE_EMU_WITH_ENDPOINTS = copy.deepcopy(DEVICE_EMU)
         DEVICE_EMU_WITH_ENDPOINTS['device_endpoints'].append(json_endpoint(DEVICE_EMU_ID, 'ep-id', 'ep-type'))
@@ -211,6 +216,8 @@ def test_device_emulated_add_correct(
     device_client : DeviceClient,       # pylint: disable=redefined-outer-name
     device_service : DeviceService):    # pylint: disable=redefined-outer-name
 
+    if not ENABLE_EMULATED: pytest.skip('Skipping test: No Emulated device has been configured')
+
     DEVICE_EMU_WITH_CONNECT_RULES = copy.deepcopy(DEVICE_EMU)
     DEVICE_EMU_WITH_CONNECT_RULES['device_config']['config_rules'].extend(DEVICE_EMU_CONNECT_RULES)
     device_client.AddDevice(Device(**DEVICE_EMU_WITH_CONNECT_RULES))
@@ -223,6 +230,8 @@ def test_device_emulated_get(
     device_client : DeviceClient,       # pylint: disable=redefined-outer-name
     device_service : DeviceService):    # pylint: disable=redefined-outer-name
 
+    if not ENABLE_EMULATED: pytest.skip('Skipping test: No Emulated device has been configured')
+
     initial_config = device_client.GetInitialConfig(DeviceId(**DEVICE_EMU_ID))
     LOGGER.info('initial_config = {:s}'.format(grpc_message_to_json_string(initial_config)))
 
@@ -235,6 +244,8 @@ def test_device_emulated_configure(
     device_client : DeviceClient,       # pylint: disable=redefined-outer-name
     device_service : DeviceService):    # pylint: disable=redefined-outer-name
 
+    if not ENABLE_EMULATED: pytest.skip('Skipping test: No Emulated device has been configured')
+
     driver : _Driver = device_service.driver_instance_cache.get(DEVICE_EMU_UUID) # we know the driver exists now
     assert driver is not None
 
@@ -257,7 +268,6 @@ def test_device_emulated_configure(
         DeviceOperationalStatusEnum.DEVICEOPERATIONALSTATUS_ENABLED
     device_client.ConfigureDevice(Device(**DEVICE_EMU_WITH_OPERATIONAL_STATUS))
 
-
     driver_config = sorted(driver.GetConfig(), key=operator.itemgetter(0))
     #LOGGER.info('driver_config = {:s}'.format(str(driver_config)))
     assert len(driver_config) == len(DEVICE_EMU_ENDPOINTS_COOKED) + len(DEVICE_EMU_CONFIG_ADDRESSES)
@@ -342,32 +352,38 @@ def test_device_emulated_monitor(
     device_service : DeviceService,                 # pylint: disable=redefined-outer-name
     monitoring_service : MockMonitoringService):    # pylint: disable=redefined-outer-name
 
-    #device_data = context_client.GetDevice(DeviceId(**DEVICE_EMU_ID))
+    if not ENABLE_EMULATED: pytest.skip('Skipping test: No Emulated device has been configured')
+
+    device_uuid = DEVICE_EMU_UUID
+    json_device_id = DEVICE_EMU_ID
+    device_id = DeviceId(**json_device_id)
+    device_data = context_client.GetDevice(device_id)
     #LOGGER.info('device_data = \n{:s}'.format(str(device_data)))
 
-    driver : _Driver = device_service.driver_instance_cache.get(DEVICE_EMU_UUID) # we know the driver exists now
+    driver : _Driver = device_service.driver_instance_cache.get(device_uuid) # we know the driver exists now
     assert driver is not None
-    driver_config = sorted(driver.GetConfig(), key=operator.itemgetter(0))
+    #driver_config = sorted(driver.GetConfig(), key=operator.itemgetter(0))
     #LOGGER.info('driver_config = {:s}'.format(str(driver_config)))
-    assert len(driver_config) == len(DEVICE_EMU_ENDPOINTS_COOKED) + len(DEVICE_EMU_CONFIG_ADDRESSES)
+    #assert len(driver_config) == len(DEVICE_EMU_ENDPOINTS_COOKED) + len(DEVICE_EMU_CONFIG_ADDRESSES)
 
     SAMPLING_DURATION_SEC = 10.0
     SAMPLING_INTERVAL_SEC = 2.0
 
     MONITORING_SETTINGS_LIST = []
     KPI_UUIDS__TO__NUM_SAMPLES_RECEIVED = {}
-    for endpoint_uuid,_,sample_types in DEVICE_EMU_EP_DESCS:
-        for sample_type_id in sample_types:
+    for endpoint in device_data.device_endpoints:
+        endpoint_uuid = endpoint.endpoint_id.endpoint_uuid.uuid
+        for sample_type_id in endpoint.kpi_sample_types:
             sample_type_name = str(KpiSampleType.Name(sample_type_id)).upper().replace('KPISAMPLETYPE_', '')
-            kpi_uuid = '{:s}-{:s}-{:s}-kpi_uuid'.format(DEVICE_EMU_UUID, endpoint_uuid, str(sample_type_id))
+            kpi_uuid = '{:s}-{:s}-{:s}-kpi_uuid'.format(device_uuid, endpoint_uuid, str(sample_type_id))
             monitoring_settings = {
                 'kpi_id'        : {'kpi_id': {'uuid': kpi_uuid}},
                 'kpi_descriptor': {
                     'kpi_description': 'Metric {:s} for Endpoint {:s} in Device {:s}'.format(
-                        sample_type_name, endpoint_uuid, DEVICE_EMU_UUID),
+                        sample_type_name, endpoint_uuid, device_uuid),
                     'kpi_sample_type': sample_type_id,
-                    'device_id': DEVICE_EMU_ID,
-                    'endpoint_id': json_endpoint_id(DEVICE_EMU_ID, endpoint_uuid),
+                    'device_id': json_device_id,
+                    'endpoint_id': json_endpoint_id(json_device_id, endpoint_uuid),
                 },
                 'sampling_duration_s': SAMPLING_DURATION_SEC,
                 'sampling_interval_s': SAMPLING_INTERVAL_SEC,
@@ -413,8 +429,9 @@ def test_device_emulated_monitor(
             timestamp = float(calendar.timegm(dt_time.timetuple())) + (dt_time.microsecond / 1.e6)
         assert timestamp > t_start_monitoring
         assert timestamp < t_end_monitoring
-        assert received_sample.kpi_value.HasField('floatVal')
-        assert isinstance(received_sample.kpi_value.floatVal, float)
+        assert received_sample.kpi_value.HasField('floatVal') or received_sample.kpi_value.HasField('intVal')
+        kpi_value = getattr(received_sample.kpi_value, received_sample.kpi_value.WhichOneof('value'))
+        assert isinstance(kpi_value, (float, int))
         KPI_UUIDS__TO__NUM_SAMPLES_RECEIVED[kpi_uuid] += 1
 
     LOGGER.info('KPI_UUIDS__TO__NUM_SAMPLES_RECEIVED = {:s}'.format(str(KPI_UUIDS__TO__NUM_SAMPLES_RECEIVED)))
@@ -436,6 +453,8 @@ def test_device_emulated_deconfigure(
     device_client : DeviceClient,       # pylint: disable=redefined-outer-name
     device_service : DeviceService):    # pylint: disable=redefined-outer-name
 
+    if not ENABLE_EMULATED: pytest.skip('Skipping test: No Emulated device has been configured')
+
     driver : _Driver = device_service.driver_instance_cache.get(DEVICE_EMU_UUID) # we know the driver exists now
     assert driver is not None
 
@@ -495,6 +514,8 @@ def test_device_emulated_delete(
     device_client : DeviceClient,       # pylint: disable=redefined-outer-name
     device_service : DeviceService):    # pylint: disable=redefined-outer-name
 
+    if not ENABLE_EMULATED: pytest.skip('Skipping test: No Emulated device has been configured')
+
     device_client.DeleteDevice(DeviceId(**DEVICE_EMU_ID))
     driver : _Driver = device_service.driver_instance_cache.get(DEVICE_EMU_UUID, {})
     assert driver is None
@@ -585,6 +606,106 @@ def test_device_openconfig_configure(
         assert config_rule in config_rules
 
 
+def test_device_openconfig_monitor(
+    context_client : ContextClient,                 # pylint: disable=redefined-outer-name
+    device_client : DeviceClient,                   # pylint: disable=redefined-outer-name
+    device_service : DeviceService,                 # pylint: disable=redefined-outer-name
+    monitoring_service : MockMonitoringService):    # pylint: disable=redefined-outer-name
+
+    if not ENABLE_OPENCONFIG: pytest.skip('Skipping test: No OpenConfig device has been configured')
+
+    device_uuid = DEVICE_OC_UUID
+    json_device_id = DEVICE_OC_ID
+    device_id = DeviceId(**json_device_id)
+    device_data = context_client.GetDevice(device_id)
+    #LOGGER.info('device_data = \n{:s}'.format(str(device_data)))
+
+    driver : _Driver = device_service.driver_instance_cache.get(device_uuid) # we know the driver exists now
+    assert driver is not None
+
+    SAMPLING_DURATION_SEC = 30.0
+    SAMPLING_INTERVAL_SEC = 10.0
+
+    MONITORING_SETTINGS_LIST = []
+    KPI_UUIDS__TO__NUM_SAMPLES_RECEIVED = {}
+    for endpoint in device_data.device_endpoints:
+        endpoint_uuid = endpoint.endpoint_id.endpoint_uuid.uuid
+        for sample_type_id in endpoint.kpi_sample_types:
+            sample_type_name = str(KpiSampleType.Name(sample_type_id)).upper().replace('KPISAMPLETYPE_', '')
+            kpi_uuid = '{:s}-{:s}-{:s}-kpi_uuid'.format(device_uuid, endpoint_uuid, str(sample_type_id))
+            monitoring_settings = {
+                'kpi_id'        : {'kpi_id': {'uuid': kpi_uuid}},
+                'kpi_descriptor': {
+                    'kpi_description': 'Metric {:s} for Endpoint {:s} in Device {:s}'.format(
+                        sample_type_name, endpoint_uuid, device_uuid),
+                    'kpi_sample_type': sample_type_id,
+                    'device_id': json_device_id,
+                    'endpoint_id': json_endpoint_id(json_device_id, endpoint_uuid),
+                },
+                'sampling_duration_s': SAMPLING_DURATION_SEC,
+                'sampling_interval_s': SAMPLING_INTERVAL_SEC,
+            }
+            MONITORING_SETTINGS_LIST.append(monitoring_settings)
+            KPI_UUIDS__TO__NUM_SAMPLES_RECEIVED[kpi_uuid] = 0
+
+    NUM_SAMPLES_EXPECTED_PER_KPI = SAMPLING_DURATION_SEC / SAMPLING_INTERVAL_SEC
+    NUM_SAMPLES_EXPECTED = len(MONITORING_SETTINGS_LIST) * NUM_SAMPLES_EXPECTED_PER_KPI
+
+    # Start monitoring the device
+    t_start_monitoring = datetime.timestamp(datetime.utcnow())
+    for monitoring_settings in MONITORING_SETTINGS_LIST:
+        device_client.MonitorDeviceKpi(MonitoringSettings(**monitoring_settings))
+
+    # wait to receive the expected number of samples
+    # if takes more than 1.5 times the sampling duration, assume there is an error
+    time_ini = time.time()
+    queue_samples : queue.Queue = monitoring_service.queue_samples
+    received_samples = []
+    while (len(received_samples) < NUM_SAMPLES_EXPECTED) and (time.time() - time_ini < SAMPLING_DURATION_SEC * 1.5):
+        try:
+            received_sample = queue_samples.get(block=True, timeout=SAMPLING_INTERVAL_SEC / NUM_SAMPLES_EXPECTED)
+            #LOGGER.info('received_sample = {:s}'.format(str(received_sample)))
+            received_samples.append(received_sample)
+        except queue.Empty:
+            continue
+
+    t_end_monitoring = datetime.timestamp(datetime.utcnow())
+
+    #LOGGER.info('received_samples = {:s}'.format(str(received_samples)))
+    LOGGER.info('len(received_samples) = {:s}'.format(str(len(received_samples))))
+    LOGGER.info('NUM_SAMPLES_EXPECTED = {:s}'.format(str(NUM_SAMPLES_EXPECTED)))
+    assert len(received_samples) == NUM_SAMPLES_EXPECTED
+    for received_sample in received_samples:
+        kpi_uuid = received_sample.kpi_id.kpi_id.uuid
+        assert kpi_uuid in KPI_UUIDS__TO__NUM_SAMPLES_RECEIVED
+        assert isinstance(received_sample.timestamp, str)
+        try:
+            timestamp = float(received_sample.timestamp)
+        except ValueError:
+            dt_time = dateutil.parser.isoparse(received_sample.timestamp).replace(tzinfo=timezone.utc)
+            timestamp = float(calendar.timegm(dt_time.timetuple())) + (dt_time.microsecond / 1.e6)
+        assert timestamp > t_start_monitoring
+        assert timestamp < t_end_monitoring
+        assert received_sample.kpi_value.HasField('floatVal') or received_sample.kpi_value.HasField('intVal')
+        kpi_value = getattr(received_sample.kpi_value, received_sample.kpi_value.WhichOneof('value'))
+        assert isinstance(kpi_value, (float, int))
+        KPI_UUIDS__TO__NUM_SAMPLES_RECEIVED[kpi_uuid] += 1
+
+    LOGGER.info('KPI_UUIDS__TO__NUM_SAMPLES_RECEIVED = {:s}'.format(str(KPI_UUIDS__TO__NUM_SAMPLES_RECEIVED)))
+    # TODO: review why num_samples_received per KPI != NUM_SAMPLES_EXPECTED_PER_KPI
+    #for kpi_uuid, num_samples_received in KPI_UUIDS__TO__NUM_SAMPLES_RECEIVED.items():
+    #    assert num_samples_received == NUM_SAMPLES_EXPECTED_PER_KPI
+
+    # Unsubscribe monitoring
+    for kpi_uuid in KPI_UUIDS__TO__NUM_SAMPLES_RECEIVED.keys():
+        MONITORING_SETTINGS_UNSUBSCRIBE = {
+            'kpi_id'             : {'kpi_id': {'uuid': kpi_uuid}},
+            'sampling_duration_s': -1, # negative value in sampling_duration_s or sampling_interval_s means unsibscribe
+            'sampling_interval_s': -1, # kpi_id is mandatory to unsibscribe
+        }
+        device_client.MonitorDeviceKpi(MonitoringSettings(**MONITORING_SETTINGS_UNSUBSCRIBE))
+
+
 def test_device_openconfig_deconfigure(
     context_client : ContextClient,     # pylint: disable=redefined-outer-name
     device_client : DeviceClient,       # pylint: disable=redefined-outer-name
diff --git a/src/l3_distributedattackdetector/service/piped/0demo/log_tcp_temp_complete b/src/l3_distributedattackdetector/service/piped/0demo/log_tcp_temp_complete
index 52bac35ef..ec9d7cb8b 100644
--- a/src/l3_distributedattackdetector/service/piped/0demo/log_tcp_temp_complete
+++ b/src/l3_distributedattackdetector/service/piped/0demo/log_tcp_temp_complete
@@ -350,3 +350,24 @@
 221.181.185.159 26406 11 0 10 4 1111 6 1111 0 0 0 1 0 10.25.0.6 22 10 0 10 5 1497 4 1497 0 0 0 1 0 1631699183883.141113 1631699185936.677002 2053.536000 327.059000 334.471000 2053.536000 1726.893000 326.976000 327.081000 1 1 0 0 0 0 0 13.977100 0.022000 42.534000 21.563233 6 40 40 334.779504 326.582000 367.107000 18.068151 5 64 64 0 0 0 0 0 0 1 1 7 1 0 1460 856 15 33536 29200 0 856 15 15 0 0 0 0 0 0 0 0 0 1 1 7 1 0 1410 1080 41 64384 64128 0 1080 41 41 0 0 0 0 0 0 0 0 0 0 0 --- 6 4 - - 0 0 0 0.000000 0.000000 0.000000 0.000000 0 0 - - 0.0 0.0 -
 221.181.185.159 26406 11 0 10 4 1111 6 1111 0 0 0 1 0 10.25.0.6 22 10 0 10 5 1497 4 1497 0 0 0 1 0 1631699183883.141113 1631699185936.677002 2053.536000 327.059000 334.471000 2053.536000 1726.893000 326.976000 327.081000 1 1 0 0 0 0 0 13.977100 0.022000 42.534000 21.563233 6 40 40 334.779504 326.582000 367.107000 18.068151 5 64 64 0 0 0 0 0 0 1 1 7 1 0 1460 856 15 33536 29200 0 856 15 15 0 0 0 0 0 0 0 0 0 1 1 7 1 0 1410 1080 41 64384 64128 0 1080 41 41 0 0 0 0 0 0 0 0 0 0 0 --- 6 4 - - 0 0 0 0.000000 0.000000 0.000000 0.000000 0 0 - - 0.0 0.0 -
 221.181.185.159 26406 11 0 10 4 1111 6 1111 0 0 0 1 0 10.25.0.6 22 10 0 10 5 1497 4 1497 0 0 0 1 0 1631699183883.141113 1631699185936.677002 2053.536000 327.059000 334.471000 2053.536000 1726.893000 326.976000 327.081000 1 1 0 0 0 0 0 13.977100 0.022000 42.534000 21.563233 6 40 40 334.779504 326.582000 367.107000 18.068151 5 64 64 0 0 0 0 0 0 1 1 7 1 0 1460 856 15 33536 29200 0 856 15 15 0 0 0 0 0 0 0 0 0 1 1 7 1 0 1410 1080 41 64384 64128 0 1080 41 41 0 0 0 0 0 0 0 0 0 0 0 --- 6 4 - - 0 0 0 0.000000 0.000000 0.000000 0.000000 0 0 - - 0.0 0.0 -
+221.181.185.159 26406 11 0 10 4 1111 6 1111 0 0 0 1 0 10.25.0.6 22 10 0 10 5 1497 4 1497 0 0 0 1 0 1631699183883.141113 1631699185936.677002 2053.536000 327.059000 334.471000 2053.536000 1726.893000 326.976000 327.081000 1 1 0 0 0 0 0 13.977100 0.022000 42.534000 21.563233 6 40 40 334.779504 326.582000 367.107000 18.068151 5 64 64 0 0 0 0 0 0 1 1 7 1 0 1460 856 15 33536 29200 0 856 15 15 0 0 0 0 0 0 0 0 0 1 1 7 1 0 1410 1080 41 64384 64128 0 1080 41 41 0 0 0 0 0 0 0 0 0 0 0 --- 6 4 - - 0 0 0 0.000000 0.000000 0.000000 0.000000 0 0 - - 0.0 0.0 -
+221.181.185.159 26406 11 0 10 4 1111 6 1111 0 0 0 1 0 10.25.0.6 22 10 0 10 5 1497 4 1497 0 0 0 1 0 1631699183883.141113 1631699185936.677002 2053.536000 327.059000 334.471000 2053.536000 1726.893000 326.976000 327.081000 1 1 0 0 0 0 0 13.977100 0.022000 42.534000 21.563233 6 40 40 334.779504 326.582000 367.107000 18.068151 5 64 64 0 0 0 0 0 0 1 1 7 1 0 1460 856 15 33536 29200 0 856 15 15 0 0 0 0 0 0 0 0 0 1 1 7 1 0 1410 1080 41 64384 64128 0 1080 41 41 0 0 0 0 0 0 0 0 0 0 0 --- 6 4 - - 0 0 0 0.000000 0.000000 0.000000 0.000000 0 0 - - 0.0 0.0 -
+221.181.185.159 26406 11 0 10 4 1111 6 1111 0 0 0 1 0 10.25.0.6 22 10 0 10 5 1497 4 1497 0 0 0 1 0 1631699183883.141113 1631699185936.677002 2053.536000 327.059000 334.471000 2053.536000 1726.893000 326.976000 327.081000 1 1 0 0 0 0 0 13.977100 0.022000 42.534000 21.563233 6 40 40 334.779504 326.582000 367.107000 18.068151 5 64 64 0 0 0 0 0 0 1 1 7 1 0 1460 856 15 33536 29200 0 856 15 15 0 0 0 0 0 0 0 0 0 1 1 7 1 0 1410 1080 41 64384 64128 0 1080 41 41 0 0 0 0 0 0 0 0 0 0 0 --- 6 4 - - 0 0 0 0.000000 0.000000 0.000000 0.000000 0 0 - - 0.0 0.0 -
+221.181.185.159 26406 11 0 10 4 1111 6 1111 0 0 0 1 0 10.25.0.6 22 10 0 10 5 1497 4 1497 0 0 0 1 0 1631699183883.141113 1631699185936.677002 2053.536000 327.059000 334.471000 2053.536000 1726.893000 326.976000 327.081000 1 1 0 0 0 0 0 13.977100 0.022000 42.534000 21.563233 6 40 40 334.779504 326.582000 367.107000 18.068151 5 64 64 0 0 0 0 0 0 1 1 7 1 0 1460 856 15 33536 29200 0 856 15 15 0 0 0 0 0 0 0 0 0 1 1 7 1 0 1410 1080 41 64384 64128 0 1080 41 41 0 0 0 0 0 0 0 0 0 0 0 --- 6 4 - - 0 0 0 0.000000 0.000000 0.000000 0.000000 0 0 - - 0.0 0.0 -
+221.181.185.159 26406 11 0 10 4 1111 6 1111 0 0 0 1 0 10.25.0.6 22 10 0 10 5 1497 4 1497 0 0 0 1 0 1631699183883.141113 1631699185936.677002 2053.536000 327.059000 334.471000 2053.536000 1726.893000 326.976000 327.081000 1 1 0 0 0 0 0 13.977100 0.022000 42.534000 21.563233 6 40 40 334.779504 326.582000 367.107000 18.068151 5 64 64 0 0 0 0 0 0 1 1 7 1 0 1460 856 15 33536 29200 0 856 15 15 0 0 0 0 0 0 0 0 0 1 1 7 1 0 1410 1080 41 64384 64128 0 1080 41 41 0 0 0 0 0 0 0 0 0 0 0 --- 6 4 - - 0 0 0 0.000000 0.000000 0.000000 0.000000 0 0 - - 0.0 0.0 -
+221.181.185.159 26406 11 0 10 4 1111 6 1111 0 0 0 1 0 10.25.0.6 22 10 0 10 5 1497 4 1497 0 0 0 1 0 1631699183883.141113 1631699185936.677002 2053.536000 327.059000 334.471000 2053.536000 1726.893000 326.976000 327.081000 1 1 0 0 0 0 0 13.977100 0.022000 42.534000 21.563233 6 40 40 334.779504 326.582000 367.107000 18.068151 5 64 64 0 0 0 0 0 0 1 1 7 1 0 1460 856 15 33536 29200 0 856 15 15 0 0 0 0 0 0 0 0 0 1 1 7 1 0 1410 1080 41 64384 64128 0 1080 41 41 0 0 0 0 0 0 0 0 0 0 0 --- 6 4 - - 0 0 0 0.000000 0.000000 0.000000 0.000000 0 0 - - 0.0 0.0 -
+221.181.185.159 26406 11 0 10 4 1111 6 1111 0 0 0 1 0 10.25.0.6 22 10 0 10 5 1497 4 1497 0 0 0 1 0 1631699183883.141113 1631699185936.677002 2053.536000 327.059000 334.471000 2053.536000 1726.893000 326.976000 327.081000 1 1 0 0 0 0 0 13.977100 0.022000 42.534000 21.563233 6 40 40 334.779504 326.582000 367.107000 18.068151 5 64 64 0 0 0 0 0 0 1 1 7 1 0 1460 856 15 33536 29200 0 856 15 15 0 0 0 0 0 0 0 0 0 1 1 7 1 0 1410 1080 41 64384 64128 0 1080 41 41 0 0 0 0 0 0 0 0 0 0 0 --- 6 4 - - 0 0 0 0.000000 0.000000 0.000000 0.000000 0 0 - - 0.0 0.0 -
+221.181.185.159 26406 11 0 10 4 1111 6 1111 0 0 0 1 0 10.25.0.6 22 10 0 10 5 1497 4 1497 0 0 0 1 0 1631699183883.141113 1631699185936.677002 2053.536000 327.059000 334.471000 2053.536000 1726.893000 326.976000 327.081000 1 1 0 0 0 0 0 13.977100 0.022000 42.534000 21.563233 6 40 40 334.779504 326.582000 367.107000 18.068151 5 64 64 0 0 0 0 0 0 1 1 7 1 0 1460 856 15 33536 29200 0 856 15 15 0 0 0 0 0 0 0 0 0 1 1 7 1 0 1410 1080 41 64384 64128 0 1080 41 41 0 0 0 0 0 0 0 0 0 0 0 --- 6 4 - - 0 0 0 0.000000 0.000000 0.000000 0.000000 0 0 - - 0.0 0.0 -
+221.181.185.159 26406 11 0 10 4 1111 6 1111 0 0 0 1 0 10.25.0.6 22 10 0 10 5 1497 4 1497 0 0 0 1 0 1631699183883.141113 1631699185936.677002 2053.536000 327.059000 334.471000 2053.536000 1726.893000 326.976000 327.081000 1 1 0 0 0 0 0 13.977100 0.022000 42.534000 21.563233 6 40 40 334.779504 326.582000 367.107000 18.068151 5 64 64 0 0 0 0 0 0 1 1 7 1 0 1460 856 15 33536 29200 0 856 15 15 0 0 0 0 0 0 0 0 0 1 1 7 1 0 1410 1080 41 64384 64128 0 1080 41 41 0 0 0 0 0 0 0 0 0 0 0 --- 6 4 - - 0 0 0 0.000000 0.000000 0.000000 0.000000 0 0 - - 0.0 0.0 -
+221.181.185.159 26406 11 0 10 4 1111 6 1111 0 0 0 1 0 10.25.0.6 22 10 0 10 5 1497 4 1497 0 0 0 1 0 1631699183883.141113 1631699185936.677002 2053.536000 327.059000 334.471000 2053.536000 1726.893000 326.976000 327.081000 1 1 0 0 0 0 0 13.977100 0.022000 42.534000 21.563233 6 40 40 334.779504 326.582000 367.107000 18.068151 5 64 64 0 0 0 0 0 0 1 1 7 1 0 1460 856 15 33536 29200 0 856 15 15 0 0 0 0 0 0 0 0 0 1 1 7 1 0 1410 1080 41 64384 64128 0 1080 41 41 0 0 0 0 0 0 0 0 0 0 0 --- 6 4 - - 0 0 0 0.000000 0.000000 0.000000 0.000000 0 0 - - 0.0 0.0 -
+221.181.185.159 26406 11 0 10 4 1111 6 1111 0 0 0 1 0 10.25.0.6 22 10 0 10 5 1497 4 1497 0 0 0 1 0 1631699183883.141113 1631699185936.677002 2053.536000 327.059000 334.471000 2053.536000 1726.893000 326.976000 327.081000 1 1 0 0 0 0 0 13.977100 0.022000 42.534000 21.563233 6 40 40 334.779504 326.582000 367.107000 18.068151 5 64 64 0 0 0 0 0 0 1 1 7 1 0 1460 856 15 33536 29200 0 856 15 15 0 0 0 0 0 0 0 0 0 1 1 7 1 0 1410 1080 41 64384 64128 0 1080 41 41 0 0 0 0 0 0 0 0 0 0 0 --- 6 4 - - 0 0 0 0.000000 0.000000 0.000000 0.000000 0 0 - - 0.0 0.0 -
+221.181.185.159 26406 11 0 10 4 1111 6 1111 0 0 0 1 0 10.25.0.6 22 10 0 10 5 1497 4 1497 0 0 0 1 0 1631699183883.141113 1631699185936.677002 2053.536000 327.059000 334.471000 2053.536000 1726.893000 326.976000 327.081000 1 1 0 0 0 0 0 13.977100 0.022000 42.534000 21.563233 6 40 40 334.779504 326.582000 367.107000 18.068151 5 64 64 0 0 0 0 0 0 1 1 7 1 0 1460 856 15 33536 29200 0 856 15 15 0 0 0 0 0 0 0 0 0 1 1 7 1 0 1410 1080 41 64384 64128 0 1080 41 41 0 0 0 0 0 0 0 0 0 0 0 --- 6 4 - - 0 0 0 0.000000 0.000000 0.000000 0.000000 0 0 - - 0.0 0.0 -
+221.181.185.159 26406 11 0 10 4 1111 6 1111 0 0 0 1 0 10.25.0.6 22 10 0 10 5 1497 4 1497 0 0 0 1 0 1631699183883.141113 1631699185936.677002 2053.536000 327.059000 334.471000 2053.536000 1726.893000 326.976000 327.081000 1 1 0 0 0 0 0 13.977100 0.022000 42.534000 21.563233 6 40 40 334.779504 326.582000 367.107000 18.068151 5 64 64 0 0 0 0 0 0 1 1 7 1 0 1460 856 15 33536 29200 0 856 15 15 0 0 0 0 0 0 0 0 0 1 1 7 1 0 1410 1080 41 64384 64128 0 1080 41 41 0 0 0 0 0 0 0 0 0 0 0 --- 6 4 - - 0 0 0 0.000000 0.000000 0.000000 0.000000 0 0 - - 0.0 0.0 -
+221.181.185.159 26406 11 0 10 4 1111 6 1111 0 0 0 1 0 10.25.0.6 22 10 0 10 5 1497 4 1497 0 0 0 1 0 1631699183883.141113 1631699185936.677002 2053.536000 327.059000 334.471000 2053.536000 1726.893000 326.976000 327.081000 1 1 0 0 0 0 0 13.977100 0.022000 42.534000 21.563233 6 40 40 334.779504 326.582000 367.107000 18.068151 5 64 64 0 0 0 0 0 0 1 1 7 1 0 1460 856 15 33536 29200 0 856 15 15 0 0 0 0 0 0 0 0 0 1 1 7 1 0 1410 1080 41 64384 64128 0 1080 41 41 0 0 0 0 0 0 0 0 0 0 0 --- 6 4 - - 0 0 0 0.000000 0.000000 0.000000 0.000000 0 0 - - 0.0 0.0 -
+221.181.185.159 26406 11 0 10 4 1111 6 1111 0 0 0 1 0 10.25.0.6 22 10 0 10 5 1497 4 1497 0 0 0 1 0 1631699183883.141113 1631699185936.677002 2053.536000 327.059000 334.471000 2053.536000 1726.893000 326.976000 327.081000 1 1 0 0 0 0 0 13.977100 0.022000 42.534000 21.563233 6 40 40 334.779504 326.582000 367.107000 18.068151 5 64 64 0 0 0 0 0 0 1 1 7 1 0 1460 856 15 33536 29200 0 856 15 15 0 0 0 0 0 0 0 0 0 1 1 7 1 0 1410 1080 41 64384 64128 0 1080 41 41 0 0 0 0 0 0 0 0 0 0 0 --- 6 4 - - 0 0 0 0.000000 0.000000 0.000000 0.000000 0 0 - - 0.0 0.0 -
+221.181.185.159 26406 11 0 10 4 1111 6 1111 0 0 0 1 0 10.25.0.6 22 10 0 10 5 1497 4 1497 0 0 0 1 0 1631699183883.141113 1631699185936.677002 2053.536000 327.059000 334.471000 2053.536000 1726.893000 326.976000 327.081000 1 1 0 0 0 0 0 13.977100 0.022000 42.534000 21.563233 6 40 40 334.779504 326.582000 367.107000 18.068151 5 64 64 0 0 0 0 0 0 1 1 7 1 0 1460 856 15 33536 29200 0 856 15 15 0 0 0 0 0 0 0 0 0 1 1 7 1 0 1410 1080 41 64384 64128 0 1080 41 41 0 0 0 0 0 0 0 0 0 0 0 --- 6 4 - - 0 0 0 0.000000 0.000000 0.000000 0.000000 0 0 - - 0.0 0.0 -
+221.181.185.159 26406 11 0 10 4 1111 6 1111 0 0 0 1 0 10.25.0.6 22 10 0 10 5 1497 4 1497 0 0 0 1 0 1631699183883.141113 1631699185936.677002 2053.536000 327.059000 334.471000 2053.536000 1726.893000 326.976000 327.081000 1 1 0 0 0 0 0 13.977100 0.022000 42.534000 21.563233 6 40 40 334.779504 326.582000 367.107000 18.068151 5 64 64 0 0 0 0 0 0 1 1 7 1 0 1460 856 15 33536 29200 0 856 15 15 0 0 0 0 0 0 0 0 0 1 1 7 1 0 1410 1080 41 64384 64128 0 1080 41 41 0 0 0 0 0 0 0 0 0 0 0 --- 6 4 - - 0 0 0 0.000000 0.000000 0.000000 0.000000 0 0 - - 0.0 0.0 -
+221.181.185.159 26406 11 0 10 4 1111 6 1111 0 0 0 1 0 10.25.0.6 22 10 0 10 5 1497 4 1497 0 0 0 1 0 1631699183883.141113 1631699185936.677002 2053.536000 327.059000 334.471000 2053.536000 1726.893000 326.976000 327.081000 1 1 0 0 0 0 0 13.977100 0.022000 42.534000 21.563233 6 40 40 334.779504 326.582000 367.107000 18.068151 5 64 64 0 0 0 0 0 0 1 1 7 1 0 1460 856 15 33536 29200 0 856 15 15 0 0 0 0 0 0 0 0 0 1 1 7 1 0 1410 1080 41 64384 64128 0 1080 41 41 0 0 0 0 0 0 0 0 0 0 0 --- 6 4 - - 0 0 0 0.000000 0.000000 0.000000 0.000000 0 0 - - 0.0 0.0 -
+221.181.185.159 26406 11 0 10 4 1111 6 1111 0 0 0 1 0 10.25.0.6 22 10 0 10 5 1497 4 1497 0 0 0 1 0 1631699183883.141113 1631699185936.677002 2053.536000 327.059000 334.471000 2053.536000 1726.893000 326.976000 327.081000 1 1 0 0 0 0 0 13.977100 0.022000 42.534000 21.563233 6 40 40 334.779504 326.582000 367.107000 18.068151 5 64 64 0 0 0 0 0 0 1 1 7 1 0 1460 856 15 33536 29200 0 856 15 15 0 0 0 0 0 0 0 0 0 1 1 7 1 0 1410 1080 41 64384 64128 0 1080 41 41 0 0 0 0 0 0 0 0 0 0 0 --- 6 4 - - 0 0 0 0.000000 0.000000 0.000000 0.000000 0 0 - - 0.0 0.0 -
+221.181.185.159 26406 11 0 10 4 1111 6 1111 0 0 0 1 0 10.25.0.6 22 10 0 10 5 1497 4 1497 0 0 0 1 0 1631699183883.141113 1631699185936.677002 2053.536000 327.059000 334.471000 2053.536000 1726.893000 326.976000 327.081000 1 1 0 0 0 0 0 13.977100 0.022000 42.534000 21.563233 6 40 40 334.779504 326.582000 367.107000 18.068151 5 64 64 0 0 0 0 0 0 1 1 7 1 0 1460 856 15 33536 29200 0 856 15 15 0 0 0 0 0 0 0 0 0 1 1 7 1 0 1410 1080 41 64384 64128 0 1080 41 41 0 0 0 0 0 0 0 0 0 0 0 --- 6 4 - - 0 0 0 0.000000 0.000000 0.000000 0.000000 0 0 - - 0.0 0.0 -
+221.181.185.159 26406 11 0 10 4 1111 6 1111 0 0 0 1 0 10.25.0.6 22 10 0 10 5 1497 4 1497 0 0 0 1 0 1631699183883.141113 1631699185936.677002 2053.536000 327.059000 334.471000 2053.536000 1726.893000 326.976000 327.081000 1 1 0 0 0 0 0 13.977100 0.022000 42.534000 21.563233 6 40 40 334.779504 326.582000 367.107000 18.068151 5 64 64 0 0 0 0 0 0 1 1 7 1 0 1460 856 15 33536 29200 0 856 15 15 0 0 0 0 0 0 0 0 0 1 1 7 1 0 1410 1080 41 64384 64128 0 1080 41 41 0 0 0 0 0 0 0 0 0 0 0 --- 6 4 - - 0 0 0 0.000000 0.000000 0.000000 0.000000 0 0 - - 0.0 0.0 -
diff --git a/src/service/service/service_handlers/l3nm_openconfig/L3NMOpenConfigServiceHandler.py b/src/service/service/service_handlers/l3nm_openconfig/L3NMOpenConfigServiceHandler.py
index 894f68b3e..0ac32c86b 100644
--- a/src/service/service/service_handlers/l3nm_openconfig/L3NMOpenConfigServiceHandler.py
+++ b/src/service/service/service_handlers/l3nm_openconfig/L3NMOpenConfigServiceHandler.py
@@ -47,9 +47,10 @@ class L3NMOpenConfigServiceHandler(_ServiceHandler):
         if len(endpoints) == 0: return []
 
         service_uuid              = self.__db_service.service_uuid
-        network_instance_name     = '{:s}-NetInst'.format(service_uuid)
-        network_interface_name    = '{:s}-NetIf'.format(service_uuid)
-        network_subinterface_name = '{:s}-NetSubIf'.format(service_uuid)
+        service_short_uuid        = service_uuid.split('-')[-1]
+        network_instance_name     = '{:s}-NetInst'.format(service_short_uuid)
+        network_interface_desc    = '{:s}-NetIf'.format(service_uuid)
+        network_subinterface_desc = '{:s}-NetSubIf'.format(service_uuid)
 
         settings : TreeNode = get_subnode(self.__resolver, self.__config, 'settings', None)
         if settings is None: raise Exception('Unable to retrieve service settings')
@@ -84,17 +85,18 @@ class L3NMOpenConfigServiceHandler(_ServiceHandler):
                 json_device_config_rules.extend([
                     config_rule_set(
                         '/network_instance[{:s}]'.format(network_instance_name), {
-                            'name': network_instance_name, 'type': 'L3VRF', 'router_id': router_id,
-                            'route_distinguisher': route_distinguisher, 'address_families': address_families,
+                            'name': network_instance_name, 'description': network_interface_desc, 'type': 'L3VRF',
+                            'router_id': router_id, 'route_distinguisher': route_distinguisher,
+                            'address_families': address_families,
                     }),
                     config_rule_set(
                         '/interface[{:s}]'.format(endpoint_uuid), {
-                            'name': endpoint_uuid, 'description': network_interface_name, 'mtu': mtu,
+                            'name': endpoint_uuid, 'description': network_interface_desc, 'mtu': mtu,
                     }),
                     config_rule_set(
                         '/interface[{:s}]/subinterface[{:d}]'.format(endpoint_uuid, sub_interface_index), {
                             'name': endpoint_uuid, 'index': sub_interface_index,
-                            'description': network_subinterface_name, 'mtu': mtu,
+                            'description': network_subinterface_desc, 'mtu': mtu,
                     }),
                     config_rule_set(
                         '/network_instance[{:s}]/interface[{:s}]'.format(network_instance_name, endpoint_uuid), {
@@ -114,7 +116,8 @@ class L3NMOpenConfigServiceHandler(_ServiceHandler):
         if len(endpoints) == 0: return []
 
         service_uuid              = self.__db_service.service_uuid
-        network_instance_name     = '{:s}-NetInst'.format(service_uuid)
+        service_short_uuid        = service_uuid.split('-')[-1]
+        network_instance_name     = '{:s}-NetInst'.format(service_short_uuid)
 
         results = []
         for endpoint in endpoints:
-- 
GitLab