Commit 03f748da authored by Lluis Gifre Renom's avatar Lluis Gifre Renom
Browse files

Device component - GNMI OpenConfig:

- Updated unitary tests
parent 74a75434
Loading
Loading
Loading
Loading
+0 −285
Original line number Diff line number Diff line
# Copyright 2022-2023 ETSI TeraFlowSDN - TFS OSG (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 pytest, re
from typing import Dict, List, Tuple

@pytest.fixture(scope='session')
def storage() -> Dict:
    yield dict()


##### POPULATE INTERFACE STORAGE #######################################################################################

def populate_interfaces_storage(
    storage : Dict,                         # pylint: disable=redefined-outer-name
    resources : List[Tuple[str, Dict]],
) -> None:
    interfaces_storage     : Dict = storage.setdefault('interfaces',                            dict())
    subinterfaces_storage  : Dict = storage.setdefault('interface_subinterfaces',               dict())
    ipv4_addresses_storage : Dict = storage.setdefault('interface_subinterface_ipv4_addresses', dict())

    for resource_key, resource_value in resources:
        match = re.match(r'^\/interface\[([^\]]+)\]$', resource_key)
        if match is not None:
            if_name = match.group(1)
            if_storage = interfaces_storage.setdefault(if_name, dict())
            if_storage['name'         ] = if_name
            if_storage['type'         ] = resource_value.get('type'         )
            if_storage['admin-status' ] = resource_value.get('admin-status' )
            if_storage['oper-status'  ] = resource_value.get('oper-status'  )
            if_storage['ifindex'      ] = resource_value.get('ifindex'      )
            if_storage['mtu'          ] = resource_value.get('mtu'          )
            if_storage['management'   ] = resource_value.get('management'   )
            if_storage['hardware-port'] = resource_value.get('hardware-port')
            if_storage['transceiver'  ] = resource_value.get('transceiver'  )
            continue

        match = re.match(r'^\/interface\[([^\]]+)\]\/ethernet$', resource_key)
        if match is not None:
            if_name = match.group(1)
            if_storage = interfaces_storage.setdefault(if_name, dict())
            if_storage['port-speed'           ] = resource_value.get('port-speed'           )
            if_storage['negotiated-port-speed'] = resource_value.get('negotiated-port-speed')
            if_storage['mac-address'          ] = resource_value.get('mac-address'          )
            if_storage['hw-mac-address'       ] = resource_value.get('hw-mac-address'       )
            continue

        match = re.match(r'^\/interface\[([^\]]+)\]\/subinterface\[([^\]]+)\]$', resource_key)
        if match is not None:
            if_name = match.group(1)
            subif_index = int(match.group(2))
            subif_storage = subinterfaces_storage.setdefault((if_name, subif_index), dict())
            subif_storage['index'] = subif_index
            continue

        match = re.match(r'^\/interface\[([^\]]+)\]\/subinterface\[([^\]]+)\]\/ipv4\[([^\]]+)\]$', resource_key)
        if match is not None:
            if_name = match.group(1)
            subif_index = int(match.group(2))
            ipv4_addr = match.group(3)
            ipv4_address_storage = ipv4_addresses_storage.setdefault((if_name, subif_index, ipv4_addr), dict())
            ipv4_address_storage['ip'    ] = ipv4_addr
            ipv4_address_storage['origin'] = resource_value.get('origin')
            ipv4_address_storage['prefix'] = resource_value.get('prefix')
            continue


##### POPULATE NETWORK INSTANCE STORAGE ################################################################################

