release: freeze Nexus One AI 1.0.0-rc1
This commit is contained in:
1 parent
63acefd890
commit
4f1419ca93
152 files changed
+9445
-828
No files matched your search
@@ -0,0 +1,247 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Evaluate a bounded, semantic, loopback-only activation health window."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import hashlib
|
||||
import ipaddress
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import subprocess
|
||||
import tempfile
|
||||
import time
|
||||
import urllib.error
|
||||
import urllib.parse
|
||||
import urllib.request
|
||||
from pathlib import Path
|
||||
from typing import Callable
|
||||
|
||||
|
||||
SCHEMA = "com.cezen.nexus-one.activation-health-evidence.v1"
|
||||
MAX_RESPONSE_BYTES = 65536
|
||||
UNIT_NAME = re.compile(r"^[A-Za-z0-9_.@-]+\.service$")
|
||||
|
||||
|
||||
class HealthConfigError(ValueError):
|
||||
pass
|
||||
|
||||
|
||||
def canonical(value: object) -> bytes:
|
||||
return (json.dumps(value, sort_keys=True, separators=(",", ":")) + "\n").encode()
|
||||
|
||||
|
||||
def validate_config(config: dict) -> list[dict]:
|
||||
if set(config) != {"schema", "components"} or config["schema"] != \
|
||||
"com.cezen.nexus-one.activation-health-profile.v1":
|
||||
raise HealthConfigError("HEALTH_PROFILE_INVALID")
|
||||
components = config["components"]
|
||||
if not isinstance(components, list) or not components:
|
||||
raise HealthConfigError("HEALTH_COMPONENTS_INVALID")
|
||||
names = set()
|
||||
result = []
|
||||
for component in components:
|
||||
if not isinstance(component, dict) or set(component) != {
|
||||
"name", "url", "required", "expected_service", "systemd_unit", "max_restarts"}:
|
||||
raise HealthConfigError("HEALTH_COMPONENT_INVALID")
|
||||
name = component["name"]
|
||||
if not isinstance(name, str) or not name or name in names:
|
||||
raise HealthConfigError("HEALTH_COMPONENT_NAME_INVALID")
|
||||
names.add(name)
|
||||
if not isinstance(component["required"], bool):
|
||||
raise HealthConfigError("HEALTH_COMPONENT_REQUIRED_INVALID")
|
||||
expected = component["expected_service"]
|
||||
if not isinstance(expected, str) or not expected:
|
||||
raise HealthConfigError("HEALTH_COMPONENT_EXPECTED_SERVICE_INVALID")
|
||||
unit = component["systemd_unit"]
|
||||
if unit is not None and (not isinstance(unit, str) or not UNIT_NAME.fullmatch(unit)):
|
||||
raise HealthConfigError("HEALTH_COMPONENT_SYSTEMD_UNIT_INVALID")
|
||||
maximum = component["max_restarts"]
|
||||
if not isinstance(maximum, int) or isinstance(maximum, bool) or maximum < 0 or maximum > 1000:
|
||||
raise HealthConfigError("HEALTH_COMPONENT_MAX_RESTARTS_INVALID")
|
||||
parsed = urllib.parse.urlparse(component["url"])
|
||||
if parsed.scheme != "http" or parsed.username or parsed.password or parsed.query \
|
||||
or parsed.fragment or not parsed.port or parsed.path == "":
|
||||
raise HealthConfigError("HEALTH_URL_INVALID")
|
||||
try:
|
||||
address = ipaddress.ip_address(parsed.hostname or "")
|
||||
except ValueError as error:
|
||||
raise HealthConfigError("HEALTH_URL_NOT_LITERAL_LOOPBACK") from error
|
||||
if not address.is_loopback:
|
||||
raise HealthConfigError("HEALTH_URL_NOT_LOOPBACK")
|
||||
result.append(dict(component))
|
||||
return result
|
||||
|
||||
|
||||
def fetch(component: dict, timeout_seconds: float) -> dict:
|
||||
request = urllib.request.Request(component["url"], headers={"Accept": "application/json"})
|
||||
try:
|
||||
with urllib.request.urlopen(request, timeout=timeout_seconds) as response:
|
||||
if response.status != 200:
|
||||
return {"ok": False, "code": f"HTTP_{response.status}"}
|
||||
content_type = response.headers.get_content_type()
|
||||
if content_type != "application/json":
|
||||
return {"ok": False, "code": "CONTENT_TYPE_INVALID"}
|
||||
payload = response.read(MAX_RESPONSE_BYTES + 1)
|
||||
if len(payload) > MAX_RESPONSE_BYTES:
|
||||
return {"ok": False, "code": "RESPONSE_TOO_LARGE"}
|
||||
except urllib.error.HTTPError as error:
|
||||
return {"ok": False, "code": f"HTTP_{error.code}"}
|
||||
except TimeoutError:
|
||||
return {"ok": False, "code": "TIMEOUT"}
|
||||
except (OSError, urllib.error.URLError):
|
||||
return {"ok": False, "code": "UNREACHABLE"}
|
||||
try:
|
||||
decoded = json.loads(payload)
|
||||
except (UnicodeDecodeError, json.JSONDecodeError):
|
||||
return {"ok": False, "code": "MALFORMED_JSON"}
|
||||
if not isinstance(decoded, dict) or set(decoded) != {"service", "status"}:
|
||||
return {"ok": False, "code": "RESPONSE_SHAPE_INVALID"}
|
||||
if decoded["service"] != component["expected_service"]:
|
||||
return {"ok": False, "code": "SERVICE_IDENTITY_MISMATCH"}
|
||||
if decoded["status"] not in {"ready", "degraded", "failed"}:
|
||||
return {"ok": False, "code": "STATUS_INVALID"}
|
||||
return {"ok": decoded["status"] == "ready", "code": decoded["status"].upper(),
|
||||
"status": decoded["status"]}
|
||||
|
||||
|
||||
def restart_state(component: dict) -> dict:
|
||||
unit = component["systemd_unit"]
|
||||
if unit is None:
|
||||
return {"ok": True, "code": "RESTART_CHECK_NOT_APPLICABLE", "restarts": 0}
|
||||
try:
|
||||
process = subprocess.run(
|
||||
["systemctl", "show", unit, "--property=NRestarts", "--property=ActiveState",
|
||||
"--property=SubState", "--no-pager"], check=True, capture_output=True, text=True,
|
||||
timeout=5,
|
||||
)
|
||||
except (OSError, subprocess.SubprocessError):
|
||||
return {"ok": False, "code": "UNIT_STATE_UNAVAILABLE", "restarts": None}
|
||||
values = {}
|
||||
for line in process.stdout.splitlines():
|
||||
key, separator, value = line.partition("=")
|
||||
if not separator or key in values:
|
||||
return {"ok": False, "code": "UNIT_STATE_INVALID", "restarts": None}
|
||||
values[key] = value
|
||||
if set(values) != {"NRestarts", "ActiveState", "SubState"} or not values["NRestarts"].isdigit():
|
||||
return {"ok": False, "code": "UNIT_STATE_INVALID", "restarts": None}
|
||||
restarts = int(values["NRestarts"])
|
||||
if restarts > component["max_restarts"]:
|
||||
return {"ok": False, "code": "RESTART_STORM", "restarts": restarts}
|
||||
if values["ActiveState"] != "active":
|
||||
return {"ok": False, "code": "UNIT_NOT_ACTIVE", "restarts": restarts}
|
||||
return {"ok": True, "code": "RESTARTS_OK", "restarts": restarts}
|
||||
|
||||
|
||||
def atomic_evidence(path: Path, evidence: dict) -> None:
|
||||
path.parent.mkdir(parents=True, exist_ok=True)
|
||||
fd, temporary = tempfile.mkstemp(prefix=f".{path.name}.", dir=path.parent)
|
||||
try:
|
||||
os.fchmod(fd, 0o600)
|
||||
with os.fdopen(fd, "wb") as stream:
|
||||
stream.write(canonical(evidence))
|
||||
stream.flush()
|
||||
os.fsync(stream.fileno())
|
||||
os.replace(temporary, path)
|
||||
directory_fd = os.open(path.parent, os.O_RDONLY)
|
||||
try:
|
||||
os.fsync(directory_fd)
|
||||
finally:
|
||||
os.close(directory_fd)
|
||||
finally:
|
||||
if os.path.exists(temporary):
|
||||
os.unlink(temporary)
|
||||
|
||||
|
||||
def evaluate(config: dict, operation_id: str, samples: int, interval_seconds: float,
|
||||
timeout_seconds: float, window_timeout_seconds: float, allow_degraded: bool,
|
||||
fetcher: Callable = fetch, restart_checker: Callable = restart_state,
|
||||
sleeper: Callable = time.sleep, clock: Callable = time.monotonic) -> dict:
|
||||
components = validate_config(config)
|
||||
if not operation_id or any(character not in "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_." for character in operation_id):
|
||||
raise HealthConfigError("OPERATION_ID_INVALID")
|
||||
if samples < 1 or samples > 100 or interval_seconds < 0 or timeout_seconds <= 0 \
|
||||
or window_timeout_seconds <= 0 or timeout_seconds > window_timeout_seconds:
|
||||
raise HealthConfigError("HEALTH_WINDOW_BOUNDS_INVALID")
|
||||
records = []
|
||||
outcome = "ready"
|
||||
deadline = clock() + window_timeout_seconds
|
||||
for sample in range(1, samples + 1):
|
||||
for component in components:
|
||||
remaining = deadline - clock()
|
||||
if remaining <= 0:
|
||||
result = {"ok": False, "code": "WINDOW_TIMEOUT"}
|
||||
restart = {"code": "NOT_CHECKED", "restarts": None}
|
||||
else:
|
||||
restart = restart_checker(component)
|
||||
if restart.get("ok") is not True:
|
||||
result = restart
|
||||
else:
|
||||
remaining = deadline - clock()
|
||||
result = {"ok": False, "code": "WINDOW_TIMEOUT"} if remaining <= 0 else \
|
||||
fetcher(component, min(timeout_seconds, remaining))
|
||||
if clock() > deadline:
|
||||
result = {"ok": False, "code": "WINDOW_TIMEOUT"}
|
||||
status = result.get("status")
|
||||
accepted_degraded = status == "degraded" and not component["required"] and allow_degraded
|
||||
accepted = result.get("ok") is True or accepted_degraded
|
||||
if accepted_degraded:
|
||||
outcome = "degraded"
|
||||
records.append({"sample": sample, "component": component["name"],
|
||||
"required": component["required"], "accepted": accepted,
|
||||
"code": result.get("code", "PROBE_RESULT_INVALID"),
|
||||
"restart_code": restart.get("code"),
|
||||
"restart_count": restart.get("restarts")})
|
||||
if not accepted:
|
||||
outcome = "rejected"
|
||||
return {"schema": SCHEMA, "operation_id": operation_id,
|
||||
"profile_sha256": hashlib.sha256(canonical(config)).hexdigest(),
|
||||
"samples_required": samples, "samples_completed": sample,
|
||||
"allow_degraded": allow_degraded, "outcome": outcome, "checks": records}
|
||||
if sample != samples:
|
||||
if clock() + interval_seconds > deadline:
|
||||
records.append({"sample": sample, "component": "window", "required": True,
|
||||
"accepted": False, "code": "WINDOW_TIMEOUT",
|
||||
"restart_code": "NOT_APPLICABLE", "restart_count": None})
|
||||
return {"schema": SCHEMA, "operation_id": operation_id,
|
||||
"profile_sha256": hashlib.sha256(canonical(config)).hexdigest(),
|
||||
"samples_required": samples, "samples_completed": sample,
|
||||
"allow_degraded": allow_degraded, "outcome": "rejected",
|
||||
"checks": records}
|
||||
sleeper(interval_seconds)
|
||||
return {"schema": SCHEMA, "operation_id": operation_id,
|
||||
"profile_sha256": hashlib.sha256(canonical(config)).hexdigest(),
|
||||
"samples_required": samples, "samples_completed": samples,
|
||||
"allow_degraded": allow_degraded, "outcome": outcome, "checks": records}
|
||||
|
||||
|
||||
def main(argv: list[str] | None = None, fetcher: Callable = fetch,
|
||||
restart_checker: Callable = restart_state, sleeper: Callable = time.sleep,
|
||||
clock: Callable = time.monotonic) -> int:
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--operation-id", required=True)
|
||||
parser.add_argument("--profile-config", type=Path, required=True)
|
||||
parser.add_argument("--samples", type=int, required=True)
|
||||
parser.add_argument("--interval-seconds", type=float, required=True)
|
||||
parser.add_argument("--timeout-seconds", type=float, required=True)
|
||||
parser.add_argument("--window-timeout-seconds", type=float, required=True)
|
||||
parser.add_argument("--evidence", type=Path, required=True)
|
||||
parser.add_argument("--allow-degraded", action="store_true")
|
||||
args = parser.parse_args(argv)
|
||||
try:
|
||||
config = json.loads(args.profile_config.read_text(encoding="utf-8"))
|
||||
evidence = evaluate(config, args.operation_id, args.samples, args.interval_seconds,
|
||||
args.timeout_seconds, args.window_timeout_seconds, args.allow_degraded,
|
||||
fetcher, restart_checker, sleeper, clock)
|
||||
atomic_evidence(args.evidence, evidence)
|
||||
except (HealthConfigError, OSError, json.JSONDecodeError) as error:
|
||||
print(json.dumps({"accepted": False, "code": str(error)}, sort_keys=True))
|
||||
return 2
|
||||
print(json.dumps({"accepted": evidence["outcome"] in {"ready", "degraded"},
|
||||
"outcome": evidence["outcome"]}, sort_keys=True))
|
||||
return 0 if evidence["outcome"] in {"ready", "degraded"} else 23
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
Reference in new issue
Block a user