Commit 81aa9b8e authored by Lluis Gifre Renom's avatar Lluis Gifre Renom
Browse files

Device component - IETF L3VPN:

- Code formatting and polishing
- Minor bug fixes
- Improved Credential check
- Improved log reporting
parent 588ce474
Loading
Loading
Loading
Loading
+40 −49
Original line number Diff line number Diff line
@@ -24,21 +24,23 @@ from .Constants import SPECIAL_RESOURCE_MAPPINGS
from .TfsApiClient import TfsApiClient
from .Tools import compose_resource_endpoint


LOGGER = logging.getLogger(__name__)


ALL_RESOURCE_KEYS = [
    RESOURCE_ENDPOINTS,
    RESOURCE_SERVICES,
]

RE_GET_ENDPOINT_FROM_INTERFACE = re.compile(r"^\/interface\[([^\]]+)\].*")

RE_IETF_L3VPN_DATA = re.compile(r"^\/service\[[^\]]+\]\/IETFL3VPN$")
RE_IETF_L3VPN_OPERATION = re.compile(r"^\/service\[[^\]]+\]\/IETFL3VPN\/operation$")
RE_IETF_L3VPN_DATA = re.compile(r'^\/service\[[^\]]+\]\/IETFL3VPN$')
RE_IETF_L3VPN_OPERATION = re.compile(r'^\/service\[[^\]]+\]\/IETFL3VPN\/operation$')

DRIVER_NAME = 'ietf_l3vpn'
METRICS_POOL = MetricsPool('Device', 'Driver', labels={'driver': DRIVER_NAME})


class IetfL3VpnDriver(_Driver):
    def __init__(self, address : str, port : str, **settings) -> None:
        super().__init__(DRIVER_NAME, address, int(port), **settings)
@@ -54,7 +56,6 @@ class IetfL3VpnDriver(_Driver):
            self.address, self.port, scheme=scheme, username=username,
            password=password, timeout=timeout
        )
        #self.__tfs_nbi_root = "{:s}://{:s}:{:d}".format(scheme, self.address, int(self.port))

        # Options are:
        #    disabled --> just import endpoints as usual
