Skip to content

Commit ed711ad

Browse files
authored
Merge branch 'master' into obfuscate_aws_cred
2 parents 78565da + b01bc0d commit ed711ad

File tree

16 files changed

+495
-46
lines changed

16 files changed

+495
-46
lines changed

packages/models-library/tests/test__pydantic_models_and_enums.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ def test_equivalent_enums_are_not_strictly_equal():
3030
#
3131
# Here two equivalent enum BUT of type str-enum
3232
#
33-
# SEE from models_library.utils.enums.AutoStrEnum
33+
# SEE from models_library.utils.enums.StrAutoEnum
3434
# SEE https://docs.pydantic.dev/dev-v2/usage/types/enums/
3535
#
3636

services/api-server/src/simcore_service_api_server/_service_function_jobs_task_client.py

Lines changed: 1 addition & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,6 @@
2020
TaskID,
2121
)
2222
from models_library.functions_errors import (
23-
FunctionExecuteAccessDeniedError,
24-
FunctionsExecuteApiAccessDeniedError,
2523
UnsupportedFunctionClassError,
2624
UnsupportedFunctionFunctionJobClassCombinationError,
2725
)
@@ -245,36 +243,7 @@ async def get_cached_function_job(
245243
function: RegisteredFunction,
246244
job_inputs: JobInputs,
247245
) -> RegisteredFunctionJob:
248-
"""
249-
N.B. this function checks access rights
250-
251-
raises FunctionsExecuteApiAccessDeniedError if user cannot execute functions
252-
raises FunctionJobCacheNotFoundError if no cached job is found
253-
254-
"""
255-
256-
user_api_access_rights = (
257-
await self._web_rpc_client.get_functions_user_api_access_rights(
258-
user_id=self.user_id, product_name=self.product_name
259-
)
260-
)
261-
if not user_api_access_rights.execute_functions:
262-
raise FunctionsExecuteApiAccessDeniedError(
263-
user_id=self.user_id,
264-
function_id=function.uid,
265-
)
266-
267-
user_permissions = await self._web_rpc_client.get_function_user_permissions(
268-
function_id=function.uid,
269-
user_id=self.user_id,
270-
product_name=self.product_name,
271-
)
272-
if not user_permissions.execute:
273-
raise FunctionExecuteAccessDeniedError(
274-
user_id=self.user_id,
275-
function_id=function.uid,
276-
)
277-
246+
"""Raises FunctionJobCacheNotFoundError if no cached job is found"""
278247
if cached_function_jobs := await self._web_rpc_client.find_cached_function_jobs(
279248
function_id=function.uid,
280249
inputs=job_inputs.values,

services/api-server/src/simcore_service_api_server/_service_functions.py

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,11 @@
55

66
from common_library.exclude import as_dict_exclude_none
77
from models_library.functions import FunctionClass, FunctionID, RegisteredFunction
8-
from models_library.functions_errors import UnsupportedFunctionClassError
8+
from models_library.functions_errors import (
9+
FunctionExecuteAccessDeniedError,
10+
FunctionsExecuteApiAccessDeniedError,
11+
UnsupportedFunctionClassError,
12+
)
913
from models_library.products import ProductName
1014
from models_library.rest_pagination import (
1115
MAXIMUM_NUMBER_OF_ITEMS_PER_PAGE,
@@ -76,3 +80,37 @@ async def get_function(self, function_id: FunctionID) -> RegisteredFunction:
7680
product_name=self.product_name,
7781
function_id=function_id,
7882
)
83+
84+
async def check_execute_function_permission(
85+
self,
86+
*,
87+
function: RegisteredFunction,
88+
) -> None:
89+
"""
90+
Check execute permissions for a user on a function
91+
92+
raises FunctionsExecuteApiAccessDeniedError if user cannot execute functions via the functions API
93+
raises FunctionExecuteAccessDeniedError if user cannot execute this functions
94+
"""
95+
96+
user_api_access_rights = (
97+
await self._web_rpc_client.get_functions_user_api_access_rights(
98+
user_id=self.user_id, product_name=self.product_name
99+
)
100+
)
101+
if not user_api_access_rights.execute_functions:
102+
raise FunctionsExecuteApiAccessDeniedError(
103+
user_id=self.user_id,
104+
function_id=function.uid,
105+
)
106+
107+
user_permissions = await self._web_rpc_client.get_function_user_permissions(
108+
function_id=function.uid,
109+
user_id=self.user_id,
110+
product_name=self.product_name,
111+
)
112+
if not user_permissions.execute:
113+
raise FunctionExecuteAccessDeniedError(
114+
user_id=self.user_id,
115+
function_id=function.uid,
116+
)

services/api-server/src/simcore_service_api_server/api/routes/functions_routes.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -343,6 +343,10 @@ async def run_function(
343343
else None
344344
)
345345
pricing_spec = JobPricingSpecification.create_from_headers(request.headers)
346+
347+
await function_service.check_execute_function_permission(
348+
function=to_run_function,
349+
)
346350
job_links = await function_service.get_function_job_links(to_run_function, url_for)
347351

348352
return await function_job_task_client_service.create_function_job_creation_task(
@@ -418,6 +422,10 @@ async def map_function(
418422
else None
419423
)
420424
pricing_spec = JobPricingSpecification.create_from_headers(request.headers)
425+
426+
await function_service.check_execute_function_permission(
427+
function=to_run_function,
428+
)
421429
job_links = await function_service.get_function_job_links(to_run_function, url_for)
422430

423431
async def _run_single_function(function_inputs: FunctionInputs) -> FunctionJobID:

services/dynamic-scheduler/src/simcore_service_dynamic_scheduler/services/generic_scheduler/__init__.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
get_step_group_proxy,
1111
get_step_store_proxy,
1212
)
13+
from ._errors import NoDataFoundError
1314
from ._event_after_registration import (
1415
register_to_start_after_on_executed_completed,
1516
register_to_start_after_on_reverted_completed,
@@ -24,21 +25,28 @@
2425
)
2526
from ._operation import (
2627
BaseStep,
28+
BaseStepGroup,
2729
Operation,
2830
OperationRegistry,
2931
ParallelStepGroup,
3032
SingleStepGroup,
3133
)
32-
from ._store import OperationContextProxy, StepGroupProxy, StepStoreProxy
34+
from ._store import (
35+
OperationContextProxy,
36+
StepGroupProxy,
37+
StepStoreProxy,
38+
)
3339

3440
__all__: tuple[str, ...] = (
3541
"BaseStep",
42+
"BaseStepGroup",
3643
"cancel_operation",
3744
"generic_scheduler_lifespan",
3845
"get_operation_context_proxy",
3946
"get_operation_name_or_none",
4047
"get_step_group_proxy",
4148
"get_step_store_proxy",
49+
"NoDataFoundError",
4250
"Operation",
4351
"OperationContextProxy",
4452
"OperationName",

services/dynamic-scheduler/src/simcore_service_dynamic_scheduler/services/generic_scheduler/_core.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
from ._errors import (
3131
CannotCancelWhileWaitingForManualInterventionError,
3232
NoDataFoundError,
33+
OperationInitialContextKeyNotFoundError,
3334
OperationNotCancellableError,
3435
StepNameNotInCurrentGroupError,
3536
StepNotInErrorStateError,
@@ -106,6 +107,12 @@ async def start_operation(
106107
# check if operation is registered
107108
operation = OperationRegistry.get_operation(operation_name)
108109

110+
for required_key in operation.initial_context_required_keys:
111+
if required_key not in initial_operation_context:
112+
raise OperationInitialContextKeyNotFoundError(
113+
operation_name=operation_name, required_key=required_key
114+
)
115+
109116
# NOTE: to ensure reproducibility of operations, the
110117
# operation steps cannot overwrite keys in the
111118
# initial context with their results
@@ -237,6 +244,8 @@ async def restart_operation_step_stuck_in_error(
237244
"""
238245
Force a step stuck in an error state to retry.
239246
Will raise errors if step cannot be retried.
247+
248+
raises NoDataFoundError
240249
"""
241250
schedule_data_proxy = ScheduleDataStoreProxy(
242251
store=self._store, schedule_id=schedule_id
@@ -742,6 +751,8 @@ async def cancel_operation(app: FastAPI, schedule_id: ScheduleId) -> None:
742751
743752
`reverting` refers to the act of reverting the effects of a step
744753
that has already been completed (eg: remove a created network)
754+
755+
raises NoDataFoundError
745756
"""
746757
await Core.get_from_app_state(app).cancel_operation(schedule_id)
747758

@@ -763,6 +774,8 @@ async def restart_operation_step_stuck_in_manual_intervention_during_execute(
763774
`waiting for manual intervention` refers to a step that has failed and exhausted
764775
all retries and is now waiting for a human to fix the issue (eg: storage service
765776
is reachable once again)
777+
778+
raises NoDataFoundError
766779
"""
767780
await Core.get_from_app_state(app).restart_operation_step_stuck_in_error(
768781
schedule_id, step_name, in_manual_intervention=True
@@ -778,6 +791,8 @@ async def restart_operation_step_stuck_during_revert(
778791
`stuck step` is a step that has failed and exhausted all retries
779792
`reverting` refers to the act of reverting the effects of a step
780793
that has already been completed (eg: remove a created network)
794+
795+
raises NoDataFoundError
781796
"""
782797
await Core.get_from_app_state(app).restart_operation_step_stuck_in_error(
783798
schedule_id, step_name, in_manual_intervention=False

services/dynamic-scheduler/src/simcore_service_dynamic_scheduler/services/generic_scheduler/_errors.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,13 @@ class OperationNotFoundError(BaseGenericSchedulerError):
1919
)
2020

2121

22-
class StepNotFoundInoperationError(BaseGenericSchedulerError):
22+
class OperationInitialContextKeyNotFoundError(BaseGenericSchedulerError):
23+
msg_template: str = (
24+
"Operation '{operation_name}' required_key='{required_key}' not in initial_operation_context"
25+
)
26+
27+
28+
class StepNotFoundInOperationError(BaseGenericSchedulerError):
2329
msg_template: str = (
2430
"Step '{step_name}' not found steps_names='{steps_names}' for operation '{operation_name}'"
2531
)

services/dynamic-scheduler/src/simcore_service_dynamic_scheduler/services/generic_scheduler/_event_after.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
from servicelib.logging_utils import log_context
66

77
from ._core import start_operation
8+
from ._errors import OperationInitialContextKeyNotFoundError
89
from ._models import (
910
EventType,
1011
OperationContext,
@@ -50,7 +51,12 @@ async def register_to_start_after(
5051
return
5152

5253
# ensure operation exists
53-
OperationRegistry.get_operation(to_start.operation_name)
54+
operation = OperationRegistry.get_operation(to_start.operation_name)
55+
for required_key in operation.initial_context_required_keys:
56+
if required_key not in to_start.initial_context:
57+
raise OperationInitialContextKeyNotFoundError(
58+
operation_name=to_start.operation_name, required_key=required_key
59+
)
5460

5561
await events_proxy.create_or_update_multiple(
5662
{

services/dynamic-scheduler/src/simcore_service_dynamic_scheduler/services/generic_scheduler/_operation.py

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
from ._errors import (
1010
OperationAlreadyRegisteredError,
1111
OperationNotFoundError,
12-
StepNotFoundInoperationError,
12+
StepNotFoundInOperationError,
1313
)
1414
from ._models import (
1515
ALL_RESERVED_CONTEXT_KEYS,
@@ -232,9 +232,17 @@ def get_step_subgroup_to_run(self) -> StepsSubGroup:
232232

233233
class Operation:
234234
def __init__(
235-
self, *step_groups: BaseStepGroup, is_cancellable: bool = True
235+
self,
236+
*step_groups: BaseStepGroup,
237+
initial_context_required_keys: set[str] | None = None,
238+
is_cancellable: bool = True,
236239
) -> None:
237240
self.step_groups = list(step_groups)
241+
self.initial_context_required_keys = (
242+
set()
243+
if initial_context_required_keys is None
244+
else initial_context_required_keys
245+
)
238246
self.is_cancellable = is_cancellable
239247

240248
def __repr__(self) -> str:
@@ -382,7 +390,7 @@ def get_step(
382390

383391
steps_names = set(cls._OPERATIONS[operation_name]["steps"].keys())
384392
if step_name not in steps_names:
385-
raise StepNotFoundInoperationError(
393+
raise StepNotFoundInOperationError(
386394
step_name=step_name,
387395
operation_name=operation_name,
388396
steps_names=steps_names,

services/dynamic-scheduler/tests/unit/services/generic_scheduler/test__operation.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
from simcore_service_dynamic_scheduler.services.generic_scheduler._errors import (
66
OperationAlreadyRegisteredError,
77
OperationNotFoundError,
8-
StepNotFoundInoperationError,
8+
StepNotFoundInOperationError,
99
)
1010
from simcore_service_dynamic_scheduler.services.generic_scheduler._models import (
1111
ALL_RESERVED_CONTEXT_KEYS,
@@ -237,7 +237,7 @@ def test_operation_registry_raises_errors():
237237
with pytest.raises(OperationNotFoundError):
238238
OperationRegistry.get_step("non_existing", "BS1")
239239

240-
with pytest.raises(StepNotFoundInoperationError):
240+
with pytest.raises(StepNotFoundInOperationError):
241241
OperationRegistry.get_step("op1", "non_existing")
242242

243243

0 commit comments

Comments
 (0)