Commit 2415fb2b authored by Javier Velázquez's avatar Javier Velázquez
Browse files

- Add get_service_path and get_device_name

- Change realizer monitor action
- Update tests
parent 7accfa02
Loading
Loading
Loading
Loading
Loading
+3 −15
Original line number Diff line number Diff line
@@ -15,7 +15,7 @@
# This file is an original contribution from Telefonica Innovación Digital S.L.

from src.utils.send_response import send_response
import logging, json, time, requests, traceback, asyncio, aiohttp
import logging, json, asyncio
from flask import current_app
from src.database.db import get_data, delete_data, get_all_data, delete_all_data
from src.database.service_db import delete_by_slice_id, get_data_by_slice_id
@@ -26,7 +26,6 @@ from src.utils.safe_get import safe_get
from src.database.sysrepo_store import get_data_store, create_data_store, delete_data_store, update_data_store, normalize_libyang_data
from typing import Dict, Tuple
from src.realizer.restconf.connectors.tfs_connector import tfs_connector as tfs_restconf_connector
from src.planner.shortest_path import get_shortest_path
from src.realizer.tfs.service_types.tfs_l2vpn import tfs_l2vpn_delete


@@ -1158,12 +1157,6 @@ class Api:
        logging.debug(f"Getting telemetry for slice_id: {slice_id}")
        try:         
            if slice_id is not None:
                slice_sdps = get_data_store(f"/ietf-network-slice-service:network-slice-services/slice-service[id='{slice_id}']/sdps")
                logging.debug(f"SDPs for slice_id '{slice_id}': {slice_sdps}")
                if not slice_sdps:
                    raise ValueError("No SDPs found")
                if len(slice_sdps) > 2:
                    raise Exception(f"Monitoring for more than 2 SDPs is not supported. Found {len(slice_sdps)} SDPs.")
                xpath = f"/ietf-network-slice-service:network-slice-services/slice-service[id='{slice_id}']"
                existing_slice = get_data_store(xpath)
                if not existing_slice:
@@ -1174,7 +1167,7 @@ class Api:
                slo_sle_template = next(iter(slo_sle_template), None)
                if not slo_sle_template:
                    raise ValueError(f"SLO/SLE template '{template_id}' not found for slice '{slice_id}'")
                metrics = self.slice_service.monitoring(slice_id, slo_sle_template, slice_sdps)
                metrics = self.slice_service.monitoring(slice_id, slo_sle_template)
                return metrics, 200
            
            telemetry_data = {}
@@ -1191,12 +1184,7 @@ class Api:
                if not slo_sle_template:
                    raise ValueError(f"SLO/SLE template '{selected_template_id}' not found for slice '{slice['id']}'")
                slice_id = slice["id"] 
                slice_sdps = get_data_store(f"/ietf-network-slice-service:network-slice-services/slice-service[id='{slice_id}']/sdps") 
                if not slice_sdps:
                    raise ValueError("No SDPs found")
                if len(slice_sdps) > 2:
                    raise Exception(f"Monitoring for more than 2 SDPs is not supported. Found {len(slice_sdps)} SDPs.")
                telemetry_data[slice_id] = self.slice_service.monitoring(slice_id, slo_sle_template, slice_sdps)
                telemetry_data[slice_id] = self.slice_service.monitoring(slice_id, slo_sle_template)
            return telemetry_data, 200

        except ValueError as e:
+2 −4
Original line number Diff line number Diff line
@@ -23,7 +23,6 @@ from src.database.store_data import store_data
from src.database.service_db import delete_data
from src.mapper.main import mapper
from src.realizer.main import realizer
from src.planner.shortest_path import get_shortest_path
from src.realizer.send_controller import send_controller

class NSController:
@@ -197,7 +196,7 @@ class NSController:
            "setup_time": setup_time
        }
    
    def monitoring(self, slice_id, slo_sle_template, sdps):
    def monitoring(self, slice_id, slo_sle_template):
        """
        Monitor the status of a specific network slice.

@@ -219,8 +218,7 @@ class NSController:
        """
        payload = {
            "slice_id": slice_id,
            "slo_sle_template": slo_sle_template,
            "sdps": sdps
            "slo_sle_template": slo_sle_template
        }

        # Request the realizer to retrieve the metrics for the links of the specified slice
