mirror of
https://github.com/openglow-org/forgefirm.git
synced 2026-10-15 17:19:55 -07:00
Catalog: forgectrl.lease leaves
forgectrl.lease leaves for forgectrl's tests/host_lease.sh (0086e65), which passed as build.yml runs it: with the gate open and cnc/state idle, the switches check held at its first wait names wizard:switches, kind hardware, as the holder; POST /diag/flow-verify, /curve/record, /logs/export, /mode with the mode in force, /controller/start and /settings are each 409 in the holder's name, none for an idle machine or a closed gate; the abort frees the lease and the same settings write is 200; lease.changed carries the take before the free. lease_test and super_test case K hold the lease's own rules. The catalog goes from 81 to 80 (80 @test in forgetest/forgetest/suite/). _EventStream, _lease and _text, which only it read, go with it, and so does urllib.parse, which only _EventStream used; socket stays for lan_ip. No test requires the id, and no unit test names it. The coverage lint on the tree manifest then named forgectrl src/events.c and src/events.h, and nothing else beside setup.check-lens's empty src/lensfind.* entry. One HOST_TESTED row takes them, to forgectrl:tests/events_test.c on forgectrl:.github/workflows/build.yml, which the pin c274f19 runs (./build/events_test). The lint then finds no uncovered path and no host-tested row problem across the 80 tests; its one finding is setup.check-lens's. test_manifest.py (34) passes, and pyflakes reports nothing on forgectrl.py or manifest.py. The full forgetest suite was not run. Fingerprints, measured against the parent: 26 tests move with forgectrl.py's shared text: the 4 left in it (auth, panel-serves, settings-bounds, tokens), the 5 left in exthost.py and the 17 of the setup family, whose modules import lan_ip from it (exthost.py and setup.py, and through setup.py the rest). The pending pin bumps from forgectrl c274f19 and forgeext 2d0391e already owe 24 of them; exthost.platform has been owed since30c0a05and setup.machine-name since70e13bd. This deletion adds none to the owed list. releases/ and TEST-AUDIT.md are unchanged.
This commit is contained in:
@@ -159,6 +159,9 @@ HOST_TESTED = [
|
||||
# the closed list of actions, the host's words, the catalog's fetch (the
|
||||
# routes against a live host's stand-in are tests/host_ext.sh).
|
||||
("forgectrl", "src/extpkg.*", "forgectrl:tests/extpkg_test.c", "forgectrl:.github/workflows/build.yml"),
|
||||
# The event stream: a button edge, a lease changing hands, telemetry
|
||||
# (the live order, the take before the free, is tests/host_lease.sh).
|
||||
("forgectrl", "src/events.*", "forgectrl:tests/events_test.c", "forgectrl:.github/workflows/build.yml"),
|
||||
]
|
||||
|
||||
# This repository's root, when the package runs from a checkout of it; a
|
||||
|
||||
@@ -8,7 +8,6 @@ import json
|
||||
import os
|
||||
import socket
|
||||
import time
|
||||
import urllib.parse
|
||||
|
||||
from ..catalog import test
|
||||
from .. import hw
|
||||
@@ -455,159 +454,6 @@ def panel_serves(ctx):
|
||||
"GET /curve/ladder.gcode -> %s without the ladder", st)
|
||||
|
||||
|
||||
class _EventStream:
|
||||
"""GET /events over a raw socket bound to a chosen loopback source
|
||||
address. Every address in 127.0.0.0/8 is this host, so the test can be
|
||||
several peers at once, and the daemon counts streams per peer address."""
|
||||
|
||||
def __init__(self, base, source, timeout=5.0):
|
||||
u = urllib.parse.urlsplit(base)
|
||||
self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
self.sock.bind((source, 0))
|
||||
self.sock.settimeout(timeout)
|
||||
self.sock.connect((u.hostname or "127.0.0.1", u.port or 80))
|
||||
self.sock.sendall(b"GET /events HTTP/1.1\r\nHost: %s\r\nAccept: text/event-stream\r\n"
|
||||
b"Connection: close\r\n\r\n" % (u.netloc or "127.0.0.1").encode())
|
||||
self.buf = b""
|
||||
self.eof = False # the response is over: the last chunk, or the socket closed
|
||||
head = self._until(b"\r\n\r\n")
|
||||
self.status = int(head.split(b" ", 2)[1]) if head.startswith(b"HTTP/") else 0
|
||||
self.head = head.decode("latin-1", "replace")
|
||||
|
||||
def _fill(self):
|
||||
try:
|
||||
chunk = self.sock.recv(4096)
|
||||
except socket.timeout:
|
||||
return False
|
||||
if not chunk:
|
||||
self.eof = True
|
||||
return False
|
||||
self.buf += chunk
|
||||
return True
|
||||
|
||||
def _until(self, mark):
|
||||
while mark not in self.buf:
|
||||
if not self._fill():
|
||||
break
|
||||
i = self.buf.find(mark)
|
||||
if i < 0:
|
||||
out, self.buf = self.buf, b""
|
||||
return out
|
||||
out, self.buf = self.buf[:i], self.buf[i + len(mark):]
|
||||
return out
|
||||
|
||||
def text(self, seconds):
|
||||
"""Everything the stream says in the next `seconds` (chunk framing
|
||||
and all: the checks look for event names inside it)."""
|
||||
end = time.time() + seconds
|
||||
self.sock.settimeout(0.2) # the window is the caller's, not one long read
|
||||
while time.time() < end and not self.eof:
|
||||
self._fill()
|
||||
if self.buf.endswith(b"\r\n0\r\n\r\n"):
|
||||
self.eof = True
|
||||
out, self.buf = self.buf, b""
|
||||
return out.decode("utf-8", "replace")
|
||||
|
||||
def close(self):
|
||||
try:
|
||||
self.sock.close()
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
|
||||
def _lease(fc):
|
||||
return (fc.status().get("lease") or {})
|
||||
|
||||
|
||||
def _text(body):
|
||||
return body if isinstance(body, str) else json.dumps(body)
|
||||
|
||||
|
||||
@test("forgectrl.lease", title="The machine lease: a holder refuses everything else, by name",
|
||||
subsystem="forgectrl", kind="auto", est_min=2,
|
||||
covers=[("forgectrl", "src/lease.*"), ("forgectrl", "src/main.c"), ("forgectrl", "src/status.*"),
|
||||
("forgectrl", "src/wizdark.*"), ("forgectrl", "src/diag.*"), ("forgectrl", "src/curverec.*"),
|
||||
("forgectrl", "src/jobrun.*"), ("forgectrl", "src/update.*"), ("forgectrl", "src/logs.*"), ("forgectrl", "src/super.*"),
|
||||
("forgectrl", "src/events.*")],
|
||||
description="The switches check is started and left waiting at its first prompt: it moves "
|
||||
"nothing, and it holds the machine lease for as long as it waits. /status must "
|
||||
"name it as the holder (wizard:switches, kind hardware), the event stream must "
|
||||
"report lease.changed, and everything that asks the lease must be refused with "
|
||||
"409 and the holder's name: a diagnostic, the dose-curve recorder, a log export, a "
|
||||
"mode switch to the mode already in force, POST /controller/start, and POST "
|
||||
"/settings (with 'settings are locked'). Only requests that would do no harm if "
|
||||
"the lease failed are made: no reboot, no boot-slot change, no update job. The "
|
||||
"check is then aborted, and the lease must read free, lease.changed must say so, "
|
||||
"and POST /settings must be accepted again. Whatever a failed refusal started is "
|
||||
"stopped on the way out.")
|
||||
def lease(ctx):
|
||||
from .setup_dark import dark
|
||||
|
||||
ev = ctx.evidence
|
||||
fc = hw.Forgectrl()
|
||||
wid, owner = "switches", "wizard:switches"
|
||||
rest = _lease(fc)
|
||||
ctx.check("holder" in rest and rest["holder"] is None, "the machine is not free at the start: %s", rest)
|
||||
ctx.check(set(rest.get("observed") or {}) == {"sender", "motors_released"},
|
||||
"what is observed outside the lease is not reported: %s", rest)
|
||||
mode = (fc.get("/mode")[1] or {}).get("mode")
|
||||
units = fc.settings().get("ui_units") or "metric"
|
||||
stream = _EventStream(fc.base, "127.0.0.6")
|
||||
ctx.check(stream.status == 200, "GET /events -> %s", stream.status)
|
||||
stream.text(0.5)
|
||||
started = False
|
||||
try:
|
||||
st, body = fc.post("/wiz/%s/start" % wid)
|
||||
ctx.check(st == 200, "the %s check did not start (%s %s)", wid, st, _text(body))
|
||||
started = True
|
||||
ok = ctx.wait_for(lambda: (_lease(fc).get("holder") or {}).get("owner") == owner, 10, poll=0.2)
|
||||
held = _lease(fc).get("holder") or {}
|
||||
ev["holder"] = held
|
||||
ctx.log("the lease while the check waits: %s", held)
|
||||
ctx.check(ok is not None and held.get("kind") == "hardware" and "switches" in (held.get("words") or ""),
|
||||
"the lease does not name the running check: %s", held)
|
||||
|
||||
refused = {}
|
||||
for name, path, kw in (
|
||||
("a diagnostic", "/diag/flow-verify", {}),
|
||||
("the recorder", "/curve/record", {}),
|
||||
("a log export", "/logs/export", {}),
|
||||
("a mode switch", "/mode", {"params": {"controller": mode}}),
|
||||
("the controller's start", "/controller/start", {}),
|
||||
("a settings write", "/settings", {"params": {"ui_units": units}})):
|
||||
st, body = fc.post(path, **kw)
|
||||
refused[name] = [st, _text(body)[:160]]
|
||||
ctx.check(st == 409 and "holds the machine" in _text(body) and "switches" in _text(body),
|
||||
"%s under the wizard's hold -> %s %s", name, st, _text(body)[:160])
|
||||
ctx.check("settings are locked" in refused["a settings write"][1],
|
||||
"the settings refusal does not say they are locked: %s", refused["a settings write"])
|
||||
ev["refused"] = refused
|
||||
ctx.check((_lease(fc).get("holder") or {}).get("owner") == owner,
|
||||
"a refused request took the lease away: %s", _lease(fc))
|
||||
finally:
|
||||
if started:
|
||||
fc.post("/wiz/%s/abort" % wid)
|
||||
ctx.wait_for(lambda: not dark(fc).get("running"), 30)
|
||||
# Nothing a failed refusal may have started is left running.
|
||||
fc.post("/diag/abort")
|
||||
fc.post("/curve/stop")
|
||||
|
||||
ok = ctx.wait_for(lambda: _lease(fc).get("holder") is None, 30, poll=0.5)
|
||||
ctx.check(ok is not None, "the lease was not given back after the abort: %s", _lease(fc))
|
||||
st, body = fc.post("/settings", params={"ui_units": units})
|
||||
ctx.check(st == 200, "POST /settings with the machine free again -> %s %s", st, _text(body)[:120])
|
||||
text = stream.text(2.0)
|
||||
stream.close()
|
||||
changes = [l for l in text.splitlines() if l.startswith("data:") and "owner" in l]
|
||||
ev["lease_changed"] = changes
|
||||
ctx.log("the event stream: %s", changes)
|
||||
i_take, i_free = text.find('"owner":"%s"' % owner), text.find('"owner":null')
|
||||
ctx.check("event: lease.changed" in text and 0 <= i_take < i_free,
|
||||
"lease.changed did not report the hold and then its end: %r", text[-300:])
|
||||
ctx.log("PASS: %s held the machine, six requests were refused by name, the abort freed it, and the "
|
||||
"event stream said both", owner)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------- scoped tokens
|
||||
|
||||
def _words_of(body):
|
||||
|
||||
Reference in New Issue
Block a user