diff --git a/src/device/service/drivers/ietf_l2vpn/IetfL2VpnDriver.py b/src/device/service/drivers/ietf_l2vpn/IetfL2VpnDriver.py index 68b8090e5f5e15e98a7aa6a86cea610fc87333f7..2bcb64384956a71ee7d25d36930af2775a4cbbfa 100644 --- a/src/device/service/drivers/ietf_l2vpn/IetfL2VpnDriver.py +++ b/src/device/service/drivers/ietf_l2vpn/IetfL2VpnDriver.py @@ -15,24 +15,30 @@ 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))) - return False +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 = [ RESOURCE_ENDPOINTS, @@ -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) - 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)) - 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)) - else: - # assume single-service retrieval - service = self.wim.get_connectivity_service() - results.append(process_connectivity_service('GetConfig', service)) + 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 + results.extend(self.dac.get_devices_endpoints()) + elif resource_key == RESOURCE_SERVICES: + # return all services through + reply = self.wim.get_all_active_connectivity_services() + results.extend(process_connectivity_services('GetConfig', reply.json())) + else: + # assume single-service retrieval + 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') - vlan_id = find_key(resource, 'vlan_id') - conn_info = {} + if service_exists(self.wim, service_uuid): + exc = NotImplementedError('IETF L2VPN Service Update is still not supported') + results.append((resource[0], exc)) + continue - result = self.wim.get_connectivity_service_status( - service_uuid, conn_info=conn_info) + 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') + + 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 + + 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 diff --git a/src/device/service/drivers/ietf_l2vpn/TfsDebugApiClient.py b/src/device/service/drivers/ietf_l2vpn/TfsDebugApiClient.py new file mode 100644 index 0000000000000000000000000000000000000000..f8c4e0d94a8f84e1af49d19bc44d168673722e34 --- /dev/null +++ b/src/device/service/drivers/ietf_l2vpn/TfsDebugApiClient.py @@ -0,0 +1,92 @@ +# 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 diff --git a/src/device/service/drivers/ietf_l2vpn/WimconnectorIETFL2VPN.py b/src/device/service/drivers/ietf_l2vpn/WimconnectorIETFL2VPN.py index b3721b2d5249e2468225f95a1428d8c83da588ed..960256cd0647447dca49851a074a11b4aee97885 100644 --- a/src/device/service/drivers/ietf_l2vpn/WimconnectorIETFL2VPN.py +++ b/src/device/service/drivers/ietf_l2vpn/WimconnectorIETFL2VPN.py @@ -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)