Skip to content

Commit a35576c

Browse files
GitHKAndrei Neagu
andauthored
♻️ exposing dynamic-scheduler interface by default on /dynamic-scheduler/ (#6906)
Co-authored-by: Andrei Neagu <[email protected]>
1 parent 3960405 commit a35576c

File tree

9 files changed

+67
-26
lines changed

9 files changed

+67
-26
lines changed

services/docker-compose.local.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,7 @@ services:
100100
environment:
101101
<<: *common_environment
102102
DYNAMIC_SCHEDULER_REMOTE_DEBUGGING_PORT : 3000
103+
DYNAMIC_SCHEDULER_UI_MOUNT_PATH: /
103104
ports:
104105
- "8012:8000"
105106
- "3016:3000"

services/dynamic-scheduler/src/simcore_service_dynamic_scheduler/api/frontend/_setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ def setup_frontend(app: FastAPI) -> None:
1313

1414
nicegui.ui.run_with(
1515
app,
16-
mount_path="/",
16+
mount_path=settings.DYNAMIC_SCHEDULER_UI_MOUNT_PATH,
1717
storage_secret=settings.DYNAMIC_SCHEDULER_UI_STORAGE_SECRET.get_secret_value(),
1818
)
1919
set_parent_app(app)

services/dynamic-scheduler/src/simcore_service_dynamic_scheduler/api/frontend/_utils.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import nicegui
22
from fastapi import FastAPI
33

4+
from ...core.settings import ApplicationSettings
5+
46

57
def set_parent_app(parent_app: FastAPI) -> None:
68
nicegui.app.state.parent_app = parent_app
@@ -9,3 +11,9 @@ def set_parent_app(parent_app: FastAPI) -> None:
911
def get_parent_app(app: FastAPI) -> FastAPI:
1012
parent_app: FastAPI = app.state.parent_app
1113
return parent_app
14+
15+
16+
def get_settings() -> ApplicationSettings:
17+
parent_app = get_parent_app(nicegui.app)
18+
settings: ApplicationSettings = parent_app.state.settings
19+
return settings

services/dynamic-scheduler/src/simcore_service_dynamic_scheduler/api/frontend/routes/_index.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010

1111
from ....services.service_tracker import TrackedServiceModel, get_all_tracked_services
1212
from ....services.service_tracker._models import SchedulerServiceState
13-
from .._utils import get_parent_app
13+
from .._utils import get_parent_app, get_settings
1414
from ._render_utils import base_page, get_iso_formatted_date
1515

1616
router = APIRouter()
@@ -70,9 +70,9 @@ def _render_buttons(node_id: NodeID, service: TrackedServiceModel) -> None:
7070

7171
async def _stop_service() -> None:
7272
confirm_dialog.close()
73-
await httpx.AsyncClient(timeout=10).get(
74-
f"http://localhost:{DEFAULT_FASTAPI_PORT}/service/{node_id}:stop"
75-
)
73+
74+
url = f"http://localhost:{DEFAULT_FASTAPI_PORT}{get_settings().DYNAMIC_SCHEDULER_UI_MOUNT_PATH}service/{node_id}:stop"
75+
await httpx.AsyncClient(timeout=10).get(f"{url}")
7676

7777
ui.notify(
7878
f"Submitted stop request for {node_id}. Please give the service some time to stop!"

services/dynamic-scheduler/src/simcore_service_dynamic_scheduler/api/frontend/routes/_service.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414

1515
from ....core.settings import ApplicationSettings
1616
from ....services.service_tracker import get_tracked_service, remove_tracked_service
17-
from .._utils import get_parent_app
17+
from .._utils import get_parent_app, get_settings
1818
from ._render_utils import base_page
1919

2020
router = APIRouter()
@@ -25,9 +25,9 @@ def _render_remove_from_tracking(node_id):
2525

2626
async def remove_from_tracking():
2727
confirm_dialog.close()
28-
await httpx.AsyncClient(timeout=10).get(
29-
f"http://localhost:{DEFAULT_FASTAPI_PORT}/service/{node_id}/tracker:remove"
30-
)
28+
29+
url = f"http://localhost:{DEFAULT_FASTAPI_PORT}{get_settings().DYNAMIC_SCHEDULER_UI_MOUNT_PATH}service/{node_id}/tracker:remove"
30+
await httpx.AsyncClient(timeout=10).get(f"{url}")
3131

3232
ui.notify(f"Service {node_id} removed from tracking")
3333
ui.navigate.to("/")

services/dynamic-scheduler/src/simcore_service_dynamic_scheduler/core/settings.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,10 @@ class ApplicationSettings(_BaseApplicationSettings):
9595
"Enables the full set of features to be used for NiceUI"
9696
),
9797
)
98+
DYNAMIC_SCHEDULER_UI_MOUNT_PATH: str = Field(
99+
"/dynamic-scheduler/",
100+
description="path on the URL where the dashboard is mounted",
101+
)
98102

