Repository navigation
feat(BA-8440): receive ingress pushes on a worker internal API port - #15656
jopemachine wants to merge 6 commits into
Conversation
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The ingress API moves off the user-facing API port to an internal port set by proxy_worker.internal_api_bind_addr, authenticated by the shared API secret, and writes to the worker's ingress state. The port opens before the worker registers, so the coordinator can push right after registration. Upsert and replace report the revision held for each pushed ingress. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…PI package The handler is served only on the worker internal API port, so keep it out of the directory that holds the subapps mounted on the user-reachable API port. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
964510a to
6cba6b1
Compare
…tion middleware The internal API answers only the coordinator, so it does not need the HTML error page and the jinja2 setup it requires. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
There was a problem hiding this comment.
🟢 Approval recommended
No unresolved issues were identified in listener isolation, authentication, startup ordering, or revision responses.
No open issues 🎉
What changed in this PR
Adds an optional internal API listener for AppProxy worker ingress pushes, separate from user-facing ports and backed by the existing ingress state.
Changes:
- Adds a configurable internal listener that starts before worker registration.
- Moves authenticated ingress endpoints off the public API.
- Returns applied revisions for upserts and replacements, with updated tests.
| File | Description |
|---|---|
| tests/unit/appproxy/worker/api/internal/test_ingress.py | Updates imports and verifies revision responses. |
| tests/unit/appproxy/worker/api/internal/BUILD | Registers the internal API tests. |
| src/ai/backend/appproxy/worker/server.py | Adds internal listener lifecycle and separates routing. |
| src/ai/backend/appproxy/worker/config.py | Adds the optional internal bind address. |
| src/ai/backend/appproxy/worker/api/internal/ingress.py | Connects handlers to ingress state and returns revisions. |
| changes/15656.feature.md | Documents the new capability. |
Review effort: Balanced
| app = web.Application(middlewares=[coordinator_auth_middleware]) | ||
| app["prefix"] = "v2/ingress" | ||
| # The ingress state is not built at startup yet. | ||
| handler = IngressHandler(state=None) | ||
| app["_root.context"] = root_ctx | ||
| handler = IngressHandler(root_ctx.ingress_state) |
There was a problem hiding this comment.
Just a question. Does it change the API endpoint prefix?
There was a problem hiding this comment.
No, only the port is changed to use the internal API. The path prefix remains the same.
| Field(default=None), | ||
| BackendAIConfigMeta( | ||
| description=( | ||
| "The address where the worker receives ingress pushes from the coordinator, " |
There was a problem hiding this comment.
The coordinator uses this API address? And it should be a single one?
There was a problem hiding this comment.
Yes, that's correct. Each worker runs as a single process and is assigned its own API address.
| ingress.frontend.key(): ingress.revision for ingress in body.parsed.ingresses | ||
| } |
There was a problem hiding this comment.
Does the update apply to an ingress instance? then it would be better to make a response from an ingress instance
There was a problem hiding this comment.
There's no problem with having apply and replace_all return a value. However, since they would always return the same value as the ingresses argument, I don't think it would add much value at this point.
… a push apply and replace_all return the ingresses held after writing, and the upsert and replace responses are built from them instead of echoing the request. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The validating writer rejects an older revision with 409 before writing, so the ingresses held after a push are always the ones sent, and returning them from the writer changed no response. This reverts commit bd4fa0c. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Once the web UI handles the existing APIs, we plan to remove the parts of our API that call the App Proxy worker directly. Running a separate internal API port would only add cost, so this is closed as not planned. |
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
📚 Stacked PRs
Part of the AppProxy worker Ingress stack (epic BA-8433). Merge in order (bottom-up):
feat(BA-8437): write circuit events to the worker ingress state #15650 —(merged)feat(BA-8437): write circuit events to the worker ingress statefeat(BA-8440): receive ingress pushes on a worker internal API port ← you are herefeat(BA-8443): push ingresses from the coordinator to worker internal API portsResolves #15536 (BA-8440)
Summary
proxy_worker.internal_api_bind_addr, unset by default. When set, the worker opens an internal API port apart from the traffic and API ports; unset opens nothing./v2/ingressoff the user-facing API port (global_subapp_pkgs) to that internal port. Requests are authenticated byX-BackendAI-Tokenagainst the shared API secret, and writes go to the worker's ingress state built iningress_state_ctx.worker_registration_ctx, so a push right after registration is received.upsertandreplacereturnIngressRevisionsResponsewith the revision held for each pushed ingress. The writer applies every ingress or raises the 409 revision conflict, so the held revision is the one sent.removestays 204.Test plan
tests/unit/appproxy/worker/api/internal/test_ingress.py— upsert and replace report the applied revisionsapp-proxy-worker.tomland the internal port set: no token → 401; upsert → 200{"10205": 3}; older revision → 409; replace → 200{"10206": 5}; remove → 204; revisions reflect each step; the user-facing API port answers 404 for/v2/ingress/revisionsand 200 for/status🤖 Generated with Claude Code