just a very simple health check for the bucket running once a week to confirm that files are alive and working
This commit is contained in:
@@ -0,0 +1,94 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
#AI slop health check for the fastdl map files. is fastdl file is unhealthy its replaced
|
||||||
|
"""Weekly R2 health check.
|
||||||
|
|
||||||
|
Downloads every file in REMOTE to nowhere (rclone cat -> /dev/null) and times it.
|
||||||
|
Any file that takes longer than LIMIT seconds is treated as broken: it is deleted
|
||||||
|
from R2 and uploaded again from LOCAL.
|
||||||
|
"""
|
||||||
|
import os
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
import time
|
||||||
|
|
||||||
|
REMOTE = "r2demo:unloze-r2/css_ze/maps"
|
||||||
|
LOCAL = "/home/gameservers/fastdl/css_ze/maps"
|
||||||
|
LIMIT = 60 # seconds
|
||||||
|
|
||||||
|
|
||||||
|
def log(msg):
|
||||||
|
print(msg, flush=True)
|
||||||
|
|
||||||
|
|
||||||
|
def list_remote_files():
|
||||||
|
result = subprocess.run(
|
||||||
|
["rclone", "lsf", "--files-only", REMOTE],
|
||||||
|
capture_output=True, text=True, check=True,
|
||||||
|
)
|
||||||
|
return [line for line in result.stdout.splitlines() if line]
|
||||||
|
|
||||||
|
|
||||||
|
def timed_download(name):
|
||||||
|
"""Return seconds taken, None if it exceeded LIMIT, or raise on rclone error."""
|
||||||
|
start = time.monotonic()
|
||||||
|
try:
|
||||||
|
subprocess.run(
|
||||||
|
["rclone", "cat", f"{REMOTE}/{name}"],
|
||||||
|
stdout=subprocess.DEVNULL, stderr=subprocess.PIPE,
|
||||||
|
timeout=LIMIT, check=True,
|
||||||
|
)
|
||||||
|
except subprocess.TimeoutExpired:
|
||||||
|
return None
|
||||||
|
return time.monotonic() - start
|
||||||
|
|
||||||
|
|
||||||
|
def reupload(name):
|
||||||
|
local_path = os.path.join(LOCAL, name)
|
||||||
|
if not os.path.isfile(local_path):
|
||||||
|
log(f" !! no local copy at {local_path}, leaving R2 file untouched")
|
||||||
|
return False
|
||||||
|
subprocess.run(["rclone", "deletefile", f"{REMOTE}/{name}"], check=True)
|
||||||
|
for attempt in range(1, 4):
|
||||||
|
result = subprocess.run(["rclone", "copyto", local_path, f"{REMOTE}/{name}"])
|
||||||
|
if result.returncode == 0:
|
||||||
|
log(" re-uploaded")
|
||||||
|
return True
|
||||||
|
log(f" !! upload attempt {attempt} failed")
|
||||||
|
time.sleep(10)
|
||||||
|
log(f" !! FILE IS NOW MISSING FROM R2: {name}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
files = list_remote_files()
|
||||||
|
log(f"Checking {len(files)} files in {REMOTE} (limit {LIMIT}s)")
|
||||||
|
problems = 0
|
||||||
|
|
||||||
|
for name in files:
|
||||||
|
try:
|
||||||
|
seconds = timed_download(name)
|
||||||
|
except subprocess.CalledProcessError as e:
|
||||||
|
# A download error (e.g. network) is not proof the file is broken,
|
||||||
|
# so log it and move on rather than deleting anything.
|
||||||
|
log(f"ERROR {name}: rclone exited {e.returncode}: {e.stderr.decode().strip()}")
|
||||||
|
problems += 1
|
||||||
|
continue
|
||||||
|
|
||||||
|
if seconds is not None:
|
||||||
|
log(f"ok {name} ({seconds:.1f}s)")
|
||||||
|
continue
|
||||||
|
|
||||||
|
log(f"SLOW {name} (>{LIMIT}s), replacing")
|
||||||
|
try:
|
||||||
|
if not reupload(name):
|
||||||
|
problems += 1
|
||||||
|
except subprocess.CalledProcessError as e:
|
||||||
|
log(f" !! rclone failed while replacing: exit {e.returncode}")
|
||||||
|
problems += 1
|
||||||
|
|
||||||
|
log(f"Done, {problems} problem(s)")
|
||||||
|
sys.exit(1 if problems else 0)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
Reference in New Issue
Block a user