Add managed storage operational probes
This commit is contained in:
@@ -23,6 +23,7 @@ from govoplan_core.core.modules import (
|
|||||||
PermissionDefinition,
|
PermissionDefinition,
|
||||||
RoleTemplate,
|
RoleTemplate,
|
||||||
)
|
)
|
||||||
|
from govoplan_core.core.operations import OperationalCheckProviderRegistration
|
||||||
from govoplan_core.core.views import ViewSurface
|
from govoplan_core.core.views import ViewSurface
|
||||||
from govoplan_core.db.base import Base
|
from govoplan_core.db.base import Base
|
||||||
from govoplan_files.backend.change_tracking import register_files_change_tracking
|
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),
|
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),
|
"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)},
|
||||||
|
)
|
||||||
|
|
||||||
@@ -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
|
||||||
|
|
||||||
Reference in New Issue
Block a user