6033967e6a
Ship Keystone auth, multi-service handlers (Yoga→Dalmatian), Compose/Helm packaging, API contract packs, and pytest/Pulumi coverage labs.
88 lines
3.2 KiB
Python
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
|