147f04c9a2
Persist aggregates and server security groups in domain tables, add Cinder quota show from DB-backed defaults, align unit tests with OpenStack series, auto-pick Keystone :5000/:15000 for smoke, and document test locations plus 6514/6514 results.
181 lines
6.7 KiB
Python
181 lines
6.7 KiB
Python
#!/usr/bin/env python3
|
|
"""Full-surface smoke: Keystone token → every default-port OpenStack service."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
import os
|
|
import sys
|
|
import urllib.error
|
|
import urllib.request
|
|
|
|
HOST = os.environ.get("OS_HOST", "127.0.0.1")
|
|
|
|
|
|
def _u(port: int, path: str) -> str:
|
|
return f"http://{HOST}:{port}{path}"
|
|
|
|
|
|
def request(
|
|
method: str,
|
|
url: str,
|
|
*,
|
|
data: dict | None = None,
|
|
token: str | None = None,
|
|
extra_headers: dict[str, str] | None = None,
|
|
):
|
|
body = None if data is None else json.dumps(data).encode()
|
|
headers = {"Accept": "application/json"}
|
|
if data is not None:
|
|
headers["Content-Type"] = "application/json"
|
|
if token:
|
|
headers["X-Auth-Token"] = token
|
|
if extra_headers:
|
|
headers.update(extra_headers)
|
|
req = urllib.request.Request(url, data=body, headers=headers, method=method)
|
|
try:
|
|
with urllib.request.urlopen(req, timeout=15) as res:
|
|
raw = res.read().decode()
|
|
return res.status, dict(res.headers), json.loads(raw) if raw else None
|
|
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, dict(exc.headers), parsed
|
|
except urllib.error.URLError as exc:
|
|
return 0, {}, {"error": str(exc.reason)}
|
|
|
|
|
|
def _keystone_base() -> str:
|
|
if len(sys.argv) > 1:
|
|
return sys.argv[1].rstrip("/")
|
|
for key in ("OS_AUTH_URL", "OS_GATEWAY_URL"):
|
|
raw = (os.environ.get(key) or "").strip()
|
|
if raw:
|
|
return raw.rstrip("/").removesuffix("/v3")
|
|
# Prefer :5000; fall back to local Compose override (:15000) when AirPlay owns 5000.
|
|
for port in (5000, 15000):
|
|
url = f"http://{HOST}:{port}"
|
|
status, _, _ = request("GET", f"{url}/health/ready")
|
|
if status == 200:
|
|
return url
|
|
return f"http://{HOST}:5000"
|
|
|
|
|
|
# (label, url, expected_json_key or None for version-only)
|
|
CHECKS: list[tuple[str, str, str | None]] = [
|
|
("nova.servers", _u(8774, "/v2.1/servers/detail"), "servers"),
|
|
("nova.flavors", _u(8774, "/v2.1/flavors"), "flavors"),
|
|
("nova.keypairs", _u(8774, "/v2.1/os-keypairs"), "keypairs"),
|
|
("nova.az", _u(8774, "/v2.1/os-availability-zone"), "availabilityZoneInfo"),
|
|
("nova.hypervisors", _u(8774, "/v2.1/os-hypervisors"), "hypervisors"),
|
|
("neutron.networks", _u(9696, "/v2.0/networks"), "networks"),
|
|
("neutron.routers", _u(9696, "/v2.0/routers"), "routers"),
|
|
("neutron.sg", _u(9696, "/v2.0/security-groups"), "security_groups"),
|
|
("neutron.fips", _u(9696, "/v2.0/floatingips"), "floatingips"),
|
|
("glance.images", _u(9292, "/v2/images"), "images"),
|
|
("cinder.volumes", _u(8776, "/v3/volumes/detail"), "volumes"),
|
|
("placement.rp", _u(8003, "/resource_providers"), "resource_providers"),
|
|
("heat.stacks", _u(8004, "/v1/demo/stacks"), "stacks"),
|
|
("swift.info", _u(8080, "/info"), None),
|
|
("ironic.nodes", _u(6385, "/v1/nodes"), "nodes"),
|
|
("octavia.lbs", _u(9876, "/v2/lbaas/loadbalancers"), "loadbalancers"),
|
|
("barbican.secrets", _u(9311, "/v1/secrets"), "secrets"),
|
|
("manila.shares", _u(8786, "/v2/shares"), "shares"),
|
|
("designate.zones", _u(9001, "/v2/zones"), "zones"),
|
|
("magnum.clusters", _u(9511, "/v1/clusters"), "clusters"),
|
|
("zun.containers", _u(9517, "/v1/containers"), "containers"),
|
|
("trove.instances", _u(8779, "/v1.0/instances"), "instances"),
|
|
("mistral.workflows", _u(8989, "/v2/workflows"), "workflows"),
|
|
("aodh.alarms", _u(8042, "/v2/alarms"), "alarms"),
|
|
("freezer.jobs", _u(9090, "/v2/jobs"), "jobs"),
|
|
("blazar.leases", _u(1234, "/leases"), "leases"),
|
|
("vitrage.alarms", _u(8999, "/v1/alarm"), "alarms"),
|
|
("masakari.segments", _u(15868, "/v1/segments"), "segments"),
|
|
("tacker.vnfs", _u(9890, "/v1.0/vnfs"), "vnfs"),
|
|
("adjutant.tasks", _u(5050, "/v1/tasks"), "tasks"),
|
|
("cloudkitty.services", _u(8889, "/v1/rating/module_config/hashmap/services"), "services"),
|
|
("heat-cfn.stacks", _u(8000, "/stacks"), "Stacks"),
|
|
]
|
|
|
|
|
|
def main() -> int:
|
|
keystone = _keystone_base()
|
|
auth = {
|
|
"auth": {
|
|
"identity": {
|
|
"methods": ["password"],
|
|
"password": {
|
|
"user": {
|
|
"name": "admin",
|
|
"domain": {"name": "Default"},
|
|
"password": "secret",
|
|
}
|
|
},
|
|
},
|
|
"scope": {"project": {"name": "demo", "domain": {"name": "Default"}}},
|
|
}
|
|
}
|
|
status, headers, body = request("POST", f"{keystone}/v3/auth/tokens", data=auth)
|
|
token = headers.get("X-Subject-Token") or headers.get("x-subject-token")
|
|
print("auth", status, "token", bool(token))
|
|
if status != 201 or not token:
|
|
print(body)
|
|
return 1
|
|
catalog = (body or {}).get("token", {}).get("catalog", [])
|
|
print("catalog_services", len(catalog), sorted(s.get("name") for s in catalog))
|
|
|
|
# Microversion header round-trip on Nova
|
|
st, hdrs, _ = request(
|
|
"GET",
|
|
_u(8774, "/v2.1/servers"),
|
|
token=token,
|
|
extra_headers={"OpenStack-API-Version": "compute 2.79"},
|
|
)
|
|
mv = hdrs.get("OpenStack-API-Version") or hdrs.get("openstack-api-version")
|
|
print("nova.microversion", st, mv)
|
|
if st >= 400:
|
|
return 1
|
|
|
|
failed = 0
|
|
for label, url, key in CHECKS:
|
|
# Heat needs project id in path — fetch from token
|
|
if label == "heat.stacks":
|
|
project_id = (body or {}).get("token", {}).get("project", {}).get("id")
|
|
if project_id:
|
|
url = _u(8004, f"/v1/{project_id}/stacks")
|
|
st, _, payload = request("GET", url, token=token)
|
|
if key is None:
|
|
print(label, st)
|
|
else:
|
|
items = (payload or {}).get(key)
|
|
count = (
|
|
len(items)
|
|
if isinstance(items, list)
|
|
else ("ok" if items is not None else "missing")
|
|
)
|
|
print(label, st, "count", count)
|
|
if st == 0 or st >= 400:
|
|
print(" FAIL", payload)
|
|
failed += 1
|
|
|
|
# Root discovery per port (Keystone may be remapped, e.g. host :15000).
|
|
from urllib.parse import urlparse
|
|
|
|
ks_port = urlparse(keystone).port or 5000
|
|
for port, name in [(ks_port, "keystone"), (8774, "nova"), (6385, "ironic"), (8080, "swift")]:
|
|
st, _, payload = request("GET", _u(port, "/"))
|
|
print(f"root.{name}", st, list((payload or {}).keys())[:3])
|
|
|
|
if failed:
|
|
print(f"FAILED {failed}/{len(CHECKS)}")
|
|
return 1
|
|
print("OK", len(CHECKS), "service checks")
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main())
|