Files
openstack-api-simulator/tools/os_api_inventory/generate_request_bodies.py
T
inecs 147f04c9a2 Fix Nova/Cinder stateful CRUD so the full surface and Pulumi matrix pass clean.
Persist aggregates and server security groups in domain tables, add Cinder quota
show from DB-backed defaults, align unit tests with OpenStack series, auto-pick
Keystone :5000/:15000 for smoke, and document test locations plus 6514/6514 results.
2026-07-18 09:38:45 +03:00

116 lines
4.1 KiB
Python

#!/usr/bin/env python3
"""Generate contracts/openstack/request_bodies/*.json for all pack write ops."""
from __future__ import annotations
import argparse
import json
import sys
from collections import defaultdict
from pathlib import Path
ROOT = Path(__file__).resolve().parents[2]
sys.path.insert(0, str(ROOT))
sys.path.insert(0, str(ROOT / "tools"))
from os_api_inventory.request_body_catalog import schema_for_operation # noqa: E402
SERIES_DEFAULT = "dalmatian"
OUT_DIR = ROOT / "contracts" / "openstack" / "request_bodies"
def _load_ops(series: str) -> dict[str, list[dict]]:
series_dir = ROOT / "contracts" / "openstack" / series
by_service: dict[str, list[dict]] = {}
for api in sorted(series_dir.glob("*/api.json")):
data = json.loads(api.read_text())
service = str(data["service"])
writes = [
op
for op in data.get("operations") or []
if op.get("method") in {"POST", "PUT", "PATCH"}
]
by_service[service] = writes
return by_service
def generate(series: str, out_dir: Path) -> dict[str, int]:
out_dir.mkdir(parents=True, exist_ok=True)
counts: dict[str, int] = {}
# Merge ops across series for max coverage of operation_ids.
merged: dict[str, dict[str, dict]] = defaultdict(dict)
for series_name in ("yoga", "antelope", "caracal", "dalmatian"):
if series and series != "all" and series_name != series:
continue
for service, ops in _load_ops(series_name).items():
for op in ops:
oid = op["operation_id"]
path_key = f"{op['method']} {op['path']}"
merged[service][oid] = op
# also keep path index later
_ = path_key
for service, by_oid in sorted(merged.items()):
operations: dict[str, dict] = {}
by_path: dict[str, dict] = {}
for oid, op in sorted(by_oid.items()):
schema = schema_for_operation(op)
operations[oid] = schema
by_path[f"{op['method']} {op['path']}"] = schema
payload = {
"service": service,
"source": "generated-from-api-ref-catalog",
"operations": operations,
"by_path": by_path,
}
path = out_dir / f"{service}.json"
path.write_text(json.dumps(payload, indent=2, sort_keys=True) + "\n")
counts[service] = len(operations)
return counts
def coverage_report(series: str) -> int:
from app.openstack.contract_loader import load_series_pack
from app.openstack.request_bodies import clear_request_body_cache, missing_write_schemas
clear_request_body_cache()
packs = load_series_pack(series)
missing = missing_write_schemas(packs)
print(f"series={series} missing={len(missing)}")
for service, method, path, oid in missing[:50]:
print(f" {service} {method} {path} ({oid})")
if len(missing) > 50:
print(f" ... and {len(missing) - 50} more")
return len(missing)
def main() -> int:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--series", default="all", help="Series to scan or 'all'")
parser.add_argument("--coverage", action="store_true", help="Report missing schemas only")
parser.add_argument(
"--coverage-series",
default=SERIES_DEFAULT,
help="Series for coverage check (default: dalmatian)",
)
args = parser.parse_args()
if args.coverage:
missing = coverage_report(args.coverage_series)
return 1 if missing else 0
series = None if args.series == "all" else args.series
counts = generate(series or "all", OUT_DIR)
total = sum(counts.values())
print(f"Wrote {len(counts)} services, {total} operation schemas → {OUT_DIR}")
for name, count in sorted(counts.items()):
print(f" {name}: {count}")
if args.series == "all":
# Coverage check requires the project venv (Python 3.13 dataclasses).
print(
"Run coverage with: python tools/os_api_inventory/generate_request_bodies.py --coverage"
)
return 0
if __name__ == "__main__":
raise SystemExit(main())