Commit aa2579a6 authored by Lluis Gifre Renom's avatar Lluis Gifre Renom
Browse files

Device component- IETF L2VPN Driver:

- Added DebugAPI client used to acquire remote controller topology info
- Extended skeleton and preliminary implemented driver methods
- Extended WIM connector with a method to get a specific service
parent faa68396
Loading
Loading
Loading
Loading
+55 −43
Original line number Diff line number Diff line
@@ -15,23 +15,29 @@
import logging, threading
from typing import Any, Iterator, List, Optional, Tuple, Union
from common.method_wrappers.Decorator import MetricsPool, metered_subclass_method
from common.tools.object_factory.Device import json_device_id
from common.tools.object_factory.EndPoint import json_endpoint_id
from common.type_checkers.Checkers import chk_string, chk_type
from device.service.driver_api._Driver import _Driver, RESOURCE_ENDPOINTS, RESOURCE_SERVICES
from device.service.drivers.ietf_l2vpn.TfsDebugApiClient import TfsDebugApiClient
from .Tools import connection_point, find_key, wim_mapping
from .WimconnectorIETFL2VPN import WimconnectorIETFL2VPN

LOGGER = logging.getLogger(__name__)

def process_endpoint(method : str, endpoint : Any) -> Any:
    LOGGER.warning('[{:s}][process_endpoint] endpoint={:s}'.format(str(method), str(endpoint)))
    return endpoint
def process_connectivity_services(method : str, services : Any) -> Any:
    LOGGER.warning('[{:s}][process_connectivity_services] services={:s}'.format(str(method), str(services)))
    return services

def process_connectivity_service(method : str, service : Any) -> Any:
    LOGGER.warning('[{:s}][process_connectivity_service] service={:s}'.format(str(method), str(service)))
    return service

def service_exists(param : Any) -> bool:
    LOGGER.warning('[service_exists] param={:s}'.format(str(param)))
def service_exists(wim : WimconnectorIETFL2VPN, service_uuid : str) -> bool:
    try:
        wim.get_connectivity_service_status(service_uuid)
        return True
    except: # pylint: disable=bare-except
        return False

