Skip to content

Commit cf516f3

Browse files
author
Andrei Neagu
committed
Merge remote-tracking branch 'upstream/master' into pr-osparc-s3-zip-stream-worker-code
2 parents 8cd8e7e + 669a59d commit cf516f3

File tree

6 files changed

+248
-6
lines changed

6 files changed

+248
-6
lines changed

services/dynamic-scheduler/VERSION

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
1.0.0
1+
1.1.0

services/dynamic-scheduler/openapi.json

Lines changed: 171 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
"info": {
44
"title": "simcore-service-dynamic-scheduler web API",
55
"description": "Service that manages lifecycle of dynamic services",
6-
"version": "1.0.0"
6+
"version": "1.1.0"
77
},
88
"paths": {
99
"/health": {
@@ -44,6 +44,32 @@
4444
}
4545
}
4646
}
47+
},
48+
"/v1/ops/running-services": {
49+
"get": {
50+
"tags": [
51+
"ops"
52+
],
53+
"summary": "Running Services",
54+
"description": "returns all running dynamic services. Used by ops internall to determine\nwhen it is safe to shutdown the platform",
55+
"operationId": "running_services_v1_ops_running_services_get",
56+
"responses": {
57+
"200": {
58+
"description": "Successful Response",
59+
"content": {
60+
"application/json": {
61+
"schema": {
62+
"items": {
63+
"$ref": "#/components/schemas/RunningDynamicServiceDetails"
64+
},
65+
"type": "array",
66+
"title": "Response Running Services V1 Ops Running Services Get"
67+
}
68+
}
69+
}
70+
}
71+
}
72+
}
4773
}
4874
},
4975
"components": {
@@ -90,6 +116,150 @@
90116
"docs_url"
91117
],
92118
"title": "Meta"
119+
},
120+
"RunningDynamicServiceDetails": {
121+
"properties": {
122+
"service_key": {
123+
"type": "string",
124+
"pattern": "^simcore/services/dynamic/([a-z0-9][a-z0-9_.-]*/)*([a-z0-9-_]+[a-z0-9])$",
125+
"title": "Service Key",
126+
"description": "distinctive name for the node based on the docker registry path"
127+
},
128+
"service_version": {
129+
"type": "string",
130+
"pattern": "^(0|[1-9]\\d*)(\\.(0|[1-9]\\d*)){2}(-(0|[1-9]\\d*|\\d*[-a-zA-Z][-\\da-zA-Z]*)(\\.(0|[1-9]\\d*|\\d*[-a-zA-Z][-\\da-zA-Z]*))*)?(\\+[-\\da-zA-Z]+(\\.[-\\da-zA-Z-]+)*)?$",
131+
"title": "Service Version",
132+
"description": "semantic version number of the node"
133+
},
134+
"user_id": {
135+
"type": "integer",
136+
"exclusiveMinimum": true,
137+
"title": "User Id",
138+
"minimum": 0
139+
},
140+
"project_id": {
141+
"type": "string",
142+
"format": "uuid",
143+
"title": "Project Id"
144+
},
145+
"service_uuid": {
146+
"type": "string",
147+
"format": "uuid",
148+
"title": "Service Uuid"
149+
},
150+
"service_basepath": {
151+
"anyOf": [
152+
{
153+
"type": "string",
154+
"format": "path"
155+
},
156+
{
157+
"type": "null"
158+
}
159+
],
160+
"title": "Service Basepath",
161+
"description": "predefined path where the dynamic service should be served. If empty, the service shall use the root endpoint."
162+
},
163+
"boot_type": {
164+
"$ref": "#/components/schemas/ServiceBootType",
165+
"description": "Describes how the dynamic services was started (legacy=V0, modern=V2).Since legacy services do not have this label it defaults to V0.",
166+
"default": "V0"
167+
},
168+
"service_host": {
169+
"type": "string",
170+
"title": "Service Host",
171+
"description": "the service swarm internal host name"
172+
},
173+
"service_port": {
174+
"type": "integer",
175+
"exclusiveMaximum": true,
176+
"exclusiveMinimum": true,
177+
"title": "Service Port",
178+
"description": "the service swarm internal port",
179+
"maximum": 65535,
180+
"minimum": 0
181+
},
182+
"published_port": {
183+
"anyOf": [
184+
{
185+
"type": "integer",
186+
"exclusiveMaximum": true,
187+
"exclusiveMinimum": true,
188+
"maximum": 65535,
189+
"minimum": 0
190+
},
191+
{
192+
"type": "null"
193+
}
194+
],
195+
"title": "Published Port",
196+
"description": "the service swarm published port if any",
197+
"deprecated": true
198+
},
199+
"entry_point": {
200+
"anyOf": [
201+
{
202+
"type": "string"
203+
},
204+
{
205+
"type": "null"
206+
}
207+
],
208+
"title": "Entry Point",
209+
"description": "if empty the service entrypoint is on the root endpoint.",
210+
"deprecated": true
211+
},
212+
"service_state": {
213+
"$ref": "#/components/schemas/ServiceState",
214+
"description": "service current state"
215+
},
216+
"service_message": {
217+
"anyOf": [
218+
{
219+
"type": "string"
220+
},
221+
{
222+
"type": "null"
223+
}
224+
],
225+
"title": "Service Message",
226+
"description": "additional information related to service state"
227+
}
228+
},
229+
"type": "object",
230+
"required": [
231+
"service_key",
232+
"service_version",
233+
"user_id",
234+
"project_id",
235+
"service_uuid",
236+
"service_host",
237+
"service_port",
238+
"service_state"
239+
],
240+
"title": "RunningDynamicServiceDetails"
241+
},
242+
"ServiceBootType": {
243+
"type": "string",
244+
"enum": [
245+
"V0",
246+
"V2"
247+
],
248+
"title": "ServiceBootType"
249+
},
250+
"ServiceState": {
251+
"type": "string",
252+
"enum": [
253+
"failed",
254+
"pending",
255+
"pulling",
256+
"starting",
257+
"running",
258+
"stopping",
259+
"complete",
260+
"idle"
261+
],
262+
"title": "ServiceState"
93263
}
94264
}
95265
}
Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
[bumpversion]
2-
current_version = 1.0.0
2+
current_version = 1.1.0
33
commit = True
44
message = services/dynamic-scheduler version: {current_version} → {new_version}
55
tag = False
@@ -9,9 +9,9 @@ commit_args = --no-verify
99

