test: add immutable Linux late-slope diagnostic tooling

This commit is contained in:
Joseph Doherty
2026-11-10 17:59:15 -05:00
parent 7023285a3e
commit 613369fc54
7 changed files with 1288 additions and 0 deletions
@@ -0,0 +1,129 @@
"""Checks shared by immutable artifact preparation and launch."""
import json
import os
from pathlib import Path
import re
import stat
import subprocess
import sys
from uuid import UUID
sys.dont_write_bytecode = True
from analyze import APP, RUNTIME, SETTINGS, digest, local_context, read_object, require
TOOLS = ("analyze.py", "bundle.py", "launch.py", "container.sh", "README.md")
TEST_DIRECTORY = f"tests/Server/{APP}"
CANONICAL_SOURCES = tuple(f"{TEST_DIRECTORY}/{name}" for name in (
"Sdk2LifecycleSoakTests.cs", "Sdk2LifecycleLateSlopeTests.cs", f"{APP}.csproj"))
SDK_PACKAGES = {
f"OPCFoundation.NetStandard.Opc.Ua.{component}/2.0.1"
for component in ("Client", "Configuration", "Core", "Core.Schema", "Core.Types",
"Security.Certificates", "Server", "Types")
}
GATEWAY_PACKAGES = {f"ZB.MOM.WW.HistorianGateway.{component}/1.0.0" for component in ("Client", "Contracts")}
def context_identity(name):
require(re.fullmatch(r"[A-Za-z0-9_.-]+", name), "Invalid local Docker context name")
require(not any(os.environ.get(key) for key in ("DOCKER_HOST", "DOCKER_TLS", "DOCKER_TLS_VERIFY", "DOCKER_CERT_PATH"))
and os.environ.get("DOCKER_CONTEXT", name) in ("", name), "Docker endpoint or TLS override is set")
result = subprocess.check_output(["docker", "context", "inspect", name], text=True, timeout=15)
contexts = json.loads(result)
require(len(contexts) == 1 and contexts[0]["Name"] == name, "Expected one explicit Docker context")
identity = {"name": name, "endpoint": contexts[0]["Endpoints"]["docker"]["Host"]}
require(local_context(identity) and Path(identity["endpoint"][len("unix://"):]).is_socket(),
"The reviewed Docker context must use a local Unix socket")
return identity
def image_identity(image, context):
require(re.fullmatch(r"sha256:[0-9a-f]{64}", image), "Use a locally cached immutable image ID")
result = subprocess.check_output(["docker", "--host", context["endpoint"], "image", "inspect", image], text=True, timeout=15)
images = json.loads(result)
require(len(images) == 1, "Expected one cached image")
value = images[0]
require((value["Id"], value["Os"], value["Architecture"]) == (image, "linux", "arm64"),
"The immutable image must be Linux ARM64")
return {"id": image, "os": "linux", "architecture": "arm64"}
def manifest(root):
"""Hash the exact regular-file tree and exclude only the receipt that contains it."""
result = {}
for path in sorted(root.rglob("*")):
kind = path.lstat().st_mode
require(stat.S_ISDIR(kind) or stat.S_ISREG(kind), f"Unsupported artifact type {path.relative_to(root)}")
if stat.S_ISREG(kind) and path != root / "receipt.json":
result[path.relative_to(root).as_posix()] = digest(path)
return result
def check_elf(path):
with path.open("rb") as stream:
header = stream.read(20)
require(header[:6] == b"\x7fELF\x02\x01" and header[18:20] == b"\xb7\x00",
f"{path.name} is not ELF64 AArch64")
def check_application(app):
check_elf(app / APP)
require(os.access(app / APP, os.X_OK), "The Linux apphost is not executable")
check_elf(app / "libcoreclr.so")
options = read_object(app / f"{APP}.runtimeconfig.json")["runtimeOptions"]
frameworks = options["includedFrameworks"]
require(options["tfm"] == "net10.0" and "framework" not in options and "frameworks" not in options
and isinstance(frameworks, list) and any(value["name"] == "Microsoft.NETCore.App" for value in frameworks)
and all(value["version"] == RUNTIME for value in frameworks), "Wrong self-contained application runtime")
dependencies = read_object(app / f"{APP}.deps.json")
require(dependencies["runtimeTarget"]["name"] == ".NETCoreApp,Version=v10.0/linux-arm64", "Wrong application RID")
libraries = dependencies["libraries"]
sdk = {key for key in libraries if key.startswith("OPCFoundation.")}
gateway = {key for key in libraries if key.startswith("ZB.MOM.WW.HistorianGateway.")}
require(sdk == SDK_PACKAGES and gateway == GATEWAY_PACKAGES, "Expected exact SDK 2.0.1 and HistorianGateway 1.0.0 graph")
return sorted(sdk), sorted(gateway)
def check_modules(modules, files):
require(isinstance(modules, list), "Missing managed module identities")
seen = set()
for module in modules:
name = module["file"]
require(name in files and name.startswith("app/") and name.endswith(".dll") and name not in seen,
"Unexpected or duplicate managed module file")
require(module["assembly"] and re.fullmatch(r"\d+(?:\.\d+){3}", module["version"])
and UUID(module["mvid"]).int != 0, "Invalid assembly version or MVID")
seen.add(name)
required = {f"app/{APP}.dll", "app/Opc.Ua.Server.dll", "app/System.Private.CoreLib.dll",
"app/ZB.MOM.WW.HistorianGateway.Client.dll", "app/ZB.MOM.WW.HistorianGateway.Contracts.dll"}
require(required <= seen, "Missing application, runtime or dependency module identity")
def verify_bundle(root, expected_hash):
path = root / "receipt.json"
require(path.is_file() and not path.is_symlink() and re.fullmatch(r"[0-9a-f]{64}", expected_hash)
and digest(path) == expected_hash, "Reviewed bundle receipt hash differs")
receipt = read_object(path)
require(receipt.get("kind") == "bundle" and all(type(receipt.get(key)) is type(value) and receipt[key] == value
for key, value in SETTINGS.items()), "Invalid fixed diagnostic settings")
require(re.fullmatch(r"[0-9a-f]{40}", receipt["sourceRevision"]), "Invalid committed source revision")
files = manifest(root)
require(receipt["files"] == files, "The immutable bundle file set or a file hash changed")
require({name for name in files if name.startswith("tools/")} == {f"tools/{name}" for name in TOOLS},
"Unexpected bundled tools")
sdk, gateway = check_application(root / "app")
require(receipt["sdkLibraries"] == sdk and receipt["gatewayLibraries"] == gateway,
"Dependency receipt differs from the published application")
check_modules(receipt["managedModules"], files)
require(receipt["imageIdentity"] == {"id": receipt["image"], "os": "linux", "architecture": "arm64"}
and re.fullmatch(r"sha256:[0-9a-f]{64}", receipt["image"]), "Invalid immutable image identity")
require(local_context(receipt["dockerContext"]), "Invalid reviewed local Docker context")
require(receipt["sourceFiles"] and all(re.fullmatch(r"[0-9a-f]{64}", value) for value in receipt["sourceFiles"].values()),
"Missing committed source hashes")
require(set(CANONICAL_SOURCES) <= receipt["sourceFiles"].keys(), "Missing canonical test or project hash")
for name in TOOLS:
source = "scripts/validation/opcua-lifecycle-memory/late-slope/" + name
require(receipt["sourceFiles"].get(source) == files[f"tools/{name}"], f"Bundled {name} differs from source")
return receipt