Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion services/dynamic-scheduler/VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
1.0.0
1.1.0
170 changes: 170 additions & 0 deletions services/dynamic-scheduler/openapi.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
{

Check failure on line 1 in services/dynamic-scheduler/openapi.json

View workflow job for this annotation

GitHub Actions / check OAS' are up to date

Error when checking services/dynamic-scheduler/openapi.json
"openapi": "3.1.0",
"info": {
"title": "simcore-service-dynamic-scheduler web API",
Expand Down Expand Up @@ -44,6 +44,32 @@
}
}
}
},
"/v1/ops/running-services": {
"get": {
"tags": [
"ops"
],
"summary": "Running Services",
"description": "returns all running dynamic services. Used by ops internall to determine\nwhen it is safe to shutdown the platform",
"operationId": "running_services_v1_ops_running_services_get",
"responses": {
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {
"items": {
"$ref": "#/components/schemas/RunningDynamicServiceDetails"
},
"type": "array",
"title": "Response Running Services V1 Ops Running Services Get"
}
}
}
}
}
}
}
},
"components": {
Expand Down Expand Up @@ -90,6 +116,150 @@
"docs_url"
],
"title": "Meta"
},
"RunningDynamicServiceDetails": {
"properties": {
"service_key": {
"type": "string",
"pattern": "^simcore/services/dynamic/([a-z0-9][a-z0-9_.-]*/)*([a-z0-9-_]+[a-z0-9])$",
"title": "Service Key",
"description": "distinctive name for the node based on the docker registry path"
},
"service_version": {
"type": "string",
"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-]+)*)?$",
"title": "Service Version",
"description": "semantic version number of the node"
},
"user_id": {
"type": "integer",
"exclusiveMinimum": true,
"title": "User Id",
"minimum": 0
},
"project_id": {
"type": "string",
"format": "uuid",
"title": "Project Id"
},
"service_uuid": {
"type": "string",
"format": "uuid",
"title": "Service Uuid"
},
"service_basepath": {
"anyOf": [
{
"type": "string",
"format": "path"
},
{
"type": "null"
}
],
"title": "Service Basepath",
"description": "predefined path where the dynamic service should be served. If empty, the service shall use the root endpoint."
},
"boot_type": {
"$ref": "#/components/schemas/ServiceBootType",
"description": "Describes how the dynamic services was started (legacy=V0, modern=V2).Since legacy services do not have this label it defaults to V0.",
"default": "V0"
},
"service_host": {
"type": "string",
"title": "Service Host",
"description": "the service swarm internal host name"
},
"service_port": {
"type": "integer",
"exclusiveMaximum": true,
"exclusiveMinimum": true,
"title": "Service Port",
"description": "the service swarm internal port",
"maximum": 65535,
"minimum": 0
},
"published_port": {
"anyOf": [
{
"type": "integer",
"exclusiveMaximum": true,
"exclusiveMinimum": true,
"maximum": 65535,
"minimum": 0
},
{
"type": "null"
}
],
"title": "Published Port",
"description": "the service swarm published port if any",
"deprecated": true
},
"entry_point": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"title": "Entry Point",
"description": "if empty the service entrypoint is on the root endpoint.",
"deprecated": true
},
"service_state": {
"$ref": "#/components/schemas/ServiceState",
"description": "service current state"
},
"service_message": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"title": "Service Message",
"description": "additional information related to service state"
}
},
"type": "object",
"required": [
"service_key",
"service_version",
"user_id",
"project_id",
"service_uuid",
"service_host",
"service_port",
"service_state"
],
"title": "RunningDynamicServiceDetails"
},
"ServiceBootType": {
"type": "string",
"enum": [
"V0",
"V2"
],
"title": "ServiceBootType"
},
"ServiceState": {
"type": "string",
"enum": [
"failed",
"pending",
"pulling",
"starting",
"running",
"stopping",
"complete",
"idle"
],
"title": "ServiceState"
}
}
}
Expand Down
6 changes: 3 additions & 3 deletions services/dynamic-scheduler/setup.cfg
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[bumpversion]
current_version = 1.0.0
current_version = 1.1.0
commit = True
message = services/dynamic-scheduler version: {current_version} → {new_version}
tag = False
Expand All @@ -9,9 +9,9 @@ commit_args = --no-verify

[tool:pytest]
asyncio_mode = auto
markers =
markers =
testit: "marks test to run during development"

[mypy]
plugins =
plugins =
pydantic.mypy
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
from typing import Annotated

from fastapi import APIRouter, Depends, FastAPI
from models_library.api_schemas_directorv2.dynamic_services import (
DynamicServiceGet,
)

from ...services import scheduler_interface
from ._dependencies import (
get_app,
)

router = APIRouter()


@router.get("/ops/running-services")
async def running_services(
app: Annotated[FastAPI, Depends(get_app)],
) -> list[DynamicServiceGet]:
"""returns all running dynamic services. Used by ops internall to determine
when it is safe to shutdown the platform"""
return await scheduler_interface.list_tracked_dynamic_services(
app, user_id=None, project_id=None
)
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,15 @@
)

from ..._meta import API_VTAG
from . import _health, _meta
from . import _health, _meta, _ops


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

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

app.add_exception_handler(Exception, handle_errors_as_500)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# pylint:disable=redefined-outer-name
# pylint:disable=unused-argument
import json
from collections.abc import Iterator

import pytest
import respx
from fastapi import status
from fastapi.encoders import jsonable_encoder
from httpx import AsyncClient
from models_library.api_schemas_directorv2.dynamic_services import (
DynamicServiceGet,
)
from pydantic import TypeAdapter
from simcore_service_dynamic_scheduler._meta import API_VTAG


@pytest.fixture
def mock_director_v2_service(
running_services: list[DynamicServiceGet],
) -> Iterator[None]:
with respx.mock(
base_url="http://director-v2:8000/v2",
assert_all_called=False,
assert_all_mocked=True, # IMPORTANT: KEEP always True!
) as mock:
mock.get("/dynamic_services").respond(
status.HTTP_200_OK,
text=json.dumps(jsonable_encoder(running_services)),
)

yield None


@pytest.mark.parametrize(
"running_services",
[
DynamicServiceGet.model_json_schema()["examples"],
[],
],
)
async def test_running_services(mock_director_v2_service: None, client: AsyncClient):
response = await client.get(f"/{API_VTAG}/ops/running-services")
assert response.status_code == status.HTTP_200_OK
assert isinstance(
TypeAdapter(list[DynamicServiceGet]).validate_python(response.json()), list
)
Loading