ariadne/ariadne/services/opensearch_prune.py

115 lines
3.4 KiB
Python

from __future__ import annotations
from dataclasses import dataclass
import re
from typing import Any
import httpx
from ..settings import settings
from ..utils.logging import get_logger
logger = get_logger(__name__)
_UNITS = {
"b": 1,
"kb": 1024,
"mb": 1024**2,
"gb": 1024**3,
"tb": 1024**4,
}
HTTP_NOT_FOUND = 404
def parse_size(value: str) -> int:
if not value:
return 0
text = value.strip().lower()
if text in {"-", "0"}:
return 0
match = re.match(r"^([0-9.]+)([a-z]+)$", text)
if not match:
return 0
number = float(match.group(1))
unit = match.group(2)
if unit not in _UNITS:
return 0
return int(number * _UNITS[unit])
@dataclass(frozen=True)
class OpensearchPruneSummary:
total_before: int
total_after: int
deleted: int
detail: str = ""
def _fetch_indices(client: httpx.Client, pattern: str) -> list[dict[str, Any]]:
url = f"{settings.opensearch_url}/_cat/indices/{pattern}"
params = {"format": "json", "h": "index,store.size,creation.date"}
resp = client.get(url, params=params)
if resp.status_code == HTTP_NOT_FOUND:
return []
resp.raise_for_status()
payload = resp.json()
return payload if isinstance(payload, list) else []
def _delete_index(client: httpx.Client, index: str) -> None:
url = f"{settings.opensearch_url}/{index}"
resp = client.delete(url)
resp.raise_for_status()
def prune_indices() -> OpensearchPruneSummary:
patterns = [p.strip() for p in settings.opensearch_index_patterns.split(",") if p.strip()]
if not patterns:
return OpensearchPruneSummary(0, 0, 0, detail="no patterns configured")
indices: list[dict[str, Any]] = []
with httpx.Client(timeout=settings.opensearch_timeout_sec) as client:
for pattern in patterns:
try:
data = _fetch_indices(client, pattern)
except Exception as exc:
logger.info(
"opensearch index fetch failed",
extra={"event": "opensearch_prune", "status": "error", "detail": str(exc)},
)
continue
for item in data:
index = item.get("index")
if not isinstance(index, str) or not index or index.startswith("."):
continue
size = parse_size(str(item.get("store.size") or ""))
created = int(item.get("creation.date") or 0)
indices.append({"index": index, "size": size, "created": created})
total = sum(item["size"] for item in indices)
if total <= settings.opensearch_limit_bytes:
return OpensearchPruneSummary(total, total, 0, detail="within limit")
indices.sort(key=lambda item: item["created"])
deleted = 0
for item in indices:
if total <= settings.opensearch_limit_bytes:
break
try:
_delete_index(client, item["index"])
deleted += 1
total -= item["size"]
except Exception as exc:
logger.info(
"opensearch delete failed",
extra={"event": "opensearch_prune", "status": "error", "detail": str(exc)},
)
return OpensearchPruneSummary(
total_before=sum(item["size"] for item in indices),
total_after=total,
deleted=deleted,
)