Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ dev-s3df: deps
# --- Docker / GHCR targets ---
GHCR_USERNAME ?= slaclab
GHCR_IMAGE ?= ghcr.io/$(GHCR_USERNAME)/iri-s3df
IMAGE_TAG ?= prod
IMAGE_TAG ?= dev

docker-build:
docker build --platform linux/amd64 -t $(GHCR_IMAGE):$(IMAGE_TAG) .
Expand Down
2 changes: 1 addition & 1 deletion app/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@

<img src="https://iri.science/images/doe-icon-old.png" height=50 />

<img src="../logo/SLAC_primary_red.png" height=100 />
<img src="/logo/SLAC_primary_red.png" height=100 />
"""

# version is the openapi.json spec version
Expand Down
File renamed without changes
6 changes: 6 additions & 0 deletions app/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@
"""Main API application"""

import logging
from pathlib import Path
from fastapi import FastAPI, Request
from fastapi.staticfiles import StaticFiles
from opentelemetry import trace
from starlette.middleware.base import BaseHTTPMiddleware
from opentelemetry.sdk.resources import Resource
Expand Down Expand Up @@ -50,6 +52,10 @@

app = FastAPI(servers=[{"url": config.API_URL_ROOT}], **config.API_CONFIG)

logo_dir = Path(__file__).resolve().parent / "logo"
if logo_dir.is_dir():
app.mount("/logo", StaticFiles(directory=str(logo_dir)), name="logo")


class _ExternalRequestContextMiddleware(BaseHTTPMiddleware):
async def dispatch(self, request: Request, call_next):
Expand Down
96 changes: 48 additions & 48 deletions app/s3df/status_registry.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,81 +32,81 @@ class ResourceMeta:
meta.id: meta
for meta in (
ResourceMeta(
id="s3df-ssh-bastions",
name="SSH Bastions",
description="S3DF SSH bastion hosts for command-line access.",
group="access",
resource_type=status_models.ResourceType.service,
id="ada",
name="Batch (ada)",
description="S3DF Slurm batch partition for Ada GPU nodes.",
group="compute",
resource_type=status_models.ResourceType.compute,
),
ResourceMeta(
id="s3df-interactive-nodes",
name="Interactive Nodes",
description="S3DF interactive login and analysis nodes.",
id="ampere",
name="Batch (ampere)",
description="S3DF Slurm batch partition for Ampere GPU nodes.",
group="compute",
resource_type=status_models.ResourceType.compute,
),
ResourceMeta(
id="s3df-docs",
name="S3DF Docs",
description="S3DF user documentation site.",
group="documentation",
resource_type=status_models.ResourceType.website,
id="turing",
name="Batch (turing)",
description="S3DF Slurm batch partition for Turing GPU nodes.",
group="compute",
resource_type=status_models.ResourceType.compute,
),
ResourceMeta(
id="s3df-batch-servers",
name="Batch Servers",
description="S3DF batch submission and scheduling servers.",
id="milano",
name="Batch (milano)",
description="S3DF Slurm batch partition for Milano CPU nodes.",
group="compute",
resource_type=status_models.ResourceType.compute,
),
ResourceMeta(
id="s3df-slurm",
name="Slurm",
description="S3DF Slurm workload management service.",
id="torino",
name="Batch (torino)",
description="S3DF Slurm batch partition for Torino CPU nodes.",
group="compute",
resource_type=status_models.ResourceType.service,
resource_type=status_models.ResourceType.compute,
),
ResourceMeta(
id="s3df-monitoring",
name="Monitoring",
description="S3DF monitoring and observability services.",
group="operations",
resource_type=status_models.ResourceType.service,
id="roma",
name="Batch (roma)",
description="S3DF Slurm batch partition for Roma CPU nodes.",
group="compute",
resource_type=status_models.ResourceType.compute,
),
ResourceMeta(
id="s3df-coact",
name="Coact",
description="S3DF Coact allocation and account service.",
group="accounts",
resource_type=status_models.ResourceType.service,
id="hopper",
name="Batch (hopper)",
description="S3DF Slurm batch partition for Hopper GPU nodes.",
group="compute",
resource_type=status_models.ResourceType.compute,
),
ResourceMeta(
id="s3df-ondemand",
name="OnDemand",
description="S3DF Open OnDemand web service.",
group="access",
resource_type=status_models.ResourceType.website,
id="sdfhome",
name="Storage (sdfhome)",
description="S3DF Weka cluster for home directories (/sdf/home).",
group="storage",
resource_type=status_models.ResourceType.storage,
),
ResourceMeta(
id="s3df-kubernetes",
name="Kubernetes",
description="S3DF Kubernetes platform.",
group="platform",
resource_type=status_models.ResourceType.system,
id="sdfdata",
name="Storage (sdfdata)",
description="S3DF Weka cluster for project/group data (sdfdata).",
group="storage",
resource_type=status_models.ResourceType.storage,
),
ResourceMeta(
id="s3df-storage",
name="Storage",
description="S3DF storage services.",
id="sdfk8s",
name="Storage (sdfk8s)",
description="S3DF Weka cluster for Kubernetes persistent volumes (sdfk8s).",
group="storage",
resource_type=status_models.ResourceType.storage,
),
ResourceMeta(
id="s3df-dtns",
name="DTNs",
description="S3DF data transfer nodes.",
group="data-transfer",
resource_type=status_models.ResourceType.network,
id="sdfscratch",
name="Storage (sdfscratch)",
description="S3DF Weka cluster for scratch storage (/sdf/scratch).",
group="storage",
resource_type=status_models.ResourceType.storage,
),
)
}
Expand Down
22 changes: 11 additions & 11 deletions app/s3df/tests/test_status_adapter.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,27 +45,27 @@ async def test_get_resources_merges_registry_and_status(monkeypatch):
resources = await adapter.get_resources(offset=0, limit=100)
assert len(resources) == len(S3DF_RESOURCES)
by_id = {r.id: r for r in resources}
bastions = by_id["s3df-ssh-bastions"]
assert bastions.name == "SSH Bastions"
assert bastions.group == "access"
assert bastions.resource_type is status_models.ResourceType.service
assert bastions.current_status is status_models.Status.up
ada = by_id["ada"]
assert ada.name == "Batch (ada)"
assert ada.group == "compute"
assert ada.resource_type is status_models.ResourceType.compute
assert ada.current_status is status_models.Status.up


@pytest.mark.asyncio
async def test_get_resources_filter_by_group(monkeypatch):
adapter, _ = _make_adapter(monkeypatch)
resources = await adapter.get_resources(offset=0, limit=100, group="compute")
ids = {r.id for r in resources}
assert ids == {"s3df-interactive-nodes", "s3df-batch-servers", "s3df-slurm"}
assert ids == {"ada", "ampere", "turing", "milano", "torino", "roma", "hopper"}


@pytest.mark.asyncio
async def test_get_resources_filter_by_current_status(monkeypatch):
payload = _statuses_payload({"s3df-storage": "down", "s3df-dtns": "degraded"})
payload = _statuses_payload({"sdfhome": "down", "sdfdata": "degraded"})
adapter, _ = _make_adapter(monkeypatch, list_resources=payload)
down = await adapter.get_resources(offset=0, limit=100, current_status=status_models.Status.down)
assert [r.id for r in down] == ["s3df-storage"]
assert [r.id for r in down] == ["sdfhome"]


@pytest.mark.asyncio
Expand All @@ -91,7 +91,7 @@ async def test_get_resource_unknown_returns_none(monkeypatch):
async def test_get_resource_known_id(monkeypatch):
async def get_resource(_id):
return {
"resource_id": "s3df-coact",
"resource_id": "ada",
"status": "degraded",
"last_changed_at": "2026-06-01T12:00:00Z",
"last_poll": "2026-06-01T12:00:00Z",
Expand All @@ -101,9 +101,9 @@ async def get_resource(_id):
adapter = S3DFStatusAdapter.__new__(S3DFStatusAdapter)
adapter._client = AsyncMock()
adapter._client.get_resource_status = AsyncMock(side_effect=get_resource)
res = await adapter.get_resource("s3df-coact")
res = await adapter.get_resource("ada")
assert res is not None
assert res.id == "s3df-coact"
assert res.id == "ada"
assert res.current_status is status_models.Status.degraded


Expand Down
37 changes: 15 additions & 22 deletions app/s3df/tests/test_status_registry.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,17 @@


EXPECTED_IDS = [
"s3df-ssh-bastions",
"s3df-interactive-nodes",
"s3df-docs",
"s3df-batch-servers",
"s3df-slurm",
"s3df-monitoring",
"s3df-coact",
"s3df-ondemand",
"s3df-kubernetes",
"s3df-storage",
"s3df-dtns",
"ada",
"ampere",
"turing",
"milano",
"torino",
"roma",
"hopper",
"sdfhome",
"sdfdata",
"sdfk8s",
"sdfscratch",
]


Expand All @@ -25,21 +25,14 @@ def test_registry_has_all_expected_resources():

def test_registry_resource_types_match_yaml():
by_id = S3DF_RESOURCES
assert by_id["s3df-ssh-bastions"].resource_type is status_models.ResourceType.service
assert by_id["s3df-interactive-nodes"].resource_type is status_models.ResourceType.compute
assert by_id["s3df-docs"].resource_type is status_models.ResourceType.website
assert by_id["s3df-storage"].resource_type is status_models.ResourceType.storage
assert by_id["s3df-kubernetes"].resource_type is status_models.ResourceType.system
assert by_id["s3df-dtns"].resource_type is status_models.ResourceType.network
assert by_id["ada"].resource_type is status_models.ResourceType.compute
assert by_id["sdfhome"].resource_type is status_models.ResourceType.storage


def test_registry_groups_match_yaml():
by_id = S3DF_RESOURCES
assert by_id["s3df-ssh-bastions"].group == "access"
assert by_id["s3df-batch-servers"].group == "compute"
assert by_id["s3df-coact"].group == "accounts"
assert by_id["s3df-storage"].group == "storage"
assert by_id["s3df-dtns"].group == "data-transfer"
assert by_id["ada"].group == "compute"
assert by_id["sdfhome"].group == "storage"


def test_parse_status_round_trip():
Expand Down
Loading