Skip to content

Commit f8a7d09

Browse files
👽️ Ensure assets cannot be cleaned when computation running (#8681)
1 parent 1aae10c commit f8a7d09

File tree

13 files changed

+194
-54
lines changed

13 files changed

+194
-54
lines changed

services/api-server/openapi.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2880,6 +2880,9 @@
28802880
}
28812881
}
28822882
},
2883+
"409": {
2884+
"description": "Job not finished yet"
2885+
},
28832886
"422": {
28842887
"description": "Validation Error",
28852888
"content": {
@@ -12251,7 +12254,7 @@
1225112254
"type": "string",
1225212255
"format": "uuid",
1225312256
"title": "Key name",
12254-
"description": "port identifier name.Correponds to the UUID of the parameter/probe node in the study"
12257+
"description": "port identifier name.Corresponds to the UUID of the parameter/probe node in the study"
1225512258
},
1225612259
"kind": {
1225712260
"type": "string",

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

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,10 @@
1212
from pydantic.types import PositiveInt
1313

1414
from ..._service_jobs import JobService, compose_solver_job_resource_name
15-
from ...exceptions.backend_errors import ProjectAlreadyStartedError
15+
from ...exceptions.backend_errors import (
16+
ProjectAlreadyStartedError,
17+
SolverJobNotStoppedYetError,
18+
)
1619
from ...exceptions.service_errors_utils import DEFAULT_BACKEND_SERVICE_STATUS_CODES
1720
from ...models.basic_types import VersionStr
1821
from ...models.schemas.errors import ErrorGet
@@ -138,7 +141,8 @@ async def delete_job(
138141
@router.delete(
139142
"/{solver_key:path}/releases/{version}/jobs/{job_id:uuid}/assets",
140143
status_code=status.HTTP_204_NO_CONTENT,
141-
responses=JOBS_STATUS_CODES,
144+
responses=JOBS_STATUS_CODES
145+
| {status.HTTP_409_CONFLICT: {"description": "Job not finished yet"}},
142146
description=create_route_description(
143147
base="Deletes assets associated with an existing solver job. N.B. this renders the solver job un-startable",
144148
changelog=[
@@ -159,6 +163,11 @@ async def delete_job_assets(
159163
job_parent_resource_name=job_parent_resource_name, job_id=job_id
160164
)
161165
assert project_job_rpc_get.uuid == job_id # nosec
166+
job_status = await job_service.inspect_solver_job(
167+
solver_key=solver_key, version=version, job_id=job_id
168+
)
169+
if job_status.stopped_at is None:
170+
raise SolverJobNotStoppedYetError(job_id=job_id, state=job_status.state)
162171

163172
await job_service.delete_job_assets(
164173
job_parent_resource_name=job_parent_resource_name, job_id=job_id
Lines changed: 78 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import parse # type: ignore[import-untyped]
2+
from common_library.user_messages import user_message
23
from fastapi import status
34

45
from ._base import ApiServerBaseError
@@ -8,7 +9,9 @@ class BaseBackEndError(ApiServerBaseError):
89
"""status_code: the default return status which will be returned to the client calling the
910
api-server (in case this exception is raised)"""
1011

11-
msg_template = "The api-server encountered an error when contacting the backend"
12+
msg_template = user_message(
13+
"An error occurred when contacting the backend service.", _version=1
14+
)
1215
status_code = status.HTTP_502_BAD_GATEWAY
1316

1417
@classmethod
@@ -19,145 +22,190 @@ def named_fields(cls) -> set[str]:
1922

2023

2124
class BackendTimeoutError(BaseBackEndError):
22-
msg_template = "Backend request timed out"
25+
msg_template = user_message("The backend request timed out.", _version=1)
2326
status_code = status.HTTP_504_GATEWAY_TIMEOUT
2427

2528

2629
class InvalidInputError(BaseBackEndError):
27-
msg_template = "Invalid input"
30+
msg_template = user_message("The provided input is not valid.", _version=1)
2831
status_code = status.HTTP_422_UNPROCESSABLE_ENTITY
2932

3033

3134
class ListSolversOrStudiesError(BaseBackEndError):
32-
msg_template = "Cannot list solvers/studies"
35+
msg_template = user_message(
36+
"Unable to retrieve the list of solvers and projects.", _version=1
37+
)
3338
status_code = status.HTTP_404_NOT_FOUND
3439

3540

3641
class ListJobsError(BaseBackEndError):
37-
msg_template = "Cannot list jobs"
42+
msg_template = user_message("Unable to retrieve the list of jobs.", _version=1)
3843
status_code = status.HTTP_404_NOT_FOUND
3944

4045

4146
class PaymentRequiredError(BaseBackEndError):
42-
msg_template = "Payment required"
47+
msg_template = user_message("Payment is required to proceed.", _version=1)
4348
status_code = status.HTTP_402_PAYMENT_REQUIRED
4449

4550

4651
class ProfileNotFoundError(BaseBackEndError):
47-
msg_template = "Profile not found"
52+
msg_template = user_message("The requested profile could not be found.", _version=1)
4853
status_code = status.HTTP_404_NOT_FOUND
4954

5055

5156
class ProgramOrSolverOrStudyNotFoundError(BaseBackEndError):
52-
msg_template = "Could not get program/solver/study {name}:{version}"
57+
msg_template = user_message(
58+
"The program, solver, or project '{name}' version {version} could not be found.",
59+
_version=1,
60+
)
5361
status_code = status.HTTP_404_NOT_FOUND
5462

5563

5664
class ServiceForbiddenAccessError(BaseBackEndError):
57-
msg_template = "Forbidden access to program/solver/study {name}:{version}"
65+
msg_template = user_message(
66+
"You do not have permission to access the program, solver, or project '{name}' version {version}.",
67+
_version=1,
68+
)
5869
status_code = status.HTTP_403_FORBIDDEN
5970

6071

6172
class JobForbiddenAccessError(BaseBackEndError):
62-
msg_template = "Forbidden access to job {project_id}"
73+
msg_template = user_message(
74+
"You do not have permission to access job {project_id}.", _version=1
75+
)
6376
status_code = status.HTTP_403_FORBIDDEN
6477

6578

6679
class JobNotFoundError(BaseBackEndError):
67-
msg_template = "Could not get solver/study job {project_id}"
80+
msg_template = user_message(
81+
"The solver or project job {project_id} could not be found.", _version=1
82+
)
6883
status_code = status.HTTP_404_NOT_FOUND
6984

7085

7186
class LogFileNotFoundError(BaseBackEndError):
72-
msg_template = "Could not get logfile for solver/study job {project_id}"
87+
msg_template = user_message(
88+
"The log file for the solver or project job {project_id} could not be found.",
89+
_version=1,
90+
)
7391
status_code = status.HTTP_404_NOT_FOUND
7492

7593

7694
class SolverOutputNotFoundError(BaseBackEndError):
77-
msg_template = "Solver output of project {project_id} not found"
95+
msg_template = user_message(
96+
"The output for project {project_id} could not be found.", _version=1
97+
)
7898
status_code = status.HTTP_404_NOT_FOUND
7999

80100

81101
class ClusterNotFoundError(BaseBackEndError):
82-
msg_template = "Cluster not found"
102+
msg_template = user_message("The requested cluster could not be found.", _version=1)
83103
status_code = status.HTTP_406_NOT_ACCEPTABLE
84104

85105

86106
class ConfigurationError(BaseBackEndError):
87-
msg_template = "Configuration error"
107+
msg_template = user_message("A configuration error occurred.", _version=1)
88108
status_code = status.HTTP_422_UNPROCESSABLE_ENTITY
89109

90110

91111
class ProductPriceNotFoundError(BaseBackEndError):
92-
msg_template = "Product price not found"
112+
msg_template = user_message("The product price could not be found.", _version=1)
93113
status_code = status.HTTP_404_NOT_FOUND
94114

95115

96116
class WalletNotFoundError(BaseBackEndError):
97-
msg_template = "Wallet not found"
117+
msg_template = user_message("The requested wallet could not be found.", _version=1)
98118
status_code = status.HTTP_404_NOT_FOUND
99119

100120

101121
class ForbiddenWalletError(BaseBackEndError):
102-
msg_template = "User does not have access to wallet"
122+
msg_template = user_message(
123+
"You do not have permission to access this wallet.", _version=1
124+
)
103125
status_code = status.HTTP_403_FORBIDDEN
104126

105127

106128
class ProjectPortsNotFoundError(BaseBackEndError):
107-
msg_template = "The ports for the job/study {project_id} could not be found"
129+
msg_template = user_message(
130+
"The ports for job or project {project_id} could not be found.", _version=1
131+
)
108132
status_code = status.HTTP_404_NOT_FOUND
109133

110134

111135
class ProjectMetadataNotFoundError(BaseBackEndError):
112-
msg_template = "The metadata for the job/study {project_id} could not be found"
136+
msg_template = user_message(
137+
"The metadata for job or project {project_id} could not be found.", _version=1
138+
)
113139
status_code = status.HTTP_404_NOT_FOUND
114140

115141

116142
class PricingUnitNotFoundError(BaseBackEndError):
117-
msg_template = "The pricing unit could not be found"
143+
msg_template = user_message("The pricing unit could not be found.", _version=1)
118144
status_code = status.HTTP_404_NOT_FOUND
119145

120146

121147
class PricingPlanNotFoundError(BaseBackEndError):
122-
msg_template = "The pricing plan could not be found"
148+
msg_template = user_message("The pricing plan could not be found.", _version=1)
123149
status_code = status.HTTP_404_NOT_FOUND
124150

125151

126152
class ProjectAlreadyStartedError(BaseBackEndError):
127-
msg_template = "Project already started"
153+
msg_template = user_message("The project has already been started.", _version=1)
128154
status_code = status.HTTP_200_OK
129155

130156

131157
class InsufficientNumberOfSeatsError(BaseBackEndError):
132-
msg_template = "Not enough available seats for license item {licensed_item_id}"
158+
msg_template = user_message(
159+
"Not enough available seats for license item {licensed_item_id}.", _version=1
160+
)
133161
status_code = status.HTTP_409_CONFLICT
134162

135163

136164
class CanNotCheckoutServiceIsNotRunningError(BaseBackEndError):
137-
msg_template = "Can not checkout license item {licensed_item_id} as dynamic service is not running. Current service id: {service_run_id}"
165+
msg_template = user_message(
166+
"Unable to check out license item {licensed_item_id} because the dynamic service is not running. Current service ID: {service_run_id}.",
167+
_version=1,
168+
)
138169
status_code = status.HTTP_422_UNPROCESSABLE_ENTITY
139170

140171

141172
class LicensedItemCheckoutNotFoundError(BaseBackEndError):
142-
msg_template = "Licensed item checkout {licensed_item_checkout_id} not found."
173+
msg_template = user_message(
174+
"The license item checkout {licensed_item_checkout_id} could not be found.",
175+
_version=1,
176+
)
143177
status_code = status.HTTP_404_NOT_FOUND
144178

145179

146180
class JobAssetsMissingError(BaseBackEndError):
147-
msg_template = "Job assets missing for job {job_id}"
181+
msg_template = user_message("The assets for job {job_id} are missing.", _version=1)
148182
status_code = status.HTTP_409_CONFLICT
149183

150184

151185
class CeleryTaskNotFoundError(BaseBackEndError):
152-
msg_template = "Task {task_uuid} not found"
186+
msg_template = user_message("Task {task_uuid} could not be found.", _version=1)
153187
status_code = status.HTTP_404_NOT_FOUND
154188

155189

156190
class SolverJobOutputRequestButNotSucceededError(BaseBackEndError):
157-
msg_template = "Solver job '{job_id}' not succeeded, when output is requested. Current state: {state}"
191+
msg_template = user_message(
192+
"Cannot retrieve output for solver job '{job_id}' because it has not completed successfully. Current state: {state}.",
193+
_version=1,
194+
)
195+
status_code = status.HTTP_409_CONFLICT
196+
197+
198+
class SolverJobNotStoppedYetError(BaseBackEndError):
199+
msg_template = user_message(
200+
"Solver job '{job_id}' has not stopped yet. Current status: {state}.",
201+
_version=1,
202+
)
158203
status_code = status.HTTP_409_CONFLICT
159204

160205

161206
class StudyJobOutputRequestButNotSucceededError(BaseBackEndError):
162-
msg_template = "Study job '{job_id}' not succeeded, when output is requested. Current state: {state}"
207+
msg_template = user_message(
208+
"Cannot retrieve output for project job '{job_id}' because it has not completed successfully. Current state: {state}.",
209+
_version=1,
210+
)
163211
status_code = status.HTTP_409_CONFLICT

services/api-server/src/simcore_service_api_server/exceptions/custom_errors.py

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
from common_library.user_messages import user_message
2+
13
from ._base import ApiServerBaseError
24

35

@@ -7,11 +9,16 @@ class CustomBaseError(ApiServerBaseError):
79

810
class InsufficientCreditsError(CustomBaseError):
911
# NOTE: Same message as WalletNotEnoughCreditsError
10-
msg_template = "Wallet '{wallet_name}' has {wallet_credit_amount} credits. Please add some before requesting solver ouputs"
12+
msg_template = user_message(
13+
"Wallet '{wallet_name}' has {wallet_credit_amount} credits. Please add credits before requesting solver outputs.",
14+
_version=1,
15+
)
1116

1217

1318
class MissingWalletError(CustomBaseError):
14-
msg_template = "Job {job_id} does not have an associated wallet."
19+
msg_template = user_message(
20+
"Job {job_id} does not have an associated wallet.", _version=1
21+
)
1522

1623

1724
class ApplicationSetupError(CustomBaseError):
@@ -26,4 +33,7 @@ class SolverServiceListJobsFiltersError(
2633
ServiceConfigurationError
2734
): # pylint: disable=too-many-ancestors
2835
service_cls_name = "SolverService"
29-
detail_msg = "solver_version is set but solver_id is not. Please provide both or none of them"
36+
detail_msg = user_message(
37+
"The solver_version parameter is set but solver_id is not. Please provide both parameters or neither.",
38+
_version=1,
39+
)

services/api-server/src/simcore_service_api_server/exceptions/function_errors.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
from common_library.user_messages import user_message
12
from fastapi import status
23

34
from .backend_errors import BaseBackEndError
@@ -8,10 +9,10 @@ class BaseFunctionBackendError(BaseBackEndError):
89

910

1011
class FunctionJobCacheNotFoundError(BaseBackEndError):
11-
msg_template: str = "No cached function job found."
12+
msg_template: str = user_message("No cached function job was found.", _version=1)
1213
status_code: int = 404 # Not Found
1314

1415

1516
class FunctionJobProjectMissingError(BaseBackEndError):
16-
msg_template: str = "Could not process function job"
17+
msg_template: str = user_message("Unable to process the function job.", _version=1)
1718
status_code: int = status.HTTP_500_INTERNAL_SERVER_ERROR # Not Found

services/api-server/src/simcore_service_api_server/exceptions/log_streaming_errors.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
from common_library.user_messages import user_message
2+
13
from ._base import ApiServerBaseError
24

35

@@ -10,4 +12,7 @@ class LogStreamerNotRegisteredError(LogStreamingBaseError):
1012

1113

1214
class LogStreamerRegistrationConflictError(LogStreamingBaseError):
13-
msg_template = "A stream was already connected to {job_id}. Only a single stream can be connected at the time"
15+
msg_template = user_message(
16+
"A stream is already connected to {job_id}. Only one stream can be connected at a time.",
17+
_version=1,
18+
)

0 commit comments

Comments
 (0)