Commit 7b3ff4a8 authored by Lluis Gifre Renom's avatar Lluis Gifre Renom
Browse files

Device - IETF ACTN Driver:

- Completed implementation of EthtServiceHandler and OsuTunnelHandler
- Updated implementation of RestApiClient
parent fcd3d2f7
Loading
Loading
Loading
Loading
+65 −10
Original line number Diff line number Diff line
@@ -12,9 +12,8 @@
# See the License for the specific language governing permissions and
# limitations under the License.

import enum, json, logging, operator, requests
from typing import Any, Dict, List, Tuple, Union
from device.service.driver_api._Driver import RESOURCE_ENDPOINTS, RESOURCE_SERVICES
import enum, logging
from typing import Dict, List, Optional, Tuple, Union
from .RestApiClient import RestApiClient

LOGGER = logging.getLogger(__name__)
@@ -73,7 +72,7 @@ def compose_etht_service_endpoint(
        'node-id'           : node_id,
        'tp-id'             : tp_id,
        'protection-role'   : EndpointProtectionRoleEnum.WORK.value,
        'layer-specific'    : compose_layer_specific_access_type,
        'layer-specific'    : compose_layer_specific_access_type(),
        'is-extendable'     : False,
        'is-terminal'       : True,
        'static-route-list' : compose_static_route_list(static_routes),
@@ -94,7 +93,7 @@ def compose_etht_service(
    src_static_routes : List[Tuple[str, int, str]] = list(), dst_static_routes : List[Tuple[str, int, str]] = list()
) -> Dict:
    return {'ietf-eth-tran-service:etht-svc': {'etht-svc-instances': [{
        'etht-svc-name' : name.lower(),
        'etht-svc-name' : name,
        'etht-svc-title': name.upper(),
        'etht-svc-type' : service_type.value,
        'source-endpoints': {'source-endpoint': [
@@ -113,10 +112,66 @@ class EthtServiceHandler:
        self._object_name     = 'EthtService'
        self._subpath_url     = '/ietf-eth-tran-service:etht-svc/etht-svc-instances'

    def get(self) -> List[Tuple[str, Any]]:
        pass
    def get(self, etht_service_name : Optional[str] = None) -> Union[Dict, List]:
        filters = [] if etht_service_name is None else [('etht-svc-name', etht_service_name)]
        data = self._rest_api_client.get(self._object_name, self._subpath_url, filters)

        if not isinstance(data, dict): return ValueError('data should be a dict')
        if 'ietf-eth-tran-service:etht-svc' not in data:
            return ValueError('data does not contain key "ietf-eth-tran-service:etht-svc"')
        data = data['ietf-eth-tran-service:etht-svc']
        if 'etht-svc-instances' not in data:
            return ValueError('data["ietf-eth-tran-service:etht-svc"] does not contain key "etht-svc-instances"')
        data = data['etht-svc-instances']
        if not isinstance(data, list):
            return ValueError('data["ietf-eth-tran-service:etht-svc"]["etht-svc-instances"] should be a list')

        etht_services : List[Dict] = list()
        for item in data:
            src_endpoints = item['source-endpoints']['source-endpoint']
            if len(src_endpoints) != 1:
                MSG = 'EthtService({:s}) has zero/multiple source endpoints'
                raise Exception(MSG.format(str(item)))
            src_endpoint = src_endpoints[0]

            dst_endpoints = item['destination-endpoints']['destination-endpoint']
            if len(dst_endpoints) != 1:
                MSG = 'EthtService({:s}) has zero/multiple destination endpoints'
                raise Exception(MSG.format(str(item)))
            dst_endpoint = dst_endpoints[0]

            svc_tunnels = item['svc-tunnel']
            if len(svc_tunnels) != 1:
                MSG = 'EthtService({:s}) has zero/multiple service tunnels'
                raise Exception(MSG.format(str(item)))
            svc_tunnel = svc_tunnels[0]

            etht_service = {
                'name'             : item['etht-svc-name'],
                'service_type'     : item['etht-svc-type'],
                'osu_tunnel_name'  : svc_tunnel['tunnel-name'],

                'src_node_id'      : src_endpoint['node-id'],
                'src_tp_id'        : src_endpoint['tp-id'],
                'src_vlan_tag'     : src_endpoint['outer-tag']['vlan-value'],
                'src_static_routes': [
                    (static_route['destination'], static_route['destination-mask'], static_route['next-hop'])
                    for static_route in src_endpoint.get('static-route-list', list())
                ],

                'dst_node_id'      : dst_endpoint['node-id'],
                'dst_tp_id'        : dst_endpoint['tp-id'],
                'dst_vlan_tag'     : src_endpoint['outer-tag']['vlan-value'],
                'dst_static_routes': [
                    (static_route['destination'], static_route['destination-mask'], static_route['next-hop'])
                    for static_route in src_endpoint.get('static-route-list', list())
                ],
            }
            etht_services.append(etht_service)

        return etht_services

    def update(self, parameters : Dict) -> List[Union[bool, Exception]]:
    def update(self, parameters : Dict) -> bool:
        name              = parameters['name'           ]
        service_type      = parameters['service_type'   ]
        osu_tunnel_name   = parameters['osu_tunnel_name']
@@ -141,6 +196,6 @@ class EthtServiceHandler:

        return self._rest_api_client.update(self._object_name, self._subpath_url, data)

    def delete(self, etht_service_name : str) -> List[Union[bool, Exception]]:
    def delete(self, etht_service_name : str) -> bool:
        filters = [('etht-svc-name', etht_service_name)]
        return self._rest_api_client.delete(self._object_name, self._subpath_url, filters)
+45 −69
Original line number Diff line number Diff line
@@ -12,9 +12,8 @@
# See the License for the specific language governing permissions and
# limitations under the License.

import enum, json, logging, operator, requests
from typing import Any, Dict, List, Tuple, Union
from device.service.driver_api._Driver import RESOURCE_ENDPOINTS, RESOURCE_SERVICES
import enum, logging
from typing import Dict, List, Optional, Union
from .RestApiClient import RestApiClient

LOGGER = logging.getLogger(__name__)
@@ -65,7 +64,7 @@ def compose_osu_tunnel(
    admin_state : TunnelAdminStateEnum = TunnelAdminStateEnum.UP
) -> Dict:
    return {'ietf-te:tunnel': [{
        'name': name.lower(),
        'name': name,
        'title': name.upper(),
        'admin-state': admin_state.value,
        'delay': delay,
@@ -87,70 +86,47 @@ class OsuTunnelHandler:
        self._object_name     = 'OsuTunnel'
        self._subpath_url     = '/ietf-te:tunnel'

    def get(self) -> List[Tuple[str, Any]]:
        pass

    def get(self, resource_key : str) -> None:
        url = '{:s}/restconf/data/tapi-common:context'.format(base_url)
        result = []
        try:
            response = requests.get(url, timeout=timeout, verify=False, auth=auth)
        except requests.exceptions.Timeout:
            LOGGER.exception('Timeout connecting {:s}'.format(url))
            return result
        except Exception as e:  # pylint: disable=broad-except
            LOGGER.exception('Exception retrieving {:s}'.format(resource_key))
            result.append((resource_key, e))
            return result

        try:
            context = json.loads(response.content)
        except Exception as e:  # pylint: disable=broad-except
            LOGGER.warning('Unable to decode reply: {:s}'.format(str(response.content)))
            result.append((resource_key, e))
            return result

        if resource_key == RESOURCE_SERVICES:
            if 'tapi-common:context' in context:
                context = context['tapi-common:context']
            elif 'context' in context:
                context = context['context']

            if 'tapi-connectivity:connectivity-context' in context:
                context = context['tapi-connectivity:connectivity-context']
            elif 'connectivity-context' in context:
                context = context['connectivity-context']

            for conn_svc in context['connectivity-service']:
                service_uuid = conn_svc['uuid']
                constraints = conn_svc.get('connectivity-constraint', {})
                total_req_cap = constraints.get('requested-capacity', {}).get('total-size', {})

                service_url = '/services/service[{:s}]'.format(service_uuid)
                service_data = {
                    'uuid': service_uuid,
                    'direction': constraints.get('connectivity-direction', 'UNIDIRECTIONAL'),
                    'capacity_unit': total_req_cap.get('unit', '<UNDEFINED>'),
                    'capacity_value': total_req_cap.get('value', '<UNDEFINED>'),
    def get(self, osu_tunnel_name : Optional[str] = None) -> Union[Dict, List]:
        filters = [] if osu_tunnel_name is None else [('name', osu_tunnel_name)]
        data = self._rest_api_client.get(self._object_name, self._subpath_url, filters)

        if not isinstance(data, dict): return ValueError('data should be a dict')
        if 'ietf-te:tunnel' not in data: return ValueError('data does not contain key "ietf-te:tunnel"')
        data = data['ietf-te:tunnel']
        if not isinstance(data, list): return ValueError('data[ietf-te:tunnel] should be a list')

        osu_tunnels : List[Dict] = list()
        for item in data:
            src_endpoints = item['source-endpoints']['source-endpoint']
            if len(src_endpoints) != 1:
                MSG = 'OsuTunnel({:s}) has zero/multiple source endpoints'
                raise Exception(MSG.format(str(item)))
            src_endpoint = src_endpoints[0]

            dst_endpoints = item['destination-endpoints']['destination-endpoint']
            if len(dst_endpoints) != 1:
                MSG = 'OsuTunnel({:s}) has zero/multiple destination endpoints'
                raise Exception(MSG.format(str(item)))
            dst_endpoint = dst_endpoints[0]

            osu_tunnel = {
                'name'                : item['name'],
                'src_node_id'         : src_endpoint['node-id'],
                'src_tp_id'           : src_endpoint['node-id'],
                'src_ttp_channel_name': src_endpoint['ttp-channel-name'],
                'dst_node_id'         : dst_endpoint['node-id'],
                'dst_tp_id'           : dst_endpoint['node-id'],
                'dst_ttp_channel_name': src_endpoint['ttp-channel-name'],
                'odu_type'            : item['te-bandwidth']['odu-type'],
                'osuflex_number'      : item['te-bandwidth']['number'],
                'delay'               : item['delay'],
                'bidirectional'       : item['bidirectional'],
            }
            osu_tunnels.append(osu_tunnel)

                for i,endpoint in enumerate(conn_svc.get('end-point', [])):
                    layer_protocol_name = endpoint.get('layer-protocol-name')
                    if layer_protocol_name is not None:
                        service_data['layer_protocol_name'] = layer_protocol_name
        return osu_tunnels

                    layer_protocol_qualifier = endpoint.get('layer-protocol-qualifier')
                    if layer_protocol_qualifier is not None:
                        service_data['layer_protocol_qualifier'] = layer_protocol_qualifier

                    sip = endpoint['service-interface-point']['service-interface-point-uuid']
                    service_data['input_sip' if i == 0 else 'output_sip'] = sip

                result.append((service_url, service_data))

        return result

    def update(self, parameters : Dict) -> List[Union[bool, Exception]]:
    def update(self, parameters : Dict) -> bool:
        name                 = parameters['name'                ]

        src_node_id          = parameters['src_node_id'         ]
@@ -175,6 +151,6 @@ class OsuTunnelHandler:

        return self._rest_api_client.update(self._object_name, self._subpath_url, data)

    def delete(self, osu_tunnel_name : str) -> List[Union[bool, Exception]]:
    def delete(self, osu_tunnel_name : str) -> bool:
        filters = [('name', osu_tunnel_name)]
        return self._rest_api_client.delete(self._object_name, self._subpath_url, filters)
+42 −75
Original line number Diff line number Diff line
@@ -43,17 +43,14 @@ class RestApiClient:
        self._timeout = int(settings.get('timeout', DEFAULT_TIMEOUT))
        self._verify  = int(settings.get('verify',  DEFAULT_VERIFY ))


    def get(
        self, object_name : str, url : str, filters : List[Tuple[str, str]]
    ) -> List[Union[Any, Exception]]:
    ) -> Union[Dict, List]:
        str_filters = ''.join([
            '[{:s}={:s}]'.format(filter_field, filter_value)
            for filter_field, filter_value in filters
        ])

        results = []
        try:
        MSG = 'Get {:s}({:s})'
        LOGGER.info(MSG.format(str(object_name), str(str_filters)))
        response = requests.get(
@@ -61,38 +58,21 @@ class RestApiClient:
            timeout=self._timeout, verify=self._verify, auth=self._auth
        )
        LOGGER.info('  Response: {:s}'.format(str(response)))
        except Exception as e:  # pylint: disable=broad-except
            MSG = 'Exception Getting {:s}({:s})'
            LOGGER.exception(MSG.format(str(object_name), str(str_filters)))
            results.append(e)
            return results
        else:
            if response.status_code not in HTTP_OK_CODES:

        if response.status_code in HTTP_OK_CODES: return json.loads(response.content)

        MSG = 'Could not get {:s}({:s}): status_code={:s} reply={:s}'
        msg = MSG.format(str(object_name), str(str_filters), str(response.status_code), str(response))
        LOGGER.error(msg)
                results.append(Exception(msg))
                return results

        try:
            results.append(json.loads(response.content))
        except Exception:  # pylint: disable=broad-except
            MSG = 'Could not decode reply {:s}({:s}): {:s} {:s}'
            msg = MSG.format(str(object_name), str(str_filters), str(response.status_code), str(response))
            LOGGER.exception(msg)
            results.append(Exception(msg))

        return results
        return Exception(msg)

    def update(
        self, object_name : str, url : str, data : Dict, headers : Dict[str, Any] = dict()
    ) -> List[Union[bool, Exception]]:
    ) -> bool:
        headers = copy.deepcopy(headers)
        if 'content-type' not in {header_name.lower() for header_name in headers.keys()}:
            headers.update({'content-type': 'application/json'})

        results = []
        try:
        MSG = 'Create/Update {:s}({:s})'
        LOGGER.info(MSG.format(str(object_name), str(data)))
        response = requests.post(
@@ -100,29 +80,21 @@ class RestApiClient:
            timeout=self._timeout, verify=self._verify, auth=self._auth
        )
        LOGGER.info('  Response: {:s}'.format(str(response)))
        except Exception as e:  # pylint: disable=broad-except
            MSG = 'Exception Creating/Updating {:s}({:s})'
            LOGGER.exception(MSG.format(str(object_name), str(data)))
            results.append(e)
        else:
            if response.status_code not in HTTP_OK_CODES:
                MSG = 'Could not create/update {:s}({:s}): status_code={:s} reply={:s}'
                LOGGER.error(MSG.format(str(object_name), str(data), str(response.status_code), str(response)))
            results.append(response.status_code in HTTP_OK_CODES)

        return results
        if response.status_code in HTTP_OK_CODES: return True

        MSG = 'Could not create/update {:s}({:s}): status_code={:s} reply={:s}'
        LOGGER.error(MSG.format(str(object_name), str(data), str(response.status_code), str(response)))
        return False

    def delete(
        self, object_name : str, url : str, filters : List[Tuple[str, str]]
    ) -> List[Union[bool, Exception]]:
    ) -> bool:
        str_filters = ''.join([
            '[{:s}={:s}]'.format(filter_field, filter_value)
            for filter_field, filter_value in filters
        ])

        results = []
        try:
        MSG = 'Delete {:s}({:s})'
        LOGGER.info(MSG.format(str(object_name), str(str_filters)))
        response = requests.delete(
@@ -130,14 +102,9 @@ class RestApiClient:
            timeout=self._timeout, verify=self._verify, auth=self._auth
        )
        LOGGER.info('  Response: {:s}'.format(str(response)))
        except Exception as e:  # pylint: disable=broad-except
            MSG = 'Exception Deleting {:s}({:s})'
            LOGGER.exception(MSG.format(str(object_name), str(str_filters)))
            results.append(e)
        else:
            if response.status_code not in HTTP_OK_CODES:

        if response.status_code in HTTP_OK_CODES: return True

        MSG = 'Could not delete {:s}({:s}): status_code={:s} reply={:s}'
        LOGGER.error(MSG.format(str(object_name), str(str_filters), str(response.status_code), str(response)))
            results.append(response.status_code in HTTP_OK_CODES)

        return results
        return False