Skip to content

Commit f12a6a7

Browse files
authored
[Feature]: Add external-filter in Header for advanced routing (#1804)
* feature: Add external-filter in Header for advanced routing * add docs and client use case for external-filter * make labelSelectorFilter into func and add test case * take redundant log --------- Signed-off-by: rayne-Li <lizhengyin1115@163.com> Signed-off-by: rayneLi <lizhengyin1115@163.com>
1 parent b9ade5a commit f12a6a7

File tree

8 files changed

+523
-9
lines changed

8 files changed

+523
-9
lines changed

docs/source/features/gateway-plugins.rst

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,39 @@ To set up rate limiting, add the user header in the request, like this:
194194
If rate limit support is required, ensure this `user` header is always set in the request. if you do not need rate limit, you do not need to set this header.
195195

196196

197+
External Filter
198+
===============
199+
The ``external-filter`` header is evaluated **before** the routing strategy selects the optimal target pod. allows users to dynamically restrict the target Pods using Kubernetes ``labelSelector`` expressions.
200+
201+
The header value follows the Kubernetes label selector syntax:
202+
203+
- ``key=value``
204+
- ``key in (a, b)``
205+
- ``key!=value``
206+
- comma-separated selector list
207+
208+
For label selector syntax reference:
209+
https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/
210+
211+
.. code-block:: bash
212+
curl -v http://${ENDPOINT}/v1/completions \
213+
-H "Content-Type: application/json" \
214+
-H "routing-strategy: random" \
215+
-H "external-filter: environment=production,tier=frontend" \
216+
-d '{
217+
"model": "deepseek-r1-distill-llama-8b",
218+
"prompt": "San Francisco is a",
219+
"max_tokens": 128,
220+
"temperature": 0
221+
}'
222+
223+
.. note::
224+
1. Filtering happens **before** the routing strategy. It never changes which Pods are considered “optimal” by the routing strategy.
225+
2. The ``external-filter`` only takes effect when a ``routing-strategy``` is set.
226+
3. It only reduces the Pod selected by `model.aibrix.ai/name` and set by applying extra label constraints.
227+
4. Same as `no target pod`, If the filter eliminates all Pods, the request will fail with ``no ready pods for routing``.
228+
5. ``external-filter`` is optional. When omitted, no extra filtering is applied.
229+
197230
Headers Explanation
198231
--------------------
199232

@@ -216,7 +249,8 @@ Target Headers & General Error Headers
216249
- Specifies the destination pod selected by the routing algorithm. Useful for verifying routing decisions.
217250
* - ``routing-strategy``
218251
- Defines the routing strategy applied to this request. Ensures correct routing logic is followed.
219-
252+
* - ``external-filter``
253+
- Provides a generic and pluggable mechanism to further filter candidate Pods after routing. Filtering applied only when a routing strategy is set; Skipped if no routing algorithm is present.
220254

221255
Routing & Error Debugging Headers
222256
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

pkg/plugins/gateway/gateway.go

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -160,7 +160,7 @@ func (s *Server) Process(srv extProcPb.ExternalProcessor_ProcessServer) error {
160160
}
161161
}
162162

163-
func (s *Server) selectTargetPod(ctx *types.RoutingContext, pods types.PodList) (string, error) {
163+
func (s *Server) selectTargetPod(ctx *types.RoutingContext, pods types.PodList, externalFilterExpr string) (string, error) {
164164
router, err := routing.Select(ctx)
165165
if err != nil {
166166
return "", err
@@ -170,6 +170,13 @@ func (s *Server) selectTargetPod(ctx *types.RoutingContext, pods types.PodList)
170170
return "", fmt.Errorf("no pods for routing")
171171
}
172172
readyPods := utils.FilterRoutablePods(pods.All())
173+
174+
// filter pod by header 'external-filter'
175+
readyPods, err = utils.FilterPodsByLabelSelector(readyPods, externalFilterExpr)
176+
if err != nil {
177+
return "", fmt.Errorf("filter pods by label selector failed: %v", err)
178+
}
179+
173180
if len(readyPods) == 0 {
174181
return "", fmt.Errorf("no ready pods for routing")
175182
}

pkg/plugins/gateway/gateway_req_body.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,8 @@ func (s *Server) HandleRequestBody(ctx context.Context, requestID string, req *e
7474
headers = buildEnvoyProxyHeaders(headers, HeaderModel, model)
7575
klog.InfoS("request start", "requestID", requestID, "requestPath", requestPath, "model", model, "stream", stream)
7676
} else {
77-
targetPodIP, err := s.selectTargetPod(routingCtx, podsArr)
77+
externalFilter := routingCtx.ReqHeaders[HeaderExternalFilter]
78+
targetPodIP, err := s.selectTargetPod(routingCtx, podsArr, externalFilter)
7879
if targetPodIP == "" || err != nil {
7980
klog.ErrorS(err, "failed to select target pod", "requestID", requestID, "routingStrategy", routingAlgorithm, "model", model, "routingDuration", routingCtx.GetRoutingDelay())
8081
return generateErrorResponse(

pkg/plugins/gateway/gateway_req_headers.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,8 @@ func (s *Server) HandleRequestHeaders(ctx context.Context, requestID string, req
5656
requestPath = string(n.RawValue)
5757
case authorizationKey:
5858
reqHeaders[n.Key] = string(n.RawValue)
59+
case HeaderExternalFilter:
60+
reqHeaders[n.Key] = string(n.RawValue)
5961
}
6062
}
6163

0 commit comments

Comments
 (0)