diff --git a/src/device/service/drivers/gnmi_openconfig/DeltaSampleCache.py b/src/device/service/drivers/gnmi_openconfig/DeltaSampleCache.py index daf04be5a1ff82a79031d8c3ffe19da10739fbcb..140efe84038ed5117d6c7924b188a7dc7dbd7958 100644 --- a/src/device/service/drivers/gnmi_openconfig/DeltaSampleCache.py +++ b/src/device/service/drivers/gnmi_openconfig/DeltaSampleCache.py @@ -13,13 +13,15 @@ # limitations under the License. import copy -from typing import Any, Dict, Tuple, Union +from typing import Any, Dict, Optional, Tuple, Union class DeltaSampleCache: def __init__(self) -> None: self._previous_samples : Dict[str, Tuple[float, Union[int, float]]] = dict() - def get_delta(self, path : str, current_timestamp : float, current_value : Any) -> None: + def get_delta( + self, path : str, current_timestamp : float, current_value : Any + ) -> Optional[Tuple[float, Optional[Any]]]: previous_sample = copy.deepcopy(self._previous_samples.get(path)) self._previous_samples[path] = current_timestamp, current_value @@ -30,6 +32,10 @@ class DeltaSampleCache: delta_value = max(0, current_value - previous_value) delay = current_timestamp - previous_timestamp - delta_sample = current_timestamp, delta_value / delay - - return delta_sample + if delay < 1.e-12: + # return a special value meaning, at that timestamp, + # computed value is not a number, e.g., division by zero + # also, recover previuos samples to do not miss any packet/byte + self._previous_samples[path] = previous_sample + return current_timestamp, None + return current_timestamp, delta_value / delay diff --git a/src/device/service/drivers/gnmi_openconfig/MonitoringThread.py b/src/device/service/drivers/gnmi_openconfig/MonitoringThread.py index c3668a86e17a1044c18a2cd092dfb590edb0eca8..1910c167186a4e9b3424b44c6c6ed203f55c1e18 100644 --- a/src/device/service/drivers/gnmi_openconfig/MonitoringThread.py +++ b/src/device/service/drivers/gnmi_openconfig/MonitoringThread.py @@ -134,7 +134,7 @@ class MonitoringThread(threading.Thread): self._response_iterator = self._stub.Subscribe(request_iterator, metadata=metadata, timeout=timeout) for subscribe_response in self._response_iterator: str_subscribe_response = grpc_message_to_json_string(subscribe_response) - self._logger.warning('[run] subscribe_response={:s}'.format(str_subscribe_response)) + self._logger.debug('[run] subscribe_response={:s}'.format(str_subscribe_response)) update = subscribe_response.update timestamp_device = float(update.timestamp) / 1.e9 timestamp_local = datetime.timestamp(datetime.utcnow()) @@ -168,8 +168,10 @@ class MonitoringThread(threading.Thread): sample = (timestamp, str_path, value) else: sample = (delta_sample[0], str_path, delta_sample[1]) - self._logger.warning('[run] sample={:s}'.format(str(sample))) - self._out_samples.put_nowait(sample) + self._logger.debug('[run] sample={:s}'.format(str(sample))) + if sample[2] is not None: + # Skip not-a-number (e.g., division by zero) samples + self._out_samples.put_nowait(sample) except grpc.RpcError as e: if e.code() != grpc.StatusCode.CANCELLED: raise # pylint: disable=no-member if e.details() != 'Locally cancelled by application!': raise # pylint: disable=no-member diff --git a/src/device/service/drivers/gnmi_openconfig/handlers/NetworkInstanceProtocol.py b/src/device/service/drivers/gnmi_openconfig/handlers/NetworkInstanceProtocol.py new file mode 100644 index 0000000000000000000000000000000000000000..f456461929bf80a04b96e2cf8cdda3070d94954c --- /dev/null +++ b/src/device/service/drivers/gnmi_openconfig/handlers/NetworkInstanceProtocol.py @@ -0,0 +1,80 @@ +# Copyright 2022-2024 ETSI OSG/SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import json, libyang, logging +from typing import Any, Dict, List, Tuple +from ._Handler import _Handler +from .Tools import get_str +from .YangHandler import YangHandler + +LOGGER = logging.getLogger(__name__) + +class NetworkInstanceProtocolHandler(_Handler): + def get_resource_key(self) -> str: return '/network_instance/protocols' + def get_path(self) -> str: + return '/openconfig-network-instance:network-instances/network-instance/protocols/protocol' + + def compose( + self, resource_key : str, resource_value : Dict, yang_handler : YangHandler, delete : bool = False + ) -> Tuple[str, str]: + ni_name = get_str(resource_value, 'name' ) # test-svc + identifier = get_str(resource_value, 'identifier') # 'STATIC' + proto_name = get_str(resource_value, 'protocol_name') # 'STATIC' + + if ':' not in identifier: + identifier = 'openconfig-policy-types:{:s}'.format(identifier) + PATH_TMPL = '/network-instances/network-instance[name={:s}]/protocols/protocol[identifier={:s}][name={:s}]' + str_path = PATH_TMPL.format(ni_name, identifier, proto_name) + + if delete: + str_data = json.dumps({}) + return str_path, str_data + + #str_data = json.dumps({ + # 'identifier': identifier, 'name': name, + # 'config': {'identifier': identifier, 'name': name, 'enabled': True}, + # 'static_routes': {'static': [{ + # 'prefix': prefix, + # 'config': {'prefix': prefix}, + # 'next_hops': { + # 'next-hop': [{ + # 'index': next_hop_index, + # 'config': {'index': next_hop_index, 'next_hop': next_hop} + # }] + # } + # }]} + #}) + + yang_nis : libyang.DContainer = yang_handler.get_data_path('/openconfig-network-instance:network-instances') + yang_ni : libyang.DContainer = yang_nis.create_path('network-instance[name="{:s}"]'.format(ni_name)) + yang_ni_prs : libyang.DContainer = yang_ni.create_path('protocols') + yang_ni_pr_path = 'protocol[identifier="{:s}"][name="{:s}"]'.format(identifier, proto_name) + yang_ni_pr : libyang.DContainer = yang_ni_prs.create_path(yang_ni_pr_path) + yang_ni_pr.create_path('config/identifier', identifier) + yang_ni_pr.create_path('config/name', proto_name) + yang_ni_pr.create_path('config/enabled', True ) + + str_data = yang_ni_pr.print_mem('json') + LOGGER.warning('[compose] str_data = {:s}'.format(str(str_data))) + json_data = json.loads(str_data) + json_data = json_data['openconfig-network-instance:protocol'][0] + str_data = json.dumps(json_data) + return str_path, str_data + + def parse( + self, json_data : Dict, yang_handler : YangHandler + ) -> List[Tuple[str, Dict[str, Any]]]: + LOGGER.warning('[parse] json_data = {:s}'.format(str(json_data))) + response = [] + return response diff --git a/src/device/service/drivers/gnmi_openconfig/handlers/NetworkInstanceStaticRoute.py b/src/device/service/drivers/gnmi_openconfig/handlers/NetworkInstanceStaticRoute.py index cc561f37d41ca6b09308a2699d4d6145630a5fee..ad1ef8b700e022a3c3b55cbad1847ad989680bb0 100644 --- a/src/device/service/drivers/gnmi_openconfig/handlers/NetworkInstanceStaticRoute.py +++ b/src/device/service/drivers/gnmi_openconfig/handlers/NetworkInstanceStaticRoute.py @@ -28,24 +28,29 @@ class NetworkInstanceStaticRouteHandler(_Handler): def compose( self, resource_key : str, resource_value : Dict, yang_handler : YangHandler, delete : bool = False ) -> Tuple[str, str]: - ni_name = get_str(resource_value, 'name' ) # test-svc - prefix = get_str(resource_value, 'prefix') # '172.0.1.0/24' + ni_name = get_str(resource_value, 'name' ) # test-svc + identifier = get_str(resource_value, 'identifier') # 'STATIC' + proto_name = get_str(resource_value, 'protocol_name') # 'STATIC' + prefix = get_str(resource_value, 'prefix') # '172.0.1.0/24' + + if ':' not in identifier: + identifier = 'openconfig-policy-types:{:s}'.format(identifier) - identifier = 'openconfig-policy-types:STATIC' - name = 'static' if delete: PATH_TMPL = '/network-instances/network-instance[name={:s}]/protocols' PATH_TMPL += '/protocol[identifier={:s}][name={:s}]/static-routes/static[prefix={:s}]' - str_path = PATH_TMPL.format(ni_name, identifier, name, prefix) + str_path = PATH_TMPL.format(ni_name, identifier, proto_name, prefix) str_data = json.dumps({}) return str_path, str_data - next_hop = get_str(resource_value, 'next_hop' ) # '172.0.0.1' - metric = get_int(resource_value, 'metric' ) # 20 - next_hop_index = get_str(resource_value, 'next_hop_index') # AUTO_1_172-0-0-1 + next_hop = get_str(resource_value, 'next_hop') # '172.0.0.1' + metric = get_int(resource_value, 'metric' ) # 20 + index = get_str(resource_value, 'index' ) # AUTO_1_172-0-0-1 + if index is None: + index = 'AUTO_{:d}_{:s}'.format(metric, next_hop) PATH_TMPL = '/network-instances/network-instance[name={:s}]/protocols/protocol[identifier={:s}][name={:s}]' - str_path = PATH_TMPL.format(ni_name, identifier, name) + str_path = PATH_TMPL.format(ni_name, identifier, proto_name) #str_data = json.dumps({ # 'identifier': identifier, 'name': name, # 'config': {'identifier': identifier, 'name': name, 'enabled': True}, @@ -64,10 +69,10 @@ class NetworkInstanceStaticRouteHandler(_Handler): yang_nis : libyang.DContainer = yang_handler.get_data_path('/openconfig-network-instance:network-instances') yang_ni : libyang.DContainer = yang_nis.create_path('network-instance[name="{:s}"]'.format(ni_name)) yang_ni_prs : libyang.DContainer = yang_ni.create_path('protocols') - yang_ni_pr_path = 'protocol[identifier="{:s}"][name="{:s}"]'.format(identifier, name) + yang_ni_pr_path = 'protocol[identifier="{:s}"][name="{:s}"]'.format(identifier, proto_name) yang_ni_pr : libyang.DContainer = yang_ni_prs.create_path(yang_ni_pr_path) yang_ni_pr.create_path('config/identifier', identifier) - yang_ni_pr.create_path('config/name', name ) + yang_ni_pr.create_path('config/name', proto_name) yang_ni_pr.create_path('config/enabled', True ) yang_ni_pr_srs : libyang.DContainer = yang_ni_pr.create_path('static-routes') @@ -76,11 +81,11 @@ class NetworkInstanceStaticRouteHandler(_Handler): yang_ni_pr_sr.create_path('config/prefix', prefix) yang_ni_pr_sr_nhs : libyang.DContainer = yang_ni_pr_sr.create_path('next-hops') - yang_ni_pr_sr_nh_path = 'next-hop[index="{:s}"]'.format(next_hop_index) + yang_ni_pr_sr_nh_path = 'next-hop[index="{:s}"]'.format(index) yang_ni_pr_sr_nh : libyang.DContainer = yang_ni_pr_sr_nhs.create_path(yang_ni_pr_sr_nh_path) - yang_ni_pr_sr_nh.create_path('config/index', next_hop_index) + yang_ni_pr_sr_nh.create_path('config/index', index) yang_ni_pr_sr_nh.create_path('config/next-hop', next_hop) - yang_ni_pr_sr_nh.create_path('config/metric', metric) + yang_ni_pr_sr_nh.create_path('config/metric', metric) str_data = yang_ni_pr.print_mem('json') LOGGER.warning('[compose] str_data = {:s}'.format(str(str_data))) diff --git a/src/device/service/drivers/gnmi_openconfig/handlers/__init__.py b/src/device/service/drivers/gnmi_openconfig/handlers/__init__.py index 0b2b95f4488cecc28c20617d4efeb0572f016518..b36313bb2030ed9d07f1f3c198ea9447a0cafbaa 100644 --- a/src/device/service/drivers/gnmi_openconfig/handlers/__init__.py +++ b/src/device/service/drivers/gnmi_openconfig/handlers/__init__.py @@ -21,6 +21,7 @@ from .Interface import InterfaceHandler from .InterfaceCounter import InterfaceCounterHandler from .NetworkInstance import NetworkInstanceHandler from .NetworkInstanceInterface import NetworkInstanceInterfaceHandler +from .NetworkInstanceProtocol import NetworkInstanceProtocolHandler from .NetworkInstanceStaticRoute import NetworkInstanceStaticRouteHandler from .Tools import get_schema from .YangHandler import YangHandler @@ -32,6 +33,7 @@ ifaceh = InterfaceHandler() ifctrh = InterfaceCounterHandler() nih = NetworkInstanceHandler() niifh = NetworkInstanceInterfaceHandler() +niph = NetworkInstanceProtocolHandler() nisrh = NetworkInstanceStaticRouteHandler() ALL_RESOURCE_KEYS = [ @@ -59,6 +61,7 @@ RESOURCE_KEY_TO_HANDLER = { ifctrh.get_resource_key() : ifctrh, nih.get_resource_key() : nih, niifh.get_resource_key() : niifh, + niph.get_resource_key() : niph, nisrh.get_resource_key() : nisrh, } @@ -68,6 +71,7 @@ PATH_TO_HANDLER = { ifctrh.get_path() : ifctrh, nih.get_path() : nih, niifh.get_path() : niifh, + niph.get_path() : niph, nisrh.get_path() : nisrh, }