99103
DYNAMIC_SCHEDULER_RABBITMQ: RabbitSettings = Field(
100104
json_schema_extra={"auto_default_from_env": True},
@@ -122,3 +126,11 @@ class ApplicationSettings(_BaseApplicationSettings):
122126
json_schema_extra={"auto_default_from_env": True},
123127
description="settings for opentelemetry tracing",
124128
)
129+
130+
@field_validator("DYNAMIC_SCHEDULER_UI_MOUNT_PATH", mode="before")
131+
@classmethod
132+
def _ensure_ends_with_slash(cls, v: str) -> str:
133+
if not v.endswith("/"):
134+
msg = f"Provided mount path: '{v}' must be '/' terminated"
135+
raise ValueError(msg)
136+
return v

services/dynamic-scheduler/tests/unit/api_frontend/conftest.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
from settings_library.rabbit import RabbitSettings
2020
from settings_library.redis import RedisSettings
2121
from settings_library.utils_service import DEFAULT_FASTAPI_PORT
22+
from simcore_service_dynamic_scheduler.api.frontend._utils import get_settings
2223
from simcore_service_dynamic_scheduler.core.application import create_app
2324
from tenacity import AsyncRetrying, stop_after_delay, wait_fixed
2425

@@ -92,13 +93,16 @@ async def _run_server() -> None:
9293

9394
server_task = asyncio.create_task(_run_server())
9495

96+
home_page_url = (
97+
f"http://{server_host_port}{get_settings().DYNAMIC_SCHEDULER_UI_MOUNT_PATH}"
98+
)
9599
async for attempt in AsyncRetrying(
96100
reraise=True, wait=wait_fixed(0.1), stop=stop_after_delay(2)
97101
):
98102
with attempt:
99103
async with AsyncClient(timeout=1) as client:
100-
result = await client.get(f"http://{server_host_port}")
101-
assert result.status_code == status.HTTP_200_OK
104+
response = await client.get(f"{home_page_url}")
105+
assert response.status_code == status.HTTP_200_OK
102106

103107
yield
104108

