Skip to content

Commit

Permalink
♻️ exposing dynamic-scheduler interface by default on `/dynamic-sch…
Browse files Browse the repository at this point in the history
…eduler/` (#6906)

Co-authored-by: Andrei Neagu <[email protected]>
  • Loading branch information
GitHK and Andrei Neagu authored Dec 11, 2024
1 parent 3960405 commit a35576c
Show file tree
Hide file tree
Showing 9 changed files with 67 additions and 26 deletions.
1 change: 1 addition & 0 deletions services/docker-compose.local.yml
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ services:
environment:
<<: *common_environment
DYNAMIC_SCHEDULER_REMOTE_DEBUGGING_PORT : 3000
DYNAMIC_SCHEDULER_UI_MOUNT_PATH: /
ports:
- "8012:8000"
- "3016:3000"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ def setup_frontend(app: FastAPI) -> None:

nicegui.ui.run_with(
app,
mount_path="/",
mount_path=settings.DYNAMIC_SCHEDULER_UI_MOUNT_PATH,
storage_secret=settings.DYNAMIC_SCHEDULER_UI_STORAGE_SECRET.get_secret_value(),
)
set_parent_app(app)
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import nicegui
from fastapi import FastAPI

from ...core.settings import ApplicationSettings


def set_parent_app(parent_app: FastAPI) -> None:
nicegui.app.state.parent_app = parent_app
Expand All @@ -9,3 +11,9 @@ def set_parent_app(parent_app: FastAPI) -> None:
def get_parent_app(app: FastAPI) -> FastAPI:
parent_app: FastAPI = app.state.parent_app
return parent_app


def get_settings() -> ApplicationSettings:
parent_app = get_parent_app(nicegui.app)
settings: ApplicationSettings = parent_app.state.settings
return settings
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@

from ....services.service_tracker import TrackedServiceModel, get_all_tracked_services
from ....services.service_tracker._models import SchedulerServiceState
from .._utils import get_parent_app
from .._utils import get_parent_app, get_settings
from ._render_utils import base_page, get_iso_formatted_date

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

async def _stop_service() -> None:
confirm_dialog.close()
await httpx.AsyncClient(timeout=10).get(
f"http://localhost:{DEFAULT_FASTAPI_PORT}/service/{node_id}:stop"
)

url = f"http://localhost:{DEFAULT_FASTAPI_PORT}{get_settings().DYNAMIC_SCHEDULER_UI_MOUNT_PATH}service/{node_id}:stop"
await httpx.AsyncClient(timeout=10).get(f"{url}")

ui.notify(
f"Submitted stop request for {node_id}. Please give the service some time to stop!"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@

from ....core.settings import ApplicationSettings
from ....services.service_tracker import get_tracked_service, remove_tracked_service
from .._utils import get_parent_app
from .._utils import get_parent_app, get_settings
from ._render_utils import base_page

router = APIRouter()
Expand All @@ -25,9 +25,9 @@ def _render_remove_from_tracking(node_id):

async def remove_from_tracking():
confirm_dialog.close()
await httpx.AsyncClient(timeout=10).get(
f"http://localhost:{DEFAULT_FASTAPI_PORT}/service/{node_id}/tracker:remove"
)

url = f"http://localhost:{DEFAULT_FASTAPI_PORT}{get_settings().DYNAMIC_SCHEDULER_UI_MOUNT_PATH}service/{node_id}/tracker:remove"
await httpx.AsyncClient(timeout=10).get(f"{url}")

ui.notify(f"Service {node_id} removed from tracking")
ui.navigate.to("/")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,10 @@ class ApplicationSettings(_BaseApplicationSettings):
"Enables the full set of features to be used for NiceUI"
),
)
DYNAMIC_SCHEDULER_UI_MOUNT_PATH: str = Field(
"/dynamic-scheduler/",
description="path on the URL where the dashboard is mounted",
)

DYNAMIC_SCHEDULER_RABBITMQ: RabbitSettings = Field(
json_schema_extra={"auto_default_from_env": True},
Expand Down Expand Up @@ -122,3 +126,11 @@ class ApplicationSettings(_BaseApplicationSettings):
json_schema_extra={"auto_default_from_env": True},
description="settings for opentelemetry tracing",
)

@field_validator("DYNAMIC_SCHEDULER_UI_MOUNT_PATH", mode="before")
@classmethod
def _ensure_ends_with_slash(cls, v: str) -> str:
if not v.endswith("/"):
msg = f"Provided mount path: '{v}' must be '/' terminated"
raise ValueError(msg)
return v
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
from settings_library.rabbit import RabbitSettings
from settings_library.redis import RedisSettings
from settings_library.utils_service import DEFAULT_FASTAPI_PORT
from simcore_service_dynamic_scheduler.api.frontend._utils import get_settings
from simcore_service_dynamic_scheduler.core.application import create_app
from tenacity import AsyncRetrying, stop_after_delay, wait_fixed

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

