mirror of
https://github.com/openglow-org/forgefirm.git
synced 2026-10-15 17:19:55 -07:00
Catalog: image.license-bundle and image.network-boot leave; the network boot's board checks fold into image.health
Each left after its host proofs passed, with the coverage lint clean at the pins, its only failure the known setup.check-lens entry on src/lensfind.*: - image.license-bundle: the pack half is test_image.py's PackLicensesTests on pack-licenses.sh (forgefirm0d63f87); the serve half is host_routes.sh's license cases, the manifest, the bundle and the Licenses page read with no token (forgectrl c9e3347). Its src/main.c stays covered by image.health, and its three pages by forgectrl.panel-serves (src/ui/**). The 40-package and 5-text counts leave with it. - image.network-boot: the interfaces file's DHCPv6 check is NetworkBootTests' case on the BSP layer's file (forgefirm0d63f87). The board checks move into image.health, unchanged: wlan0 in ifup's state file and no ifup or ifdown running; a getty on ttymxc0; at least one udhcpc, each with -b as its own word and parent pid 1; no udhcpc6, dhcp6c or odhcp6c running or on the PATH, and no /usr/share/udhcpc/default6.script; on wlan0 disable_ipv6 0, accept_ra neither empty nor 0, and a link-local address, the global address logged. image.health's description says so. The interfaces file carries no -b, so -b is checked here, on the client that runs. No HOST_TESTED row: network-boot covered nothing, and license-bundle's paths stay covered by the two tests that keep them. image.health's covers are unchanged. The helpers ifstate_names, dhcp6_in_interfaces, parse_stat, link_addrs, _procs and _read, and the module's constants, stay as they were; only the two functions and the inside of image_health changed. Fingerprints: image.health's moved and no other id's (the module's shared text, which every exthost test reads through its import of kernel_config, is unchanged). image.health is always-required, so the next campaign reruns it. Proof: the coverage lint, 53 tests (forgectrl, src/lensfind.* its one entry); pyflakes; forgetest's unit suite on Linux (python3 -B -m unittest discover -s tests), 335 OK.
This commit is contained in:
@@ -127,7 +127,13 @@ def fds_of(pid):
|
|||||||
"stops before it on a machine with no package installed and nothing running under a "
|
"stops before it on a machine with no package installed and nothing running under a "
|
||||||
"pool account, the data directory open for search on a machine with an account, the file modes the release depends on, and the mounts: the "
|
"pool account, the data directory open for search on a machine with an account, the file modes the release depends on, and the mounts: the "
|
||||||
"rootfs read-only, /data writable, the account files and the banner rendered "
|
"rootfs read-only, /data writable, the account files and the banner rendered "
|
||||||
"into tmpfs, the sshd host keys on /data, the factory slots on the dev image only.")
|
"into tmpfs, the sshd host keys on /data, the factory slots on the dev image only. "
|
||||||
|
"And the boot does not wait on the network: ifup has returned for wlan0 (it is "
|
||||||
|
"in the state file and no ifup is running) and the console login is up; each "
|
||||||
|
"DHCP client runs with -b and has left ifup (its parent is pid 1); no DHCPv6 "
|
||||||
|
"client is running or on the image, nor its hook script; and IPv6 is the "
|
||||||
|
"kernel's own: enabled on wlan0, router advertisements accepted, a link-local "
|
||||||
|
"address up (a global address is logged, not checked).")
|
||||||
def image_health(ctx):
|
def image_health(ctx):
|
||||||
ev = ctx.evidence
|
ev = ctx.evidence
|
||||||
manifest = ctx.runner.manifest
|
manifest = ctx.runner.manifest
|
||||||
@@ -399,59 +405,52 @@ def image_health(ctx):
|
|||||||
ctx.check("linux-fslc" in manifest.components, "manifest lacks the kernel entry")
|
ctx.check("linux-fslc" in manifest.components, "manifest lacks the kernel entry")
|
||||||
ctx.log("manifest %s: %d components", manifest.content_sha[:12], len(manifest.components))
|
ctx.log("manifest %s: %d components", manifest.content_sha[:12], len(manifest.components))
|
||||||
|
|
||||||
|
# 10. the network boot. init starts the rest of the boot, the console
|
||||||
|
# login last, only after the networking script returns, so a client
|
||||||
|
# that waits in ifup's foreground on a server's answer holds the
|
||||||
|
# whole machine. The interfaces file starts no DHCPv6 client
|
||||||
|
# (test_image.py reads the layer's file); the bounded DHCP client
|
||||||
|
# (-b, which leaves ifup after three unanswered discovers) is checked
|
||||||
|
# here, on the client that runs.
|
||||||
|
procs = _procs()
|
||||||
|
up = ifstate_names(_read(IFSTATE, ""))
|
||||||
|
ev["ifstate"] = sorted(up)
|
||||||
|
ctx.check("wlan0" in up, "ifup has not finished wlan0: %s holds %s", IFSTATE, sorted(up))
|
||||||
|
stuck = [p for p in procs if p[2] in ("ifup", "ifdown")]
|
||||||
|
ev["ifup_running"] = [p[3] for p in stuck]
|
||||||
|
ctx.check(not stuck, "ifup is still running: %s", [p[3] for p in stuck])
|
||||||
|
logins = [p[3] for p in procs if p[2] == "getty"]
|
||||||
|
ev["getty"] = logins
|
||||||
|
ctx.check(any("ttymxc0" in c for c in logins), "no console login is running: %s", logins)
|
||||||
|
v4 = [p for p in procs if p[2] == "udhcpc"]
|
||||||
|
ev["udhcpc"] = [p[3] for p in v4]
|
||||||
|
ctx.check(v4, "no DHCP client is running")
|
||||||
|
for pid, ppid, _comm, cmd in v4:
|
||||||
|
ctx.check("-b" in cmd.split(), "the DHCP client runs without -b: %s", cmd)
|
||||||
|
ctx.check(ppid == 1, "the DHCP client (pid %d) is still a child of pid %d", pid, ppid)
|
||||||
|
# no DHCPv6 client: not running, not on the image
|
||||||
|
alive = [p[3] for p in procs if p[2] in DHCP6_CLIENTS]
|
||||||
|
ev["dhcp6_running"] = alive
|
||||||
|
ctx.check(not alive, "a DHCPv6 client is running: %s", alive)
|
||||||
|
for name in DHCP6_CLIENTS:
|
||||||
|
ctx.check(shutil.which(name) is None, "%s is on the image", name)
|
||||||
|
ctx.check(not os.path.exists("/usr/share/udhcpc/default6.script"),
|
||||||
|
"the DHCPv6 hook script is on the image")
|
||||||
|
# IPv6 is the kernel's own. A global address is evidence only: a
|
||||||
|
# network whose router advertisement offers no SLAAC prefix gives none.
|
||||||
|
conf = "/proc/sys/net/ipv6/conf/wlan0/"
|
||||||
|
ev["disable_ipv6"] = (_read(conf + "disable_ipv6", "") or "").strip()
|
||||||
|
ev["accept_ra"] = (_read(conf + "accept_ra", "") or "").strip()
|
||||||
|
ctx.check(ev["disable_ipv6"] == "0", "IPv6 is off on wlan0 (disable_ipv6=%r)", ev["disable_ipv6"])
|
||||||
|
ctx.check(ev["accept_ra"] not in ("", "0"),
|
||||||
|
"wlan0 does not accept router advertisements (accept_ra=%r)", ev["accept_ra"])
|
||||||
|
local, world = link_addrs(_read("/proc/net/if_inet6", ""), "wlan0")
|
||||||
|
ev["link_local"] = local
|
||||||
|
ev["global"] = world
|
||||||
|
ctx.check(local, "wlan0 has no link-local IPv6 address")
|
||||||
|
ctx.log("wlan0: %d link-local, %d global IPv6 address(es)", len(local), len(world))
|
||||||
|
|
||||||
@test("image.license-bundle", title="The license texts travel with the image",
|
|
||||||
subsystem="image", kind="auto", est_min=1,
|
|
||||||
covers=[("forgectrl", "src/main.c"), ("forgectrl", "src/ui/index.html"),
|
|
||||||
("forgectrl", "src/ui/wizard.html"), ("forgectrl", "src/ui/login.html")],
|
|
||||||
requires=["forgectrl.panel-serves"],
|
|
||||||
description="The image carries /usr/share/forgefirm/licenses.tar.gz, a gzip tar with "
|
|
||||||
"the license manifest (every installed package with its license) and the "
|
|
||||||
"license texts, and no loose common-licenses tree; the panel serves the "
|
|
||||||
"bundle, the manifest as text, and the Licenses page every footer links, "
|
|
||||||
"each without a login.")
|
|
||||||
def license_bundle(ctx):
|
|
||||||
import io
|
|
||||||
import tarfile
|
|
||||||
fc = ctx.forgectrl
|
|
||||||
ev = ctx.evidence
|
|
||||||
path = "/usr/share/forgefirm/licenses.tar.gz"
|
|
||||||
ctx.check(os.path.isfile(path), "%s is missing", path)
|
|
||||||
ctx.check(not os.path.isdir("/usr/share/common-licenses"),
|
|
||||||
"the loose common-licenses tree is still on the rootfs")
|
|
||||||
size = os.path.getsize(path)
|
|
||||||
ev["bundle_bytes"] = size
|
|
||||||
ctx.log("%s: %d bytes", path, size)
|
|
||||||
with tarfile.open(path, "r:gz") as t:
|
|
||||||
names = t.getnames()
|
|
||||||
ctx.check("common-licenses/license.manifest" in names, "no license.manifest in the bundle")
|
|
||||||
manifest = t.extractfile("common-licenses/license.manifest").read().decode("utf-8", "replace")
|
|
||||||
packages = manifest.count("PACKAGE NAME: ")
|
|
||||||
texts = sum(1 for n in names if "/generic_" in n and not n.endswith("/"))
|
|
||||||
ev["packages"] = packages
|
|
||||||
ev["generic_texts"] = texts
|
|
||||||
ctx.log("manifest lists %d packages; %d generic license entries", packages, texts)
|
|
||||||
ctx.check(packages >= 40, "only %d packages in the manifest", packages)
|
|
||||||
ctx.check(texts >= 5, "only %d generic license entries", texts)
|
|
||||||
for name in ("forgectrl", "grblhal-glowforge", "kernel-module-glowforge"):
|
|
||||||
ctx.check("RECIPE NAME: %s\n" % name in manifest, "%s is not in the manifest", name)
|
|
||||||
|
|
||||||
st, body = fc.get("/system/licenses/manifest", raw=True)
|
|
||||||
ctx.check(st == 200, "GET /system/licenses/manifest -> %s", st)
|
|
||||||
ctx.check(isinstance(body, (bytes, bytearray)) and b"PACKAGE NAME: " in body,
|
|
||||||
"the served manifest is not the manifest")
|
|
||||||
st, body = fc.get("/system/licenses", raw=True)
|
|
||||||
ctx.check(st == 200, "GET /system/licenses -> %s", st)
|
|
||||||
ctx.check(isinstance(body, (bytes, bytearray)) and len(body) == size,
|
|
||||||
"the served bundle is %s bytes, the file %d", len(body) if body else None, size)
|
|
||||||
with tarfile.open(fileobj=io.BytesIO(bytes(body)), mode="r:gz") as t:
|
|
||||||
ctx.check("common-licenses/license.manifest" in t.getnames(),
|
|
||||||
"the served bundle holds no manifest")
|
|
||||||
st, body = fc.get("/licenses", raw=True)
|
|
||||||
ctx.check(st == 200, "GET /licenses -> %s", st)
|
|
||||||
text = bytes(body).decode("utf-8", "replace") if body else ""
|
|
||||||
ctx.check("/system/licenses" in text and "PACKAGE NAME: " in text,
|
|
||||||
"the Licenses page lacks the download link or the manifest")
|
|
||||||
|
|
||||||
|
|
||||||
# ------------------------------------------------------- the network boot
|
# ------------------------------------------------------- the network boot
|
||||||
@@ -524,68 +523,3 @@ def link_addrs(text, iface):
|
|||||||
return local, world
|
return local, world
|
||||||
|
|
||||||
|
|
||||||
@test("image.network-boot", title="The boot does not wait on the network",
|
|
||||||
subsystem="image", kind="auto", est_min=1,
|
|
||||||
description="The network comes up without the boot waiting on a server. init starts the "
|
|
||||||
"rest of the boot, the console login last, only after the networking script "
|
|
||||||
"returns, so a client that waits in ifup's foreground on a server's answer "
|
|
||||||
"holds the whole machine: no login, no SSH, no control panel. ifup has "
|
|
||||||
"returned for wlan0 (it is in the state file and no ifup is running) and the "
|
|
||||||
"console login is up; the DHCP client is the bounded one (-b, which leaves "
|
|
||||||
"ifup after three unanswered discovers) and has left ifup; no DHCPv6 client "
|
|
||||||
"is named in the interfaces file, running, or on the image; and IPv6 is the "
|
|
||||||
"kernel's own: enabled on wlan0, router advertisements accepted, a link-local "
|
|
||||||
"address up. A global address is evidence only, because a network whose "
|
|
||||||
"router advertisement offers no SLAAC prefix gives none. What a hostile "
|
|
||||||
"server does to a client is a bench drill, not a test. No component covers "
|
|
||||||
"this: the interfaces file is layer content, in the platform identity of "
|
|
||||||
"every fingerprint.")
|
|
||||||
def network_boot(ctx):
|
|
||||||
ev = ctx.evidence
|
|
||||||
procs = _procs()
|
|
||||||
|
|
||||||
# 1. ifup came back, and the boot went on to the console login
|
|
||||||
state = ifstate_names(_read(IFSTATE, ""))
|
|
||||||
ev["ifstate"] = sorted(state)
|
|
||||||
ctx.check("wlan0" in state, "ifup has not finished wlan0: %s holds %s", IFSTATE, sorted(state))
|
|
||||||
stuck = [p for p in procs if p[2] in ("ifup", "ifdown")]
|
|
||||||
ev["ifup_running"] = [p[3] for p in stuck]
|
|
||||||
ctx.check(not stuck, "ifup is still running: %s", [p[3] for p in stuck])
|
|
||||||
logins = [p[3] for p in procs if p[2] == "getty"]
|
|
||||||
ev["getty"] = logins
|
|
||||||
ctx.check(any("ttymxc0" in c for c in logins), "no console login is running: %s", logins)
|
|
||||||
|
|
||||||
# 2. the DHCP client is the bounded one, and it has left ifup
|
|
||||||
v4 = [p for p in procs if p[2] == "udhcpc"]
|
|
||||||
ev["udhcpc"] = [p[3] for p in v4]
|
|
||||||
ctx.check(v4, "no DHCP client is running")
|
|
||||||
for pid, ppid, _comm, cmd in v4:
|
|
||||||
ctx.check("-b" in cmd.split(), "the DHCP client runs without -b: %s", cmd)
|
|
||||||
ctx.check(ppid == 1, "the DHCP client (pid %d) is still a child of pid %d", pid, ppid)
|
|
||||||
|
|
||||||
# 3. no DHCPv6 client: not started, not running, not on the image
|
|
||||||
text = _read(INTERFACES)
|
|
||||||
ctx.check(text is not None, "%s is missing", INTERFACES)
|
|
||||||
named = dhcp6_in_interfaces(text)
|
|
||||||
ev["interfaces_dhcp6"] = named
|
|
||||||
ctx.check(not named, "%s starts a DHCPv6 client: %s", INTERFACES, named)
|
|
||||||
alive = [p[3] for p in procs if p[2] in DHCP6_CLIENTS]
|
|
||||||
ev["dhcp6_running"] = alive
|
|
||||||
ctx.check(not alive, "a DHCPv6 client is running: %s", alive)
|
|
||||||
for name in DHCP6_CLIENTS:
|
|
||||||
ctx.check(shutil.which(name) is None, "%s is on the image", name)
|
|
||||||
ctx.check(not os.path.exists("/usr/share/udhcpc/default6.script"),
|
|
||||||
"the DHCPv6 hook script is on the image")
|
|
||||||
|
|
||||||
# 4. IPv6 is the kernel's own
|
|
||||||
conf = "/proc/sys/net/ipv6/conf/wlan0/"
|
|
||||||
ev["disable_ipv6"] = (_read(conf + "disable_ipv6", "") or "").strip()
|
|
||||||
ev["accept_ra"] = (_read(conf + "accept_ra", "") or "").strip()
|
|
||||||
ctx.check(ev["disable_ipv6"] == "0", "IPv6 is off on wlan0 (disable_ipv6=%r)", ev["disable_ipv6"])
|
|
||||||
ctx.check(ev["accept_ra"] not in ("", "0"),
|
|
||||||
"wlan0 does not accept router advertisements (accept_ra=%r)", ev["accept_ra"])
|
|
||||||
local, world = link_addrs(_read("/proc/net/if_inet6", ""), "wlan0")
|
|
||||||
ev["link_local"] = local
|
|
||||||
ev["global"] = world
|
|
||||||
ctx.check(local, "wlan0 has no link-local IPv6 address")
|
|
||||||
ctx.log("wlan0: %d link-local, %d global IPv6 address(es)", len(local), len(world))
|
|
||||||
|
|||||||
Reference in New Issue
Block a user