Add OpenStack request-body schemas and nested console PARAM sync.
This commit is contained in:
+33
-10
@@ -9,18 +9,41 @@ import requests
|
||||
|
||||
|
||||
def discover_base_url() -> str:
|
||||
for candidate in (
|
||||
os.environ.get("OVIRT_TEST_URL"),
|
||||
"https://api-gateway",
|
||||
"https://127.0.0.1",
|
||||
"https://127.0.0.1:9443",
|
||||
):
|
||||
if not candidate:
|
||||
"""Prefer OVIRT_TEST_URL / OVIRT_ENGINE_PORT; require Engine SSO path (not a foreign :443)."""
|
||||
|
||||
port = (os.environ.get("OVIRT_ENGINE_PORT") or "").strip()
|
||||
candidates: list[str] = []
|
||||
if os.environ.get("OVIRT_TEST_URL"):
|
||||
candidates.append(os.environ["OVIRT_TEST_URL"].rstrip("/"))
|
||||
candidates.append("https://api-gateway")
|
||||
if port and port != "443":
|
||||
candidates.append(f"https://127.0.0.1:{port}")
|
||||
candidates.extend(
|
||||
[
|
||||
"https://127.0.0.1:7443",
|
||||
"https://127.0.0.1:6443",
|
||||
"https://127.0.0.1",
|
||||
"https://127.0.0.1:9443",
|
||||
]
|
||||
)
|
||||
seen: set[str] = set()
|
||||
for candidate in candidates:
|
||||
if not candidate or candidate in seen:
|
||||
continue
|
||||
seen.add(candidate)
|
||||
try:
|
||||
r = requests.get(f"{candidate.rstrip('/')}/health/live", timeout=3, verify=False)
|
||||
if r.status_code == 200:
|
||||
return candidate.rstrip("/")
|
||||
live = requests.get(f"{candidate}/health/live", timeout=3, verify=False)
|
||||
if live.status_code != 200:
|
||||
continue
|
||||
# Distinguish this lab from other listeners on :443.
|
||||
probe = requests.get(
|
||||
f"{candidate}/ovirt-engine/api/",
|
||||
headers={"Accept": "application/json"},
|
||||
timeout=3,
|
||||
verify=False,
|
||||
)
|
||||
if probe.status_code in {200, 401}:
|
||||
return candidate
|
||||
except Exception:
|
||||
continue
|
||||
pytest.skip("no running oVirt simulator gateway")
|
||||
|
||||
@@ -0,0 +1,282 @@
|
||||
"""Nested inventory + affinity/quota realism after minimal seed."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import uuid
|
||||
|
||||
import pytest
|
||||
import requests
|
||||
|
||||
from app.ovirt.ids import stable_id
|
||||
|
||||
from .conftest import auth_headers, collection_items, oauth_token
|
||||
|
||||
pytestmark = pytest.mark.integration
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def session_ctx():
|
||||
from .conftest import discover_base_url
|
||||
|
||||
base = discover_base_url()
|
||||
token = oauth_token(base)
|
||||
return base, auth_headers(token, version="4")
|
||||
|
||||
|
||||
def _ids() -> dict[str, str]:
|
||||
return {
|
||||
"vm": str(stable_id("vm", "lab-vm-01")),
|
||||
"dc": str(stable_id("dc", "Default")),
|
||||
"cluster": str(stable_id("cluster", "Default")),
|
||||
"host": str(stable_id("host", "host01")),
|
||||
"nic": str(stable_id("nic", "lab-vm-01")),
|
||||
"snap": str(stable_id("snap", "lab-vm-01", "1")),
|
||||
"user": str(stable_id("user", "admin")),
|
||||
"net": str(stable_id("net", "ovirtmgmt")),
|
||||
}
|
||||
|
||||
|
||||
def test_nested_seeded_collections_non_empty(session_ctx) -> None:
|
||||
base, headers = session_ctx
|
||||
ids = _ids()
|
||||
probes = [
|
||||
(f"/ovirt-engine/api/datacenters/{ids['dc']}/quotas", "quota"),
|
||||
(f"/ovirt-engine/api/clusters/{ids['cluster']}/affinitygroups", "affinity_group"),
|
||||
(f"/ovirt-engine/api/vms/{ids['vm']}/nics", "nic"),
|
||||
(f"/ovirt-engine/api/vms/{ids['vm']}/snapshots", "snapshot"),
|
||||
(f"/ovirt-engine/api/vms/{ids['vm']}/diskattachments", "disk_attachment"),
|
||||
(f"/ovirt-engine/api/vms/{ids['vm']}/graphicsconsoles", "graphics_console"),
|
||||
(f"/ovirt-engine/api/vms/{ids['vm']}/mediateddevices", "vm_mediated_device"),
|
||||
(f"/ovirt-engine/api/vms/{ids['vm']}/affinitylabels", "affinity_label"),
|
||||
(f"/ovirt-engine/api/hosts/{ids['host']}/nics", "nic"),
|
||||
(f"/ovirt-engine/api/hosts/{ids['host']}/storage", "host_storage"),
|
||||
(f"/ovirt-engine/api/clusters/{ids['cluster']}/glustervolumes", "gluster_volume"),
|
||||
(f"/ovirt-engine/api/networks/{ids['net']}/networklabels", "network_label"),
|
||||
(f"/ovirt-engine/api/users/{ids['user']}/sshpublickeys", "ssh_public_key"),
|
||||
("/ovirt-engine/api/affinitygroups", "affinity_group"),
|
||||
("/ovirt-engine/api/quotas", "quota"),
|
||||
]
|
||||
for path, element in probes:
|
||||
r = requests.get(f"{base}{path}", headers=headers, verify=False, timeout=60)
|
||||
assert r.status_code == 200, f"{path}: {r.status_code} {r.text[:200]}"
|
||||
items = collection_items(r.json(), element)
|
||||
assert len(items) >= 1, f"{path}: expected non-empty {element}"
|
||||
assert items[0].get("id") and items[0].get("href")
|
||||
|
||||
|
||||
def test_job_steps_and_event_entity_href(session_ctx) -> None:
|
||||
base, headers = session_ctx
|
||||
jobs = requests.get(f"{base}/ovirt-engine/api/jobs", headers=headers, verify=False, timeout=60)
|
||||
assert jobs.status_code == 200
|
||||
job = collection_items(jobs.json(), "job")[0]
|
||||
steps = requests.get(
|
||||
f"{base}/ovirt-engine/api/jobs/{job['id']}/steps",
|
||||
headers=headers,
|
||||
verify=False,
|
||||
timeout=60,
|
||||
)
|
||||
assert steps.status_code == 200
|
||||
step = collection_items(steps.json(), "step")[0]
|
||||
assert step.get("href") == f"/ovirt-engine/api/jobs/{job['id']}/steps/{step['id']}"
|
||||
one = requests.get(
|
||||
f"{base}/ovirt-engine/api/jobs/{job['id']}/steps/{step['id']}",
|
||||
headers=headers,
|
||||
verify=False,
|
||||
timeout=60,
|
||||
)
|
||||
assert one.status_code == 200
|
||||
assert one.json()["step"]["href"]
|
||||
|
||||
events = requests.get(
|
||||
f"{base}/ovirt-engine/api/events", headers=headers, verify=False, timeout=60
|
||||
)
|
||||
assert events.status_code == 200
|
||||
ev = collection_items(events.json(), "event")[0]
|
||||
one_ev = requests.get(
|
||||
f"{base}/ovirt-engine/api/events/{ev['id']}",
|
||||
headers=headers,
|
||||
verify=False,
|
||||
timeout=60,
|
||||
)
|
||||
assert one_ev.status_code == 200
|
||||
assert one_ev.json()["event"].get("href")
|
||||
|
||||
|
||||
def test_vm_tag_and_permission_get_by_id(session_ctx) -> None:
|
||||
base, headers = session_ctx
|
||||
ids = _ids()
|
||||
tags = requests.get(
|
||||
f"{base}/ovirt-engine/api/vms/{ids['vm']}/tags",
|
||||
headers=headers,
|
||||
verify=False,
|
||||
timeout=60,
|
||||
)
|
||||
assert tags.status_code == 200
|
||||
tag = collection_items(tags.json(), "tag")[0]
|
||||
one_tag = requests.get(
|
||||
f"{base}/ovirt-engine/api/vms/{ids['vm']}/tags/{tag['id']}",
|
||||
headers=headers,
|
||||
verify=False,
|
||||
timeout=60,
|
||||
)
|
||||
assert one_tag.status_code == 200, one_tag.text
|
||||
assert one_tag.json()["tag"]["id"] == tag["id"]
|
||||
|
||||
perms = requests.get(
|
||||
f"{base}/ovirt-engine/api/vms/{ids['vm']}/permissions",
|
||||
headers=headers,
|
||||
verify=False,
|
||||
timeout=60,
|
||||
)
|
||||
assert perms.status_code == 200
|
||||
perm = collection_items(perms.json(), "permission")[0]
|
||||
one_perm = requests.get(
|
||||
f"{base}/ovirt-engine/api/vms/{ids['vm']}/permissions/{perm['id']}",
|
||||
headers=headers,
|
||||
verify=False,
|
||||
timeout=60,
|
||||
)
|
||||
assert one_perm.status_code == 200, one_perm.text
|
||||
assert one_perm.json()["permission"]["id"] == perm["id"]
|
||||
assert one_perm.json()["permission"].get("href")
|
||||
|
||||
|
||||
def test_nic_and_snapshot_get_by_id(session_ctx) -> None:
|
||||
base, headers = session_ctx
|
||||
ids = _ids()
|
||||
nic = requests.get(
|
||||
f"{base}/ovirt-engine/api/vms/{ids['vm']}/nics/{ids['nic']}",
|
||||
headers=headers,
|
||||
verify=False,
|
||||
timeout=60,
|
||||
)
|
||||
assert nic.status_code == 200, nic.text
|
||||
assert nic.json()["nic"]["id"] == ids["nic"]
|
||||
|
||||
snap = requests.get(
|
||||
f"{base}/ovirt-engine/api/vms/{ids['vm']}/snapshots/{ids['snap']}",
|
||||
headers=headers,
|
||||
verify=False,
|
||||
timeout=60,
|
||||
)
|
||||
assert snap.status_code == 200, snap.text
|
||||
assert snap.json()["snapshot"]["id"] == ids["snap"]
|
||||
|
||||
|
||||
def test_affinity_group_create_returns_entity(session_ctx) -> None:
|
||||
base, headers = session_ctx
|
||||
cluster = str(stable_id("cluster", "Default"))
|
||||
name = f"ag-{uuid.uuid4().hex[:8]}"
|
||||
r = requests.post(
|
||||
f"{base}/ovirt-engine/api/clusters/{cluster}/affinitygroups",
|
||||
headers=headers,
|
||||
json={"affinity_group": {"name": name, "enforcing": False}},
|
||||
verify=False,
|
||||
timeout=60,
|
||||
)
|
||||
assert r.status_code == 201, r.text
|
||||
body = r.json()["affinity_group"]
|
||||
assert body["name"] == name
|
||||
assert body["id"] and "/affinitygroups/" in body["href"]
|
||||
|
||||
listing = requests.get(
|
||||
f"{base}/ovirt-engine/api/clusters/{cluster}/affinitygroups",
|
||||
headers=headers,
|
||||
verify=False,
|
||||
timeout=60,
|
||||
)
|
||||
names = [i["name"] for i in collection_items(listing.json(), "affinity_group")]
|
||||
assert name in names
|
||||
|
||||
|
||||
def test_quota_create_and_read_after_write(session_ctx) -> None:
|
||||
base, headers = session_ctx
|
||||
dc = str(stable_id("dc", "Default"))
|
||||
name = f"quota-{uuid.uuid4().hex[:8]}"
|
||||
created = requests.post(
|
||||
f"{base}/ovirt-engine/api/datacenters/{dc}/quotas",
|
||||
headers=headers,
|
||||
json={"quota": {"name": name, "description": "lab"}},
|
||||
verify=False,
|
||||
timeout=60,
|
||||
)
|
||||
assert created.status_code == 201, created.text
|
||||
qid = created.json()["quota"]["id"]
|
||||
detail = requests.get(
|
||||
f"{base}/ovirt-engine/api/datacenters/{dc}/quotas/{qid}",
|
||||
headers=headers,
|
||||
verify=False,
|
||||
timeout=60,
|
||||
)
|
||||
assert detail.status_code == 200
|
||||
assert detail.json()["quota"]["name"] == name
|
||||
|
||||
|
||||
def test_template_create_from_vm_copies_nested(session_ctx) -> None:
|
||||
base, headers = session_ctx
|
||||
vm = str(stable_id("vm", "lab-vm-01"))
|
||||
name = f"tpl-{uuid.uuid4().hex[:8]}"
|
||||
created = requests.post(
|
||||
f"{base}/ovirt-engine/api/templates",
|
||||
headers=headers,
|
||||
json={"template": {"name": name, "vm": {"id": vm}}},
|
||||
verify=False,
|
||||
timeout=60,
|
||||
)
|
||||
assert created.status_code == 201, created.text
|
||||
tid = created.json()["template"]["id"]
|
||||
nics = requests.get(
|
||||
f"{base}/ovirt-engine/api/templates/{tid}/nics",
|
||||
headers=headers,
|
||||
verify=False,
|
||||
timeout=60,
|
||||
)
|
||||
assert nics.status_code == 200
|
||||
assert len(collection_items(nics.json(), "nic")) >= 1
|
||||
das = requests.get(
|
||||
f"{base}/ovirt-engine/api/templates/{tid}/diskattachments",
|
||||
headers=headers,
|
||||
verify=False,
|
||||
timeout=60,
|
||||
)
|
||||
assert das.status_code == 200
|
||||
assert len(collection_items(das.json(), "disk_attachment")) >= 1
|
||||
|
||||
|
||||
def test_vm_clone_copies_nics_and_disks(session_ctx) -> None:
|
||||
base, headers = session_ctx
|
||||
vm = str(stable_id("vm", "lab-vm-01"))
|
||||
clone_name = f"clone-{uuid.uuid4().hex[:8]}"
|
||||
action = requests.post(
|
||||
f"{base}/ovirt-engine/api/vms/{vm}/clone",
|
||||
headers=headers,
|
||||
json={"action": {"vm": {"name": clone_name}}},
|
||||
verify=False,
|
||||
timeout=60,
|
||||
)
|
||||
assert action.status_code == 200, action.text
|
||||
assert "job" in action.json().get("action", {})
|
||||
|
||||
listing = requests.get(
|
||||
f"{base}/ovirt-engine/api/vms",
|
||||
headers=headers,
|
||||
verify=False,
|
||||
timeout=60,
|
||||
)
|
||||
vms = {v["name"]: v for v in collection_items(listing.json(), "vm")}
|
||||
assert clone_name in vms
|
||||
clone_id = vms[clone_name]["id"]
|
||||
nics = requests.get(
|
||||
f"{base}/ovirt-engine/api/vms/{clone_id}/nics",
|
||||
headers=headers,
|
||||
verify=False,
|
||||
timeout=60,
|
||||
)
|
||||
assert len(collection_items(nics.json(), "nic")) >= 1
|
||||
das = requests.get(
|
||||
f"{base}/ovirt-engine/api/vms/{clone_id}/diskattachments",
|
||||
headers=headers,
|
||||
verify=False,
|
||||
timeout=60,
|
||||
)
|
||||
assert len(collection_items(das.json(), "disk_attachment")) >= 1
|
||||
@@ -0,0 +1,33 @@
|
||||
"""Cluster size specs for demo seed profiles."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
from app.ovirt.demo_datacenter import CLUSTER_SIZES, normalize_cluster_size
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("name", "hosts", "vms"),
|
||||
[
|
||||
("small", 3, 50),
|
||||
("large", 10, 1000),
|
||||
("big", 30, 2000),
|
||||
],
|
||||
)
|
||||
def test_cluster_size_targets(name: str, hosts: int, vms: int) -> None:
|
||||
spec = CLUSTER_SIZES[name]
|
||||
assert spec.hosts == hosts
|
||||
assert spec.vms == vms
|
||||
topology = spec.datacenters * spec.clusters_per_dc * spec.hosts_per_cluster
|
||||
assert topology == hosts
|
||||
|
||||
|
||||
def test_demo_alias_maps_to_large() -> None:
|
||||
assert normalize_cluster_size("demo") == "large"
|
||||
assert normalize_cluster_size("LARGE") == "large"
|
||||
|
||||
|
||||
def test_unknown_size_raises() -> None:
|
||||
with pytest.raises(ValueError):
|
||||
normalize_cluster_size("huge")
|
||||
@@ -0,0 +1,104 @@
|
||||
"""Console body examples use minimal-seed IDs and Engine-shaped roots."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from app.ovirt.ids import stable_id
|
||||
from app.web.ovirt_body_examples import body_example_for
|
||||
from app.web.ovirt_catalog import path_param_example
|
||||
|
||||
|
||||
def test_path_params_use_minimal_seed_ids() -> None:
|
||||
assert path_param_example("vmId") == str(stable_id("vm", "lab-vm-01"))
|
||||
assert path_param_example("hostId") == str(stable_id("host", "host01"))
|
||||
assert path_param_example("clusterId") == str(stable_id("cluster", "Default"))
|
||||
assert path_param_example("storageDomainId") == str(stable_id("sd", "data1"))
|
||||
|
||||
|
||||
def test_post_vm_body_is_root_wrapped_with_cluster_ref() -> None:
|
||||
body = body_example_for(method="POST", kind="collection", element="vm", path="/vms")
|
||||
assert body is not None
|
||||
assert "vm" in body
|
||||
vm = body["vm"]
|
||||
assert vm["cluster"]["id"] == str(stable_id("cluster", "Default"))
|
||||
assert vm["template"]["name"] == "Blank"
|
||||
|
||||
|
||||
def test_disk_attachment_creates_disk_inline() -> None:
|
||||
body = body_example_for(
|
||||
method="POST",
|
||||
kind="collection",
|
||||
element="disk_attachment",
|
||||
path="/vms/{vm_id}/diskattachments",
|
||||
)
|
||||
assert body is not None
|
||||
disk = body["disk_attachment"]["disk"]
|
||||
assert "id" not in disk
|
||||
assert disk["name"] == "example-attached-disk"
|
||||
assert disk["provisioned_size"] == 10737418240
|
||||
|
||||
|
||||
def test_put_does_not_rename_entity() -> None:
|
||||
body = body_example_for(method="PUT", kind="item", element="vm", path="/vms/{vm_id}")
|
||||
assert body is not None
|
||||
assert "name" not in body["vm"]
|
||||
assert body["vm"]["description"].startswith("Updated")
|
||||
|
||||
|
||||
def test_action_start_uses_action_root() -> None:
|
||||
body = body_example_for(
|
||||
method="POST", kind="action", element="action", path="/vms/{vm_id}/start"
|
||||
)
|
||||
assert body == {"action": {"async": True}}
|
||||
|
||||
|
||||
def test_body_fields_derived_from_example_for_params_drawer() -> None:
|
||||
from app.web.ovirt_catalog import _body_fields_from_example, ovirt_method_payload
|
||||
|
||||
fields = _body_fields_from_example(
|
||||
{"bookmark": {"name": "example-bookmark", "value": "Vms: status=up"}},
|
||||
element="bookmark",
|
||||
)
|
||||
names = {f["name"] for f in fields}
|
||||
assert names == {"name", "value"}
|
||||
|
||||
payload = ovirt_method_payload(
|
||||
major=45,
|
||||
path="/ovirt-engine/api/bookmarks",
|
||||
verb="POST",
|
||||
runtime_version="ovirt-4.5",
|
||||
)
|
||||
assert payload["body_example"] is not None
|
||||
assert "bookmark" in payload["body_example"]
|
||||
field_names = {f["name"] for f in payload["body_fields"]}
|
||||
assert "name" in field_names
|
||||
assert "value" in field_names
|
||||
|
||||
|
||||
def test_body_fields_include_nested_vm_example_paths() -> None:
|
||||
from app.web.ovirt_catalog import _body_fields_from_example, ovirt_method_payload
|
||||
from app.web.ovirt_body_examples import body_example_for
|
||||
|
||||
example = body_example_for(
|
||||
method="POST",
|
||||
kind="collection",
|
||||
element="vm",
|
||||
path="/ovirt-engine/api/vms",
|
||||
)
|
||||
fields = _body_fields_from_example(example, element="vm")
|
||||
names = {f["name"] for f in fields}
|
||||
assert "name" in names
|
||||
assert "memory" in names
|
||||
assert "cpu.topology.sockets" in names
|
||||
assert "os.type" in names
|
||||
assert "cluster.id" in names
|
||||
assert "template.name" in names
|
||||
|
||||
payload = ovirt_method_payload(
|
||||
major=45,
|
||||
path="/ovirt-engine/api/vms",
|
||||
verb="POST",
|
||||
runtime_version="ovirt-4.5",
|
||||
)
|
||||
nested = {f["name"] for f in payload["body_fields"]}
|
||||
assert "cluster.id" in nested
|
||||
assert "cpu.topology.cores" in nested
|
||||
Reference in New Issue
Block a user