from __future__ import annotations from datetime import datetime, timezone import types from prometheus_client import REGISTRY from ariadne.services import jenkins_workspace_cleanup as cleanup_module def _metric_value(name: str, labels: dict[str, str]) -> float: value = REGISTRY.get_sample_value(name, labels) return float(value) if value is not None else 0.0 def _dummy_settings(*, dry_run: bool, max_deletions: int = 20) -> types.SimpleNamespace: return types.SimpleNamespace( jenkins_workspace_namespace="jenkins", jenkins_workspace_pvc_prefix="pvc-workspace-", jenkins_workspace_cleanup_min_age_hours=1.0, jenkins_workspace_cleanup_dry_run=dry_run, jenkins_workspace_cleanup_max_deletions_per_run=max_deletions, ) def _fake_payloads(now_iso: str, old_iso: str) -> dict[str, dict[str, object]]: return { "/api/v1/namespaces/jenkins/pods": { "items": [ { "metadata": { "annotations": { "jenkins.io/workspace-pvc": "pvc-workspace-annotated-active", } }, "spec": { "volumes": [ {"persistentVolumeClaim": {"claimName": "pvc-workspace-active"}}, ] }, } ] }, "/api/v1/namespaces/jenkins/persistentvolumeclaims": { "items": [ { "metadata": {"name": "pvc-workspace-stale", "creationTimestamp": old_iso}, "status": {"phase": "Lost"}, }, { "metadata": {"name": "pvc-workspace-active", "creationTimestamp": old_iso}, "status": {"phase": "Bound"}, }, { "metadata": {"name": "pvc-workspace-annotated-active", "creationTimestamp": old_iso}, "status": {"phase": "Lost"}, }, { "metadata": {"name": "pvc-workspace-fresh", "creationTimestamp": now_iso}, "status": {"phase": "Lost"}, }, { "metadata": { "name": "pvc-workspace-deleting", "creationTimestamp": old_iso, "deletionTimestamp": old_iso, }, "status": {"phase": "Lost"}, }, ] }, "/api/v1/persistentvolumes": { "items": [ { "metadata": {"name": "pvc-old", "creationTimestamp": old_iso}, "status": {"phase": "Released"}, "spec": {"claimRef": {"namespace": "jenkins", "name": "pvc-workspace-stale"}}, }, { "metadata": {"name": "pvc-active", "creationTimestamp": old_iso}, "status": {"phase": "Released"}, "spec": {"claimRef": {"namespace": "jenkins", "name": "pvc-workspace-active"}}, }, { "metadata": {"name": "pvc-annotated", "creationTimestamp": old_iso}, "status": {"phase": "Released"}, "spec": {"claimRef": {"namespace": "jenkins", "name": "pvc-workspace-annotated-active"}}, }, { "metadata": {"name": "pvc-fresh", "creationTimestamp": now_iso}, "status": {"phase": "Released"}, "spec": {"claimRef": {"namespace": "jenkins", "name": "pvc-workspace-fresh"}}, }, { "metadata": { "name": "pvc-deleting", "creationTimestamp": old_iso, "deletionTimestamp": old_iso, }, "status": {"phase": "Released"}, "spec": {"claimRef": {"namespace": "jenkins", "name": "pvc-workspace-deleting"}}, }, ] }, "/apis/longhorn.io/v1beta2/namespaces/longhorn-system/volumes": { "items": [ {"metadata": {"name": "pvc-old", "creationTimestamp": old_iso}}, { "metadata": { "name": "pvc-orphan", "creationTimestamp": old_iso, "labels": { "kubernetes.io/created-for/pvc/name": "pvc-workspace-orphan", "kubernetes.io/created-for/pvc/namespace": "jenkins", }, } }, { "metadata": { "name": "pvc-attached", "creationTimestamp": old_iso, "labels": { "kubernetes.io/created-for/pvc/name": "pvc-workspace-annotated-active", "kubernetes.io/created-for/pvc/namespace": "jenkins", }, }, "status": {"state": "attached", "isAttached": True, "robustness": "healthy"}, "spec": {"frontend": "blockdev"}, }, { "metadata": { "name": "pvc-orphan-other-namespace", "creationTimestamp": old_iso, "labels": { "kubernetes.io/created-for/pvc/name": "pvc-workspace-orphan", "kubernetes.io/created-for/pvc/namespace": "nextcloud", }, } }, { "metadata": { "name": "pvc-orphan-fresh", "creationTimestamp": now_iso, "labels": { "kubernetes.io/created-for/pvc/name": "pvc-workspace-fresh", "kubernetes.io/created-for/pvc/namespace": "jenkins", }, } }, { "metadata": { "name": "pvc-vol-deleting", "creationTimestamp": old_iso, "deletionTimestamp": old_iso, "labels": { "kubernetes.io/created-for/pvc/name": "pvc-workspace-orphan", "kubernetes.io/created-for/pvc/namespace": "jenkins", }, } }, ] }, } def test_cleanup_jenkins_workspace_storage_dry_run(monkeypatch) -> None: monkeypatch.setattr(cleanup_module, "settings", _dummy_settings(dry_run=True)) now_iso = datetime.now(timezone.utc).isoformat().replace("+00:00", "Z") old_iso = "2020-01-01T00:00:00Z" payloads = _fake_payloads(now_iso, old_iso) deleted_paths: list[str] = [] def fake_get_json(path: str): if path in payloads: return payloads[path] raise AssertionError(f"unexpected path: {path}") def fake_delete_json(path: str): deleted_paths.append(path) return {"status": "Success"} before_runs = _metric_value( "ariadne_jenkins_workspace_cleanup_runs_total", {"status": "ok", "mode": "dry_run"}, ) before_planned = _metric_value( "ariadne_jenkins_workspace_cleanup_objects_total", {"kind": "pvc", "action": "planned", "mode": "dry_run"}, ) monkeypatch.setattr(cleanup_module, "get_json", fake_get_json) monkeypatch.setattr(cleanup_module, "delete_json", fake_delete_json) summary = cleanup_module.cleanup_jenkins_workspace_storage() assert summary.dry_run is True assert summary.pvcs_planned == 1 assert summary.pvs_planned == 1 assert summary.volumes_planned == 2 assert summary.pvcs_deleted == 0 assert summary.pvs_deleted == 0 assert summary.volumes_deleted == 0 assert summary.failures == 0 assert deleted_paths == [] assert _metric_value( "ariadne_jenkins_workspace_cleanup_runs_total", {"status": "ok", "mode": "dry_run"}, ) == before_runs + 1 assert _metric_value( "ariadne_jenkins_workspace_cleanup_objects_total", {"kind": "pvc", "action": "planned", "mode": "dry_run"}, ) == before_planned + 1 def test_cleanup_jenkins_workspace_storage(monkeypatch) -> None: monkeypatch.setattr(cleanup_module, "settings", _dummy_settings(dry_run=False)) now_iso = datetime.now(timezone.utc).isoformat().replace("+00:00", "Z") old_iso = "2020-01-01T00:00:00Z" deleted_paths: list[str] = [] payloads = _fake_payloads(now_iso, old_iso) def fake_get_json(path: str): if path in payloads: return payloads[path] raise AssertionError(f"unexpected path: {path}") def fake_delete_json(path: str): deleted_paths.append(path) return {"status": "Success"} before_runs = _metric_value( "ariadne_jenkins_workspace_cleanup_runs_total", {"status": "ok", "mode": "delete"}, ) before_deleted = _metric_value( "ariadne_jenkins_workspace_cleanup_objects_total", {"kind": "longhorn_volume", "action": "deleted", "mode": "delete"}, ) monkeypatch.setattr(cleanup_module, "get_json", fake_get_json) monkeypatch.setattr(cleanup_module, "delete_json", fake_delete_json) summary = cleanup_module.cleanup_jenkins_workspace_storage() assert summary.pvcs_deleted == 1 assert summary.pvs_deleted == 1 assert summary.volumes_deleted == 2 assert summary.failures == 0 assert "/api/v1/namespaces/jenkins/persistentvolumeclaims/pvc-workspace-stale" in deleted_paths assert "/api/v1/persistentvolumes/pvc-old" in deleted_paths assert "/apis/longhorn.io/v1beta2/namespaces/longhorn-system/volumes/pvc-old" in deleted_paths assert "/apis/longhorn.io/v1beta2/namespaces/longhorn-system/volumes/pvc-orphan" in deleted_paths assert "/apis/longhorn.io/v1beta2/namespaces/longhorn-system/volumes/pvc-orphan-other-namespace" not in deleted_paths assert "/apis/longhorn.io/v1beta2/namespaces/longhorn-system/volumes/pvc-attached" not in deleted_paths assert _metric_value( "ariadne_jenkins_workspace_cleanup_runs_total", {"status": "ok", "mode": "delete"}, ) == before_runs + 1 assert _metric_value( "ariadne_jenkins_workspace_cleanup_objects_total", {"kind": "longhorn_volume", "action": "deleted", "mode": "delete"}, ) == before_deleted + 2 def test_cleanup_jenkins_workspace_storage_failure(monkeypatch) -> None: monkeypatch.setattr(cleanup_module, "settings", _dummy_settings(dry_run=False)) def fake_get_json(path: str): if path == "/api/v1/namespaces/jenkins/pods": return {"items": []} if path == "/api/v1/namespaces/jenkins/persistentvolumeclaims": return { "items": [ { "metadata": {"name": "pvc-workspace-stale", "creationTimestamp": "2020-01-01T00:00:00Z"}, "status": {"phase": "Lost"}, } ] } if path == "/api/v1/persistentvolumes": return {"items": []} if path == "/apis/longhorn.io/v1beta2/namespaces/longhorn-system/volumes": return {"items": []} raise AssertionError(f"unexpected path: {path}") def fake_delete_json(_path: str): raise RuntimeError("boom") before_failures = _metric_value( "ariadne_jenkins_workspace_cleanup_objects_total", {"kind": "cleanup", "action": "failed", "mode": "delete"}, ) monkeypatch.setattr(cleanup_module, "get_json", fake_get_json) monkeypatch.setattr(cleanup_module, "delete_json", fake_delete_json) summary = cleanup_module.cleanup_jenkins_workspace_storage() assert summary.failures == 1 assert summary.pvcs_deleted == 0 assert _metric_value( "ariadne_jenkins_workspace_cleanup_objects_total", {"kind": "cleanup", "action": "failed", "mode": "delete"}, ) == before_failures + 1 def test_cleanup_jenkins_workspace_storage_uses_longhorn_kubernetes_status(monkeypatch) -> None: monkeypatch.setattr(cleanup_module, "settings", _dummy_settings(dry_run=False)) deleted_paths: list[str] = [] def fake_get_json(path: str): if path == "/api/v1/namespaces/jenkins/pods": return {"items": []} if path == "/api/v1/namespaces/jenkins/persistentvolumeclaims": return {"items": []} if path == "/api/v1/persistentvolumes": return {"items": []} if path == "/apis/longhorn.io/v1beta2/namespaces/longhorn-system/volumes": return { "items": [ { "metadata": { "name": "pvc-orphan-kstatus", "creationTimestamp": "2020-01-01T00:00:00Z", }, "status": { "state": "detached", "isAttached": False, "robustness": "unknown", "kubernetesStatus": { "namespace": "jenkins", "pvcName": "pvc-workspace-kstatus", "pvName": "pvc-orphan-kstatus", }, }, "spec": {"frontend": "blockdev"}, } ] } raise AssertionError(f"unexpected path: {path}") def fake_delete_json(path: str): deleted_paths.append(path) return {"status": "Success"} monkeypatch.setattr(cleanup_module, "get_json", fake_get_json) monkeypatch.setattr(cleanup_module, "delete_json", fake_delete_json) summary = cleanup_module.cleanup_jenkins_workspace_storage() assert summary.volumes_planned == 1 assert summary.volumes_deleted == 1 assert summary.failures == 0 assert deleted_paths == ["/apis/longhorn.io/v1beta2/namespaces/longhorn-system/volumes/pvc-orphan-kstatus"] def test_cleanup_jenkins_workspace_storage_guard_caps_mass_delete(monkeypatch) -> None: monkeypatch.setattr(cleanup_module, "settings", _dummy_settings(dry_run=False, max_deletions=1)) now_iso = datetime.now(timezone.utc).isoformat().replace("+00:00", "Z") old_iso = "2020-01-01T00:00:00Z" payloads = _fake_payloads(now_iso, old_iso) deleted_paths: list[str] = [] def fake_get_json(path: str): if path in payloads: return payloads[path] raise AssertionError(f"unexpected path: {path}") def fake_delete_json(path: str): deleted_paths.append(path) return {"status": "Success"} monkeypatch.setattr(cleanup_module, "get_json", fake_get_json) monkeypatch.setattr(cleanup_module, "delete_json", fake_delete_json) summary = cleanup_module.cleanup_jenkins_workspace_storage() assert summary.failures == 0 assert summary.pvcs_planned == 1 assert summary.pvs_planned == 1 assert summary.volumes_planned == 1 assert summary.pvcs_deleted == 1 assert summary.pvs_deleted == 0 assert summary.volumes_deleted == 0 assert summary.skipped == 2 assert deleted_paths == ["/api/v1/namespaces/jenkins/persistentvolumeclaims/pvc-workspace-stale"]