| |
| """Qdrant collection audit + cleanup for test_col_* artifacts. |
| |
| T15 (G14 FIX) — Qdrant accumulates test_col_* collections from integration |
| tests that forget to clean up. This script audits and cleans them. |
| |
| Usage: |
| # Audit only (safe — no changes): |
| python scripts/ops/qdrant_audit.py |
| python scripts/ops/qdrant_audit.py --audit-only |
| |
| # Audit + cleanup (drops test_col_*): |
| python scripts/ops/qdrant_audit.py --cleanup |
| |
| # Custom Qdrant URL: |
| python scripts/ops/qdrant_audit.py --url http://qdrant.example:6333 --cleanup |
| |
| # CI gate mode (exit 1 if any test_col_* found): |
| python scripts/ops/qdrant_audit.py --check |
| |
| Cron suggestion (netcup): |
| # Daily audit, notify if anything leaked: |
| 0 6 * * * python3 /root/scripts/qdrant_audit.py --audit-only | \\ |
| curl -s --data-binary @- http://localhost:9080/rmi-info || true |
| """ |
|
|
| from __future__ import annotations |
|
|
| import argparse |
| import json |
| import sys |
| import urllib.request |
|
|
|
|
| def fetch_collections(base_url: str) -> list[dict]: |
| with urllib.request.urlopen(f"{base_url}/collections", timeout=10) as r: |
| return json.loads(r.read()).get("result", {}).get("collections", []) |
|
|
|
|
| def fetch_collection_info(base_url: str, name: str) -> dict: |
| try: |
| with urllib.request.urlopen( |
| f"{base_url}/collections/{name}", timeout=5 |
| ) as r: |
| return json.loads(r.read()).get("result", {}) |
| except Exception as e: |
| return {"error": str(e)} |
|
|
|
|
| def delete_collection(base_url: str, name: str) -> bool: |
| req = urllib.request.Request( |
| f"{base_url}/collections/{name}", method="DELETE" |
| ) |
| try: |
| with urllib.request.urlopen(req, timeout=10) as r: |
| return json.loads(r.read()).get("status") == "ok" |
| except Exception: |
| return False |
|
|
|
|
| def main() -> int: |
| p = argparse.ArgumentParser(description=__doc__) |
| p.add_argument( |
| "--url", |
| default="http://localhost:6333", |
| help="Qdrant base URL (default: http://localhost:6333)", |
| ) |
| p.add_argument( |
| "--audit-only", |
| action="store_true", |
| help="Only audit — never delete anything", |
| ) |
| p.add_argument( |
| "--cleanup", |
| action="store_true", |
| help="Audit + drop all test_col_* collections", |
| ) |
| p.add_argument( |
| "--check", |
| action="store_true", |
| help="CI mode: exit 1 if any test_col_* found (no output unless found)", |
| ) |
| args = p.parse_args() |
|
|
| cols = fetch_collections(args.url) |
| test_like = [c["name"] for c in cols if c["name"].startswith("test_col")] |
| production = [c["name"] for c in cols if not c["name"].startswith("test_col")] |
|
|
| if args.check: |
| if test_like: |
| print( |
| f"FAIL: {len(test_like)} test_col_* collection(s) found: " |
| f"{test_like}", |
| file=sys.stderr, |
| ) |
| return 1 |
| return 0 |
|
|
| print(f"=== Qdrant Audit: {args.url} ===") |
| print(f"Total collections: {len(cols)}") |
| print(f" Production: {len(production)}") |
| print(f" Test artifacts (test_col_*): {len(test_like)}") |
|
|
| if not test_like: |
| print("\nCLEAN — zero test_col_* artifacts.") |
| return 0 |
|
|
| print("\n--- Test artifacts detail ---") |
| for name in test_like: |
| info = fetch_collection_info(args.url, name) |
| points = info.get("points_count", "?") |
| vectors_size = ( |
| info.get("config", {}).get("params", {}).get("vectors", {}).get("size", "?") |
| ) |
| print(f" {name}: {points} points, {vectors_size}-dim vectors") |
|
|
| if args.audit_only: |
| print(f"\n(Use --cleanup to drop these {len(test_like)} collection(s))") |
| return 0 |
|
|
| if args.cleanup: |
| print("\n--- Cleanup ---") |
| dropped = 0 |
| for name in test_like: |
| if delete_collection(args.url, name): |
| print(f" ✓ dropped {name}") |
| dropped += 1 |
| else: |
| print(f" ✗ FAILED to drop {name}") |
| print(f"\n{dropped}/{len(test_like)} dropped.") |
| return 0 if dropped == len(test_like) else 1 |
|
|
| print("\n(Use --cleanup to drop these, or --audit-only to suppress this hint)") |
| return 0 |
|
|
|
|
| if __name__ == "__main__": |
| raise SystemExit(main()) |
|
|