Files
rustfs/scripts/inspect_validation_runner.py
T
cxymds 4a63e53161 test(validation): run canonical cases and build pinned Heal tools (#8455)
* test(validation): add pinned auto-testing manual lane

* Install pinned ILM client for canonical validation lane

* Prepare Python client for pool validation cases

* Install validation client for replication cases

* test(validation): provide the Python client for Heal cases

* test(validation): build the candidate-bound Heal inspector artifact

* test(validation): expose tool artifacts through the existing manual lane

* test(validation): verify pinned Heal tools before fleet execution

* test(validation): verify Heal tools against released shard captures

* ci: audit retained validation environments before finalization

* test(validation): add optional native Heal regression tooling

* test(validation): route native tools through the existing driver

* Run validation tool resolution on hosted Linux without acquiring the fleet lock

* test(validation): bind C07 to pinned native authority evidence

* test(validation): build optional C10 fixture codec on hosted Linux

* test(ci): bind C10 execution to the verified shared codec

* feat(ci): expose read-only validation fleet inventory

* docs(ci): describe supported validation recovery

* test(ci): add isolated manager native validation builds

* test(ci): expose manager regressions through tools-only dispatch

* ci(validation): consume pinned manager native prerequisites

* fix(heal): preserve typed bucket errors for bounded retries

* fix(heal): preserve typed errors and SSE-C response headers

* ci(validation): publish isolated immutable candidate packages

* test(validation): enforce private candidate manifest schema

* test(validation): verify isolated package publication failures

* ci(validation): dispatch isolated packages through the driver

* fix(heal): deeply verify read repair payload failures

* test(ecstore): verify incarnation fence at repair commit

* ci(validation): verify C11 native storage evidence

* fix(ci): isolate validation package publication credentials

* test(ci): archive scoped F07 directory fsync evidence

* fix(ecstore): retain typed quorum errors for missing parts

* chore(ecstore): shrink typed-error ratchet baseline

* fix(ecstore): retain below-quorum data during no-remove heal

* fix(ci): allow recovery of completed cancelled validation runs

* fix(heal): retain exhausted admin batch repair receipts

* fix(observability): retain typed object error diagnostics

* fix(s3): map typed RPC unavailability to service refusal

* fix(filemeta): classify supported inline framing damage as corrupt

* test(ecstore): exercise heal during committed overwrite cleanup

* ci(validation): admit source-bound G01 native evidence before fleet execution

* test(ecstore): fence version races and bind cleanup slots

* ci(validation): require complete race native runtime preflight

* test(ecstore): fence marker and batch deletes during heal

* ci(validation): require G04 native and runtime admission

* test(ecstore): fence multipart continue complete and abort during heal

* test(ecstore): cover default heal owners and copy metadata races

* ci(validation): admit source-bound owner and copy race proofs

* test(scanner): assert typed corruption after metadata truncation

* fix(ci): require complete native admission for multipart races

* test(validation): wire I05 queued-successor native prerequisites

* fix(scanner): bind object heals to the scanning disk scope

* test(scanner): reuse storage boundary for scope regression

* style: format RPC digest precedence assertion

* fix(heal): query automatic tasks on their owning node

* test(admin): reuse error facade in heal query regression

* fix(heal): retain cancellation after successful bucket storage I/O

* fix(ci): retain validation evidence outside runner temp

* fix(heal): retain resolved object identity on terminal failures

* test(scanner): adapt scope regression to admission receipts

* ci(validation): inspect shared runner without fleet access

* ci(validation): prepare Python before creating client environments

* ci(validation): isolate each client virtual environment

* ci(validation): retain interrupted evidence without fleet recovery

* ci(validation): bind recovery to retained runner evidence

* ci(validation): wire persistence and lock source plans

* fix(validation): align read-only dispatch defaults

---------

Co-authored-by: Chris <anzhengchao@gmail.com>
2026-10-29 08:50:42 +08:00

104 lines
4.6 KiB
Python

#!/usr/bin/env python3
"""Inspect GitHub runner registration and queued jobs without accessing the fleet."""
import argparse
from datetime import datetime, timezone
import json
import os
from pathlib import Path
import sys
from resolve_functional_candidate import REPOSITORY, api, require
LABEL = "smoke-testing"
ACTIVE_STATUSES = ("queued", "in_progress", "pending", "waiting", "requested")
def validate_request(inputs):
require(isinstance(inputs, dict) and inputs.get("validation_runner_inspect_only") is True,
"runner inspection must be explicitly selected")
defaults = {"validation_runner_inspect_only": True,
"validation_retain_evidence_run_attempt": "1",
"i05_diagnostic_branch": "source-plan", "validation_tools_run_attempt": "1"}
require(all(value == defaults[key] if key in defaults else value in ("", False, None)
for key, value in inputs.items()),
"runner inspection cannot select a candidate, tools, fleet case or recovery")
def pages(path, field):
rows = []
for page in range(1, 11):
separator = "&" if "?" in path else "?"
batch = api(f"{path}{separator}per_page=100&page={page}").get(field)
require(isinstance(batch, list), "GitHub inventory response is incomplete")
rows.extend(batch)
if len(batch) < 100:
return rows
raise ValueError("GitHub inventory exceeds the bounded inspection limit")
def collect(report):
prefix = f"repos/{REPOSITORY}/actions"
report["stage"] = "runner-inventory"
runners = pages(prefix + "/runners", "runners")
report["runners"] = [
{"id": row["id"], "name": row["name"], "status": row["status"],
"busy": row["busy"], "labels": [label["name"] for label in row["labels"]]}
for row in runners if LABEL in {label["name"] for label in row["labels"]}]
online = [row for row in report["runners"] if row["status"] == "online"]
report["runnerAvailability"] = (
"online-idle" if any(row["busy"] is False for row in online) else
"online-busy" if online else "offline" if report["runners"] else "unregistered")
report["stage"] = "active-workflows"
runs = {}
for status in ACTIVE_STATUSES:
for row in pages(prefix + f"/runs?status={status}", "workflow_runs"):
runs[row["id"]] = row
report["fleetJobs"] = []
report["runsWithoutJobs"] = []
for run_id, row in sorted(runs.items()):
report["stage"] = "workflow-jobs"
jobs = pages(prefix + f"/runs/{run_id}/jobs", "jobs")
identity = {"runId": run_id, "workflow": row["name"], "status": row["status"],
"createdAt": row["created_at"], "url": row["html_url"]}
if not jobs:
report["runsWithoutJobs"].append(identity)
for job in jobs:
if LABEL in job.get("labels", []) and job["status"] != "completed":
report["fleetJobs"].append({**identity, "jobId": job["id"], "jobName": job["name"],
"jobStatus": job["status"], "runnerName": job.get("runner_name"),
"labels": job["labels"], "jobUrl": job["html_url"]})
report.update(stage="complete", complete=True, activeWorkflowCount=len(runs))
def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--output", type=Path, required=True)
args = parser.parse_args()
require(not args.output.exists(), "retain the original runner inspection report")
report = {"schemaVersion": 1, "repository": REPOSITORY, "label": LABEL,
"observedAt": datetime.now(timezone.utc).isoformat(), "complete": False,
"runId": os.environ.get("GITHUB_RUN_ID"), "runAttempt": os.environ.get("GITHUB_RUN_ATTEMPT"),
"workflowCommit": os.environ.get("GITHUB_SHA"),
"runnerAvailability": "unknown", "stage": "request-validation",
"fleetAccessed": False, "mutated": False,
"cleanStateCertified": False, "sharedWorkflowIdleCertified": False,
"canonicalCasePassClaimed": False}
try:
validate_request(json.loads(os.environ["RUNNER_INSPECTION_INPUTS"]))
collect(report)
except Exception as error:
# Subprocess diagnostics can include configuration or credentials.
report["errorType"] = type(error).__name__
args.output.parent.mkdir(parents=True, exist_ok=True)
with args.output.open("x") as stream:
json.dump(report, stream, indent=2)
stream.write("\n")
print(json.dumps({key: report[key] for key in ("complete", "stage", "runnerAvailability")}))
return 0 if report["complete"] else 1
if __name__ == "__main__":
sys.exit(main())