Loading src/device/service/drivers/ietf_slice/TfsApiClient.pydeleted 100644 → 0 +0 −172 Original line number Diff line number Diff line # 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") src/device/service/drivers/ietf_slice/Tools.py +1 −194 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -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, Loading src/device/service/drivers/ietf_slice/driver.py +18 −27 Original line number Diff line number Diff line Loading @@ -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__) Loading Loading @@ -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 Loading Loading @@ -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 ) Loading Loading @@ -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( Loading src/device/service/drivers/ietf_slice/tfs_slice_nbi_client.py 0 → 100644 +71 −0 Original line number Diff line number Diff line # 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") Loading
src/device/service/drivers/ietf_slice/TfsApiClient.pydeleted 100644 → 0 +0 −172 Original line number Diff line number Diff line # 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")
src/device/service/drivers/ietf_slice/Tools.py +1 −194 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -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, Loading
src/device/service/drivers/ietf_slice/driver.py +18 −27 Original line number Diff line number Diff line Loading @@ -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__) Loading Loading @@ -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 Loading Loading @@ -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 ) Loading Loading @@ -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( Loading
src/device/service/drivers/ietf_slice/tfs_slice_nbi_client.py 0 → 100644 +71 −0 Original line number Diff line number Diff line # 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")