mindXtrain / tests /test_deploy_amd_dev_cloud.py
Gregory-L's picture
fork mindXtrain from GitHub (Professor-Codephreak/mindXtrain@661bd41) as the mindX-specific line
dfb775d verified
Raw
History Blame Contribute Delete
6.69 kB
"""AMD Dev Cloud REST client — stubs the httpx transport, no network.
`httpx.MockTransport` is built into httpx (no `respx` dep needed).
"""
from __future__ import annotations
import json
from typing import Any
import httpx
import pytest
from mindxtrain.deploy.amd_dev_cloud import (
DEFAULT_BASE,
AmdDevCloudAuthError,
AmdDevCloudClient,
AmdDevCloudConfig,
AmdDevCloudError,
build_create_payload,
extract_public_ip,
from_env,
missing_env,
)
def _cfg(**ov: Any) -> AmdDevCloudConfig:
base = {"token": "dop_v1_TEST", "ssh_key_id": 56216059}
base.update(ov)
return AmdDevCloudConfig(**base) # type: ignore[arg-type]
def _client_with_handler(handler, *, cfg: AmdDevCloudConfig | None = None) -> AmdDevCloudClient:
cfg = cfg or _cfg()
transport = httpx.MockTransport(handler)
return AmdDevCloudClient(
cfg,
client=httpx.Client(
base_url=cfg.api_base,
headers={"Authorization": f"Bearer {cfg.token}"},
transport=transport,
),
)
def test_missing_env_lists_required() -> None:
miss = missing_env({})
assert "AMD_DEV_CLOUD_TOKEN" in miss
assert "AMD_DEV_CLOUD_SSH_KEY_ID" in miss
full = missing_env({"AMD_DEV_CLOUD_TOKEN": "x", "AMD_DEV_CLOUD_SSH_KEY_ID": "1"})
assert full == []
def test_from_env_parses_int_ssh_key_id() -> None:
cfg = from_env({
"AMD_DEV_CLOUD_TOKEN": "x",
"AMD_DEV_CLOUD_SSH_KEY_ID": "56216059",
"AMD_DEV_CLOUD_TAGS": "a, b ,c",
})
assert cfg.ssh_key_id == 56216059
assert cfg.tags == ("a", "b", "c")
assert cfg.api_base == DEFAULT_BASE
def test_from_env_rejects_non_integer_ssh_key_id() -> None:
with pytest.raises(RuntimeError, match="must be an int"):
from_env({"AMD_DEV_CLOUD_TOKEN": "x", "AMD_DEV_CLOUD_SSH_KEY_ID": "notanint"})
def test_build_create_payload_matches_curl_example() -> None:
"""Pin the exact JSON body the user supplied in the spec."""
cfg = _cfg(
region="atl1",
size="gpu-mi300x8-1536gb-devcloud",
image="vllm-0-17-1",
ssh_key_id=56216059,
tags=("mindx", "train", "aglm", "agenticplace", "pythai"),
)
body = build_create_payload(cfg, name="mindxtrain", user_data="")
# Field-by-field match to the curl example to lock the contract.
assert body == {
"name": "mindxtrain",
"region": "atl1",
"size": "gpu-mi300x8-1536gb-devcloud",
"image": "vllm-0-17-1",
"ssh_keys": [56216059],
"backups": False,
"ipv6": True,
"monitoring": True,
"tags": ["mindx", "train", "aglm", "agenticplace", "pythai"],
"user_data": "",
"vpc_uuid": "",
}
def test_create_returns_droplet_dict() -> None:
captured = {}
def handler(request: httpx.Request) -> httpx.Response:
assert request.method == "POST"
assert request.url.path == "/v2/droplets"
captured["auth"] = request.headers.get("authorization")
captured["body"] = json.loads(request.content)
return httpx.Response(202, json={"droplet": {"id": 12345, "status": "new", "name": "mindxtrain"}})
with _client_with_handler(handler) as c:
droplet = c.create(name="mindxtrain", user_data="#cloud-config\n")
assert droplet["id"] == 12345
assert captured["auth"] == "Bearer dop_v1_TEST"
assert captured["body"]["name"] == "mindxtrain"
assert captured["body"]["user_data"] == "#cloud-config\n"
def test_create_401_raises_auth_error() -> None:
def handler(_request: httpx.Request) -> httpx.Response:
return httpx.Response(401, json={"message": "bad token"})
with _client_with_handler(handler) as c:
with pytest.raises(AmdDevCloudAuthError, match="auth failed"):
c.create()
def test_create_500_raises_generic_error() -> None:
def handler(_request: httpx.Request) -> httpx.Response:
return httpx.Response(500, json={"message": "boom"})
with _client_with_handler(handler) as c:
with pytest.raises(AmdDevCloudError, match="500"):
c.create()
def test_poll_until_active_returns_when_status_active() -> None:
statuses = iter(["new", "new", "active"])
def handler(_request: httpx.Request) -> httpx.Response:
s = next(statuses)
body = {"droplet": {
"id": 1, "status": s,
"networks": {"v4": [{"type": "public", "ip_address": "1.2.3.4"}]},
}}
return httpx.Response(200, json=body)
sleeps: list[float] = []
with _client_with_handler(handler) as c:
droplet = c.poll_until_active(1, sleep=sleeps.append, interval=5.0, timeout=600)
assert droplet["status"] == "active"
# Three GETs → two sleeps between them.
assert sleeps == [5.0, 5.0]
def test_poll_until_active_raises_on_timeout() -> None:
def handler(_request: httpx.Request) -> httpx.Response:
return httpx.Response(200, json={"droplet": {"id": 1, "status": "new"}})
# Fake a clock that's always past the deadline after one tick.
times = iter([0.0, 0.0, 1000.0, 1000.0, 1000.0])
with _client_with_handler(handler) as c:
with pytest.raises(TimeoutError, match="did not reach 'active'"):
c.poll_until_active(1, timeout=10.0, interval=1.0, sleep=lambda _s: None, now=lambda: next(times))
def test_poll_until_active_raises_on_terminal_state() -> None:
def handler(_request: httpx.Request) -> httpx.Response:
return httpx.Response(200, json={"droplet": {"id": 1, "status": "errored"}})
with _client_with_handler(handler) as c:
with pytest.raises(AmdDevCloudError, match="terminal state"):
c.poll_until_active(1, sleep=lambda _s: None, now=lambda: 0.0, timeout=60)
def test_extract_public_ip_handles_no_v4() -> None:
assert extract_public_ip({"networks": {"v4": []}}) is None
assert extract_public_ip({"networks": {}}) is None
assert extract_public_ip({}) is None
def test_extract_public_ip_prefers_public_type() -> None:
droplet = {"networks": {"v4": [
{"type": "private", "ip_address": "10.0.0.1"},
{"type": "public", "ip_address": "1.2.3.4"},
]}}
assert extract_public_ip(droplet) == "1.2.3.4"
def test_list_filters_by_name() -> None:
def handler(_request: httpx.Request) -> httpx.Response:
return httpx.Response(200, json={"droplets": [
{"id": 1, "name": "mindxtrain"},
{"id": 2, "name": "other"},
]})
with _client_with_handler(handler) as c:
all_ = c.list()
only = c.list(name="mindxtrain")
assert len(all_) == 2
assert len(only) == 1
assert only[0]["id"] == 1