Loading src/main.py +60 −3 Original line number Diff line number Diff line Loading @@ -64,7 +64,7 @@ class NSController: self.end_time = 0 self.setup_time = 0 def nsc(self, intent_json, slice_id=None): def nsc(self, intent_json, slice_id=None, old_service_id=None): """ Main Network Slice Controller method to process and realize network slice intents. Loading @@ -80,6 +80,7 @@ class NSController: Args: intent_json (dict): Network slice intent in 3GPP or IETF format slice_id (str, optional): Existing slice identifier for modification old_service_id (str, optional): Old service identifier to delete when modifying Returns: dict: Contains slice creation responses and setup time in milliseconds Loading @@ -95,10 +96,16 @@ class NSController: # Process intent (translate if 3GPP) ietf_intents = nbi_processor(intent_json) is_update = True if slice_id else False for intent in ietf_intents: logging.debug(intent) payload = { "intent": intent, "is_update": is_update } # Mapper services, rules = mapper(intent, controller_type=self.controller_type) services, rules = mapper(payload, controller_type=self.controller_type) logging.debug(f"Services: {services}") # Build response self.response = build_response(intent, self.response, controller_type= self.controller_type) Loading @@ -118,11 +125,61 @@ class NSController: raise RuntimeError("No service to process.") # Send config to controllers response = send_controller(self.controller_type, requests) is_update = True if slice_id else False response = send_controller(self.controller_type, requests, is_update=is_update, old_service_id=old_service_id) if not response: raise Exception("Controller upload failed") # Alert subscription if flag is enabled from flask import current_app import os import requests as http_requests if current_app.config.get("SUBSCRIBE_ALERTS"): sub_url = current_app.config.get("SUBSCRIBE_ALERTS_URL") if sub_url: sub_slice_id = slice_id if not sub_slice_id: try: for intent in ietf_intents: sub_slice_id = intent["ietf-network-slice-service:network-slice-services"]["slice-service"][0]["id"] break except Exception: pass if sub_slice_id: logging.info(f"Subscribing to alerts at: {sub_url}") payload = { "tapi-notification:input": { "subscription-filter": { "requested-notification-types": [ "ALARM_EVENT", "PATH_RESTORED" ] }, "subscriber-id": "e2e-orchestrator" } } try: headers = {"Content-Type": "application/json"} sub_resp = http_requests.post(sub_url, json=payload, headers=headers, timeout=10) logging.info(f"Subscription response status: {sub_resp.status_code}") if sub_resp.status_code in (200, 201): resp_json = sub_resp.json() logging.info(f"Subscription response: {resp_json}") output = resp_json.get("tapi-notification:output", {}) subscription_id = output.get("subscription-id") or output.get("tapi-notification:subscription", {}).get("subscription-id") if subscription_id: from src.database.db import save_subscription save_subscription(subscription_id, sub_slice_id) logging.info(f"Successfully mapped subscription_id {subscription_id} to slice_id {sub_slice_id}") else: logging.warning("subscription-id not found in response output") else: logging.error(f"Subscription failed: {sub_resp.text}") except Exception as e: logging.error(f"Failed to subscribe to notification service: {e}") # End performance tracking self.end_time = time.perf_counter() setup_time = (self.end_time - self.start_time) * 1000 Loading src/mapper/main.py +5 −2 Original line number Diff line number Diff line Loading @@ -40,6 +40,8 @@ def mapper(payload, controller_type="TFS", action="CREATE"): Args: ietf_intent (dict): IETF-formatted network slice intent. controller_type (str): Type of SDN controller. is_update (bool): Whether it is a modification request. Returns: dict or None: Optimal path if planner is enabled; otherwise, None. Loading @@ -48,7 +50,7 @@ def mapper(payload, controller_type="TFS", action="CREATE"): services = None if action == "CREATE": ietf_intent = payload ietf_intent = payload.get("intent", None) services = [ietf_intent] if current_app.config["NRP_ENABLED"]: # Retrieve NRP view Loading Loading @@ -83,7 +85,8 @@ def mapper(payload, controller_type="TFS", action="CREATE"): # TODO Here we should put how the slice is attached to the new nrp if current_app.config["PLANNER_ENABLED"]: optimal_path = Planner().planner(ietf_intent, current_app.config["PLANNER_TYPE"]) is_update = payload.get("is_update", False) optimal_path = Planner().planner(ietf_intent, current_app.config["PLANNER_TYPE"], is_update=is_update) logging.debug(f"Optimal path: {optimal_path}") if controller_type == "RESTCONF": Loading src/planner/e2e_optical_planner/e2e_optical.py +4 −5 Original line number Diff line number Diff line Loading @@ -27,10 +27,9 @@ def e2e_optical_planner(intent, ip: str, action: str = "create") -> dict: action (str, optional): Operation to perform - "create" or "delete". Defaults to "create" """ if action == 'delete': logging.debug("DELETE REQUEST RECEIVED FOR E2EOptical: %s", intent) return None if action == 'update': url = f"http://{ip}/tfs-api/recompute_optical_path" else: url = f"http://{ip}/tfs-api/e2e_path_computation" headers = { "Content-Type": "application/json", Loading src/planner/planner.py +5 −2 Original line number Diff line number Diff line Loading @@ -30,13 +30,14 @@ class Planner: Planner class to compute the optimal path for a network slice based on energy consumption and topology. """ def planner(self, intent, type): def planner(self, intent, type, is_update=False): """ Plan the optimal path for a network slice based on energy consumption and topology. Args: intent (dict): Network slice intent type (str): Planner type (ENERGY, HRAT, TFS_OPTICAL) is_update (bool): Whether this is an update/modification request Returns: dict or None: Planner result or None if type is invalid Loading @@ -48,6 +49,8 @@ class Planner: # Use HRAT planner with configured IP elif type == "HRAT" : return hrat_planner(intent, current_app.config["HRAT_IP"]) # Use E2E optical planner with configured IP elif type == "E2E_OPTICAL": return e2e_optical_planner(intent, current_app.config["E2E_OPTICAL_IP"], action = "create") elif type == "E2E_OPTICAL": action = "update" if is_update else "create" return e2e_optical_planner(intent, current_app.config["E2E_OPTICAL_IP"], action = action) # Return None if planner type is unsupported else : return None src/realizer/e2e/e2e_connect.py +9 −2 Original line number Diff line number Diff line Loading @@ -18,13 +18,15 @@ from ..tfs.helpers.tfs_connector import tfs_connector import logging, requests import os def e2e_connect(requests, controller_ip): def e2e_connect(requests, controller_ip, is_update=False, old_service_id=None): """ Function to connect end-to-end services in TeraFlowSDN (TFS) controller. Args: requests (list): List of requests to be sent to the TFS e2e controller. controller_ip (str): IP address of the TFS e2e controller. is_update (bool): If True, updates the service instead of creating it. old_service_id (str): Old service ID to delete (optional). """ def _extract_slice_id(payload): Loading Loading @@ -58,5 +60,10 @@ def e2e_connect(requests, controller_ip): slice_id = _extract_slice_id(requests) or "TEST-SLICE" logging.info("Connecting end-to-end services in TFS controller at %s with slice ID %s", controller_ip, slice_id) if is_update: payload = dict(requests) if isinstance(requests, dict) else {"services": requests} payload["old_service_id"] = old_service_id response = tfs_connector().ipowdm_put(os.getenv("E2E_OPTICAL_IP"), slice_id, payload) else: response = tfs_connector().ipowdm_post(os.getenv("E2E_OPTICAL_IP"), slice_id, requests) return response Loading
src/main.py +60 −3 Original line number Diff line number Diff line Loading @@ -64,7 +64,7 @@ class NSController: self.end_time = 0 self.setup_time = 0 def nsc(self, intent_json, slice_id=None): def nsc(self, intent_json, slice_id=None, old_service_id=None): """ Main Network Slice Controller method to process and realize network slice intents. Loading @@ -80,6 +80,7 @@ class NSController: Args: intent_json (dict): Network slice intent in 3GPP or IETF format slice_id (str, optional): Existing slice identifier for modification old_service_id (str, optional): Old service identifier to delete when modifying Returns: dict: Contains slice creation responses and setup time in milliseconds Loading @@ -95,10 +96,16 @@ class NSController: # Process intent (translate if 3GPP) ietf_intents = nbi_processor(intent_json) is_update = True if slice_id else False for intent in ietf_intents: logging.debug(intent) payload = { "intent": intent, "is_update": is_update } # Mapper services, rules = mapper(intent, controller_type=self.controller_type) services, rules = mapper(payload, controller_type=self.controller_type) logging.debug(f"Services: {services}") # Build response self.response = build_response(intent, self.response, controller_type= self.controller_type) Loading @@ -118,11 +125,61 @@ class NSController: raise RuntimeError("No service to process.") # Send config to controllers response = send_controller(self.controller_type, requests) is_update = True if slice_id else False response = send_controller(self.controller_type, requests, is_update=is_update, old_service_id=old_service_id) if not response: raise Exception("Controller upload failed") # Alert subscription if flag is enabled from flask import current_app import os import requests as http_requests if current_app.config.get("SUBSCRIBE_ALERTS"): sub_url = current_app.config.get("SUBSCRIBE_ALERTS_URL") if sub_url: sub_slice_id = slice_id if not sub_slice_id: try: for intent in ietf_intents: sub_slice_id = intent["ietf-network-slice-service:network-slice-services"]["slice-service"][0]["id"] break except Exception: pass if sub_slice_id: logging.info(f"Subscribing to alerts at: {sub_url}") payload = { "tapi-notification:input": { "subscription-filter": { "requested-notification-types": [ "ALARM_EVENT", "PATH_RESTORED" ] }, "subscriber-id": "e2e-orchestrator" } } try: headers = {"Content-Type": "application/json"} sub_resp = http_requests.post(sub_url, json=payload, headers=headers, timeout=10) logging.info(f"Subscription response status: {sub_resp.status_code}") if sub_resp.status_code in (200, 201): resp_json = sub_resp.json() logging.info(f"Subscription response: {resp_json}") output = resp_json.get("tapi-notification:output", {}) subscription_id = output.get("subscription-id") or output.get("tapi-notification:subscription", {}).get("subscription-id") if subscription_id: from src.database.db import save_subscription save_subscription(subscription_id, sub_slice_id) logging.info(f"Successfully mapped subscription_id {subscription_id} to slice_id {sub_slice_id}") else: logging.warning("subscription-id not found in response output") else: logging.error(f"Subscription failed: {sub_resp.text}") except Exception as e: logging.error(f"Failed to subscribe to notification service: {e}") # End performance tracking self.end_time = time.perf_counter() setup_time = (self.end_time - self.start_time) * 1000 Loading
src/mapper/main.py +5 −2 Original line number Diff line number Diff line Loading @@ -40,6 +40,8 @@ def mapper(payload, controller_type="TFS", action="CREATE"): Args: ietf_intent (dict): IETF-formatted network slice intent. controller_type (str): Type of SDN controller. is_update (bool): Whether it is a modification request. Returns: dict or None: Optimal path if planner is enabled; otherwise, None. Loading @@ -48,7 +50,7 @@ def mapper(payload, controller_type="TFS", action="CREATE"): services = None if action == "CREATE": ietf_intent = payload ietf_intent = payload.get("intent", None) services = [ietf_intent] if current_app.config["NRP_ENABLED"]: # Retrieve NRP view Loading Loading @@ -83,7 +85,8 @@ def mapper(payload, controller_type="TFS", action="CREATE"): # TODO Here we should put how the slice is attached to the new nrp if current_app.config["PLANNER_ENABLED"]: optimal_path = Planner().planner(ietf_intent, current_app.config["PLANNER_TYPE"]) is_update = payload.get("is_update", False) optimal_path = Planner().planner(ietf_intent, current_app.config["PLANNER_TYPE"], is_update=is_update) logging.debug(f"Optimal path: {optimal_path}") if controller_type == "RESTCONF": Loading
src/planner/e2e_optical_planner/e2e_optical.py +4 −5 Original line number Diff line number Diff line Loading @@ -27,10 +27,9 @@ def e2e_optical_planner(intent, ip: str, action: str = "create") -> dict: action (str, optional): Operation to perform - "create" or "delete". Defaults to "create" """ if action == 'delete': logging.debug("DELETE REQUEST RECEIVED FOR E2EOptical: %s", intent) return None if action == 'update': url = f"http://{ip}/tfs-api/recompute_optical_path" else: url = f"http://{ip}/tfs-api/e2e_path_computation" headers = { "Content-Type": "application/json", Loading
src/planner/planner.py +5 −2 Original line number Diff line number Diff line Loading @@ -30,13 +30,14 @@ class Planner: Planner class to compute the optimal path for a network slice based on energy consumption and topology. """ def planner(self, intent, type): def planner(self, intent, type, is_update=False): """ Plan the optimal path for a network slice based on energy consumption and topology. Args: intent (dict): Network slice intent type (str): Planner type (ENERGY, HRAT, TFS_OPTICAL) is_update (bool): Whether this is an update/modification request Returns: dict or None: Planner result or None if type is invalid Loading @@ -48,6 +49,8 @@ class Planner: # Use HRAT planner with configured IP elif type == "HRAT" : return hrat_planner(intent, current_app.config["HRAT_IP"]) # Use E2E optical planner with configured IP elif type == "E2E_OPTICAL": return e2e_optical_planner(intent, current_app.config["E2E_OPTICAL_IP"], action = "create") elif type == "E2E_OPTICAL": action = "update" if is_update else "create" return e2e_optical_planner(intent, current_app.config["E2E_OPTICAL_IP"], action = action) # Return None if planner type is unsupported else : return None
src/realizer/e2e/e2e_connect.py +9 −2 Original line number Diff line number Diff line Loading @@ -18,13 +18,15 @@ from ..tfs.helpers.tfs_connector import tfs_connector import logging, requests import os def e2e_connect(requests, controller_ip): def e2e_connect(requests, controller_ip, is_update=False, old_service_id=None): """ Function to connect end-to-end services in TeraFlowSDN (TFS) controller. Args: requests (list): List of requests to be sent to the TFS e2e controller. controller_ip (str): IP address of the TFS e2e controller. is_update (bool): If True, updates the service instead of creating it. old_service_id (str): Old service ID to delete (optional). """ def _extract_slice_id(payload): Loading Loading @@ -58,5 +60,10 @@ def e2e_connect(requests, controller_ip): slice_id = _extract_slice_id(requests) or "TEST-SLICE" logging.info("Connecting end-to-end services in TFS controller at %s with slice ID %s", controller_ip, slice_id) if is_update: payload = dict(requests) if isinstance(requests, dict) else {"services": requests} payload["old_service_id"] = old_service_id response = tfs_connector().ipowdm_put(os.getenv("E2E_OPTICAL_IP"), slice_id, payload) else: response = tfs_connector().ipowdm_post(os.getenv("E2E_OPTICAL_IP"), slice_id, requests) return response