diff --git a/Makefile b/Makefile
index 388ab2c4..5b8b5284 100644
--- a/Makefile
+++ b/Makefile
@@ -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) .
diff --git a/app/config.py b/app/config.py
index ebf176de..5f970fc1 100644
--- a/app/config.py
+++ b/app/config.py
@@ -17,7 +17,7 @@
-
+
"""
# version is the openapi.json spec version
diff --git a/logo/SLAC_primary_red.png b/app/logo/SLAC_primary_red.png
similarity index 100%
rename from logo/SLAC_primary_red.png
rename to app/logo/SLAC_primary_red.png
diff --git a/app/main.py b/app/main.py
index 92c662c3..295f79ed 100644
--- a/app/main.py
+++ b/app/main.py
@@ -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
@@ -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):
diff --git a/app/s3df/status_registry.py b/app/s3df/status_registry.py
index 8c4551d1..0588b942 100644
--- a/app/s3df/status_registry.py
+++ b/app/s3df/status_registry.py
@@ -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,
),
)
}
diff --git a/app/s3df/tests/test_status_adapter.py b/app/s3df/tests/test_status_adapter.py
index e7199026..08ce5133 100644
--- a/app/s3df/tests/test_status_adapter.py
+++ b/app/s3df/tests/test_status_adapter.py
@@ -45,11 +45,11 @@ 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
@@ -57,15 +57,15 @@ 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
@@ -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",
@@ -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
diff --git a/app/s3df/tests/test_status_registry.py b/app/s3df/tests/test_status_registry.py
index 74612483..d3b534cb 100644
--- a/app/s3df/tests/test_status_registry.py
+++ b/app/s3df/tests/test_status_registry.py
@@ -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",
]
@@ -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():