Repository navigation
fix: run the nok8s smoketest without a cluster - #1708
Merged
Merged
Conversation
`llmdbenchmark ... smoketest` on a no-Kubernetes scenario died before step 00 with "Failed to load Kubernetes configuration", contradicting docs/nok8s.md which says no cluster access is required. Two layers were broken: `_do_smoketest` was the only phase builder that never computed `container_only`, so `resolve_cluster()` took the Kubernetes branch and the phase exited 1 before any step ran. It also enforced a namespace unconditionally, which standup, teardown and run all relax for nok8s. Fixing the phase builder (rather than the subcommand dispatcher) also repairs the `experiment` subcommand, which calls `_do_smoketest` with no nok8s guard. Even past that, the validators were cluster-shaped by construction: `require_cmd()` + `require_namespace()`, Service IP discovery, pod readiness waits, pod IPs and an `oc get route` lookup, none of which exist in a nok8s deployment. `BaseSmoketest.run_health_checks` and `run_inference_test` now branch on `container_only` to two HTTP probes that read the same rendered `34_nok8s-containers.yaml` launch spec step 06 deployed from, so the endpoint probed and the model asserted match what was launched. No validator subclass overrides either method, so the single base branch covers every stack. Step 01's kubectl-based ephemeral pod cleanup is skipped for the same reason (nok8s does carry a namespace, so the guard is load-bearing). Probes fail loudly on an unreachable endpoint, a non-200, a non-JSON body, a model the server does not list, and an empty completion, and issue no request at all under --dry-run. Verified against a stub server speaking the same OpenAI-shaped /v1/models and /v1/completions contract on an ephemeral loopback port, plus end to end through the CLI with KUBECONFIG=/nonexistent (all three steps green with the server up, a clean endpoint failure with it down). A live nok8s standup was not verifiable here: vLLM needs --gpus all. Fixes llm-d#1698 Signed-off-by: Kush Zingade <[email protected]>
Review of the cluster-free nok8s probes surfaced four gaps, all in the "reports a false failure" or "reports a confusing failure" direction rather than false success: Timeouts and retries did not match the cluster path they replace. One 15s timeout covered both requests, while BaseSmoketest._curl_post defaults to 120s and _try_completions retries the transient statuses (502/503/504) with backoff, precisely because a no-retry probe "made every transient warmup race a hard smoketest failure". Split the constant (15s for GET /v1/models, 120s for the completion, which is not always quick on a cold or CPU-served model) and mount a urllib3 Retry adapter on a requests.Session for both probes. raise_on_status stays False so an exhausted retry still reports the real status and body instead of a RetryError. A 200 response whose JSON was not the expected shape (null, a top level array, choices as a list of strings) raised AttributeError out of the probe. _safe_execute_step turned that into a step failure, so it was never a false pass, but the operator saw a stack trace instead of a message naming the URL and body. Guard the top-level type in both probes; the non-JSON and unexpected-shape branches now share one message, so this is a line shorter than what it replaces. test_do_smoketest_sets_container_only asserted `resolve_cluster() is None`, which is tautological (the method is annotated -> None) and could only react to a regression by shelling out against whatever kubeconfig the machine has. Patch the lazily imported resolver to fail instead, so the short-circuit is pinned directly. docs/nok8s.md documented every other phase but not smoketest, and the smoketests README module tree did not list nok8s.py. Verified live rather than by mocking: a stdlib stub server on localhost:8081 driven through the real CLI (KUBECONFIG=/nonexistent) gives exit 0 for a healthy stack and exit 1 with a clean diagnostic for wrong model, null body, array body, empty choices, string choices, persistent 503 and no server at all. A stub that answers 503 once and then 200 now passes, which is the transient case the retry budget is for. New tests cover the retry (asserting the server saw two requests) and every malformed shape; mutation-testing both guards (removing the container_only short-circuit, emptying the status forcelist) fails the matching test. Suite: 762 passed, 31 skipped. All pre-commit hooks pass. Signed-off-by: Kush Zingade <[email protected]>
UgaTheDev
requested review from
Vezio,
achandrasekar,
kalantar,
maugustosilva,
mengmeiye and
namasl
as code owners
July 30, 2026 17:05
maugustosilva
approved these changes
Aug 7, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
Fixes #1698.
llmdbenchmark ... smoketeston a no-Kubernetes scenario died before step 00 with"Failed to load Kubernetes configuration", contradicting docs/nok8s.md, which says
no cluster access is required. Two layers were broken.
_do_smoketestwas the only phase builder that never computedcontainer_only, soresolve_cluster()took the Kubernetes branch and the phase exited 1 before any stepran. It also enforced a namespace unconditionally, which standup, teardown and run all
relax for nok8s. Fixing the phase builder rather than the subcommand dispatcher also
repairs
experiment, which calls_do_smoketestwith no nok8s guard.Even past that, the validators were cluster-shaped by construction:
require_cmd()plus
require_namespace(), Service IP discovery, pod readiness waits, pod IPs and anoc get routelookup, none of which exist in a nok8s deployment.BaseSmoketest.run_health_checksandrun_inference_testnow branch oncontainer_onlyto two HTTP probes that read the same rendered34_nok8s-containers.yamllaunch spec step 06 deployed from, so the endpoint probedand the model asserted match what was actually launched. No validator subclass
overrides either method, so the single base branch covers every stack. Step 01's
kubectl-based ephemeral pod cleanup is skipped for the same reason.
The probes fail loudly on an unreachable endpoint, a non-200, a non-JSON body, a
malformed body shape, a model the server does not list, and an empty completion, and
issue no request at all under
--dry-run.Three deliberate calls:
BaseSmoketest._curl_post's existing default. A 16-token generation on a cold oraccelerator: cpustack is not reliably sub-15s, and a single 15s budget would haveturned that into a false failure.
requests.Sessionwith a urllib3 Retryadapter (total=3, backoff_factor=2, 502/503/504,
raise_on_status=False).base.py already records that the old no-retry cluster behaviour "made every transient
warmup race a hard smoketest failure", and the first draft of these probes had
reintroduced exactly that. Reusing base.py's
_is_retryable/_compute_backoffwasnot possible, since base.py imports nok8s.py at module level and importing back would
be circular.
raise_on_status=Falsepreserves the existing"returned HTTP 503: " diagnostic instead of surfacing a RetryError.
standupphase still does not chain the smoketest for nok8s. Changing whichphases auto-chain is a separate behavioural decision for maintainers, and I cannot
validate it on a host with no GPU. The skip is now documented in docs/nok8s.md and
explained honestly in the code comment rather than by a stale rationale.
Not done here: a
/v1/chat/completionsfallback. The nok8s stack launchesvllm serve <model>from the same rendered spec the probe reads, and vLLM's OpenAIserver always registers
/v1/completions. The cluster path's fallback exists becauseit probes arbitrary user-supplied routes; nok8s cannot produce that configuration.
Type of change
How Has This Been Tested?
Adds
tests/test_nok8s_smoketest.py(23 tests) driving the real code against a stdlibstub server on an ephemeral loopback port.
Baseline on main is 739; the branch adds 23.
Revert check, with the three production files reset to main and the tests kept:
Two mutation checks on the new logic: emptying the Retry
status_forcelistfailstest_transient_503_is_retried, and deleting thecontainer_onlyshort-circuit inExecutionContext.resolve_clusterfailstest_do_smoketest_sets_container_only.Exercised through the real CLI with
KUBECONFIG=/nonexistent, no--dry-runand nomocking: healthy gives exit 0; wrong model, null body, array body, empty choices,
string choices, null completion body, persistent 503 and no server at all each give
exit 1 with the correct diagnostic and no traceback; 503-once-then-200 gives exit 0,
which is the retry budget working.
--dry-runissues zero I/O.pre-commit run --fileson the 5 changed files: py-compile, pytest, render-changed-scenarios, detect-secrets, ruff-check and ruff-format all Passed. The SBOM hook
self-skipped on its file filter.
Test Configuration
was not verifiable here, since vLLM needs
--gpus alland this host has no GPU. Theprobes are exercised against a stub server speaking the same OpenAI-shaped contract,
not against a real vLLM.
Checklist
./setup/standup.sh->run.sh->./setup/teardown.shsequence completed successfullypre-commit runwas run and all checks passeddocs/nok8s.md,llmdbenchmark/smoketests/README.md)No cluster and no GPU here, so the standup/run/teardown sequence was not run.