Commit 8962a9c2 authored by Lluis Gifre Renom's avatar Lluis Gifre Renom
Browse files

Device component - MicroWave driver

- added support for user/pass basic authentication
- added HTTP schema settings
- added field to filter desired underlying devices under management
- corrected endpoint uuid composition
- improved error handling
- improved retrieval of existing services
parent 5b1d191c
Loading
Loading
Loading
Loading
+15 −5
Original line number Diff line number Diff line
@@ -13,6 +13,7 @@
# limitations under the License.

import logging, requests, threading
from requests.auth import HTTPBasicAuth
from typing import Any, Iterator, List, Optional, Tuple, Union
from common.method_wrappers.Decorator import MetricsPool, metered_subclass_method
from common.type_checkers.Checkers import chk_string, chk_type
@@ -29,15 +30,20 @@ class IETFApiDriver(_Driver):
        self.__lock = threading.Lock()
        self.__started = threading.Event()
        self.__terminate = threading.Event()
        self.__ietf_root = 'https://' + address + ':' + str(port)
        username = settings.get('username')
        password = settings.get('password')
        self.__auth = HTTPBasicAuth(username, password) if username is not None and password is not None else None
        scheme = settings.get('scheme', 'http')
        self.__ietf_root = '{:s}://{:s}:{:d}'.format(scheme, address, int(port))
        self.__timeout = int(settings.get('timeout', 120))
        self.__node_ids = set(settings.get('node_ids', []))

    def Connect(self) -> bool:
        url = self.__ietf_root + '/nmswebs/restconf/data/ietf-network:networks'
        with self.__lock:
            if self.__started.is_set(): return True
            try:
                requests.get(url, timeout=self.__timeout, verify=False)
                requests.get(url, timeout=self.__timeout, verify=False, auth=self.__auth)
            except requests.exceptions.Timeout:
                LOGGER.exception('Timeout connecting {:s}'.format(str(self.__ietf_root)))
                return False
@@ -67,7 +73,9 @@ class IETFApiDriver(_Driver):
            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)
                results.extend(config_getter(self.__ietf_root, resource_key, self.__timeout))
                results.extend(config_getter(
                    self.__ietf_root, resource_key, timeout=self.__timeout, auth=self.__auth,
                    node_ids=self.__node_ids))
        return results

    @metered_subclass_method(METRICS_POOL)
@@ -87,7 +95,8 @@ class IETFApiDriver(_Driver):
                uuid = find_key(resource, 'uuid')

                data = create_connectivity_service(
                    self.__ietf_root, self.__timeout, uuid, node_id_src, tp_id_src, node_id_dst, tp_id_dst, vlan_id)
                    self.__ietf_root, uuid, node_id_src, tp_id_src, node_id_dst, tp_id_dst, vlan_id,
                    timeout=self.__timeout, auth=self.__auth)
                results.extend(data)
        return results

@@ -99,7 +108,8 @@ class IETFApiDriver(_Driver):
            for resource in resources:
                LOGGER.info('resource = {:s}'.format(str(resource)))
                uuid = find_key(resource, 'uuid')
                results.extend(delete_connectivity_service(self.__ietf_root, self.__timeout, uuid))
                results.extend(delete_connectivity_service(
                    self.__ietf_root, uuid, timeout=self.__timeout, auth=self.__auth))
        return results

    @metered_subclass_method(METRICS_POOL)
+46 −36
Original line number Diff line number Diff line
@@ -13,6 +13,8 @@
# limitations under the License.

import json, logging, requests
from requests.auth import HTTPBasicAuth
from typing import Optional, Set
from device.service.driver_api._Driver import RESOURCE_ENDPOINTS

LOGGER = logging.getLogger(__name__)
@@ -28,6 +30,8 @@ def find_key(resource, key):
    return json.loads(resource[1])[key]

# this function exports only the endpoints which are not already involved in a microwave physical link
# TODO: improvement: create a Set[Tuple[node_id:str, tp_id:str]] containing the endpoints involved in links
# TODO: exportable endpoints are those not in this set. That will prevent looping through links for every endpoint
def is_exportable_endpoint(node, termination_point_id, links):
    # for each link we check if the endpoint (termination_point_id) is already used by an existing link
    for link in links:
@@ -39,7 +43,10 @@ def is_exportable_endpoint(node, termination_point_id, links):
            return False
    return True

