prole/knoe/core/ops/garage_store.py
chrisfu 761d80486b feat: add storage probing and operational service updates
- add reusable storage probing subsystem with discovery, bounded probe execution, IO classification, caching, and topology integration

- render per-node storage inventory in Cluster Nodes UI and extend installer test coverage for topology/storage behavior

- introduce core service operation modules and align actions, milestones, services, and supporting configs/scripts for repair/update workflows

- update CNPG/Supabase/database artifacts, placement and port mapping configs, plus related integration tests

Co-authored-by: Junie <junie@jetbrains.com>
2026-03-26 09:44:23 -07:00

132 lines
3.7 KiB
Python

from __future__ import annotations
from pathlib import Path
from ._services_common import (
_LogFn,
_detect_mode,
_ensure_namespace,
_exists,
_kubectl,
_log,
_manifest_path,
_namespace,
_to_bool,
_wait_rollout,
)
def _garage_namespace(namespace: str | None, env: dict | None) -> str:
if env:
explicit = str(env.get("GARAGE_NAMESPACE") or "").strip()
if explicit:
return explicit
return _namespace(namespace, env, default="default")
def _manifest_files(project_root: str | Path) -> list[Path]:
root = Path(project_root)
return [
_manifest_path(root, "k8s", "prole", "storageclass-synology-iscsi.yaml"),
_manifest_path(root, "k8s", "prole", "iscsi-pvs.yaml"),
_manifest_path(root, "k8s", "prole", "garage-configmap.yaml"),
_manifest_path(root, "k8s", "prole", "garage-statefulset.yaml"),
_manifest_path(root, "k8s", "prole", "garage-service.yaml"),
]
def initialize(
*,
namespace: str | None = None,
env: dict | None = None,
project_root: str | Path = ".",
log: _LogFn | None = None,
mode: str | None = None,
) -> None:
update(namespace=namespace, env=env, project_root=project_root, log=log, mode=mode)
def start(
*,
namespace: str | None = None,
env: dict | None = None,
project_root: str | Path = ".",
log: _LogFn | None = None,
mode: str | None = None,
) -> None:
update(namespace=namespace, env=env, project_root=project_root, log=log, mode=mode)
def update(
*,
namespace: str | None = None,
env: dict | None = None,
project_root: str | Path = ".",
log: _LogFn | None = None,
mode: str | None = None,
) -> None:
_detect_mode(mode, env) # mode kept for API parity
target_ns = _garage_namespace(namespace, env)
_ensure_namespace(target_ns, env)
for manifest in _manifest_files(project_root):
if not manifest.exists():
continue
_log(log, f"[GARAGE] Applying {manifest}")
_kubectl(["apply", "-f", str(manifest)], env=env, timeout=240, check=True)
if _exists("statefulset", "garage", target_ns, env=env):
_wait_rollout("statefulset", "garage", target_ns, env=env)
def stop(
*,
namespace: str | None = None,
env: dict | None = None,
log: _LogFn | None = None,
) -> None:
target_ns = _garage_namespace(namespace, env)
if _exists("statefulset", "garage", target_ns, env=env):
_log(log, f"[GARAGE] Scaling statefulset/garage to 0 in namespace {target_ns}")
_kubectl(
["-n", target_ns, "scale", "statefulset/garage", "--replicas=0"],
env=env,
timeout=90,
check=True,
)
def restart(
*,
namespace: str | None = None,
env: dict | None = None,
log: _LogFn | None = None,
) -> None:
target_ns = _garage_namespace(namespace, env)
if _exists("statefulset", "garage", target_ns, env=env):
_log(log, f"[GARAGE] Restarting statefulset/garage in namespace {target_ns}")
_kubectl(
["-n", target_ns, "rollout", "restart", "statefulset/garage"],
env=env,
timeout=120,
check=True,
)
_wait_rollout("statefulset", "garage", target_ns, env=env)
def status(
*,
namespace: str | None = None,
env: dict | None = None,
) -> bool:
target_ns = _garage_namespace(namespace, env)
sts = _kubectl(
["-n", target_ns, "get", "statefulset", "garage", "-o", "jsonpath={.status.readyReplicas}"],
env=env,
timeout=20,
)
svc = _kubectl(["-n", target_ns, "get", "svc", "garage"], env=env, timeout=20)
return _to_bool(
sts.returncode == 0 and (sts.stdout or "0").strip() not in {"", "0"} and svc.returncode == 0
)