def populate_network_instances_storage(
    storage : Dict,                         # pylint: disable=redefined-outer-name
    resources : List[Tuple[str, Dict]],
) -> None:
    network_instances_storage                : Dict = storage.setdefault('network_instances',                dict())
    network_instance_protocols_storage       : Dict = storage.setdefault('network_instance_protocols',       dict())
    network_instance_protocol_static_storage : Dict = storage.setdefault('network_instance_protocol_static', dict())
    network_instance_tables_storage          : Dict = storage.setdefault('network_instance_tables',          dict())
    network_instance_vlans_storage           : Dict = storage.setdefault('network_instance_vlans',           dict())

    for resource_key, resource_value in resources:
        match = re.match(r'^\/network\_instance\[([^\]]+)\]$', resource_key)
        if match is not None:
            name = match.group(1)
            ni_storage = network_instances_storage.setdefault(name, dict())
            ni_storage['name'] = name
            ni_storage['type'] = resource_value.get('type')
            continue

        match = re.match(r'^\/network\_instance\[([^\]]+)\]\/protocol\[([^\]]+)\]$', resource_key)
        if match is not None:
            name = match.group(1)
            protocol = match.group(2)
            ni_p_storage = network_instance_protocols_storage.setdefault((name, protocol), dict())
            ni_p_storage['id'  ] = protocol
            ni_p_storage['name'] = protocol
            continue

        pattern = r'^\/network\_instance\[([^\]]+)\]\/protocol\[([^\]]+)\]\/static\_routes\[([^\]]+)\]$'
        match = re.match(pattern, resource_key)
        if match is not None:
            name = match.group(1)
            protocol = match.group(2)
            prefix = match.group(3)
            ni_p_s_storage = network_instance_protocol_static_storage.setdefault((name, protocol, prefix), dict())
            ni_p_s_storage['prefix'   ] = prefix
            ni_p_s_storage['next_hops'] = sorted(resource_value.get('next_hops'))
            continue

        match = re.match(r'^\/network\_instance\[([^\]]+)\]\/table\[([^\,]+)\,([^\]]+)\]$', resource_key)
        if match is not None:
            name = match.group(1)
            protocol = match.group(2)
            address_family = match.group(3)
            ni_t_storage = network_instance_tables_storage.setdefault((name, protocol, address_family), dict())
            ni_t_storage['protocol'      ] = protocol
            ni_t_storage['address_family'] = address_family
            continue

        match = re.match(r'^\/network\_instance\[([^\]]+)\]\/vlan\[([^\]]+)\]$', resource_key)
        if match is not None:
            name = match.group(1)
            vlan_id = int(match.group(2))
            ni_v_storage = network_instance_vlans_storage.setdefault((name, vlan_id), dict())
            ni_v_storage['vlan_id'] = vlan_id
            ni_v_storage['name'   ] = resource_value.get('name')
            ni_v_storage['members'] = sorted(resource_value.get('members'))
            continue


##### GET EXPECTED INTERFACE CONFIG ####################################################################################

INTERFACE_CONFIG_STRUCTURE : List[Tuple[str, List[str]]] = [
    ('/interface[{if_name:s}]', [
        'name', 'type', 'admin-status', 'oper-status', 'management', 'mtu', 'ifindex', 'hardware-port', 'transceiver'
    ]),
    ('/interface[{if_name:s}]/ethernet', [
        'port-speed', 'negotiated-port-speed', 'mac-address', 'hw-mac-address'
    ]),
]

INTERFACE_SUBINTERFACE_CONFIG_STRUCTURE : List[Tuple[str, List[str]]] = [
    ('/interface[{if_name:s}]/subinterface[{subif_index:d}]', ['index']),
]

INTERFACE_SUBINTERFACE_IPV4_ADDRESS_CONFIG_STRUCTURE : List[Tuple[str, List[str]]] = [
    ('/interface[{if_name:s}]/subinterface[{subif_index:d}]/ipv4[{ipv4_addr:s}]', ['ip', 'origin', 'prefix']),
]

def get_expected_interface_config(
    storage : Dict,                         # pylint: disable=redefined-outer-name
) -> List[Tuple[str, Dict]]:
    interfaces_storage     : Dict = storage.setdefault('interfaces',                            dict())
    subinterfaces_storage  : Dict = storage.setdefault('interface_subinterfaces',               dict())
    ipv4_addresses_storage : Dict = storage.setdefault('interface_subinterface_ipv4_addresses', dict())

    expected_interface_config = list()
    for if_name, if_storage in interfaces_storage.items():
        for resource_key_template, resource_key_field_names in INTERFACE_CONFIG_STRUCTURE:
            resource_key = resource_key_template.format(if_name=if_name)
            resource_value = {
                field_name : if_storage[field_name]
                for field_name in resource_key_field_names
                if field_name in if_storage and if_storage[field_name] is not None
            }
            expected_interface_config.append((resource_key, resource_value))

    for (if_name, subif_index), subif_storage in subinterfaces_storage.items():
        for resource_key_template, resource_key_field_names in INTERFACE_SUBINTERFACE_CONFIG_STRUCTURE:
            resource_key = resource_key_template.format(if_name=if_name, subif_index=subif_index)
            resource_value = {
                field_name : subif_storage[field_name]
                for field_name in resource_key_field_names
                if field_name in subif_storage and subif_storage[field_name] is not None
            }
            expected_interface_config.append((resource_key, resource_value))

    for (if_name, subif_index, ipv4_addr), ipv4_storage in ipv4_addresses_storage.items():
        for resource_key_template, resource_key_field_names in INTERFACE_SUBINTERFACE_IPV4_ADDRESS_CONFIG_STRUCTURE:
            resource_key = resource_key_template.format(if_name=if_name, subif_index=subif_index, ipv4_addr=ipv4_addr)
            resource_value = {
                field_name : ipv4_storage[field_name]
                for field_name in resource_key_field_names
                if field_name in ipv4_storage and ipv4_storage[field_name] is not None
            }
            expected_interface_config.append((resource_key, resource_value))

    return expected_interface_config


