fix(e2e): route model management to the control plane - #32261
fix(e2e): route model management to the control plane #32261mubashir1osmani wants to merge 2 commits into
Conversation
…eway.create_model The split-transport routing table listed only /model/info as a control-plane prefix, so /model/new and /model/delete were sent to the data-plane gateway, which does not serve management routes and 404s them. Every suite that registers deployments at runtime (llm_translation, batches, access_control) failed on the split stage deployment because of this. Widen the prefix to /model/ so all model-management routes reach the control plane while /models stays on the data plane. Separately, batch_client.py and several llm_translation tests call gateway.create_model, but Gateway never had that method, so all 17 batch tests errored at fixture setup with AttributeError. Add create_model/delete_model to Gateway (with the optional mode that batches needs) and make EndpointsClient delegate to it instead of carrying its own copy. Regression tests cover both: the routing predicate for management vs LLM paths and the Gateway model-management surface via a typed fake Transport. Both fail on the previous code
Greptile SummaryFixes two test harness bugs that caused 33+ stage failures:
Confidence Score: 4/5All changes are confined to the e2e test harness; no production code is touched. The routing fix and Gateway surface additions are correct, well-tested, and narrowly scoped. The two core fixes (routing prefix and Gateway method) are straightforward and backed by new passing tests. The only minor concern is that _RecordingTransport.post hard-codes a model_id payload for every response type, including NoBody — this relies on Pydantic's default extra-field ignore behaviour and would break if NoBody ever tightens its config. Everything else is clean. tests/e2e/test_e2e_gateway.py — the fake transport's post method could be more defensive about response types that have no fields.
|
| Filename | Overview |
|---|---|
| tests/e2e/transport.py | Changed CONTROL_PLANE_PREFIXES from "/model/info" to "/model/" so all model-management routes (/model/new, /model/delete, /model/update, /model/info) route to the control plane while /models stays on the data plane. Change is correct and complete. |
| tests/e2e/e2e_gateway.py | Added create_model and delete_model to Gateway, centralizing model management per harness convention. delete_model issues a warning on failure (appropriate for teardown). create_model correctly threads the optional mode argument that batch tests require. |
| tests/e2e/llm_translation/endpoints_client.py | Removed the duplicate create_model/delete_model implementations and delegated to Gateway. Removed now-unused imports. Clean refactor with no functional change from the caller's perspective. |
| tests/e2e/models.py | Extracted ModelMode as a named type alias so the mode literal is DRY across ModelInfoBody and Gateway.create_model's signature. Straightforward improvement. |
| tests/e2e/test_e2e_gateway.py | New unit tests for Gateway.create_model and delete_model via a typed fake transport (_RecordingTransport). The fake always returns {"model_id": "registered-id"} for every response_type including NoBody — this works today because Pydantic ignores extra fields by default, but is implicitly fragile. |
| tests/e2e/test_transport.py | New parametrized tests pinning is_control_plane_path for both management routes (must go to control plane) and LLM routes (must stay on data plane). Good regression coverage for the routing predicate. |
Reviews (1): Last reviewed commit: "fix(e2e): route model management to the ..." | Re-trigger Greptile
Greptile SummaryThis PR fixes two bugs in the e2e test harness that together caused 33+ test failures in the stage run:
Confidence Score: 4/5All changes are confined to the e2e test harness; no production code is touched. The routing fix is a single-character change with clear before/after evidence from curl proofs. The two targeted fixes are straightforward and well-evidenced. The new regression tests directly pin the exact failure conditions. The only concern is a fragile assumption in _RecordingTransport.post that works today because Pydantic ignores extra fields on NoBody by default. test_e2e_gateway.py — the _RecordingTransport.post canned response relies on Pydantic's default extra-field handling for the delete path.
|
| Filename | Overview |
|---|---|
| tests/e2e/transport.py | One-line fix: CONTROL_PLANE_PREFIXES entry changed from "/model/info" to "/model/" so that /model/new and /model/delete are routed to the control plane; /models (OpenAI list-models) correctly falls through to data plane since it doesn't start with "/model/" |
| tests/e2e/e2e_gateway.py | Adds create_model and delete_model to Gateway, moving the model-management surface out of EndpointsClient; create_model accepts an optional mode parameter to support batch deployments |
| tests/e2e/llm_translation/endpoints_client.py | create_model and delete_model now delegate to Gateway instead of duplicating the transport logic; unused imports cleaned up accordingly |
| tests/e2e/models.py | Extracts ModelMode type alias from the inline Literal in ModelInfoBody; purely cosmetic refactor enabling reuse in Gateway.create_model |
| tests/e2e/test_e2e_gateway.py | New unit tests for Gateway model-management surface using a typed fake Transport; _RecordingTransport.post always validates with {"model_id": "registered-id"} regardless of response_type, which silently succeeds for NoBody (delete path) only because Pydantic ignores extra fields by default |
| tests/e2e/test_transport.py | New parametrized tests pinning the is_control_plane_path predicate for management routes (/model/new, /model/delete, etc.) vs data-plane routes (/models, /chat/completions, etc.) |
Reviews (2): Last reviewed commit: "fix(e2e): route model management to the ..." | Re-trigger Greptile
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
The recording fake always answered with {"model_id": ...} even when the
caller asked for NoBody, which only validated because pydantic ignores extra
fields by default. Return an empty payload for response types that carry no
fields so a future extra="forbid" on NoBody cannot turn the delete test into
a ValidationError inside the fake
Relevant issues
Linear ticket
Pre-Submission checklist
Please complete all items before asking a LiteLLM maintainer to review your PR
@greptileaiand received a Confidence Score of at least 4/5 before requesting a maintainer reviewScreenshots / Proof of Fix
The 2026-07-06 00:59 UTC stage run failed 46 tests and errored 17 more. Every test that registers a deployment at runtime died on one of the two bugs this PR fixes
Proof against the live split deployment on berrie-litellm-stage, with
kubectl port-forward svc/litellm-gateway 14000:4000(data plane) andkubectl port-forward svc/litellm-backend 14001:4001(control plane). First/model/newagainst the data plane, which is where the old routing table sent it:The identical request against the control plane, which is where this PR routes it, registers the deployment:
Same split for the teardown route:
The new regression tests fail on the previous code (6 failed) and pass with the fix
Type
Bug Fix
Changes
Two harness bugs, one routing and one API surface, together account for 33 of the 63 failures in the stage run (the rest are exhausted Anthropic credits plus a handful still under investigation)
SplitTransportroutes each path to the data plane or the control plane by prefix, butCONTROL_PLANE_PREFIXESlisted only/model/info.POST /model/newandPOST /model/deletetherefore fell through to the data-plane gateway, which serves no management routes and answers 404. Every llm_translation test that registers its deployment at runtime failed at setup, and the access_control 403 test failed because its probe of/model/newnever reached the control plane's auth check. The prefix is now/model/, which covers all model-management routes while/models, the OpenAI-compatible list-models route, stays on the data planebatch_client.py,test_provider_features_e2e.py, andtest_deepseek_reasoning_e2e.pycallgateway.create_model, butGatewaynever had that method, so all 17 batch tests errored at fixture setup with AttributeError before a single request was sent.Gatewaynow ownscreate_model(with the optionalmodethe batches suite passes) anddelete_model, andEndpointsClientdelegates to them instead of carrying its own copy, per the harness convention that shared routes live onGatewayRegression coverage:
test_transport.pypins the management-vs-LLM routing predicate, andtest_e2e_gateway.pypins theGatewaymodel-management surface through a typed fakeTransport, including the batch-mode delegation that was the exact failing call chainA follow-up commit also excludes the master key from HttpTransport's repr: pytest prints fixture values verbatim into failure output, so every failed e2e test was embedding the proxy master key in CI logs and any aggregator they ship to (the 2026-07-06 stage run's Loki export contains it verbatim; rotating the stage master key is recommended). A canary test pins the fixture repr chain