diff --git a/.gitignore b/.gitignore index 9eac55be6c387aa78941998cb0664575b94a65fd..f16afd3e9872853782cdc698ce567780da54c857 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,5 @@ __pycache__/ swagger/__pycache__/ src/__pycache__/ venv/ +.env diff --git a/app.py b/app.py index 784c90438c2c59a95cfb7bce7865e959274d46a9..ccdbed34dd7ca6ae99514881b848899731487b3e 100644 --- a/app.py +++ b/app.py @@ -23,10 +23,11 @@ from swagger.ixia_namespace import ixia_ns from src.config.constants import NSC_PORT from src.webui.gui import gui_bp from src.config.config import create_config - +from src.database.db import init_db def create_app(): """Factory para crear la app Flask con la configuración cargada""" + init_db() app = Flask(__name__) app = create_config(app) CORS(app) diff --git a/src/api/main.py b/src/api/main.py index 2896284eaa2f5525c81b6138c85d39209ff812b1..8261a9bcb49ef8a84a81f38cbf656570c7ae4208 100644 --- a/src/api/main.py +++ b/src/api/main.py @@ -14,10 +14,10 @@ # This file is an original contribution from Telefonica Innovación Digital S.L. -from src.config.constants import DATABASE_PATH from src.utils.send_response import send_response -import os, json, logging +import logging from flask import current_app +from src.database.db import get_data, delete_data, get_all_data, delete_all_data class Api: def __init__(self, slice_service): @@ -81,8 +81,7 @@ class Api: """ try: # Read slice database from JSON file - with open(os.path.join(DATABASE_PATH, "slice_ddbb.json"), 'r') as file: - content = json.load(file) + content = get_all_data() # If specific slice ID is provided, find and return matching slice if slice_id: for slice in content: @@ -160,24 +159,14 @@ class Api: - If need_l2vpn_support is True, performs additional L2VPN cleanup """ try: - # Read current slice database - with open(os.path.join(DATABASE_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 and slice.get("controller") == self.slice_service.controller_type: - del content[i] - id = i - break + slice = get_data(slice_id) # Raise error if slice not found - if id is None: + if not slice or slice.get("controller") != self.slice_service.controller_type: raise ValueError("Transport network slice not found") # Update slice database - with open(os.path.join(DATABASE_PATH, "slice_ddbb.json"), 'w') as file: - json.dump(content, file, indent=4) + delete_data(slice_id) logging.info(f"Slice {slice_id} removed successfully") return {}, 204 @@ -189,16 +178,8 @@ class Api: if current_app.config["TFS_L2VPN_SUPPORT"]: self.slice_service.tfs_l2vpn_delete() - data_removed = [slice for slice in content if slice.get("controller") == self.slice_service.controller_type] - - # Verify slices exist before deletion - if len(data_removed) == 0: - raise ValueError("Transport network slices not found") - - filtered_data = [slice for slice in content if slice.get("controller") != self.slice_service.controller_type] # Clear slice database - with open(os.path.join(DATABASE_PATH, "slice_ddbb.json"), 'w') as file: - json.dump(filtered_data, file, indent=4) + delete_all_data() logging.info("All slices removed successfully") return {}, 204 diff --git a/src/database/db.py b/src/database/db.py new file mode 100644 index 0000000000000000000000000000000000000000..4ace056f84b0e8c3008487fa34f316ec95d36caa --- /dev/null +++ b/src/database/db.py @@ -0,0 +1,138 @@ +# 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. + +import sqlite3, json, logging + +# Database file +DB_NAME = "slice.db" + +# Initialize database and create table +def init_db(): + conn = sqlite3.connect(DB_NAME) + cursor = conn.cursor() + cursor.execute(""" + CREATE TABLE IF NOT EXISTS slice ( + slice_id TEXT PRIMARY KEY, + intent TEXT NOT NULL, + controller TEXT NOT NULL + ) + """) + conn.commit() + conn.close() + +# Save data to the database +def save_data(slice_id: str, intent_dict: dict, controller: str): + intent_str = json.dumps(intent_dict) + conn = sqlite3.connect(DB_NAME) + cursor = conn.cursor() + try: + cursor.execute("INSERT INTO slice (slice_id, intent, controller) VALUES (?, ?, ?)", (slice_id, intent_str, controller)) + conn.commit() + except sqlite3.IntegrityError: + raise ValueError(f"Slice with id '{slice_id}' already exists.") + finally: + conn.close() + +# Update data in the database +def update_data(slice_id: str, new_intent_dict: dict, controller: str): + intent_str = json.dumps(new_intent_dict) + conn = sqlite3.connect(DB_NAME) + cursor = conn.cursor() + cursor.execute("UPDATE slice SET intent = ?, controller = ? WHERE slice_id = ?", (intent_str, controller, slice_id)) + if cursor.rowcount == 0: + raise ValueError(f"No slice found with id '{slice_id}' to update.") + else: + logging.debug(f"Slice '{slice_id}' updated.") + conn.commit() + conn.close() + +# Delete data from the database +def delete_data(slice_id: str): + conn = sqlite3.connect(DB_NAME) + cursor = conn.cursor() + cursor.execute("DELETE FROM slice WHERE slice_id = ?", (slice_id,)) + if cursor.rowcount == 0: + raise ValueError(f"No slice found with id '{slice_id}' to delete.") + else: + logging.debug(f"Slice '{slice_id}' deleted.") + conn.commit() + conn.close() + +# Get data from the database +def get_data(slice_id: str) -> dict[str, dict, str]: + conn = sqlite3.connect(DB_NAME) + cursor = conn.cursor() + cursor.execute("SELECT * FROM slice WHERE slice_id = ?", (slice_id,)) + row = cursor.fetchone() + conn.close() + + if row: + column_names = [description[0] for description in cursor.description] + result = dict(zip(column_names, row)) + if isinstance(result.get("intent"), str): + try: + result["intent"] = json.loads(result["intent"]) + except json.JSONDecodeError: + raise Exception("Warning: 'intent' is not a valid JSON string.") + return result + + else: + raise ValueError(f"No slice found with id '{slice_id}'.") + +# Get all slices +def get_all_data() -> dict[str, dict, str]: + conn = sqlite3.connect(DB_NAME) + cursor = conn.cursor() + cursor.execute("SELECT * FROM slice") + rows = cursor.fetchall() + conn.close() + return [ + { + "slice_id": row[0], + "intent": json.loads(row[1]), + "controller": row[2] + } + for row in rows + ] + +def delete_all_data(): + conn = sqlite3.connect(DB_NAME) + cursor = conn.cursor() + cursor.execute("DELETE FROM slice") + conn.commit() + conn.close() + logging.debug("All slice data deleted.") + +# Example usage +if __name__ == "__main__": + init_db() + + # Save a slice + test_intent = {"bandwidth": "1Gbps", "latency": "10ms", "provider": "opensec"} + save_data("slice-001", test_intent, "TFS") + + # Get the slice + result = get_data("slice-001") + if result: + print(f"Retrieved intent for slice-001: {result}") + + # Update the slice + updated_intent = {"bandwidth": "2Gbps", "latency": "5ms", "provider": "opensec"} + update_data("slice-001", updated_intent, "TFS") + + # Delete the slice + delete_data("slice-001") + + get_all_data() + delete_all_data() diff --git a/src/database/slice_ddbb.json b/src/database/slice_ddbb.json deleted file mode 100644 index 0637a088a01e8ddab3bf3fa98dbe804cbde1a0dc..0000000000000000000000000000000000000000 --- a/src/database/slice_ddbb.json +++ /dev/null @@ -1 +0,0 @@ -[] \ No newline at end of file diff --git a/src/database/store_data.py b/src/database/store_data.py index 5806d6cb38e27d38cf90d935a44c1d7e849cc4a7..1fccbfb6c61878ac5658b9c8570332dbab263075 100644 --- a/src/database/store_data.py +++ b/src/database/store_data.py @@ -14,8 +14,7 @@ # This file is an original contribution from Telefonica Innovación Digital S.L. -import json, os -from src.config.constants import DATABASE_PATH +from src.database.db import save_data, update_data def store_data(intent, slice_id, controller_type=None): """ @@ -30,31 +29,10 @@ def store_data(intent, slice_id, controller_type=None): 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(DATABASE_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 + update_data(slice_id, intent, controller_type) else: # Add new slice intent - content.append( - { - "slice_id": intent["ietf-network-slice-service:network-slice-services"]["slice-service"][0]["id"], - "intent": intent, - "controller": controller_type, - }) - - # # Write updated content back to file - with open(file_path, 'w') as file: - json.dump(content, file, indent=4) \ No newline at end of file + slice_id = intent["ietf-network-slice-service:network-slice-services"]["slice-service"][0]["id"] + save_data(slice_id, intent, controller_type) \ No newline at end of file diff --git a/src/webui/gui.py b/src/webui/gui.py index 88745304a6dd746a2bdeb3160fca447c6caff259..6b7d2feab6c9058252cc4dffdb74a45c08703de1 100644 --- a/src/webui/gui.py +++ b/src/webui/gui.py @@ -141,7 +141,6 @@ def __datos_json(): try: with open(os.path.join(SRC_PATH, 'slice_ddbb.json'), 'r') as fichero: datos =json.load(fichero) - print(datos) rows =[] for source_ip, source_info in datos["source"].items(): vlan = source_info["vlan"] @@ -165,8 +164,8 @@ def home(): session['enter'] = False # Leer las IPs actuales del archivo de configuración try: - tfs_ip = {"TFS_IP": current_app.config["TFS_IP"]} - ixia_ip = {"IXIA_IP": current_app.config["IXIA_IP"]} + tfs_ip = current_app.config["TFS_IP"] + ixia_ip = current_app.config["IXIA_IP"] except Exception: tfs_ip = 'No configurada' ixia_ip = 'No configurada' diff --git a/swagger/models/create_models.py b/swagger/models/create_models.py index 9e965bfdfafba73ae3d3d13a9d717f5d22364d30..a1f7d3a94758b1ea3efe10a41271fead6b8ab6c7 100644 --- a/swagger/models/create_models.py +++ b/swagger/models/create_models.py @@ -293,7 +293,8 @@ def create_ietf_network_slice_nbi_yang_model(slice_ns): slice_ddbb_model = slice_ns.model('ddbb_model', { 'slice_id': fields.String(), - 'intent': fields.List(fields.Nested(ietf_network_slice_request_model)) + 'intent': fields.List(fields.Nested(ietf_network_slice_request_model)), + 'controller': fields.String() })