Loading src/qkd_app/service/QKDAppServiceServicerImpl.py +6 −36 Original line number Diff line number Diff line Loading @@ -139,48 +139,18 @@ class AppServiceServicerImpl(AppServiceServicer): """ Lists all apps in the system, including their statistics and QoS attributes. """ LOGGER.debug(f"Received ListApps request: {grpc_message_to_json_string(request)}") try: apps = app_list_objs(self.db_engine, request.context_uuid.uuid) for app in apps.apps: LOGGER.debug(f"App retrieved: {grpc_message_to_json_string(app)}") LOGGER.debug(f"ListApps returned {len(apps.apps)} apps for context_id: {request.context_uuid.uuid}") return apps except Exception as e: context.set_code(grpc.StatusCode.INTERNAL) context.set_details("An internal error occurred while listing apps.") raise e return app_list_objs(self.db_engine, request) @safe_and_metered_rpc_method(METRICS_POOL, LOGGER) def GetApp(self, request : AppId, context : grpc.ServicerContext) -> App: """ Fetches details of a specific app based on its AppId, including QoS and performance stats. """ LOGGER.debug(f"Received GetApp request: {grpc_message_to_json_string(request)}") try: app = app_get(self.db_engine, request) LOGGER.debug(f"GetApp found app with app_uuid: {request.app_uuid.uuid}") return app except NotFoundException as e: context.set_code(grpc.StatusCode.NOT_FOUND) context.set_details(f"App not found: {e}") raise e return app_get(self.db_engine, request) @safe_and_metered_rpc_method(METRICS_POOL, LOGGER) def DeleteApp(self, request : AppId, context : grpc.ServicerContext) -> Empty: """ Deletes an app from the system by its AppId, following ETSI compliance. """ LOGGER.debug(f"Received DeleteApp request for app_uuid: {request.app_uuid.uuid}") try: app_delete(self.db_engine, request.app_uuid.uuid) LOGGER.debug(f"App with UUID {request.app_uuid.uuid} deleted successfully.") return Empty() except NotFoundException as e: context.set_code(grpc.StatusCode.NOT_FOUND) context.set_details(f"App not found: {e}") raise e return app_delete(self.db_engine, request) src/qkd_app/service/database/QKDApp.py +33 −32 Original line number Diff line number Diff line Loading @@ -23,9 +23,8 @@ from sqlalchemy_cockroachdb import run_transaction from common.method_wrappers.ServiceExceptions import NotFoundException from common.message_broker.MessageBroker import MessageBroker from common.proto.context_pb2 import ContextId, Empty from common.proto.qkd_app_pb2 import AppList, App, AppId from qkd_app.service.database.uuids._Builder import get_uuid_from_string, get_uuid_random from common.method_wrappers.ServiceExceptions import InvalidArgumentsException from common.tools.object_factory.QKDApp import json_app_id from common.tools.object_factory.Context import json_context_id Loading @@ -38,22 +37,20 @@ from .models.enums.QKDAppTypes import grpc_to_enum__qkd_app_types LOGGER = logging.getLogger(__name__) def app_list_objs(db_engine: Engine, context_uuid: str = None) -> AppList: def app_list_objs(db_engine : Engine, request : ContextId) -> AppList: """ Fetches a list of all QKD applications from the database. Optionally filters by context UUID. :param db_engine: SQLAlchemy Engine for DB connection :param context_uuid: UUID of the context to filter by (optional) :param request: Context Id containing the UUID of the context to filter by :return: AppList containing all apps """ context_uuid = context_get_uuid(request, allow_random=False) def callback(session : Session) -> List[Dict]: query = session.query(AppModel) if context_uuid: query = query.filter_by(context_uuid=context_uuid) return [obj.dump() for obj in query.all()] obj_list : List[AppModel] = query.all() return [obj.dump() for obj in obj_list] apps = run_transaction(sessionmaker(bind=db_engine), callback) return AppList(apps=apps) Loading @@ -67,17 +64,21 @@ def app_get(db_engine: Engine, request: AppId) -> App: :return: App protobuf object :raises NotFoundException: If the app is not found in the database """ app_uuid = app_get_uuid(request, allow_random=False) context_uuid,app_uuid = app_get_uuid(request, allow_random=False) def callback(session : Session) -> Optional[Dict]: obj = session.query(AppModel).filter_by(app_uuid=app_uuid).one_or_none() return obj.dump() if obj else None query = session.query(AppModel) query = query.filter_by(app_uuid=app_uuid) obj : Optional[AppModel] = query.one_or_none() return None if obj is None else obj.dump() obj = run_transaction(sessionmaker(bind=db_engine), callback) if not obj: raise NotFoundException('App', request.app_uuid.uuid, extra_details=[ f'app_uuid generated was: {app_uuid}' if obj is None: raw_app_uuid = '{:s}/{:s}'.format(request.context_id.context_uuid.uuid, request.app_uuid.uuid) raise NotFoundException('App', raw_app_uuid, extra_details=[ 'context_uuid generated was: {:s}'.format(context_uuid), 'app_uuid generated was: {:s}'.format(app_uuid), ]) return App(**obj) Loading @@ -93,8 +94,7 @@ def app_set(db_engine: Engine, messagebroker: MessageBroker, request: App) -> Ap :param request: App protobuf object containing app data :return: AppId protobuf object representing the newly created or updated app """ context_uuid = context_get_uuid(request.app_id.context_id, allow_random=False) app_uuid = app_get_uuid(request.app_id, allow_random=True) context_uuid,app_uuid = app_get_uuid(request.app_id, allow_random=True) # Prepare app data for insertion/update app_data = { Loading Loading @@ -154,20 +154,21 @@ def app_get_by_server(db_engine: Engine, server_app_id: str) -> App: return App(**obj) def app_delete(db_engine: Engine, app_uuid: str) -> None: def app_delete(db_engine : Engine, request : AppId) -> Empty: """ Deletes an app by its UUID from the database. :param db_engine: SQLAlchemy Engine for DB connection :param app_uuid: The UUID of the app to be deleted :param app_id: The UUID of the app to be deleted """ def callback(session: Session) -> bool: app_obj = session.query(AppModel).filter_by(app_uuid=app_uuid).one_or_none() if app_obj is None: raise NotFoundException('App', app_uuid) _,app_uuid = app_get_uuid(request, allow_random=False) session.delete(app_obj) return True def callback(session : Session) -> bool: query = session.query(AppModel) query = query.filter_by(app_uuid=app_uuid) num_deleted = query.delete() return num_deleted > 0 run_transaction(sessionmaker(bind=db_engine), callback) return Empty() src/qkd_app/service/database/uuids/Context.py 0 → 100644 +37 −0 Original line number Diff line number Diff line # 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. from common.Constants import DEFAULT_CONTEXT_NAME from common.proto.context_pb2 import ContextId from common.method_wrappers.ServiceExceptions import InvalidArgumentsException from ._Builder import get_uuid_from_string, get_uuid_random def context_get_uuid( context_id : ContextId, context_name : str = '', allow_random : bool = False, allow_default : bool = False ) -> str: context_uuid = context_id.context_uuid.uuid if len(context_uuid) > 0: return get_uuid_from_string(context_uuid) if len(context_name) > 0: return get_uuid_from_string(context_name) if allow_default: return get_uuid_from_string(DEFAULT_CONTEXT_NAME) if allow_random: return get_uuid_random() raise InvalidArgumentsException([ ('context_id.context_uuid.uuid', context_uuid), ('name', context_name), ], extra_details=['At least one is required to produce a Context UUID']) src/qkd_app/service/database/uuids/QKDApp.py +20 −10 Original line number Diff line number Diff line Loading @@ -12,26 +12,36 @@ # See the License for the specific language governing permissions and # limitations under the License. from typing import Tuple from common.proto.qkd_app_pb2 import AppId from common.method_wrappers.ServiceExceptions import InvalidArgumentsException from ._Builder import get_uuid_from_string, get_uuid_random from .Context import context_get_uuid def app_get_uuid(app_id: AppId, allow_random: bool = False) -> str: def app_get_uuid( app_id : AppId, app_name : str = '', allow_random : bool = False ) -> Tuple[str, str]: """ Retrieves or generates the UUID for an app. :param app_id: AppId object that contains the app UUID :param application_id: AppId object that contains the app UUID :param application_name: string that contains optional app name :param allow_random: If True, generates a random UUID if app_uuid is not set :return: App UUID as a string :return: Context UUID as a string , App UUID as a string """ app_uuid = app_id.app_uuid.uuid context_uuid = context_get_uuid(app_id.context_id, allow_random=False, allow_default=True) raw_app_uuid = app_id.app_uuid.uuid if app_uuid: return get_uuid_from_string(app_uuid) if len(raw_app_uuid) > 0: return context_uuid, get_uuid_from_string(raw_app_uuid, prefix_for_name=context_uuid) if len(app_name) > 0: return context_uuid, get_uuid_from_string(app_name, prefix_for_name=context_uuid) if allow_random: return get_uuid_random() return context_uuid, get_uuid_random() raise InvalidArgumentsException([ ('app_id.app_uuid.uuid', app_uuid), ], extra_details=['At least one UUID is required to identify the app.']) ('app_id.app_uuid.uuid', raw_app_uuid), ('name', app_name), ], extra_details=['At least one is required to produce a App UUID']) Loading
src/qkd_app/service/QKDAppServiceServicerImpl.py +6 −36 Original line number Diff line number Diff line Loading @@ -139,48 +139,18 @@ class AppServiceServicerImpl(AppServiceServicer): """ Lists all apps in the system, including their statistics and QoS attributes. """ LOGGER.debug(f"Received ListApps request: {grpc_message_to_json_string(request)}") try: apps = app_list_objs(self.db_engine, request.context_uuid.uuid) for app in apps.apps: LOGGER.debug(f"App retrieved: {grpc_message_to_json_string(app)}") LOGGER.debug(f"ListApps returned {len(apps.apps)} apps for context_id: {request.context_uuid.uuid}") return apps except Exception as e: context.set_code(grpc.StatusCode.INTERNAL) context.set_details("An internal error occurred while listing apps.") raise e return app_list_objs(self.db_engine, request) @safe_and_metered_rpc_method(METRICS_POOL, LOGGER) def GetApp(self, request : AppId, context : grpc.ServicerContext) -> App: """ Fetches details of a specific app based on its AppId, including QoS and performance stats. """ LOGGER.debug(f"Received GetApp request: {grpc_message_to_json_string(request)}") try: app = app_get(self.db_engine, request) LOGGER.debug(f"GetApp found app with app_uuid: {request.app_uuid.uuid}") return app except NotFoundException as e: context.set_code(grpc.StatusCode.NOT_FOUND) context.set_details(f"App not found: {e}") raise e return app_get(self.db_engine, request) @safe_and_metered_rpc_method(METRICS_POOL, LOGGER) def DeleteApp(self, request : AppId, context : grpc.ServicerContext) -> Empty: """ Deletes an app from the system by its AppId, following ETSI compliance. """ LOGGER.debug(f"Received DeleteApp request for app_uuid: {request.app_uuid.uuid}") try: app_delete(self.db_engine, request.app_uuid.uuid) LOGGER.debug(f"App with UUID {request.app_uuid.uuid} deleted successfully.") return Empty() except NotFoundException as e: context.set_code(grpc.StatusCode.NOT_FOUND) context.set_details(f"App not found: {e}") raise e return app_delete(self.db_engine, request)
src/qkd_app/service/database/QKDApp.py +33 −32 Original line number Diff line number Diff line Loading @@ -23,9 +23,8 @@ from sqlalchemy_cockroachdb import run_transaction from common.method_wrappers.ServiceExceptions import NotFoundException from common.message_broker.MessageBroker import MessageBroker from common.proto.context_pb2 import ContextId, Empty from common.proto.qkd_app_pb2 import AppList, App, AppId from qkd_app.service.database.uuids._Builder import get_uuid_from_string, get_uuid_random from common.method_wrappers.ServiceExceptions import InvalidArgumentsException from common.tools.object_factory.QKDApp import json_app_id from common.tools.object_factory.Context import json_context_id Loading @@ -38,22 +37,20 @@ from .models.enums.QKDAppTypes import grpc_to_enum__qkd_app_types LOGGER = logging.getLogger(__name__) def app_list_objs(db_engine: Engine, context_uuid: str = None) -> AppList: def app_list_objs(db_engine : Engine, request : ContextId) -> AppList: """ Fetches a list of all QKD applications from the database. Optionally filters by context UUID. :param db_engine: SQLAlchemy Engine for DB connection :param context_uuid: UUID of the context to filter by (optional) :param request: Context Id containing the UUID of the context to filter by :return: AppList containing all apps """ context_uuid = context_get_uuid(request, allow_random=False) def callback(session : Session) -> List[Dict]: query = session.query(AppModel) if context_uuid: query = query.filter_by(context_uuid=context_uuid) return [obj.dump() for obj in query.all()] obj_list : List[AppModel] = query.all() return [obj.dump() for obj in obj_list] apps = run_transaction(sessionmaker(bind=db_engine), callback) return AppList(apps=apps) Loading @@ -67,17 +64,21 @@ def app_get(db_engine: Engine, request: AppId) -> App: :return: App protobuf object :raises NotFoundException: If the app is not found in the database """ app_uuid = app_get_uuid(request, allow_random=False) context_uuid,app_uuid = app_get_uuid(request, allow_random=False) def callback(session : Session) -> Optional[Dict]: obj = session.query(AppModel).filter_by(app_uuid=app_uuid).one_or_none() return obj.dump() if obj else None query = session.query(AppModel) query = query.filter_by(app_uuid=app_uuid) obj : Optional[AppModel] = query.one_or_none() return None if obj is None else obj.dump() obj = run_transaction(sessionmaker(bind=db_engine), callback) if not obj: raise NotFoundException('App', request.app_uuid.uuid, extra_details=[ f'app_uuid generated was: {app_uuid}' if obj is None: raw_app_uuid = '{:s}/{:s}'.format(request.context_id.context_uuid.uuid, request.app_uuid.uuid) raise NotFoundException('App', raw_app_uuid, extra_details=[ 'context_uuid generated was: {:s}'.format(context_uuid), 'app_uuid generated was: {:s}'.format(app_uuid), ]) return App(**obj) Loading @@ -93,8 +94,7 @@ def app_set(db_engine: Engine, messagebroker: MessageBroker, request: App) -> Ap :param request: App protobuf object containing app data :return: AppId protobuf object representing the newly created or updated app """ context_uuid = context_get_uuid(request.app_id.context_id, allow_random=False) app_uuid = app_get_uuid(request.app_id, allow_random=True) context_uuid,app_uuid = app_get_uuid(request.app_id, allow_random=True) # Prepare app data for insertion/update app_data = { Loading Loading @@ -154,20 +154,21 @@ def app_get_by_server(db_engine: Engine, server_app_id: str) -> App: return App(**obj) def app_delete(db_engine: Engine, app_uuid: str) -> None: def app_delete(db_engine : Engine, request : AppId) -> Empty: """ Deletes an app by its UUID from the database. :param db_engine: SQLAlchemy Engine for DB connection :param app_uuid: The UUID of the app to be deleted :param app_id: The UUID of the app to be deleted """ def callback(session: Session) -> bool: app_obj = session.query(AppModel).filter_by(app_uuid=app_uuid).one_or_none() if app_obj is None: raise NotFoundException('App', app_uuid) _,app_uuid = app_get_uuid(request, allow_random=False) session.delete(app_obj) return True def callback(session : Session) -> bool: query = session.query(AppModel) query = query.filter_by(app_uuid=app_uuid) num_deleted = query.delete() return num_deleted > 0 run_transaction(sessionmaker(bind=db_engine), callback) return Empty()
src/qkd_app/service/database/uuids/Context.py 0 → 100644 +37 −0 Original line number Diff line number Diff line # 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. from common.Constants import DEFAULT_CONTEXT_NAME from common.proto.context_pb2 import ContextId from common.method_wrappers.ServiceExceptions import InvalidArgumentsException from ._Builder import get_uuid_from_string, get_uuid_random def context_get_uuid( context_id : ContextId, context_name : str = '', allow_random : bool = False, allow_default : bool = False ) -> str: context_uuid = context_id.context_uuid.uuid if len(context_uuid) > 0: return get_uuid_from_string(context_uuid) if len(context_name) > 0: return get_uuid_from_string(context_name) if allow_default: return get_uuid_from_string(DEFAULT_CONTEXT_NAME) if allow_random: return get_uuid_random() raise InvalidArgumentsException([ ('context_id.context_uuid.uuid', context_uuid), ('name', context_name), ], extra_details=['At least one is required to produce a Context UUID'])
src/qkd_app/service/database/uuids/QKDApp.py +20 −10 Original line number Diff line number Diff line Loading @@ -12,26 +12,36 @@ # See the License for the specific language governing permissions and # limitations under the License. from typing import Tuple from common.proto.qkd_app_pb2 import AppId from common.method_wrappers.ServiceExceptions import InvalidArgumentsException from ._Builder import get_uuid_from_string, get_uuid_random from .Context import context_get_uuid def app_get_uuid(app_id: AppId, allow_random: bool = False) -> str: def app_get_uuid( app_id : AppId, app_name : str = '', allow_random : bool = False ) -> Tuple[str, str]: """ Retrieves or generates the UUID for an app. :param app_id: AppId object that contains the app UUID :param application_id: AppId object that contains the app UUID :param application_name: string that contains optional app name :param allow_random: If True, generates a random UUID if app_uuid is not set :return: App UUID as a string :return: Context UUID as a string , App UUID as a string """ app_uuid = app_id.app_uuid.uuid context_uuid = context_get_uuid(app_id.context_id, allow_random=False, allow_default=True) raw_app_uuid = app_id.app_uuid.uuid if app_uuid: return get_uuid_from_string(app_uuid) if len(raw_app_uuid) > 0: return context_uuid, get_uuid_from_string(raw_app_uuid, prefix_for_name=context_uuid) if len(app_name) > 0: return context_uuid, get_uuid_from_string(app_name, prefix_for_name=context_uuid) if allow_random: return get_uuid_random() return context_uuid, get_uuid_random() raise InvalidArgumentsException([ ('app_id.app_uuid.uuid', app_uuid), ], extra_details=['At least one UUID is required to identify the app.']) ('app_id.app_uuid.uuid', raw_app_uuid), ('name', app_name), ], extra_details=['At least one is required to produce a App UUID'])