##### GET EXPECTED NETWORK INSTANCE CONFIG #############################################################################

NETWORK_INSTANCE_CONFIG_STRUCTURE : List[Tuple[str, List[str]]] = [
    ('/network_instance[{ni_name:s}]', ['name', 'type']),
]

NETWORK_INSTANCE_PROTOCOL_CONFIG_STRUCTURE : List[Tuple[str, List[str]]] = [
    ('/network_instance[{ni_name:s}]/protocol[{protocol:s}]', ['id', 'name']),
]

NETWORK_INSTANCE_PROTOCOL_STATIC_CONFIG_STRUCTURE : List[Tuple[str, List[str]]] = [
    ('/network_instance[{ni_name:s}]/protocol[{protocol:s}]/static_routes[{prefix:s}]', ['prefix', 'next_hops']),
]

NETWORK_INSTANCE_TABLE_CONFIG_STRUCTURE : List[Tuple[str, List[str]]] = [
    ('/network_instance[{ni_name:s}]/table[{protocol:s},{address_family:s}]', ['protocol', 'address_family']),
]

NETWORK_INSTANCE_VLAN_CONFIG_STRUCTURE : List[Tuple[str, List[str]]] = [
    ('/network_instance[{ni_name:s}]/vlan[{vlan_id:d}]', ['vlan_id', 'name', 'members']),
]

def get_expected_network_instance_config(
    storage : Dict,                         # pylint: disable=redefined-outer-name
) -> List[Tuple[str, Dict]]:
    network_instances_storage                : Dict = storage.setdefault('network_instances',                dict())
    network_instance_protocols_storage       : Dict = storage.setdefault('network_instance_protocols',       dict())
    network_instance_protocol_static_storage : Dict = storage.setdefault('network_instance_protocol_static', dict())
    network_instance_tables_storage          : Dict = storage.setdefault('network_instance_tables',          dict())
    network_instance_vlans_storage           : Dict = storage.setdefault('network_instance_vlans',           dict())

    expected_network_instance_config = list()
    for ni_name, ni_storage in network_instances_storage.items():
        for resource_key_template, resource_key_field_names in NETWORK_INSTANCE_CONFIG_STRUCTURE:
            resource_key = resource_key_template.format(ni_name=ni_name)
            resource_value = {
                field_name : ni_storage[field_name]
                for field_name in resource_key_field_names
                if field_name in ni_storage and ni_storage[field_name] is not None
            }
            expected_network_instance_config.append((resource_key, resource_value))

    for (ni_name, protocol), ni_p_storage in network_instance_protocols_storage.items():
        for resource_key_template, resource_key_field_names in NETWORK_INSTANCE_PROTOCOL_CONFIG_STRUCTURE:
            resource_key = resource_key_template.format(ni_name=ni_name, protocol=protocol)
            resource_value = {
                field_name : ni_p_storage[field_name]
                for field_name in resource_key_field_names
                if field_name in ni_p_storage and ni_p_storage[field_name] is not None
            }
            expected_network_instance_config.append((resource_key, resource_value))

    for (ni_name, protocol, prefix), ni_p_s_storage in network_instance_protocol_static_storage.items():
        for resource_key_template, resource_key_field_names in NETWORK_INSTANCE_PROTOCOL_STATIC_CONFIG_STRUCTURE:
            resource_key = resource_key_template.format(ni_name=ni_name, protocol=protocol, prefix=prefix)
            resource_value = {
                field_name : ni_p_s_storage[field_name]
                for field_name in resource_key_field_names
                if field_name in ni_p_s_storage and ni_p_s_storage[field_name] is not None
            }
            expected_network_instance_config.append((resource_key, resource_value))

    for (ni_name, protocol, address_family), ni_t_storage in network_instance_tables_storage.items():
        for resource_key_template, resource_key_field_names in NETWORK_INSTANCE_TABLE_CONFIG_STRUCTURE:
            resource_key = resource_key_template.format(
                ni_name=ni_name, protocol=protocol, address_family=address_family
            )
            resource_value = {
                field_name : ni_t_storage[field_name]
                for field_name in resource_key_field_names
                if field_name in ni_t_storage and ni_t_storage[field_name] is not None
            }
            expected_network_instance_config.append((resource_key, resource_value))

    for (ni_name, vlan_id), ni_v_storage in network_instance_vlans_storage.items():
        for resource_key_template, resource_key_field_names in NETWORK_INSTANCE_VLAN_CONFIG_STRUCTURE:
            resource_key = resource_key_template.format(ni_name=ni_name, vlan_id=vlan_id)
            resource_value = {
                field_name : ni_v_storage[field_name]
                for field_name in resource_key_field_names
                if field_name in ni_v_storage and ni_v_storage[field_name] is not None
            }
            expected_network_instance_config.append((resource_key, resource_value))

    return expected_network_instance_config
