Initial commit: stateful OpenStack API laboratory simulator.

Ship Keystone auth, multi-service handlers (Yoga→Dalmatian), Compose/Helm
packaging, API contract packs, and pytest/Pulumi coverage labs.
This commit is contained in:
2026-07-18 04:26:48 +03:00
commit 6033967e6a
509 changed files with 464404 additions and 0 deletions
+208
View File
@@ -0,0 +1,208 @@
"""HTTP pack coverage with completeness and non-empty body checks."""
from __future__ import annotations
import json
from collections import Counter
from pathlib import Path
from typing import Any
from _lib.validate import payload_nonempty
# Methods that normally return a JSON body on success (DELETE / 204 may be empty).
_BODY_METHODS = frozenset({"GET", "POST", "PUT", "PATCH"})
def _expected_ops(packs: dict[str, Any], *, collections_only: bool) -> int:
if not collections_only:
return sum(len(p.operations) for p in packs.values())
total = 0
for pack in packs.values():
for op in pack.operations:
if op.method == "GET" and "{" not in op.path:
total += 1
return total
def _methods_breakdown(results: list[Any]) -> dict[str, int]:
counts: Counter[str] = Counter()
for r in results:
method = getattr(r, "method", None) or (r.get("method") if isinstance(r, dict) else None)
if method:
counts[str(method).upper()] += 1
return {m: counts.get(m, 0) for m in ("GET", "POST", "PUT", "PATCH", "DELETE")}
def _nonempty_from_lifecycle(report: Any) -> list[dict[str, Any]]:
"""Check succeeded lifecycle bodies (skip DELETE / 204 / 202 / no-body)."""
failures: list[dict[str, Any]] = []
for r in report.results:
if not r.succeeded:
continue
if r.method == "DELETE" or r.status in {202, 204}:
continue
if r.method not in _BODY_METHODS:
continue
# OpenStack often returns 200/201 with an empty body (Swift PUT, tag put).
if r.payload is None:
continue
if not payload_nonempty(r.payload, collection_key=r.collection_key, method=r.method):
failures.append(
{
"service": r.service,
"operation_id": r.operation_id,
"method": r.method,
"path": r.path,
"status": r.status,
"detail": "empty response body",
"ok": False,
"nonempty": False,
}
)
return failures
def _nonempty_smoke_collections(
series: str,
*,
host: str,
packs: dict[str, Any],
) -> list[dict[str, Any]]:
"""Re-check collection GET bodies for smoke mode (stable seed data)."""
from app.openstack.surface_probe import (
SUCCESS,
fill_path,
http_request,
issue_token,
_seed_context,
)
token, auth_body = issue_token(host)
project_id = str(((auth_body.get("token") or {}).get("project") or {}).get("id") or "")
ctx = _seed_context(host, token, project_id)
failures: list[dict[str, Any]] = []
for name in sorted(packs):
pack = packs[name]
for op in pack.operations:
if "{" in op.path or op.method != "GET":
continue
path = fill_path(
op.path,
{
**ctx,
"project_id": project_id,
"project": project_id,
"tenant_id": project_id,
"account": project_id,
},
)
url = f"{host.rstrip('/')}{path}"
status, payload = http_request(op.method, url, token=token, service=pack.name)
if status not in SUCCESS or status == 204:
continue
if not payload_nonempty(payload, collection_key=op.collection_key, method=op.method):
failures.append(
{
"service": pack.name,
"operation_id": op.operation_id,
"method": op.method,
"path": op.path,
"status": status,
"detail": "empty response body",
"ok": False,
"nonempty": False,
}
)
return failures
def probe_pack_operations(
series: str,
*,
host: str,
collections_only: bool = False,
require_nonempty: bool = True,
) -> dict[str, Any]:
from app.openstack.contract_loader import load_series_pack
from app.openstack.surface_probe import probe_series
report = probe_series(
series,
host=host,
collections_only=collections_only,
lifecycle=not collections_only,
)
packs = load_series_pack(series)
expected_ops = _expected_ops(packs, collections_only=collections_only)
methods = _methods_breakdown(report.results)
coverage_incomplete = len(report.results) != expected_ops
nonempty_failures: list[dict[str, Any]] = []
if require_nonempty:
if collections_only:
nonempty_failures = _nonempty_smoke_collections(series, host=host, packs=packs)
else:
nonempty_failures = _nonempty_from_lifecycle(report)
probe_failures = [
{
"service": r.service,
"operation_id": r.operation_id,
"method": r.method,
"path": r.path,
"status": r.status,
"detail": r.detail,
"ok": False,
"nonempty": True,
}
for r in report.failures
]
coverage_failures: list[dict[str, Any]] = []
if coverage_incomplete:
coverage_failures.append(
{
"service": "_coverage",
"operation_id": "coverage_incomplete",
"method": "*",
"path": "*",
"status": 0,
"detail": f"coverage_incomplete: total={len(report.results)} expected_ops={expected_ops}",
"ok": False,
"nonempty": True,
}
)
return {
"series": series,
"host": host,
"mode": report.mode,
"total": len(report.results),
"expected_ops": expected_ops,
"coverage_incomplete": coverage_incomplete,
"methods": methods,
"ok_count": len(report.results) - len(report.failures),
"fail_count": len(report.failures) + (1 if coverage_incomplete else 0),
"nonempty_fail_count": len(nonempty_failures),
"results": [
{
"service": r.service,
"method": r.method,
"path": r.path,
"operation_id": r.operation_id,
"status": r.status,
"detail": r.detail,
"mode": r.mode,
"ok": r.ok,
"succeeded": r.succeeded,
}
for r in report.results
],
"failures": coverage_failures + probe_failures + nonempty_failures,
}
def write_probe_json(payload: dict[str, Any], path: Path) -> None:
path.parent.mkdir(parents=True, exist_ok=True)
path.write_text(json.dumps(payload, indent=2) + "\n", encoding="utf-8")