diff --git a/src/device/Dockerfile b/src/device/Dockerfile index 6566625527f8ceaa8de4639d558c92572c4835cb..2bcb5322af358a4892683a1c0d547392dd327c1b 100644 --- a/src/device/Dockerfile +++ b/src/device/Dockerfile @@ -53,6 +53,21 @@ RUN python3 -m grpc_tools.protoc -I=. --python_out=. --grpc_python_out=. *.proto RUN rm *.proto RUN find . -type f -exec sed -i -E 's/(import\ .*)_pb2/from . \1_pb2/g' {} \; +# Download, build and install libyang. Note that APT package is outdated +# - Ref: https://github.com/CESNET/libyang +# - Ref: https://github.com/CESNET/libyang-python/ +RUN apt-get --yes --quiet --quiet update && \ + apt-get --yes --quiet --quiet install build-essential cmake libpcre2-dev python3-dev python3-cffi && \ + rm -rf /var/lib/apt/lists/* +RUN mkdir -p /var/libyang +RUN git clone https://github.com/CESNET/libyang.git /var/libyang +RUN mkdir -p /var/libyang/build +WORKDIR /var/libyang/build +RUN cmake -D CMAKE_BUILD_TYPE:String="Release" .. +RUN make +RUN make install +RUN ldconfig + # Create component sub-folders, get specific Python packages RUN mkdir -p /var/teraflow/device WORKDIR /var/teraflow/device @@ -62,9 +77,11 @@ RUN python3 -m pip install -r requirements.txt # Add component files into working directory WORKDIR /var/teraflow -COPY src/context/. context/ COPY src/device/. device/ -COPY src/monitoring/. monitoring/ +COPY src/context/__init__.py context/__init__.py +COPY src/context/client/. context/client/ +COPY src/monitoring/__init__.py monitoring/__init__.py +COPY src/monitoring/client/. monitoring/client/ # Start the service ENTRYPOINT ["python", "-m", "device.service"] diff --git a/src/device/requirements.in b/src/device/requirements.in index d8a33455e446b270ae5f1407c0d039fea889574f..20ed1e2dc611b312299ccd33ecf5b5b71a9400b2 100644 --- a/src/device/requirements.in +++ b/src/device/requirements.in @@ -12,30 +12,30 @@ # See the License for the specific language governing permissions and # limitations under the License. - anytree==2.8.0 APScheduler==3.10.1 bitarray==2.8.* cryptography==36.0.2 #fastcache==1.1.0 +ipaddress Jinja2==3.0.3 +libyang==2.8.0 +macaddress ncclient==0.6.13 p4runtime==1.3.0 pandas==1.5.* paramiko==2.9.2 +pyang==2.6.* +git+https://github.com/robshakir/pyangbind.git python-json-logger==2.0.2 #pytz==2021.3 #redis==4.1.2 requests==2.27.1 requests-mock==1.9.3 -xmltodict==0.12.0 tabulate -ipaddress -macaddress -yattag -pyang==2.6.* -git+https://github.com/robshakir/pyangbind.git websockets==10.4 +xmltodict==0.12.0 +yattag # pip's dependency resolver does not take into account installed packages. # p4runtime does not specify the version of grpcio/protobuf it needs, so it tries to install latest one diff --git a/src/device/service/drivers/gnmi_openconfig/handlers/Component.py b/src/device/service/drivers/gnmi_openconfig/handlers/Component.py index 73728192f3089bedffb092d0bea35f9ed6713cf7..9b92c6b83bb313f0042c74c0b2f5d6d3ace4c826 100644 --- a/src/device/service/drivers/gnmi_openconfig/handlers/Component.py +++ b/src/device/service/drivers/gnmi_openconfig/handlers/Component.py @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -import json, logging # libyang +import json, logging, re # libyang from typing import Any, Dict, List, Tuple from common.proto.kpi_sample_types_pb2 import KpiSampleType from ._Handler import _Handler @@ -55,7 +55,7 @@ class ComponentHandler(_Handler): # TODO: improve mapping between interface name and component name # By now, computed by time for the sake of saving time for the Hackfest. - interface_name = component_name.lower().replace('-port', '') + interface_name = re.sub(r'\-[pP][oO][rR][tT]', '', component_name) endpoint = {'uuid': interface_name, 'type': '-'} endpoint['sample_types'] = { diff --git a/src/device/service/drivers/gnmi_openconfig/handlers/NetworkInstance.py b/src/device/service/drivers/gnmi_openconfig/handlers/NetworkInstance.py index b9761298704f9e13e75ad7421370a7ce00349e82..d8231b2a6660fdf54b7beb42a269645759bf22a1 100644 --- a/src/device/service/drivers/gnmi_openconfig/handlers/NetworkInstance.py +++ b/src/device/service/drivers/gnmi_openconfig/handlers/NetworkInstance.py @@ -134,18 +134,15 @@ class NetworkInstanceHandler(_Handler): for static_route in static_routes: static_route_prefix = static_route['prefix'] - next_hops = static_route.get('next-hops', {}).get('next-hop', []) - _next_hops = [ - { - 'index' : next_hop['index'], - 'gateway': next_hop['config']['next-hop'], - 'metric' : next_hop['config']['metric'], + next_hops = { + next_hop['index'] : { + 'next_hop': next_hop['config']['next-hop'], + 'metric' : next_hop['config']['metric'], } - for next_hop in next_hops - ] - _next_hops = sorted(_next_hops, key=operator.itemgetter('index')) + for next_hop in static_route.get('next-hops', {}).get('next-hop', []) + } - _static_route = {'prefix': static_route_prefix, 'next_hops': _next_hops} + _static_route = {'prefix': static_route_prefix, 'next_hops': next_hops} entry_static_route_key = '{:s}/static_routes[{:s}]'.format( entry_protocol_key, static_route_prefix ) diff --git a/src/device/service/drivers/gnmi_openconfig/handlers/NetworkInstanceStaticRoute.py b/src/device/service/drivers/gnmi_openconfig/handlers/NetworkInstanceStaticRoute.py index 0343e3cbaab3f554f45590c3832bd89b6b552aa8..03c04e316845d6ba7f884755a9449ff7dba42411 100644 --- a/src/device/service/drivers/gnmi_openconfig/handlers/NetworkInstanceStaticRoute.py +++ b/src/device/service/drivers/gnmi_openconfig/handlers/NetworkInstanceStaticRoute.py @@ -39,8 +39,8 @@ class NetworkInstanceStaticRouteHandler(_Handler): str_data = json.dumps({}) return str_path, str_data - next_hop = get_str(resource_value, 'next_hop' ) # '172.0.0.1' - next_hop_index = get_int(resource_value, 'next_hop_index', 0) # 0 + next_hop = get_str(resource_value, 'next_hop' ) # '172.0.0.1' + next_hop_index = get_str(resource_value, 'next_hop_index') # AUTO_1_172-0-0-1 PATH_TMPL = '/network-instances/network-instance[name={:s}]/protocols/protocol[identifier={:s}][name={:s}]' str_path = PATH_TMPL.format(ni_name, identifier, name) @@ -74,7 +74,7 @@ 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="{:d}"]'.format(next_hop_index) + yang_ni_pr_sr_nh_path = 'next-hop[index="{:s}"]'.format(next_hop_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/next-hop', next_hop) diff --git a/src/device/tests/gnmi_openconfig/storage/StorageEndpoints.py b/src/device/tests/gnmi_openconfig/storage/StorageEndpoints.py index 815a1b0adda1a2e7bfae18ad297595403a725f24..d2596b73265c0dfc913de6f5bbc55189bf451333 100644 --- a/src/device/tests/gnmi_openconfig/storage/StorageEndpoints.py +++ b/src/device/tests/gnmi_openconfig/storage/StorageEndpoints.py @@ -39,13 +39,16 @@ class Endpoints: for _, field_names in Endpoints.STRUCT: field_names = set(field_names) - item.update({k:v for k,v in resource_value if k in field_names}) + item.update({k:v for k,v in resource_value.items() if k in field_names}) item['sample_types'] = { sample_type_id : sample_type_path.format(ep_uuid) for sample_type_id, sample_type_path in ENDPOINT_PACKET_SAMPLE_TYPES.items() } + def get(self, ep_uuid : str) -> Dict: + return self._items.get(ep_uuid) + def remove(self, ep_uuid : str) -> None: self._items.pop(ep_uuid, None) diff --git a/src/device/tests/gnmi_openconfig/storage/StorageInterface.py b/src/device/tests/gnmi_openconfig/storage/StorageInterface.py index a0391e92f1d0250638e5b14ead1357cc626ebb23..0933433cb406614ad00ab40030d424986ac2cf25 100644 --- a/src/device/tests/gnmi_openconfig/storage/StorageInterface.py +++ b/src/device/tests/gnmi_openconfig/storage/StorageInterface.py @@ -37,7 +37,10 @@ class Interfaces: item['name'] = if_name for _, field_names in Interfaces.STRUCT: field_names = set(field_names) - item.update({k:v for k,v in resource_value if k in field_names}) + item.update({k:v for k,v in resource_value.items() if k in field_names}) + + def get(self, if_name : str) -> Dict: + return self._items.get(if_name) def remove(self, if_name : str) -> None: self._items.pop(if_name, None) @@ -57,6 +60,9 @@ class SubInterfaces: item = self._items.setdefault((if_name, subif_index), dict()) item['index'] = subif_index + def get(self, if_name : str, subif_index : int) -> Dict: + return self._items.get((if_name, subif_index)) + def remove(self, if_name : str, subif_index : int) -> None: self._items.pop((if_name, subif_index), None) @@ -77,6 +83,9 @@ class IPv4Addresses: item['origin'] = resource_value.get('origin') item['prefix'] = resource_value.get('prefix') + def get(self, if_name : str, subif_index : int, ipv4_address : str) -> Dict: + return self._items.get((if_name, subif_index, ipv4_address)) + def remove(self, if_name : str, subif_index : int, ipv4_address : str) -> None: self._items.pop((if_name, subif_index, ipv4_address), None) diff --git a/src/device/tests/gnmi_openconfig/storage/StorageNetworkInstance.py b/src/device/tests/gnmi_openconfig/storage/StorageNetworkInstance.py index fa336488317776a10e20b283acfc1dcfbc3d7b8a..ba437ef9d59bbc273a57a0da7bdd8854bfdec58a 100644 --- a/src/device/tests/gnmi_openconfig/storage/StorageNetworkInstance.py +++ b/src/device/tests/gnmi_openconfig/storage/StorageNetworkInstance.py @@ -37,6 +37,9 @@ class NetworkInstances: item['name'] = ni_name item['type'] = resource_value.get('type') + def get(self, ni_name : str) -> Dict: + return self._items.get(ni_name) + def remove(self, ni_name : str) -> None: self._items.pop(ni_name, None) @@ -45,17 +48,24 @@ class NetworkInstances: class Interfaces: STRUCT : List[Tuple[str, List[str]]] = [ - ('/network_instance[{:s}]/interface[{:s}]', []), + ('/network_instance[{:s}]/interface[{:s}.{:d}]', ['name', 'id', 'if_name', 'sif_index']), ] def __init__(self) -> None: self._items : Dict[Tuple[str, str], Dict] = dict() - def add(self, ni_name : str, if_name : str) -> None: - self._items.setdefault((ni_name, if_name), dict()) + def add(self, ni_name : str, if_name : str, sif_index : int) -> None: + item = self._items.setdefault((ni_name, if_name, sif_index), dict()) + item['name' ] = ni_name + item['id' ] = '{:s}.{:d}'.format(if_name, sif_index) + item['if_name' ] = if_name + item['sif_index'] = sif_index + + def get(self, ni_name : str, if_name : str, sif_index : int) -> Dict: + return self._items.get((ni_name, if_name, sif_index)) - def remove(self, ni_name : str, if_name : str) -> None: - self._items.pop((ni_name, if_name), None) + def remove(self, ni_name : str, if_name : str, sif_index : int) -> None: + self._items.pop((ni_name, if_name, sif_index), None) def compose_resources(self) -> List[Dict]: return compose_resources(self._items, Interfaces.STRUCT) @@ -73,6 +83,9 @@ class Protocols: item['id' ] = protocol item['name'] = protocol + def get(self, ni_name : str, protocol : str) -> Dict: + return self._items.get((ni_name, protocol)) + def remove(self, ni_name : str, protocol : str) -> None: self._items.pop((ni_name, protocol), None) @@ -84,18 +97,16 @@ class StaticRoutes: ('/network_instance[{:s}]/protocol[{:s}]/static_routes[{:s}]', ['prefix', 'next_hops']), ] - #('/network_instance[{:s}]/static_route[{:s}]'.format(ni_name, static_route['prefix']), { - # 'name': ni_name, 'prefix': static_route['prefix'], 'next_hop': static_route['gateway'], - # 'next_hop_index': 0, 'metric': static_route['metric'] - #}) - def __init__(self) -> None: self._items : Dict[Tuple[str, str, str], Dict] = dict() def add(self, ni_name : str, protocol : str, prefix : str, resource_value : Dict) -> None: item = self._items.setdefault((ni_name, protocol, prefix), dict()) item['prefix' ] = prefix - item['next_hops'] = sorted(resource_value.get('next_hops')) + item['next_hops'] = resource_value.get('next_hops') + + def get(self, ni_name : str, protocol : str, prefix : str) -> Dict: + return self._items.get((ni_name, protocol, prefix)) def remove(self, ni_name : str, protocol : str, prefix : str) -> None: self._items.pop((ni_name, protocol, prefix), None) @@ -116,6 +127,9 @@ class Tables: item['protocol' ] = protocol item['address_family'] = address_family + def get(self, ni_name : str, protocol : str, address_family : str) -> Dict: + return self._items.get((ni_name, protocol, address_family)) + def remove(self, ni_name : str, protocol : str, address_family : str) -> None: self._items.pop((ni_name, protocol, address_family), None) @@ -136,6 +150,9 @@ class Vlans: item['name' ] = resource_value.get('name') item['members'] = sorted(resource_value.get('members')) + def get(self, ni_name : str, vlan_id : int) -> Dict: + return self._items.get((ni_name, vlan_id)) + def remove(self, ni_name : str, vlan_id : int) -> None: self._items.pop((ni_name, vlan_id), None) @@ -160,7 +177,11 @@ class StorageNetworkInstance: match = RE_RESKEY_INTERFACE.match(resource_key) if match is not None: - self.interfaces.add(match.group(1), match.group(2)) + if_id = match.group(2) + if_id_parts = if_id.split('.') + if_name = if_id_parts[0] + sif_index = 0 if len(if_id_parts) == 1 else int(if_id_parts[1]) + self.interfaces.add(match.group(1), if_name, sif_index) continue match = RE_RESKEY_PROTOCOL.match(resource_key) diff --git a/src/device/tests/gnmi_openconfig/storage/Tools.py b/src/device/tests/gnmi_openconfig/storage/Tools.py index 4da48af4680a6b608e29d61ae83f027261b16424..c9dab12e6366632154c59962163eb1fe8c3f2c91 100644 --- a/src/device/tests/gnmi_openconfig/storage/Tools.py +++ b/src/device/tests/gnmi_openconfig/storage/Tools.py @@ -21,6 +21,7 @@ def compose_resources( for resource_key_fields, resource_value_data in storage.items(): for resource_key_template, resource_key_field_names in config_struct: + if isinstance(resource_key_fields, (str, int, float, bool)): resource_key_fields = (resource_key_fields,) resource_key = resource_key_template.format(*resource_key_fields) resource_value = { field_name : resource_value_data[field_name] diff --git a/src/device/tests/gnmi_openconfig/test_unitary_gnmi_openconfig.py b/src/device/tests/gnmi_openconfig/test_unitary_gnmi_openconfig.py index 3970e65a63ba5325da37cc3ea20baf42936ad947..a601e1f234b984d5be7fd335ce2718a68276eb7d 100644 --- a/src/device/tests/gnmi_openconfig/test_unitary_gnmi_openconfig.py +++ b/src/device/tests/gnmi_openconfig/test_unitary_gnmi_openconfig.py @@ -17,11 +17,15 @@ os.environ['DEVICE_EMULATED_ONLY'] = 'YES' # pylint: disable=wrong-import-position import logging, pytest, time -from typing import Dict +from typing import Dict, List +from device.service.driver_api._Driver import ( + RESOURCE_ENDPOINTS, RESOURCE_INTERFACES, RESOURCE_NETWORK_INSTANCES, + RESOURCE_ROUTING_POLICIES, RESOURCE_SERVICES +) from device.service.drivers.gnmi_openconfig.GnmiOpenConfigDriver import GnmiOpenConfigDriver from .storage.Storage import Storage -from .tools.check_config import ( - check_config_endpoints, check_config_interfaces, check_config_network_instances +from .tools.manage_config import ( + check_config_endpoints, check_config_interfaces, check_config_network_instances, del_config, get_config, set_config ) from .tools.check_updates import check_updates from .tools.request_composers import ( @@ -69,26 +73,26 @@ NETWORK_INSTANCES = [ 'name': 'test-l3-svc', 'type': 'L3VRF', 'interfaces': [ - {'name': 'Ethernet1', 'subif_index': 0, 'ipv4_addr': '192.168.1.1', 'ipv4_prefix': 24, 'enabled': True}, - {'name': 'Ethernet10', 'subif_index': 0, 'ipv4_addr': '192.168.10.1', 'ipv4_prefix': 24, 'enabled': True}, + {'name': 'Ethernet1', 'index': 0, 'ipv4_addr': '192.168.1.1', 'ipv4_prefix': 24, 'enabled': True}, + {'name': 'Ethernet10', 'index': 0, 'ipv4_addr': '192.168.10.1', 'ipv4_prefix': 24, 'enabled': True}, ], 'static_routes': [ - {'prefix': '172.0.0.0/24', 'gateway': '172.16.0.2', 'metric': 1}, - {'prefix': '172.2.0.0/24', 'gateway': '172.16.0.3', 'metric': 1}, + {'prefix': '172.0.0.0/24', 'next_hop': '172.16.0.2', 'metric': 1}, + {'prefix': '172.2.0.0/24', 'next_hop': '172.16.0.3', 'metric': 1}, ] }, - { - 'name': 'test-l2-svc', - 'type': 'L2VSI', - 'interfaces': [ - {'name': 'Ethernet2', 'subif_index': 0, 'ipv4_addr': '192.168.1.1', 'ipv4_prefix': 24, 'enabled': True}, - {'name': 'Ethernet4', 'subif_index': 0, 'ipv4_addr': '192.168.10.1', 'ipv4_prefix': 24, 'enabled': True}, - ], - 'static_routes': [ - {'prefix': '172.0.0.0/24', 'gateway': '172.16.0.2', 'metric': 1}, - {'prefix': '172.2.0.0/24', 'gateway': '172.16.0.3', 'metric': 1}, - ] - } + #{ + # 'name': 'test-l2-svc', + # 'type': 'L2VSI', + # 'interfaces': [ + # {'name': 'Ethernet2', 'index': 0, 'ipv4_addr': '192.168.1.1', 'ipv4_prefix': 24, 'enabled': True}, + # {'name': 'Ethernet4', 'index': 0, 'ipv4_addr': '192.168.10.1', 'ipv4_prefix': 24, 'enabled': True}, + # ], + # 'static_routes': [ + # {'prefix': '172.0.0.0/24', 'next_hop': '172.16.0.2', 'metric': 1}, + # {'prefix': '172.2.0.0/24', 'next_hop': '172.16.0.3', 'metric': 1}, + # ] + #} ] @@ -98,7 +102,7 @@ def test_get_endpoints( driver : GnmiOpenConfigDriver, # pylint: disable=redefined-outer-name storage : Storage, # pylint: disable=redefined-outer-name ) -> None: - results_getconfig = check_config_endpoints(driver, storage) + results_getconfig = get_config(driver, [RESOURCE_ENDPOINTS]) storage.endpoints.populate(results_getconfig) check_config_endpoints(driver, storage) @@ -107,7 +111,7 @@ def test_get_interfaces( driver : GnmiOpenConfigDriver, # pylint: disable=redefined-outer-name storage : Storage, # pylint: disable=redefined-outer-name ) -> None: - results_getconfig = check_config_interfaces(driver, storage) + results_getconfig = get_config(driver, [RESOURCE_INTERFACES]) storage.interfaces.populate(results_getconfig) check_config_interfaces(driver, storage) @@ -116,7 +120,7 @@ def test_get_network_instances( driver : GnmiOpenConfigDriver, # pylint: disable=redefined-outer-name storage : Storage, # pylint: disable=redefined-outer-name ) -> None: - results_getconfig = check_config_network_instances(driver, storage) + results_getconfig = get_config(driver, [RESOURCE_NETWORK_INSTANCES]) storage.network_instances.populate(results_getconfig) check_config_network_instances(driver, storage) @@ -136,17 +140,18 @@ def test_set_network_instances( resources_to_set.append(network_instance(ni_name, ni_type)) ni_names.append(ni_name) storage.network_instances.network_instances.add(ni_name, {'type': ni_type}) - - LOGGER.info('resources_to_set = {:s}'.format(str(resources_to_set))) - results_setconfig = driver.SetConfig(resources_to_set) - LOGGER.info('results_setconfig = {:s}'.format(str(results_setconfig))) + storage.network_instances.protocols.add(ni_name, 'DIRECTLY_CONNECTED') + storage.network_instances.tables.add(ni_name, 'DIRECTLY_CONNECTED', 'IPV4') + storage.network_instances.tables.add(ni_name, 'DIRECTLY_CONNECTED', 'IPV6') + + results_setconfig = set_config(driver, resources_to_set) check_updates(results_setconfig, '/network_instance[{:s}]', ni_names) - check_config_interfaces(driver, storage, max_retries=5) - check_config_network_instances(driver, storage, max_retries=5) + check_config_interfaces(driver, storage, max_retries=10, retry_delay=2.0) + check_config_network_instances(driver, storage, max_retries=10, retry_delay=2.0) -def test_set_interfaces( +def test_add_interfaces_to_network_instance( driver : GnmiOpenConfigDriver, # pylint: disable=redefined-outer-name storage : Storage, # pylint: disable=redefined-outer-name ) -> None: @@ -154,33 +159,24 @@ def test_set_interfaces( check_config_network_instances(driver, storage) resources_to_set = list() - if_names = list() + ni_if_names = list() for ni in NETWORK_INSTANCES: ni_name = ni['name'] for ni_if in ni.get('interfaces', list()): - if_name = ni_if['if_name'] - subif_index = ni_if['sif_index'] - ipv4_address = ni_if['ipv4_addr'] - ipv4_prefix = ni_if['ipv4_prefix'] - enabled = ni_if['enabled'] - resources_to_set.append(interface( - if_name, subif_index, ipv4_address, ipv4_prefix, enabled - )) - if_names.append(ni_name) - storage.interfaces.ipv4_addresses.add(if_name, subif_index, ipv4_address, { - 'origin' : 'STATIC', 'prefix': ipv4_prefix - }) + if_name = ni_if['name' ] + subif_index = ni_if['index'] + resources_to_set.append(network_instance_interface(ni_name, if_name, subif_index)) + ni_if_names.append((ni_name, '{:s}.{:d}'.format(if_name, subif_index))) + storage.network_instances.interfaces.add(ni_name, if_name, subif_index) - LOGGER.info('resources_to_set = {:s}'.format(str(resources_to_set))) - results_setconfig = driver.SetConfig(resources_to_set) - LOGGER.info('results_setconfig = {:s}'.format(str(results_setconfig))) - check_updates(results_setconfig, '/interface[{:s}]', if_names) + results_setconfig = set_config(driver, resources_to_set) + check_updates(results_setconfig, '/network_instance[{:s}]/interface[{:s}]', ni_if_names) - check_config_interfaces(driver, storage, max_retries=5) - check_config_network_instances(driver, storage, max_retries=5) + check_config_interfaces(driver, storage, max_retries=10, retry_delay=2.0) + check_config_network_instances(driver, storage, max_retries=10, retry_delay=2.0) -def test_add_interfaces_to_network_instance( +def test_set_interfaces( driver : GnmiOpenConfigDriver, # pylint: disable=redefined-outer-name storage : Storage, # pylint: disable=redefined-outer-name ) -> None: @@ -188,23 +184,30 @@ def test_add_interfaces_to_network_instance( check_config_network_instances(driver, storage) resources_to_set = list() - ni_if_names = list() + if_names = list() for ni in NETWORK_INSTANCES: - ni_name = ni['name'] for ni_if in ni.get('interfaces', list()): - if_name = ni_if['if_name'] - subif_index = ni_if['sif_index'] - resources_to_set.append(network_instance_interface(ni_name, if_name, subif_index)) - ni_if_names.append((ni_name, '{:s}.{:d}'.format(if_name, subif_index))) - storage.network_instances.interfaces.add(ni_name, if_name) # TODO: add subif_index + if_name = ni_if['name' ] + subif_index = ni_if['index' ] + ipv4_address = ni_if['ipv4_addr' ] + ipv4_prefix = ni_if['ipv4_prefix'] + enabled = ni_if['enabled' ] + resources_to_set.append(interface( + if_name, subif_index, ipv4_address, ipv4_prefix, enabled + )) + if_names.append(if_name) + storage.interfaces.ipv4_addresses.add(if_name, subif_index, ipv4_address, { + 'origin' : 'STATIC', 'prefix': ipv4_prefix + }) + default_vlan = storage.network_instances.vlans.get('default', 1) + default_vlan_members : List[str] = default_vlan.setdefault('members', list()) + if if_name in default_vlan_members: default_vlan_members.remove(if_name) - LOGGER.info('resources_to_set = {:s}'.format(str(resources_to_set))) - results_setconfig = driver.SetConfig(resources_to_set) - LOGGER.info('results_setconfig = {:s}'.format(str(results_setconfig))) - check_updates(results_setconfig, '/network_instance[{:s}]/interface[{:s}]', ni_if_names) + results_setconfig = set_config(driver, resources_to_set) + check_updates(results_setconfig, '/interface[{:s}]', if_names) - check_config_interfaces(driver, storage, max_retries=5) - check_config_network_instances(driver, storage, max_retries=5) + check_config_interfaces(driver, storage, max_retries=10, retry_delay=2.0) + check_config_network_instances(driver, storage, max_retries=10, retry_delay=2.0) def test_set_network_instance_static_routes( @@ -219,24 +222,28 @@ def test_set_network_instance_static_routes( for ni in NETWORK_INSTANCES: ni_name = ni['name'] for ni_sr in ni.get('static_routes', list()): - ni_sr_prefix = ni_sr['prefix' ] - ni_sr_gateway = ni_sr['gateway'] - ni_sr_metric = ni_sr['metric' ] - resources_to_set.append( - network_instance_static_route(ni_name, ni_sr_prefix, ni_sr_gateway, metric=ni_sr_metric) - ) + ni_sr_prefix = ni_sr['prefix' ] + ni_sr_next_hop = ni_sr['next_hop'] + ni_sr_metric = ni_sr['metric' ] + ni_sr_next_hop_index = 'AUTO_{:d}_{:s}'.format(ni_sr_metric, '-'.join(ni_sr_next_hop.split('.'))) + resources_to_set.append(network_instance_static_route( + ni_name, ni_sr_prefix, ni_sr_next_hop_index, ni_sr_next_hop, metric=ni_sr_metric + )) ni_sr_prefixes.append((ni_name, ni_sr_prefix)) + storage.network_instances.protocols.add(ni_name, 'STATIC') storage.network_instances.protocol_static.add(ni_name, 'STATIC', ni_sr_prefix, { - 'prefix': ni_sr_prefix, + 'prefix': ni_sr_prefix, 'next_hops': { + ni_sr_next_hop_index: {'next_hop': ni_sr_next_hop, 'metric': ni_sr_metric} + } }) + storage.network_instances.tables.add(ni_name, 'STATIC', 'IPV4') + storage.network_instances.tables.add(ni_name, 'STATIC', 'IPV6') - LOGGER.info('resources_to_set = {:s}'.format(str(resources_to_set))) - results_setconfig = driver.SetConfig(resources_to_set) - LOGGER.info('results_setconfig = {:s}'.format(str(results_setconfig))) + results_setconfig = set_config(driver, resources_to_set) check_updates(results_setconfig, '/network_instance[{:s}]/static_route[{:s}]', ni_sr_prefixes) - check_config_interfaces(driver, storage, max_retries=5) - check_config_network_instances(driver, storage, max_retries=5) + check_config_interfaces(driver, storage, max_retries=10, retry_delay=2.0) + check_config_network_instances(driver, storage, max_retries=10, retry_delay=2.0) def test_del_network_instance_static_routes( @@ -251,79 +258,80 @@ def test_del_network_instance_static_routes( for ni in NETWORK_INSTANCES: ni_name = ni['name'] for ni_sr in ni.get('static_routes', list()): - ni_sr_prefix = ni_sr['prefix' ] - ni_sr_gateway = ni_sr['gateway'] - ni_sr_metric = ni_sr['metric' ] - resources_to_delete.append( - network_instance_static_route(ni_name, ni_sr_prefix, ni_sr_gateway, metric=ni_sr_metric) - ) + ni_sr_prefix = ni_sr['prefix' ] + ni_sr_next_hop = ni_sr['next_hop'] + ni_sr_metric = ni_sr['metric' ] + ni_sr_next_hop_index = 'AUTO_{:d}_{:s}'.format(ni_sr_metric, '-'.join(ni_sr_next_hop.split('.'))) + resources_to_delete.append(network_instance_static_route( + ni_name, ni_sr_prefix, ni_sr_next_hop_index, ni_sr_next_hop, metric=ni_sr_metric + )) ni_sr_prefixes.append((ni_name, ni_sr_prefix)) + + storage.network_instances.protocols.remove(ni_name, 'STATIC') storage.network_instances.protocol_static.remove(ni_name, 'STATIC', ni_sr_prefix) + storage.network_instances.tables.remove(ni_name, 'STATIC', 'IPV4') + storage.network_instances.tables.remove(ni_name, 'STATIC', 'IPV6') - LOGGER.info('resources_to_delete = {:s}'.format(str(resources_to_delete))) - results_deleteconfig = driver.DeleteConfig(resources_to_delete) - LOGGER.info('results_deleteconfig = {:s}'.format(str(results_deleteconfig))) + results_deleteconfig = del_config(driver, resources_to_delete) check_updates(results_deleteconfig, '/network_instance[{:s}]/static_route[{:s}]', ni_sr_prefixes) - check_config_interfaces(driver, storage, max_retries=5) - check_config_network_instances(driver, storage, max_retries=5) + check_config_interfaces(driver, storage, max_retries=10, retry_delay=2.0) + #check_config_network_instances(driver, storage, max_retries=10, retry_delay=2.0) -def test_del_interfaces_from_network_instance( +def test_del_interfaces( driver : GnmiOpenConfigDriver, # pylint: disable=redefined-outer-name storage : Storage, # pylint: disable=redefined-outer-name ) -> None: check_config_interfaces(driver, storage) - check_config_network_instances(driver, storage) + #check_config_network_instances(driver, storage) resources_to_delete = list() - ni_if_names = list() + if_names = list() for ni in NETWORK_INSTANCES: - ni_name = ni['name'] for ni_if in ni.get('interfaces', list()): - if_name = ni_if['if_name'] - subif_index = ni_if['sif_index'] - resources_to_delete.append(network_instance_interface(ni_name, if_name, subif_index)) - ni_if_names.append((ni_name, '{:s}.{:d}'.format(if_name, subif_index))) - storage.network_instances.interfaces.remove(ni_name, if_name) # TODO: add subif_index + if_name = ni_if['name' ] + subif_index = ni_if['index' ] + ipv4_address = ni_if['ipv4_addr' ] + ipv4_prefix = ni_if['ipv4_prefix'] + enabled = ni_if['enabled' ] + resources_to_delete.append(interface(if_name, subif_index, ipv4_address, ipv4_prefix, enabled)) + if_names.append(if_name) + storage.interfaces.ipv4_addresses.remove(if_name, subif_index, ipv4_address) + default_vlan = storage.network_instances.vlans.get('default', 1) + default_vlan_members : List[str] = default_vlan.setdefault('members', list()) + if if_name not in default_vlan_members: default_vlan_members.append(if_name) - LOGGER.info('resources_to_delete = {:s}'.format(str(resources_to_delete))) - results_deleteconfig = driver.DeleteConfig(resources_to_delete) - LOGGER.info('results_deleteconfig = {:s}'.format(str(results_deleteconfig))) - check_updates(results_deleteconfig, '/network_instance[{:s}]/interface[{:s}]', ni_if_names) - - check_config_interfaces(driver, storage, max_retries=5) - check_config_network_instances(driver, storage, max_retries=5) + results_deleteconfig = del_config(driver, resources_to_delete) + check_updates(results_deleteconfig, '/interface[{:s}]', if_names) + check_config_interfaces(driver, storage, max_retries=10, retry_delay=2.0) + #check_config_network_instances(driver, storage, max_retries=10, retry_delay=2.0) -def test_del_interfaces( + +def test_del_interfaces_from_network_instance( driver : GnmiOpenConfigDriver, # pylint: disable=redefined-outer-name storage : Storage, # pylint: disable=redefined-outer-name ) -> None: check_config_interfaces(driver, storage) - check_config_network_instances(driver, storage) + #check_config_network_instances(driver, storage) resources_to_delete = list() - if_names = list() + ni_if_names = list() for ni in NETWORK_INSTANCES: ni_name = ni['name'] for ni_if in ni.get('interfaces', list()): - if_name = ni_if['if_name'] - subif_index = ni_if['sif_index'] - ipv4_address = ni_if['ipv4_addr'] - ipv4_prefix = ni_if['ipv4_prefix'] - enabled = ni_if['enabled'] - resources_to_delete.append(interface(if_name, subif_index, ipv4_address, ipv4_prefix, enabled)) - if_names.append(ni_name) - storage.interfaces.ipv4_addresses.remove(if_name, subif_index, ipv4_address) - - LOGGER.info('resources_to_delete = {:s}'.format(str(resources_to_delete))) - results_deleteconfig = driver.DeleteConfig(resources_to_delete) - LOGGER.info('results_deleteconfig = {:s}'.format(str(results_deleteconfig))) - check_updates(results_deleteconfig, '/interface[{:s}]', if_names) + if_name = ni_if['name' ] + subif_index = ni_if['index'] + resources_to_delete.append(network_instance_interface(ni_name, if_name, subif_index)) + ni_if_names.append((ni_name, '{:s}.{:d}'.format(if_name, subif_index))) + storage.network_instances.interfaces.remove(ni_name, if_name, subif_index) - check_config_interfaces(driver, storage, max_retries=5) - check_config_network_instances(driver, storage, max_retries=5) + results_deleteconfig = del_config(driver, resources_to_delete) + check_updates(results_deleteconfig, '/network_instance[{:s}]/interface[{:s}]', ni_if_names) + + check_config_interfaces(driver, storage, max_retries=10, retry_delay=2.0) + #check_config_network_instances(driver, storage, max_retries=10, retry_delay=2.0) def test_del_network_instances( @@ -331,7 +339,7 @@ def test_del_network_instances( storage : Storage, # pylint: disable=redefined-outer-name ) -> None: check_config_interfaces(driver, storage) - check_config_network_instances(driver, storage) + #check_config_network_instances(driver, storage) resources_to_delete = list() ni_names = list() @@ -341,11 +349,12 @@ def test_del_network_instances( resources_to_delete.append(network_instance(ni_name, ni_type)) ni_names.append(ni_name) storage.network_instances.network_instances.remove(ni_name) + storage.network_instances.protocols.remove(ni_name, 'DIRECTLY_CONNECTED') + storage.network_instances.tables.remove(ni_name, 'DIRECTLY_CONNECTED', 'IPV4') + storage.network_instances.tables.remove(ni_name, 'DIRECTLY_CONNECTED', 'IPV6') - LOGGER.info('resources_to_delete = {:s}'.format(str(resources_to_delete))) - results_deleteconfig = driver.DeleteConfig(resources_to_delete) - LOGGER.info('results_deleteconfig = {:s}'.format(str(results_deleteconfig))) + results_deleteconfig = del_config(driver, resources_to_delete) check_updates(results_deleteconfig, '/network_instance[{:s}]', ni_names) - check_config_interfaces(driver, storage, max_retries=5) - check_config_network_instances(driver, storage, max_retries=5) + check_config_interfaces(driver, storage, max_retries=10, retry_delay=2.0) + check_config_network_instances(driver, storage, max_retries=10, retry_delay=2.0) diff --git a/src/device/tests/gnmi_openconfig/tools/check_updates.py b/src/device/tests/gnmi_openconfig/tools/check_updates.py index 7f31844cffb43e25b81f9bbb97f5c7313497550b..a9e2a1be930baab7fd3a8d4c98ea512b583b5119 100644 --- a/src/device/tests/gnmi_openconfig/tools/check_updates.py +++ b/src/device/tests/gnmi_openconfig/tools/check_updates.py @@ -17,5 +17,6 @@ from typing import Iterable, List, Tuple def check_updates(results : Iterable[Tuple[str, bool]], format_str : str, item_ids : List[Tuple]) -> None: results = set(results) assert len(results) == len(item_ids) - for item_id in item_ids: - assert (format_str.format(*item_id), True) in results + for item_id_fields in item_ids: + if isinstance(item_id_fields, (str, int, float, bool)): item_id_fields = (item_id_fields,) + assert (format_str.format(*item_id_fields), True) in results diff --git a/src/device/tests/gnmi_openconfig/tools/check_config.py b/src/device/tests/gnmi_openconfig/tools/manage_config.py similarity index 71% rename from src/device/tests/gnmi_openconfig/tools/check_config.py rename to src/device/tests/gnmi_openconfig/tools/manage_config.py index 5258da80c0e333c0a987d1f1d1543604dbebac92..72d6a09d323368cdf210701dfec8ae2fcd7ef55d 100644 --- a/src/device/tests/gnmi_openconfig/tools/check_config.py +++ b/src/device/tests/gnmi_openconfig/tools/manage_config.py @@ -13,7 +13,7 @@ # limitations under the License. import copy, deepdiff, logging, time -from typing import Callable, Dict, List, Tuple +from typing import Callable, Dict, List, Tuple, Union from device.service.driver_api._Driver import ( RESOURCE_ENDPOINTS, RESOURCE_INTERFACES, RESOURCE_NETWORK_INSTANCES, RESOURCE_ROUTING_POLICIES, RESOURCE_SERVICES @@ -24,6 +24,28 @@ from .result_config_adapters import adapt_endpoint, adapt_interface, adapt_netwo LOGGER = logging.getLogger(__name__) +def get_config(driver : GnmiOpenConfigDriver, resources_to_get : List[str]) -> List[Tuple[str, Dict]]: + LOGGER.info('[get_config] resources_to_get = {:s}'.format(str(resources_to_get))) + results_getconfig = driver.GetConfig(resources_to_get) + LOGGER.info('[get_config] results_getconfig = {:s}'.format(str(results_getconfig))) + return results_getconfig + +def set_config( + driver : GnmiOpenConfigDriver, resources_to_set : List[Tuple[str, Dict]] +) -> List[Tuple[str, Union[bool, Exception]]]: + LOGGER.info('[set_config] resources_to_set = {:s}'.format(str(resources_to_set))) + results_setconfig = driver.SetConfig(resources_to_set) + LOGGER.info('[set_config] results_setconfig = {:s}'.format(str(results_setconfig))) + return results_setconfig + +def del_config( + driver : GnmiOpenConfigDriver, resources_to_delete : List[Tuple[str, Dict]] +) -> List[Tuple[str, Union[bool, Exception]]]: + LOGGER.info('resources_to_delete = {:s}'.format(str(resources_to_delete))) + results_deleteconfig = driver.DeleteConfig(resources_to_delete) + LOGGER.info('results_deleteconfig = {:s}'.format(str(results_deleteconfig))) + return results_deleteconfig + def check_expected_config( driver : GnmiOpenConfigDriver, resources_to_get : List[str], expected_config : List[Dict], func_adapt_returned_config : Callable[[Tuple[str, Dict]], Tuple[str, Dict]] = lambda x: x, @@ -34,9 +56,7 @@ def check_expected_config( num_retry = 0 return_data = None while num_retry < max_retries: - LOGGER.info('resources_to_get = {:s}'.format(str(resources_to_get))) - results_getconfig = driver.GetConfig(resources_to_get) - LOGGER.info('results_getconfig = {:s}'.format(str(results_getconfig))) + results_getconfig = get_config(driver, resources_to_get) return_data = copy.deepcopy(results_getconfig) results_getconfig = [ @@ -49,7 +69,7 @@ def check_expected_config( if num_diffs == 0: break # let the device take some time to reconfigure time.sleep(retry_delay) - num_retry -= 1 + num_retry += 1 if num_diffs > 0: LOGGER.error('Differences[{:d}]:\n{:s}'.format(num_diffs, str(diff_data.pretty()))) assert num_diffs == 0 diff --git a/src/device/tests/gnmi_openconfig/tools/request_composers.py b/src/device/tests/gnmi_openconfig/tools/request_composers.py index faa8425c8afa04fe5e1e5fa4a3c641052d865d9c..be058710109580448ceea86e6a09df35b503ded8 100644 --- a/src/device/tests/gnmi_openconfig/tools/request_composers.py +++ b/src/device/tests/gnmi_openconfig/tools/request_composers.py @@ -29,10 +29,10 @@ def network_instance(ni_name, ni_type) -> Tuple[str, Dict]: } return str_path, str_data -def network_instance_static_route(ni_name, prefix, next_hop, next_hop_index=0, metric=1) -> Tuple[str, Dict]: +def network_instance_static_route(ni_name, prefix, next_hop_index, next_hop, metric=1) -> Tuple[str, Dict]: str_path = '/network_instance[{:s}]/static_route[{:s}]'.format(ni_name, prefix) str_data = { - 'name': ni_name, 'prefix': prefix, 'next_hop': next_hop, 'next_hop_index': next_hop_index, 'metric': metric + 'name': ni_name, 'prefix': prefix, 'next_hop_index': next_hop_index, 'next_hop': next_hop, 'metric': metric } return str_path, str_data