observer: use native HTTP readiness without interpreter startup
This commit is contained in:
parent
658ddaddd1
commit
7a81c3c41e
@ -55,10 +55,8 @@ spec:
|
||||
requests: {cpu: 10m, memory: 16Mi}
|
||||
limits: {cpu: 100m, memory: 64Mi}
|
||||
readinessProbe:
|
||||
exec:
|
||||
command: [python, -c, "import socket; socket.create_connection(('127.0.0.1', 8081), 2).close()"]
|
||||
# Starting an interpreter under the ARM CPU quota can exceed the
|
||||
# default one-second exec timeout, even when the listener is healthy.
|
||||
# Probe through nginx; the receipt listener remains loopback-only.
|
||||
httpGet: {path: /healthz, port: 8080}
|
||||
timeoutSeconds: 5
|
||||
periodSeconds: 15
|
||||
volumeMounts:
|
||||
|
||||
@ -29,6 +29,14 @@ http {
|
||||
location = /lesavka-av-test { return 308 /av-test/; }
|
||||
location = /lesavka-av-test/ { return 308 /av-test/; }
|
||||
location = /lesavka-av-test/index.html { return 308 /av-test/; }
|
||||
# Kubelet checks the running listener without spawning Python each time.
|
||||
# This path is outside the observer's public ingress prefixes.
|
||||
location = /healthz {
|
||||
proxy_pass http://127.0.0.1:8081/healthz;
|
||||
proxy_connect_timeout 2s;
|
||||
proxy_read_timeout 2s;
|
||||
proxy_pass_request_headers off;
|
||||
}
|
||||
location = /av-test { return 308 /av-test/; }
|
||||
location = /av-test/ {
|
||||
if ($request_method !~ ^(GET|HEAD)$) { return 405; }
|
||||
|
||||
@ -28,6 +28,9 @@ class Handler(BaseHTTPRequestHandler):
|
||||
self.wfile.write(body)
|
||||
|
||||
def do_GET(self):
|
||||
"""Answer the internal readiness check without reading receipt state."""
|
||||
if self.path == "/healthz":
|
||||
return self.reply(204)
|
||||
self.reply(405)
|
||||
|
||||
do_HEAD = do_GET
|
||||
|
||||
41
testing/tests/test_av_observer_health.py
Normal file
41
testing/tests/test_av_observer_health.py
Normal file
@ -0,0 +1,41 @@
|
||||
"""Exercise readiness over HTTP without exposing receipt operations or content."""
|
||||
|
||||
import importlib.util
|
||||
from pathlib import Path
|
||||
import threading
|
||||
import urllib.error
|
||||
import urllib.request
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
def test_health_is_read_only_and_does_not_open_other_get_paths(monkeypatch, capfd):
|
||||
"""A real listener answers health, keeps API GET closed, and emits no logs."""
|
||||
source = (Path(__file__).resolve().parents[2] / "services/bstein-dev-home"
|
||||
/ "av-observer/report-service/server.py")
|
||||
monkeypatch.syspath_prepend(str(source.parent))
|
||||
spec = importlib.util.spec_from_file_location("observer_health_server", source)
|
||||
module = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(module)
|
||||
# An object without receipt methods fails if health ever accesses that state.
|
||||
server = module.Server(("127.0.0.1", 0), "https://example.invalid", object())
|
||||
worker = threading.Thread(target=server.serve_forever, daemon=True)
|
||||
worker.start()
|
||||
client = urllib.request.build_opener(urllib.request.ProxyHandler({}))
|
||||
root = f"http://127.0.0.1:{server.server_port}"
|
||||
try:
|
||||
with client.open(root + "/healthz", timeout=2) as response:
|
||||
assert response.status == 204
|
||||
assert response.read() == b""
|
||||
with pytest.raises(urllib.error.HTTPError) as error:
|
||||
client.open(root + "/av-test/api/start", timeout=2)
|
||||
assert error.value.code == 405
|
||||
with pytest.raises(urllib.error.HTTPError) as error:
|
||||
client.open(urllib.request.Request(root + "/av-test/api/start", data=b"{}"), timeout=2)
|
||||
assert error.value.code == 403
|
||||
finally:
|
||||
server.shutdown()
|
||||
server.server_close()
|
||||
worker.join(timeout=2)
|
||||
captured = capfd.readouterr()
|
||||
assert not captured.out and not captured.err
|
||||
Loading…
x
Reference in New Issue
Block a user