ALL_RESOURCE_KEYS = [
@@ -55,6 +61,7 @@ class IetfL2VpnDriver(_Driver):
        wim_account = {'user': username, 'password': password}
        # Mapping updated dynamically with each request
        config = {'mapping_not_needed': False, 'service_endpoint_mapping': []}
        self.dac = TfsDebugApiClient(address, int(port), scheme=scheme, username=username, password=password)
        self.wim = WimconnectorIETFL2VPN(wim, wim_account, config=config)
        self.conn_info = {} # internal database emulating OSM storage provided to WIM Connectors

@@ -88,20 +95,22 @@ class IetfL2VpnDriver(_Driver):
            if len(resource_keys) == 0: resource_keys = ALL_RESOURCE_KEYS
            for i, resource_key in enumerate(resource_keys):
                str_resource_name = 'resource_key[#{:d}]'.format(i)
                try:
                    chk_string(str_resource_name, resource_key, allow_empty=False)

                    if resource_key == RESOURCE_ENDPOINTS:
                        # return endpoints through debug-api and list-devices method
                    endpoints = self.debug_api.get_endpoints()
                    for endpoint in endpoints: results.append(process_endpoint('GetConfig', endpoint))
                        results.extend(self.dac.get_devices_endpoints())
                    elif resource_key == RESOURCE_SERVICES:
                        # return all services through 
                    services = self.wim.get_all_active_connectivity_services()
                    for service in services: results.append(process_connectivity_service('GetConfig', service))
                        reply = self.wim.get_all_active_connectivity_services()
                        results.extend(process_connectivity_services('GetConfig', reply.json()))
                    else:
                        # assume single-service retrieval
                    service = self.wim.get_connectivity_service()
                    results.append(process_connectivity_service('GetConfig', service))
                        reply = self.wim.get_connectivity_service(resource_key)
                        results.append(process_connectivity_service('GetConfig', reply.json()))
                except Exception as e: # pylint: disable=broad-except
                    LOGGER.exception('Unhandled error processing resource_key({:s})'.format(str(resource_key)))
                    results.append((resource_key, e))
        return results

    @metered_subclass_method(METRICS_POOL)
@@ -114,32 +123,35 @@ class IetfL2VpnDriver(_Driver):
                LOGGER.info('resource = {:s}'.format(str(resource)))

                service_uuid = find_key(resource, 'uuid')
                a_endpoint = find_key(resource, 'a_endpoint')
                z_endpoint = find_key(resource, 'z_endpoint')
                #capacity_value = find_key(resource, 'capacity_value')
                #capacity_unit = find_key(resource, 'capacity_unit')
                #layer_protocol_name = find_key(resource, 'layer_protocol_name')
                #layer_protocol_qualifier = find_key(resource, 'layer_protocol_qualifier')
                #direction = find_key(resource, 'direction')
                encapsulation_type = find_key(resource, 'encapsulation_type')

                if service_exists(self.wim, service_uuid):
                    exc = NotImplementedError('IETF L2VPN Service Update is still not supported')
                    results.append((resource[0], exc))
                    continue

                src_device_uuid   = find_key(resource, 'src_device_uuid')
                src_endpoint_uuid = find_key(resource, 'src_endpoint_uuid')
                dst_device_uuid   = find_key(resource, 'dst_device_uuid')
                dst_endpoint_uuid = find_key(resource, 'dst_endpoint_uuid')
                encap_type        = find_key(resource, 'encapsulation_type')
                vlan_id           = find_key(resource, 'vlan_id')

                conn_info = {}
                src_endpoint_id = json_endpoint_id(json_device_id(src_device_uuid), src_endpoint_uuid)
                src_service_endpoint_id, src_mapping = wim_mapping('1', src_endpoint_id)
                self.wim.mappings[src_service_endpoint_id] = src_mapping

                result = self.wim.get_connectivity_service_status(
                    service_uuid, conn_info=conn_info)
                dst_endpoint_id = json_endpoint_id(json_device_id(dst_device_uuid), dst_endpoint_uuid)
                dst_service_endpoint_id, dst_mapping = wim_mapping('2', dst_endpoint_id)
                self.wim.mappings[dst_service_endpoint_id] = dst_mapping

                connection_points = [
                    connection_point(src_service_endpoint_id, encap_type, vlan_id),
                    connection_point(dst_service_endpoint_id, encap_type, vlan_id),
                ]

                result = self.wim.create_connectivity_service(SERVICE_TYPE, connection_points)
                LOGGER.info('[SetConfig] CREATE result={:s}'.format(str(result)))

                connection_points = []
                for endpoint_id in [a_endpoint, z_endpoint]:
                    site_id = str(endpoint_id)
                    self.wim.mappings[endpoint_id] = wim_mapping(site_id, endpoint_id)
                    connection_points.append(connection_point(endpoint_id, encapsulation_type, vlan_id))
                if service_exists(result):
                    result = self.wim.create_connectivity_service(
                        SERVICE_TYPE, connection_points)
                else:
                    self.wim.edit_connectivity_service(
                        service_uuid, conn_info=conn_info, connection_points=connection_points)
                results.extend(process_connectivity_service('SetConfig', None))
        return results

+92 −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 logging, requests
from requests.auth import HTTPBasicAuth
from typing import Dict, List, Optional

GET_DEVICES_URL = '{:s}://{:s}:{:d}/restconf/debug-api/devices'
TIMEOUT = 30

HTTP_OK_CODES = {
    200,    # OK
    201,    # Created
    202,    # Accepted
    204,    # No Content
}

MAPPING_STATUS = {
    'DEVICEOPERATIONALSTATUS_UNDEFINED': 0,
    'DEVICEOPERATIONALSTATUS_DISABLED' : 1,
    'DEVICEOPERATIONALSTATUS_ENABLED'  : 2,
}

MAPPING_DRIVER = {
    'DEVICEDRIVER_UNDEFINED'            : 0,
    'DEVICEDRIVER_OPENCONFIG'           : 1,
    'DEVICEDRIVER_TRANSPORT_API'        : 2,
    'DEVICEDRIVER_P4'                   : 3,
    'DEVICEDRIVER_IETF_NETWORK_TOPOLOGY': 4,
    'DEVICEDRIVER_ONF_TR_352'           : 5,
    'DEVICEDRIVER_XR'                   : 6,
    'DEVICEDRIVER_IETF_L2VPN'           : 7,
}

MSG_ERROR = 'Could not retrieve devices in remote TeraFlowSDN instance({:s}). status_code={:s} reply={:s}'

LOGGER = logging.getLogger(__name__)

class TfsDebugApiClient:
    def __init__(
        self, address : str, port : int, scheme : str = 'http',
        username : Optional[str] = None, password : Optional[str] = None
    ) -> None:
        self._url = GET_DEVICES_URL.format(scheme, address, port)
        self._auth = HTTPBasicAuth(username, password) if username is not None and password is not None else None

    def get_devices_endpoints(self) -> List[Dict]:
        reply = requests.get(self._url, timeout=TIMEOUT, verify=False, auth=self._auth)
        if reply.status_code not in HTTP_OK_CODES:
            msg = MSG_ERROR.format(str(self._url), str(reply.status_code), str(reply))
            LOGGER.error(msg)
            raise Exception(msg)

        result = list()
        for json_device in reply.json()['devices']:
            device_uuid : str = json_device['device_id']['device_uuid']['uuid']
            device_type : str = json_device['device_type']
            if not device_type.startswith('emu-'): device_type = 'emu-' + device_type
            device_status = json_device['device_operational_status']
            device_url = '/devices/device[{:s}]'.format(device_uuid)
            device_data = {
                'uuid': json_device['device_id']['device_uuid']['uuid'],
                'name': json_device['name'],
                'type': device_type,
                'status': MAPPING_STATUS[device_status],
                'drivers': [MAPPING_DRIVER[driver] for driver in json_device['device_drivers']],
            }
            result.append((device_url, device_data))

            for json_endpoint in json_device['device_endpoints']:
                endpoint_uuid = json_endpoint['endpoint_id']['endpoint_uuid']['uuid']
                endpoint_url = '/endpoints/endpoint[{:s}]'.format(endpoint_uuid)
                endpoint_data = {
                    'device_uuid': device_uuid,
                    'uuid': endpoint_uuid,
                    'name': json_endpoint['name'],
                    'type': json_endpoint['endpoint_type'],
                }
                result.append((endpoint_url, endpoint_data))

        return result
+25 −0
Original line number Diff line number Diff line
@@ -541,3 +541,28 @@ class WimconnectorIETFL2VPN(SdnConnectorBase):
            return response
        except requests.exceptions.ConnectionError:
            raise SdnConnectorError("Request Timeout", http_code=408)

    def get_connectivity_service(self, service_uuid, conn_info=None):
        """Provide information about a specific connection provisioned by a WIM.

        This method should receive as the first argument the UUID generated by
        the ``create_connectivity_service``
        """
        try:
            self.logger.info("Sending get connectivity service")
            servicepoint = (
                "{}/restconf/data/ietf-l2vpn-svc:l2vpn-svc/vpn-services/vpn-service={}/".format(
                    self.wim["wim_url"], service_uuid
                )
            )
            response = requests.get(servicepoint, auth=self.auth)

            if response.status_code != requests.codes.ok:
                raise SdnConnectorError(
                    "Unable to get connectivity service {:s}".format(str(service_uuid)),
                    http_code=response.status_code,
                )

            return response
        except requests.exceptions.ConnectionError:
            raise SdnConnectorError("Request Timeout", http_code=408)