From 5d46e82bafd89f1d96443f22501f3950784fcb4b Mon Sep 17 00:00:00 2001 From: armingol Date: Tue, 30 Jun 2026 08:05:57 +0000 Subject: [PATCH 1/2] feat: implement automatic intent path rerouting based on incoming alerts and service mapping --- src/api/main.py | 141 ++++++++++++++++++++++++++++++++++++++++++- src/config/config.py | 2 + src/database/db.py | 25 ++++++++ 3 files changed, 167 insertions(+), 1 deletion(-) diff --git a/src/api/main.py b/src/api/main.py index e3f3c58..c9c2fca 100644 --- a/src/api/main.py +++ b/src/api/main.py @@ -78,18 +78,157 @@ class Api: # Extract uuid if exists context = alert_data.get("tapi-notification:notification-context", []) alert_id = None + service_id = None + subscription_id = None if context and isinstance(context, list): notification = context[0].get("tapi-notification:notification", {}) alert_id = notification.get("uuid") + subscription_id = alert_id + additional_info = notification.get("additional-info", {}) + service_id = additional_info.get("service-id") if not alert_id: return send_response(False, code=400, message="UUID not found in alert data") + # Save alert to DB alert_db.save_alert(alert_id, alert_data) + + # Process intent modification based on alert + slice_id = None + slice_info = None + + logging.info(f"Looking up intent for subscription_id: {subscription_id}, service_id: {service_id}") + + if subscription_id: + try: + import src.database.db as db + mapped_slice_id = db.get_slice_id_by_subscription(subscription_id) + if mapped_slice_id: + slice_id = mapped_slice_id + logging.info(f"Found slice_id {slice_id} mapped to subscription_id {subscription_id}") + except Exception as e: + logging.info(f"Subscription mapping lookup failed: {e}") + + if not slice_id and service_id: + # 1. Try to find slice_id from service_db + try: + import src.database.service_db as service_db + service_info = service_db.get_data(service_id) + slice_id = service_info.get("slice_id") + logging.info(f"Found slice_id {slice_id} in service_db for service_id {service_id}") + except Exception as e: + logging.info(f"service_db lookup failed: {e}") + slice_id = service_id + + if slice_id: + # 2. Try to get slice data from db + try: + import src.database.db as db + slice_info = db.get_data(slice_id) + logging.info(f"Found slice_info in db by slice_id {slice_id}") + except Exception as e: + logging.info(f"db lookup by slice_id {slice_id} failed: {e}") + + # Fallback: if not found by ID, look up any existing slice in the DB + if not slice_info: + try: + import src.database.db as db + slices = db.get_all_data() + logging.info(f"Slices in db: {[s.get('slice_id') for s in slices]}") + for s in slices: + if s.get("slice_id") == slice_id: + slice_info = s + break + if not slice_info and slices: + # Default to the first/only slice if there's any + slice_info = slices[0] + logging.info(f"Defaulted to first slice from db: {slice_info.get('slice_id')}") + except Exception as e: + logging.info(f"db get_all_data lookup failed: {e}") + + # Fallback: read from /home/llmserver/tfs-nsc/intent.json if DB is empty + if not slice_info: + import os + import json + fallback_path = "/home/llmserver/tfs-nsc/intent.json" + if os.path.exists(fallback_path): + try: + with open(fallback_path, "r") as f: + intent_data = json.load(f) + slice_info = {"slice_id": slice_id or "slice", "intent": intent_data} + logging.info("Loaded fallback intent from intent.json") + except Exception as e: + logging.error(f"Failed to read fallback intent.json: {e}") + + if slice_info: + intent = slice_info.get("intent") + curr_slice_id = slice_info.get("slice_id") + logging.info(f"Processing intent for slice {curr_slice_id}") + + if intent: + nss = intent.get("ietf-network-slice-service:network-slice-services", {}) + slice_services = nss.get("slice-service", []) + modified = False + + for service in slice_services: + # Get all SDP IDs in this slice service + sdp_list = service.get("sdps", {}).get("sdp", []) + sdp_ids = [sdp.get("id") for sdp in sdp_list if sdp.get("id")] + + connection_groups = service.get("connection-groups", {}).get("connection-group", []) + for cg in connection_groups: + connectivity_constructs = cg.get("connectivity-construct", []) + for cc in connectivity_constructs: + p2mp_sender = cc.get("p2mp-sender-sdp") + p2mp_receivers = cc.get("p2mp-receiver-sdp", []) + + logging.info(f"sdp_ids: {sdp_ids}, p2mp_sender: {p2mp_sender}, p2mp_receivers: {p2mp_receivers}") + + # Find alternative receiver endpoints + other_endpoints = [ + sdp_id for sdp_id in sdp_ids + if sdp_id != p2mp_sender and sdp_id not in p2mp_receivers + ] + + old_receiver = None + new_receiver = None + + if other_endpoints and p2mp_receivers: + if len(p2mp_receivers) >= 2: + old_receiver = p2mp_receivers[1] + new_receiver = other_endpoints[0] + cc["p2mp-receiver-sdp"] = [p2mp_receivers[0], new_receiver] + else: + old_receiver = p2mp_receivers[0] + new_receiver = other_endpoints[0] + cc["p2mp-receiver-sdp"] = [new_receiver] + modified = True + + # Log ORIGEN and DESTINO + logging.info(f"ORIGEN: {p2mp_sender} DESTINO: {new_receiver}") + print(f"ORIGEN: {p2mp_sender} DESTINO: {new_receiver}", flush=True) + else: + logging.warning("No alternative receiver endpoints found to swap.") + + if p2mp_receivers: + old_service_id = f"{p2mp_sender}_to_{','.join(p2mp_receivers)}" + else: + old_service_id = None + + if modified: + # Re-apply the modified intent via slice_service.nsc + try: + self.slice_service.nsc(intent, curr_slice_id, old_service_id=old_service_id) + logging.info(f"Slice {curr_slice_id} updated successfully following alert.") + except Exception as e: + logging.error(f"Failed to update slice configuration: {e}") + else: + logging.warning("No slice intent found to process alert.") + return send_response( True, code=201, - message="Alert saved successfully", + message="Alert processed and saved successfully", data=alert_data ) except Exception as e: diff --git a/src/config/config.py b/src/config/config.py index c059ede..122c2fb 100644 --- a/src/config/config.py +++ b/src/config/config.py @@ -46,6 +46,8 @@ def create_config(app: Flask): app.config["PCE_EXTERNAL"] = os.getenv("PCE_EXTERNAL", "false").lower() == "true" app.config["HRAT_IP"] = os.getenv("HRAT_IP", "192.168.1.143") app.config["E2E_OPTICAL_IP"] = os.getenv("E2E_OPTICAL_IP", "127.0.0.1") + app.config["SUBSCRIBE_ALERTS"] = os.getenv("SUBSCRIBE_ALERTS", "false").lower() == "true" + app.config["SUBSCRIBE_ALERTS_URL"] = os.getenv("SUBSCRIBE_ALERTS_URL", "") # Realizer app.config["DUMMY_MODE"] = os.getenv("DUMMY_MODE", "true").lower() == "true" diff --git a/src/database/db.py b/src/database/db.py index f5be792..2616e11 100644 --- a/src/database/db.py +++ b/src/database/db.py @@ -31,9 +31,34 @@ def init_db(): controller TEXT NOT NULL ) """) + cursor.execute(""" + CREATE TABLE IF NOT EXISTS subscription ( + subscription_id TEXT PRIMARY KEY, + slice_id TEXT NOT NULL + ) + """) conn.commit() conn.close() +def save_subscription(subscription_id: str, slice_id: str): + conn = sqlite3.connect(DB_NAME) + cursor = conn.cursor() + try: + cursor.execute("INSERT OR REPLACE INTO subscription (subscription_id, slice_id) VALUES (?, ?)", (subscription_id, slice_id)) + conn.commit() + finally: + conn.close() + +def get_slice_id_by_subscription(subscription_id: str) -> str: + conn = sqlite3.connect(DB_NAME) + cursor = conn.cursor() + cursor.execute("SELECT slice_id FROM subscription WHERE subscription_id = ?", (subscription_id,)) + row = cursor.fetchone() + conn.close() + if row: + return row[0] + return None + # Save data to the database def save_data(slice_id: str, intent_dict: dict, controller: str): """ -- GitLab From 49cc9b28ab6872cf8469728f064044b6bd45c7c3 Mon Sep 17 00:00:00 2001 From: armingol Date: Tue, 30 Jun 2026 08:05:57 +0000 Subject: [PATCH 2/2] feat: implement automatic intent path rerouting based on incoming alerts and service mapping --- src/api/main.py | 141 ++++++++++++++++++++++++++++++++++++++++++- src/config/config.py | 2 + src/database/db.py | 25 ++++++++ 3 files changed, 167 insertions(+), 1 deletion(-) diff --git a/src/api/main.py b/src/api/main.py index b5fb0e1..365561e 100644 --- a/src/api/main.py +++ b/src/api/main.py @@ -84,18 +84,157 @@ class Api: # Extract uuid if exists context = alert_data.get("tapi-notification:notification-context", []) alert_id = None + service_id = None + subscription_id = None if context and isinstance(context, list): notification = context[0].get("tapi-notification:notification", {}) alert_id = notification.get("uuid") + subscription_id = alert_id + additional_info = notification.get("additional-info", {}) + service_id = additional_info.get("service-id") if not alert_id: return send_response(False, code=400, message="UUID not found in alert data") + # Save alert to DB alert_db.save_alert(alert_id, alert_data) + + # Process intent modification based on alert + slice_id = None + slice_info = None + + logging.info(f"Looking up intent for subscription_id: {subscription_id}, service_id: {service_id}") + + if subscription_id: + try: + import src.database.db as db + mapped_slice_id = db.get_slice_id_by_subscription(subscription_id) + if mapped_slice_id: + slice_id = mapped_slice_id + logging.info(f"Found slice_id {slice_id} mapped to subscription_id {subscription_id}") + except Exception as e: + logging.info(f"Subscription mapping lookup failed: {e}") + + if not slice_id and service_id: + # 1. Try to find slice_id from service_db + try: + import src.database.service_db as service_db + service_info = service_db.get_data(service_id) + slice_id = service_info.get("slice_id") + logging.info(f"Found slice_id {slice_id} in service_db for service_id {service_id}") + except Exception as e: + logging.info(f"service_db lookup failed: {e}") + slice_id = service_id + + if slice_id: + # 2. Try to get slice data from db + try: + import src.database.db as db + slice_info = db.get_data(slice_id) + logging.info(f"Found slice_info in db by slice_id {slice_id}") + except Exception as e: + logging.info(f"db lookup by slice_id {slice_id} failed: {e}") + + # Fallback: if not found by ID, look up any existing slice in the DB + if not slice_info: + try: + import src.database.db as db + slices = db.get_all_data() + logging.info(f"Slices in db: {[s.get('slice_id') for s in slices]}") + for s in slices: + if s.get("slice_id") == slice_id: + slice_info = s + break + if not slice_info and slices: + # Default to the first/only slice if there's any + slice_info = slices[0] + logging.info(f"Defaulted to first slice from db: {slice_info.get('slice_id')}") + except Exception as e: + logging.info(f"db get_all_data lookup failed: {e}") + + # Fallback: read from /home/llmserver/tfs-nsc/intent.json if DB is empty + if not slice_info: + import os + import json + fallback_path = "/home/llmserver/tfs-nsc/intent.json" + if os.path.exists(fallback_path): + try: + with open(fallback_path, "r") as f: + intent_data = json.load(f) + slice_info = {"slice_id": slice_id or "slice", "intent": intent_data} + logging.info("Loaded fallback intent from intent.json") + except Exception as e: + logging.error(f"Failed to read fallback intent.json: {e}") + + if slice_info: + intent = slice_info.get("intent") + curr_slice_id = slice_info.get("slice_id") + logging.info(f"Processing intent for slice {curr_slice_id}") + + if intent: + nss = intent.get("ietf-network-slice-service:network-slice-services", {}) + slice_services = nss.get("slice-service", []) + modified = False + + for service in slice_services: + # Get all SDP IDs in this slice service + sdp_list = service.get("sdps", {}).get("sdp", []) + sdp_ids = [sdp.get("id") for sdp in sdp_list if sdp.get("id")] + + connection_groups = service.get("connection-groups", {}).get("connection-group", []) + for cg in connection_groups: + connectivity_constructs = cg.get("connectivity-construct", []) + for cc in connectivity_constructs: + p2mp_sender = cc.get("p2mp-sender-sdp") + p2mp_receivers = cc.get("p2mp-receiver-sdp", []) + + logging.info(f"sdp_ids: {sdp_ids}, p2mp_sender: {p2mp_sender}, p2mp_receivers: {p2mp_receivers}") + + # Find alternative receiver endpoints + other_endpoints = [ + sdp_id for sdp_id in sdp_ids + if sdp_id != p2mp_sender and sdp_id not in p2mp_receivers + ] + + old_receiver = None + new_receiver = None + + if other_endpoints and p2mp_receivers: + if len(p2mp_receivers) >= 2: + old_receiver = p2mp_receivers[1] + new_receiver = other_endpoints[0] + cc["p2mp-receiver-sdp"] = [p2mp_receivers[0], new_receiver] + else: + old_receiver = p2mp_receivers[0] + new_receiver = other_endpoints[0] + cc["p2mp-receiver-sdp"] = [new_receiver] + modified = True + + # Log ORIGEN and DESTINO + logging.info(f"ORIGEN: {p2mp_sender} DESTINO: {new_receiver}") + print(f"ORIGEN: {p2mp_sender} DESTINO: {new_receiver}", flush=True) + else: + logging.warning("No alternative receiver endpoints found to swap.") + + if p2mp_receivers: + old_service_id = f"{p2mp_sender}_to_{','.join(p2mp_receivers)}" + else: + old_service_id = None + + if modified: + # Re-apply the modified intent via slice_service.nsc + try: + self.slice_service.nsc(intent, curr_slice_id, old_service_id=old_service_id) + logging.info(f"Slice {curr_slice_id} updated successfully following alert.") + except Exception as e: + logging.error(f"Failed to update slice configuration: {e}") + else: + logging.warning("No slice intent found to process alert.") + return send_response( True, code=201, - message="Alert saved successfully", + message="Alert processed and saved successfully", data=alert_data ) except Exception as e: diff --git a/src/config/config.py b/src/config/config.py index 2e5a9aa..338f7b7 100644 --- a/src/config/config.py +++ b/src/config/config.py @@ -46,6 +46,8 @@ def create_config(app: Flask): app.config["PCE_EXTERNAL"] = os.getenv("PCE_EXTERNAL", "false").lower() == "true" app.config["HRAT_IP"] = os.getenv("HRAT_IP", "192.168.1.143") app.config["E2E_OPTICAL_IP"] = os.getenv("E2E_OPTICAL_IP", "127.0.0.1") + app.config["SUBSCRIBE_ALERTS"] = os.getenv("SUBSCRIBE_ALERTS", "false").lower() == "true" + app.config["SUBSCRIBE_ALERTS_URL"] = os.getenv("SUBSCRIBE_ALERTS_URL", "") # Realizer app.config["DUMMY_MODE"] = os.getenv("DUMMY_MODE", "true").lower() == "true" diff --git a/src/database/db.py b/src/database/db.py index f5be792..2616e11 100644 --- a/src/database/db.py +++ b/src/database/db.py @@ -31,9 +31,34 @@ def init_db(): controller TEXT NOT NULL ) """) + cursor.execute(""" + CREATE TABLE IF NOT EXISTS subscription ( + subscription_id TEXT PRIMARY KEY, + slice_id TEXT NOT NULL + ) + """) conn.commit() conn.close() +def save_subscription(subscription_id: str, slice_id: str): + conn = sqlite3.connect(DB_NAME) + cursor = conn.cursor() + try: + cursor.execute("INSERT OR REPLACE INTO subscription (subscription_id, slice_id) VALUES (?, ?)", (subscription_id, slice_id)) + conn.commit() + finally: + conn.close() + +def get_slice_id_by_subscription(subscription_id: str) -> str: + conn = sqlite3.connect(DB_NAME) + cursor = conn.cursor() + cursor.execute("SELECT slice_id FROM subscription WHERE subscription_id = ?", (subscription_id,)) + row = cursor.fetchone() + conn.close() + if row: + return row[0] + return None + # Save data to the database def save_data(slice_id: str, intent_dict: dict, controller: str): """ -- GitLab