diff --git a/src/device/service/drivers/ietf_slice/TfsApiClient.py b/src/device/service/drivers/ietf_slice/TfsApiClient.py deleted file mode 100644 index 487390f95b5fd4c8c543c1dbffa941935192bc7e..0000000000000000000000000000000000000000 --- a/src/device/service/drivers/ietf_slice/TfsApiClient.py +++ /dev/null @@ -1,172 +0,0 @@ -# Copyright 2022-2024 ETSI OSG/SDG TeraFlowSDN (TFS) (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 logging -from typing import Dict, List, Optional - -import requests -from requests.auth import HTTPBasicAuth - -from device.service.driver_api.ImportTopologyEnum import ImportTopologyEnum - -GET_DEVICES_URL = "{:s}://{:s}:{:d}/tfs-api/devices" -GET_LINKS_URL = "{:s}://{:s}:{:d}/tfs-api/links" -IETF_SLICE_URL = "{:s}://{:s}:{:d}/restconf/data/ietf-network-slice-service:ietf-nss" -TIMEOUT = 30 - -HTTP_OK_CODES = { - 200, # OK - 201, # Created - 202, # Accepted - 204, # No Content -} - -MAPPING_STATUS = { - "DEVICEOPERATIONALSTATUS_UNDEFINED": 0, - "DEVICEOPERATIONALSTATUS_DISABLED": 1, - "DEVICEOPERATIONALSTATUS_ENABLED": 2, -} - -MAPPING_DRIVER = { - "DEVICEDRIVER_UNDEFINED": 0, - "DEVICEDRIVER_OPENCONFIG": 1, - "DEVICEDRIVER_TRANSPORT_API": 2, - "DEVICEDRIVER_P4": 3, - "DEVICEDRIVER_IETF_NETWORK_TOPOLOGY": 4, - "DEVICEDRIVER_ONF_TR_532": 5, - "DEVICEDRIVER_XR": 6, - "DEVICEDRIVER_IETF_L2VPN": 7, - "DEVICEDRIVER_GNMI_OPENCONFIG": 8, - "DEVICEDRIVER_OPTICAL_TFS": 9, - "DEVICEDRIVER_IETF_ACTN": 10, - "DEVICEDRIVER_OC": 11, -} - -MSG_ERROR = "Could not retrieve devices in remote TeraFlowSDN instance({:s}). status_code={:s} reply={:s}" - -LOGGER = logging.getLogger(__name__) - - -class TfsApiClient: - def __init__( - self, - address: str, - port: int, - scheme: str = "http", - username: Optional[str] = None, - password: Optional[str] = None, - ) -> None: - self._devices_url = GET_DEVICES_URL.format(scheme, address, port) - self._links_url = GET_LINKS_URL.format(scheme, address, port) - self._slice_url = IETF_SLICE_URL.format(scheme, address, port) - self._auth = None - # ( - # HTTPBasicAuth(username, password) - # if username is not None and password is not None - # else None - # ) - - # def get_devices_endpoints( - # self, import_topology: ImportTopologyEnum = ImportTopologyEnum.DEVICES - # ) -> List[Dict]: - # LOGGER.debug("[get_devices_endpoints] begin") - # LOGGER.debug( - # "[get_devices_endpoints] import_topology={:s}".format(str(import_topology)) - # ) - - # reply = requests.get(self._devices_url, timeout=TIMEOUT, auth=self._auth) - # if reply.status_code not in HTTP_OK_CODES: - # msg = MSG_ERROR.format( - # str(self._devices_url), str(reply.status_code), str(reply) - # ) - # LOGGER.error(msg) - # raise Exception(msg) - - # if import_topology == ImportTopologyEnum.DISABLED: - # raise Exception( - # "Unsupported import_topology mode: {:s}".format(str(import_topology)) - # ) - - # result = list() - # for json_device in reply.json()["devices"]: - # device_uuid: str = json_device["device_id"]["device_uuid"]["uuid"] - # device_type: str = json_device["device_type"] - # device_status = json_device["device_operational_status"] - # device_url = "/devices/device[{:s}]".format(device_uuid) - # device_data = { - # "uuid": json_device["device_id"]["device_uuid"]["uuid"], - # "name": json_device["name"], - # "type": device_type, - # "status": MAPPING_STATUS[device_status], - # "drivers": [ - # MAPPING_DRIVER[driver] for driver in json_device["device_drivers"] - # ], - # } - # result.append((device_url, device_data)) - - # for json_endpoint in json_device["device_endpoints"]: - # endpoint_uuid = json_endpoint["endpoint_id"]["endpoint_uuid"]["uuid"] - # endpoint_url = "/endpoints/endpoint[{:s}]".format(endpoint_uuid) - # endpoint_data = { - # "device_uuid": device_uuid, - # "uuid": endpoint_uuid, - # "name": json_endpoint["name"], - # "type": json_endpoint["endpoint_type"], - # } - # result.append((endpoint_url, endpoint_data)) - - # if import_topology == ImportTopologyEnum.DEVICES: - # LOGGER.debug("[get_devices_endpoints] devices only; returning") - # return result - - # reply = requests.get(self._links_url, timeout=TIMEOUT, auth=self._auth) - # if reply.status_code not in HTTP_OK_CODES: - # msg = MSG_ERROR.format( - # str(self._links_url), str(reply.status_code), str(reply) - # ) - # LOGGER.error(msg) - # raise Exception(msg) - - # for json_link in reply.json()["links"]: - # link_uuid: str = json_link["link_id"]["link_uuid"]["uuid"] - # link_url = "/links/link[{:s}]".format(link_uuid) - # link_endpoint_ids = [ - # ( - # json_endpoint_id["device_id"]["device_uuid"]["uuid"], - # json_endpoint_id["endpoint_uuid"]["uuid"], - # ) - # for json_endpoint_id in json_link["link_endpoint_ids"] - # ] - # link_data = { - # "uuid": json_link["link_id"]["link_uuid"]["uuid"], - # "name": json_link["name"], - # "endpoints": link_endpoint_ids, - # } - # result.append((link_url, link_data)) - - # LOGGER.debug("[get_devices_endpoints] topology; returning") - # # return resu - - def create_slice(self, slice_data: dict) -> None: - try: - requests.post(self._slice_url, json=slice_data) - except requests.exceptions.ConnectionError: - raise Exception("faild to send post request to TFS L3VPN NBI") - - def delete_slice(self, slice_uuid: str) -> None: - url = self.__url + f"/vpn-service={slice_uuid}" - try: - requests.delete(url, auth=self._auth) - except requests.exceptions.ConnectionError: - raise Exception("faild to send delete request to TFS L3VPN NBI") diff --git a/src/device/service/drivers/ietf_slice/Tools.py b/src/device/service/drivers/ietf_slice/Tools.py index 3ef08ddf795bd2af2c301bcb340a64f07cc7df8c..fddfd894035cec9af89171a59cb779e6e5dec853 100644 --- a/src/device/service/drivers/ietf_slice/Tools.py +++ b/src/device/service/drivers/ietf_slice/Tools.py @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. import logging -from typing import Any, Dict, Optional, Tuple, TypedDict +from typing import Any, Dict, Optional, Tuple import requests @@ -25,199 +25,6 @@ from .Constants import SPECIAL_RESOURCE_MAPPINGS LOGGER = logging.getLogger(__name__) -def service_exists(wim_url: str, auth, service_uuid: str) -> bool: - try: - get_connectivity_service(wim_url, auth, service_uuid) - return True - except: # pylint: disable=bare-except - return False - - -def get_all_active_connectivity_services(wim_url: str, auth): - try: - LOGGER.info("Sending get all connectivity services") - servicepoint = f"{wim_url}/restconf/data/ietf-l3vpn-svc:l3vpn-svc/vpn-services" - response = requests.get(servicepoint, auth=auth) - - if response.status_code != requests.codes.ok: - raise Exception( - "Unable to get all connectivity services", - http_code=response.status_code, - ) - - return response - except requests.exceptions.ConnectionError: - raise Exception("Request Timeout", http_code=408) - - -def get_connectivity_service(wim_url, auth, service_uuid): - try: - LOGGER.info("Sending get connectivity service") - servicepoint = f"{wim_url}/restconf/data/ietf-l3vpn-svc:l3vpn-svc/vpn-services/vpn-service={service_uuid}" - - response = requests.get(servicepoint) - - if response.status_code != requests.codes.ok: - raise Exception( - "Unable to get connectivity service{:s}".format(str(service_uuid)), - http_code=response.status_code, - ) - - return response - except requests.exceptions.ConnectionError: - raise Exception("Request Timeout", http_code=408) - - -def create_slice_datamodel(resource_value: dict) -> dict: - src_node_id: str = resource_value["src_node_id"] - src_mgmt_ip_address: str = resource_value["src_mgmt_ip_address"] - src_ac_node_id: str = resource_value["src_ac_node_id"] - src_ac_ep_id: str = resource_value["src_ac_ep_id"] - src_vlan: str = resource_value["src_vlan"] - - dst_node_id: str = resource_value["dst_node_id"] - dst_mgmt_ip_address: str = resource_value["dst_mgmt_ip_address"] - dst_ac_node_id: str = resource_value["dst_ac_node_id"] - dst_ac_ep_id: str = resource_value["dst_ac_ep_id"] - dst_vlan: str = resource_value["dst_vlan"] - - slice_id: str = resource_value["slice_id"] - delay: str = resource_value["delay"] - bandwidth: str = resource_value["bandwidth"] - packet_loss: str = resource_value["packet_loss"] - - sdps = [ - { - "id": "1", - "node-id": src_node_id, - "sdp-ip-address": [src_mgmt_ip_address], - "service-match-criteria": { - "match-criterion": [ - { - "index": 1, - "match-type": [ - { - "type": "ietf-network-slice-service:vlan", - "value": [src_vlan], - }, - ], - "target-connection-group-id": "line1", - } - ] - }, - "attachment-circuits": { - "attachment-circuit": [ - { - "id": "0", - "description": "dsc", - "ac-node-id": src_ac_node_id, - "ac-tp-id": src_ac_ep_id, - } - ] - }, - }, - { - "id": "2", - "node-id": dst_node_id, - "sdp-ip-address": [dst_mgmt_ip_address], - "service-match-criteria": { - "match-criterion": [ - { - "index": 1, - "match-type": [ - { - "type": "ietf-network-slice-service:vlan", - "value": [dst_vlan], - }, - ], - "target-connection-group-id": "line1", - } - ] - }, - "attachment-circuits": { - "attachment-circuit": [ - { - "id": "0", - "description": "dsc", - "ac-node-id": dst_ac_node_id, - "ac-tp-id": dst_ac_ep_id, - } - ] - }, - }, - ] - - connection_groups = [ - { - "id": "line1", - "connectivity-type": "point-to-point", - "connectivity-construct": [ - { - "id": 1, - "p2mp-sender-sdp": "1", - "p2mp-receiver-sdp": ["2"], - "service-slo-sle-policy": { - "slo-policy": { - "metric-bound": [ - { - "metric-type": "ietf-network-slice-service:one-way-delay-maximum", - "metric-unit": "milliseconds", - "bound": delay, - }, - { - "metric-type": "ietf-network-slice-service:one-way-bandwidth", - "metric-unit": "Mbps", - "bound": bandwidth, - }, - { - "metric-type": "ietf-network-slice-service:two-way-packet-loss", - "metric-unit": "percentage", - "percentile-value": packet_loss, - }, - ] - } - }, - }, - { - "id": 2, - "p2mp-sender-sdp": "2", - "p2mp-receiver-sdp": ["1"], - "service-slo-sle-policy": { - "slo-policy": { - "metric-bound": [ - { - "metric-type": "ietf-network-slice-service:one-way-delay-maximum", - "metric-unit": "milliseconds", - "bound": delay, - }, - { - "metric-type": "ietf-network-slice-service:one-way-bandwidth", - "metric-unit": "Mbps", - "bound": bandwidth, - }, - { - "metric-type": "ietf-network-slice-service:two-way-packet-loss", - "metric-unit": "percentage", - "percentile-value": packet_loss, - }, - ] - } - }, - }, - ], - } - ] - - slice_service = { - "id": slice_id, - "description": "dsc", - "sdps": {"sdp": sdps}, - "connection-groups": {"connection-group": connection_groups}, - } - slice_data_model = {"network-slice-services": {"slice-service": [slice_service]}} - return slice_data_model - - def process_optional_string_field( endpoint_data: Dict[str, Any], field_name: str, diff --git a/src/device/service/drivers/ietf_slice/driver.py b/src/device/service/drivers/ietf_slice/driver.py index 970833232c938ff3b507ffd8c3b1b5d1ccfb39b0..e02542d37c353b7bcad71b8d73bdcdbf2fa45458 100644 --- a/src/device/service/drivers/ietf_slice/driver.py +++ b/src/device/service/drivers/ietf_slice/driver.py @@ -41,11 +41,8 @@ from device.service.driver_api.ImportTopologyEnum import ( ) from .Constants import SPECIAL_RESOURCE_MAPPINGS -from .TfsApiClient import TfsApiClient -from .Tools import ( - compose_resource_endpoint, - service_exists, -) +from .tfs_slice_nbi_client import TfsApiClient +from .Tools import compose_resource_endpoint LOGGER = logging.getLogger(__name__) @@ -145,8 +142,7 @@ class IetfSliceDriver(_Driver): if self.__started.is_set(): return True try: - # requests.get(url, timeout=self.__timeout, auth=self.__auth) - ... + requests.get(url, timeout=self.__timeout) except requests.exceptions.Timeout: LOGGER.exception("Timeout connecting {:s}".format(url)) return False @@ -195,7 +191,6 @@ class IetfSliceDriver(_Driver): (resource_key, e) ) # if validation fails, store the exception continue - resource_node = get_subnode( resolver, self.__running, resource_path, default=None ) @@ -229,27 +224,23 @@ class IetfSliceDriver(_Driver): continue try: resource_value = json.loads(resource_value) - + slice_name = resource_value["network-slice-services"][ + "slice-service" + ][0]["connection-groups"]["connection-group"] if operation_type == "create": - # create the underlying service - # self.tac.create_slice(resource_value) - ... - elif ( - len( - resource_value["network-slice-services"]["slice-service"][ - 0 - ]["connection-groups"]["connection-group"] - ) - == 0 - and operation_type == "update" - ): - # Remove the IP transport service - # self.tac.remove_slice(service_uuid) - ... + self.tac.create_slice(resource_value) elif operation_type == "update": - # update the underlying service bandwidth - # self.tac.update_slice(resource_value) - ... + connection_groups = resource_value["network-slice-services"][ + "slice-service" + ][0]["connection-groups"]["connection-group"] + if len(connection_groups) != 1: + raise Exception("only one connection group is supported") + connection_group = connection_groups[0] + self.tac.update_slice( + slice_name, connection_group["id"], connection_group + ) + elif operation_type == "delete": + self.tac.delete_slice(slice_name) results.append((resource_key, True)) except Exception as e: # pylint: disable=broad-except LOGGER.exception( diff --git a/src/device/service/drivers/ietf_slice/tfs_slice_nbi_client.py b/src/device/service/drivers/ietf_slice/tfs_slice_nbi_client.py new file mode 100644 index 0000000000000000000000000000000000000000..5443484d7f9d1a97e92ce8733d7b40245cb09e69 --- /dev/null +++ b/src/device/service/drivers/ietf_slice/tfs_slice_nbi_client.py @@ -0,0 +1,71 @@ +# Copyright 2022-2024 ETSI OSG/SDG TeraFlowSDN (TFS) (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 logging +from typing import Optional + +import requests +from requests.auth import HTTPBasicAuth + +IETF_SLICE_URL = "{:s}://{:s}:{:d}/restconf/data/ietf-network-slice-service:ietf-nss" +TIMEOUT = 30 + +LOGGER = logging.getLogger(__name__) + + +class TfsApiClient: + def __init__( + self, + address: str, + port: int, + scheme: str = "http", + username: Optional[str] = None, + password: Optional[str] = None, + ) -> None: + self._slice_url = IETF_SLICE_URL.format(scheme, address, port) + self._auth = None + # ( + # HTTPBasicAuth(username, password) + # if username is not None and password is not None + # else None + # ) + + def create_slice(self, slice_data: dict) -> None: + url = self._slice_url + "/network-slice-services" + try: + requests.post(url, json=slice_data) + except requests.exceptions.ConnectionError: + raise Exception("faild to send post request to TFS IETF Slice NBI") + + def update_slice( + self, + slice_name: str, + connection_group_id: str, + updated_connection_group_data: dict, + ) -> None: + url = ( + self._slice_url + + f"/network-slice-services/slice-service={slice_name}/connection-groups/connection-group={connection_group_id}" + ) + try: + requests.put(url, json=updated_connection_group_data) + except requests.exceptions.ConnectionError: + raise Exception("faild to send update request to TFS IETF Slice NBI") + + def delete_slice(self, slice_name: str) -> None: + url = self._slice_url + f"/network-slice-services/slice-service={slice_name}" + try: + requests.delete(url) + except requests.exceptions.ConnectionError: + raise Exception("faild to send delete request to TFS IETF Slice NBI")