Loading app.py +4 −2 Original line number Diff line number Diff line Loading @@ -25,7 +25,8 @@ from swagger.restconf_namespace import restconf_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 from src.database.db import init_db as init_slice from src.database.service_db import init_db as init_service # Paths that do not require authentication (Swagger UI and its static assets) # /nsc → Swagger UI Loading Loading @@ -55,7 +56,8 @@ def _check_basic_auth(app): def create_app(): """Create Flask application with configured API and namespaces.""" init_db() init_slice() init_service() app = Flask(__name__) app = create_config(app) CORS(app) Loading src/api/main.py +16 −3 Original line number Diff line number Diff line Loading @@ -18,6 +18,7 @@ from src.utils.send_response import send_response import logging 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 from src.realizer.tfs.helpers.tfs_connector import tfs_connector 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 Loading Loading @@ -618,7 +619,11 @@ class Api: slice_type = "L2" logging.warning(f"Slice type not found in slice intent. Defaulting to L2") logging.debug(f"Send slice to delete in TFS with slice_type {slice_type}") tfs_connector().nbi_delete(current_app.config["RESTCONF_IP"], slice_type, slice.get("id")) services = get_data_by_slice_id(slice.get("id")) for service in services: id = service.get("service_id") tfs_connector().nbi_delete(current_app.config["RESTCONF_IP"], slice_type, id) delete_by_slice_id(slice.get("id")) if current_app.config["TFS_L2VPN_SUPPORT"]: self.slice_service.tfs_l2vpn_delete() Loading Loading @@ -670,7 +675,11 @@ class Api: slice_type = "L2" logging.warning(f"Slice type not found in slice intent. Defaulting to L2") logging.debug(f"Send slice to delete in TFS with slice_type {slice_type}") tfs_connector().nbi_delete(current_app.config["RESTCONF_IP"], slice_type, existing_slice.get("id")) services = get_data_by_slice_id(existing_slice.get("id")) for service in services: id = service.get("service_id") tfs_connector().nbi_delete(current_app.config["RESTCONF_IP"], slice_type, id) delete_by_slice_id(slice.get("id")) if current_app.config["TFS_L2VPN_SUPPORT"]: self.slice_service.tfs_l2vpn_delete() Loading @@ -689,7 +698,11 @@ class Api: slice_type = "L2" logging.warning(f"Slice type not found in slice intent. Defaulting to L2") logging.debug(f"Send slice to delete in TFS with slice_type {slice_type}") tfs_connector().nbi_delete(current_app.config["RESTCONF_IP"], slice_type, slice.get("id")) services = get_data_by_slice_id(slice.get("id")) for service in services: id = service.get("service_id") tfs_connector().nbi_delete(current_app.config["RESTCONF_IP"], slice_type, id) delete_by_slice_id(slice.get("id")) if current_app.config["TFS_L2VPN_SUPPORT"]: self.slice_service.tfs_l2vpn_delete() delete_data_store(xpath) Loading src/config/.env.example +1 −1 Original line number Diff line number Diff line Loading @@ -66,7 +66,7 @@ TFS_E2E_IP=127.0.0.1 # ------------------------- # Restconf Controller # ------------------------- RESTCONF_IP=192.168.27.165 RESTCONF_IP=127.0.0.1 # Options: TFS or IXIA SDN_CONTROLLER_TYPE=TFS # Options: FRR, CISCO Loading src/database/service_db.py 0 → 100644 +233 −0 Original line number Diff line number Diff line # Copyright 2022-2026 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. # This file is an original contribution from Telefonica Innovación Digital S.L. import sqlite3, logging # Database file DB_NAME = "service.db" # Initialize database and create table def init_db(): """ Initialize the SQLite database and create the service table if not exists. """ conn = sqlite3.connect(DB_NAME) cursor = conn.cursor() cursor.execute(""" CREATE TABLE IF NOT EXISTS service ( service_id TEXT PRIMARY KEY, slice_id TEXT NOT NULL ) """) conn.commit() conn.close() # Save data to the database def save_data(service_id: str, slice_id: str): """ Save a new service entry to the database. Args: service_id (str): Unique identifier for the service slice_id (dict): Unique identifier for the slice Raises: ValueError: If a service with the given service_id already exists """ conn = sqlite3.connect(DB_NAME) cursor = conn.cursor() try: cursor.execute("INSERT INTO service (service_id, slice_id) VALUES (?, ?)", (service_id, slice_id)) conn.commit() # Handle duplicate service ID except sqlite3.IntegrityError: raise ValueError(f"Service with id '{service_id}' already exists.") finally: conn.close() # Update data in the database def update_data(service_id: str, new_slice_id: str): """ Update an existing service entry in the database. Args: service_id (str): Unique identifier for the service slice_id (str): Unique identifier for the slice Raises: ValueError: If no service is found with the given service_id """ conn = sqlite3.connect(DB_NAME) cursor = conn.cursor() cursor.execute("UPDATE service SET slice_id = ? WHERE service_id = ?", (new_slice_id, service_id)) if cursor.rowcount == 0: raise ValueError(f"No slice found with id '{service_id}' to update.") else: logging.debug(f"Slice '{service_id}' updated.") conn.commit() conn.close() # Delete data from the database def delete_data(service_id: str): """ Delete a service entry from the database. Args: service_id (str): Unique identifier for the service to delete Raises: ValueError: If no service is found with the given service_id """ conn = sqlite3.connect(DB_NAME) cursor = conn.cursor() cursor.execute("DELETE FROM service WHERE service_id = ?", (service_id,)) if cursor.rowcount == 0: raise ValueError(f"No service found with id '{service_id}' to delete.") else: logging.debug(f"Service '{service_id}' deleted.") conn.commit() conn.close() # Get data from the database def get_data(service_id: str) -> dict[str, str]: conn = sqlite3.connect(DB_NAME) cursor = conn.cursor() cursor.execute("SELECT * FROM service WHERE service_id = ?", (service_id,)) row = cursor.fetchone() if not row: conn.close() raise ValueError(f"No service found with id '{service_id}'.") column_names = [desc[0] for desc in cursor.description] conn.close() return dict(zip(column_names, row)) # Get all services def get_all_data() -> list[dict[str, str]]: """ Retrieve all service entries from the database. Returns: list: List of service data dictionaries including service_id and slice_id """ conn = sqlite3.connect(DB_NAME) cursor = conn.cursor() cursor.execute("SELECT * FROM service") rows = cursor.fetchall() column_names = [description[0] for description in cursor.description] conn.close() return [dict(zip(column_names, row)) for row in rows] def delete_all_data(): """ Delete all service entries from the database. """ conn = sqlite3.connect(DB_NAME) cursor = conn.cursor() cursor.execute("DELETE FROM service") conn.commit() conn.close() logging.debug("All service data deleted.") def get_data_by_slice_id(slice_id: str) -> list[dict[str, str]]: """ Retrieve all service entries associated with a given slice_id. Args: slice_id (str): Identifier of the slice Returns: list: List of service data dictionaries including service_id and slice_id Raises: ValueError: If no services are found with the given slice_id """ conn = sqlite3.connect(DB_NAME) cursor = conn.cursor() cursor.execute("SELECT * FROM service WHERE slice_id = ?", (slice_id,)) rows = cursor.fetchall() if not rows: conn.close() raise ValueError(f"No services found with slice_id '{slice_id}'.") column_names = [desc[0] for desc in cursor.description] conn.close() return [dict(zip(column_names, row)) for row in rows] def delete_by_slice_id(slice_id: str): """ Delete all service entries associated with a given slice_id. Args: slice_id (str): Identifier of the slice whose services will be deleted Raises: ValueError: If no services are found with the given slice_id """ conn = sqlite3.connect(DB_NAME) cursor = conn.cursor() cursor.execute("DELETE FROM service WHERE slice_id = ?", (slice_id,)) if cursor.rowcount == 0: conn.close() raise ValueError(f"No services found with slice_id '{slice_id}' to delete.") logging.debug(f"All services with slice_id '{slice_id}' deleted.") conn.commit() conn.close() # Example usage if __name__ == "__main__": init_db() # Save a service save_data("service-001", "slice-001") save_data("service-002", "slice-001") save_data("service-003", "slice-002") # Get the service result = get_data("service-001") if result: print(f"Retrieved service for service-001: {result}") # Update the service update_data("service-001", "slice-002") # Delete the service delete_data("service-001") delete_by_slice_id("slice-001") result = get_all_data() if result: print(f"Retrieved data: {result}") delete_all_data() src/mapper/main.py +4 −0 Original line number Diff line number Diff line Loading @@ -23,6 +23,7 @@ from .process_connnectivity import process_connectivity from src.realizer.main import realizer from flask import current_app from src.database.sysrepo_store import get_data_store, create_data_store, delete_data_store, update_data_store, normalize_libyang_data from src.database.service_db import save_data, update_data def mapper(ietf_intent, controller_type="TFS"): """ Loading Loading @@ -158,6 +159,9 @@ def mapper(ietf_intent, controller_type="TFS"): "way": way } services.append(service) if not current_app.config["DUMMY_MODE"]: # Save mapping from service_id to slice_id save_data(service_id=safe_get(service, ["id"]), slice_id=service_id) logging.debug(f"Service added: {service}") # Break only for point-to-point Loading Loading
app.py +4 −2 Original line number Diff line number Diff line Loading @@ -25,7 +25,8 @@ from swagger.restconf_namespace import restconf_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 from src.database.db import init_db as init_slice from src.database.service_db import init_db as init_service # Paths that do not require authentication (Swagger UI and its static assets) # /nsc → Swagger UI Loading Loading @@ -55,7 +56,8 @@ def _check_basic_auth(app): def create_app(): """Create Flask application with configured API and namespaces.""" init_db() init_slice() init_service() app = Flask(__name__) app = create_config(app) CORS(app) Loading
src/api/main.py +16 −3 Original line number Diff line number Diff line Loading @@ -18,6 +18,7 @@ from src.utils.send_response import send_response import logging 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 from src.realizer.tfs.helpers.tfs_connector import tfs_connector 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 Loading Loading @@ -618,7 +619,11 @@ class Api: slice_type = "L2" logging.warning(f"Slice type not found in slice intent. Defaulting to L2") logging.debug(f"Send slice to delete in TFS with slice_type {slice_type}") tfs_connector().nbi_delete(current_app.config["RESTCONF_IP"], slice_type, slice.get("id")) services = get_data_by_slice_id(slice.get("id")) for service in services: id = service.get("service_id") tfs_connector().nbi_delete(current_app.config["RESTCONF_IP"], slice_type, id) delete_by_slice_id(slice.get("id")) if current_app.config["TFS_L2VPN_SUPPORT"]: self.slice_service.tfs_l2vpn_delete() Loading Loading @@ -670,7 +675,11 @@ class Api: slice_type = "L2" logging.warning(f"Slice type not found in slice intent. Defaulting to L2") logging.debug(f"Send slice to delete in TFS with slice_type {slice_type}") tfs_connector().nbi_delete(current_app.config["RESTCONF_IP"], slice_type, existing_slice.get("id")) services = get_data_by_slice_id(existing_slice.get("id")) for service in services: id = service.get("service_id") tfs_connector().nbi_delete(current_app.config["RESTCONF_IP"], slice_type, id) delete_by_slice_id(slice.get("id")) if current_app.config["TFS_L2VPN_SUPPORT"]: self.slice_service.tfs_l2vpn_delete() Loading @@ -689,7 +698,11 @@ class Api: slice_type = "L2" logging.warning(f"Slice type not found in slice intent. Defaulting to L2") logging.debug(f"Send slice to delete in TFS with slice_type {slice_type}") tfs_connector().nbi_delete(current_app.config["RESTCONF_IP"], slice_type, slice.get("id")) services = get_data_by_slice_id(slice.get("id")) for service in services: id = service.get("service_id") tfs_connector().nbi_delete(current_app.config["RESTCONF_IP"], slice_type, id) delete_by_slice_id(slice.get("id")) if current_app.config["TFS_L2VPN_SUPPORT"]: self.slice_service.tfs_l2vpn_delete() delete_data_store(xpath) Loading
src/config/.env.example +1 −1 Original line number Diff line number Diff line Loading @@ -66,7 +66,7 @@ TFS_E2E_IP=127.0.0.1 # ------------------------- # Restconf Controller # ------------------------- RESTCONF_IP=192.168.27.165 RESTCONF_IP=127.0.0.1 # Options: TFS or IXIA SDN_CONTROLLER_TYPE=TFS # Options: FRR, CISCO Loading
src/database/service_db.py 0 → 100644 +233 −0 Original line number Diff line number Diff line # Copyright 2022-2026 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. # This file is an original contribution from Telefonica Innovación Digital S.L. import sqlite3, logging # Database file DB_NAME = "service.db" # Initialize database and create table def init_db(): """ Initialize the SQLite database and create the service table if not exists. """ conn = sqlite3.connect(DB_NAME) cursor = conn.cursor() cursor.execute(""" CREATE TABLE IF NOT EXISTS service ( service_id TEXT PRIMARY KEY, slice_id TEXT NOT NULL ) """) conn.commit() conn.close() # Save data to the database def save_data(service_id: str, slice_id: str): """ Save a new service entry to the database. Args: service_id (str): Unique identifier for the service slice_id (dict): Unique identifier for the slice Raises: ValueError: If a service with the given service_id already exists """ conn = sqlite3.connect(DB_NAME) cursor = conn.cursor() try: cursor.execute("INSERT INTO service (service_id, slice_id) VALUES (?, ?)", (service_id, slice_id)) conn.commit() # Handle duplicate service ID except sqlite3.IntegrityError: raise ValueError(f"Service with id '{service_id}' already exists.") finally: conn.close() # Update data in the database def update_data(service_id: str, new_slice_id: str): """ Update an existing service entry in the database. Args: service_id (str): Unique identifier for the service slice_id (str): Unique identifier for the slice Raises: ValueError: If no service is found with the given service_id """ conn = sqlite3.connect(DB_NAME) cursor = conn.cursor() cursor.execute("UPDATE service SET slice_id = ? WHERE service_id = ?", (new_slice_id, service_id)) if cursor.rowcount == 0: raise ValueError(f"No slice found with id '{service_id}' to update.") else: logging.debug(f"Slice '{service_id}' updated.") conn.commit() conn.close() # Delete data from the database def delete_data(service_id: str): """ Delete a service entry from the database. Args: service_id (str): Unique identifier for the service to delete Raises: ValueError: If no service is found with the given service_id """ conn = sqlite3.connect(DB_NAME) cursor = conn.cursor() cursor.execute("DELETE FROM service WHERE service_id = ?", (service_id,)) if cursor.rowcount == 0: raise ValueError(f"No service found with id '{service_id}' to delete.") else: logging.debug(f"Service '{service_id}' deleted.") conn.commit() conn.close() # Get data from the database def get_data(service_id: str) -> dict[str, str]: conn = sqlite3.connect(DB_NAME) cursor = conn.cursor() cursor.execute("SELECT * FROM service WHERE service_id = ?", (service_id,)) row = cursor.fetchone() if not row: conn.close() raise ValueError(f"No service found with id '{service_id}'.") column_names = [desc[0] for desc in cursor.description] conn.close() return dict(zip(column_names, row)) # Get all services def get_all_data() -> list[dict[str, str]]: """ Retrieve all service entries from the database. Returns: list: List of service data dictionaries including service_id and slice_id """ conn = sqlite3.connect(DB_NAME) cursor = conn.cursor() cursor.execute("SELECT * FROM service") rows = cursor.fetchall() column_names = [description[0] for description in cursor.description] conn.close() return [dict(zip(column_names, row)) for row in rows] def delete_all_data(): """ Delete all service entries from the database. """ conn = sqlite3.connect(DB_NAME) cursor = conn.cursor() cursor.execute("DELETE FROM service") conn.commit() conn.close() logging.debug("All service data deleted.") def get_data_by_slice_id(slice_id: str) -> list[dict[str, str]]: """ Retrieve all service entries associated with a given slice_id. Args: slice_id (str): Identifier of the slice Returns: list: List of service data dictionaries including service_id and slice_id Raises: ValueError: If no services are found with the given slice_id """ conn = sqlite3.connect(DB_NAME) cursor = conn.cursor() cursor.execute("SELECT * FROM service WHERE slice_id = ?", (slice_id,)) rows = cursor.fetchall() if not rows: conn.close() raise ValueError(f"No services found with slice_id '{slice_id}'.") column_names = [desc[0] for desc in cursor.description] conn.close() return [dict(zip(column_names, row)) for row in rows] def delete_by_slice_id(slice_id: str): """ Delete all service entries associated with a given slice_id. Args: slice_id (str): Identifier of the slice whose services will be deleted Raises: ValueError: If no services are found with the given slice_id """ conn = sqlite3.connect(DB_NAME) cursor = conn.cursor() cursor.execute("DELETE FROM service WHERE slice_id = ?", (slice_id,)) if cursor.rowcount == 0: conn.close() raise ValueError(f"No services found with slice_id '{slice_id}' to delete.") logging.debug(f"All services with slice_id '{slice_id}' deleted.") conn.commit() conn.close() # Example usage if __name__ == "__main__": init_db() # Save a service save_data("service-001", "slice-001") save_data("service-002", "slice-001") save_data("service-003", "slice-002") # Get the service result = get_data("service-001") if result: print(f"Retrieved service for service-001: {result}") # Update the service update_data("service-001", "slice-002") # Delete the service delete_data("service-001") delete_by_slice_id("slice-001") result = get_all_data() if result: print(f"Retrieved data: {result}") delete_all_data()
src/mapper/main.py +4 −0 Original line number Diff line number Diff line Loading @@ -23,6 +23,7 @@ from .process_connnectivity import process_connectivity from src.realizer.main import realizer from flask import current_app from src.database.sysrepo_store import get_data_store, create_data_store, delete_data_store, update_data_store, normalize_libyang_data from src.database.service_db import save_data, update_data def mapper(ietf_intent, controller_type="TFS"): """ Loading Loading @@ -158,6 +159,9 @@ def mapper(ietf_intent, controller_type="TFS"): "way": way } services.append(service) if not current_app.config["DUMMY_MODE"]: # Save mapping from service_id to slice_id save_data(service_id=safe_get(service, ["id"]), slice_id=service_id) logging.debug(f"Service added: {service}") # Break only for point-to-point Loading