rmi-backend/scripts/ops/qdrant_audit.py
opencode c762564d40 style(rmi-backend): complete lint cleanup — 1175→0 ruff errors
- Fix 71 invalid-syntax files (class-body newline-broken assignments)
- Add from/None chain to 307 B904 raise-without-from sites
- Add B008 ignore to ruff.toml (already in pyproject.toml)
- Noqa F401 on __init__.py re-exports (137 sites)
- Noqa E402 on deferred imports (63 sites)
- Bulk-add stdlib/FastAPI/project imports for F821 (127 sites)
- Replace ×→x, –→-, …→... in docstrings (4093 chars)
- Manual refactor of 5 SIM103/SIM116 patterns

Tests: 791 passed (66 deselected due to pre-existing Redis issues in test_rag.py)
Co-authored-by: opencode <opencode@rugmunch.io>
2026-07-06 15:43:20 +02:00

138 lines
4.2 KiB
Python

#!/usr/bin/env python3
"""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())