Skip to content

Commit

Permalink
♻️ Redirecting labels request via catalog in director-v2 (ITISFoundat…
Browse files Browse the repository at this point in the history
…ion#7016)

Co-authored-by: Andrei Neagu <[email protected]>
  • Loading branch information
GitHK and Andrei Neagu authored Jan 10, 2025
1 parent 0963820 commit a8ca793
Show file tree
Hide file tree
Showing 27 changed files with 303 additions and 219 deletions.
2 changes: 1 addition & 1 deletion services/catalog/VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
0.6.0
0.7.0
56 changes: 55 additions & 1 deletion services/catalog/openapi.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"info": {
"title": "simcore-service-catalog",
"description": "Manages and maintains a catalog of all published components (e.g. macro-algorithms, scripts, etc)",
"version": "0.6.0"
"version": "0.7.0"
},
"paths": {
"/": {
Expand Down Expand Up @@ -136,6 +136,60 @@
}
}
},
"/v0/services/{service_key}/{service_version}/labels": {
"get": {
"tags": [
"services"
],
"summary": "Get Service Labels",
"operationId": "get_service_labels_v0_services__service_key___service_version__labels_get",
"parameters": [
{
"name": "service_key",
"in": "path",
"required": true,
"schema": {
"type": "string",
"pattern": "^simcore/services/((comp|dynamic|frontend))/([a-z0-9][a-z0-9_.-]*/)*([a-z0-9-_]+[a-z0-9])$",
"title": "Service Key"
}
},
{
"name": "service_version",
"in": "path",
"required": true,
"schema": {
"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"
}
}
],
"responses": {
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {
"type": "object",
"title": "Response Get Service Labels V0 Services Service Key Service Version Labels Get"
}
}
}
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
}
}
}
}
},
"/v0/services/{service_key}/{service_version}/specifications": {
"get": {
"tags": [
Expand Down
2 changes: 1 addition & 1 deletion services/catalog/setup.cfg
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[bumpversion]
current_version = 0.6.0
current_version = 0.7.0
commit = True
message = services/catalog version: {current_version} → {new_version}
tag = False
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
from typing import Annotated, Any

from fastapi import APIRouter, Depends
from models_library.services import ServiceKey, ServiceVersion

from ...services.director import DirectorApi
from ..dependencies.director import get_director_api

router = APIRouter()


@router.get("/{service_key:path}/{service_version}/labels")
async def get_service_labels(
service_key: ServiceKey,
service_version: ServiceVersion,
director_client: Annotated[DirectorApi, Depends(get_director_api)],
) -> dict[str, Any]:
return await director_client.get_service_labels(service_key, service_version)
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import logging
import urllib.parse
from copy import deepcopy
from typing import Annotated, Any, Final, cast
from typing import Annotated, Any, Final

import yaml
from fastapi import APIRouter, Depends, HTTPException, status
Expand Down Expand Up @@ -131,12 +131,7 @@ async def _get_service_labels(
director_client: DirectorApi, key: ServiceKey, version: ServiceVersion
) -> dict[str, Any] | None:
try:
service_labels = cast(
dict[str, Any],
await director_client.get(
f"/services/{urllib.parse.quote_plus(key)}/{version}/labels"
),
)
service_labels = await director_client.get_service_labels(key, version)
_logger.debug(
"received for %s %s",
f"/services/{urllib.parse.quote_plus(key)}/{version}/labels",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
_meta,
_services,
_services_access_rights,
_services_labels,
_services_ports,
_services_resources,
_services_specifications,
Expand Down Expand Up @@ -38,6 +39,11 @@
tags=_SERVICE_TAGS,
prefix=_SERVICE_PREFIX,
)
v0_router.include_router(
_services_labels.router,
tags=_SERVICE_TAGS,
prefix=_SERVICE_PREFIX,
)
v0_router.include_router(
_services_specifications.router,
tags=_SERVICE_TAGS,
Expand Down
11 changes: 11 additions & 0 deletions services/catalog/src/simcore_service_catalog/services/director.py
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,17 @@ async def get_service(
assert len(data) == 1 # nosec
return ServiceMetaDataPublished.model_validate(data[0])

async def get_service_labels(
self,
service_key: ServiceKey,
service_version: ServiceVersion,
) -> dict[str, Any]:
response = await self.get(
f"/services/{urllib.parse.quote_plus(service_key)}/{service_version}/labels"
)
assert isinstance(response, dict) # nosec
return response


async def setup_director(
app: FastAPI, tracing_settings: TracingSettings | None
Expand Down
55 changes: 31 additions & 24 deletions services/catalog/tests/unit/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -393,11 +393,41 @@ def mocked_director_service_api_base(
yield respx_mock


@pytest.fixture
def get_mocked_service_labels() -> Callable[[str, str], dict]:
def _(service_key: str, service_version: str) -> dict:
return {
"io.simcore.authors": '{"authors": [{"name": "John Smith", "email": "[email protected]", "affiliation": "ACME\'IS Foundation"}]}',
"io.simcore.contact": '{"contact": "[email protected]"}',
"io.simcore.description": '{"description": "Autonomous Nervous System Network model"}',
"io.simcore.inputs": '{"inputs": {"input_1": {"displayOrder": 1.0, "label": "Simulation time", "description": "Duration of the simulation", "type": "ref_contentSchema", "contentSchema": {"type": "number", "x_unit": "milli-second"}, "defaultValue": 2.0}}}',
"io.simcore.integration-version": '{"integration-version": "1.0.0"}',
"io.simcore.key": '{"key": "xxxxx"}'.replace("xxxxx", service_key),
"io.simcore.name": '{"name": "Autonomous Nervous System Network model"}',
"io.simcore.outputs": '{"outputs": {"output_1": {"displayOrder": 1.0, "label": "ANS output", "description": "Output of simulation of Autonomous Nervous System Network model", "type": "data:*/*", "fileToKeyMap": {"ANS_output.txt": "output_1"}}, "output_2": {"displayOrder": 2.0, "label": "Stimulation parameters", "description": "stim_param.txt file containing the input provided in the inputs port", "type": "data:*/*", "fileToKeyMap": {"ANS_stim_param.txt": "output_2"}}}}',
"io.simcore.thumbnail": '{"thumbnail": "https://www.statnews.com/wp-content/uploads/2020/05/3D-rat-heart.-iScience--768x432.png"}',
"io.simcore.type": '{"type": "computational"}',
"io.simcore.version": '{"version": "xxxxx"}'.replace(
"xxxxx", service_version
),
"maintainer": "johnsmith",
"org.label-schema.build-date": "2023-04-17T08:04:15Z",
"org.label-schema.schema-version": "1.0",
"org.label-schema.vcs-ref": "",
"org.label-schema.vcs-url": "",
"simcore.service.restart-policy": "no-restart",
"simcore.service.settings": '[{"name": "Resources", "type": "Resources", "value": {"Limits": {"NanoCPUs": 4000000000, "MemoryBytes": 2147483648}, "Reservations": {"NanoCPUs": 4000000000, "MemoryBytes": 2147483648}}}]',
}

return _


@pytest.fixture
def mocked_director_service_api(
mocked_director_service_api_base: respx.MockRouter,
director_service_openapi_specs: dict[str, Any],
expected_director_list_services: list[dict[str, Any]],
get_mocked_service_labels: Callable[[str, str], dict],
) -> respx.MockRouter:
"""
STANDARD fixture to mock director service API
Expand Down Expand Up @@ -461,30 +491,7 @@ def _get_service_labels(request, service_key, service_version):
return httpx.Response(
status_code=status.HTTP_200_OK,
json={
"data": {
"io.simcore.authors": '{"authors": [{"name": "John Smith", "email": "[email protected]", "affiliation": "ACME\'IS Foundation"}]}',
"io.simcore.contact": '{"contact": "[email protected]"}',
"io.simcore.description": '{"description": "Autonomous Nervous System Network model"}',
"io.simcore.inputs": '{"inputs": {"input_1": {"displayOrder": 1.0, "label": "Simulation time", "description": "Duration of the simulation", "type": "ref_contentSchema", "contentSchema": {"type": "number", "x_unit": "milli-second"}, "defaultValue": 2.0}}}',
"io.simcore.integration-version": '{"integration-version": "1.0.0"}',
"io.simcore.key": '{"key": "xxxxx"}'.replace(
"xxxxx", found["key"]
),
"io.simcore.name": '{"name": "Autonomous Nervous System Network model"}',
"io.simcore.outputs": '{"outputs": {"output_1": {"displayOrder": 1.0, "label": "ANS output", "description": "Output of simulation of Autonomous Nervous System Network model", "type": "data:*/*", "fileToKeyMap": {"ANS_output.txt": "output_1"}}, "output_2": {"displayOrder": 2.0, "label": "Stimulation parameters", "description": "stim_param.txt file containing the input provided in the inputs port", "type": "data:*/*", "fileToKeyMap": {"ANS_stim_param.txt": "output_2"}}}}',
"io.simcore.thumbnail": '{"thumbnail": "https://www.statnews.com/wp-content/uploads/2020/05/3D-rat-heart.-iScience--768x432.png"}',
"io.simcore.type": '{"type": "computational"}',
"io.simcore.version": '{"version": "xxxxx"}'.replace(
"xxxxx", found["version"]
),
"maintainer": "iavarone",
"org.label-schema.build-date": "2023-04-17T08:04:15Z",
"org.label-schema.schema-version": "1.0",
"org.label-schema.vcs-ref": "",
"org.label-schema.vcs-url": "",
"simcore.service.restart-policy": "no-restart",
"simcore.service.settings": '[{"name": "Resources", "type": "Resources", "value": {"Limits": {"NanoCPUs": 4000000000, "MemoryBytes": 2147483648}, "Reservations": {"NanoCPUs": 4000000000, "MemoryBytes": 2147483648}}}]',
}
"data": get_mocked_service_labels(found["key"], found["version"])
},
)
return httpx.Response(
Expand Down
31 changes: 31 additions & 0 deletions services/catalog/tests/unit/test_utils_service_labels.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# pylint: disable=redefined-outer-name
# pylint: disable=unused-argument

from collections.abc import Callable
from unittest.mock import AsyncMock

import pytest
from fastapi import FastAPI
from httpx import AsyncClient
from respx import MockRouter


@pytest.fixture
def mock_engine(app: FastAPI) -> None:
app.state.engine = AsyncMock()


async def test_get_service_labels(
postgres_setup_disabled: None,
mocked_director_service_api: MockRouter,
rabbitmq_and_rpc_setup_disabled: None,
background_tasks_setup_disabled: None,
mock_engine: None,
get_mocked_service_labels: Callable[[str, str], dict],
aclient: AsyncClient,
):
service_key = "simcore/services/comp/ans-model"
service_version = "3.0.0"
result = await aclient.get(f"/v0/services/{service_key}/{service_version}/labels")
assert result.status_code == 200, result.text
assert result.json() == get_mocked_service_labels(service_key, service_version)
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,7 @@ class _ServiceResourceParams:
],
)
async def test_get_service_resources(
background_tasks_setup_disabled,
background_tasks_setup_disabled: None,
rabbitmq_and_rpc_setup_disabled: None,
mocked_director_service_labels: Route,
client: TestClient,
Expand Down Expand Up @@ -298,7 +298,7 @@ def factory(services_labels: dict[str, dict[str, Any]]) -> None:
],
)
async def test_get_service_resources_sim4life_case(
background_tasks_setup_disabled,
background_tasks_setup_disabled: None,
rabbitmq_and_rpc_setup_disabled: None,
create_mock_director_service_labels: Callable,
client: TestClient,
Expand All @@ -319,7 +319,7 @@ async def test_get_service_resources_sim4life_case(


async def test_get_service_resources_raises_errors(
background_tasks_setup_disabled,
background_tasks_setup_disabled: None,
rabbitmq_and_rpc_setup_disabled: None,
mocked_director_service_labels: Route,
client: TestClient,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ async def director_client(app: FastAPI) -> DirectorApi:

# ensures manifest API cache is reset
assert hasattr(manifest.get_service, "cache")
assert manifest.get_service.cache.clear()
assert await manifest.get_service.cache.clear()

return director_api

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@
from models_library.projects import ProjectAtDB, ProjectID
from models_library.projects_nodes_io import NodeID
from models_library.service_settings_labels import SimcoreServiceLabels
from models_library.services import ServiceKeyVersion
from models_library.users import UserID
from pydantic import NonNegativeFloat, NonNegativeInt
from servicelib.fastapi.requests_decorators import cancel_on_disconnect
Expand All @@ -32,11 +31,13 @@
from tenacity.stop import stop_after_delay
from tenacity.wait import wait_fixed

from ...api.dependencies.catalog import get_catalog_client
from ...api.dependencies.database import get_repository
from ...api.dependencies.rabbitmq import get_rabbitmq_client_from_request
from ...core.dynamic_services_settings import DynamicServicesSettings
from ...core.dynamic_services_settings.scheduler import DynamicServicesSchedulerSettings
from ...modules import projects_networks
from ...modules.catalog import CatalogClient
from ...modules.db.repositories.projects import ProjectsRepository
from ...modules.db.repositories.projects_networks import ProjectsNetworksRepository
from ...modules.director_v0 import DirectorV0Client
Expand Down Expand Up @@ -104,6 +105,7 @@ async def list_tracked_dynamic_services(
@log_decorator(logger=logger)
async def create_dynamic_service(
service: DynamicServiceCreate,
catalog_client: Annotated[CatalogClient, Depends(get_catalog_client)],
director_v0_client: Annotated[DirectorV0Client, Depends(get_director_v0_client)],
dynamic_services_settings: Annotated[
DynamicServicesSettings, Depends(get_dynamic_services_settings)
Expand All @@ -114,9 +116,7 @@ async def create_dynamic_service(
x_simcore_user_agent: str = Header(...),
) -> DynamicServiceGet | RedirectResponse:
simcore_service_labels: SimcoreServiceLabels = (
await director_v0_client.get_service_labels(
service=ServiceKeyVersion(key=service.key, version=service.version)
)
await catalog_client.get_service_labels(service.key, service.version)
)

# LEGACY (backwards compatibility)
Expand Down Expand Up @@ -324,7 +324,7 @@ async def update_projects_networks(
ProjectsRepository, Depends(get_repository(ProjectsRepository))
],
scheduler: Annotated[DynamicSidecarsScheduler, Depends(get_scheduler)],
director_v0_client: Annotated[DirectorV0Client, Depends(get_director_v0_client)],
catalog_client: Annotated[CatalogClient, Depends(get_catalog_client)],
rabbitmq_client: Annotated[
RabbitMQClient, Depends(get_rabbitmq_client_from_request)
],
Expand All @@ -334,7 +334,7 @@ async def update_projects_networks(
projects_networks_repository=projects_networks_repository,
projects_repository=projects_repository,
scheduler=scheduler,
director_v0_client=director_v0_client,
catalog_client=catalog_client,
rabbitmq_client=rabbitmq_client,
project_id=project_id,
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
from rich.live import Live
from rich.table import Table
from servicelib.services_utils import get_service_from_key
from simcore_service_director_v2.modules.catalog import CatalogClient
from tenacity.asyncio import AsyncRetrying
from tenacity.stop import stop_after_attempt
from tenacity.wait import wait_random_exponential
Expand All @@ -25,7 +26,6 @@
from ..models.dynamic_services_scheduler import DynamicSidecarNamesHelper
from ..modules import db, director_v0, dynamic_sidecar
from ..modules.db.repositories.projects import ProjectsRepository
from ..modules.director_v0 import DirectorV0Client
from ..modules.dynamic_sidecar import api_client
from ..modules.projects_networks import requires_dynamic_sidecar
from ..utils.db import get_repository
Expand Down Expand Up @@ -101,7 +101,7 @@ async def async_project_save_state(project_id: ProjectID, save_attempts: int) ->
if not await requires_dynamic_sidecar(
service_key=node_content.key,
service_version=node_content.version,
director_v0_client=DirectorV0Client.instance(app),
catalog_client=CatalogClient.instance(app),
):
continue

Expand Down
Loading

0 comments on commit a8ca793

Please sign in to comment.