Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

♻️ exposing dynamic-scheduler interface by default on /dynamic-scheduler/ #6906

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"
GitHK marked this conversation as resolved.
Show resolved Hide resolved
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"
GitHK marked this conversation as resolved.
Show resolved Hide resolved
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",
)
GitHK marked this conversation as resolved.
Show resolved Hide resolved

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}"
GitHK marked this conversation as resolved.
Show resolved Hide resolved
)
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()
Loading