fix(cnpg/k3d): skip reinitialize when cluster already healthy

install_barman_plugin() fetches from GitHub on every run (up to 6×120 s
retries), stalling the installer worker thread even when the CNPG cluster
is already at 'Cluster in healthy state' with all pods 2/2 Ready.

Add a fast-path at the top of initialize(): check .status.phase for
'healthy' then verify all pods show 2/N ready — if both pass, return
immediately. Full pipeline (ensure_operator, install_barman_plugin,
_apply_manifest, _wait_cnpg_pods) is only entered when needed.

Also adds test_cnpg_initialize_skips_when_cluster_healthy to
test_deployment_mode_isolation.py to guard against regression.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
chrisfu 2026-05-03 23:54:22 -07:00
parent 5295e47753
commit 2f98a5afd2
2 changed files with 61 additions and 0 deletions

View File

@ -1823,6 +1823,28 @@ def initialize(
"""
_log(log, f"Initializing CNPG cluster '{cluster_name}' in namespace '{namespace}'...")
# Fast-path: if cluster already healthy and all pods Ready, skip the full pipeline.
# install_barman_plugin() fetches from GitHub (up to 6 × 120 s attempts) — pure waste
# when the cluster is already running. We check phase then pod readiness.
_phase_r = _kubectl(
["-n", namespace, "get", "cluster", cluster_name, "-o", "jsonpath={.status.phase}"],
env=env, timeout=15,
)
if _phase_r.returncode == 0 and "healthy" in (_phase_r.stdout or "").lower():
_pods_r = _kubectl(
["-n", namespace, "get", "pods", "--no-headers",
"-l", f"cnpg.io/cluster={cluster_name}"],
env=env, timeout=20,
)
_pod_lines = [ln for ln in (_pods_r.stdout or "").splitlines() if ln.strip()]
_not_ready = [
ln for ln in _pod_lines
if len(ln.split()) < 2 or not ln.split()[1].startswith("2/")
]
if _pod_lines and not _not_ready:
_log(log, f"[CNPG] Cluster '{cluster_name}' already healthy with all pods Ready — skipping reinitialize")
return
_log(log, "[CNPG] Ensuring operator is installed and healthy...")
ensure_operator(
env=env,

View File

@ -125,6 +125,45 @@ def test_port_forward_argocd_excluded_when_disabled():
assert "dashboard" not in ids, "dashboard must be excluded when dashboard_enabled=False"
# ---------------------------------------------------------------------------
# 7f — CNPG initialize() fast-path: skip when cluster already healthy
# ---------------------------------------------------------------------------
def test_cnpg_initialize_skips_when_cluster_healthy(monkeypatch):
"""Regression: initialize() must return immediately when the cluster is healthy.
Formerly it always ran install_barman_plugin() which fetches from GitHub
(up to 6 × 120 s), stalling the installer even when CNPG was already running.
"""
import types
from knoe.core.ops import cloudnative_pg as cnpg
calls: list[str] = []
def _fake_kubectl(args, env=None, timeout=None):
cmd = " ".join(str(a) for a in args)
calls.append(cmd)
result = types.SimpleNamespace(returncode=0, stdout="", stderr="")
if "jsonpath={.status.phase}" in cmd:
result.stdout = "Cluster in healthy state"
elif "get pods" in cmd:
result.stdout = "knoe-db-1 2/2 Running 0 5d\nknoe-db-2 2/2 Running 0 5d\n"
return result
monkeypatch.setattr(cnpg, "_kubectl", _fake_kubectl)
logged: list[str] = []
cnpg.initialize("knoe-db", "knoe-db", log=lambda msg: logged.append(msg))
assert any("skipping reinitialize" in m for m in logged), (
"Expected fast-path log message not found"
)
# ensure_operator / install_barman_plugin / _apply_manifest must NOT be called
assert not any("apply" in c and "barman" in c.lower() for c in calls), (
"install_barman_plugin must not be called when cluster is already healthy"
)
def test_port_forward_grafana_uses_monitoring_release():
from knoe.core.env import _build_required_port_forwards