Files
rustfs/scripts/validation_registry.py
T
Chris An 4fcbf60f10 ci: unify validation planning, artifacts and suite orchestration (#9059)
* ci: unify validation planning, artifacts and suite orchestration

* fix(ci): preserve JSON diagnostics for release checks

* fix(ci): keep manual attempts without plans visible

* fix(ci): distinguish nextest setup scripts from test results

* ci: reuse extracted archives and consolidate duplicate checks
2026-10-30 02:02:38 +08:00

207 lines
11 KiB
Python

#!/usr/bin/env python3
"""One responsibility registry for suite selection and periodic health monitors."""
from __future__ import annotations
import argparse
import json
import os
from pathlib import Path
import re
ROOT = Path(__file__).resolve().parents[1]
REGISTRY = ROOT / ".github/scheduled-validations.json"
def require(condition, message):
if not condition:
raise ValueError(message)
def load_registry(path=REGISTRY):
value = json.loads(Path(path).read_text())
require(isinstance(value, dict) and type(value.get("schema")) is int and value["schema"] == 1,
"unsupported validation registry")
require(set(value.get("plans", {})) == {"nightly", "weekly", "monthly"}, "missing validation cadence")
for plan in value["plans"].values():
require(isinstance(plan, dict) and plan.get("timezone") == "UTC"
and isinstance(plan.get("cadences"), list) and plan["cadences"]
and set(plan["cadences"]) <= {"nightly", "weekly", "monthly"}, "invalid cadence plan")
workflows = value.get("workflows")
suites = value.get("suites")
require(isinstance(workflows, list) and workflows and isinstance(suites, list) and suites,
"empty validation responsibilities")
paths = set()
for item in workflows:
require(isinstance(item, dict), "invalid workflow responsibility")
path = item.get("workflow")
require(isinstance(path, str) and re.fullmatch(r"\.github/workflows/[a-z0-9-]+\.yml", path)
and path not in paths, "invalid or duplicate workflow responsibility")
paths.add(path)
require(item.get("owner") and item.get("name") and item.get("roles") and item.get("events"),
f"incomplete responsibility: {path}")
require(item.get("timezone") == "UTC" and item.get("policy") in ("active", "paused", "disabled"),
f"invalid execution policy: {path}")
require(item.get("monitor") in ("workflow", "complete-chain", "functional-chain", "none"),
f"invalid monitor: {path}")
if item["monitor"] in ("workflow", "complete-chain"):
require(type(item.get("max_age_hours")) is int and item["max_age_hours"] > 0,
f"missing freshness budget: {path}")
if item["monitor"] == "none":
require(item.get("monitor_reason"), f"missing monitor exclusion: {path}")
ids = []
for suite in suites:
require(isinstance(suite, dict) and isinstance(suite.get("id"), str)
and re.fullmatch(r"[a-z][a-z0-9-]*", suite["id"]) and suite["id"] not in ids,
"invalid or duplicate suite")
ids.append(suite["id"])
require(suite.get("workflow") in paths and suite.get("cadence") in ("nightly", "weekly", "monthly")
and suite.get("timezone") == "UTC" and suite.get("resource_lock") and suite.get("environment")
and suite.get("owner") and suite.get("evidence") and type(suite.get("required")) is bool
and isinstance(suite.get("dependencies"), list), "incomplete suite responsibility")
require(suite["required"] or suite.get("optional_reason"), "optional suite needs a reason")
for suite in suites:
require(set(suite["dependencies"]) <= set(ids) and suite["id"] not in suite["dependencies"],
"unknown or self-dependent suite")
return value
def suite_ids(registry=None):
return tuple(item["id"] for item in (registry or load_registry())["suites"])
def workflow_entry(path, registry=None):
entries = [item for item in (registry or load_registry())["workflows"] if item["workflow"] == path]
require(len(entries) == 1, f"unregistered workflow: {path}")
return entries[0]
def monitored_workflows(registry=None):
return [item for item in (registry or load_registry())["workflows"]
if item["monitor"] in ("workflow", "complete-chain")]
def select_plan(registry, cadence="nightly", requested="", event="workflow_dispatch"):
require(cadence in registry["plans"], "unknown validation plan")
require(event in ("workflow_dispatch", "workflow_run", "schedule"), "unsupported plan event")
require(not requested or event == "workflow_dispatch", "automatic plans cannot override suite selection")
by_id = {item["id"]: item for item in registry["suites"]}
explicit = bool(requested and requested != "all")
names = requested.split(",") if explicit else [item["id"] for item in registry["suites"]
if item["cadence"] in registry["plans"][cadence]["cadences"]]
require(names and all(name == name.strip() and name in by_id for name in names)
and len(names) == len(set(names)), "unknown, empty or duplicate selected suite")
selected = set()
def add(name, ancestors=()):
require(name not in ancestors, "cyclic suite dependency")
for dependency in by_id[name]["dependencies"]:
add(dependency, ancestors + (name,))
selected.add(name)
for name in names:
add(name)
ordered = [name for name in by_id if name in selected]
for name in ordered:
require(workflow_entry(by_id[name]["workflow"], registry)["policy"] == "active",
f"selected suite is disabled or paused: {name}")
return {"schema": 1, "cadence": cadence, "timezone": registry["plans"][cadence]["timezone"],
"suites": ordered, "optional_suites": [] if explicit else [name for name in ordered if not by_id[name]["required"]],
"environments": sorted({by_id[name]["environment"] for name in ordered}),
"resource_locks": sorted({by_id[name]["resource_lock"] for name in ordered}),
"periodic_workflows": [item["workflow"] for item in registry["workflows"]
if item["cadence"] in registry["plans"][cadence]["cadences"]
and item["policy"] == "active" and item["monitor"] in ("workflow", "complete-chain")]}
def validate_plan(plan, registry=None):
registry = registry or load_registry()
require(isinstance(plan, dict) and plan.get("schema") == 1, "invalid suite selection plan")
# Explicit single-suite invocations have no optional lanes; an automatic
# plan can omit only optional lanes declared in the registry.
full = select_plan(registry, plan.get("cadence", ""))
selected = plan.get("suites")
require(isinstance(selected, list) and selected
and selected == [name for name in suite_ids(registry) if name in selected], "invalid selected suites")
optional = plan.get("optional_suites")
require(isinstance(optional, list) and len(optional) == len(set(optional))
and set(optional) <= set(full["optional_suites"]) & set(selected), "invalid optional suites")
for suite in registry["suites"]:
if suite["id"] in selected:
require(set(suite["dependencies"]) <= set(selected), "plan omits a suite dependency")
return plan
def watchdog_event(event, registry=None):
run = event.get("workflow_run", {})
matches = [item for item in monitored_workflows(registry)
if item["workflow"] == run.get("path") and item["name"] == run.get("name")]
if len(matches) != 1:
return False
item = matches[0]
expected_event = "workflow_run" if item["monitor"] == "complete-chain" else "schedule"
repository = event.get("repository", {})
return (item["policy"] == "active" and run.get("event") == expected_event
and run.get("head_branch") == repository.get("default_branch")
and (run.get("head_repository") or {}).get("full_name") == repository.get("full_name")
and run.get("conclusion") != "success"
and (run.get("conclusion") != "failure" or item.get("failure_owner") == "watchdog"))
def check_wiring(registry, root=ROOT):
declared = {item["workflow"] for item in registry["workflows"]}
actual = {str(path.relative_to(root)) for path in (root / ".github/workflows").glob("*.yml")}
require(declared == actual, "registry workflow coverage differs: " + ", ".join(sorted(declared ^ actual)))
for item in registry["workflows"]:
source = (root / item["workflow"]).read_text()
name = re.search(r"^name: (.+)$", source, re.M)
require(name and name.group(1).strip("\"'") == item["name"], "workflow name differs from registry")
trigger = re.search(r"^on:\n(.*?)(?=^[^ #\n]|\Z)", source, re.M | re.S)
require(trigger is not None, "workflow has no event declaration")
events = re.findall(r"^ (\w+):", trigger.group(1), re.M)
schedules = re.findall(r"^\s+- cron:\s*['\"]([^'\"]+)", trigger.group(1), re.M)
require(events == item["events"] and schedules == item["schedule"],
"workflow events or cadence differ from registry: " + item["workflow"])
for suite in registry["suites"]:
source = (root / suite["workflow"]).read_text()
require("repository_dispatch" not in source and "Continue functional chain" not in source,
"retired functional relay remains wired")
require("group: " + suite["resource_lock"] in source and "cancel-in-progress: false" in source,
"suite resource lock differs from registry")
require("functional_chain_evidence.py record --suite " + suite["id"] in source,
"suite evidence exit differs from registry")
return True
def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("mode", choices=("check", "select", "watchdog"))
parser.add_argument("--registry", type=Path, default=REGISTRY)
parser.add_argument("--cadence", default=os.environ.get("VALIDATION_PLAN") or "nightly")
parser.add_argument("--suites", default=os.environ.get("VALIDATION_SUITES", ""))
parser.add_argument("--event", default=os.environ.get("GITHUB_EVENT_NAME", "workflow_dispatch"))
parser.add_argument("--output", type=Path)
args = parser.parse_args()
registry = load_registry(args.registry)
if args.mode == "check":
check_wiring(registry)
print(f"Validated {len(registry['workflows'])} workflow responsibilities and {len(registry['suites'])} suites")
elif args.mode == "watchdog":
selected = watchdog_event(json.loads(Path(os.environ["GITHUB_EVENT_PATH"]).read_text()), registry)
with open(os.environ["GITHUB_OUTPUT"], "a") as output:
output.write("selected=" + str(selected).lower() + "\n")
else:
plan = select_plan(registry, args.cadence, args.suites, args.event)
encoded = json.dumps(plan, separators=(",", ":"))
if args.output:
args.output.write_text(encoded + "\n")
if os.environ.get("GITHUB_OUTPUT"):
with open(os.environ["GITHUB_OUTPUT"], "a") as output:
output.write("plan=" + encoded + "\n")
for key in ("suites", "environments", "optional_suites"):
output.write(key + "=" + json.dumps(plan[key], separators=(",", ":")) + "\n")
print(encoded)
if __name__ == "__main__":
main()