NK-WP-0056-T10: wait for OpenBao reachability before the first login

First live journey Job was refused at setup (URLError). netcheck showed every
connection refused at 0 s and open from 3 s: k3s applies NetworkPolicy to a
new pod a moment late. Both CLI modes now poll OpenBao's unauthenticated
sys/health for up to 30 s before logging in; only connection errors retry.
48 executor tests.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 5502@bnt-lap001
Assistant-Session: 68b3b02f-5d0f-4e71-8421-796f62e50196
This commit is contained in:
tegwick 2026-10-31 20:33:17 +01:00
parent 110eb63058
commit 15c9cdbef8
5 changed files with 76 additions and 0 deletions

View file

@ -61,6 +61,32 @@ def _opener(ca_file: str | None):
# --------------------------------------------------------------------------------------------------
def wait_reachable(addr: str, *, timeout: float = 30, interval: float = 1, ca_file: str | None = None,
opener=None, clock=time.monotonic, sleep=time.sleep) -> float:
"""Wait until OpenBao answers on the network, before the first login. k3s applies NetworkPolicy to a new pod a
moment after it starts, and connections in that window are refused (seen live 2026-10-31: refused at 0 s,
open at 3 s). Polls the unauthenticated sys/health. Any HTTP answer, including 429/473/503, counts as reachable;
only connection errors are retried. Returns the seconds waited; raises Unreachable after `timeout`."""
open_ = opener or _opener(ca_file).open
start = clock()
while True:
request = urllib.request.Request(addr.rstrip("/") + "/v1/sys/health", method="GET")
try:
with open_(request, timeout=5):
return clock() - start
except urllib.error.HTTPError as error:
error.close()
return clock() - start
except (urllib.error.URLError, OSError):
if clock() - start >= timeout:
raise Unreachable("openbao not reachable") from None
sleep(interval)
class Unreachable(Exception):
pass
class OpenBao:
def __init__(self, addr: str, *, token: str | None = None, k8s_role: str | None = None,
jwt_file: str | None = None, k8s_mount: str = "kubernetes", ca_file: str | None = None,