feat(observatory): cache sealed camera and TGS replay on Core
This commit is contained in:
@@ -54,6 +54,7 @@ from k1link.observatory.portable_queue_binding import (
|
||||
PortableQueueBindingError,
|
||||
PortableRecordedQueueBindingService,
|
||||
)
|
||||
from k1link.observatory.portable_replay import PortableReplayService
|
||||
from k1link.observatory.portable_result_cache import PortableResultCache
|
||||
from k1link.observatory.portable_result_contract import (
|
||||
PortableCalculationProfileRegistry,
|
||||
@@ -212,6 +213,7 @@ from k1link.web.plugin_runtime import (
|
||||
PluginRuntimeUnavailableError,
|
||||
)
|
||||
from k1link.web.polygon_api import build_polygon_router, configured_polygon_runs_root
|
||||
from k1link.web.portable_replay_api import build_portable_replay_router
|
||||
from k1link.web.runtime_diagnostics import configure_scanner_diagnostics
|
||||
from k1link.web.runtime_readiness import (
|
||||
BackgroundReconcilerReadiness,
|
||||
@@ -1053,6 +1055,21 @@ async def device_plugin_events(websocket: WebSocket, plugin_id: str) -> None:
|
||||
await websocket.close(code=1011, reason="Device plugin state stream failed")
|
||||
|
||||
|
||||
if session_artifact_gateway is not None and _ffmpeg is not None:
|
||||
app.include_router(
|
||||
build_portable_replay_router(
|
||||
PortableReplayService(
|
||||
view=PortableResultViewService(
|
||||
sessions=session_store, artifacts=session_artifact_gateway.store
|
||||
),
|
||||
data_dir=session_store.data_dir,
|
||||
media=session_recorded_media_inspector,
|
||||
recording_source=_canonical_lab_recording_source,
|
||||
ffmpeg_path=_ffmpeg,
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
for legacy_router in plugin_environment.legacy_routers:
|
||||
app.include_router(legacy_router)
|
||||
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
"""Same-origin immutable replay derivatives; GET never starts packaging."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
from typing import Annotated
|
||||
|
||||
from fastapi import APIRouter, HTTPException, Path, Query, Response
|
||||
from fastapi.responses import FileResponse
|
||||
|
||||
from k1link.laboratory.canonical_rerun_overlay import CanonicalLabOverlayError
|
||||
from k1link.observatory.portable_replay import PortableReplayService
|
||||
from k1link.observatory.portable_result_view import PortableResultViewError
|
||||
|
||||
ResultId = Annotated[str, Path(pattern=r"^m49-tgs-portable-review-[a-f0-9]{64}$")]
|
||||
BaseSha = Annotated[str, Path(pattern=r"^[a-f0-9]{64}$")]
|
||||
_LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def build_portable_replay_router(service: PortableReplayService) -> APIRouter:
|
||||
router = APIRouter(tags=["observatory"])
|
||||
path = "/api/v1/observatory/portable-results/{result_id}/replays/{base_sha}/recording.rrd"
|
||||
|
||||
@router.head(path)
|
||||
def prepare(result_id: ResultId, base_sha: BaseSha) -> Response:
|
||||
try:
|
||||
artifact = service.prepare(result_id, base_sha)
|
||||
except (
|
||||
ValueError,
|
||||
OSError,
|
||||
KeyError,
|
||||
TypeError,
|
||||
CanonicalLabOverlayError,
|
||||
PortableResultViewError,
|
||||
) as exc:
|
||||
_LOG.exception("Portable replay packaging rejected")
|
||||
raise HTTPException(
|
||||
409, "Сохранённый результат не удалось подготовить к просмотру."
|
||||
) from exc
|
||||
return Response(
|
||||
media_type="application/vnd.rerun.rrd",
|
||||
headers={
|
||||
"Content-Length": str(artifact.byte_length),
|
||||
"ETag": f'"{artifact.sha256}"',
|
||||
"X-Rerun-Format": "RRF2",
|
||||
"Cache-Control": "private, no-store",
|
||||
},
|
||||
)
|
||||
|
||||
@router.get(path)
|
||||
def read(
|
||||
result_id: ResultId,
|
||||
base_sha: BaseSha,
|
||||
generation: Annotated[str, Query(pattern=r"^[a-f0-9]{64}$")],
|
||||
) -> FileResponse:
|
||||
try:
|
||||
artifact = service.cached(result_id, base_sha)
|
||||
except (ValueError, OSError, KeyError, TypeError, PortableResultViewError) as exc:
|
||||
raise HTTPException(409, "Кэш результата не прошёл проверку.") from exc
|
||||
if artifact is None:
|
||||
raise HTTPException(409, "Сохранённый просмотр ещё не подготовлен.")
|
||||
if artifact.sha256 != generation:
|
||||
raise HTTPException(412, "Версия сохранённого просмотра изменилась.")
|
||||
return FileResponse(
|
||||
artifact.path,
|
||||
media_type="application/vnd.rerun.rrd",
|
||||
headers={
|
||||
"ETag": f'"{artifact.sha256}"',
|
||||
"X-Rerun-Format": "RRF2",
|
||||
"Cache-Control": "private, max-age=31536000, immutable",
|
||||
"X-Content-Type-Options": "nosniff",
|
||||
},
|
||||
)
|
||||
|
||||
return router
|
||||
Reference in New Issue
Block a user