diff --git a/fedn/network/api/v1/__init__.py b/fedn/network/api/v1/__init__.py index 0e05dd249..e5542084f 100644 --- a/fedn/network/api/v1/__init__.py +++ b/fedn/network/api/v1/__init__.py @@ -1,5 +1,6 @@ from fedn.network.api.v1.client_routes import bp as client_bp from fedn.network.api.v1.combiner_routes import bp as combiner_bp +from fedn.network.api.v1.helper_routes import bp as helper_bp from fedn.network.api.v1.inference_routes import bp as inference_bp from fedn.network.api.v1.model_routes import bp as model_bp from fedn.network.api.v1.package_routes import bp as package_bp @@ -8,4 +9,4 @@ from fedn.network.api.v1.status_routes import bp as status_bp from fedn.network.api.v1.validation_routes import bp as validation_bp -_routes = [client_bp, combiner_bp, model_bp, package_bp, round_bp, session_bp, status_bp, validation_bp, inference_bp] +_routes = [client_bp, combiner_bp, model_bp, package_bp, round_bp, session_bp, status_bp, validation_bp, inference_bp, helper_bp] diff --git a/fedn/network/api/v1/helper_routes.py b/fedn/network/api/v1/helper_routes.py new file mode 100644 index 000000000..03cfed7bb --- /dev/null +++ b/fedn/network/api/v1/helper_routes.py @@ -0,0 +1,62 @@ +from flask import Blueprint, jsonify, request + +from fedn.network.api.auth import jwt_auth_required +from fedn.network.api.v1.shared import api_version, package_store +from fedn.network.storage.statestore.stores.shared import EntityNotFound + +bp = Blueprint("helper", __name__, url_prefix=f"/api/{api_version}/helpers") + + + +@bp.route("/active", methods=["GET"]) +@jwt_auth_required(role="admin") +def get_active_helper(): + """Get active helper + Retrieves the active helper + --- + tags: + - Helpers + responses: + 200: + description: Active helper + 404: + description: No active helper + 500: + description: An unexpected error occurred + """ + try: + + active_package = package_store.get_active() + + response = active_package["helper"] + + return jsonify(response), 200 + except EntityNotFound: + return jsonify({"message": "No active helper"}), 404 + except Exception: + return jsonify({"message": "An unexpected error occurred"}), 500 + +@bp.route("/active", methods=["PUT"]) +@jwt_auth_required(role="admin") +def set_active_helper(): + """Set active helper + Sets the active helper + --- + tags: + - Helpers + responses: + 200: + description: Active helper set + 500: + description: An unexpected error occurred + """ + try: + data = request.get_json() + helper = data["helper"] + package_store.set_active_helper(helper) + + return jsonify({"message": "Active helper set"}), 200 + except ValueError: + return jsonify({"message": "Helper is required to be either 'numpyhelper', 'binaryhelper' or 'androidhelper'"}), 400 + except Exception: + return jsonify({"message": "An unexpected error occurred"}), 500 diff --git a/fedn/network/api/v1/package_routes.py b/fedn/network/api/v1/package_routes.py index d844a93ba..c92d77cd4 100644 --- a/fedn/network/api/v1/package_routes.py +++ b/fedn/network/api/v1/package_routes.py @@ -5,13 +5,13 @@ from fedn.common.config import FEDN_COMPUTE_PACKAGE_DIR from fedn.network.api.auth import jwt_auth_required -from fedn.network.api.v1.shared import api_version, get_post_data_to_kwargs, get_typed_list_headers, get_use_typing, mdb, repository -from fedn.network.storage.statestore.stores.package_store import PackageStore +from fedn.network.api.v1.shared import (api_version, get_post_data_to_kwargs, + get_typed_list_headers, get_use_typing, + package_store, repository) from fedn.network.storage.statestore.stores.shared import EntityNotFound bp = Blueprint("package", __name__, url_prefix=f"/api/{api_version}/packages") -package_store = PackageStore(mdb, "control.package") @bp.route("/", methods=["GET"]) diff --git a/fedn/network/api/v1/shared.py b/fedn/network/api/v1/shared.py index e9d8af937..75b5d264b 100644 --- a/fedn/network/api/v1/shared.py +++ b/fedn/network/api/v1/shared.py @@ -3,11 +3,13 @@ import pymongo from pymongo.database import Database -from fedn.network.api.shared import modelstorage_config, network_id, statestore_config +from fedn.network.api.shared import (modelstorage_config, network_id, + statestore_config) from fedn.network.storage.s3.base import RepositoryBase from fedn.network.storage.s3.miniorepository import MINIORepository from fedn.network.storage.s3.repository import Repository from fedn.network.storage.statestore.stores.client_store import ClientStore +from fedn.network.storage.statestore.stores.package_store import PackageStore api_version = "v1" mc = pymongo.MongoClient(**statestore_config["mongo_config"]) @@ -15,6 +17,7 @@ mdb: Database = mc[network_id] client_store = ClientStore(mdb, "network.clients") +package_store = PackageStore(mdb, "control.package") minio_repository: RepositoryBase = None diff --git a/fedn/network/storage/statestore/stores/package_store.py b/fedn/network/storage/statestore/stores/package_store.py index 5c777cc55..de55d888c 100644 --- a/fedn/network/storage/statestore/stores/package_store.py +++ b/fedn/network/storage/statestore/stores/package_store.py @@ -144,6 +144,20 @@ def get_active(self, use_typing: bool = False) -> Package: return Package.from_dict(response, response) if use_typing else from_document(response) + def set_active_helper(self, helper: str) -> bool: + """Set the active helper + param helper: The helper to set as active + type: str + return: Whether the operation was successful + """ + if not helper or helper == "" or helper not in ["numpyhelper", "binaryhelper", "androidhelper"]: + raise ValueError() + + try: + self.database[self.collection].update_one({"key": "active"}, {"$set": {"helper": helper}}, upsert=True) + except Exception: + return False + def _allowed_file_extension(self, filename: str, ALLOWED_EXTENSIONS={"gz", "bz2", "tar", "zip", "tgz"}) -> bool: """Check if file extension is allowed. diff --git a/fedn/network/storage/statestore/stores/session_store.py b/fedn/network/storage/statestore/stores/session_store.py index f2675c912..7b29354ce 100644 --- a/fedn/network/storage/statestore/stores/session_store.py +++ b/fedn/network/storage/statestore/stores/session_store.py @@ -23,7 +23,7 @@ def from_dict(data: dict) -> "Session": id=str(data["_id"]), session_id=data["session_id"] if "session_id" in data else None, status=data["status"] if "status" in data else None, - session_config=data["session_config"] if "session_config" in data else None + session_config=data["session_config"] if "session_config" in data else None, ) @@ -71,7 +71,7 @@ def _validate_session_config(self, session_config: dict) -> Tuple[bool, str]: if not isinstance(session_config["validate"], bool): return False, "session_config.validate must be a boolean" - if "helper_type" not in session_config: + if "helper_type" not in session_config or session_config["helper_type"] == "": return False, "session_config.helper_type is required" if not isinstance(session_config["helper_type"], str): @@ -92,7 +92,7 @@ def _validate(self, item: Session) -> Tuple[bool, str]: else: return False, "session_config must be a dict" - return self._validate_session_config(session_config) + return self._validate_session_config(session_config) def _complement(self, item: Session): item["status"] = "Created" @@ -101,7 +101,6 @@ def _complement(self, item: Session): if "session_id" not in item or item["session_id"] == "" or not isinstance(item["session_id"], str): item["session_id"] = str(uuid.uuid4()) - def get(self, id: str, use_typing: bool = False) -> Session: """Get an entity by id param id: The id of the entity @@ -173,7 +172,4 @@ def list(self, limit: int, skip: int, sort_key: str, sort_order=pymongo.DESCENDI result = [Session.from_dict(item) for item in response["result"]] if use_typing else response["result"] - return { - "count": response["count"], - "result": result - } + return {"count": response["count"], "result": result}