server_task = asyncio.create_task(_run_server())

home_page_url = (
f"http://{server_host_port}{get_settings().DYNAMIC_SCHEDULER_UI_MOUNT_PATH}"
)
async for attempt in AsyncRetrying(
reraise=True, wait=wait_fixed(0.1), stop=stop_after_delay(2)
):
with attempt:
async with AsyncClient(timeout=1) as client:
result = await client.get(f"http://{server_host_port}")
assert result.status_code == status.HTTP_200_OK
response = await client.get(f"{home_page_url}")
assert response.status_code == status.HTTP_200_OK

yield

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
click_on_text,
get_legacy_service_status,
get_new_style_service_status,
take_screenshot_on_error,
)
from models_library.api_schemas_directorv2.dynamic_services import DynamicServiceGet
from models_library.api_schemas_dynamic_scheduler.dynamic_services import (
Expand All @@ -22,6 +23,7 @@
from models_library.api_schemas_webserver.projects_nodes import NodeGet
from models_library.projects_nodes_io import NodeID
from playwright.async_api import Page
from simcore_service_dynamic_scheduler.api.frontend._utils import get_settings
from simcore_service_dynamic_scheduler.services.service_tracker import (
set_if_status_changed_for_service,
set_request_as_running,
Expand All @@ -47,7 +49,9 @@ async def test_index_with_elements(
get_dynamic_service_start: Callable[[NodeID], DynamicServiceStart],
get_dynamic_service_stop: Callable[[NodeID], DynamicServiceStop],
):
await async_page.goto(server_host_port)
await async_page.goto(
f"{server_host_port}{get_settings().DYNAMIC_SCHEDULER_UI_MOUNT_PATH}"
)

# 1. no content
await assert_contains_text(async_page, "Total tracked services:")
Expand Down Expand Up @@ -81,7 +85,9 @@ async def test_main_page(
get_dynamic_service_start: Callable[[NodeID], DynamicServiceStart],
mock_stop_dynamic_service: AsyncMock,
):
await async_page.goto(server_host_port)
await async_page.goto(
f"{server_host_port}{get_settings().DYNAMIC_SCHEDULER_UI_MOUNT_PATH}"
)

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

mock_stop_dynamic_service.assert_not_awaited()
await click_on_text(async_page, "Stop Now")
async for attempt in AsyncRetrying(
reraise=True, wait=wait_fixed(0.1), stop=stop_after_delay(3)
):
with attempt:
mock_stop_dynamic_service.assert_awaited_once()

async with take_screenshot_on_error(async_page):
async for attempt in AsyncRetrying(
reraise=True, wait=wait_fixed(0.1), stop=stop_after_delay(3)
):
with attempt:
mock_stop_dynamic_service.assert_awaited_once()
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
click_on_text,
get_legacy_service_status,
get_new_style_service_status,
take_screenshot_on_error,
)
from models_library.api_schemas_directorv2.dynamic_services import DynamicServiceGet
from models_library.api_schemas_dynamic_scheduler.dynamic_services import (
Expand All @@ -19,6 +20,7 @@
from models_library.api_schemas_webserver.projects_nodes import NodeGet
from models_library.projects_nodes_io import NodeID
from playwright.async_api import Page
from simcore_service_dynamic_scheduler.api.frontend._utils import get_settings
from simcore_service_dynamic_scheduler.services.service_tracker import (
set_if_status_changed_for_service,
set_request_as_running,
Expand Down Expand Up @@ -47,7 +49,9 @@ async def test_service_details_no_status_present(
not_initialized_app, get_dynamic_service_start(node_id)
)

await async_page.goto(server_host_port)
await async_page.goto(
f"{server_host_port}{get_settings().DYNAMIC_SCHEDULER_UI_MOUNT_PATH}"
)

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


Expand Down Expand Up @@ -96,7 +101,9 @@ async def test_service_details(
not_initialized_app, node_id, service_status
)

await async_page.goto(server_host_port)
await async_page.goto(
f"{server_host_port}{get_settings().DYNAMIC_SCHEDULER_UI_MOUNT_PATH}"
)

# 1. one service is tracked
await assert_contains_text(async_page, "Total tracked services:")
Expand All @@ -114,8 +121,9 @@ async def test_service_details(
# 4. click "Remove from tracking" -> confirm
await click_on_text(async_page, "Remove from tracking")
await click_on_text(async_page, "Remove service")
async for attempt in AsyncRetrying(
reraise=True, wait=wait_fixed(0.1), stop=stop_after_delay(3)
):
with attempt:
mock_remove_tracked_service.assert_awaited_once()
async with take_screenshot_on_error(async_page):
async for attempt in AsyncRetrying(
reraise=True, wait=wait_fixed(0.1), stop=stop_after_delay(3)
):
with attempt:
mock_remove_tracked_service.assert_awaited_once()

0 comments on commit a35576c

Please sign in to comment.