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

@ -120,6 +120,9 @@ class CliTests(unittest.TestCase):
(root / "cfg.json").write_text(json.dumps(cfg))
admission = CLI.X.REPO / "examples/bounded-execution-admission/bind-rotation-svc-authelia.yaml"
import contextlib, io
wait = CLI.OPS.wait_reachable
self.addCleanup(setattr, CLI.OPS, "wait_reachable", wait)
CLI.OPS.wait_reachable = lambda addr, **kw: wait(addr, **{**kw, "timeout": 0.2, "interval": 0.1})
with contextlib.redirect_stdout(io.StringIO()) as out:
code = CLI.main(["--admission", str(admission), "--approval-id", "apr-1", "--config", str(root / "cfg.json")])
self.assertEqual(code, 4)

View file

@ -162,6 +162,39 @@ class JourneyOnly(Base):
self.run_cli(self.cfg(), "--approval-id", "apr-1")
class WaitReachable(unittest.TestCase):
"""k3s applies NetworkPolicy to a new pod a moment late: early connections are refused (live, 2026-10-31)."""
def fake(self, outcomes):
self.now, self.slept, calls = 0.0, [], list(outcomes)
def open_(request, timeout):
outcome = calls.pop(0)
if isinstance(outcome, BaseException):
raise outcome
return contextlib.nullcontext()
def sleep(seconds):
self.slept.append(seconds)
self.now += seconds
return dict(opener=open_, clock=lambda: self.now, sleep=sleep)
def test_refused_then_reachable(self):
refused = OPS.urllib.error.URLError(ConnectionRefusedError())
waited = OPS.wait_reachable("http://bao:8200", **self.fake([refused, refused, "ok"]))
self.assertEqual((waited, self.slept), (2.0, [1, 1]))
def test_any_http_answer_is_reachable(self):
sealed = OPS.urllib.error.HTTPError("http://bao/v1/sys/health", 503, "sealed", {}, io.BytesIO(b""))
self.assertEqual(OPS.wait_reachable("http://bao:8200", **self.fake([sealed])), 0.0)
def test_gives_up_after_the_timeout(self):
refused = OPS.urllib.error.URLError(ConnectionRefusedError())
with self.assertRaises(OPS.Unreachable):
OPS.wait_reachable("http://bao:8200", timeout=3, **self.fake([refused] * 10))
self.assertEqual(self.slept, [1, 1, 1])
class Renewal(Base):
def test_logs_in_again_before_the_lease_ends_and_revokes_the_old_token(self):
self.bao.lease = 1 # renew_at = max(1 - margin, 0.5 s)