Withhold addresses after the database is recreated without a backup
When ripe.db is corrupted and no valid backup exists, a new empty database is created with a db_recreated.json marker; /addresses and /addresses/diff answer 503 until the collector gathers data again, /health reports db_recreated. Tests now isolate all state files via conftest. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
46d56654d8
commit
0155fd3f38
13 files changed
+226
-32
No files matched your search
+25
-3
@@ -108,6 +108,19 @@ def verify_token(x_api_key: Optional[str] = Header(None)):
|
||||
raise HTTPException(status_code=401, detail="Invalid or missing X-API-Key.")
|
||||
|
||||
|
||||
def kinds_of(address_type):
|
||||
"""Типы данных в базе для значения параметра type."""
|
||||
return {"asn" if t == AddressType.cidr else "fqdn"
|
||||
for t in (AddressType.cidr, AddressType.fqdn) if address_type in (t, AddressType.all_types)}
|
||||
|
||||
|
||||
def ensure_data_ready(conn, kinds):
|
||||
"""503, пока база пересоздана после порчи и данные не собраны заново: пустой список ввёл бы потребителя в заблуждение."""
|
||||
if db.recreated_pending(conn, kinds):
|
||||
raise HTTPException(status_code=503, headers={"Retry-After": "300"},
|
||||
detail="The database was recreated after corruption; data is being collected again.")
|
||||
|
||||
|
||||
def get_cidrs() -> List[str]:
|
||||
with db.session() as conn:
|
||||
return db.get_values(conn, "asn")
|
||||
@@ -133,6 +146,7 @@ def get_addresses(
|
||||
):
|
||||
# Курсор читаем до данных: изменения между чтением курсора и данных повторятся в diff, что безвредно
|
||||
with db.session() as conn:
|
||||
ensure_data_ready(conn, kinds_of(type))
|
||||
headers = {"X-Changes-Cursor": str(db.journal_head(conn))}
|
||||
results = set()
|
||||
|
||||
@@ -175,9 +189,9 @@ def get_addresses_diff(
|
||||
ip_version: IPVersion = Query(IPVersion.all_versions, description="Filter by IP version"),
|
||||
):
|
||||
cursor, since_ts = parse_since(since)
|
||||
kinds = {"asn" if t == AddressType.cidr else "fqdn"
|
||||
for t in (AddressType.cidr, AddressType.fqdn) if type in (t, AddressType.all_types)}
|
||||
kinds = kinds_of(type)
|
||||
with db.session() as conn:
|
||||
ensure_data_ready(conn, kinds)
|
||||
changes = db.get_changes(conn, kinds, cursor, since_ts)
|
||||
if changes is None:
|
||||
raise HTTPException(status_code=410, detail="since is outside the change journal; fetch the full /addresses list")
|
||||
@@ -216,13 +230,19 @@ def health():
|
||||
|
||||
with db.session() as conn:
|
||||
counts = {"cidrs": db.count_values(conn, "asn"), "fqdn_ips": db.count_values(conn, "fqdn")}
|
||||
pending = db.recreated_pending(conn, ("asn", "fqdn"))
|
||||
|
||||
try:
|
||||
last_restore = load_json(cc.RESTORE_FILE, None) # след автовосстановления базы из копии
|
||||
except StorageError:
|
||||
last_restore = None
|
||||
|
||||
healthy = alive and not any(j.get("last_error") for j in jobs.values())
|
||||
try:
|
||||
recreated = load_json(cc.RECREATED_FILE, None) # база пересоздана после порчи без копий
|
||||
except StorageError:
|
||||
recreated = None
|
||||
|
||||
healthy = alive and not pending and not any(j.get("last_error") for j in jobs.values())
|
||||
return {
|
||||
"status": "ok" if healthy else "degraded",
|
||||
"collector_alive": alive,
|
||||
@@ -230,6 +250,8 @@ def health():
|
||||
"jobs": jobs,
|
||||
"counts": counts,
|
||||
"last_restore": last_restore,
|
||||
# Только время и признак ожидания (пути карантина наружу не отдаём)
|
||||
"db_recreated": {"at": recreated.get("at"), "pending": pending} if recreated else None,
|
||||
}
|
||||
|
||||
|
||||
|
||||
Reference in new issue
Block a user