services/dynamic-scheduler/tests/unit/api_frontend/test_api_frontend_routes_index.py

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
click_on_text,
1414
get_legacy_service_status,
1515
get_new_style_service_status,
16+
take_screenshot_on_error,
1617
)
1718
from models_library.api_schemas_directorv2.dynamic_services import DynamicServiceGet
1819
from models_library.api_schemas_dynamic_scheduler.dynamic_services import (
@@ -22,6 +23,7 @@
2223
from models_library.api_schemas_webserver.projects_nodes import NodeGet
2324
from models_library.projects_nodes_io import NodeID
2425
from playwright.async_api import Page
26+
from simcore_service_dynamic_scheduler.api.frontend._utils import get_settings
2527
from simcore_service_dynamic_scheduler.services.service_tracker import (
2628
set_if_status_changed_for_service,
2729
set_request_as_running,
@@ -47,7 +49,9 @@ async def test_index_with_elements(
4749
get_dynamic_service_start: Callable[[NodeID], DynamicServiceStart],
4850
get_dynamic_service_stop: Callable[[NodeID], DynamicServiceStop],
4951
):
50-
await async_page.goto(server_host_port)
52+
await async_page.goto(
53+
f"{server_host_port}{get_settings().DYNAMIC_SCHEDULER_UI_MOUNT_PATH}"
54+
)
5155

5256
# 1. no content
5357
await assert_contains_text(async_page, "Total tracked services:")
@@ -81,7 +85,9 @@ async def test_main_page(
8185
get_dynamic_service_start: Callable[[NodeID], DynamicServiceStart],
8286
mock_stop_dynamic_service: AsyncMock,
8387
):
84-
await async_page.goto(server_host_port)
88+
await async_page.goto(
89+
f"{server_host_port}{get_settings().DYNAMIC_SCHEDULER_UI_MOUNT_PATH}"
90+
)
8591

8692
# 1. no content
8793
await assert_contains_text(async_page, "Total tracked services:")
@@ -118,8 +124,10 @@ async def test_main_page(
118124

119125
mock_stop_dynamic_service.assert_not_awaited()
120126
await click_on_text(async_page, "Stop Now")
121-
async for attempt in AsyncRetrying(
122-
reraise=True, wait=wait_fixed(0.1), stop=stop_after_delay(3)
123-
):
124-
with attempt:
125-
mock_stop_dynamic_service.assert_awaited_once()
127+
128+
async with take_screenshot_on_error(async_page):
129+
async for attempt in AsyncRetrying(
130+
reraise=True, wait=wait_fixed(0.1), stop=stop_after_delay(3)
131+
):
132+
with attempt:
133+
mock_stop_dynamic_service.assert_awaited_once()

services/dynamic-scheduler/tests/unit/api_frontend/test_api_frontend_routes_service.py

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
click_on_text,
1212
get_legacy_service_status,
1313
get_new_style_service_status,
14+
take_screenshot_on_error,
1415
)
1516
from models_library.api_schemas_directorv2.dynamic_services import DynamicServiceGet
1617
from models_library.api_schemas_dynamic_scheduler.dynamic_services import (
@@ -19,6 +20,7 @@
1920
from models_library.api_schemas_webserver.projects_nodes import NodeGet
2021
from models_library.projects_nodes_io import NodeID
2122
from playwright.async_api import Page
23+
from simcore_service_dynamic_scheduler.api.frontend._utils import get_settings
2224
from simcore_service_dynamic_scheduler.services.service_tracker import (
2325
set_if_status_changed_for_service,
2426
set_request_as_running,
@@ -47,7 +49,9 @@ async def test_service_details_no_status_present(
4749
not_initialized_app, get_dynamic_service_start(node_id)
4850
)
4951

50-
await async_page.goto(server_host_port)
52+
await async_page.goto(
53+
f"{server_host_port}{get_settings().DYNAMIC_SCHEDULER_UI_MOUNT_PATH}"
54+
)
5155

5256
# 1. one service is tracked
5357
await assert_contains_text(async_page, "Total tracked services:")
@@ -65,7 +69,8 @@ async def test_service_details_renders_friendly_404(
6569
app_runner: None, async_page: Page, server_host_port: str, node_id: NodeID
6670
):
6771
# node was not started
68-
await async_page.goto(f"{server_host_port}/service/{node_id}:details")
72+
url = f"http://{server_host_port}{get_settings().DYNAMIC_SCHEDULER_UI_MOUNT_PATH}service/{node_id}:details"
73+
await async_page.goto(f"{url}")
6974
await assert_contains_text(async_page, "Sorry could not find any details for")
7075

7176

@@ -96,7 +101,9 @@ async def test_service_details(
96101
not_initialized_app, node_id, service_status
97102
)
98103

99-
await async_page.goto(server_host_port)
104+
await async_page.goto(
105+
f"{server_host_port}{get_settings().DYNAMIC_SCHEDULER_UI_MOUNT_PATH}"
106+
)
100107

101108
# 1. one service is tracked
102109
await assert_contains_text(async_page, "Total tracked services:")
@@ -114,8 +121,9 @@ async def test_service_details(
114121
# 4. click "Remove from tracking" -> confirm
115122
await click_on_text(async_page, "Remove from tracking")
116123
await click_on_text(async_page, "Remove service")
117-
async for attempt in AsyncRetrying(
118-
reraise=True, wait=wait_fixed(0.1), stop=stop_after_delay(3)
119-
):
120-
with attempt:
121-
mock_remove_tracked_service.assert_awaited_once()
124+
async with take_screenshot_on_error(async_page):
125+
async for attempt in AsyncRetrying(
126+
reraise=True, wait=wait_fixed(0.1), stop=stop_after_delay(3)
127+
):
128+
with attempt:
129+
mock_remove_tracked_service.assert_awaited_once()

0 commit comments

Comments
 (0)