1010
[tool:pytest]
1111
asyncio_mode = auto
12-
markers =
12+
markers =
1313
testit: "marks test to run during development"
1414

1515
[mypy]
16-
plugins =
16+
plugins =
1717
pydantic.mypy
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
from typing import Annotated
2+
3+
from fastapi import APIRouter, Depends, FastAPI
4+
from models_library.api_schemas_directorv2.dynamic_services import (
5+
DynamicServiceGet,
6+
)
7+
8+
from ...services import scheduler_interface
9+
from ._dependencies import (
10+
get_app,
11+
)
12+
13+
router = APIRouter()
14+
15+
16+
@router.get("/ops/running-services")
17+
async def running_services(
18+
app: Annotated[FastAPI, Depends(get_app)],
19+
) -> list[DynamicServiceGet]:
20+
"""returns all running dynamic services. Used by ops internall to determine
21+
when it is safe to shutdown the platform"""
22+
return await scheduler_interface.list_tracked_dynamic_services(
23+
app, user_id=None, project_id=None
24+
)

services/dynamic-scheduler/src/simcore_service_dynamic_scheduler/api/rest/routes.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,15 @@
55
)
66

77
from ..._meta import API_VTAG
8-
from . import _health, _meta
8+
from . import _health, _meta, _ops
99

1010

1111
def initialize_rest_api(app: FastAPI) -> None:
1212
app.include_router(_health.router)
1313

1414
api_router = APIRouter(prefix=f"/{API_VTAG}")
1515
api_router.include_router(_meta.router, tags=["meta"])
16+
api_router.include_router(_ops.router, tags=["ops"])
1617
app.include_router(api_router)
1718

1819
app.add_exception_handler(Exception, handle_errors_as_500)
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
# pylint:disable=redefined-outer-name
2+
# pylint:disable=unused-argument
3+
import json
4+
from collections.abc import Iterator
5+
6+
import pytest
7+
import respx
8+
from fastapi import status
9+
from fastapi.encoders import jsonable_encoder
10+
from httpx import AsyncClient
11+
from models_library.api_schemas_directorv2.dynamic_services import (
12+
DynamicServiceGet,
13+
)
14+
from pydantic import TypeAdapter
15+
from simcore_service_dynamic_scheduler._meta import API_VTAG
16+
17+
18+
@pytest.fixture
19+
def mock_director_v2_service(
20+
running_services: list[DynamicServiceGet],
21+
) -> Iterator[None]:
22+
with respx.mock(
23+
base_url="http://director-v2:8000/v2",
24+
assert_all_called=False,
25+
assert_all_mocked=True, # IMPORTANT: KEEP always True!
26+
) as mock:
27+
mock.get("/dynamic_services").respond(
28+
status.HTTP_200_OK,
29+
text=json.dumps(jsonable_encoder(running_services)),
30+
)
31+
32+
yield None
33+
34+
35+
@pytest.mark.parametrize(
36+
"running_services",
37+
[
38+
DynamicServiceGet.model_json_schema()["examples"],
39+
[],
40+
],
41+
)
42+
async def test_running_services(mock_director_v2_service: None, client: AsyncClient):
43+
response = await client.get(f"/{API_VTAG}/ops/running-services")
44+
assert response.status_code == status.HTTP_200_OK
45+
assert isinstance(
46+
TypeAdapter(list[DynamicServiceGet]).validate_python(response.json()), list
47+
)

0 commit comments

Comments
 (0)