Initial release of the oVirt/RHV Engine API simulator.

Stateful FastAPI lab with contract packs, Compose/Helm, Docker Hub release
targets, and Pulumi coverage across all Engine series (GET/POST/PUT/DELETE/HEAD).
This commit is contained in:
2026-07-18 04:49:28 +03:00
commit cbd0adca91
218 changed files with 246804 additions and 0 deletions
+174
View File
@@ -0,0 +1,174 @@
"""Resolve oVirt Engine series contract pack locations."""
from __future__ import annotations
import json
import os
import threading
from dataclasses import dataclass, field
from pathlib import Path
from typing import Any
from app.ovirt.opspec import OperationSpec, SeriesPack
_SERIES_MAJOR = {
"3.0": 30,
"3.1": 31,
"3.2": 32,
"3.3": 33,
"3.4": 34,
"3.5": 35,
"3.6": 36,
"4.3": 43,
"4.4": 44,
"4.5": 45,
"master": 50,
}
_MAJOR_SERIES = {v: k for k, v in _SERIES_MAJOR.items()}
def contracts_root() -> Path:
"""Locate ``contracts/ovirt`` for source, wheel, or Docker layouts."""
env = os.environ.get("OVIRT_CONTRACTS_ROOT")
if env:
return Path(env)
here = Path(__file__).resolve()
candidates = [
here.parents[2] / "contracts" / "ovirt", # repo checkout or site-packages
Path("/app/contracts/ovirt"), # runtime image WORKDIR layout
Path.cwd() / "contracts" / "ovirt",
]
for path in candidates:
if path.is_dir():
return path
return candidates[0]
def series_for_major(major: int) -> str:
return _MAJOR_SERIES.get(major, "4.5")
def major_for_series(series: str) -> int:
return _SERIES_MAJOR.get(series.lower(), 45)
def list_series() -> list[dict[str, Any]]:
root = contracts_root()
if not root.exists():
return []
result: list[dict[str, Any]] = []
for path in sorted(root.iterdir()):
man = path / "manifest.json"
if not man.is_file():
continue
data = json.loads(man.read_text())
result.append(
{
"series": data.get("series", path.name),
"major": data.get("major", major_for_series(path.name)),
"api_version": data.get("api_version", "4"),
"operation_count": data.get("operation_count", 0),
"service_count": data.get("service_count", 1),
"checksum": data.get("checksum", ""),
"generated_at": data.get("generated_at", ""),
"deltas": data.get("deltas", {}),
"product": data.get("product", {}),
}
)
return result
def _op_from_dict(raw: dict[str, Any]) -> OperationSpec:
return OperationSpec(
operation_id=str(raw["operation_id"]),
method=raw["method"],
path=str(raw["path"]),
resource_type=str(raw.get("resource_type") or "object"),
collection_key=str(raw.get("collection_key") or "objects"),
element=str(raw.get("element") or raw.get("resource_type") or "object"),
kind=str(raw.get("kind") or "collection"),
search=bool(raw.get("search", False)),
introduced_in=str(raw.get("introduced_in") or "3.0"),
requires_auth=bool(raw.get("requires_auth", True)),
status_code=int(raw.get("status_code") or 200),
create_status=int(raw.get("create_status") or 201),
notes=str(raw.get("notes") or ""),
response_fixture=raw.get("response_fixture"),
)
def load_series_pack(series: str) -> SeriesPack:
series = series.lower()
series_dir = contracts_root() / series
man_path = series_dir / "manifest.json"
api_path = series_dir / "api.json"
if not man_path.is_file() or not api_path.is_file():
raise FileNotFoundError(f"oVirt contract pack not found: {series_dir}")
man = json.loads(man_path.read_text())
data = json.loads(api_path.read_text())
ops = [_op_from_dict(raw) for raw in data.get("operations") or []]
return SeriesPack(
series=str(man.get("series", series)),
api_version=str(man.get("api_version") or data.get("api_version") or "4"),
major=int(man.get("major") or major_for_series(series)),
operations=ops,
product=dict(man.get("product") or data.get("product") or {}),
entry_point_links=list(man.get("entry_point_links") or []),
checksum=str(man.get("checksum") or ""),
)
@dataclass
class ContractRuntime:
series: str = "4.5"
pack: SeriesPack | None = None
_lock: threading.RLock = field(default_factory=threading.RLock)
def reload(self, series: str | None = None) -> dict[str, Any]:
with self._lock:
target = (series or self.series).lower()
self.pack = load_series_pack(target)
self.series = target
return self.summary()
def summary(self) -> dict[str, Any]:
with self._lock:
pack = self.pack
if pack is None:
return {
"series": self.series,
"operation_count": 0,
"major": major_for_series(self.series),
}
return {
"series": pack.series,
"major": pack.major,
"api_version": pack.api_version,
"operation_count": pack.operation_count(),
"service_count": 1,
"checksum": pack.checksum,
"product": pack.product,
"deltas": next(
(s.get("deltas") for s in list_series() if s["series"] == pack.series),
{},
),
}
_RUNTIME = ContractRuntime()
def get_runtime() -> ContractRuntime:
return _RUNTIME
def ensure_loaded(series: str = "4.5") -> ContractRuntime:
rt = get_runtime()
if rt.pack is None:
try:
rt.reload(series)
except FileNotFoundError:
pass
return rt