Add managed storage operational probes

This commit is contained in:
2026-07-31 22:48:07 +02:00
parent b752dea610
commit f084a0f3a9
3 changed files with 121 additions and 0 deletions
+12
View File
@@ -23,6 +23,7 @@ from govoplan_core.core.modules import (
PermissionDefinition,
RoleTemplate,
)
from govoplan_core.core.operations import OperationalCheckProviderRegistration
from govoplan_core.core.views import ViewSurface
from govoplan_core.db.base import Base
from govoplan_files.backend.change_tracking import register_files_change_tracking
@@ -710,6 +711,17 @@ manifest = ModuleManifest(
CAPABILITY_FILES_ACCESS: lambda context: __import__("govoplan_files.backend.capabilities", fromlist=["access_capability"]).access_capability(context),
"files.campaign_attachments": lambda context: __import__("govoplan_files.backend.capabilities", fromlist=["campaign_capability"]).campaign_capability(context),
},
operational_check_providers=(
OperationalCheckProviderRegistration(
module_id="files",
check_id="files.managed_storage_roundtrip",
provider=lambda: __import__(
"govoplan_files.backend.operational_checks",
fromlist=["managed_storage_roundtrip_check"],
).managed_storage_roundtrip_check(),
cache_seconds=60,
),
),
)
@@ -0,0 +1,67 @@
from __future__ import annotations
import hashlib
import secrets
from uuid import uuid4
from govoplan_core.core.operations import OperationalCheck
from govoplan_files.backend.storage.backends import (
StorageBackendError,
get_storage_backend,
)
def managed_storage_roundtrip_check() -> OperationalCheck:
"""Exercise the configured managed store without retaining probe data."""
backend = get_storage_backend()
key = f".govoplan-health/probes/{uuid4().hex}.bin"
payload = secrets.token_bytes(64)
expected_digest = hashlib.sha256(payload).hexdigest()
delete_error: Exception | None = None
try:
backend.put_bytes(key, payload, content_type="application/octet-stream")
stored = backend.get_bytes(key)
info = backend.stat(key)
if info.size_bytes != len(payload):
raise StorageBackendError("Managed storage returned an unexpected object size")
if hashlib.sha256(stored).hexdigest() != expected_digest:
raise StorageBackendError("Managed storage returned different bytes than were written")
except Exception as exc: # noqa: BLE001 - operational boundary reports provider failures.
return OperationalCheck(
id="files.managed_storage_roundtrip",
label="Managed file storage",
state="error",
detail=(
"The configured managed file store failed a bounded write/read/stat/delete "
f"probe ({type(exc).__name__})."
),
readiness_critical=True,
metrics={"backend": backend.name, "probe_bytes": len(payload)},
)
finally:
try:
backend.delete(key)
except Exception as exc: # noqa: BLE001 - reported below when the data probe passed.
delete_error = exc
if delete_error is not None:
return OperationalCheck(
id="files.managed_storage_roundtrip",
label="Managed file storage",
state="error",
detail=(
"Managed file bytes round-tripped, but probe cleanup failed "
f"({type(delete_error).__name__})."
),
readiness_critical=True,
metrics={"backend": backend.name, "probe_bytes": len(payload)},
)
return OperationalCheck(
id="files.managed_storage_roundtrip",
label="Managed file storage",
state="ok",
detail="The configured managed file store passed write, read, stat, integrity, and delete checks.",
metrics={"backend": backend.name, "probe_bytes": len(payload)},
)
+42
View File
@@ -0,0 +1,42 @@
from __future__ import annotations
from unittest.mock import patch
from govoplan_files.backend.operational_checks import managed_storage_roundtrip_check
from govoplan_files.backend.storage.backends import LocalFilesystemStorageBackend
def test_managed_storage_roundtrip_removes_probe(tmp_path) -> None:
backend = LocalFilesystemStorageBackend(tmp_path)
with patch(
"govoplan_files.backend.operational_checks.get_storage_backend",
return_value=backend,
):
result = managed_storage_roundtrip_check()
assert result.state == "ok"
assert result.metrics["backend"] == "local"
assert list(tmp_path.rglob("*.bin")) == []
def test_managed_storage_roundtrip_fails_closed() -> None:
class BrokenBackend:
name = "broken"
def put_bytes(self, *_args, **_kwargs):
raise OSError("private provider detail")
def delete(self, *_args, **_kwargs):
return None
with patch(
"govoplan_files.backend.operational_checks.get_storage_backend",
return_value=BrokenBackend(),
):
result = managed_storage_roundtrip_check()
assert result.state == "error"
assert result.readiness_critical is True
assert "private provider detail" not in result.detail