diff --git a/nvflare/apis/event_type.py b/nvflare/apis/event_type.py index 7ecac17486..e2448da113 100644 --- a/nvflare/apis/event_type.py +++ b/nvflare/apis/event_type.py @@ -66,6 +66,7 @@ class EventType(object): # event types for job scheduling - server side BEFORE_CHECK_CLIENT_RESOURCES = "_before_check_client_resources" AFTER_CHECK_CLIENT_RESOURCES = "_after_check_client_resources" + SUBMIT_JOB = "_submit_job" DEPLOY_JOB_TO_SERVER = "_deploy_job_to_server" DEPLOY_JOB_TO_CLIENT = "_deploy_job_to_client" diff --git a/nvflare/app_opt/confidential_computing/cc_manager.py b/nvflare/app_opt/confidential_computing/cc_manager.py index 052a61f5b4..1cda4f5755 100644 --- a/nvflare/app_opt/confidential_computing/cc_manager.py +++ b/nvflare/app_opt/confidential_computing/cc_manager.py @@ -15,6 +15,7 @@ import time from typing import Dict, List +from nvflare.apis.app_validation import AppValidationKey from nvflare.apis.event_type import EventType from nvflare.apis.fl_component import FLComponent from nvflare.apis.fl_constant import FLContextKey, RunProcessKey @@ -146,6 +147,13 @@ def handle_event(self, event_type: str, fl_ctx: FLContext): ): threading.Thread(target=self._shutdown_system, args=[reason, fl_ctx]).start() break + elif event_type == EventType.SUBMIT_JOB: + job_meta = fl_ctx.get_prop(FLContextKey.JOB_META, {}) + byoc = job_meta.get(AppValidationKey.BYOC, False) + if byoc: + fl_ctx.set_prop( + key=FLContextKey.JOB_BLOCK_REASON, value="BYOC job not allowed for CC", sticky=False, private=True + ) def _setup_cc_authorizers(self, fl_ctx): engine = fl_ctx.get_engine() diff --git a/nvflare/private/fed/server/job_cmds.py b/nvflare/private/fed/server/job_cmds.py index ffe3452d13..2e664fbffb 100644 --- a/nvflare/private/fed/server/job_cmds.py +++ b/nvflare/private/fed/server/job_cmds.py @@ -21,7 +21,8 @@ import nvflare.fuel.hci.file_transfer_defs as ftd from nvflare.apis.client import Client -from nvflare.apis.fl_constant import AdminCommandNames, RunProcessKey +from nvflare.apis.event_type import EventType +from nvflare.apis.fl_constant import AdminCommandNames, FLContextKey, RunProcessKey from nvflare.apis.job_def import Job, JobMetaKey, is_valid_job_id from nvflare.apis.job_def_manager_spec import JobDefManagerSpec, RunStatus from nvflare.apis.storage import DATA, JOB_ZIP, META, META_JSON, WORKSPACE, WORKSPACE_ZIP @@ -541,6 +542,17 @@ def submit_job(self, conn: Connection, args: List[str]): f"job_def_manager in engine is not of type JobDefManagerSpec, but got {type(job_def_manager)}" ) + fl_ctx.set_prop(FLContextKey.JOB_META, meta, private=True, sticky=False) + engine.fire_event(EventType.SUBMIT_JOB, fl_ctx) + block_reason = fl_ctx.get_prop(FLContextKey.JOB_BLOCK_REASON) + if block_reason: + # submitted job blocked + self.logger.error(f"submitted job is blocked: {block_reason}") + conn.append_error( + block_reason, meta=make_meta(MetaStatusValue.INVALID_JOB_DEFINITION, block_reason) + ) + return + # set submitter info meta[JobMetaKey.SUBMITTER_NAME.value] = conn.get_prop(ConnProps.USER_NAME, "") meta[JobMetaKey.SUBMITTER_ORG.value] = conn.get_prop(ConnProps.USER_ORG, "") diff --git a/nvflare/private/fed/server/job_meta_validator.py b/nvflare/private/fed/server/job_meta_validator.py index f5361d2ea6..0856b736eb 100644 --- a/nvflare/private/fed/server/job_meta_validator.py +++ b/nvflare/private/fed/server/job_meta_validator.py @@ -18,6 +18,7 @@ from typing import Optional, Set, Tuple from zipfile import ZipFile +from nvflare.apis.app_validation import AppValidationKey from nvflare.apis.fl_constant import JobConstants from nvflare.apis.job_def import ALL_SITES, SERVER_SITE_NAME, JobMetaKey from nvflare.apis.job_meta_validator_spec import JobMetaValidatorSpec @@ -25,6 +26,9 @@ from nvflare.fuel.utils.config_factory import ConfigFactory from nvflare.security.logging import secure_format_exception +CONFIG_FOLDER = "/config/" +CUSTOM_FOLDER = "/custom/" + MAX_CLIENTS = 1000000 logger = logging.getLogger(__name__) @@ -120,11 +124,12 @@ def _validate_app(self, job_name: str, meta: dict, zip_file: ZipFile) -> None: deploy_map = meta.get(JobMetaKey.DEPLOY_MAP.value) + has_byoc = False for app, deployments in deploy_map.items(): - zip_folder = job_name + "/" + app + "/config/" - if not self._entry_exists(zip_file, zip_folder): - logger.debug(f"zip folder {zip_folder} missing. Files in the zip:") + config_folder = job_name + "/" + app + CONFIG_FOLDER + if not self._entry_exists(zip_file, config_folder): + logger.debug(f"zip folder {config_folder} missing. Files in the zip:") for x in zip_file.namelist(): logger.debug(f" {x}") raise ValueError(f"App '{app}' in deploy_map doesn't exist for job {job_name}") @@ -132,15 +137,22 @@ def _validate_app(self, job_name: str, meta: dict, zip_file: ZipFile) -> None: all_sites = ALL_SITES.casefold() in (site.casefold() for site in deployments) if (all_sites or SERVER_SITE_NAME in deployments) and not self._config_exists( - zip_file, zip_folder, JobConstants.SERVER_JOB_CONFIG + zip_file, config_folder, JobConstants.SERVER_JOB_CONFIG ): raise ValueError(f"App '{app}' will be deployed to server but server config is missing") if (all_sites or [site for site in deployments if site != SERVER_SITE_NAME]) and not self._config_exists( - zip_file, zip_folder, JobConstants.CLIENT_JOB_CONFIG + zip_file, config_folder, JobConstants.CLIENT_JOB_CONFIG ): raise ValueError(f"App '{app}' will be deployed to client but client config is missing") + custom_folder = job_name + "/" + app + CUSTOM_FOLDER + if self._entry_exists(zip_file, custom_folder): + has_byoc = True + + if has_byoc: + meta[AppValidationKey.BYOC] = True + @staticmethod def _convert_value_to_int(v) -> int: if isinstance(v, int):