diff --git a/packages/common-library/src/common_library/unset.py b/packages/common-library/src/common_library/unset.py new file mode 100644 index 00000000000..3d4dfcbc947 --- /dev/null +++ b/packages/common-library/src/common_library/unset.py @@ -0,0 +1,12 @@ +from typing import Any + + +class UnSet: + VALUE: "UnSet" + + +UnSet.VALUE = UnSet() + + +def as_dict_exclude_unset(**params) -> dict[str, Any]: + return {k: v for k, v in params.items() if not isinstance(v, UnSet)} diff --git a/packages/common-library/tests/test_unset.py b/packages/common-library/tests/test_unset.py new file mode 100644 index 00000000000..0fece0d466c --- /dev/null +++ b/packages/common-library/tests/test_unset.py @@ -0,0 +1,15 @@ +from typing import Any + +from common_library.unset import UnSet, as_dict_exclude_unset + + +def test_as_dict_exclude_unset(): + def f( + par1: str | UnSet = UnSet.VALUE, par2: int | UnSet = UnSet.VALUE + ) -> dict[str, Any]: + return as_dict_exclude_unset(par1=par1, par2=par2) + + assert f() == {} + assert f(par1="hi") == {"par1": "hi"} + assert f(par2=4) == {"par2": 4} + assert f(par1="hi", par2=4) == {"par1": "hi", "par2": 4} diff --git a/packages/models-library/src/models_library/api_schemas_directorv2/dynamic_services_service.py b/packages/models-library/src/models_library/api_schemas_directorv2/dynamic_services_service.py index d103a3ea8c5..769e1fc9419 100644 --- a/packages/models-library/src/models_library/api_schemas_directorv2/dynamic_services_service.py +++ b/packages/models-library/src/models_library/api_schemas_directorv2/dynamic_services_service.py @@ -92,32 +92,33 @@ class RunningDynamicServiceDetails(ServiceDetails): ignored_types=(cached_property,), json_schema_extra={ "examples": [ + # legacy { - "boot_type": "V0", - "key": "simcore/services/dynamic/3dviewer", - "version": "2.4.5", - "user_id": 234, - "project_id": "dd1d04d9-d704-4f7e-8f0f-1ca60cc771fe", - "uuid": "75c7f3f4-18f9-4678-8610-54a2ade78eaa", - "basepath": "/x/75c7f3f4-18f9-4678-8610-54a2ade78eaa", - "host": "3dviewer_75c7f3f4-18f9-4678-8610-54a2ade78eaa", - "internal_port": 8888, - "state": "running", - "message": "", - "node_uuid": "75c7f3f4-18f9-4678-8610-54a2ade78eaa", + "service_key": "simcore/services/dynamic/raw-graphs", + "service_version": "2.10.6", + "user_id": 1, + "project_id": "32fb4eb6-ab30-11ef-9ee4-0242ac140008", + "service_uuid": "0cd049ba-cd6b-4a12-b416-a50c9bc8e7bb", + "service_basepath": "/x/0cd049ba-cd6b-4a12-b416-a50c9bc8e7bb", + "service_host": "raw-graphs_0cd049ba-cd6b-4a12-b416-a50c9bc8e7bb", + "service_port": 4000, + "published_port": None, + "entry_point": "", + "service_state": "running", + "service_message": "", }, + # new style { + "service_key": "simcore/services/dynamic/jupyter-math", + "service_version": "3.0.3", + "user_id": 1, + "project_id": "32fb4eb6-ab30-11ef-9ee4-0242ac140008", + "service_uuid": "6e3cad3a-eb64-43de-b476-9ac3c413fd9c", "boot_type": "V2", - "key": "simcore/services/dynamic/dy-static-file-viewer-dynamic-sidecar", - "version": "1.0.0", - "user_id": 234, - "project_id": "dd1d04d9-d704-4f7e-8f0f-1ca60cc771fe", - "uuid": "75c7f3f4-18f9-4678-8610-54a2ade78eaa", - "host": "dy-sidecar_75c7f3f4-18f9-4678-8610-54a2ade78eaa", - "internal_port": 80, - "state": "running", - "message": "", - "node_uuid": "75c7f3f4-18f9-4678-8610-54a2ade78eaa", + "service_host": "dy-sidecar_6e3cad3a-eb64-43de-b476-9ac3c413fd9c", + "service_port": 8888, + "service_state": "running", + "service_message": "", }, ] }, diff --git a/packages/service-library/src/servicelib/rabbitmq/rpc_interfaces/dynamic_scheduler/services.py b/packages/service-library/src/servicelib/rabbitmq/rpc_interfaces/dynamic_scheduler/services.py index 3dcc9ed502f..d941f889bd7 100644 --- a/packages/service-library/src/servicelib/rabbitmq/rpc_interfaces/dynamic_scheduler/services.py +++ b/packages/service-library/src/servicelib/rabbitmq/rpc_interfaces/dynamic_scheduler/services.py @@ -8,8 +8,10 @@ DynamicServiceStop, ) from models_library.api_schemas_webserver.projects_nodes import NodeGet, NodeGetIdle +from models_library.projects import ProjectID from models_library.projects_nodes_io import NodeID from models_library.rabbitmq_basic_types import RPCMethodName +from models_library.users import UserID from pydantic import NonNegativeInt, TypeAdapter from servicelib.logging_utils import log_decorator from servicelib.rabbitmq import RabbitMQRPCClient @@ -29,6 +31,24 @@ _RPC_METHOD_NAME_ADAPTER: TypeAdapter[RPCMethodName] = TypeAdapter(RPCMethodName) +@log_decorator(_logger, level=logging.DEBUG) +async def list_tracked_dynamic_services( + rabbitmq_rpc_client: RabbitMQRPCClient, + *, + user_id: UserID | None = None, + project_id: ProjectID | None = None, +) -> list[DynamicServiceGet]: + result = await rabbitmq_rpc_client.request( + DYNAMIC_SCHEDULER_RPC_NAMESPACE, + _RPC_METHOD_NAME_ADAPTER.validate_python("list_tracked_dynamic_services"), + user_id=user_id, + project_id=project_id, + timeout_s=_RPC_DEFAULT_TIMEOUT_S, + ) + assert isinstance(result, list) # nosec + return result + + @log_decorator(_logger, level=logging.DEBUG) async def get_service_status( rabbitmq_rpc_client: RabbitMQRPCClient, *, node_id: NodeID diff --git a/services/dynamic-scheduler/src/simcore_service_dynamic_scheduler/api/rpc/_services.py b/services/dynamic-scheduler/src/simcore_service_dynamic_scheduler/api/rpc/_services.py index 76a8e30ceec..8cd90ddb8f0 100644 --- a/services/dynamic-scheduler/src/simcore_service_dynamic_scheduler/api/rpc/_services.py +++ b/services/dynamic-scheduler/src/simcore_service_dynamic_scheduler/api/rpc/_services.py @@ -5,7 +5,9 @@ DynamicServiceStop, ) from models_library.api_schemas_webserver.projects_nodes import NodeGet, NodeGetIdle +from models_library.projects import ProjectID from models_library.projects_nodes_io import NodeID +from models_library.users import UserID from servicelib.rabbitmq import RPCRouter from servicelib.rabbitmq.rpc_interfaces.dynamic_scheduler.errors import ( ServiceWaitingForManualInterventionError, @@ -17,6 +19,15 @@ router = RPCRouter() +@router.expose() +async def list_tracked_dynamic_services( + app: FastAPI, *, user_id: UserID | None = None, project_id: ProjectID | None = None +) -> list[DynamicServiceGet]: + return await scheduler_interface.list_tracked_dynamic_services( + app, user_id=user_id, project_id=project_id + ) + + @router.expose() async def get_service_status( app: FastAPI, *, node_id: NodeID diff --git a/services/dynamic-scheduler/src/simcore_service_dynamic_scheduler/services/director_v2/_public_client.py b/services/dynamic-scheduler/src/simcore_service_dynamic_scheduler/services/director_v2/_public_client.py index 5ee4ae3bcac..4b49618d6df 100644 --- a/services/dynamic-scheduler/src/simcore_service_dynamic_scheduler/services/director_v2/_public_client.py +++ b/services/dynamic-scheduler/src/simcore_service_dynamic_scheduler/services/director_v2/_public_client.py @@ -7,7 +7,9 @@ DynamicServiceStart, ) from models_library.api_schemas_webserver.projects_nodes import NodeGet, NodeGetIdle +from models_library.projects import ProjectID from models_library.projects_nodes_io import NodeID +from models_library.users import UserID from pydantic import TypeAdapter from servicelib.fastapi.app_state import SingletonInAppStateMixin from servicelib.fastapi.http_client import AttachLifespanMixin, HasClientSetupInterface @@ -73,7 +75,7 @@ async def stop_dynamic_service( node_id: NodeID, simcore_user_agent: str, save_state: bool, - timeout: datetime.timedelta + timeout: datetime.timedelta, # noqa: ASYNC109 ) -> None: try: await self.thin_client.delete_dynamic_service( @@ -98,6 +100,14 @@ async def stop_dynamic_service( raise + async def list_tracked_dynamic_services( + self, *, user_id: UserID | None = None, project_id: ProjectID | None = None + ) -> list[DynamicServiceGet]: + response = await self.thin_client.get_dynamic_services( + user_id=user_id, project_id=project_id + ) + return TypeAdapter(list[DynamicServiceGet]).validate_python(response.json()) + def setup_director_v2(app: FastAPI) -> None: public_client = DirectorV2Client(app) diff --git a/services/dynamic-scheduler/src/simcore_service_dynamic_scheduler/services/director_v2/_thin_client.py b/services/dynamic-scheduler/src/simcore_service_dynamic_scheduler/services/director_v2/_thin_client.py index 19d93b3a6f1..bfb64e8839a 100644 --- a/services/dynamic-scheduler/src/simcore_service_dynamic_scheduler/services/director_v2/_thin_client.py +++ b/services/dynamic-scheduler/src/simcore_service_dynamic_scheduler/services/director_v2/_thin_client.py @@ -2,13 +2,16 @@ from typing import cast from common_library.json_serialization import json_dumps +from common_library.unset import UnSet, as_dict_exclude_unset from fastapi import FastAPI, status from httpx import Response, Timeout from models_library.api_schemas_dynamic_scheduler.dynamic_services import ( DynamicServiceStart, ) +from models_library.projects import ProjectID from models_library.projects_nodes_io import NodeID from models_library.services_resources import ServiceResourcesDictHelpers +from models_library.users import UserID from servicelib.common_headers import ( X_DYNAMIC_SIDECAR_REQUEST_DNS, X_DYNAMIC_SIDECAR_REQUEST_SCHEME, @@ -108,3 +111,16 @@ async def _( ) return await _(self) + + @retry_on_errors() + @expect_status(status.HTTP_200_OK) + async def get_dynamic_services( + self, + *, + user_id: UserID | None | UnSet = UnSet.VALUE, + project_id: ProjectID | None | UnSet = UnSet.VALUE, + ) -> Response: + return await self.client.get( + "/dynamic_services", + params=as_dict_exclude_unset(user_id=user_id, project_id=project_id), + ) diff --git a/services/dynamic-scheduler/src/simcore_service_dynamic_scheduler/services/scheduler_interface.py b/services/dynamic-scheduler/src/simcore_service_dynamic_scheduler/services/scheduler_interface.py index 1d4bcdd112b..6f655b544e2 100644 --- a/services/dynamic-scheduler/src/simcore_service_dynamic_scheduler/services/scheduler_interface.py +++ b/services/dynamic-scheduler/src/simcore_service_dynamic_scheduler/services/scheduler_interface.py @@ -5,13 +5,28 @@ DynamicServiceStop, ) from models_library.api_schemas_webserver.projects_nodes import NodeGet, NodeGetIdle +from models_library.projects import ProjectID from models_library.projects_nodes_io import NodeID +from models_library.users import UserID from ..core.settings import ApplicationSettings from .director_v2 import DirectorV2Client from .service_tracker import set_request_as_running, set_request_as_stopped +async def list_tracked_dynamic_services( + app: FastAPI, *, user_id: UserID | None = None, project_id: ProjectID | None = None +) -> list[DynamicServiceGet]: + settings: ApplicationSettings = app.state.settings + if settings.DYNAMIC_SCHEDULER_USE_INTERNAL_SCHEDULER: + raise NotImplementedError + + director_v2_client = DirectorV2Client.get_from_app_state(app) + return await director_v2_client.list_tracked_dynamic_services( + user_id=user_id, project_id=project_id + ) + + async def get_service_status( app: FastAPI, *, node_id: NodeID ) -> NodeGet | DynamicServiceGet | NodeGetIdle: diff --git a/services/dynamic-scheduler/tests/unit/api_rpc/test_api_rpc__services.py b/services/dynamic-scheduler/tests/unit/api_rpc/test_api_rpc__services.py index ddd8bfd6e2d..7c1665065ae 100644 --- a/services/dynamic-scheduler/tests/unit/api_rpc/test_api_rpc__services.py +++ b/services/dynamic-scheduler/tests/unit/api_rpc/test_api_rpc__services.py @@ -109,6 +109,15 @@ def mock_director_v2_service_state( 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( + DynamicServiceGet.model_config["json_schema_extra"]["examples"] + ) + ), + ) + mock.get(f"/dynamic_services/{node_id_new_style}").respond( status.HTTP_200_OK, text=service_status_new_style.model_dump_json() ) @@ -177,6 +186,17 @@ async def rpc_client( return await rabbitmq_rpc_client("client") +async def test_list_tracked_dynamic_services(rpc_client: RabbitMQRPCClient): + results = await services.list_tracked_dynamic_services( + rpc_client, user_id=None, project_id=None + ) + assert len(results) == 2 + assert results == [ + TypeAdapter(DynamicServiceGet).validate_python(x) + for x in DynamicServiceGet.model_config["json_schema_extra"]["examples"] + ] + + async def test_get_state( rpc_client: RabbitMQRPCClient, node_id_new_style: NodeID, diff --git a/services/dynamic-scheduler/tests/unit/service_tracker/test__api.py b/services/dynamic-scheduler/tests/unit/service_tracker/test__api.py index f8b4b442a8e..ab89f54e861 100644 --- a/services/dynamic-scheduler/tests/unit/service_tracker/test__api.py +++ b/services/dynamic-scheduler/tests/unit/service_tracker/test__api.py @@ -208,8 +208,8 @@ def _get_dynamic_service_get_from( service_state: ServiceState, ) -> DynamicServiceGet: dict_data = DynamicServiceGet.model_config["json_schema_extra"]["examples"][1] - assert "state" in dict_data - dict_data["state"] = service_state + assert "service_state" in dict_data + dict_data["service_state"] = service_state return TypeAdapter(DynamicServiceGet).validate_python(dict_data) diff --git a/services/dynamic-scheduler/tests/unit/status_monitor/test_services_status_monitor__monitor.py b/services/dynamic-scheduler/tests/unit/status_monitor/test_services_status_monitor__monitor.py index 5924f9dec84..f0bc878fcd9 100644 --- a/services/dynamic-scheduler/tests/unit/status_monitor/test_services_status_monitor__monitor.py +++ b/services/dynamic-scheduler/tests/unit/status_monitor/test_services_status_monitor__monitor.py @@ -99,9 +99,8 @@ def _get_dynamic_service_get_legacy_with( _add_to_dict( dict_data, [ - ("state", state), - ("uuid", f"{node_id}"), - ("node_uuid", f"{node_id}"), + ("service_state", state), + ("service_uuid", f"{node_id}"), ], ) return TypeAdapter(DynamicServiceGet).validate_python(dict_data) @@ -116,9 +115,8 @@ def _get_dynamic_service_get_new_style_with( _add_to_dict( dict_data, [ - ("state", state), - ("uuid", f"{node_id}"), - ("node_uuid", f"{node_id}"), + ("service_state", state), + ("service_uuid", f"{node_id}"), ], ) return TypeAdapter(DynamicServiceGet).validate_python(dict_data) diff --git a/services/web/server/src/simcore_service_webserver/director_v2/_core_dynamic_services.py b/services/web/server/src/simcore_service_webserver/director_v2/_core_dynamic_services.py index 21793b79376..bd4c03f31fe 100644 --- a/services/web/server/src/simcore_service_webserver/director_v2/_core_dynamic_services.py +++ b/services/web/server/src/simcore_service_webserver/director_v2/_core_dynamic_services.py @@ -7,11 +7,9 @@ import logging from aiohttp import web -from models_library.api_schemas_directorv2.dynamic_services import DynamicServiceGet from models_library.projects import ProjectID from models_library.services import ServicePortKey -from pydantic import BaseModel, NonNegativeInt, TypeAdapter -from pydantic.types import PositiveInt +from pydantic import NonNegativeInt from servicelib.logging_utils import log_decorator from yarl import URL @@ -22,36 +20,6 @@ _log = logging.getLogger(__name__) -class _Params(BaseModel): - user_id: PositiveInt | None = None - project_id: str | None = None - - -async def list_dynamic_services( - app: web.Application, - user_id: PositiveInt | None = None, - project_id: str | None = None, -) -> list[DynamicServiceGet]: - params = _Params(user_id=user_id, project_id=project_id) - params_dict = params.model_dump(exclude_none=True) - settings: DirectorV2Settings = get_plugin_settings(app) - if params_dict: # Update query doesnt work with no params to unwrap - backend_url = (settings.base_url / "dynamic_services").update_query( - **params_dict - ) - else: - backend_url = settings.base_url / "dynamic_services" - - services = await request_director_v2( - app, "GET", backend_url, expected_status=web.HTTPOk - ) - - if services is None: - services = [] - assert isinstance(services, list) # nosec - return TypeAdapter(list[DynamicServiceGet]).validate_python(services) - - # NOTE: ANE https://github.com/ITISFoundation/osparc-simcore/issues/3191 @log_decorator(logger=_log) async def retrieve( diff --git a/services/web/server/src/simcore_service_webserver/director_v2/api.py b/services/web/server/src/simcore_service_webserver/director_v2/api.py index 2de6b49e4a2..f56de16b543 100644 --- a/services/web/server/src/simcore_service_webserver/director_v2/api.py +++ b/services/web/server/src/simcore_service_webserver/director_v2/api.py @@ -18,7 +18,6 @@ ) from ._core_dynamic_services import ( get_project_inactivity, - list_dynamic_services, request_retrieve_dyn_service, restart_dynamic_service, retrieve, @@ -39,7 +38,6 @@ "get_project_run_policy", "is_healthy", "is_pipeline_running", - "list_dynamic_services", "request_retrieve_dyn_service", "restart_dynamic_service", "retrieve", diff --git a/services/web/server/src/simcore_service_webserver/dynamic_scheduler/api.py b/services/web/server/src/simcore_service_webserver/dynamic_scheduler/api.py index be02b28bf73..ef8f2b1f703 100644 --- a/services/web/server/src/simcore_service_webserver/dynamic_scheduler/api.py +++ b/services/web/server/src/simcore_service_webserver/dynamic_scheduler/api.py @@ -18,19 +18,34 @@ from models_library.projects import ProjectID from models_library.projects_nodes_io import NodeID from models_library.rabbitmq_messages import ProgressRabbitMessageProject, ProgressType +from models_library.users import UserID from pydantic.types import PositiveInt from servicelib.progress_bar import ProgressBarData from servicelib.rabbitmq import RabbitMQClient, RPCServerError from servicelib.rabbitmq.rpc_interfaces.dynamic_scheduler import services from servicelib.utils import logged_gather -from ..director_v2.api import list_dynamic_services from ..rabbitmq import get_rabbitmq_client, get_rabbitmq_rpc_client from .settings import DynamicSchedulerSettings, get_plugin_settings _logger = logging.getLogger(__name__) +async def list_dynamic_services( + app: web.Application, + *, + user_id: UserID | None = None, + project_id: ProjectID | None = None, +) -> list[DynamicServiceGet]: + """ + Returns: + list of currently running dynamic services + """ + return await services.list_tracked_dynamic_services( + get_rabbitmq_rpc_client(app), user_id=user_id, project_id=project_id + ) + + async def get_dynamic_service( app: web.Application, *, node_id: NodeID ) -> NodeGetIdle | NodeGetUnknown | DynamicServiceGet | NodeGet: @@ -98,7 +113,7 @@ async def stop_dynamic_services_in_project( ) -> None: """Stops all dynamic services in the project""" running_dynamic_services = await list_dynamic_services( - app, user_id=user_id, project_id=project_id + app, user_id=user_id, project_id=ProjectID(project_id) ) async with AsyncExitStack() as stack: diff --git a/services/web/server/src/simcore_service_webserver/folders/_folders_db.py b/services/web/server/src/simcore_service_webserver/folders/_folders_db.py index 88bb3987de4..dee552377fa 100644 --- a/services/web/server/src/simcore_service_webserver/folders/_folders_db.py +++ b/services/web/server/src/simcore_service_webserver/folders/_folders_db.py @@ -10,6 +10,7 @@ import sqlalchemy as sa from aiohttp import web +from common_library.unset import UnSet, as_dict_exclude_unset from models_library.folders import ( FolderDB, FolderID, @@ -33,7 +34,6 @@ from simcore_postgres_database.utils_workspaces_sql import ( create_my_workspace_access_rights_subquery, ) -from simcore_service_webserver.utils import UnSet, as_dict_exclude_unset from sqlalchemy import func from sqlalchemy.ext.asyncio import AsyncConnection from sqlalchemy.orm import aliased @@ -312,12 +312,12 @@ async def update( folders_id_or_ids: FolderID | set[FolderID], product_name: ProductName, # updatable columns - name: str | UnSet = _unset, - parent_folder_id: FolderID | None | UnSet = _unset, - trashed_at: datetime | None | UnSet = _unset, - trashed_explicitly: bool | UnSet = _unset, - workspace_id: WorkspaceID | None | UnSet = _unset, - user_id: UserID | None | UnSet = _unset, + name: str | UnSet = UnSet.VALUE, + parent_folder_id: FolderID | None | UnSet = UnSet.VALUE, + trashed_at: datetime | None | UnSet = UnSet.VALUE, + trashed_explicitly: bool | UnSet = UnSet.VALUE, + workspace_id: WorkspaceID | None | UnSet = UnSet.VALUE, + user_id: UserID | None | UnSet = UnSet.VALUE, ) -> FolderDB: """ Batch/single patch of folder/s diff --git a/services/web/server/src/simcore_service_webserver/garbage_collector/_core_orphans.py b/services/web/server/src/simcore_service_webserver/garbage_collector/_core_orphans.py index 31433088c1d..d369de3ed2f 100644 --- a/services/web/server/src/simcore_service_webserver/garbage_collector/_core_orphans.py +++ b/services/web/server/src/simcore_service_webserver/garbage_collector/_core_orphans.py @@ -13,7 +13,6 @@ from servicelib.utils import logged_gather from simcore_postgres_database.models.users import UserRole -from ..director_v2 import api as director_v2_api from ..dynamic_scheduler import api as dynamic_scheduler_api from ..projects.api import has_user_project_access_rights from ..projects.projects_api import ( @@ -90,7 +89,7 @@ async def remove_orphaned_services( # in between and the GC would remove services that actually should be running. with log_catch(_logger, reraise=False): - running_services = await director_v2_api.list_dynamic_services(app) + running_services = await dynamic_scheduler_api.list_dynamic_services(app) if not running_services: # nothing to do return diff --git a/services/web/server/src/simcore_service_webserver/projects/_folders_db.py b/services/web/server/src/simcore_service_webserver/projects/_folders_db.py index e655cc17bf5..2e5001343d4 100644 --- a/services/web/server/src/simcore_service_webserver/projects/_folders_db.py +++ b/services/web/server/src/simcore_service_webserver/projects/_folders_db.py @@ -6,16 +6,15 @@ import logging from datetime import datetime -from typing import Final from aiohttp import web +from common_library.unset import UnSet, as_dict_exclude_unset from models_library.folders import FolderID from models_library.projects import ProjectID from models_library.users import UserID from pydantic import BaseModel from simcore_postgres_database.models.projects_to_folders import projects_to_folders from simcore_postgres_database.utils_repos import transaction_context -from simcore_service_webserver.utils import UnSet, as_dict_exclude_unset from sqlalchemy import func, literal_column from sqlalchemy.ext.asyncio import AsyncConnection from sqlalchemy.sql import select @@ -24,7 +23,6 @@ _logger = logging.getLogger(__name__) -_unset: Final = UnSet() ### Models @@ -126,7 +124,7 @@ async def update_project_to_folder( *, folders_id_or_ids: FolderID | set[FolderID], # updatable columns - user_id: UserID | None | UnSet = _unset, + user_id: UserID | None | UnSet = UnSet.VALUE, ) -> None: """ Batch/single patch of project to folders diff --git a/services/web/server/src/simcore_service_webserver/projects/_trash_api.py b/services/web/server/src/simcore_service_webserver/projects/_trash_api.py index d3bc6092aaf..e15a98423c7 100644 --- a/services/web/server/src/simcore_service_webserver/projects/_trash_api.py +++ b/services/web/server/src/simcore_service_webserver/projects/_trash_api.py @@ -12,6 +12,7 @@ from servicelib.utils import fire_and_forget_task from ..director_v2 import api as director_v2_api +from ..dynamic_scheduler import api as dynamic_scheduler_api from . import projects_api from ._access_rights_api import check_user_project_permission from .exceptions import ProjectRunningConflictError @@ -56,8 +57,8 @@ async def _is_project_running( app, user_id=user_id, project_id=project_id ) ) or bool( - await director_v2_api.list_dynamic_services( - app, user_id=user_id, project_id=f"{project_id}" + await dynamic_scheduler_api.list_dynamic_services( + app, user_id=user_id, project_id=project_id ) ) diff --git a/services/web/server/src/simcore_service_webserver/projects/projects_api.py b/services/web/server/src/simcore_service_webserver/projects/projects_api.py index cf9445985c6..c0d3c8af835 100644 --- a/services/web/server/src/simcore_service_webserver/projects/projects_api.py +++ b/services/web/server/src/simcore_service_webserver/projects/projects_api.py @@ -609,8 +609,8 @@ async def _start_dynamic_service( project_settings.PROJECTS_MAX_NUM_RUNNING_DYNAMIC_NODES ), ): - project_running_nodes = await director_v2_api.list_dynamic_services( - request.app, user_id, f"{project_uuid}" + project_running_nodes = await dynamic_scheduler_api.list_dynamic_services( + request.app, user_id=user_id, project_id=project_uuid ) _nodes_api.check_num_service_per_projects_limit( app=request.app, @@ -903,8 +903,10 @@ async def delete_project_node( permission="write", ) - list_running_dynamic_services = await director_v2_api.list_dynamic_services( - request.app, project_id=f"{project_uuid}", user_id=user_id + list_running_dynamic_services = await dynamic_scheduler_api.list_dynamic_services( + request.app, + user_id=user_id, + project_id=project_uuid, ) fire_and_forget_task( @@ -1628,9 +1630,9 @@ async def run_project_dynamic_services( # first get the services if they already exist project_settings: ProjectsSettings = get_plugin_settings(request.app) running_services_uuids: list[NodeIDStr] = [ - NodeIDStr(f"{d.node_uuid}") - for d in await director_v2_api.list_dynamic_services( - request.app, user_id, project["uuid"] + f"{d.node_uuid}" + for d in await dynamic_scheduler_api.list_dynamic_services( + request.app, user_id=user_id, project_id=ProjectID(project["uuid"]) ) ] diff --git a/services/web/server/src/simcore_service_webserver/utils.py b/services/web/server/src/simcore_service_webserver/utils.py index 1f73ac06e0a..c6eade6345d 100644 --- a/services/web/server/src/simcore_service_webserver/utils.py +++ b/services/web/server/src/simcore_service_webserver/utils.py @@ -194,17 +194,3 @@ def compute_sha1_on_small_dataset(d: Any) -> SHA1Str: # SEE options in https://github.com/ijl/orjson#option data_bytes = orjson.dumps(d, option=orjson.OPT_NON_STR_KEYS | orjson.OPT_SORT_KEYS) return SHA1Str(hashlib.sha1(data_bytes).hexdigest()) # nosec # NOSONAR - - -# ----------------------------------------------- -# -# UNSET -# - - -class UnSet: - ... - - -def as_dict_exclude_unset(**params) -> dict[str, Any]: - return {k: v for k, v in params.items() if not isinstance(v, UnSet)} diff --git a/services/web/server/tests/integration/01/test_garbage_collection.py b/services/web/server/tests/integration/01/test_garbage_collection.py index d3aee60764d..9c5c133f378 100644 --- a/services/web/server/tests/integration/01/test_garbage_collection.py +++ b/services/web/server/tests/integration/01/test_garbage_collection.py @@ -56,6 +56,7 @@ from simcore_service_webserver.socketio.plugin import setup_socketio from simcore_service_webserver.users.plugin import setup_users from sqlalchemy import func, select +from tenacity import AsyncRetrying, stop_after_delay, wait_fixed log = logging.getLogger(__name__) @@ -101,7 +102,9 @@ def osparc_product_name() -> str: @pytest.fixture -async def director_v2_service_mock() -> AsyncIterable[aioresponses]: +async def director_v2_service_mock( + mocker: MockerFixture, +) -> AsyncIterable[aioresponses]: """uses aioresponses to mock all calls of an aiohttpclient WARNING: any request done through the client will go through aioresponses. It is unfortunate but that means any valid request (like calling the test server) prefix must be set as passthrough. @@ -115,9 +118,13 @@ async def director_v2_service_mock() -> AsyncIterable[aioresponses]: projects_networks_pattern = re.compile( r"^http://[a-z\-_]*director-v2:[0-9]+/v2/dynamic_services/projects/.*/-/networks$" ) - dynamic_services_list_pattern = re.compile( - r"^http://[a-z\-_]*director-v2:[0-9]+/v2/dynamic_services?.*" + + mocker.patch( + "simcore_service_webserver.dynamic_scheduler.api.list_dynamic_services", + autospec=True, + return_value={}, ) + # NOTE: GitHK I have to copy paste that fixture for some unclear reason for now. # I think this is due to some conflict between these non-pytest-simcore fixtures and the loop fixture being defined at different locations?? not sure.. # anyway I think this should disappear once the garbage collector moves to its own micro-service @@ -130,7 +137,6 @@ async def director_v2_service_mock() -> AsyncIterable[aioresponses]: ) mock.delete(delete_computation_pattern, status=204, repeat=True) mock.patch(projects_networks_pattern, status=204, repeat=True) - mock.get(dynamic_services_list_pattern, status=200, repeat=True, payload=[]) yield mock @@ -343,10 +349,18 @@ async def disconnect_user_from_socketio( socket_registry = get_registry(client.app) await sio.disconnect() assert not sio.sid - await asyncio.sleep(0) # just to ensure there is a context switch - assert not await socket_registry.find_keys(("socket_id", sio.get_sid())) - assert sid not in await socket_registry.find_resources(resource_key, "socket_id") - assert not await socket_registry.find_resources(resource_key, "socket_id") + + async for attempt in AsyncRetrying( + wait=wait_fixed(0.1), + stop=stop_after_delay(10), + reraise=True, + ): + with attempt: + assert not await socket_registry.find_keys(("socket_id", sio.get_sid())) + assert sid not in await socket_registry.find_resources( + resource_key, "socket_id" + ) + assert not await socket_registry.find_resources(resource_key, "socket_id") async def assert_users_count( diff --git a/services/web/server/tests/unit/isolated/test_garbage_collector_core.py b/services/web/server/tests/unit/isolated/test_garbage_collector_core.py index 920dfb2b035..3226abb2284 100644 --- a/services/web/server/tests/unit/isolated/test_garbage_collector_core.py +++ b/services/web/server/tests/unit/isolated/test_garbage_collector_core.py @@ -91,7 +91,7 @@ async def mock_is_node_id_present_in_any_project_workbench( @pytest.fixture async def mock_list_dynamic_services(mocker: MockerFixture) -> mock.AsyncMock: return mocker.patch( - f"{MODULE_GC_CORE_ORPHANS}.director_v2_api.list_dynamic_services", + f"{MODULE_GC_CORE_ORPHANS}.dynamic_scheduler_api.list_dynamic_services", autospec=True, return_value=[], ) diff --git a/services/web/server/tests/unit/with_dbs/01/test_director_v2_handlers.py b/services/web/server/tests/unit/with_dbs/01/test_director_v2_handlers.py index e2c9b7e03c1..613e32cee19 100644 --- a/services/web/server/tests/unit/with_dbs/01/test_director_v2_handlers.py +++ b/services/web/server/tests/unit/with_dbs/01/test_director_v2_handlers.py @@ -16,7 +16,6 @@ from pytest_simcore.services_api_mocks_for_aiohttp_clients import AioResponsesMock from servicelib.aiohttp import status from simcore_service_webserver.db.models import UserRole -from simcore_service_webserver.director_v2 import api @pytest.fixture @@ -117,12 +116,3 @@ async def test_stop_computation( else expected.no_content ), ) - - -async def test_regression_get_dynamic_services_empty_params( - director_v2_service_mock: AioResponsesMock, - client: TestClient, -): - assert client.app - list_of_services = await api.list_dynamic_services(client.app) - assert list_of_services == [] diff --git a/services/web/server/tests/unit/with_dbs/02/test_projects_cancellations.py b/services/web/server/tests/unit/with_dbs/02/test_projects_cancellations.py index 960d97969ca..92184c0d145 100644 --- a/services/web/server/tests/unit/with_dbs/02/test_projects_cancellations.py +++ b/services/web/server/tests/unit/with_dbs/02/test_projects_cancellations.py @@ -6,6 +6,7 @@ import asyncio from collections.abc import Awaitable, Callable from typing import Any +from unittest.mock import MagicMock from urllib.parse import urlparse import pytest @@ -88,6 +89,7 @@ async def test_copying_large_project_and_aborting_correctly_removes_new_project( catalog_subsystem_mock: Callable[[list[ProjectDict]], None], slow_storage_subsystem_mock: MockedStorageSubsystem, project_db_cleaner: None, + mocked_dynamic_services_interface: dict[str, MagicMock], ): assert client.app catalog_subsystem_mock([user_project]) @@ -140,6 +142,7 @@ async def test_copying_large_project_and_retrieving_copy_task( catalog_subsystem_mock: Callable[[list[ProjectDict]], None], slow_storage_subsystem_mock: MockedStorageSubsystem, project_db_cleaner: None, + mocked_dynamic_services_interface: dict[str, MagicMock], ): assert client.app catalog_subsystem_mock([user_project]) diff --git a/services/web/server/tests/unit/with_dbs/02/test_projects_crud_handlers__delete.py b/services/web/server/tests/unit/with_dbs/02/test_projects_crud_handlers__delete.py index 956e0d415e2..c4d5d1f26b0 100644 --- a/services/web/server/tests/unit/with_dbs/02/test_projects_crud_handlers__delete.py +++ b/services/web/server/tests/unit/with_dbs/02/test_projects_crud_handlers__delete.py @@ -60,7 +60,7 @@ async def test_delete_project( user_project: ProjectDict, expected: ExpectedResponse, storage_subsystem_mock: MockedStorageSubsystem, - mocked_director_v2_api: dict[str, MagicMock], + mocked_dynamic_services_interface: dict[str, MagicMock], catalog_subsystem_mock: Callable[[list[ProjectDict]], None], fake_services: Callable[..., Awaitable[list[DynamicServiceGet]]], assert_get_same_project_caller: Callable, @@ -70,7 +70,7 @@ async def test_delete_project( # DELETE /v0/projects/{project_id} fakes = await fake_services(5) - mocked_director_v2_api[ + mocked_dynamic_services_interface[ "dynamic_scheduler.api.list_dynamic_services" ].return_value = fakes @@ -90,7 +90,7 @@ async def test_delete_project( # might have finished, and therefore there is no need to waith await tasks[0] - mocked_director_v2_api[ + mocked_dynamic_services_interface[ "dynamic_scheduler.api.list_dynamic_services" ].assert_called_once() @@ -108,7 +108,7 @@ async def test_delete_project( ) for service in fakes ] - mocked_director_v2_api[ + mocked_dynamic_services_interface[ "dynamic_scheduler.api.stop_dynamic_service" ].assert_has_calls(expected_calls) @@ -141,7 +141,7 @@ async def test_delete_multiple_opened_project_forbidden( client: TestClient, logged_user: UserInfoDict, user_project: ProjectDict, - mocked_director_v2_api, + mocked_dynamic_services_interface, create_dynamic_service_mock: Callable[..., Awaitable[DynamicServiceGet]], socketio_client_factory: Callable, client_session_id_factory: Callable, diff --git a/services/web/server/tests/unit/with_dbs/02/test_projects_metadata_handlers.py b/services/web/server/tests/unit/with_dbs/02/test_projects_metadata_handlers.py index c960a86fa13..80c941eca23 100644 --- a/services/web/server/tests/unit/with_dbs/02/test_projects_metadata_handlers.py +++ b/services/web/server/tests/unit/with_dbs/02/test_projects_metadata_handlers.py @@ -6,6 +6,7 @@ import json import random from collections.abc import Awaitable, Callable +from unittest.mock import MagicMock import aiopg import aiopg.sa @@ -40,7 +41,7 @@ @pytest.mark.parametrize(*standard_user_role_response()) async def test_custom_metadata_handlers( # for deletion - mocked_director_v2_api: None, + mocked_dynamic_services_interface: dict[str, MagicMock], storage_subsystem_mock: MockedStorageSubsystem, # client: TestClient, @@ -114,7 +115,7 @@ async def _wait_until_deleted(): @pytest.mark.parametrize(*standard_user_role_response()) async def test_new_project_with_parent_project_node( # for deletion - mocked_director_v2_api: None, + mocked_dynamic_services_interface: dict[str, MagicMock], storage_subsystem_mock: MockedStorageSubsystem, # client: TestClient, @@ -191,7 +192,7 @@ async def test_new_project_with_parent_project_node( @pytest.mark.parametrize(*standard_user_role_response()) async def test_new_project_with_invalid_parent_project_node( # for deletion - mocked_director_v2_api: None, + mocked_dynamic_services_interface: dict[str, MagicMock], storage_subsystem_mock: MockedStorageSubsystem, # client: TestClient, @@ -274,7 +275,7 @@ async def test_new_project_with_invalid_parent_project_node( @pytest.mark.parametrize(*standard_user_role_response()) async def test_set_project_parent_backward_compatibility( # for deletion - mocked_director_v2_api: None, + mocked_dynamic_services_interface: dict[str, MagicMock], storage_subsystem_mock: MockedStorageSubsystem, # client: TestClient, @@ -338,7 +339,7 @@ async def test_set_project_parent_backward_compatibility( @pytest.mark.parametrize(*standard_user_role_response()) async def test_update_project_metadata_backward_compatibility_with_same_project_does_not_raises_and_does_not_work( # for deletion - mocked_director_v2_api: None, + mocked_dynamic_services_interface: dict[str, MagicMock], storage_subsystem_mock: MockedStorageSubsystem, # client: TestClient, @@ -393,7 +394,7 @@ async def test_update_project_metadata_backward_compatibility_with_same_project_ @pytest.mark.parametrize(*standard_user_role_response()) async def test_update_project_metadata_s4lacad_backward_compatibility_passing_nil_parent_node_id( # for deletion - mocked_director_v2_api: None, + mocked_dynamic_services_interface: dict[str, MagicMock], storage_subsystem_mock: MockedStorageSubsystem, # client: TestClient, diff --git a/services/web/server/tests/unit/with_dbs/02/test_projects_nodes_handler.py b/services/web/server/tests/unit/with_dbs/02/test_projects_nodes_handler.py index 5496cb46458..6fc5c13b194 100644 --- a/services/web/server/tests/unit/with_dbs/02/test_projects_nodes_handler.py +++ b/services/web/server/tests/unit/with_dbs/02/test_projects_nodes_handler.py @@ -293,7 +293,7 @@ async def test_create_node_returns_422_if_body_is_missing( user_project: ProjectDict, expected: ExpectedResponse, faker: Faker, - mocked_director_v2_api: dict[str, mock.MagicMock], + mocked_dynamic_services_interface: dict[str, mock.MagicMock], ): assert client.app url = client.app.router["create_node"].url_for(project_id=user_project["uuid"]) @@ -307,7 +307,7 @@ async def test_create_node_returns_422_if_body_is_missing( response = await client.post(url.path, json=partial_body) assert response.status == expected.unprocessable # this does not start anything in the backend - mocked_director_v2_api[ + mocked_dynamic_services_interface[ "dynamic_scheduler.api.run_dynamic_service" ].assert_not_called() @@ -324,7 +324,7 @@ async def test_create_node( user_project: ProjectDict, expected: ExpectedResponse, faker: Faker, - mocked_director_v2_api: dict[str, mock.MagicMock], + mocked_dynamic_services_interface: dict[str, mock.MagicMock], mock_catalog_api: dict[str, mock.Mock], postgres_db: sa.engine.Engine, ): @@ -340,15 +340,15 @@ async def test_create_node( data, error = await assert_status(response, expected.created) if data: assert not error - mocked_director_v2_api[ + mocked_dynamic_services_interface[ "director_v2.api.create_or_update_pipeline" ].assert_called_once() if expect_run_service_call: - mocked_director_v2_api[ + mocked_dynamic_services_interface[ "dynamic_scheduler.api.run_dynamic_service" ].assert_called_once() else: - mocked_director_v2_api[ + mocked_dynamic_services_interface[ "dynamic_scheduler.api.run_dynamic_service" ].assert_not_called() @@ -380,7 +380,7 @@ async def test_create_and_delete_many_nodes_in_parallel( client: TestClient, user_project: ProjectDict, expected: ExpectedResponse, - mocked_director_v2_api: dict[str, mock.MagicMock], + mocked_dynamic_services_interface: dict[str, mock.MagicMock], mock_catalog_api: dict[str, mock.Mock], faker: Faker, postgres_db: sa.engine.Engine, @@ -413,10 +413,10 @@ def inc_running_services(self, *args, **kwargs): # noqa: ARG002 # let's count the started services running_services = _RunningServices() assert running_services.running_services_uuids == [] - mocked_director_v2_api[ - "director_v2.api.list_dynamic_services" + mocked_dynamic_services_interface[ + "dynamic_scheduler.api.list_dynamic_services" ].side_effect = running_services.num_services - mocked_director_v2_api[ + mocked_dynamic_services_interface[ "dynamic_scheduler.api.run_dynamic_service" ].side_effect = running_services.inc_running_services @@ -437,7 +437,9 @@ def inc_running_services(self, *args, **kwargs): # noqa: ARG002 # but only the allowed number of services should have started assert ( - mocked_director_v2_api["dynamic_scheduler.api.run_dynamic_service"].call_count + mocked_dynamic_services_interface[ + "dynamic_scheduler.api.run_dynamic_service" + ].call_count == NUM_DY_SERVICES ) assert len(running_services.running_services_uuids) == NUM_DY_SERVICES @@ -471,7 +473,7 @@ async def test_create_node_does_not_start_dynamic_node_if_there_are_already_too_ client: TestClient, user_project_with_num_dynamic_services: Callable[[int], Awaitable[ProjectDict]], expected: ExpectedResponse, - mocked_director_v2_api: dict[str, mock.MagicMock], + mocked_dynamic_services_interface: dict[str, mock.MagicMock], mock_catalog_api: dict[str, mock.Mock], faker: Faker, max_amount_of_auto_started_dyn_services: int, @@ -481,7 +483,9 @@ async def test_create_node_does_not_start_dynamic_node_if_there_are_already_too_ max_amount_of_auto_started_dyn_services ) all_service_uuids = list(project["workbench"]) - mocked_director_v2_api["director_v2.api.list_dynamic_services"].return_value = [ + mocked_dynamic_services_interface[ + "dynamic_scheduler.api.list_dynamic_services" + ].return_value = [ {"service_uuid": service_uuid} for service_uuid in all_service_uuids ] url = client.app.router["create_node"].url_for(project_id=project["uuid"]) @@ -492,7 +496,7 @@ async def test_create_node_does_not_start_dynamic_node_if_there_are_already_too_ } response = await client.post(f"{ url}", json=body) await assert_status(response, expected.created) - mocked_director_v2_api[ + mocked_dynamic_services_interface[ "dynamic_scheduler.api.run_dynamic_service" ].assert_not_called() @@ -502,7 +506,7 @@ async def test_create_many_nodes_in_parallel_still_is_limited_to_the_defined_max client: TestClient, user_project_with_num_dynamic_services: Callable[[int], Awaitable[ProjectDict]], expected: ExpectedResponse, - mocked_director_v2_api: dict[str, mock.MagicMock], + mocked_dynamic_services_interface: dict[str, mock.MagicMock], mock_catalog_api: dict[str, mock.Mock], faker: Faker, max_amount_of_auto_started_dyn_services: int, @@ -538,10 +542,10 @@ async def inc_running_services(self, *args, **kwargs): # noqa: ARG002 # let's count the started services running_services = _RunninServices() assert running_services.running_services_uuids == [] - mocked_director_v2_api[ - "director_v2.api.list_dynamic_services" + mocked_dynamic_services_interface[ + "dynamic_scheduler.api.list_dynamic_services" ].side_effect = running_services.num_services - mocked_director_v2_api[ + mocked_dynamic_services_interface[ "dynamic_scheduler.api.run_dynamic_service" ].side_effect = running_services.inc_running_services @@ -561,7 +565,9 @@ async def inc_running_services(self, *args, **kwargs): # noqa: ARG002 # but only the allowed number of services should have started assert ( - mocked_director_v2_api["dynamic_scheduler.api.run_dynamic_service"].call_count + mocked_dynamic_services_interface[ + "dynamic_scheduler.api.run_dynamic_service" + ].call_count == max_amount_of_auto_started_dyn_services ) assert ( @@ -586,14 +592,16 @@ async def test_create_node_does_start_dynamic_node_if_max_num_set_to_0( client: TestClient, user_project_with_num_dynamic_services: Callable[[int], Awaitable[ProjectDict]], expected: ExpectedResponse, - mocked_director_v2_api: dict[str, mock.MagicMock], + mocked_dynamic_services_interface: dict[str, mock.MagicMock], mock_catalog_api: dict[str, mock.Mock], faker: Faker, ): assert client.app project = await user_project_with_num_dynamic_services(faker.pyint(min_value=3)) all_service_uuids = list(project["workbench"]) - mocked_director_v2_api["director_v2.api.list_dynamic_services"].return_value = [ + mocked_dynamic_services_interface[ + "dynamic_scheduler.api.list_dynamic_services" + ].return_value = [ {"service_uuid": service_uuid} for service_uuid in all_service_uuids ] url = client.app.router["create_node"].url_for(project_id=project["uuid"]) @@ -606,7 +614,7 @@ async def test_create_node_does_start_dynamic_node_if_max_num_set_to_0( } response = await client.post(f"{ url}", json=body) await assert_status(response, expected.created) - mocked_director_v2_api[ + mocked_dynamic_services_interface[ "dynamic_scheduler.api.run_dynamic_service" ].assert_called_once() @@ -621,7 +629,7 @@ async def test_creating_deprecated_node_returns_406_not_acceptable( user_project: ProjectDict, expected: ExpectedResponse, faker: Faker, - mocked_director_v2_api: dict[str, mock.MagicMock], + mocked_dynamic_services_interface: dict[str, mock.MagicMock], mock_catalog_api: dict[str, mock.Mock], node_class: str, ): @@ -642,7 +650,7 @@ async def test_creating_deprecated_node_returns_406_not_acceptable( assert error assert not data # this does not start anything in the backend since this node is deprecated - mocked_director_v2_api[ + mocked_dynamic_services_interface[ "dynamic_scheduler.api.run_dynamic_service" ].assert_not_called() @@ -660,7 +668,7 @@ async def test_delete_node( logged_user: dict, user_project: ProjectDict, expected: ExpectedResponse, - mocked_director_v2_api: dict[str, mock.MagicMock], + mocked_dynamic_services_interface: dict[str, mock.MagicMock], mock_catalog_api: dict[str, mock.Mock], storage_subsystem_mock: MockedStorageSubsystem, dy_service_running: bool, @@ -682,7 +690,7 @@ async def test_delete_node( ) for service_uuid in running_dy_services ] - # mocked_director_v2_api["director_v2.api.list_dynamic_services"].return_value = [ + # mocked_dynamic_services_interface["dynamic_scheduler.api.list_dynamic_services"].return_value = [ # {"service_uuid": service_uuid} for service_uuid in running_dy_services # ] for node_id in user_project["workbench"]: @@ -695,13 +703,15 @@ async def test_delete_node( if error: continue - mocked_director_v2_api[ - "director_v2.api.list_dynamic_services" + mocked_dynamic_services_interface[ + "dynamic_scheduler.api.list_dynamic_services" ].assert_called_once() - mocked_director_v2_api["director_v2.api.list_dynamic_services"].reset_mock() + mocked_dynamic_services_interface[ + "dynamic_scheduler.api.list_dynamic_services" + ].reset_mock() if node_id in running_dy_services: - mocked_director_v2_api[ + mocked_dynamic_services_interface[ "dynamic_scheduler.api.stop_dynamic_service" ].assert_called_once_with( mock.ANY, @@ -713,11 +723,11 @@ async def test_delete_node( save_state=False, ), ) - mocked_director_v2_api[ + mocked_dynamic_services_interface[ "dynamic_scheduler.api.stop_dynamic_service" ].reset_mock() else: - mocked_director_v2_api[ + mocked_dynamic_services_interface[ "dynamic_scheduler.api.stop_dynamic_service" ].assert_not_called() @@ -739,7 +749,7 @@ async def test_start_node( user_project_with_num_dynamic_services: Callable[[int], Awaitable[ProjectDict]], user_role: UserRole, expected: ExpectedResponse, - mocked_director_v2_api: dict[str, mock.MagicMock], + mocked_dynamic_services_interface: dict[str, mock.MagicMock], mock_catalog_api: dict[str, mock.Mock], faker: Faker, max_amount_of_auto_started_dyn_services: int, @@ -763,11 +773,11 @@ async def test_start_node( ), ) if error is None: - mocked_director_v2_api[ + mocked_dynamic_services_interface[ "dynamic_scheduler.api.run_dynamic_service" ].assert_called_once() else: - mocked_director_v2_api[ + mocked_dynamic_services_interface[ "dynamic_scheduler.api.run_dynamic_service" ].assert_not_called() @@ -778,7 +788,7 @@ async def test_start_node_raises_if_dynamic_services_limit_attained( user_project_with_num_dynamic_services: Callable[[int], Awaitable[ProjectDict]], user_role: UserRole, expected: ExpectedResponse, - mocked_director_v2_api: dict[str, mock.MagicMock], + mocked_dynamic_services_interface: dict[str, mock.MagicMock], mock_catalog_api: dict[str, mock.Mock], faker: Faker, max_amount_of_auto_started_dyn_services: int, @@ -788,7 +798,9 @@ async def test_start_node_raises_if_dynamic_services_limit_attained( max_amount_of_auto_started_dyn_services ) all_service_uuids = list(project["workbench"]) - mocked_director_v2_api["director_v2.api.list_dynamic_services"].return_value = [ + mocked_dynamic_services_interface[ + "dynamic_scheduler.api.list_dynamic_services" + ].return_value = [ {"service_uuid": service_uuid} for service_uuid in all_service_uuids ] # start the node, shall work as expected @@ -802,7 +814,7 @@ async def test_start_node_raises_if_dynamic_services_limit_attained( ) assert not data assert error - mocked_director_v2_api[ + mocked_dynamic_services_interface[ "dynamic_scheduler.api.run_dynamic_service" ].assert_not_called() @@ -814,14 +826,16 @@ async def test_start_node_starts_dynamic_service_if_max_number_of_services_set_t user_project_with_num_dynamic_services: Callable[[int], Awaitable[ProjectDict]], user_role: UserRole, expected: ExpectedResponse, - mocked_director_v2_api: dict[str, mock.MagicMock], + mocked_dynamic_services_interface: dict[str, mock.MagicMock], mock_catalog_api: dict[str, mock.Mock], faker: Faker, ): assert client.app project = await user_project_with_num_dynamic_services(faker.pyint(min_value=3)) all_service_uuids = list(project["workbench"]) - mocked_director_v2_api["director_v2.api.list_dynamic_services"].return_value = [ + mocked_dynamic_services_interface[ + "dynamic_scheduler.api.list_dynamic_services" + ].return_value = [ {"service_uuid": service_uuid} for service_uuid in all_service_uuids ] # start the node, shall work as expected @@ -835,7 +849,7 @@ async def test_start_node_starts_dynamic_service_if_max_number_of_services_set_t ) assert not data assert not error - mocked_director_v2_api[ + mocked_dynamic_services_interface[ "dynamic_scheduler.api.run_dynamic_service" ].assert_called_once() @@ -846,7 +860,7 @@ async def test_start_node_raises_if_called_with_wrong_data( user_project_with_num_dynamic_services: Callable[[int], Awaitable[ProjectDict]], user_role: UserRole, expected: ExpectedResponse, - mocked_director_v2_api: dict[str, mock.MagicMock], + mocked_dynamic_services_interface: dict[str, mock.MagicMock], mock_catalog_api: dict[str, mock.Mock], faker: Faker, max_amount_of_auto_started_dyn_services: int, @@ -868,7 +882,7 @@ async def test_start_node_raises_if_called_with_wrong_data( ) assert not data assert error - mocked_director_v2_api[ + mocked_dynamic_services_interface[ "dynamic_scheduler.api.run_dynamic_service" ].assert_not_called() @@ -883,7 +897,7 @@ async def test_start_node_raises_if_called_with_wrong_data( ) assert not data assert error - mocked_director_v2_api[ + mocked_dynamic_services_interface[ "dynamic_scheduler.api.run_dynamic_service" ].assert_not_called() @@ -894,7 +908,7 @@ async def test_stop_node( user_project_with_num_dynamic_services: Callable[[int], Awaitable[ProjectDict]], user_role: UserRole, expected: ExpectedResponse, - mocked_director_v2_api: dict[str, mock.MagicMock], + mocked_dynamic_services_interface: dict[str, mock.MagicMock], mock_catalog_api: dict[str, mock.Mock], faker: Faker, max_amount_of_auto_started_dyn_services: int, @@ -914,11 +928,11 @@ async def test_stop_node( status.HTTP_202_ACCEPTED if user_role == UserRole.GUEST else expected.accepted, ) if error is None: - mocked_director_v2_api[ + mocked_dynamic_services_interface[ "dynamic_scheduler.api.stop_dynamic_service" ].assert_called_once() else: - mocked_director_v2_api[ + mocked_dynamic_services_interface[ "dynamic_scheduler.api.stop_dynamic_service" ].assert_not_called() diff --git a/services/web/server/tests/unit/with_dbs/02/test_projects_states_handlers.py b/services/web/server/tests/unit/with_dbs/02/test_projects_states_handlers.py index 901a597da40..15af4778e85 100644 --- a/services/web/server/tests/unit/with_dbs/02/test_projects_states_handlers.py +++ b/services/web/server/tests/unit/with_dbs/02/test_projects_states_handlers.py @@ -266,7 +266,7 @@ async def test_share_project( user_role: UserRole, expected: ExpectedResponse, storage_subsystem_mock, - mocked_director_v2_api: dict[str, mock.Mock], + mocked_dynamic_services_interface: dict[str, mock.Mock], catalog_subsystem_mock: Callable[[list[ProjectDict]], None], share_rights: dict, project_db_cleaner, @@ -344,7 +344,7 @@ async def test_open_project( client_session_id_factory: Callable[[], str], expected: HTTPStatus, save_state: bool, - mocked_director_v2_api: dict[str, mock.Mock], + mocked_dynamic_services_interface: dict[str, mock.Mock], mock_service_resources: ServiceResourcesDict, mock_orphaned_services: mock.Mock, mock_catalog_api: dict[str, mock.Mock], @@ -397,7 +397,7 @@ async def test_open_project( ), ) ) - mocked_director_v2_api[ + mocked_dynamic_services_interface[ "dynamic_scheduler.api.run_dynamic_service" ].assert_has_calls(calls) else: @@ -420,7 +420,7 @@ async def test_open_template_project_for_edition( client_session_id_factory: Callable[[], str], expected: HTTPStatus, save_state: bool, - mocked_director_v2_api: dict[str, mock.Mock], + mocked_dynamic_services_interface: dict[str, mock.Mock], mock_service_resources: ServiceResourcesDict, mock_orphaned_services: mock.Mock, mock_catalog_api: dict[str, mock.Mock], @@ -476,7 +476,7 @@ async def test_open_template_project_for_edition( ), ) ) - mocked_director_v2_api[ + mocked_dynamic_services_interface[ "dynamic_scheduler.api.run_dynamic_service" ].assert_has_calls(calls) else: @@ -498,7 +498,7 @@ async def test_open_template_project_for_edition_with_missing_write_rights( create_template_project: Callable[..., Awaitable[ProjectDict]], client_session_id_factory: Callable[[], str], expected: HTTPStatus, - mocked_director_v2_api: dict[str, mock.Mock], + mocked_dynamic_services_interface: dict[str, mock.Mock], mock_service_resources: ServiceResourcesDict, mock_orphaned_services: mock.Mock, mock_catalog_api: dict[str, mock.Mock], @@ -530,7 +530,7 @@ async def test_open_project_with_small_amount_of_dynamic_services_starts_them_au user_project_with_num_dynamic_services: Callable[[int], Awaitable[ProjectDict]], client_session_id_factory: Callable, expected: ExpectedResponse, - mocked_director_v2_api: dict[str, mock.Mock], + mocked_dynamic_services_interface: dict[str, mock.Mock], mock_catalog_api: dict[str, mock.Mock], max_amount_of_auto_started_dyn_services: int, faker: Faker, @@ -561,10 +561,12 @@ async def test_open_project_with_small_amount_of_dynamic_services_starts_them_au client.app, ProjectID(project["uuid"]) ) mocked_notifications_plugin["subscribe"].reset_mock() - assert mocked_director_v2_api[ + assert mocked_dynamic_services_interface[ "dynamic_scheduler.api.run_dynamic_service" ].call_count == (num_of_dyn_services - num_service_already_running) - mocked_director_v2_api["dynamic_scheduler.api.run_dynamic_service"].reset_mock() + mocked_dynamic_services_interface[ + "dynamic_scheduler.api.run_dynamic_service" + ].reset_mock() @pytest.mark.parametrize(*standard_user_role()) @@ -574,7 +576,7 @@ async def test_open_project_with_disable_service_auto_start_set_overrides_behavi user_project_with_num_dynamic_services: Callable[[int], Awaitable[ProjectDict]], client_session_id_factory: Callable, expected: ExpectedResponse, - mocked_director_v2_api: dict[str, mock.Mock], + mocked_dynamic_services_interface: dict[str, mock.Mock], mock_catalog_api: dict[str, mock.Mock], max_amount_of_auto_started_dyn_services: int, faker: Faker, @@ -587,7 +589,9 @@ async def test_open_project_with_disable_service_auto_start_set_overrides_behavi project = await user_project_with_num_dynamic_services(num_of_dyn_services) all_service_uuids = list(project["workbench"]) for num_service_already_running in range(num_of_dyn_services): - mocked_director_v2_api["director_v2.api.list_dynamic_services"].return_value = [ + mocked_dynamic_services_interface[ + "dynamic_scheduler.api.list_dynamic_services" + ].return_value = [ {"service_uuid": all_service_uuids[service_id]} for service_id in range(num_service_already_running) ] @@ -604,7 +608,7 @@ async def test_open_project_with_disable_service_auto_start_set_overrides_behavi client.app, ProjectID(project["uuid"]) ) mocked_notifications_plugin["subscribe"].reset_mock() - mocked_director_v2_api[ + mocked_dynamic_services_interface[ "dynamic_scheduler.api.run_dynamic_service" ].assert_not_called() @@ -616,7 +620,7 @@ async def test_open_project_with_large_amount_of_dynamic_services_does_not_start user_project_with_num_dynamic_services: Callable[[int], Awaitable[ProjectDict]], client_session_id_factory: Callable, expected: ExpectedResponse, - mocked_director_v2_api: dict[str, mock.Mock], + mocked_dynamic_services_interface: dict[str, mock.Mock], mock_catalog_api: dict[str, mock.Mock], max_amount_of_auto_started_dyn_services: int, mocked_notifications_plugin: dict[str, mock.Mock], @@ -649,7 +653,7 @@ async def test_open_project_with_large_amount_of_dynamic_services_does_not_start client.app, ProjectID(project["uuid"]) ) mocked_notifications_plugin["subscribe"].reset_mock() - mocked_director_v2_api[ + mocked_dynamic_services_interface[ "dynamic_scheduler.api.run_dynamic_service" ].assert_not_called() @@ -663,7 +667,7 @@ async def test_open_project_with_large_amount_of_dynamic_services_starts_them_if user_project_with_num_dynamic_services: Callable[[int], Awaitable[ProjectDict]], client_session_id_factory: Callable, expected: ExpectedResponse, - mocked_director_v2_api: dict[str, mock.Mock], + mocked_dynamic_services_interface: dict[str, mock.Mock], mock_catalog_api: dict[str, mock.Mock], max_amount_of_auto_started_dyn_services: int, faker: Faker, @@ -699,7 +703,9 @@ async def test_open_project_with_large_amount_of_dynamic_services_starts_them_if client.app, ProjectID(project["uuid"]) ) mocked_notifications_plugin["subscribe"].reset_mock() - mocked_director_v2_api["dynamic_scheduler.api.run_dynamic_service"].assert_called() + mocked_dynamic_services_interface[ + "dynamic_scheduler.api.run_dynamic_service" + ].assert_called() @pytest.mark.parametrize(*standard_user_role()) @@ -709,7 +715,7 @@ async def test_open_project_with_deprecated_services_ok_but_does_not_start_dynam user_project, client_session_id_factory: Callable, expected: ExpectedResponse, - mocked_director_v2_api: dict[str, mock.Mock], + mocked_dynamic_services_interface: dict[str, mock.Mock], mock_service_resources: ServiceResourcesDict, mock_orphaned_services, mock_catalog_api: dict[str, mock.Mock], @@ -724,7 +730,7 @@ async def test_open_project_with_deprecated_services_ok_but_does_not_start_dynam mocked_notifications_plugin["subscribe"].assert_called_once_with( client.app, ProjectID(user_project["uuid"]) ) - mocked_director_v2_api[ + mocked_dynamic_services_interface[ "dynamic_scheduler.api.run_dynamic_service" ].assert_not_called() @@ -763,7 +769,7 @@ async def test_open_project_more_than_limitation_of_max_studies_open_per_user( user_project: ProjectDict, shared_project: ProjectDict, expected: ExpectedResponse, - mocked_director_v2_api: dict[str, mock.Mock], + mocked_dynamic_services_interface: dict[str, mock.Mock], mock_catalog_api: dict[str, mock.Mock], user_role: UserRole, mocked_notifications_plugin: dict[str, mock.Mock], @@ -792,7 +798,7 @@ async def test_close_project( user_project: ProjectDict, client_session_id_factory: Callable, expected, - mocked_director_v2_api: dict[str, mock.Mock], + mocked_dynamic_services_interface: dict[str, mock.Mock], mock_catalog_api: dict[str, mock.Mock], fake_services: Callable[..., Awaitable[list[DynamicServiceGet]]], mock_dynamic_scheduler_rabbitmq: None, @@ -801,7 +807,7 @@ async def test_close_project( # POST /v0/projects/{project_id}:close fake_dynamic_services = await fake_services(number_services=5) assert len(fake_dynamic_services) == 5 - mocked_director_v2_api[ + mocked_dynamic_services_interface[ "dynamic_scheduler.api.list_dynamic_services" ].return_value = fake_dynamic_services @@ -817,10 +823,14 @@ async def test_close_project( mocked_notifications_plugin["subscribe"].assert_called_once_with( client.app, ProjectID(user_project["uuid"]) ) - mocked_director_v2_api["director_v2.api.list_dynamic_services"].assert_any_call( - client.app, user_id, user_project["uuid"] + mocked_dynamic_services_interface[ + "dynamic_scheduler.api.list_dynamic_services" + ].assert_any_call( + client.app, user_id=user_id, project_id=ProjectID(user_project["uuid"]) ) - mocked_director_v2_api["director_v2.api.list_dynamic_services"].reset_mock() + mocked_dynamic_services_interface[ + "dynamic_scheduler.api.list_dynamic_services" + ].reset_mock() else: mocked_notifications_plugin["subscribe"].assert_not_called() @@ -840,10 +850,10 @@ async def test_close_project( call( client.app, user_id=user_id, - project_id=user_project["uuid"], + project_id=ProjectID(user_project["uuid"]), ), ] - mocked_director_v2_api[ + mocked_dynamic_services_interface[ "dynamic_scheduler.api.list_dynamic_services" ].assert_has_calls(calls) @@ -861,7 +871,7 @@ async def test_close_project( ) for service in fake_dynamic_services ] - mocked_director_v2_api[ + mocked_dynamic_services_interface[ "dynamic_scheduler.api.stop_dynamic_service" ].assert_has_calls(calls) @@ -884,7 +894,7 @@ async def test_get_active_project( client_session_id_factory: Callable, expected, socketio_client_factory: Callable, - mocked_director_v2_api: dict[str, mock.Mock], + mocked_dynamic_services_interface: dict[str, mock.Mock], mock_catalog_api: dict[str, mock.Mock], mocked_notifications_plugin: dict[str, mock.Mock], ): @@ -980,7 +990,7 @@ async def test_project_node_lifetime( # noqa: PLR0915 expected_response_on_create, expected_response_on_get, expected_response_on_delete, - mocked_director_v2_api: dict[str, mock.Mock], + mocked_dynamic_services_interface: dict[str, mock.Mock], storage_subsystem_mock, mock_catalog_api: dict[str, mock.Mock], mocker, @@ -1004,18 +1014,20 @@ async def test_project_node_lifetime( # noqa: PLR0915 data, errors = await assert_status(resp, expected_response_on_create) dynamic_node_id = None if resp.status == status.HTTP_201_CREATED: - mocked_director_v2_api[ + mocked_dynamic_services_interface[ "dynamic_scheduler.api.run_dynamic_service" ].assert_called_once() assert "node_id" in data dynamic_node_id = data["node_id"] else: - mocked_director_v2_api[ + mocked_dynamic_services_interface[ "dynamic_scheduler.api.run_dynamic_service" ].assert_not_called() # create a new NOT dynamic node... - mocked_director_v2_api["dynamic_scheduler.api.run_dynamic_service"].reset_mock() + mocked_dynamic_services_interface[ + "dynamic_scheduler.api.run_dynamic_service" + ].reset_mock() url = client.app.router["create_node"].url_for(project_id=user_project["uuid"]) body = { "service_key": "simcore/services/comp/key", @@ -1026,13 +1038,13 @@ async def test_project_node_lifetime( # noqa: PLR0915 data, errors = await assert_status(resp, expected_response_on_create) computational_node_id = None if resp.status == status.HTTP_201_CREATED: - mocked_director_v2_api[ + mocked_dynamic_services_interface[ "dynamic_scheduler.api.run_dynamic_service" ].assert_not_called() assert "node_id" in data computational_node_id = data["node_id"] else: - mocked_director_v2_api[ + mocked_dynamic_services_interface[ "dynamic_scheduler.api.run_dynamic_service" ].assert_not_called() @@ -1049,7 +1061,7 @@ async def test_project_node_lifetime( # noqa: PLR0915 ) node_sample = deepcopy(NodeGet.model_config["json_schema_extra"]["examples"][1]) - mocked_director_v2_api[ + mocked_dynamic_services_interface[ "dynamic_scheduler.api.get_dynamic_service" ].return_value = NodeGet.model_validate( { @@ -1068,7 +1080,7 @@ async def test_project_node_lifetime( # noqa: PLR0915 url = client.app.router["get_node"].url_for( project_id=user_project["uuid"], node_id=computational_node_id ) - mocked_director_v2_api[ + mocked_dynamic_services_interface[ "dynamic_scheduler.api.get_dynamic_service" ].return_value = NodeGetIdle.model_validate( { @@ -1090,18 +1102,20 @@ async def test_project_node_lifetime( # noqa: PLR0915 data, errors = await assert_status(resp, expected_response_on_delete) await asyncio.sleep(5) if resp.status == status.HTTP_204_NO_CONTENT: - mocked_director_v2_api[ + mocked_dynamic_services_interface[ "dynamic_scheduler.api.stop_dynamic_service" ].assert_called_once() mock_storage_api_delete_data_folders_of_project_node.assert_called_once() else: - mocked_director_v2_api[ + mocked_dynamic_services_interface[ "dynamic_scheduler.api.stop_dynamic_service" ].assert_not_called() mock_storage_api_delete_data_folders_of_project_node.assert_not_called() # delete the NOT dynamic node - mocked_director_v2_api["dynamic_scheduler.api.stop_dynamic_service"].reset_mock() + mocked_dynamic_services_interface[ + "dynamic_scheduler.api.stop_dynamic_service" + ].reset_mock() mock_storage_api_delete_data_folders_of_project_node.reset_mock() url = client.app.router["delete_node"].url_for( project_id=user_project["uuid"], node_id=computational_node_id @@ -1109,12 +1123,12 @@ async def test_project_node_lifetime( # noqa: PLR0915 resp = await client.delete(f"{url}") data, errors = await assert_status(resp, expected_response_on_delete) if resp.status == status.HTTP_204_NO_CONTENT: - mocked_director_v2_api[ + mocked_dynamic_services_interface[ "dynamic_scheduler.api.stop_dynamic_service" ].assert_not_called() mock_storage_api_delete_data_folders_of_project_node.assert_called_once() else: - mocked_director_v2_api[ + mocked_dynamic_services_interface[ "dynamic_scheduler.api.stop_dynamic_service" ].assert_not_called() mock_storage_api_delete_data_folders_of_project_node.assert_not_called() @@ -1175,7 +1189,7 @@ async def test_open_shared_project_2_users_locked( user_role: UserRole, expected: ExpectedResponse, mocker, - mocked_director_v2_api: dict[str, mock.Mock], + mocked_dynamic_services_interface: dict[str, mock.Mock], mock_orphaned_services, mock_catalog_api: dict[str, mock.Mock], clean_redis_table: None, @@ -1357,7 +1371,7 @@ async def test_open_shared_project_at_same_time( client_session_id_factory: Callable, user_role: UserRole, expected: ExpectedResponse, - mocked_director_v2_api: dict[str, mock.Mock], + mocked_dynamic_services_interface: dict[str, mock.Mock], mock_orphaned_services, mock_catalog_api: dict[str, mock.Mock], clean_redis_table, @@ -1447,7 +1461,7 @@ async def test_opened_project_can_still_be_opened_after_refreshing_tab( socketio_client_factory: Callable, user_role: UserRole, expected: ExpectedResponse, - mocked_director_v2_api: dict[str, mock.MagicMock], + mocked_dynamic_services_interface: dict[str, mock.MagicMock], mock_orphaned_services, mock_catalog_api: dict[str, mock.Mock], clean_redis_table, diff --git a/services/web/server/tests/unit/with_dbs/03/tags/conftest.py b/services/web/server/tests/unit/with_dbs/03/tags/conftest.py index ab1556b7e9d..45682405bfe 100644 --- a/services/web/server/tests/unit/with_dbs/03/tags/conftest.py +++ b/services/web/server/tests/unit/with_dbs/03/tags/conftest.py @@ -40,7 +40,7 @@ def client( aiohttp_client, app_cfg, postgres_db, - mocked_director_v2_api, + mocked_dynamic_services_interface, mock_orphaned_services, redis_client, # this ensure redis is properly cleaned monkeypatch_setenv_from_app_config: Callable, diff --git a/services/web/server/tests/unit/with_dbs/03/test_trash.py b/services/web/server/tests/unit/with_dbs/03/test_trash.py index 76f4aefb46b..6ab597e3972 100644 --- a/services/web/server/tests/unit/with_dbs/03/test_trash.py +++ b/services/web/server/tests/unit/with_dbs/03/test_trash.py @@ -8,6 +8,7 @@ import asyncio from collections.abc import AsyncIterable, Callable +from unittest.mock import MagicMock from uuid import UUID import arrow @@ -88,7 +89,7 @@ async def test_trash_projects( # noqa: PLR0915 autospec=True, ) mocker.patch( - "simcore_service_webserver.projects._trash_api.director_v2_api.list_dynamic_services", + "simcore_service_webserver.projects._trash_api.dynamic_scheduler_api.list_dynamic_services", return_value=[mocker.MagicMock()] if is_project_running else [], autospec=True, ) @@ -125,9 +126,11 @@ async def test_trash_projects( # noqa: PLR0915 ) _, error = await assert_status( resp, - status.HTTP_409_CONFLICT - if (is_project_running and not force) - else status.HTTP_204_NO_CONTENT, + ( + status.HTTP_409_CONFLICT + if (is_project_running and not force) + else status.HTTP_204_NO_CONTENT + ), ) could_not_trash = is_project_running and not force @@ -182,6 +185,7 @@ async def test_trash_projects( # noqa: PLR0915 "For https://github.com/ITISFoundation/osparc-simcore/pull/6642" ) async def test_trash_single_folder(client: TestClient, logged_user: UserInfoDict): + assert client.app # CREATE a folder @@ -263,7 +267,7 @@ async def test_trash_folder_with_content( logged_user: UserInfoDict, user_project: ProjectDict, mocked_catalog: None, - mocked_director_v2: None, + mocked_dynamic_services_interface: dict[str, MagicMock], ): assert client.app project_uuid = UUID(user_project["uuid"]) diff --git a/services/web/server/tests/unit/with_dbs/03/version_control/conftest.py b/services/web/server/tests/unit/with_dbs/03/version_control/conftest.py index 6903e2e1ef5..e9a6244886c 100644 --- a/services/web/server/tests/unit/with_dbs/03/version_control/conftest.py +++ b/services/web/server/tests/unit/with_dbs/03/version_control/conftest.py @@ -189,7 +189,7 @@ def request_update_project( return_value=ServiceResourcesDict(), ) mocker.patch( - "simcore_service_webserver.director_v2.api.list_dynamic_services", + "simcore_service_webserver.dynamic_scheduler.api.list_dynamic_services", return_value=[], ) diff --git a/services/web/server/tests/unit/with_dbs/04/garbage_collector/test_resource_manager.py b/services/web/server/tests/unit/with_dbs/04/garbage_collector/test_resource_manager.py index d1d6bcc73ee..962e8539d04 100644 --- a/services/web/server/tests/unit/with_dbs/04/garbage_collector/test_resource_manager.py +++ b/services/web/server/tests/unit/with_dbs/04/garbage_collector/test_resource_manager.py @@ -478,7 +478,7 @@ async def test_interactive_services_removed_after_logout( client: TestClient, logged_user: dict[str, Any], empty_user_project: dict[str, Any], - mocked_director_v2_api: dict[str, mock.MagicMock], + mocked_dynamic_services_interface: dict[str, mock.MagicMock], create_dynamic_service_mock: Callable[..., Awaitable[DynamicServiceGet]], client_session_id_factory: Callable[[], str], socketio_client_factory: Callable, @@ -517,7 +517,7 @@ async def test_interactive_services_removed_after_logout( print( f"--> Waiting for stop_dynamic_service with: {service.node_uuid}, {expected_save_state=}", ) - mocked_director_v2_api[ + mocked_dynamic_services_interface[ "dynamic_scheduler.api.stop_dynamic_service" ].assert_awaited_with( app=client.app, @@ -544,7 +544,7 @@ async def test_interactive_services_remain_after_websocket_reconnection_from_2_t client: TestClient, logged_user: UserInfoDict, empty_user_project, - mocked_director_v2_api, + mocked_dynamic_services_interface, create_dynamic_service_mock: Callable[..., Awaitable[DynamicServiceGet]], socketio_client_factory: Callable, client_session_id_factory: Callable[[], str], @@ -609,14 +609,14 @@ async def test_interactive_services_remain_after_websocket_reconnection_from_2_t await gc_core.collect_garbage(client.app) # assert dynamic service is still around - mocked_director_v2_api[ + mocked_dynamic_services_interface[ "dynamic_scheduler.api.stop_dynamic_service" ].assert_not_called() # disconnect second websocket await sio2.disconnect() assert not sio2.sid # assert dynamic service is still around for now - mocked_director_v2_api[ + mocked_dynamic_services_interface[ "dynamic_scheduler.api.stop_dynamic_service" ].assert_not_called() # reconnect websocket @@ -625,7 +625,7 @@ async def test_interactive_services_remain_after_websocket_reconnection_from_2_t await asyncio.sleep(SERVICE_DELETION_DELAY + 1) await gc_core.collect_garbage(client.app) - mocked_director_v2_api[ + mocked_dynamic_services_interface[ "dynamic_scheduler.api.stop_dynamic_service" ].assert_not_called() # now really disconnect @@ -652,7 +652,7 @@ async def test_interactive_services_remain_after_websocket_reconnection_from_2_t progress=mock.ANY, ) ] - mocked_director_v2_api[ + mocked_dynamic_services_interface[ "dynamic_scheduler.api.stop_dynamic_service" ].assert_has_calls(calls) @@ -682,7 +682,7 @@ async def test_interactive_services_removed_per_project( logged_user, empty_user_project, empty_user_project2, - mocked_director_v2_api, + mocked_dynamic_services_interface, create_dynamic_service_mock: Callable[..., Awaitable[DynamicServiceGet]], mocked_notification_system, socketio_client_factory: Callable, @@ -716,7 +716,7 @@ async def test_interactive_services_removed_per_project( await sio1.disconnect() assert not sio1.sid # assert dynamic service is still around - mocked_director_v2_api[ + mocked_dynamic_services_interface[ "dynamic_scheduler.api.stop_dynamic_service" ].assert_not_called() # wait the defined delay @@ -736,16 +736,18 @@ async def test_interactive_services_removed_per_project( progress=mock.ANY, ) ] - mocked_director_v2_api[ + mocked_dynamic_services_interface[ "dynamic_scheduler.api.stop_dynamic_service" ].assert_has_calls(calls) - mocked_director_v2_api["dynamic_scheduler.api.stop_dynamic_service"].reset_mock() + mocked_dynamic_services_interface[ + "dynamic_scheduler.api.stop_dynamic_service" + ].reset_mock() # disconnect websocket2 await sio2.disconnect() assert not sio2.sid # assert dynamic services are still around - mocked_director_v2_api[ + mocked_dynamic_services_interface[ "dynamic_scheduler.api.stop_dynamic_service" ].assert_not_called() # wait the defined delay @@ -776,10 +778,12 @@ async def test_interactive_services_removed_per_project( progress=mock.ANY, ), ] - mocked_director_v2_api[ + mocked_dynamic_services_interface[ "dynamic_scheduler.api.stop_dynamic_service" ].assert_has_calls(calls) - mocked_director_v2_api["dynamic_scheduler.api.stop_dynamic_service"].reset_mock() + mocked_dynamic_services_interface[ + "dynamic_scheduler.api.stop_dynamic_service" + ].reset_mock() @pytest.mark.xfail( @@ -799,7 +803,7 @@ async def test_services_remain_after_closing_one_out_of_two_tabs( logged_user, empty_user_project, empty_user_project2, - mocked_director_v2_api, + mocked_dynamic_services_interface, create_dynamic_service_mock: Callable[..., Awaitable[DynamicServiceGet]], socketio_client_factory: Callable, client_session_id_factory: Callable[[], str], @@ -826,7 +830,7 @@ async def test_services_remain_after_closing_one_out_of_two_tabs( await asyncio.sleep(SERVICE_DELETION_DELAY + 1) await gc_core.collect_garbage(client.app) # assert dynamic service is still around - mocked_director_v2_api[ + mocked_dynamic_services_interface[ "dynamic_scheduler.api.stop_dynamic_service" ].assert_not_called() # close project in tab2 @@ -834,7 +838,7 @@ async def test_services_remain_after_closing_one_out_of_two_tabs( # wait the defined delay await asyncio.sleep(SERVICE_DELETION_DELAY + 1) await gc_core.collect_garbage(client.app) - mocked_director_v2_api[ + mocked_dynamic_services_interface[ "dynamic_scheduler.api.stop_dynamic_service" ].assert_has_calls( [call(client.server.app, service.node_uuid, expected_save_state)] @@ -853,7 +857,7 @@ async def test_websocket_disconnected_remove_or_maintain_files_based_on_role( client, logged_user, empty_user_project, - mocked_director_v2_api, + mocked_dynamic_services_interface, create_dynamic_service_mock: Callable[..., Awaitable[DynamicServiceGet]], client_session_id_factory: Callable[[], str], socketio_client_factory: Callable, @@ -898,7 +902,7 @@ async def test_websocket_disconnected_remove_or_maintain_files_based_on_role( progress=mock.ANY, ) ] - mocked_director_v2_api[ + mocked_dynamic_services_interface[ "dynamic_scheduler.api.stop_dynamic_service" ].assert_has_calls(calls) diff --git a/services/web/server/tests/unit/with_dbs/conftest.py b/services/web/server/tests/unit/with_dbs/conftest.py index eb24a2d1174..37217d58519 100644 --- a/services/web/server/tests/unit/with_dbs/conftest.py +++ b/services/web/server/tests/unit/with_dbs/conftest.py @@ -19,7 +19,7 @@ from copy import deepcopy from decimal import Decimal from pathlib import Path -from typing import Any, Final +from typing import Any from unittest import mock from unittest.mock import AsyncMock, MagicMock @@ -396,33 +396,14 @@ def asyncpg_storage_system_mock(mocker): ) -_LIST_DYNAMIC_SERVICES_MODULES_TO_PATCH: Final[list[str]] = [ - "director_v2.api", - "director_v2._core_dynamic_services", - "dynamic_scheduler.api", -] - - @pytest.fixture -async def mocked_director_v2_api(mocker: MockerFixture) -> dict[str, MagicMock]: +async def mocked_dynamic_services_interface( + mocker: MockerFixture, +) -> dict[str, MagicMock]: mock = {} - # - # NOTE: depending on the test, function might have to be patched - # via the director_v2_api or director_v2_core_dynamic_services modules - # - for mod_name in _LIST_DYNAMIC_SERVICES_MODULES_TO_PATCH: - name = f"{mod_name}.list_dynamic_services" - mock[name] = mocker.patch( - f"simcore_service_webserver.{name}", - autospec=True, - return_value=[], - ) - # add here redirects from director-v2 via dynamic-scheduler - # NOTE: once all above are moved to dynamic-scheduler - # this fixture needs to be renamed to mocked_dynamic_scheduler - for func_name in ( + "list_dynamic_services", "get_dynamic_service", "run_dynamic_service", "stop_dynamic_service", @@ -444,7 +425,7 @@ async def mocked_director_v2_api(mocker: MockerFixture) -> dict[str, MagicMock]: @pytest.fixture def create_dynamic_service_mock( - client: TestClient, mocked_director_v2_api: dict, faker: Faker + client: TestClient, mocked_dynamic_services_interface: dict, faker: Faker ) -> Callable[..., Awaitable[DynamicServiceGet]]: services = [] @@ -469,10 +450,9 @@ async def _create(**service_override_kwargs) -> DynamicServiceGet: services.append(running_service) # reset the future or an invalidStateError will appear as set_result sets the future to done - for module_name in _LIST_DYNAMIC_SERVICES_MODULES_TO_PATCH: - mocked_director_v2_api[ - f"{module_name}.list_dynamic_services" - ].return_value = services + mocked_dynamic_services_interface[ + "dynamic_scheduler.api.list_dynamic_services" + ].return_value = services return running_service