115 lines
3.4 KiB
Python
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,
|
|
)
|