Add OpenStack request-body schemas and nested console PARAM sync.
This commit is contained in:
@@ -0,0 +1,562 @@
|
||||
"""Specialized IaaS handlers: auth, CRUD read-after-write, Nova actions.
|
||||
|
||||
Runs against a live api-gateway after minimal/demo seed. Prefers real service
|
||||
ports when reachable; falls back to Keystone gateway + X-OpenStack-Route-Service.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import os
|
||||
import urllib.error
|
||||
import urllib.request
|
||||
import uuid
|
||||
from typing import Any
|
||||
|
||||
import pytest
|
||||
|
||||
pytestmark = pytest.mark.integration
|
||||
|
||||
|
||||
def _probe(url: str, timeout: float = 2.0) -> bool:
|
||||
try:
|
||||
with urllib.request.urlopen(url, timeout=timeout) as res:
|
||||
return res.status == 200
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
|
||||
def _pick_keystone() -> str:
|
||||
candidates = [
|
||||
os.environ.get("OS_PROBE_HOST"),
|
||||
os.environ.get("OS_HOST"),
|
||||
"http://127.0.0.1:15000",
|
||||
"http://127.0.0.1:5000",
|
||||
"http://api-gateway:5000",
|
||||
"http://localhost:5000",
|
||||
]
|
||||
for host in candidates:
|
||||
if not host:
|
||||
continue
|
||||
base = host.rstrip("/")
|
||||
if _probe(f"{base}/health/live") or _probe(f"{base}/v3"):
|
||||
return base
|
||||
return ""
|
||||
|
||||
|
||||
KEYSTONE = _pick_keystone()
|
||||
|
||||
# Real OpenStack default ports (compose publishes 1:1; local override keeps them).
|
||||
_PORTS = {
|
||||
"keystone": 5000,
|
||||
"nova": 8774,
|
||||
"neutron": 9696,
|
||||
"glance": 9292,
|
||||
"cinder": 8776,
|
||||
"placement": 8003,
|
||||
"heat": 8004,
|
||||
"swift": 8080,
|
||||
"ironic": 6385,
|
||||
"octavia": 9876,
|
||||
}
|
||||
|
||||
|
||||
def _service_base(service: str) -> tuple[str, str | None]:
|
||||
"""Return (base_url, route_service_header_or_None)."""
|
||||
|
||||
if service == "keystone":
|
||||
return KEYSTONE, None
|
||||
port = _PORTS[service]
|
||||
candidates = [port]
|
||||
if service == "swift":
|
||||
# Local override often maps Swift to host 18080.
|
||||
candidates = [18080, 8080]
|
||||
for p in candidates:
|
||||
base = f"http://127.0.0.1:{p}"
|
||||
# Any HTTP response (incl. 401/404) means the port is published.
|
||||
try:
|
||||
urllib.request.urlopen(base + "/", timeout=1)
|
||||
return base, None
|
||||
except urllib.error.HTTPError:
|
||||
return base, None
|
||||
except Exception:
|
||||
continue
|
||||
return KEYSTONE, service
|
||||
|
||||
|
||||
@pytest.fixture(scope="module", autouse=True)
|
||||
def _require_gateway():
|
||||
if not KEYSTONE:
|
||||
pytest.skip("OpenStack gateway unreachable")
|
||||
|
||||
|
||||
def _request(
|
||||
method: str,
|
||||
service: str,
|
||||
path: str,
|
||||
*,
|
||||
token: str | None = None,
|
||||
data: dict[str, Any] | None = None,
|
||||
headers: dict[str, str] | None = None,
|
||||
raw_body: bytes | None = None,
|
||||
) -> tuple[int, dict[str, str], Any]:
|
||||
base, route_svc = _service_base(service)
|
||||
body = raw_body
|
||||
hdrs = {"Accept": "application/json"}
|
||||
if data is not None:
|
||||
hdrs["Content-Type"] = "application/json"
|
||||
body = json.dumps(data).encode()
|
||||
if token:
|
||||
hdrs["X-Auth-Token"] = token
|
||||
if route_svc:
|
||||
hdrs["X-OpenStack-Route-Service"] = route_svc
|
||||
if headers:
|
||||
hdrs.update(headers)
|
||||
req = urllib.request.Request(f"{base}{path}", data=body, headers=hdrs, method=method)
|
||||
try:
|
||||
with urllib.request.urlopen(req, timeout=30) as res:
|
||||
raw = res.read().decode()
|
||||
try:
|
||||
parsed: Any = json.loads(raw) if raw else None
|
||||
except json.JSONDecodeError:
|
||||
parsed = raw
|
||||
return res.status, {k: v for k, v in res.headers.items()}, parsed
|
||||
except urllib.error.HTTPError as exc:
|
||||
raw = exc.read().decode()
|
||||
try:
|
||||
parsed = json.loads(raw) if raw else None
|
||||
except json.JSONDecodeError:
|
||||
parsed = raw
|
||||
return exc.code, {k: v for k, v in exc.headers.items()}, parsed
|
||||
|
||||
|
||||
def _auth(
|
||||
*,
|
||||
user: str = "demo",
|
||||
project: str = "demo",
|
||||
password: str = "secret",
|
||||
) -> tuple[str, str, dict[str, Any]]:
|
||||
status, headers, body = _request(
|
||||
"POST",
|
||||
"keystone",
|
||||
"/v3/auth/tokens",
|
||||
data={
|
||||
"auth": {
|
||||
"identity": {
|
||||
"methods": ["password"],
|
||||
"password": {
|
||||
"user": {
|
||||
"name": user,
|
||||
"domain": {"name": "Default"},
|
||||
"password": password,
|
||||
}
|
||||
},
|
||||
},
|
||||
"scope": {"project": {"name": project, "domain": {"name": "Default"}}},
|
||||
}
|
||||
},
|
||||
)
|
||||
token = headers.get("X-Subject-Token") or headers.get("x-subject-token")
|
||||
assert status == 201, (status, body)
|
||||
assert token
|
||||
project = ((body or {}).get("token") or {}).get("project") or {}
|
||||
project_id = str(project.get("id") or "")
|
||||
assert project_id
|
||||
return token, project_id, body or {}
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def auth_ctx() -> tuple[str, str, dict[str, Any]]:
|
||||
return _auth()
|
||||
|
||||
|
||||
def test_keystone_token_catalog_ports(auth_ctx: tuple[str, str, dict[str, Any]]) -> None:
|
||||
_token, _pid, body = auth_ctx
|
||||
catalog = (body.get("token") or {}).get("catalog") or []
|
||||
by_type = {e.get("type"): e for e in catalog if isinstance(e, dict)}
|
||||
expected = {
|
||||
"identity": ":5000",
|
||||
"compute": ":8774",
|
||||
"network": ":9696",
|
||||
"image": ":9292",
|
||||
"volumev3": ":8776",
|
||||
"placement": ":8003",
|
||||
"orchestration": ":8004",
|
||||
"object-store": ":8080",
|
||||
"baremetal": ":6385",
|
||||
"load-balancer": ":9876",
|
||||
}
|
||||
for typ, port_frag in expected.items():
|
||||
entry = by_type.get(typ)
|
||||
assert entry, f"missing catalog type {typ}"
|
||||
urls = [
|
||||
ep.get("url") or ""
|
||||
for ep in entry.get("endpoints") or []
|
||||
if ep.get("interface") == "public"
|
||||
]
|
||||
assert any(port_frag in u for u in urls), (typ, urls)
|
||||
|
||||
|
||||
def test_specialized_lists_nonempty_after_seed(auth_ctx: tuple[str, str, dict[str, Any]]) -> None:
|
||||
token, pid, _ = auth_ctx
|
||||
checks = [
|
||||
("nova", "/v2.1/servers/detail", "servers", {"OpenStack-API-Version": "compute 2.79"}),
|
||||
("nova", "/v2.1/flavors", "flavors", None),
|
||||
("neutron", "/v2.0/networks", "networks", None),
|
||||
("neutron", "/v2.0/subnets", "subnets", None),
|
||||
("neutron", "/v2.0/floatingips", "floatingips", None),
|
||||
("glance", "/v2/images", "images", None),
|
||||
("cinder", "/v3/volumes/detail", "volumes", None),
|
||||
(
|
||||
"placement",
|
||||
"/resource_providers",
|
||||
"resource_providers",
|
||||
{"OpenStack-API-Version": "placement 1.39"},
|
||||
),
|
||||
("heat", f"/v1/{pid}/stacks", "stacks", None),
|
||||
("octavia", "/v2/lbaas/loadbalancers", "loadbalancers", None),
|
||||
("ironic", "/v1/nodes", "nodes", {"OpenStack-API-Version": "baremetal 1.90"}),
|
||||
]
|
||||
for service, path, key, hdrs in checks:
|
||||
status, _, body = _request("GET", service, path, token=token, headers=hdrs)
|
||||
assert status == 200, (service, path, status, body)
|
||||
items = (body or {}).get(key)
|
||||
assert isinstance(items, list) and len(items) >= 1, (service, path, key, body)
|
||||
|
||||
status, _, body = _request("GET", "swift", f"/v1/AUTH_{pid}", token=token)
|
||||
assert status == 200
|
||||
assert isinstance(body, list) and len(body) >= 1
|
||||
|
||||
|
||||
def test_nova_server_crud_and_actions(auth_ctx: tuple[str, str, dict[str, Any]]) -> None:
|
||||
token, _pid, _ = auth_ctx
|
||||
mv = {"OpenStack-API-Version": "compute 2.79"}
|
||||
st, _, flavors = _request("GET", "nova", "/v2.1/flavors", token=token)
|
||||
st, _, images = _request("GET", "glance", "/v2/images", token=token)
|
||||
st, _, nets = _request("GET", "neutron", "/v2.0/networks", token=token)
|
||||
flavor = (flavors or {}).get("flavors") or [{}]
|
||||
image = (images or {}).get("images") or [{}]
|
||||
networks = (nets or {}).get("networks") or [{}]
|
||||
# Prefer tenant demo-net over shared public for boot.
|
||||
net = next((n for n in networks if n.get("name") == "demo-net"), networks[0])
|
||||
name = f"lc-{uuid.uuid4().hex[:8]}"
|
||||
st, _, created = _request(
|
||||
"POST",
|
||||
"nova",
|
||||
"/v2.1/servers",
|
||||
token=token,
|
||||
headers=mv,
|
||||
data={
|
||||
"server": {
|
||||
"name": name,
|
||||
"flavorRef": flavor[0]["id"],
|
||||
"imageRef": image[0]["id"],
|
||||
"networks": [{"uuid": net["id"]}],
|
||||
}
|
||||
},
|
||||
)
|
||||
assert st == 202, created
|
||||
sid = (created or {}).get("server", {}).get("id")
|
||||
assert sid
|
||||
st, _, shown = _request("GET", "nova", f"/v2.1/servers/{sid}", token=token, headers=mv)
|
||||
assert st == 200
|
||||
assert shown["server"]["name"] == name
|
||||
|
||||
st, _, _ = _request(
|
||||
"PUT",
|
||||
"nova",
|
||||
f"/v2.1/servers/{sid}",
|
||||
token=token,
|
||||
headers=mv,
|
||||
data={"server": {"name": f"{name}-ren"}},
|
||||
)
|
||||
assert st == 200
|
||||
|
||||
for action, expect in (
|
||||
({"os-stop": None}, "SHUTOFF"),
|
||||
({"os-start": None}, "ACTIVE"),
|
||||
({"reboot": {"type": "SOFT"}}, "ACTIVE"),
|
||||
({"suspend": None}, "SUSPENDED"),
|
||||
({"resume": None}, "ACTIVE"),
|
||||
({"pause": None}, "PAUSED"),
|
||||
({"unpause": None}, "ACTIVE"),
|
||||
({"shelve": None}, "SHELVED"),
|
||||
({"shelveOffload": None}, "SHELVED_OFFLOADED"),
|
||||
({"unshelve": None}, "ACTIVE"),
|
||||
):
|
||||
st, _, _ = _request(
|
||||
"POST",
|
||||
"nova",
|
||||
f"/v2.1/servers/{sid}/action",
|
||||
token=token,
|
||||
headers=mv,
|
||||
data=action,
|
||||
)
|
||||
assert st == 202, action
|
||||
st, _, shown = _request("GET", "nova", f"/v2.1/servers/{sid}", token=token, headers=mv)
|
||||
assert shown["server"]["status"] == expect, (action, shown["server"]["status"])
|
||||
|
||||
st, _, _ = _request("DELETE", "nova", f"/v2.1/servers/{sid}", token=token, headers=mv)
|
||||
assert st == 204
|
||||
st, _, _ = _request("GET", "nova", f"/v2.1/servers/{sid}", token=token, headers=mv)
|
||||
assert st == 404
|
||||
|
||||
|
||||
def test_neutron_network_subnet_port_fip_crud(
|
||||
auth_ctx: tuple[str, str, dict[str, Any]],
|
||||
) -> None:
|
||||
token, _pid, _ = auth_ctx
|
||||
tag = uuid.uuid4().hex[:8]
|
||||
st, _, created = _request(
|
||||
"POST",
|
||||
"neutron",
|
||||
"/v2.0/networks",
|
||||
token=token,
|
||||
data={"network": {"name": f"n-{tag}", "admin_state_up": True}},
|
||||
)
|
||||
assert st == 201
|
||||
nid = created["network"]["id"]
|
||||
st, _, sub = _request(
|
||||
"POST",
|
||||
"neutron",
|
||||
"/v2.0/subnets",
|
||||
token=token,
|
||||
data={
|
||||
"subnet": {
|
||||
"name": f"s-{tag}",
|
||||
"network_id": nid,
|
||||
"cidr": "10.210.0.0/24",
|
||||
"ip_version": 4,
|
||||
}
|
||||
},
|
||||
)
|
||||
assert st == 201
|
||||
sid = sub["subnet"]["id"]
|
||||
st, _, port = _request(
|
||||
"POST",
|
||||
"neutron",
|
||||
"/v2.0/ports",
|
||||
token=token,
|
||||
data={"port": {"name": f"p-{tag}", "network_id": nid}},
|
||||
)
|
||||
assert st == 201
|
||||
pid = port["port"]["id"]
|
||||
|
||||
st, _, nets = _request("GET", "neutron", "/v2.0/networks", token=token)
|
||||
assert any(n.get("id") == nid for n in nets.get("networks") or [])
|
||||
public = next(n for n in nets["networks"] if n.get("name") == "public")
|
||||
assert public.get("router:external") is True
|
||||
|
||||
st, _, fip = _request(
|
||||
"POST",
|
||||
"neutron",
|
||||
"/v2.0/floatingips",
|
||||
token=token,
|
||||
data={"floatingip": {"floating_network_id": public["id"]}},
|
||||
)
|
||||
assert st == 201
|
||||
fid = fip["floatingip"]["id"]
|
||||
st, _, shown = _request("GET", "neutron", f"/v2.0/floatingips/{fid}", token=token)
|
||||
assert st == 200
|
||||
assert shown["floatingip"]["id"] == fid
|
||||
|
||||
st, _, _ = _request("DELETE", "neutron", f"/v2.0/floatingips/{fid}", token=token)
|
||||
assert st == 204
|
||||
st, _, _ = _request("DELETE", "neutron", f"/v2.0/ports/{pid}", token=token)
|
||||
assert st == 204
|
||||
st, _, _ = _request("DELETE", "neutron", f"/v2.0/subnets/{sid}", token=token)
|
||||
assert st == 204
|
||||
st, _, _ = _request("DELETE", "neutron", f"/v2.0/networks/{nid}", token=token)
|
||||
assert st == 204
|
||||
st, _, _ = _request("GET", "neutron", f"/v2.0/networks/{nid}", token=token)
|
||||
assert st == 404
|
||||
|
||||
|
||||
def test_glance_cinder_heat_octavia_ironic_swift_crud(
|
||||
auth_ctx: tuple[str, str, dict[str, Any]],
|
||||
) -> None:
|
||||
token, project_id, _ = auth_ctx
|
||||
tag = uuid.uuid4().hex[:8]
|
||||
|
||||
st, _, img = _request(
|
||||
"POST",
|
||||
"glance",
|
||||
"/v2/images",
|
||||
token=token,
|
||||
data={"name": f"img-{tag}", "container_format": "bare", "disk_format": "qcow2"},
|
||||
)
|
||||
assert st == 201
|
||||
iid = img["id"]
|
||||
st, _, shown = _request("GET", "glance", f"/v2/images/{iid}", token=token)
|
||||
assert st == 200 and shown["id"] == iid
|
||||
st, _, _ = _request("DELETE", "glance", f"/v2/images/{iid}", token=token)
|
||||
assert st == 204
|
||||
|
||||
st, _, vol = _request(
|
||||
"POST",
|
||||
"cinder",
|
||||
"/v3/volumes",
|
||||
token=token,
|
||||
data={"volume": {"size": 1, "name": f"vol-{tag}"}},
|
||||
)
|
||||
assert st == 202
|
||||
vid = vol["volume"]["id"]
|
||||
st, _, shown = _request("GET", "cinder", f"/v3/volumes/{vid}", token=token)
|
||||
assert st == 200 and shown["volume"]["id"] == vid
|
||||
st, _, _ = _request(
|
||||
"PUT",
|
||||
"cinder",
|
||||
f"/v3/volumes/{vid}",
|
||||
token=token,
|
||||
data={"volume": {"name": f"vol-{tag}-ren", "description": "lc"}},
|
||||
)
|
||||
assert st == 200
|
||||
st, _, _ = _request("DELETE", "cinder", f"/v3/volumes/{vid}", token=token)
|
||||
assert st == 202
|
||||
st, _, _ = _request("GET", "cinder", f"/v3/volumes/{vid}", token=token)
|
||||
assert st == 404
|
||||
|
||||
st, _, stack = _request(
|
||||
"POST",
|
||||
"heat",
|
||||
f"/v1/{project_id}/stacks",
|
||||
token=token,
|
||||
data={
|
||||
"stack_name": f"stk-{tag}",
|
||||
"template": {"heat_template_version": "2015-04-30", "resources": {}},
|
||||
},
|
||||
)
|
||||
assert st == 201
|
||||
sid = stack["stack"]["id"]
|
||||
sname = stack["stack"]["stack_name"]
|
||||
st, _, _ = _request(
|
||||
"PUT",
|
||||
"heat",
|
||||
f"/v1/{project_id}/stacks/{sname}/{sid}",
|
||||
token=token,
|
||||
data={
|
||||
"template": {
|
||||
"heat_template_version": "2015-04-30",
|
||||
"description": "upd",
|
||||
"resources": {},
|
||||
},
|
||||
"description": "updated",
|
||||
},
|
||||
)
|
||||
assert st == 200
|
||||
st, _, shown = _request("GET", "heat", f"/v1/{project_id}/stacks/{sname}/{sid}", token=token)
|
||||
assert st == 200
|
||||
assert shown["stack"]["stack_status"] == "UPDATE_COMPLETE"
|
||||
st, _, _ = _request("DELETE", "heat", f"/v1/{project_id}/stacks/{sname}/{sid}", token=token)
|
||||
assert st == 204
|
||||
|
||||
st, _, subs = _request("GET", "neutron", "/v2.0/subnets", token=token)
|
||||
sub_id = (subs.get("subnets") or [{}])[0].get("id")
|
||||
assert sub_id
|
||||
st, _, lb = _request(
|
||||
"POST",
|
||||
"octavia",
|
||||
"/v2/lbaas/loadbalancers",
|
||||
token=token,
|
||||
data={"loadbalancer": {"name": f"lb-{tag}", "vip_subnet_id": sub_id}},
|
||||
)
|
||||
assert st == 201
|
||||
lbid = lb["loadbalancer"]["id"]
|
||||
st, _, _ = _request(
|
||||
"PUT",
|
||||
"octavia",
|
||||
f"/v2/lbaas/loadbalancers/{lbid}",
|
||||
token=token,
|
||||
data={"loadbalancer": {"name": f"lb-{tag}-ren"}},
|
||||
)
|
||||
assert st == 200
|
||||
st, _, _ = _request("DELETE", "octavia", f"/v2/lbaas/loadbalancers/{lbid}", token=token)
|
||||
assert st == 204
|
||||
st, _, _ = _request("GET", "octavia", f"/v2/lbaas/loadbalancers/{lbid}", token=token)
|
||||
assert st == 404
|
||||
|
||||
st, _, node = _request(
|
||||
"POST",
|
||||
"ironic",
|
||||
"/v1/nodes",
|
||||
token=token,
|
||||
headers={"OpenStack-API-Version": "baremetal 1.90"},
|
||||
data={"name": f"node-{tag}", "driver": "ipmi"},
|
||||
)
|
||||
assert st == 201
|
||||
nuid = node["uuid"]
|
||||
st, _, _ = _request(
|
||||
"PUT",
|
||||
"ironic",
|
||||
f"/v1/nodes/{nuid}/states/power",
|
||||
token=token,
|
||||
headers={"OpenStack-API-Version": "baremetal 1.90"},
|
||||
data={"target": "power on"},
|
||||
)
|
||||
assert st == 202
|
||||
st, _, shown = _request(
|
||||
"GET",
|
||||
"ironic",
|
||||
f"/v1/nodes/{nuid}",
|
||||
token=token,
|
||||
headers={"OpenStack-API-Version": "baremetal 1.90"},
|
||||
)
|
||||
assert shown.get("power_state") == "power on"
|
||||
st, _, _ = _request(
|
||||
"DELETE",
|
||||
"ironic",
|
||||
f"/v1/nodes/{nuid}",
|
||||
token=token,
|
||||
headers={"OpenStack-API-Version": "baremetal 1.90"},
|
||||
)
|
||||
assert st == 204
|
||||
|
||||
acct = f"AUTH_{project_id}"
|
||||
cname = f"c-{tag}"
|
||||
st, _, _ = _request("PUT", "swift", f"/v1/{acct}/{cname}", token=token)
|
||||
assert st == 201
|
||||
st, _, _ = _request(
|
||||
"PUT",
|
||||
"swift",
|
||||
f"/v1/{acct}/{cname}/hello.txt",
|
||||
token=token,
|
||||
headers={"Content-Type": "text/plain"},
|
||||
raw_body=b"hello",
|
||||
)
|
||||
assert st == 201
|
||||
st, _, obj = _request("GET", "swift", f"/v1/{acct}/{cname}/hello.txt", token=token)
|
||||
assert st == 200
|
||||
st, _, _ = _request("DELETE", "swift", f"/v1/{acct}/{cname}/hello.txt", token=token)
|
||||
assert st == 204
|
||||
st, _, _ = _request("DELETE", "swift", f"/v1/{acct}/{cname}", token=token)
|
||||
assert st == 204
|
||||
st, _, containers = _request("GET", "swift", f"/v1/{acct}", token=token)
|
||||
assert st == 200
|
||||
assert not any(c.get("name") == cname for c in containers or [])
|
||||
|
||||
|
||||
def test_project_scoped_token_required_for_nova() -> None:
|
||||
status, headers, body = _request(
|
||||
"POST",
|
||||
"keystone",
|
||||
"/v3/auth/tokens",
|
||||
data={
|
||||
"auth": {
|
||||
"identity": {
|
||||
"methods": ["password"],
|
||||
"password": {
|
||||
"user": {
|
||||
"name": "demo",
|
||||
"domain": {"name": "Default"},
|
||||
"password": "secret",
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
token = headers.get("X-Subject-Token") or headers.get("x-subject-token")
|
||||
assert status == 201 and token
|
||||
st, _, err = _request("GET", "nova", "/v2.1/servers", token=token)
|
||||
assert st in {401, 403}
|
||||
assert isinstance(err, dict)
|
||||
@@ -8,10 +8,13 @@ import asyncpg
|
||||
import pytest
|
||||
|
||||
from app.openstack.demo_cloud import (
|
||||
DEMO_CLUSTER_SIZES,
|
||||
DEMO_PROFILE,
|
||||
DEMO_SERVER_COUNT,
|
||||
clear_openstack_state,
|
||||
demo_profile_name,
|
||||
is_demo_profile,
|
||||
openstack_demo_summary,
|
||||
resolve_demo_size,
|
||||
seed_openstack_demo,
|
||||
)
|
||||
from app.openstack.seed import seed_openstack
|
||||
@@ -41,15 +44,35 @@ async def conn():
|
||||
await connection.close()
|
||||
|
||||
|
||||
def test_resolve_demo_sizes() -> None:
|
||||
small = resolve_demo_size("small")
|
||||
large = resolve_demo_size("demo")
|
||||
big = resolve_demo_size("big")
|
||||
assert small.hypervisors == 3 and small.servers == 50
|
||||
assert large.hypervisors == 10 and large.servers == 1000
|
||||
assert big.hypervisors == 20 and big.servers == 2000
|
||||
assert big.volumes == large.volumes * 2
|
||||
assert small.extra_networks < large.extra_networks < big.extra_networks
|
||||
assert small.keypairs_per_user < large.keypairs_per_user < big.keypairs_per_user
|
||||
assert is_demo_profile(demo_profile_name("large"))
|
||||
assert is_demo_profile(DEMO_PROFILE)
|
||||
assert not is_demo_profile("minimal")
|
||||
assert {cfg.name for cfg in DEMO_CLUSTER_SIZES.values()} == {"small", "large", "big"}
|
||||
|
||||
|
||||
async def test_demo_seed_roundtrip(conn: asyncpg.Connection) -> None:
|
||||
await seed_openstack_demo(conn)
|
||||
# Prefer small for CI speed; still exercises full topology.
|
||||
await seed_openstack_demo(conn, size="small")
|
||||
summary = await openstack_demo_summary(conn)
|
||||
small = DEMO_CLUSTER_SIZES["small"]
|
||||
assert summary["loaded"] is True
|
||||
assert summary["servers"] == DEMO_SERVER_COUNT
|
||||
assert summary["hypervisors"] == 16
|
||||
assert summary["servers"] == small.servers
|
||||
assert summary["hypervisors"] == small.hypervisors
|
||||
assert summary["volumes"] == small.volumes
|
||||
assert summary["size"] == "small"
|
||||
assert summary["profile"] == demo_profile_name("small")
|
||||
assert summary["projects"] == 5
|
||||
assert summary["volumes"] == 600
|
||||
assert summary["profile"] == DEMO_PROFILE
|
||||
assert len(summary["sizes"]) == 3
|
||||
|
||||
await clear_openstack_state(conn)
|
||||
result = await seed_openstack(conn)
|
||||
@@ -59,8 +82,10 @@ async def test_demo_seed_roundtrip(conn: asyncpg.Connection) -> None:
|
||||
assert summary["servers"] == 1
|
||||
assert summary["profile"] == "minimal"
|
||||
|
||||
# Restore demo so a shared lab DB stays usable after the test.
|
||||
await seed_openstack_demo(conn)
|
||||
# Restore large so a shared lab DB stays usable after the test.
|
||||
await seed_openstack_demo(conn, size="large")
|
||||
summary = await openstack_demo_summary(conn)
|
||||
large = DEMO_CLUSTER_SIZES["large"]
|
||||
assert summary["loaded"] is True
|
||||
assert summary["servers"] == DEMO_SERVER_COUNT
|
||||
assert summary["servers"] == large.servers
|
||||
assert summary["hypervisors"] == large.hypervisors
|
||||
|
||||
Reference in New Issue
Block a user