+23 −0
Original line number Diff line number Diff line
# Copyright 2022-2023 ETSI TeraFlowSDN - TFS OSG (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.

from .StorageEndpoints import StorageEndpoints
from .StorageInterface import StorageInterface
from .StorageNetworkInstance import StorageNetworkInstance

class Storage:
    def __init__(self) -> None:
        self.endpoints         = StorageEndpoints()
        self.interfaces        = StorageInterface()
        self.network_instances = StorageNetworkInstance()
+72 −0
Original line number Diff line number Diff line
# Copyright 2022-2023 ETSI TeraFlowSDN - TFS OSG (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 re
from typing import Dict, List, Tuple
from .Tools import compose_resources

RE_RESKEY_ENDPOINT = re.compile(r'^\/endpoints\/endpoint\[([^\]]+)\]$')

ENDPOINT_PACKET_SAMPLE_TYPES : Dict[int, str] = {
    101: '/openconfig-interfaces:interfaces/interface[name={:s}]/state/counters/out-pkts',
    102: '/openconfig-interfaces:interfaces/interface[name={:s}]/state/counters/in-pkts',
    201: '/openconfig-interfaces:interfaces/interface[name={:s}]/state/counters/out-octets',
    202: '/openconfig-interfaces:interfaces/interface[name={:s}]/state/counters/in-octets',
}

class Endpoints:
    STRUCT : List[Tuple[str, List[str]]] = [
        ('/endpoints/endpoint[{:s}]', ['uuid', 'type', 'sample_types']),
    ]

    def __init__(self) -> None:
        self._items : Dict[str, Dict] = dict()

    def add(self, ep_uuid : str, resource_value : Dict) -> None:
        item = self._items.setdefault(ep_uuid, dict())
        item['uuid'] = ep_uuid

        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['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 remove(self, ep_uuid : str) -> None:
        self._items.pop(ep_uuid, None)
    
    def compose_resources(self) -> List[Dict]:
        return compose_resources(self._items, Endpoints.STRUCT)

class StorageEndpoints:
    def __init__(self) -> None:
        self.endpoints = Endpoints()

    def populate(self, resources : List[Tuple[str, Dict]]) -> None:
        for resource_key, resource_value in resources:
            match = RE_RESKEY_ENDPOINT.match(resource_key)
            if match is not None:
                self.endpoints.add(match.group(1), resource_value)
                continue

            MSG = 'Unhandled Resource Key: {:s} => {:s}'
            raise Exception(MSG.format(str(resource_key), str(resource_value)))

    def get_expected_config(self) -> List[Tuple[str, Dict]]:
        expected_config = list()
        expected_config.extend(self.endpoints.compose_resources())
        return expected_config
+122 −0
Original line number Diff line number Diff line
# Copyright 2022-2023 ETSI TeraFlowSDN - TFS OSG (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 re
from typing import Dict, List, Tuple
from .Tools import compose_resources

PREFIX = r'^\/interface\[([^\]]+)\]'
RE_RESKEY_INTERFACE    = re.compile(PREFIX + r'$')
RE_RESKEY_ETHERNET     = re.compile(PREFIX + r'\/ethernet$')
RE_RESKEY_SUBINTERFACE = re.compile(PREFIX + r'\/subinterface\[([^\]]+)\]$')
RE_RESKEY_IPV4_ADDRESS = re.compile(PREFIX + r'\/subinterface\[([^\]]+)\]\/ipv4\[([^\]]+)\]$')

class Interfaces:
    STRUCT : List[Tuple[str, List[str]]] = [
        ('/interface[{:s}]', ['name', 'type', 'admin-status', 'oper-status', 'management', 'mtu', 'ifindex',
                              'hardware-port', 'transceiver']),
        ('/interface[{:s}]/ethernet', ['port-speed', 'negotiated-port-speed', 'mac-address', 'hw-mac-address']),
    ]

    def __init__(self) -> None:
        self._items : Dict[str, Dict] = dict()

    def add(self, if_name : str, resource_value : Dict) -> None:
        item = self._items.setdefault(if_name, dict())
        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})

    def remove(self, if_name : str) -> None:
        self._items.pop(if_name, None)
    
    def compose_resources(self) -> List[Dict]:
        return compose_resources(self._items, Interfaces.STRUCT)

