Skip to content

Commit 5ad8c46

Browse files
👽️ 🐛 fix stop job endpoint in API server (#5069)
1 parent 0177abf commit 5ad8c46

File tree

4 files changed

+166
-4
lines changed

4 files changed

+166
-4
lines changed

services/api-server/openapi.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -848,7 +848,7 @@
848848
"content": {
849849
"application/json": {
850850
"schema": {
851-
"$ref": "#/components/schemas/Job"
851+
"$ref": "#/components/schemas/JobStatus"
852852
}
853853
}
854854
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -206,7 +206,7 @@ async def start_job(
206206

207207
@router.post(
208208
"/{solver_key:path}/releases/{version}/jobs/{job_id:uuid}:stop",
209-
response_model=Job,
209+
response_model=JobStatus,
210210
)
211211
async def stop_job(
212212
solver_key: SolverKeyId,
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
[
2+
{
3+
"name": "POST /v2/computations/4989fa99-b567-43bd-978a-68c2b95fdabc:stop",
4+
"description": "<Request('POST', 'http://director-v2:8000/v2/computations/4989fa99-b567-43bd-978a-68c2b95fdabc:stop')>",
5+
"method": "POST",
6+
"host": "director-v2",
7+
"path": {
8+
"path": "/v2/computations/{project_id}:stop",
9+
"path_parameters": [
10+
{
11+
"in": "path",
12+
"name": "project_id",
13+
"required": true,
14+
"schema": {
15+
"title": "Project Id",
16+
"type": "str",
17+
"pattern": null,
18+
"format": "uuid",
19+
"exclusiveMinimum": null,
20+
"minimum": null,
21+
"anyOf": null,
22+
"allOf": null,
23+
"oneOf": null
24+
},
25+
"response_value": "computations"
26+
}
27+
]
28+
},
29+
"query": null,
30+
"request_payload": {
31+
"user_id": 1
32+
},
33+
"response_body": {
34+
"id": "4989fa99-b567-43bd-978a-68c2b95fdabc",
35+
"state": "NOT_STARTED",
36+
"result": null,
37+
"pipeline_details": {
38+
"adjacency_list": {
39+
"0c8b627e-2d3e-5560-a4de-f6cbc8ebca2f": []
40+
},
41+
"progress": 0.0,
42+
"node_states": {
43+
"0c8b627e-2d3e-5560-a4de-f6cbc8ebca2f": {
44+
"modified": true,
45+
"dependencies": [],
46+
"currentStatus": "NOT_STARTED",
47+
"progress": null
48+
}
49+
}
50+
},
51+
"iteration": null,
52+
"cluster_id": null,
53+
"started": null,
54+
"stopped": null,
55+
"submitted": "2023-11-17T13:04:59.327557+00:00",
56+
"url": "http://director-v2:8000/v2/computations/4989fa99-b567-43bd-978a-68c2b95fdabc:stop",
57+
"stop_url": null
58+
},
59+
"status_code": 202
60+
},
61+
{
62+
"name": "GET /v2/computations/4989fa99-b567-43bd-978a-68c2b95fdabc",
63+
"description": "<Request('GET', 'http://director-v2:8000/v2/computations/4989fa99-b567-43bd-978a-68c2b95fdabc?user_id=1')>",
64+
"method": "GET",
65+
"host": "director-v2",
66+
"path": {
67+
"path": "/v2/computations/{project_id}",
68+
"path_parameters": [
69+
{
70+
"in": "path",
71+
"name": "project_id",
72+
"required": true,
73+
"schema": {
74+
"title": "Project Id",
75+
"type": "str",
76+
"pattern": null,
77+
"format": "uuid",
78+
"exclusiveMinimum": null,
79+
"minimum": null,
80+
"anyOf": null,
81+
"allOf": null,
82+
"oneOf": null
83+
},
84+
"response_value": "computations"
85+
}
86+
]
87+
},
88+
"query": "user_id=1",
89+
"request_payload": null,
90+
"response_body": {
91+
"id": "4989fa99-b567-43bd-978a-68c2b95fdabc",
92+
"state": "NOT_STARTED",
93+
"result": null,
94+
"pipeline_details": {
95+
"adjacency_list": {
96+
"0c8b627e-2d3e-5560-a4de-f6cbc8ebca2f": []
97+
},
98+
"progress": 0.0,
99+
"node_states": {
100+
"0c8b627e-2d3e-5560-a4de-f6cbc8ebca2f": {
101+
"modified": true,
102+
"dependencies": [],
103+
"currentStatus": "NOT_STARTED",
104+
"progress": null
105+
}
106+
}
107+
},
108+
"iteration": null,
109+
"cluster_id": null,
110+
"started": null,
111+
"stopped": null,
112+
"submitted": "2023-11-17T13:04:59.327557+00:00",
113+
"url": "http://director-v2:8000/v2/computations/4989fa99-b567-43bd-978a-68c2b95fdabc?user_id=1",
114+
"stop_url": null
115+
},
116+
"status_code": 200
117+
}
118+
]

services/api-server/tests/unit/test_api_solver_jobs.py

Lines changed: 46 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,20 @@
11
from pathlib import Path
2-
from typing import Any, Callable
2+
from typing import Any, Callable, Final
33
from uuid import UUID
44

55
import httpx
66
import pytest
77
import respx
88
from faker import Faker
99
from fastapi import status
10+
from fastapi.encoders import jsonable_encoder
1011
from httpx import AsyncClient
1112
from models_library.api_schemas_webserver.resource_usage import PricingUnitGet
1213
from pydantic import parse_obj_as
1314
from simcore_service_api_server._meta import API_VTAG
14-
from simcore_service_api_server.models.schemas.jobs import Job
15+
from simcore_service_api_server.models.schemas.jobs import Job, JobStatus
1516
from simcore_service_api_server.models.schemas.solvers import Solver
17+
from simcore_service_api_server.services.director_v2 import ComputationTaskGet
1618
from simcore_service_api_server.utils.http_calls_capture import HttpApiCallCaptureModel
1719
from unit.conftest import SideEffectCallback
1820

@@ -297,3 +299,45 @@ async def test_get_solver_job_pricing_unit_no_payment(
297299

298300
assert response.status_code == status.HTTP_200_OK
299301
assert response.json()["job_id"] == _job_id
302+
303+
304+
async def test_stop_job(
305+
client: AsyncClient,
306+
mocked_directorv2_service_api_base,
307+
mocked_groups_extra_properties,
308+
respx_mock_from_capture: Callable[
309+
[list[respx.MockRouter], Path, list[SideEffectCallback]],
310+
list[respx.MockRouter],
311+
],
312+
auth: httpx.BasicAuth,
313+
project_tests_dir: Path,
314+
):
315+
316+
_solver_key: Final[str] = "simcore/services/comp/isolve"
317+
_version: Final[str] = "2.1.24"
318+
_job_id: Final[str] = "1eefc09b-5d08-4022-bc18-33dedbbd7d0f"
319+
320+
def _stop_job_side_effect(
321+
request: httpx.Request,
322+
path_params: dict[str, Any],
323+
capture: HttpApiCallCaptureModel,
324+
) -> Any:
325+
task = ComputationTaskGet.parse_obj(capture.response_body)
326+
task.id = UUID(_job_id)
327+
328+
return jsonable_encoder(task)
329+
330+
respx_mock = respx_mock_from_capture(
331+
[mocked_directorv2_service_api_base],
332+
project_tests_dir / "mocks" / "stop_job.json",
333+
[_stop_job_side_effect, get_inspect_job_side_effect(job_id=_job_id)],
334+
)
335+
336+
response = await client.post(
337+
f"{API_VTAG}/solvers/{_solver_key}/releases/{_version}/jobs/{_job_id}:stop",
338+
auth=auth,
339+
)
340+
341+
assert response.status_code == status.HTTP_200_OK
342+
status_ = JobStatus.parse_obj(response.json())
343+
assert status_.job_id == UUID(_job_id)

0 commit comments

Comments
 (0)