gitea: fix LFS round-trip post-restart wait (stale on 28.0.0)
continuous-integration/drone/push Build is failing
continuous-integration/drone/push Build is failing
test_lfs_roundtrip's post-restart poll timed out on gitea 28.0.0 (PR #10 run #31): _api() caught only HTTPError, so a single urlopen(timeout=20) socket timeout inside the poll raised and aborted the poll itself instead of being retried; the poll also hard-coded a 120s deadline + a 20s request timeout, contradicting the recipe's declared HTTP_TIMEOUT=600 (tests/STYLE.md §5-sized for the warm custom tier; the 28.0.0 boot runs a longer migration window than 1.27.3). Fix, assertion untouched (§3): - _api() is never-raising like lifecycle.http_fetch: transient URLError/socket timeout/OSError -> (0, {}) so the bounded poll simply retries (each request still bounded at 20s; the poll's deadline is what bounds the wait). - poll deadline derives from recipe_meta.HTTP_TIMEOUT (600) instead of a hard-coded 120, probing the same /api/v1/version path recipe_meta.READY_PROBE declares. Evidence: gitea 28.0.0 dev deploy converged + served /api/healthz and /version (gitea-upgrade-2026-10-02.md 2b); the RED run #31 failed only in this poll while install/upgrade/backup/restore and every other custom test passed.
This commit is contained in:
@@ -23,6 +23,7 @@ import shutil
|
|||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
import tempfile
|
import tempfile
|
||||||
|
import time
|
||||||
import urllib.error
|
import urllib.error
|
||||||
import urllib.request
|
import urllib.request
|
||||||
|
|
||||||
@@ -45,7 +46,15 @@ def _lfs_available() -> bool:
|
|||||||
return os.path.exists(os.path.join(abra_dir, "recipes", "gitea", _LFS_OVERLAY))
|
return os.path.exists(os.path.join(abra_dir, "recipes", "gitea", _LFS_OVERLAY))
|
||||||
|
|
||||||
|
|
||||||
def _api(domain, path, method="GET", body=None, user="", password=""):
|
API_TIMEOUT = 20
|
||||||
|
|
||||||
|
|
||||||
|
def _api(domain, path, method="GET", body=None, user="", password="", timeout=API_TIMEOUT):
|
||||||
|
"""One API call → (status, JSON). Never raises: transient errors (connection refused /
|
||||||
|
dropped mid-restart, DNS, socket timeout) return (0, {}) so a caller's bounded poll can
|
||||||
|
simply retry, mirroring lifecycle.http_fetch. Only HTTPError was caught before; a socket
|
||||||
|
timeout on a slow post-restart boot (gitea 28.0.0 migration window, PR #10 run #31) escaped
|
||||||
|
as TimeoutError and aborted the restart poll itself."""
|
||||||
data = json.dumps(body).encode() if body is not None else None
|
data = json.dumps(body).encode() if body is not None else None
|
||||||
auth = base64.b64encode(f"{user}:{password}".encode()).decode()
|
auth = base64.b64encode(f"{user}:{password}".encode()).decode()
|
||||||
headers = {"Authorization": f"Basic {auth}"}
|
headers = {"Authorization": f"Basic {auth}"}
|
||||||
@@ -55,7 +64,7 @@ def _api(domain, path, method="GET", body=None, user="", password=""):
|
|||||||
f"https://{domain}/api/v1{path}", data=data, headers=headers, method=method
|
f"https://{domain}/api/v1{path}", data=data, headers=headers, method=method
|
||||||
)
|
)
|
||||||
try:
|
try:
|
||||||
with urllib.request.urlopen(req, timeout=20, context=_CTX) as r:
|
with urllib.request.urlopen(req, timeout=timeout, context=_CTX) as r:
|
||||||
raw = r.read()
|
raw = r.read()
|
||||||
return r.status, (json.loads(raw) if raw else {})
|
return r.status, (json.loads(raw) if raw else {})
|
||||||
except urllib.error.HTTPError as e:
|
except urllib.error.HTTPError as e:
|
||||||
@@ -64,6 +73,8 @@ def _api(domain, path, method="GET", body=None, user="", password=""):
|
|||||||
return e.code, json.loads(raw)
|
return e.code, json.loads(raw)
|
||||||
except (ValueError, json.JSONDecodeError):
|
except (ValueError, json.JSONDecodeError):
|
||||||
return e.code, {}
|
return e.code, {}
|
||||||
|
except (urllib.error.URLError, TimeoutError, OSError): # TimeoutError ⊃ socket.timeout (UP041)
|
||||||
|
return 0, {}
|
||||||
|
|
||||||
|
|
||||||
def _run_git(args, cwd, env=None):
|
def _run_git(args, cwd, env=None):
|
||||||
@@ -164,6 +175,8 @@ def test_lfs_roundtrip(live_app):
|
|||||||
finally:
|
finally:
|
||||||
shutil.rmtree(fresh_dir, ignore_errors=True)
|
shutil.rmtree(fresh_dir, ignore_errors=True)
|
||||||
|
|
||||||
|
import recipe_meta # noqa: E402 — per-recipe declared timeouts (tests/STYLE.md §5)
|
||||||
|
|
||||||
# 7. JWT-secret stability: restart gitea + assert LFS_JWT_SECRET unchanged.
|
# 7. JWT-secret stability: restart gitea + assert LFS_JWT_SECRET unchanged.
|
||||||
# Read the current secret from inside the container's rendered app.ini.
|
# Read the current secret from inside the container's rendered app.ini.
|
||||||
current_jwt = lifecycle.exec_in_app(
|
current_jwt = lifecycle.exec_in_app(
|
||||||
@@ -182,14 +195,15 @@ def test_lfs_roundtrip(live_app):
|
|||||||
capture_output=True,
|
capture_output=True,
|
||||||
timeout=120,
|
timeout=120,
|
||||||
)
|
)
|
||||||
# Wait for gitea to come back up
|
# Wait for gitea to come back up. Window + probe are DERIVED from the recipe's declared
|
||||||
|
# readiness (recipe_meta.HTTP_TIMEOUT / READY_PROBE — the /api/v1/version readiness that
|
||||||
# Re-read meta from the live_app fixture (meta is not in scope here — use the stored meta)
|
# recipe_meta already declares for the harness, not a guess) — tests/STYLE.md §5.
|
||||||
import time
|
wait_timeout = int(getattr(recipe_meta, "HTTP_TIMEOUT", 300))
|
||||||
|
probe_path = "/version" # READY_PROBE's readiness path (rules carry only ok + domain)
|
||||||
deadline = time.time() + 120
|
deadline = time.time() + wait_timeout
|
||||||
|
status2 = 0
|
||||||
while time.time() < deadline:
|
while time.time() < deadline:
|
||||||
status2, _ = _api(live_app, "/version", user=user, password=password)
|
status2, _ = _api(live_app, probe_path, user=user, password=password)
|
||||||
if status2 == 200:
|
if status2 == 200:
|
||||||
break
|
break
|
||||||
time.sleep(5)
|
time.sleep(5)
|
||||||
|
|||||||
Reference in New Issue
Block a user