|
1 | 1 | """E2E test fixtures: provision real endpoints, configure SDK, clean up.""" |
2 | 2 |
|
3 | | -import json |
4 | 3 | import logging |
5 | 4 | import os |
6 | | -import urllib.request |
| 5 | +import subprocess |
7 | 6 | from pathlib import Path |
8 | 7 |
|
9 | 8 | import pytest |
|
17 | 16 | # Repo root: tests/e2e/conftest.py -> ../../ |
18 | 17 | _REPO_ROOT = Path(__file__).resolve().parents[2] |
19 | 18 |
|
20 | | -_GRAPHQL_URL = "https://api.runpod.io/graphql" |
21 | | - |
22 | | - |
23 | | -def _graphql(api_key: str, query: str, variables: dict | None = None) -> dict: |
24 | | - """Execute a Runpod GraphQL query.""" |
25 | | - payload = json.dumps({"query": query, "variables": variables or {}}).encode() |
26 | | - req = urllib.request.Request( |
27 | | - f"{_GRAPHQL_URL}?api_key={api_key}", |
28 | | - data=payload, |
29 | | - headers={"Content-Type": "application/json"}, |
30 | | - ) |
31 | | - with urllib.request.urlopen(req, timeout=30) as resp: |
32 | | - return json.loads(resp.read()) |
33 | | - |
34 | | - |
35 | | -def _delete_endpoints_by_name(api_key: str, names: list[str]) -> None: |
36 | | - """Delete endpoints matching the given names via GraphQL API.""" |
37 | | - result = _graphql(api_key, """ |
38 | | - query { myself { endpoints { id name } } } |
39 | | - """) |
40 | | - all_endpoints = result.get("data", {}).get("myself", {}).get("endpoints", []) |
41 | | - name_set = set(names) |
42 | | - targets = [ep for ep in all_endpoints if ep.get("name") in name_set] |
43 | | - |
44 | | - if not targets: |
45 | | - log.warning("No matching endpoints found for names: %s", names) |
46 | | - return |
47 | | - |
48 | | - for ep in targets: |
49 | | - try: |
50 | | - resp = _graphql( |
51 | | - api_key, |
52 | | - "mutation($id: String!) { deleteEndpoint(id: $id) }", |
53 | | - {"id": ep["id"]}, |
54 | | - ) |
55 | | - if "errors" in resp: |
56 | | - log.warning("Failed to delete %s (%s): %s", ep["name"], ep["id"], resp["errors"]) |
57 | | - else: |
58 | | - log.info("Deleted endpoint %s (%s)", ep["name"], ep["id"]) |
59 | | - except Exception: |
60 | | - log.exception("Error deleting endpoint %s (%s)", ep["name"], ep["id"]) |
61 | | - |
62 | 19 |
|
63 | 20 | @pytest.fixture(scope="session", autouse=True) |
64 | 21 | def verify_local_runpod(): |
@@ -100,13 +57,17 @@ def endpoints(require_api_key, test_cases): |
100 | 57 | log.info("Endpoint ready: name=%s image=%s template.dockerArgs=%s", ep.name, ep.image, ep.template.dockerArgs if ep.template else "N/A") |
101 | 58 | yield eps |
102 | 59 |
|
103 | | - # Delete provisioned endpoints via GraphQL API directly. |
104 | | - # flash undeploy relies on .runpod/resources.pkl which doesn't exist in CI. |
105 | | - api_key = os.environ.get("RUNPOD_API_KEY", "") |
106 | | - endpoint_names = [ep.name for ep in eps.values()] |
107 | | - log.info("Cleaning up %d provisioned endpoints: %s", len(endpoint_names), endpoint_names) |
108 | | - if api_key and endpoint_names: |
109 | | - try: |
110 | | - _delete_endpoints_by_name(api_key, endpoint_names) |
111 | | - except Exception: |
112 | | - log.exception("Endpoint cleanup failed (API key may lack GraphQL access)") |
| 60 | + log.info("Cleaning up all provisioned endpoints") |
| 61 | + try: |
| 62 | + result = subprocess.run( |
| 63 | + ["flash", "undeploy", "--all", "--force"], |
| 64 | + capture_output=True, |
| 65 | + text=True, |
| 66 | + timeout=120, |
| 67 | + ) |
| 68 | + if result.returncode == 0: |
| 69 | + log.info("Undeployed all endpoints") |
| 70 | + else: |
| 71 | + log.warning("flash undeploy --all --force failed (rc=%d): %s", result.returncode, result.stderr) |
| 72 | + except Exception: |
| 73 | + log.exception("Failed to undeploy endpoints") |
0 commit comments