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:
@@ -0,0 +1,87 @@
|
||||
"""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
|
||||
Reference in New Issue
Block a user