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