Skip to content

Commit 68f0b24

Browse files
GitHKAndrei Neagu
andauthored
♻️ reroute get project inactivity via dynamic-scheduler (#6949)
Co-authored-by: Andrei Neagu <[email protected]>
1 parent d75b0a3 commit 68f0b24

File tree

14 files changed

+140
-56
lines changed

14 files changed

+140
-56
lines changed

packages/models-library/src/models_library/api_schemas_directorv2/dynamic_services.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,3 +79,5 @@ class DynamicServiceCreate(ServiceDetails):
7979

8080
class GetProjectInactivityResponse(BaseModel):
8181
is_inactive: bool
82+
83+
model_config = ConfigDict(json_schema_extra={"example": {"is_inactive": "false"}})

packages/service-library/src/servicelib/rabbitmq/rpc_interfaces/dynamic_scheduler/services.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
from models_library.api_schemas_directorv2.dynamic_services import (
55
DynamicServiceGet,
6+
GetProjectInactivityResponse,
67
RetrieveDataOutEnveloped,
78
)
89
from models_library.api_schemas_dynamic_scheduler import DYNAMIC_SCHEDULER_RPC_NAMESPACE
@@ -99,6 +100,24 @@ async def stop_dynamic_service(
99100
assert result is None # nosec
100101

101102

103+
@log_decorator(_logger, level=logging.DEBUG)
104+
async def get_project_inactivity(
105+
rabbitmq_rpc_client: RabbitMQRPCClient,
106+
*,
107+
project_id: ProjectID,
108+
max_inactivity_seconds: NonNegativeInt,
109+
) -> GetProjectInactivityResponse:
110+
result = await rabbitmq_rpc_client.request(
111+
DYNAMIC_SCHEDULER_RPC_NAMESPACE,
112+
_RPC_METHOD_NAME_ADAPTER.validate_python("get_project_inactivity"),
113+
project_id=project_id,
114+
max_inactivity_seconds=max_inactivity_seconds,
115+
timeout_s=_RPC_DEFAULT_TIMEOUT_S,
116+
)
117+
assert isinstance(result, GetProjectInactivityResponse) # nosec
118+
return result
119+
120+
102121
@log_decorator(_logger, level=logging.DEBUG)
103122
async def restart_user_services(
104123
rabbitmq_rpc_client: RabbitMQRPCClient,

services/director-v2/openapi.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2058,7 +2058,10 @@
20582058
"required": [
20592059
"is_inactive"
20602060
],
2061-
"title": "GetProjectInactivityResponse"
2061+
"title": "GetProjectInactivityResponse",
2062+
"example": {
2063+
"is_inactive": "false"
2064+
}
20622065
},
20632066
"HTTPValidationError": {
20642067
"properties": {

services/dynamic-scheduler/src/simcore_service_dynamic_scheduler/api/rpc/_services.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
from fastapi import FastAPI
22
from models_library.api_schemas_directorv2.dynamic_services import (
33
DynamicServiceGet,
4+
GetProjectInactivityResponse,
45
RetrieveDataOutEnveloped,
56
)
67
from models_library.api_schemas_dynamic_scheduler.dynamic_services import (
@@ -12,6 +13,7 @@
1213
from models_library.projects_nodes_io import NodeID
1314
from models_library.services_types import ServicePortKey
1415
from models_library.users import UserID
16+
from pydantic import NonNegativeInt
1517
from servicelib.rabbitmq import RPCRouter
1618
from servicelib.rabbitmq.rpc_interfaces.dynamic_scheduler.errors import (
1719
ServiceWaitingForManualInterventionError,
@@ -62,6 +64,15 @@ async def stop_dynamic_service(
6264
)
6365

6466

67+
@router.expose()
68+
async def get_project_inactivity(
69+
app: FastAPI, *, project_id: ProjectID, max_inactivity_seconds: NonNegativeInt
70+
) -> GetProjectInactivityResponse:
71+
return await scheduler_interface.get_project_inactivity(
72+
app, project_id=project_id, max_inactivity_seconds=max_inactivity_seconds
73+
)
74+
75+
6576
@router.expose()
6677
async def restart_user_services(app: FastAPI, *, node_id: NodeID) -> None:
6778
await scheduler_interface.restart_user_services(app, node_id=node_id)

services/dynamic-scheduler/src/simcore_service_dynamic_scheduler/services/director_v2/_public_client.py

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
from fastapi import FastAPI, status
55
from models_library.api_schemas_directorv2.dynamic_services import (
66
DynamicServiceGet,
7+
GetProjectInactivityResponse,
78
RetrieveDataOutEnveloped,
89
)
910
from models_library.api_schemas_dynamic_scheduler.dynamic_services import (
@@ -14,7 +15,7 @@
1415
from models_library.projects_nodes_io import NodeID
1516
from models_library.services_types import ServicePortKey
1617
from models_library.users import UserID
17-
from pydantic import TypeAdapter
18+
from pydantic import NonNegativeInt, TypeAdapter
1819
from servicelib.fastapi.app_state import SingletonInAppStateMixin
1920
from servicelib.fastapi.http_client import AttachLifespanMixin, HasClientSetupInterface
2021
from servicelib.fastapi.http_client_thin import UnexpectedStatusError
@@ -125,6 +126,16 @@ async def list_tracked_dynamic_services(
125126
)
126127
return TypeAdapter(list[DynamicServiceGet]).validate_python(response.json())
127128

129+
async def get_project_inactivity(
130+
self, *, project_id: ProjectID, max_inactivity_seconds: NonNegativeInt
131+
) -> GetProjectInactivityResponse:
132+
response = await self.thin_client.get_projects_inactivity(
133+
project_id=project_id, max_inactivity_seconds=max_inactivity_seconds
134+
)
135+
return TypeAdapter(GetProjectInactivityResponse).validate_python(
136+
response.json()
137+
)
138+
128139
async def restart_user_services(self, *, node_id: NodeID) -> None:
129140
await self.thin_client.post_restart(node_id=node_id)
130141

services/dynamic-scheduler/src/simcore_service_dynamic_scheduler/services/director_v2/_thin_client.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
from models_library.services_resources import ServiceResourcesDictHelpers
1414
from models_library.services_types import ServicePortKey
1515
from models_library.users import UserID
16+
from pydantic import NonNegativeInt
1617
from servicelib.common_headers import (
1718
X_DYNAMIC_SIDECAR_REQUEST_DNS,
1819
X_DYNAMIC_SIDECAR_REQUEST_SCHEME,
@@ -143,6 +144,15 @@ async def get_dynamic_services(
143144
)
144145

145146
@retry_on_errors()
147+
@expect_status(status.HTTP_200_OK)
148+
async def get_projects_inactivity(
149+
self, *, project_id: ProjectID, max_inactivity_seconds: NonNegativeInt
150+
) -> Response:
151+
return await self.client.get(
152+
f"/dynamic_services/projects/{project_id}/inactivity",
153+
params={"max_inactivity_seconds": max_inactivity_seconds},
154+
)
155+
146156
@expect_status(status.HTTP_204_NO_CONTENT)
147157
async def post_restart(self, *, node_id: NodeID) -> Response:
148158
return await self.client.post(f"/dynamic_services/{node_id}:restart")

services/dynamic-scheduler/src/simcore_service_dynamic_scheduler/services/scheduler_interface.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
from fastapi import FastAPI
22
from models_library.api_schemas_directorv2.dynamic_services import (
33
DynamicServiceGet,
4+
GetProjectInactivityResponse,
45
RetrieveDataOutEnveloped,
56
)
67
from models_library.api_schemas_dynamic_scheduler.dynamic_services import (
@@ -12,6 +13,7 @@
1213
from models_library.projects_nodes_io import NodeID
1314
from models_library.services_types import ServicePortKey
1415
from models_library.users import UserID
16+
from pydantic import NonNegativeInt
1517

1618
from ..core.settings import ApplicationSettings
1719
from .director_v2 import DirectorV2Client
@@ -79,6 +81,22 @@ async def stop_dynamic_service(
7981
await set_request_as_stopped(app, dynamic_service_stop)
8082

8183

84+
async def get_project_inactivity(
85+
app: FastAPI, *, project_id: ProjectID, max_inactivity_seconds: NonNegativeInt
86+
) -> GetProjectInactivityResponse:
87+
settings: ApplicationSettings = app.state.settings
88+
if settings.DYNAMIC_SCHEDULER_USE_INTERNAL_SCHEDULER:
89+
raise NotImplementedError
90+
91+
director_v2_client = DirectorV2Client.get_from_app_state(app)
92+
response: GetProjectInactivityResponse = (
93+
await director_v2_client.get_project_inactivity(
94+
project_id=project_id, max_inactivity_seconds=max_inactivity_seconds
95+
)
96+
)
97+
return response
98+
99+
82100
async def restart_user_services(app: FastAPI, *, node_id: NodeID) -> None:
83101
settings: ApplicationSettings = app.state.settings
84102
if settings.DYNAMIC_SCHEDULER_USE_INTERNAL_SCHEDULER:

services/dynamic-scheduler/tests/unit/api_rpc/test_api_rpc__services.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
from fastapi.encoders import jsonable_encoder
1212
from models_library.api_schemas_directorv2.dynamic_services import (
1313
DynamicServiceGet,
14+
GetProjectInactivityResponse,
1415
RetrieveDataOutEnveloped,
1516
)
1617
from models_library.api_schemas_dynamic_scheduler.dynamic_services import (
@@ -493,6 +494,40 @@ async def test_stop_dynamic_service_serializes_generic_errors(
493494
)
494495

495496

497+
@pytest.fixture
498+
def inactivity_response() -> GetProjectInactivityResponse:
499+
return TypeAdapter(GetProjectInactivityResponse).validate_python(
500+
GetProjectInactivityResponse.model_json_schema()["example"]
501+
)
502+
503+
504+
@pytest.fixture
505+
def mock_director_v2_get_project_inactivity(
506+
project_id: ProjectID, inactivity_response: GetProjectInactivityResponse
507+
) -> Iterator[None]:
508+
with respx.mock(
509+
base_url="http://director-v2:8000/v2",
510+
assert_all_called=False,
511+
assert_all_mocked=True, # IMPORTANT: KEEP always True!
512+
) as mock:
513+
mock.get(f"/dynamic_services/projects/{project_id}/inactivity").respond(
514+
status.HTTP_200_OK, text=inactivity_response.model_dump_json()
515+
)
516+
yield None
517+
518+
519+
async def test_get_project_inactivity(
520+
mock_director_v2_get_project_inactivity: None,
521+
rpc_client: RabbitMQRPCClient,
522+
project_id: ProjectID,
523+
inactivity_response: GetProjectInactivityResponse,
524+
):
525+
result = await services.get_project_inactivity(
526+
rpc_client, project_id=project_id, max_inactivity_seconds=5
527+
)
528+
assert result == inactivity_response
529+
530+
496531
@pytest.fixture
497532
def mock_director_v2_restart_user_services(node_id: NodeID) -> Iterator[None]:
498533
with respx.mock(

services/web/server/src/simcore_service_webserver/api/v0/openapi.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9952,6 +9952,8 @@ components:
99529952
required:
99539953
- is_inactive
99549954
title: GetProjectInactivityResponse
9955+
example:
9956+
is_inactive: 'false'
99559957
GetWalletAutoRecharge:
99569958
properties:
99579959
enabled:

services/web/server/src/simcore_service_webserver/director_v2/_core_dynamic_services.py

Lines changed: 0 additions & 35 deletions
This file was deleted.

0 commit comments

Comments
 (0)