Files
openstack-api-simulator/app/openstack/microversions.py
T
inecs 6033967e6a 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.
2026-07-18 04:26:48 +03:00

88 lines
3.2 KiB
Python

"""OpenStack API microversion middleware (Nova / Cinder / Manila style)."""
from __future__ import annotations
from starlette.middleware.base import BaseHTTPMiddleware
from starlette.requests import Request
from starlette.responses import Response
# Fallback service -> (default, max) when contract pack is not loaded.
MICROVERSIONS: dict[str, tuple[str, str]] = {
"nova": ("2.1", "2.96"),
"cinder": ("3.0", "3.70"),
"manila": ("2.0", "2.82"),
"ironic": ("1.1", "1.90"),
"placement": ("1.0", "1.39"),
}
def _service_from_request(request: Request) -> str | None:
header = request.headers.get("x-openstack-service")
if header:
return header.lower()
port = request.headers.get("x-forwarded-port")
port_map = {
"8774": "nova",
"8776": "cinder",
"8786": "manila",
"6385": "ironic",
"8003": "placement",
}
return port_map.get(str(port))
def _bounds(service: str) -> tuple[str, str] | None:
try:
from app.openstack.contract_loader import get_runtime
runtime = get_runtime()
pack = runtime.packs.get(service)
if pack and pack.default_microversion and pack.max_microversion:
return pack.default_microversion, pack.max_microversion
override = runtime.active_microversion(service)
if pack and override and pack.max_microversion:
return override, pack.max_microversion
except Exception:
pass
return MICROVERSIONS.get(service)
class MicroversionMiddleware(BaseHTTPMiddleware):
async def dispatch(self, request: Request, call_next) -> Response:
service = _service_from_request(request)
requested = request.headers.get("openstack-api-version") or request.headers.get(
"x-openstack-nova-api-version"
)
version = None
if requested:
parts = requested.strip().split()
version = parts[-1] if parts else None
bounds = _bounds(service) if service else None
if service and bounds:
default, maximum = bounds
try:
from app.openstack.contract_loader import get_runtime
custom = get_runtime().microversion_overrides.get(service)
except Exception:
custom = None
chosen = version or custom or default
request.state.microversion = chosen
request.state.microversion_max = maximum
request.state.microversion_service = service
response = await call_next(request)
if service and bounds:
default, maximum = bounds
chosen = getattr(request.state, "microversion", default)
if service == "nova":
response.headers["OpenStack-API-Version"] = f"compute {chosen}"
response.headers["X-OpenStack-Nova-API-Version"] = chosen
elif service == "cinder":
response.headers["OpenStack-API-Version"] = f"volume {chosen}"
elif service == "placement":
response.headers["OpenStack-API-Version"] = f"placement {chosen}"
else:
response.headers["OpenStack-API-Version"] = f"{service} {chosen}"
response.headers.setdefault("Vary", "OpenStack-API-Version")
return response