# Copyright 2022-2025 ETSI 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. # This file includes original contributions from Telefonica Innovación Digital S.L. import json, time, os, logging, uuid from datetime import datetime from src.helpers import tfs_connector, cisco_connector from src.Constants import DEFAULT_LOGGING_LEVEL, TFS_UPLOAD, TFS_IP, TFS_L2VPN_SUPPORT, SRC_PATH, TEMPLATES_PATH # Configure logging to provide clear and informative log messages logging.basicConfig( level=DEFAULT_LOGGING_LEVEL, format='%(levelname)s - %(message)s') class NSController: """ Network Slice Controller (NSC) - A class to manage network slice creation, modification, and deletion across different network domains. This controller handles the translation, mapping, and realization of network slice intents from different formats (3GPP and IETF) to network-specific configurations. Key Functionalities: - Intent Processing: Translate and process network slice intents - Slice Management: Create, modify, and delete network slices - NRP (Network Resource Partition) Mapping: Match slice requirements with available resources - Slice Realization: Convert intents to specific network configurations (L2VPN, L3VPN) """ def __init__(self, upload_to_tfs = TFS_UPLOAD, tfs_ip=TFS_IP, need_l2vpn_support=TFS_L2VPN_SUPPORT): """ Initialize the Network Slice Controller. Args: upload_to_tfs (bool, optional): Flag to determine if configurations should be uploaded to Teraflow system. Defaults to False. need_l2vpn_support (bool, optional): Flag to determine if additional L2VPN configuration support is required. Defaults to False. Attributes: upload_to_tfs (bool): Flag for Teraflow upload answer (dict): Stores slice creation responses tfs_requests (dict): Stores requests to be sent to Teraflow start_time (float): Tracks slice setup start time end_time (float): Tracks slice setup end time need_l2vpn_support (bool): Flag for additional L2VPN configuration support """ self.upload_to_tfs = upload_to_tfs self.tfs_ip = tfs_ip self.answer = {} self.cool_answer = {} self.start_time = 0 self.end_time = 0 self.setup_time = 0 self.need_l2vpn_support = need_l2vpn_support # Internal templates and views self.__gpp_template = "" self.__ietf_template = "" self.__teraflow_template = "" self.__nrp_view = "" self.subnet="" # API Methods def add_flow(self, intent): """ Create a new transport network slice. Args: intent (dict): Network slice intent in 3GPP or IETF format Returns: Result of the Network Slice Controller (NSC) operation API Endpoint: POST /slice Raises: ValueError: If no transport network slices are found Exception: For unexpected errors during slice creation process """ return self.nsc(intent) def get_flows(self,slice_id=None): """ Retrieve transport network slice information. This method allows retrieving: - All transport network slices - A specific slice by its ID Args: slice_id (str, optional): Unique identifier of a specific slice. Defaults to None. Returns: dict or list: - If slice_id is provided: Returns the specific slice details - If slice_id is None: Returns a list of all slices - Returns an error response if no slices are found API Endpoint: GET /slice/{id} Raises: ValueError: If no transport network slices are found Exception: For unexpected errors during file processing """ try: # Read slice database from JSON file with open(os.path.join(SRC_PATH, "slice_ddbb.json"), 'r') as file: content = json.load(file) # If specific slice ID is provided, find and return matching slice if slice_id: for slice in content: if slice["slice_id"] == slice_id: return slice # If no slices exist, raise an error if len(content) == 0: raise ValueError("Transport network slices not found") # Return all slices if no specific ID is given return content except ValueError as e: # Handle case where no slices are found return self.__send_response(False, code=404, message=str(e)) except Exception as e: # Handle unexpected errors return self.__send_response(False, code=500, message=str(e)) def modify_flow(self,slice_id, intent): """ Modify an existing transport network slice. Args: slice_id (str): Unique identifier of the slice to modify intent (dict): New intent configuration for the slice Returns: Result of the Network Slice Controller (NSC) operation API Endpoint: PUT /slice/{id} """ return self.nsc(intent, slice_id) def delete_flows(self, slice_id=None): """ Delete transport network slice(s). This method supports: - Deleting a specific slice by ID - Deleting all slices - Optional cleanup of L2VPN configurations Args: slice_id (str, optional): Unique identifier of slice to delete. Defaults to None. Returns: dict: Response indicating successful deletion or error details API Endpoint: DELETE /slice/{id} Raises: ValueError: If no slices are found to delete Exception: For unexpected errors during deletion process Notes: - If upload_to_tfs is True, attempts to delete from Teraflow - If need_l2vpn_support is True, performs additional L2VPN cleanup """ try: # Read current slice database with open(os.path.join(SRC_PATH, "slice_ddbb.json"), 'r') as file: content = json.load(file) id = None # Delete specific slice if slice_id is provided if slice_id: for i, slice in enumerate(content): if slice["slice_id"] == slice_id: del content[i] id = i break # Raise error if slice not found if id is None: raise ValueError("Transport network slice not found") # Update slice database with open(os.path.join(SRC_PATH, "slice_ddbb.json"), 'w') as file: json.dump(content, file, indent=4) logging.info(f"Slice {slice_id} removed successfully") return self.__send_response(False, code=200, status="success", message=f"Transpor network slice {slice_id} deleted successfully") # Delete all slices else: # Optional: Delete in Teraflow if configured if self.upload_to_tfs == True: # TODO: should send a delete request to Teraflow if self.need_l2vpn_support: self.__tfs_l2vpn_delete() # Verify slices exist before deletion with open(os.path.join(SRC_PATH, "slice_ddbb.json"), 'r') as file: if len(json.load(file)) == 0: raise ValueError("Transport network slices not found") # Clear slice database with open(os.path.join(SRC_PATH, "slice_ddbb.json"), 'w') as file: json.dump([], file, indent=4) logging.info("All slices removed successfully") return self.__send_response(False, code=200, status="success", message="All transport network slices deleted successfully.") except ValueError as e: return self.__send_response(False, code=404, message=str(e)) except Exception as e: return self.__send_response(False, code=500, message=str(e)) # Main NSC Functionalities def nsc(self, intent_json, slice_id=None): """ Main Network Slice Controller method to process and realize network slice intents. Workflow: 1. Load IETF template 2. Process intent (detect format, translate if needed) 3. Extract slice data 4. Store slice information 5. Map slice to Network Resource Pool (NRP) 6. Realize slice configuration 7. Upload to Teraflow (optional) Args: intent_json (dict): Network slice intent in 3GPP or IETF format slice_id (str, optional): Existing slice identifier for modification Returns: tuple: Response status and HTTP status code """ try: # Start performance tracking self.start_time = time.perf_counter() # Reset requests and load IETF template self.__load_template(1, os.path.join(TEMPLATES_PATH, "ietf_template_empty.json")) tfs_requests = {"services":[]} # Process intent (translate if 3GPP) ietf_intents = self.__nbi_processor(intent_json) if ietf_intents: for intent in ietf_intents: # Extract and store slice request details self.__extract_data(intent) self.__store_data(intent, slice_id) # Mapper self.__mapper(intent) # Realizer tfs_request = self.__realizer(intent) tfs_requests["services"].append(tfs_request) else: return self.__send_response(False, code=404, message="No intents found") # Generated service logging.debug(json.dumps(tfs_requests, indent=2)) # Optional: Upload template to Teraflow if self.upload_to_tfs == True: response = tfs_connector().simple_post(self.tfs_ip, tfs_requests) if not response.ok: return self.__send_response(False, code=response.status_code, message=f"Teraflow upload failed. Response: {response.text}") # For deploying an L2VPN with path selection (not supported by Teraflow) if self.need_l2vpn_support: self.__tfs_l2vpn_support(tfs_requests["services"]) logging.info("Request sent to Teraflow") # End performance tracking self.end_time = time.perf_counter() return self.__send_response(True, code=200) except ValueError as e: return self.__send_response(False, code=400, message=str(e)) except Exception as e: return self.__send_response(False, code=500, message=str(e)) def __nbi_processor(self, intent_json): """ Process and translate network slice intents from different formats (3GPP or IETF). This method detects the input JSON format and converts 3GPP intents to IETF format. Supports multiple slice subnets in 3GPP format. Args: intent_json (dict): Input network slice intent in either 3GPP or IETF format. Returns: list: A list of IETF-formatted network slice intents. Raises: ValueError: If the JSON request format is not recognized. """ # Detect the input JSON format (3GPP or IETF) format = self.__detect_format(intent_json) ietf_intents = [] logging.info("--------NEW REQUEST--------") # TODO Needs to be generalized to support different names of slicesubnets # Process different input formats if format == "3GPP": # Translate each subnet in 3GPP format to IETF format for subnet in intent_json["RANSliceSubnet1"]["networkSliceSubnetRef"]: ietf_intents.append(self.__translator(intent_json, subnet)) logging.info(f"3GPP requests translated to IETF template") elif format == "IETF": # If already in IETF format, add directly logging.info(f"IETF intent received") ietf_intents.append(intent_json) else: # Handle unrecognized format logging.error(f"JSON request format not recognized") raise ValueError("JSON request format not recognized") return ietf_intents def __mapper(self, ietf_intent): """ Map an IETF network slice intent to the most suitable Network Resource Partition (NRP). This method: 1. Retrieves the current NRP view 2. Extracts Service Level Objectives (SLOs) from the intent 3. Finds NRPs that can meet the SLO requirements 4. Selects the best NRP based on viability and availability 5. Attaches the slice to the selected NRP or creates a new one Args: ietf_intent (dict): IETF-formatted network slice intent. Raises: Exception: If no suitable NRP is found and slice creation fails. """ # Retrieve NRP view self.__realizer(None, True, "READ") # Extract Service Level Objectives (SLOs) from the intent slos = ietf_intent["ietf-network-slice-service:network-slice-services"]["slo-sle-templates"]["slo-sle-template"][0]["slo-policy"]["metric-bound"] # Find candidate NRPs that can meet the SLO requirements candidates = [ (nrp, self.__slo_viability(slos, nrp)[1]) for nrp in self.__nrp_view if self.__slo_viability(slos, nrp)[0] and nrp["available"] ] logging.debug(f"Candidates: {candidates}") # Select the best NRP based on candidates best_nrp = max(candidates, key=lambda x: x[1])[0] if candidates else None logging.debug(f"Best NRP: {best_nrp}") if best_nrp: best_nrp["slices"].append(ietf_intent["ietf-network-slice-service:network-slice-services"]["slice-service"][0]["id"]) # Update NRP view self.__realizer(ietf_intent, True, "UPDATE") # TODO Here we should put how the slice is attached to an already created nrp else: # Request the controller to create a new NRP that meets the SLOs answer = self.__realizer(ietf_intent, True, "CREATE", best_nrp) if not answer: raise Exception("Slice rejected due to lack of NRPs") # TODO Here we should put how the slice is attached to the new nrp def __realizer(self, ietf_intent, need_nrp=False, order=None, nrp=None): """ Manage the slice creation workflow. This method handles two primary scenarios: 1. Interact with network controllers for NRP (Network Resource Partition) operations when need_nrp is True 2. Slice service selection when need_nrp is False Args: ietf_intent (dict): IETF-formatted network slice intent. need_nrp (bool, optional): Flag to indicate if NRP operations are needed. Defaults to False. order (str, optional): Type of NRP operation (READ, UPDATE, CREATE). Defaults to None. nrp (dict, optional): Specific Network Resource Partition to operate on. Defaults to None. """ if need_nrp: # Perform NRP-related operations self.__nrp(order, nrp) else: # Select slice service method return self.__select_way("L2VPN", ietf_intent) ### Generic functionalities def __load_template(self, which, dir_t): """ Load and process JSON templates for different network slice formats. Args: which (int): Template selector (0: 3GPP, 1: IETF, other: Teraflow) dir_t (str): Directory path to the template file """ try: # Open and read the template file with open(dir_t, 'r') as source: # Clean up the JSON template template = source.read().replace('\t', '').replace('\n', '').replace("'", '"').strip() # Store template based on selector if which == 0: self.__gpp_template = template elif which == 1: self.__ietf_template = template else: self.__teraflow_template = template except Exception as e: logging.error(f"Template loading error: {e}") return self.__send_response(False, code=500, message=f"Template loading error: {e}") def __send_response(self, result, status="error", message=None, code=None): """ Generate and send a response to the 3GPP client about the slice request. Args: result (bool): Indicates whether the slice request was successful status (str, optional): Response status. Defaults to "error" message (str, optional): Additional error message. Defaults to None code (str, optional): Response code. Defaults to None Returns: tuple: A tuple containing the response dictionary and status code """ if result: # Successful slice creation logging.info("Your slice request was fulfilled sucessfully") self.setup_time = (self.end_time - self.start_time)*1000 logging.info(f"Setup time: {self.setup_time:.2f}") # Construct detailed successful response answer = { "status": "success", "code": code, "slices": [], "setup_time": self.setup_time } # Add slice details to the response for subnet in self.answer: slice_info = { "id": subnet, "source": self.answer[subnet]["Source"], "destination": self.answer[subnet]["Destination"], "vlan": self.answer[subnet]["VLAN"], "bandwidth(Mbps)": self.answer[subnet]["QoS Requirements"][0], "latency(ms)": self.answer[subnet]["QoS Requirements"][1] } answer["slices"].append(slice_info) self.cool_answer = answer else: # Failed slice creation logging.info("Your request cannot be fulfilled. Reason: "+message) self.cool_answer = { "status" :status, "code": code, "message": message } return self.cool_answer, code def __extract_data(self, intent_json): """ Extract source and destination IP addresses from the IETF intent. Args: intent_json (dict): IETF-formatted network slice intent """ # Extract source and destination IP addresses source = intent_json["ietf-network-slice-service:network-slice-services"]["slice-service"][0]["sdps"]["sdp"][0]["sdp-ip-address"] destination = intent_json["ietf-network-slice-service:network-slice-services"]["slice-service"][0]["sdps"]["sdp"][1]["sdp-ip-address"] logging.info(f"Intent generated between {source} and {destination}") # Store slice and connection details self.subnet = intent_json["ietf-network-slice-service:network-slice-services"]["slice-service"][0]["id"] self.subnet = intent_json["ietf-network-slice-service:network-slice-services"]["slice-service"][0]["id"] self.answer[self.subnet] = { "Source": source, "Destination": destination } def __store_data(self, intent, slice_id): """ Store network slice intent information in a JSON database file. This method: 1. Creates a JSON file if it doesn't exist 2. Reads existing content 3. Updates or adds new slice intent information Args: intent (dict): Network slice intent to be stored slice_id (str, optional): Existing slice ID to update. Defaults to None. """ file_path = os.path.join(SRC_PATH, "slice_ddbb.json") # Create initial JSON file if it doesn't exist if not os.path.exists(file_path): with open(file_path, 'w') as file: json.dump([], file, indent=4) # Read existing content with open(file_path, 'r') as file: content = json.load(file) # Update or add new slice intent if slice_id: # Update existing slice intent for slice in content: if slice["slice_id"] == slice_id: slice["intent"] = intent else: # Add new slice intent content.append( { "slice_id": intent["ietf-network-slice-service:network-slice-services"]["slice-service"][0]["id"], "intent": intent }) # # Write updated content back to file with open(file_path, 'w') as file: json.dump(content, file, indent=4) ### NBI processor functionalities def __detect_format(self,json_data): """ Detect the format of the input network slice intent. This method identifies whether the input JSON is in 3GPP or IETF format by checking for specific keys in the JSON structure. Args: json_data (dict): Input network slice intent JSON Returns: str or None: - "IETF" if IETF-specific keys are found - "3GPP" if 3GPP-specific keys are found - None if no recognizable format is detected """ # Check for IETF-specific key if "ietf-network-slice-service:network-slice-services" in json_data: return "IETF" # Check for 3GPP-specific keys if any(key in json_data for key in ["NetworkSlice1", "TopSliceSubnet1", "CNSliceSubnet1", "RANSliceSubnet1"]): return "3GPP" return None def __translator(self, gpp_intent, subnet): """ Translate a 3GPP network slice intent to IETF format. This method converts a 3GPP intent into a standardized IETF intent template, mapping key parameters such as QoS profiles, service endpoints, and connection details. Args: gpp_intent (dict): Original 3GPP network slice intent subnet (str): Specific subnet reference within the 3GPP intent Returns: dict: Translated IETF-formatted network slice intent Notes: - Generates a unique slice service ID using UUID - Maps QoS requirements, source/destination endpoints - Logs the translated intent to a JSON file for reference """ # Load IETF template and create a copy to modify ietf_i = json.loads(str(self.__ietf_template)) # Extract endpoint transport objects ep_transport_objects = gpp_intent[subnet]["EpTransport"] # Populate template with SLOs (currently supporting QoS profile, latency and bandwidth) ietf_i["ietf-network-slice-service:network-slice-services"]["slo-sle-templates"]["slo-sle-template"][0]["id"] = gpp_intent[ep_transport_objects[0]]["qosProfile"] ietf_i["ietf-network-slice-service:network-slice-services"]["slo-sle-templates"]["slo-sle-template"][0]["slo-policy"]["metric-bound"][0]["bound"] = gpp_intent[subnet]["SliceProfileList"][0]["RANSliceSubnetProfile"]["uLThptPerSliceSubnet"]["MaxThpt"] ietf_i["ietf-network-slice-service:network-slice-services"]["slo-sle-templates"]["slo-sle-template"][0]["slo-policy"]["metric-bound"][1]["bound"] = gpp_intent[subnet]["SliceProfileList"][0]["RANSliceSubnetProfile"]["uLLatency"] # Generate unique slice service ID and description ietf_i["ietf-network-slice-service:network-slice-services"]["slice-service"][0]["id"] = f"slice-service-{uuid.uuid4()}" ietf_i["ietf-network-slice-service:network-slice-services"]["slice-service"][0]["description"] = f"Transport network slice mapped with 3GPP slice {next(iter(gpp_intent))}" ietf_i["ietf-network-slice-service:network-slice-services"]["slice-service"][0]["slo-sle-policy"]["slo-sle-template"] = ietf_i["ietf-network-slice-service:network-slice-services"]["slo-sle-templates"]["slo-sle-template"][0]["id"] # Configure Source SDP ietf_i["ietf-network-slice-service:network-slice-services"]["slice-service"][0]["sdps"]["sdp"][0]["node-id"] = ep_transport_objects[0].split(" ", 1)[1] ietf_i["ietf-network-slice-service:network-slice-services"]["slice-service"][0]["sdps"]["sdp"][0]["sdp-ip-address"] = gpp_intent[gpp_intent[ep_transport_objects[0]]["EpApplicationRef"][0]]["localAddress"] ietf_i["ietf-network-slice-service:network-slice-services"]["slice-service"][0]["sdps"]["sdp"][0]["service-match-criteria"]["match-criterion"][0]["match-type"] = gpp_intent[ep_transport_objects[0]]["logicalInterfaceInfo"]["logicalInterfaceType"] ietf_i["ietf-network-slice-service:network-slice-services"]["slice-service"][0]["sdps"]["sdp"][0]["service-match-criteria"]["match-criterion"][0]["value"] = gpp_intent[ep_transport_objects[0]]["logicalInterfaceInfo"]["logicalInterfaceId"] ietf_i["ietf-network-slice-service:network-slice-services"]["slice-service"][0]["sdps"]["sdp"][0]["attachment-circuits"]["attachment-circuit"][0]["ac-ipv4-address"] = gpp_intent[ep_transport_objects[0]]["IpAddress"] ietf_i["ietf-network-slice-service:network-slice-services"]["slice-service"][0]["sdps"]["sdp"][0]["attachment-circuits"]["attachment-circuit"][0]["sdp-peering"]["peer-sap-id"] = gpp_intent[ep_transport_objects[0]]["NextHopInfo"] # Configure Destination SDP ietf_i["ietf-network-slice-service:network-slice-services"]["slice-service"][0]["sdps"]["sdp"][1]["node-id"] = ep_transport_objects[1].split(" ", 1)[1] ietf_i["ietf-network-slice-service:network-slice-services"]["slice-service"][0]["sdps"]["sdp"][1]["sdp-ip-address"] = gpp_intent[gpp_intent[ep_transport_objects[1]]["EpApplicationRef"][0]]["localAddress"] ietf_i["ietf-network-slice-service:network-slice-services"]["slice-service"][0]["sdps"]["sdp"][1]["service-match-criteria"]["match-criterion"][0]["match-type"] = gpp_intent[ep_transport_objects[1]]["logicalInterfaceInfo"]["logicalInterfaceType"] ietf_i["ietf-network-slice-service:network-slice-services"]["slice-service"][0]["sdps"]["sdp"][1]["service-match-criteria"]["match-criterion"][0]["value"] = gpp_intent[ep_transport_objects[1]]["logicalInterfaceInfo"]["logicalInterfaceId"] ietf_i["ietf-network-slice-service:network-slice-services"]["slice-service"][0]["sdps"]["sdp"][1]["attachment-circuits"]["attachment-circuit"][0]["ac-ipv4-address"] = gpp_intent[ep_transport_objects[1]]["IpAddress"] ietf_i["ietf-network-slice-service:network-slice-services"]["slice-service"][0]["sdps"]["sdp"][1]["attachment-circuits"]["attachment-circuit"][0]["sdp-peering"]["peer-sap-id"] = gpp_intent[ep_transport_objects[1]]["NextHopInfo"] # Configure Connection Group and match-criteria ietf_i["ietf-network-slice-service:network-slice-services"]["slice-service"][0]["connection-groups"]["connection-group"][0]["id"] = f"{ep_transport_objects[0].split(' ', 1)[1]}_{ep_transport_objects[1].split(' ', 1)[1]}" ietf_i["ietf-network-slice-service:network-slice-services"]["slice-service"][0]["sdps"]["sdp"][0]["service-match-criteria"]["match-criterion"][0]["target-connection-group-id"] = f"{ep_transport_objects[0].split(' ', 1)[1]}_{ep_transport_objects[1].split(' ', 1)[1]}" ietf_i["ietf-network-slice-service:network-slice-services"]["slice-service"][0]["sdps"]["sdp"][1]["service-match-criteria"]["match-criterion"][0]["target-connection-group-id"] = f"{ep_transport_objects[0].split(' ', 1)[1]}_{ep_transport_objects[1].split(' ', 1)[1]}" # Log translated intent for debugging logging.debug(json.dumps(ietf_i,indent=2)) with open(os.path.join(TEMPLATES_PATH, "ietf_template_example.json"), "w") as archivo: archivo.write(json.dumps(ietf_i,indent=2)) return ietf_i ### Mapper functionalities def __slo_viability(self, slice_slos, nrp_slos): """ Compare Service Level Objectives (SLOs) between a slice and a Network Resource Partition (NRP). This method assesses whether an NRP can satisfy the SLOs of a network slice. Args: slice_slos (list): Service Level Objectives of the slice nrp_slos (dict): Service Level Objectives of the Network Resource Pool Returns: tuple: A boolean indicating viability and a flexibility score - First value: True if NRP meets SLOs, False otherwise - Second value: A score representing how well the NRP meets the SLOs """ # Define SLO types for maximum and minimum constraints slo_type = { "max": ["one-way-delay-maximum", "two-way-delay-maximum", "one-way-delay-percentile", "two-way-delay-percentile", "one-way-delay-variation-maximum", "two-way-delay-variation-maximum", "one-way-delay-variation-percentile", "two-way-delay-variation-percentile", "one-way-packet-loss", "two-way-packet-loss"], "min": ["one-way-bandwidth", "two-way-bandwidth", "shared-bandwidth"] } flexibility_scores = [] for slo in slice_slos: for nrp_slo in nrp_slos['slos']: if slo["metric-type"] == nrp_slo["metric-type"]: # Handle maximum type SLOs if slo["metric-type"] in slo_type["max"]: flexibility = (nrp_slo["bound"] - slo["bound"]) / slo["bound"] if slo["bound"] > nrp_slo["bound"]: return False, 0 # Does not meet maximum constraint # Handle minimum type SLOs if slo["metric-type"] in slo_type["min"]: flexibility = (slo["bound"] - nrp_slo["bound"]) / slo["bound"] if slo["bound"] < nrp_slo["bound"]: return False, 0 # Does not meet minimum constraint flexibility_scores.append(flexibility) break # Exit inner loop after finding matching metric # Calculate final viability score score = sum(flexibility_scores) / len(flexibility_scores) if flexibility_scores else 0 return True, score # Si pasó todas las verificaciones, la NRP es viable def __planner(self, intent, nrp_view): """ TODO Request slice viability from the Planner module. This method prepares and sends a viability request for network slice creation, detaching the implementation from the main core thread. Args: intent (dict): Network slice intent nrp_view (list): Current Network Resource Pool view Returns: tuple: A tuple containing: - Viability status (str): "Good" or other status - Reason (str): Explanation of viability Notes: - Calculates source and destination service delivery points (SDP) - Extracts QoS requirements - Performs viability check through internal methods """ #Version 1 matriz = {} matriz["payloads"] = [] #for i in intent: # SI ya existe, suma, si no existe, lo crea origen = self.__calculate_sdp(intent["ietf-network-slice-service:network-slice-services"]["slice-service"][0]["sdps"]["sdp"][0]["sdp-ip-address"]) destino = self.__calculate_sdp(intent["ietf-network-slice-service:network-slice-services"]["slice-service"][0]["sdps"]["sdp"][1]["sdp-ip-address"]) #qos_req = i["ietf-network-slice-service:network-slice-services"]["slice-service"][0]["slo-sle-policy"]["slo-sle-template"] qos_req = intent["ietf-network-slice-service:network-slice-services"]["slo-sle-templates"]["slo-sle-template"][0]["slo-policy"]["metric-bound"][0]["bound"] payload = { "Source": origen, "Destination": destino, "QoS requirements": qos_req } matriz["payloads"].append(payload) m_res = [] m_qos = [] for p in matriz["payloads"]: res = p m_res.append(res) m_qos.append(p["QoS requirements"]) m_res, m_qos = self.__viability(m_res,intent, m_qos) reason="Viable" viability = "Good" return viability, reason def __viability(self, matrix, intent, qos): """ TODO """ aux = {} aux["good"] = [] aux["bad"] = [] l_qos = [] for i in range(len(intent)): # if matrix[i]['success'] == True: aux["good"].append(matrix[i]) l_qos.append(qos[i]) # else: # aux["bad"].append(intent[i]) return aux, l_qos def __calculate_sdp(self, pip): ''' TODO Imput: Output: Work: Identifies the network entpoint from the IP-sdp received. Version 0 will be done directly with next hop. Version 1 will translate directly from the public IP of each node to their Loopback address. ''' nid = 0 with open(os.path.join(TEMPLATES_PATH, "ips.json"), 'r') as source: jason = source.read() jason = jason.replace('\t', '').replace('\n', '').replace("'", '"').strip() nodos = json.loads(str(jason)) #template = json.loads(str(jason)) # Once we have the template, we search for the one charged. for nodo in nodos["public-prefixes"]: if pip == nodo["prefix"]: #nid = nodo["node-id"] nid = nodo["node-name"] for nodo in nodos["CU"]: if pip == nodo["prefix"]: #nid = nodo["node-id"] nid = nodo["node-name"] for nodo in nodos["DU"]: if pip == nodo["prefix"]: #nid = nodo["node-id"] nid = nodo["node-name"] return nid ### Realizer functionalities. def __nrp(self, request, nrp): """ Manage Network Resource Partition (NRP) operations. This method handles CRUD operations for Network Resource Partitions, interacting with Network Controllers (currently done statically via a JSON-based database file). Args: request (str): The type of operation to perform. Supported values: - "CREATE": Add a new NRP to the database - "READ": Retrieve the current NRP view - "UPDATE": Update an existing NRP (currently a placeholder) nrp (dict): The Network Resource Partition details to create or update. Returns: None or answer: - For "CREATE": Returns the response from the controller (currently using a static JSON) - For "READ": Gets the NRP view from the controller (currently using a static JSON) - For "UPDATE": Placeholder for update functionality Notes: - Uses a local JSON file "nrp_ddbb.json" to store NRP information as controller operation is not yet defined """ if request == "CREATE": # TODO: Implement actual request to Controller to create an NRP logging.debug("Creating NRP") # Load existing NRP database with open(os.path.join(SRC_PATH, "nrp_ddbb.json"), "r") as archivo: self.__nrp_view = json.load(archivo) # Append new NRP to the view self.__nrp_view.append(nrp) # Placeholder for controller POST request answer = None return answer elif request == "READ": # TODO: Request to Controller to get topology and current NRP view logging.debug("Reading Topology") # Load NRP database with open(os.path.join(SRC_PATH, "nrp_ddbb.json"), "r") as archivo: self.__nrp_view = json.load(archivo) elif request == "UPDATE": # TODO: Implement request to Controller to update NRP logging.debug("Updating NRP") answer = "" def __select_way(self,way, ietf_intent): """ Determine the method of slice realization. Args: way (str): The type of technology to use. Supported values: - "L2VPN": Layer 2 Virtual Private Network - "L3VPN": Layer 3 Virtual Private Network ietf_intent (dict): IETF-formatted network slice intent. Returns: dict: A realization request for the specified network slice type. """ if way == "L2VPN": realizing_request = self.__tfs_l2vpn(ietf_intent) elif way == "L3VPN": realizing_request = self.__tfs_l3vpn(ietf_intent) return realizing_request def __tfs_l2vpn(self, ietf_intent): """ Translate slice intent into a TeraFlow service request. This method prepares a L2VPN service request by: 1. Defining endpoint routers 2. Loading a service template 3. Generating a unique service UUID 4. Configuring service endpoints 5. Adding QoS constraints 6. Preparing configuration rules for network interfaces Args: ietf_intent (dict): IETF-formatted network slice intent. Returns: dict: A TeraFlow service request for L2VPN configuration. """ # Hardcoded router endpoints # TODO (should be dynamically determined) origin_router_id = ietf_intent["ietf-network-slice-service:network-slice-services"]["slice-service"][0]["sdps"]["sdp"][0]["attachment-circuits"]["attachment-circuit"][0]["sdp-peering"]["peer-sap-id"] origin_router_if = '0/0/0-GigabitEthernet0/0/0/0' destination_router_id = ietf_intent["ietf-network-slice-service:network-slice-services"]["slice-service"][0]["sdps"]["sdp"][1]["attachment-circuits"]["attachment-circuit"][0]["sdp-peering"]["peer-sap-id"] destination_router_if = '0/0/3-GigabitEthernet0/0/0/3' # Extract QoS Profile from intent QoSProfile = ietf_intent["ietf-network-slice-service:network-slice-services"]["slo-sle-templates"]["slo-sle-template"][0]["id"] vlan_value = 0 # Load L2VPN service template self.__load_template(2, os.path.join(TEMPLATES_PATH, "L2-VPN_template_empty.json")) tfs_request = json.loads(str(self.__teraflow_template))["services"][0] # Generate unique service UUID tfs_request["service_id"]["service_uuid"]["uuid"] += "-" + str(int(datetime.now().timestamp() * 1e7)) # Configure service endpoints for endpoint in tfs_request["service_endpoint_ids"]: endpoint["device_id"]["device_uuid"]["uuid"] = origin_router_id if endpoint is tfs_request["service_endpoint_ids"][0] else destination_router_id endpoint["endpoint_uuid"]["uuid"] = origin_router_if if endpoint is tfs_request["service_endpoint_ids"][0] else destination_router_if self.answer[self.subnet]["QoS Requirements"] = [] # Add service constraints for i, constraint in enumerate(tfs_request["service_constraints"]): bound = ietf_intent["ietf-network-slice-service:network-slice-services"]["slo-sle-templates"]["slo-sle-template"][0]["slo-policy"]["metric-bound"][i]["bound"] self.answer[self.subnet]["QoS Requirements"].append(bound) constraint["custom"]["constraint_value"] = str(bound) # Add configuration rules for i, config_rule in enumerate(tfs_request["service_config"]["config_rules"][1:], start=1): router_id = origin_router_id if i == 1 else destination_router_id router_if = origin_router_if if i == 1 else destination_router_if resource_value = config_rule["custom"]["resource_value"] sdp_index = i - 1 vlan_value = ietf_intent["ietf-network-slice-service:network-slice-services"]["slice-service"][0]["sdps"]["sdp"][sdp_index]["service-match-criteria"]["match-criterion"][0]["value"] resource_value["vlan_id"] = int(vlan_value) resource_value["circuit_id"] = vlan_value resource_value["remote_router"] = destination_router_id if i == 1 else origin_router_id resource_value["ni_name"] = 'ELAN{:s}'.format(str(vlan_value)) config_rule["custom"]["resource_key"] = f"/device[{router_id}]/endpoint[{router_if}]/settings" # Log and store VLAN information logging.info(f"Intent with VLAN {vlan_value} realized\n") self.answer[self.subnet]["VLAN"] = vlan_value return tfs_request def __tfs_l2vpn_support(self, requests): """ Configuration support for L2VPN with path selection based on MPLS traffic-engineering tunnels Args: requests (list): A list of configuration parameters. """ sources={ "source": "10.60.125.44", "config":[] } destinations={ "destination": "10.60.125.45", "config":[] } for request in requests: # Configure Source Endpoint temp_source = request["service_config"]["config_rules"][1]["custom"]["resource_value"] endpoints = request["service_endpoint_ids"] config = { "ni_name": temp_source["ni_name"], "remote_router": temp_source["remote_router"], "interface": endpoints[0]["endpoint_uuid"]["uuid"].replace("0/0/0-", ""), "vlan" : temp_source["vlan_id"], "number" : temp_source["vlan_id"] % 10 + 1 } sources["config"].append(config) # Configure Destination Endpoint temp_destiny = request["service_config"]["config_rules"][2]["custom"]["resource_value"] config = { "ni_name": temp_destiny["ni_name"], "remote_router": temp_destiny["remote_router"], "interface": endpoints[1]["endpoint_uuid"]["uuid"].replace("0/0/3-", ""), "vlan" : temp_destiny["vlan_id"], "number" : temp_destiny["vlan_id"] % 10 + 1 } destinations["config"].append(config) #cisco_source = cisco_connector(source_address, ni_name, remote_router, vlan, vlan % 10 + 1) cisco_source = cisco_connector(sources["source"], sources["config"]) commands = cisco_source.full_create_command_template() cisco_source.execute_commands(commands) #cisco_destiny = cisco_connector(destination_address, ni_name, remote_router, vlan, vlan % 10 + 1) cisco_destiny = cisco_connector(destinations["destination"], destinations["config"]) commands = cisco_destiny.full_create_command_template() cisco_destiny.execute_commands(commands) def __tfs_l2vpn_delete(self): """ Delete L2VPN configurations from Cisco devices. This method removes L2VPN configurations from Cisco routers Notes: - Uses cisco_connector to generate and execute deletion commands - Clears Network Interface (NI) settings """ # Delete Source Endpoint Configuration source_address = "10.60.125.44" cisco_source = cisco_connector(source_address) cisco_source.execute_commands(cisco_source.create_command_template_delete()) # Delete Destination Endpoint Configuration destination_address = "10.60.125.45" cisco_destiny = cisco_connector(destination_address) cisco_destiny.execute_commands(cisco_destiny.create_command_template_delete()) def __tfs_l3vpn(self, ietf_intent): """ Translate L3VPN (Layer 3 Virtual Private Network) intent into a TeraFlow service request. Similar to __tfs_l2vpn, but configured for Layer 3 VPN: 1. Defines endpoint routers 2. Loads service template 3. Generates unique service UUID 4. Configures service endpoints 5. Adds QoS constraints 6. Prepares configuration rules for network interfaces Args: ietf_intent (dict): IETF-formatted network slice intent. Returns: dict: A TeraFlow service request for L3VPN configuration. """ # Hardcoded router endpoints # TODO should be dynamically determined origin_router_id = ietf_intent["ietf-network-slice-service:network-slice-services"]["slice-service"][0]["sdps"]["sdp"][0]["attachment-circuits"]["attachment-circuit"][0]["sdp-peering"]["peer-sap-id"] origin_router_if = '0/0/0-GigabitEthernet0/0/0/0' destination_router_id = ietf_intent["ietf-network-slice-service:network-slice-services"]["slice-service"][0]["sdps"]["sdp"][1]["attachment-circuits"]["attachment-circuit"][0]["sdp-peering"]["peer-sap-id"] destination_router_if = '0/0/3-GigabitEthernet0/0/0/3' # Load L3VPN service template self.__load_template(2, os.path.join(TEMPLATES_PATH, "L3-VPN_template_empty.json")) tfs_request = json.loads(str(self.__teraflow_template))["services"][0] # Generate unique service UUID tfs_request["service_id"]["service_uuid"]["uuid"] += "-" + str(int(datetime.now().timestamp() * 1e7)) # Configure service endpoints for endpoint in tfs_request["service_endpoint_ids"]: endpoint["device_id"]["device_uuid"]["uuid"] = origin_router_id if endpoint is tfs_request["service_endpoint_ids"][0] else destination_router_id endpoint["endpoint_uuid"]["uuid"] = origin_router_if if endpoint is tfs_request["service_endpoint_ids"][0] else destination_router_if # Add service constraints for i, constraint in enumerate(tfs_request["service_constraints"]): bound = ietf_intent["ietf-network-slice-service:network-slice-services"]["slo-sle-templates"]["slo-sle-template"][0]["slo-policy"]["metric-bound"][i]["bound"] constraint["custom"]["constraint_value"] = str(bound) # Add configuration rules for i, config_rule in enumerate(tfs_request["service_config"]["config_rules"][1:], start=1): router_id = origin_router_id if i == 1 else destination_router_id router_if = origin_router_if if i == 1 else destination_router_if resource_value = config_rule["custom"]["resource_value"] sdp_index = i - 1 vlan_value = ietf_intent["ietf-network-slice-service:network-slice-services"]["slice-service"][0]["sdps"]["sdp"][sdp_index]["service-match-criteria"]["match-criterion"][0]["value"] resource_value["router_id"] = destination_router_id if i == 1 else origin_router_id resource_value["vlan_id"] = int(vlan_value) resource_value["address_ip"] = destination_router_id if i == 1 else origin_router_id resource_value["policy_AZ"] = "policyA" resource_value["policy_ZA"] = "policyB" resource_value["ni_name"] = 'ELAN{:s}'.format(str(vlan_value)) config_rule["custom"]["resource_key"] = f"/device[{router_id}]/endpoint[{router_if}]/settings" return tfs_request