+2 −2
Original line number Diff line number Diff line
@@ -35,7 +35,7 @@ def get_shortest_path(network, src_node_id, dst_node_id, directed_graph: bool =
        nodes = network["node"]
        links = network["ietf-network-topology:link"]

        # Construir grafo
        # Build graph
        graph = {
            normalize_node_id(node["node-id"]): set()
            for node in nodes
@@ -77,7 +77,7 @@ def get_shortest_path(network, src_node_id, dst_node_id, directed_graph: bool =
        if not visited[destination_node_idx]:
            return {"message": "No path found"}, 404

        # Reconstruir camino
        # Rebuild path
        path = []
        at = destination_node_idx

+10 −16
Original line number Diff line number Diff line
@@ -19,7 +19,7 @@ from .select_way import select_way
from .nrp_handler import nrp_handler
from .get_metrics import get_metrics
from src.utils.safe_get import safe_get
from src.planner.shortest_path import get_shortest_path
from src.database.service_db import get_data_by_slice_id
from src.realizer.restconf.connectors.tfs_connector import tfs_connector
from flask import current_app

@@ -90,20 +90,14 @@ def realizer(payload, need_nrp=False, order=None, nrp=None, controller_type=None
    elif    action == "MONITOR":
        logging.debug("Realizer action: MONITOR")
        slice_id = payload.get("slice_id", None)
        sdps = payload.get("sdps", None)
        sdps_object = safe_get(sdps, ['network-slice-services', 'slice-service'])
        sdps_object = sdps_object[slice_id]['sdps']['sdp']
        sdp_ids = [sdp["id"] for sdp in sdps_object]
        # Retrieve Network topology
        topology, response = tfs_connector().get_network_topology(current_app.config["RESTCONF_IP"],slice_id)
        service_data = get_data_by_slice_id(slice_id)
        if len(service_data) > 1:
            raise ValueError(f"Slices with multiple associated services are not supported: '{slice_id}'")
        service_id = service_data[0]["service_id"]
        # Retrieve Service Path
        path, response = tfs_connector().get_service_path(current_app.config["RESTCONF_IP"], service_id)
        if response == 200:
            logging.debug(f"Retrieved topology for slice '{slice_id}'")
            # Get shortest path
            path, response = get_shortest_path(topology, sdp_ids[0], sdp_ids[1])
            if response == 200:
                logging.debug(f"Retrieved shortest path for slice '{slice_id}': {path}")
            logging.debug(f"Retrieved service path for slice '{slice_id}' (service '{service_id}'): {path}")
            get_metrics(path, slice_id, controller_type)
        else:
                raise Exception("Error: Shortest path not retrieved ")
        else:
            raise Exception("Error: Topology not retrieved")
 No newline at end of file
            raise Exception("Error: Service path not retrieved")
 No newline at end of file
+65 −1
Original line number Diff line number Diff line
@@ -16,7 +16,7 @@

import logging, requests, json, aiohttp, asyncio, threading
from src.config.constants import NBI_L2_PATH, NBI_L3_PATH, NBI_IETF_NETWORKS_PATH, NBI_SIMAP_SUSCRIPTION_PATH
from typing import Dict, Tuple
from typing import Dict, Tuple, List

# Temp until moving to .env
SDN_SUBSCRIPTION_PERIOD = 10  # seconds
@@ -141,6 +141,70 @@ class tfs_connector():

        return network, 200

    def get_device_name(self, tfs_ip: str, device_uuid: str) -> str:
        """
        Get device name for a given device_uuid from TFS.

        Args:
            tfs_ip (str): IP address of the TFS instance
            device_uuid (str): Device UUID

        Returns:
            str: Name of the device
        """
        url = f"http://{tfs_ip}/tfs-api/device/{device_uuid}"
        headers = {'accept': 'application/json'}
        response = requests.get(url, headers=headers, timeout=60)
        response.raise_for_status()
        device_data = response.json()
        return device_data.get("name", "")

    def get_service_path(self, tfs_ip: str, service_id: str) -> Tuple[List[str], int]:
        """
        Get ordered list of node names along the service path from TFS.
        
        Args:
            tfs_ip (str): IP address of the TFS instance
            service_id (str): Service UUID
            
        Returns:
            Tuple[List[str], int]: List of node names in order and status code
        """
        url = f'http://{tfs_ip}/tfs-api/context/admin/service/{service_id}/connections'
        headers = {'accept': 'application/json'}
        try:
            response = requests.get(url, headers=headers, timeout=60)
            if response.status_code == 200:
                data = response.json()
                connections = data.get("connections", [])
                raw_uuids = []
                for conn in connections:
                    for hop in conn.get("path_hops_endpoint_ids", []):
                        device_id = hop.get("device_id", {}).get("device_uuid", {}).get("uuid")
                        if device_id and (not raw_uuids or raw_uuids[-1] != device_id):
                            raw_uuids.append(device_id)

                name_cache = {}
                path = []
                for dev_uuid in raw_uuids:
                    if dev_uuid not in name_cache:
                        try:
                            dev_name = self.get_device_name(tfs_ip, dev_uuid)
                            name_cache[dev_uuid] = dev_name if dev_name else dev_uuid
                        except Exception as e:
                            logging.warning(f"Could not retrieve device name for '{dev_uuid}': {e}")
                            name_cache[dev_uuid] = dev_uuid
                    path.append(name_cache[dev_uuid])

                logging.debug(f"Retrieved service path for service '{service_id}': {path}")
                return path, 200
            else:
                logging.error(f"Failed to retrieve service path for service '{service_id}': status {response.status_code}")
                return [], response.status_code
        except Exception as e:
            logging.exception(f"Error retrieving service path for service '{service_id}': {e}")
            return [], 500

    # --- SDN STREAMS ---

    async def get_session(self):
Loading