diff --git a/src/device/service/drivers/ietf_actn/ComposerEthService.py b/src/device/service/drivers/ietf_actn/ComposerEthService.py new file mode 100644 index 0000000000000000000000000000000000000000..1549d9811aa5d1c193a44ad45d0d7773236c0612 --- /dev/null +++ b/src/device/service/drivers/ietf_actn/ComposerEthService.py @@ -0,0 +1,14 @@ +# 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. + diff --git a/src/device/service/drivers/ietf_actn/ComposerOsuTunnel.py b/src/device/service/drivers/ietf_actn/ComposerOsuTunnel.py new file mode 100644 index 0000000000000000000000000000000000000000..ad369482e67d56a8cccf64db15e16f1be0effa4c --- /dev/null +++ b/src/device/service/drivers/ietf_actn/ComposerOsuTunnel.py @@ -0,0 +1,80 @@ +# 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 enum +from typing import Dict + +OSU_TUNNEL_URL = '/restconf/data/ietf-te:tunnel' + +class EndpointProtectionRoleEnum(enum.Enum): + WORK = 'work' + +class LspProtectionTypeEnum(enum.Enum): + UNPROTECTED = 'ietf-te-types:lsp-protection-unprotected' + +class LspRestorationTypeEnum(enum.Enum): + NOT_APPLICABLE = 'ietf-te-types:lsp-restoration-not-applicable' + +class TunnelAdminStateEnum(enum.Enum): + UP = 'ietf-te-types:tunnel-admin-state-up' + +class OduTypeEnum(enum.Enum): + OSUFLEX = 'osuflex' + +def compose_osu_tunnel_endpoint( + node_id : str, tp_id : str, ttp_channel_name : str, + protection_role : EndpointProtectionRoleEnum = EndpointProtectionRoleEnum.WORK +) -> Dict: + return { + 'node-id': node_id, 'tp-id': tp_id, 'ttp-channel-name': ttp_channel_name, + 'protection-role': protection_role.value + } + +def compose_osu_tunnel_te_bandwidth_odu(odu_type : OduTypeEnum, number : int) -> Dict: + return {'layer': 'odu', 'odu-type': odu_type.value, 'number': number} + +def compose_osu_tunnel_protection( + type_ : LspProtectionTypeEnum = LspProtectionTypeEnum.UNPROTECTED, reversion_disable : bool = True +) -> Dict: + return {'protection-type': type_.value, 'protection-reversion-disable': reversion_disable} + +def compose_osu_tunnel_restoration( + type_ : LspRestorationTypeEnum = LspRestorationTypeEnum.NOT_APPLICABLE, restoration_lock : bool = False +) -> Dict: + return {'restoration-type': type_.value, 'restoration-lock': restoration_lock} + +def compose_osu_tunnel( + name : str, + src_node_id : str, src_tp_id : str, src_ttp_channel_name : str, + dst_node_id : str, dst_tp_id : str, dst_ttp_channel_name : str, + odu_type : OduTypeEnum, osuflex_number : int, + delay : int, bidirectional : bool = True, + admin_state : TunnelAdminStateEnum = TunnelAdminStateEnum.UP +) -> Dict: + return {'ietf-te:tunnel': [{ + 'name': name.lower(), + 'title': name.upper(), + 'admin-state': admin_state.value, + 'delay': delay, + 'te-bandwidth': compose_osu_tunnel_te_bandwidth_odu(odu_type, osuflex_number), + 'bidirectional': bidirectional, + 'source-endpoints': {'source-endpoint': [ + compose_osu_tunnel_endpoint(src_node_id, src_tp_id, src_ttp_channel_name), + ]}, + 'destination-endpoints': {'destination-endpoint': [ + compose_osu_tunnel_endpoint(dst_node_id, dst_tp_id, dst_ttp_channel_name), + ]}, + 'restoration': compose_osu_tunnel_restoration(), + 'protection': compose_osu_tunnel_protection(), + }]} diff --git a/src/device/service/drivers/ietf_actn/IetfActnDriver.py b/src/device/service/drivers/ietf_actn/IetfActnDriver.py index 34362c0b09bfa154f4634529189c2707bcffa9d0..c31bd85b92c3421b8cdc6001a25f9a2e5e2a3b47 100644 --- a/src/device/service/drivers/ietf_actn/IetfActnDriver.py +++ b/src/device/service/drivers/ietf_actn/IetfActnDriver.py @@ -12,12 +12,13 @@ # See the License for the specific language governing permissions and # limitations under the License. -import logging, requests, threading +import json, 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 from device.service.driver_api._Driver import _Driver +from device.service.drivers.ietf_actn.Tools import create_resource, delete_resource, get_resource from . import ALL_RESOURCE_KEYS #from .Tools import create_connectivity_service, find_key, config_getter, delete_connectivity_service @@ -26,33 +27,40 @@ LOGGER = logging.getLogger(__name__) DRIVER_NAME = 'ietf_actn' METRICS_POOL = MetricsPool('Device', 'Driver', labels={'driver': DRIVER_NAME}) +DEFAULT_BASE_URL = '/restconf/data' +DEFAULT_TIMEOUT = 120 + class IetfActnDriver(_Driver): def __init__(self, address: str, port: int, **settings) -> None: super().__init__(DRIVER_NAME, address, port, **settings) self.__lock = threading.Lock() self.__started = threading.Event() self.__terminate = threading.Event() + username = self.settings.get('username') password = self.settings.get('password') self.__auth = HTTPBasicAuth(username, password) if username is not None and password is not None else None + scheme = self.settings.get('scheme', 'http') - self.__base_url = '{:s}://{:s}:{:d}'.format(scheme, self.address, int(self.port)) - self.__timeout = int(self.settings.get('timeout', 120)) + base_url = self.settings.get('base_url', DEFAULT_BASE_URL) + self.__base_url = '{:s}://{:s}:{:d}{:s}'.format(scheme, address, int(port), base_url) + + self.__timeout = int(self.settings.get('timeout', DEFAULT_TIMEOUT)) def Connect(self) -> bool: - #url = self.__base_url + '/restconf/data/tapi-common:context' - #with self.__lock: - # if self.__started.is_set(): return True - # try: - # requests.get(url, timeout=self.__timeout, verify=False, auth=self.__auth) - # except requests.exceptions.Timeout: - # LOGGER.exception('Timeout connecting {:s}'.format(str(self.__base_url))) - # return False - # except Exception: # pylint: disable=broad-except - # LOGGER.exception('Exception connecting {:s}'.format(str(self.__base_url))) - # return False - # else: - # self.__started.set() + url = self.__base_url + '/tapi-common:context' + with self.__lock: + if self.__started.is_set(): return True + try: + requests.get(url, timeout=self.__timeout, verify=False, auth=self.__auth) + except requests.exceptions.Timeout: + LOGGER.exception('Timeout connecting {:s}'.format(str(self.__base_url))) + return False + except Exception: # pylint: disable=broad-except + LOGGER.exception('Exception connecting {:s}'.format(str(self.__base_url))) + return False + else: + self.__started.set() return True def Disconnect(self) -> bool: @@ -69,13 +77,14 @@ class IetfActnDriver(_Driver): def GetConfig(self, resource_keys : List[str] = []) -> List[Tuple[str, Union[Any, None, Exception]]]: chk_type('resources', resource_keys, list) results = [] - #with self.__lock: - # 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) - # results.extend(config_getter( - # self.__base_url, resource_key, timeout=self.__timeout, auth=self.__auth)) + with self.__lock: + if len(resource_keys) == 0: resource_keys = ALL_RESOURCE_KEYS + for i, resource_key in enumerate(resource_keys): + chk_string('resource_key[#{:d}]'.format(i), resource_key, allow_empty=False) + results.extend(get_resource( + self.__base_url, resource_key, + timeout=self.__timeout, auth=self.__auth + )) return results @metered_subclass_method(METRICS_POOL) @@ -83,35 +92,28 @@ class IetfActnDriver(_Driver): results = [] if len(resources) == 0: return results - #with self.__lock: - # for resource in resources: - # LOGGER.info('resource = {:s}'.format(str(resource))) - # - # uuid = find_key(resource, 'uuid') - # input_sip = find_key(resource, 'input_sip_uuid') - # output_sip = find_key(resource, 'output_sip_uuid') - # 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') - # - # data = create_connectivity_service( - # self.__base_url, uuid, input_sip, output_sip, direction, capacity_value, capacity_unit, - # layer_protocol_name, layer_protocol_qualifier, timeout=self.__timeout, auth=self.__auth) - # results.extend(data) + with self.__lock: + for resource_key, resource_value in resources: + LOGGER.info('resource: key({:s}) => value({:s})'.format(str(resource_key), str(resource_value))) + if isinstance(value, str): value = json.loads(value) + results.extend(create_resource( + self.__base_url, resource_key, resource_value, + timeout=self.__timeout, auth=self.__auth + )) return results @metered_subclass_method(METRICS_POOL) def DeleteConfig(self, resources: List[Tuple[str, Any]]) -> List[Union[bool, Exception]]: results = [] if len(resources) == 0: return results - #with self.__lock: - # for resource in resources: - # LOGGER.info('resource = {:s}'.format(str(resource))) - # uuid = find_key(resource, 'uuid') - # results.extend(delete_connectivity_service( - # self.__base_url, uuid, timeout=self.__timeout, auth=self.__auth)) + with self.__lock: + for resource_key, resource_value in resources: + LOGGER.info('resource: key({:s}) => value({:s})'.format(str(resource_key), str(resource_value))) + if isinstance(value, str): value = json.loads(value) + results.extend(delete_resource( + self.__base_url, resource_key, resource_value, + timeout=self.__timeout, auth=self.__auth + )) return results @metered_subclass_method(METRICS_POOL) diff --git a/src/device/service/drivers/ietf_actn/Tools.py b/src/device/service/drivers/ietf_actn/Tools.py index bbd4247f0debdd17885c5aadccafc32607e4cbe5..1b89315b1820b77f36e45938c6a7c77d01e2b5f7 100644 --- a/src/device/service/drivers/ietf_actn/Tools.py +++ b/src/device/service/drivers/ietf_actn/Tools.py @@ -14,7 +14,7 @@ import json, logging, operator, requests from requests.auth import HTTPBasicAuth -from typing import Optional +from typing import Dict, Optional from device.service.driver_api._Driver import RESOURCE_ENDPOINTS, RESOURCE_SERVICES LOGGER = logging.getLogger(__name__) @@ -29,11 +29,11 @@ HTTP_OK_CODES = { def find_key(resource, key): return json.loads(resource[1])[key] - -def config_getter( - root_url : str, resource_key : str, auth : Optional[HTTPBasicAuth] = None, timeout : Optional[int] = None +def get_resource( + base_url : str, resource_key : str, + auth : Optional[HTTPBasicAuth] = None, timeout : Optional[int] = None ): - url = '{:s}/restconf/data/tapi-common:context'.format(root_url) + url = '{:s}/restconf/data/tapi-common:context'.format(base_url) result = [] try: response = requests.get(url, timeout=timeout, verify=False, auth=auth) @@ -123,13 +123,22 @@ def config_getter( return result -def create_connectivity_service( - root_url, uuid, input_sip, output_sip, direction, capacity_value, capacity_unit, layer_protocol_name, - layer_protocol_qualifier, +def create_resource( + base_url : str, resource_key : str, resource_value : Dict, auth : Optional[HTTPBasicAuth] = None, timeout : Optional[int] = None ): - url = '{:s}/restconf/data/tapi-common:context/tapi-connectivity:connectivity-context'.format(root_url) + uuid = find_key(resource, 'uuid') + input_sip = find_key(resource, 'input_sip_uuid') + output_sip = find_key(resource, 'output_sip_uuid') + 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') + + + url = '{:s}/restconf/data/tapi-common:context/tapi-connectivity:connectivity-context'.format(base_url) headers = {'content-type': 'application/json'} data = { 'tapi-connectivity:connectivity-service': [ @@ -181,9 +190,14 @@ def create_connectivity_service( results.append(response.status_code in HTTP_OK_CODES) return results -def delete_connectivity_service(root_url, uuid, auth : Optional[HTTPBasicAuth] = None, timeout : Optional[int] = None): +def delete_resource( + base_url : str, resource_key : str, resource_value : Dict, + auth : Optional[HTTPBasicAuth] = None, timeout : Optional[int] = None +): + uuid = find_key(resource, 'uuid') + url = '{:s}/restconf/data/tapi-common:context/tapi-connectivity:connectivity-context/connectivity-service={:s}' - url = url.format(root_url, uuid) + url = url.format(base_url, uuid) results = [] try: response = requests.delete(url=url, timeout=timeout, verify=False, auth=auth)