def config_getter(root_url, resource_key, timeout):
def config_getter(
    root_url : str, resource_key : str, auth : Optional[HTTPBasicAuth] = None, timeout : Optional[int] = None,
    node_ids : Set[str] = set()
):
    # getting endpoints
    network_name = 'SIAE-ETH-TOPOLOGY'
    FIELDS = ''.join([
@@ -51,51 +58,53 @@ def config_getter(root_url, resource_key, timeout):
    url = URL_TEMPLATE.format(root_url, network_name, FIELDS)

    result = []
    if resource_key == RESOURCE_ENDPOINTS:
        # getting existing endpoints
        try:
        response = requests.get(url, timeout=timeout, verify=False)
    except requests.exceptions.Timeout:
        LOGGER.exception('Timeout connecting {:s}'.format(url))
    except Exception as e:  # pylint: disable=broad-except
        LOGGER.exception('Exception retrieving {:s}'.format(resource_key))
        result.append((resource_key, e))
    else:
            response = requests.get(url, timeout=timeout, verify=False, auth=auth)
            context = json.loads(response.content)
        if resource_key == RESOURCE_ENDPOINTS:
            network_instance = context.get('ietf-network:network', {})
            links = network_instance.get('ietf-network-topology:link', [])
            for sip in network_instance['node']:
                tp = sip['ietf-network-topology:termination-point']
                node_id = sip['node-id']
                for te in tp:
                    tp_id = te['tp-id']
            for node in network_instance['node']:
                node_id = node['node-id']
                if len(node_ids) > 0 and node_id not in node_ids: continue
                tp_list = node['ietf-network-topology:termination-point']
                for tp in tp_list:
                    tp_id = tp['tp-id']
                    if not is_exportable_endpoint(node_id, tp_id, links): continue
                    resource_key = '/endpoints/endpoint[{:s}:{:s}]'.format(node_id,tp_id)
                    resource_value = {'uuid': tp_id, 'type': te['ietf-te-topology:te']['name']}
                    tp_uuid = '{:s}:{:s}'.format(node_id,tp_id)
                    resource_key = '/endpoints/endpoint[{:s}]'.format(tp_uuid)
                    resource_value = {'uuid': tp_uuid, 'type': tp['ietf-te-topology:te']['name']}
                    result.append((resource_key, resource_value))

    # getting created services
    url = '{:s}/nmswebs/restconf/data/ietf-eth-tran-service:etht-svc'.format(root_url)
    try:
        response = requests.get(url, timeout=timeout, verify=False)
        except requests.exceptions.Timeout:
            LOGGER.exception('Timeout connecting {:s}'.format(url))
        except Exception as e:  # pylint: disable=broad-except
        LOGGER.exception('Exception retrieving {:s}'.format(resource_key))
            LOGGER.exception('Exception retrieving/parsing endpoints for {:s}'.format(resource_key))
            result.append((resource_key, e))
    else:
        # getting created services
        url = '{:s}/nmswebs/restconf/data/ietf-eth-tran-service:etht-svc'.format(root_url)
        try:
            response = requests.get(url, timeout=timeout, verify=False, auth=auth)
            context = json.loads(response.content)
        if resource_key == RESOURCE_ENDPOINTS:
            etht_service = context.get('ietf-eth-tran-service:etht-svc', {})
            service_instances = etht_service.get('etht-svc-instances', [])
            for service in service_instances:
                service_name = service['etht-svc-name']
                resource_key = '/services/service[{:s}]'.format(service_name)
                resource_value = {'uuid': service_name, 'type': service['etht-svc-type']}
                result.append((resource_key, resource_value))
                result.append((resource_key, service))
        except requests.exceptions.Timeout:
            LOGGER.exception('Timeout connecting {:s}'.format(url))
        except Exception as e:  # pylint: disable=broad-except
            LOGGER.exception('Exception retrieving/parsing services for {:s}'.format(resource_key))
            result.append((resource_key, e))

    return result

def create_connectivity_service(
    root_url, timeout, uuid, node_id_src, tp_id_src, node_id_dst, tp_id_dst, vlan_id):
    root_url, uuid, node_id_src, tp_id_src, node_id_dst, tp_id_dst, vlan_id,
    auth : Optional[HTTPBasicAuth] = None, timeout : Optional[int] = None
):

    url = '{:s}/nmswebs/restconf/data/ietf-eth-tran-service:etht-svc'.format(root_url)
    headers = {'content-type': 'application/json'}
@@ -128,7 +137,8 @@ def create_connectivity_service(
    results = []
    try:
        LOGGER.info('Connectivity service {:s}: {:s}'.format(str(uuid), str(data)))
        response = requests.post(url=url, data=json.dumps(data), timeout=timeout, headers=headers, verify=False)
        response = requests.post(
            url=url, data=json.dumps(data), timeout=timeout, headers=headers, verify=False, auth=auth)
        LOGGER.info('Microwave Driver response: {:s}'.format(str(response)))
    except Exception as e:  # pylint: disable=broad-except
        LOGGER.exception('Exception creating ConnectivityService(uuid={:s}, data={:s})'.format(str(uuid), str(data)))
@@ -140,12 +150,12 @@ def create_connectivity_service(
        results.append(response.status_code in HTTP_OK_CODES)
    return results

def delete_connectivity_service(root_url, timeout, uuid):
def delete_connectivity_service(root_url, uuid, auth : Optional[HTTPBasicAuth] = None, timeout : Optional[int] = None):
    url = '{:s}/nmswebs/restconf/data/ietf-eth-tran-service:etht-svc/etht-svc-instances={:s}'
    url = url.format(root_url, uuid)
    results = []
    try:
        response = requests.delete(url=url, timeout=timeout, verify=False)
        response = requests.delete(url=url, timeout=timeout, verify=False, auth=auth)
    except Exception as e:  # pylint: disable=broad-except
        LOGGER.exception('Exception deleting ConnectivityService(uuid={:s})'.format(str(uuid)))
        results.append(e)