diff --git a/knoe/core/ops/cloudnative_pg.py b/knoe/core/ops/cloudnative_pg.py index d55a9c0..45ea76a 100644 --- a/knoe/core/ops/cloudnative_pg.py +++ b/knoe/core/ops/cloudnative_pg.py @@ -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, diff --git a/tests/test_deployment_mode_isolation.py b/tests/test_deployment_mode_isolation.py index 495b167..412b615 100644 --- a/tests/test_deployment_mode_isolation.py +++ b/tests/test_deployment_mode_isolation.py @@ -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