class SubInterfaces:
    STRUCT : List[Tuple[str, List[str]]] = [
        ('/interface[{:s}]/subinterface[{:d}]', ['index']),
    ]

    def __init__(self) -> None:
        self._items : Dict[Tuple[str, int], Dict] = dict()

    def add(self, if_name : str, subif_index : int) -> None:
        item = self._items.setdefault((if_name, subif_index), dict())
        item['index'] = subif_index

    def remove(self, if_name : str, subif_index : int) -> None:
        self._items.pop((if_name, subif_index), None)
    
    def compose_resources(self) -> List[Dict]:
        return compose_resources(self._items, SubInterfaces.STRUCT)

class IPv4Addresses:
    STRUCT : List[Tuple[str, List[str]]] = [
        ('/interface[{:s}]/subinterface[{:d}]/ipv4[{:s}]', ['ip', 'origin', 'prefix']),
    ]

    def __init__(self) -> None:
        self._items : Dict[Tuple[str, int, str], Dict] = dict()

    def add(self, if_name : str, subif_index : int, ipv4_address : str, resource_value : Dict) -> None:
        item = self._items.setdefault((if_name, subif_index, ipv4_address), dict())
        item['ip'    ] = ipv4_address
        item['origin'] = resource_value.get('origin')
        item['prefix'] = resource_value.get('prefix')

    def remove(self, if_name : str, subif_index : int, ipv4_address : str) -> None:
        self._items.pop((if_name, subif_index, ipv4_address), None)

    def compose_resources(self) -> List[Dict]:
        return compose_resources(self._items, IPv4Addresses.STRUCT)

class StorageInterface:
    def __init__(self) -> None:
        self.interfaces     = Interfaces()
        self.subinterfaces  = SubInterfaces()
        self.ipv4_addresses = IPv4Addresses()

    def populate(self, resources : List[Tuple[str, Dict]]) -> None:
        for resource_key, resource_value in resources:
            match = RE_RESKEY_INTERFACE.match(resource_key)
            if match is not None:
                self.interfaces.add(match.group(1), resource_value)
                continue

            match = RE_RESKEY_ETHERNET.match(resource_key)
            if match is not None:
                self.interfaces.add(match.group(1), resource_value)
                continue

            match = RE_RESKEY_SUBINTERFACE.match(resource_key)
            if match is not None:
                self.subinterfaces.add(match.group(1), int(match.group(2)))
                continue

            match = RE_RESKEY_IPV4_ADDRESS.match(resource_key)
            if match is not None:
                self.ipv4_addresses.add(match.group(1), int(match.group(2)), match.group(3), resource_value)
                continue

            MSG = 'Unhandled Resource Key: {:s} => {:s}'
            raise Exception(MSG.format(str(resource_key), str(resource_value)))

    def get_expected_config(self) -> List[Tuple[str, Dict]]:
        expected_config = list()
        expected_config.extend(self.interfaces.compose_resources())
        expected_config.extend(self.subinterfaces.compose_resources())
        expected_config.extend(self.ipv4_addresses.compose_resources())
        return expected_config
+194 −0

File added.

Preview size limit exceeded, changes collapsed.

Loading