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,109 @@
|
||||
"""Per-path OpenStack contract registration (Proxmox-style)."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from fastapi import FastAPI
|
||||
from fastapi.routing import APIRoute
|
||||
|
||||
from app.openstack.contract_loader import ensure_loaded, load_series_pack
|
||||
from app.openstack.mount import build_openstack_handlers, mount_openstack_routes
|
||||
from app.openstack.registry import (
|
||||
HandlerRegistry,
|
||||
normalize_path_template,
|
||||
register_specialized_handlers,
|
||||
)
|
||||
from app.openstack.routes import nova
|
||||
from app.openstack.schema_engine import remount_schema_services
|
||||
|
||||
|
||||
def test_normalize_path_template_collapses_param_names() -> None:
|
||||
assert normalize_path_template("/v2.1/servers/{id}") == normalize_path_template(
|
||||
"/v2.1/servers/{server_id}"
|
||||
)
|
||||
assert normalize_path_template("/v1/{account}/{container}/{object}") == normalize_path_template(
|
||||
"/v1/{account}/{container}/{object_name:path}"
|
||||
)
|
||||
|
||||
|
||||
def test_swift_object_handler_resolves_from_contract_path() -> None:
|
||||
registry = build_openstack_handlers()
|
||||
assert registry.get("swift", "/v1/{account}/{container}/{object}", "GET") is not None
|
||||
assert registry.get("swift", "/v1/{account}/{container}/{object}", "PUT") is not None
|
||||
|
||||
|
||||
def test_handler_registry_structural_lookup() -> None:
|
||||
registry = HandlerRegistry()
|
||||
|
||||
async def handler(request): # noqa: ANN001
|
||||
return request
|
||||
|
||||
registry.register("nova", "/v2.1/servers/{server_id}", "GET", handler)
|
||||
found = registry.get("nova", "/v2.1/servers/{id}", "GET")
|
||||
assert found is handler
|
||||
|
||||
|
||||
def test_specialized_handlers_imported_from_nova_router() -> None:
|
||||
registry = HandlerRegistry()
|
||||
count = register_specialized_handlers(registry, "nova", nova.router)
|
||||
assert count > 0
|
||||
assert registry.get("nova", "/v2.1/servers", "GET") is not None
|
||||
assert registry.get("nova", "/v2.1/servers/{id}", "GET") is not None
|
||||
|
||||
|
||||
def test_mount_registers_one_route_per_unique_method_path() -> None:
|
||||
app = FastAPI()
|
||||
mount_openstack_routes(app, series="dalmatian")
|
||||
|
||||
packs = load_series_pack("dalmatian")
|
||||
expected = 0
|
||||
for pack in packs.values():
|
||||
expected += len({(op.method, op.path) for op in pack.operations})
|
||||
|
||||
contract_routes = [
|
||||
route
|
||||
for route in app.router.routes
|
||||
if isinstance(route, APIRoute)
|
||||
and isinstance(route.name, str)
|
||||
and route.name.startswith("os-contract:")
|
||||
]
|
||||
# Contract paths plus specialized-only aliases (trailing slash, PUT tags, …).
|
||||
assert len(contract_routes) >= expected
|
||||
assert app.state.openstack_schema_ops == len(contract_routes)
|
||||
# name format: os-contract:{service}:{METHOD}:{path}
|
||||
mounted_ops = set()
|
||||
for route in contract_routes:
|
||||
rest = route.name[len("os-contract:") :]
|
||||
_service, _, remainder = rest.partition(":")
|
||||
method, _, path = remainder.partition(":")
|
||||
mounted_ops.add((method, path))
|
||||
for pack in packs.values():
|
||||
for op in pack.operations:
|
||||
assert (op.method, op.path) in mounted_ops
|
||||
# No legacy schema-* route names.
|
||||
assert not any(
|
||||
isinstance(getattr(r, "name", None), str) and str(r.name).startswith("schema-")
|
||||
for r in app.router.routes
|
||||
)
|
||||
|
||||
|
||||
def test_remount_preserves_handlers_and_route_count() -> None:
|
||||
app = FastAPI()
|
||||
mount_openstack_routes(app, series="dalmatian")
|
||||
handlers = app.state.openstack_handlers
|
||||
assert isinstance(handlers, HandlerRegistry)
|
||||
before = app.state.openstack_schema_ops
|
||||
|
||||
ensure_loaded("caracal")
|
||||
summary = remount_schema_services(app, "caracal")
|
||||
assert app.state.openstack_handlers is handlers
|
||||
assert summary["routes_mounted"] == app.state.openstack_schema_ops
|
||||
assert app.state.openstack_schema_ops > 0
|
||||
# Switching series rebuilds routes; count may differ by series deltas.
|
||||
assert isinstance(before, int)
|
||||
|
||||
|
||||
def test_build_openstack_handlers_covers_core_services() -> None:
|
||||
registry = build_openstack_handlers()
|
||||
for service in ("keystone", "nova", "neutron", "glance", "cinder"):
|
||||
keys = [k for k in registry.keys() if k[0] == service]
|
||||
assert keys, f"expected handlers for {service}"
|
||||
Reference in New Issue
Block a user