Commit 877eccd2 authored by Javier Velázquez's avatar Javier Velázquez
Browse files

Merge branch 'feat/38-tid-audate-service-fot-e2e-optical-intents' into 'develop'

Resolve "(TID) Update service fot E2E Optical intents"

See merge request !38
parents f32fb21d 27abe54d
Loading
Loading
Loading
Loading
Loading
+60 −3
Original line number Diff line number Diff line
@@ -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.

@@ -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
@@ -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)
@@ -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
+7 −2
Original line number Diff line number Diff line
@@ -40,6 +40,10 @@ 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.
        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.
@@ -48,7 +52,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
@@ -83,7 +87,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":
+4 −5
Original line number Diff line number Diff line
@@ -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",
+5 −2
Original line number Diff line number Diff line
@@ -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
@@ -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
+9 −2
Original line number Diff line number Diff line
@@ -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):
@@ -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