diff --git a/src/device/service/drivers/ietf_actn/handlers/EthService.py b/src/device/service/drivers/ietf_actn/handlers/EthtServiceHandler.py similarity index 63% rename from src/device/service/drivers/ietf_actn/handlers/EthService.py rename to src/device/service/drivers/ietf_actn/handlers/EthtServiceHandler.py index e8fe9817bcf2d0f3eedf87c5f79d5e1de2030861..66734199afa60149bf51990ab00fe815bbae3600 100644 --- a/src/device/service/drivers/ietf_actn/handlers/EthService.py +++ b/src/device/service/drivers/ietf_actn/handlers/EthtServiceHandler.py @@ -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 update(self, parameters : Dict) -> List[Union[bool, Exception]]: + 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) -> 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) diff --git a/src/device/service/drivers/ietf_actn/handlers/OsuTunnel.py b/src/device/service/drivers/ietf_actn/handlers/OsuTunnelHandler.py similarity index 59% rename from src/device/service/drivers/ietf_actn/handlers/OsuTunnel.py rename to src/device/service/drivers/ietf_actn/handlers/OsuTunnelHandler.py index d6332a8d7cc6aa660b0959f328e3a8d5ad69ebfa..57395de4f372757c0d147ffb939b5d781e6e85fe 100644 --- a/src/device/service/drivers/ietf_actn/handlers/OsuTunnel.py +++ b/src/device/service/drivers/ietf_actn/handlers/OsuTunnelHandler.py @@ -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>'), - } - - 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 - - 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 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) + + return osu_tunnels + + 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) diff --git a/src/device/service/drivers/ietf_actn/handlers/RestApiClient.py b/src/device/service/drivers/ietf_actn/handlers/RestApiClient.py index 8660f35ce1a6a8bd42cddcc2d0a50f81568fe27c..15321f7a060541465f85d87c358eed6608350512 100644 --- a/src/device/service/drivers/ietf_actn/handlers/RestApiClient.py +++ b/src/device/service/drivers/ietf_actn/handlers/RestApiClient.py @@ -43,101 +43,68 @@ 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( - self._base_url + url + str_filters, - 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: - 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 + MSG = 'Get {:s}({:s})' + LOGGER.info(MSG.format(str(object_name), str(str_filters))) + response = requests.get( + self._base_url + url + str_filters, + timeout=self._timeout, verify=self._verify, auth=self._auth + ) + LOGGER.info(' Response: {:s}'.format(str(response))) + + 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) + 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( - self._base_url + url, data=json.dumps(data), headers=headers, - 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 + MSG = 'Create/Update {:s}({:s})' + LOGGER.info(MSG.format(str(object_name), str(data))) + response = requests.post( + self._base_url + url, data=json.dumps(data), headers=headers, + timeout=self._timeout, verify=self._verify, auth=self._auth + ) + LOGGER.info(' Response: {:s}'.format(str(response))) + 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( - self._base_url + url + str_filters, - 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: - 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 + MSG = 'Delete {:s}({:s})' + LOGGER.info(MSG.format(str(object_name), str(str_filters))) + response = requests.delete( + self._base_url + url + str_filters, + timeout=self._timeout, verify=self._verify, auth=self._auth + ) + LOGGER.info(' Response: {:s}'.format(str(response))) + + 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))) + return False