@@ -90,7 +91,7 @@ class IetfL3VpnDriver(_Driver):
                    resource_key, resource_value = resource
                    chk_string(str_resource_name, resource_key, allow_empty=False)
                    resource_path = resource_key.split("/")
                except Exception as e:  # pylint: disable=broad-except
                except Exception as e:
                    LOGGER.exception(
                        "Exception validating {:s}: {:s}".format(
                            str_resource_name, str(resource_key)
@@ -114,14 +115,9 @@ class IetfL3VpnDriver(_Driver):
    def Connect(self) -> bool:
        with self.__lock:
            if self.__started.is_set(): return True
            try:
                self.tac.check_credentials()
            except:     # pylint: disable=bare-except
                LOGGER.exception('Exception checking credentials')
                return False
            else:
                self.__started.set()
                return True
            checked = self.tac.check_credentials(raise_if_fail=False)
            if checked: self.__started.set()
            return checked

    def Disconnect(self) -> bool:
        with self.__lock:
@@ -179,12 +175,14 @@ class IetfL3VpnDriver(_Driver):
            for resource in resources:
                resource_key, resource_value = resource
                if RE_IETF_L3VPN_OPERATION.match(resource_key):
                    operation_type = json.loads(resource_value)["type"]
                    operation_type = json.loads(resource_value)['type']
                    results.append((resource_key, True))
                    break
            else:
                raise Exception("operation type not found in resources")
            for resource in resources:
                raise Exception('operation type not found in resources')

            for i, resource in enumerate(resources):
                str_resource_name = 'resource_key[#{:d}]'.format(i)
                LOGGER.info('resource = {:s}'.format(str(resource)))
                resource_key, resource_value = resource
                if not RE_IETF_L3VPN_DATA.match(resource_key):
@@ -192,31 +190,24 @@ class IetfL3VpnDriver(_Driver):
                try:
                    resource_value = json.loads(resource_value)

                    # if service_exists(self.__tfs_nbi_root, self.__auth, service_uuid):
                    #     exc = NotImplementedError(
                    #         "IETF L3VPN Service Update is still not supported"
                    #     )
                    #     results.append((resource[0], exc))
                    #     continue
                    if operation_type == "create":
                        service_id = resource_value["ietf-l3vpn-svc:l3vpn-svc"][
                            "vpn-services"
                        ]["vpn-service"][0]["vpn-id"]
                    service_uuid = resource_value['ietf-l3vpn-svc:l3vpn-svc'][
                        'vpn-services'
                    ]['vpn-service'][0]['vpn-id']

                    if operation_type == 'create':
                        self.tac.create_connectivity_service(resource_value)
                    elif operation_type == "update":
                        service_id = resource_value["ietf-l3vpn-svc:l3vpn-svc"][
                            "vpn-services"
                        ]["vpn-service"][0]["vpn-id"]
                    elif operation_type == 'update':
                        self.tac.update_connectivity_service(resource_value)
                    elif operation_type == 'delete':
                        self.tac.delete_connectivity_service(service_uuid)
                    else:
                        raise Exception("operation type not supported")
                        MSG = 'OperationType({:s}) not supported'
                        raise Exception(MSG.format(str(operation_type)))

                    results.append((resource_key, True))
                except Exception as e:  # pylint: disable=broad-except
                    LOGGER.exception(
                        "Unhandled error processing resource_key({:s})".format(
                            str(resource_key)
                        )
                    )
                except Exception as e:
                    MSG = 'Unhandled error processing {:s}: resource_key({:s})'
                    LOGGER.exception(MSG.format(str_resource_name, str(resource_key)))
                    results.append((resource_key, e))
        return results

@@ -228,24 +219,24 @@ class IetfL3VpnDriver(_Driver):
        if len(resources) == 0:
            return results
        with self.__lock:
            for resource in resources:
                LOGGER.info("resource = {:s}".format(str(resource)))
            for i, resource in enumerate(resources):
                str_resource_name = 'resource_key[#{:d}]'.format(i)
                LOGGER.info('resource = {:s}'.format(str(resource)))
                resource_key, resource_value = resource

                if not RE_IETF_L3VPN_DATA.match(resource_key):
                    continue

                try:
                    resource_value = json.loads(resource_value)
                    service_id = resource_value["id"]

                    # if service_exists(self.__tfs_nbi_root, self.__auth, service_uuid):
                    self.tac.delete_connectivity_service(service_id)
                    service_uuid = resource_value['ietf-l3vpn-svc:l3vpn-svc'][
                        'vpn-services'
                    ]['vpn-service'][0]['vpn-id']
                    self.tac.delete_connectivity_service(service_uuid)
                    results.append((resource_key, True))
                except Exception as e:  # pylint: disable=broad-except
                    LOGGER.exception(
                        "Unhandled error processing resource_key({:s})".format(
                            str(resource_key)
                        )
                    )
                except Exception as e:
                    MSG = 'Unhandled error processing {:s}: resource_key({:s})'
                    LOGGER.exception(MSG.format(str_resource_name, str(resource_key)))
                    results.append((resource_key, e))
        return results

+53 −19
Original line number Diff line number Diff line
@@ -17,10 +17,15 @@ from typing import Dict, List, Optional
from common.tools.client.RestApiClient import RestApiClient
from device.service.driver_api.ImportTopologyEnum import ImportTopologyEnum


GET_CONTEXT_IDS_URL = '/tfs-api/context_ids'
GET_DEVICES_URL     = '/tfs-api/devices'
GET_LINKS_URL       = '/tfs-api/links'
L3VPN_URL           = '/restconf/data/ietf-l3vpn-svc:l3vpn-svc/vpn-services'


IETF_L3VPN_ALL_URL  = '/restconf/data/ietf-l3vpn-svc:l3vpn-svc/vpn-services'
IETF_L3VPN_ONE_URL  = IETF_L3VPN_ALL_URL + '/vpn-service={:s}'


MAPPING_STATUS = {
    'DEVICEOPERATIONALSTATUS_UNDEFINED': 0,
@@ -50,8 +55,10 @@ MAPPING_DRIVER = {
    'DEVICEDRIVER_RYU'                  : 18,
}


LOGGER = logging.getLogger(__name__)


class TfsApiClient(RestApiClient):
    def __init__(
        self, address : str, port : int, scheme : str = 'http',
@@ -63,9 +70,26 @@ class TfsApiClient(RestApiClient):
            timeout=timeout, verify_certs=False, allow_redirects=True, logger=LOGGER
        )

    def check_credentials(self) -> None:

    def check_credentials(self, raise_if_fail : bool = True) -> None:
        try:
            LOGGER.info('Checking credentials...')
            self.get(GET_CONTEXT_IDS_URL, expected_status_codes={requests.codes['OK']})
            LOGGER.info('Credentials checked')
            return True
        except requests.exceptions.Timeout as e:
            MSG = 'Timeout connecting {:s}'
            msg = MSG.format(GET_CONTEXT_IDS_URL)
            LOGGER.exception(msg)
            if raise_if_fail: raise Exception(msg) from e
            return False
        except Exception as e:
            MSG = 'Exception connecting credentials: {:s}'
            msg = MSG.format(GET_CONTEXT_IDS_URL)
            LOGGER.exception(msg)
            if raise_if_fail: raise Exception(msg) from e
            return False


    def get_devices_endpoints(
        self, import_topology : ImportTopologyEnum = ImportTopologyEnum.DEVICES
@@ -142,31 +166,41 @@ class TfsApiClient(RestApiClient):
        LOGGER.debug('[get_devices_endpoints] topology; returning')
        return result

    def create_connectivity_service(self, l3vpn_data : dict) -> None:
        MSG = '[create_connectivity_service] l3vpn_data={:s}'
        LOGGER.debug(MSG.format(str(l3vpn_data)))

    def create_connectivity_service(self, data : Dict) -> None:
        MSG = '[create_connectivity_service] data={:s}'
        LOGGER.debug(MSG.format(str(data)))
        try:
            self.post(L3VPN_URL, body=l3vpn_data)
            MSG = '[create_connectivity_service] POST {:s}: {:s}'
            LOGGER.info(MSG.format(str(IETF_L3VPN_ALL_URL), str(data)))
            self.post(IETF_L3VPN_ALL_URL, body=data)
        except requests.exceptions.ConnectionError as e:
            MSG = 'Failed to send POST request to TFS L3VPN NBI'
            MSG = 'Failed to send POST request to TFS IETF L3VPN NBI'
            raise Exception(MSG) from e

    def update_connectivity_service(self, l3vpn_data : dict) -> None:
        MSG = '[update_connectivity_service] l3vpn_data={:s}'
        LOGGER.debug(MSG.format(str(l3vpn_data)))
        vpn_id = l3vpn_data['ietf-l3vpn-svc:l3vpn-svc']['vpn-services']['vpn-service'][0]['vpn-id']

    def update_connectivity_service(self, data : Dict) -> None:
        MSG = '[update_connectivity_service] data={:s}'
        LOGGER.debug(MSG.format(str(data)))
        vpn_id = data['ietf-l3vpn-svc:l3vpn-svc']['vpn-services']['vpn-service'][0]['vpn-id']
        url = IETF_L3VPN_ONE_URL.format(vpn_id)
        try:
            self.put(L3VPN_URL + f'/vpn-service={vpn_id}', body=l3vpn_data)
            MSG = '[update_connectivity_service] PUT {:s}: {:s}'
            LOGGER.info(MSG.format(str(url), str(data)))
            self.put(url, body=data)
        except requests.exceptions.ConnectionError as e:
            MSG = 'Failed to send PUT request to TFS L3VPN NBI'
            MSG = 'Failed to send PUT request to TFS IETF L3VPN NBI'
            raise Exception(MSG) from e


    def delete_connectivity_service(self, service_uuid : str) -> None:
        url = L3VPN_URL + f'/vpn-service={service_uuid}'
        MSG = '[delete_connectivity_service] url={:s}'
        LOGGER.debug(MSG.format(str(url)))
        MSG = '[delete_connectivity_service] service_uuid={:s}'
        LOGGER.debug(MSG.format(str(service_uuid)))
        url = IETF_L3VPN_ONE_URL.format(service_uuid)
        try:
            MSG = '[delete_connectivity_service] DELETE {:s}'
            LOGGER.info(MSG.format(str(url)))
            self.delete(url)
        except requests.exceptions.ConnectionError as e:
            MSG = 'Failed to send DELETE request to TFS L3VPN NBI'
            MSG = 'Failed to send DELETE request to TFS IETF L3VPN NBI'
            raise Exception(MSG) from e