Skip to content

xds/resolver: support HTTP filters for routes using cluster specifier plugins#9100

Open
Pranjali-2501 wants to merge 6 commits intogrpc:masterfrom
Pranjali-2501:fix-issue-8857
Open

xds/resolver: support HTTP filters for routes using cluster specifier plugins#9100
Pranjali-2501 wants to merge 6 commits intogrpc:masterfrom
Pranjali-2501:fix-issue-8857

Conversation

@Pranjali-2501
Copy link
Copy Markdown
Contributor

Fixes #8857

The xDS resolver was building the interceptor chain (which handles HTTP filters) only for routes that pointed to clusters directly or via weighted clusters. Routes that specified a cluster_specifier_plugin were missing this step, resulting in HTTP filters being ignored for those routes.

This PR updates newConfigSelector in xds_resolver.go to build the interceptor chain for routes with a ClusterSpecifierPlugin. Added a new test TestResolverClusterSpecifierPlugin_WithFilters in cluster_specifier_plugin_test.go to verify that the interceptor chain is built and executed for routes matching cluster specifier plugins when HTTP filters are configured.

RELEASE NOTES: N/A

@Pranjali-2501 Pranjali-2501 added this to the 1.82 Release milestone Apr 28, 2026
@Pranjali-2501 Pranjali-2501 added Type: Bug Area: xDS Includes everything xDS related, including LB policies used with xDS. labels Apr 28, 2026
@codecov
Copy link
Copy Markdown

codecov Bot commented Apr 28, 2026

Codecov Report

❌ Patch coverage is 53.33333% with 7 lines in your changes missing coverage. Please review.
✅ Project coverage is 83.22%. Comparing base (3d82ab3) to head (26ccfe8).
⚠️ Report is 8 commits behind head on master.

Files with missing lines Patch % Lines
internal/xds/resolver/xds_resolver.go 53.33% 4 Missing and 3 partials ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master    #9100      +/-   ##
==========================================
+ Coverage   83.08%   83.22%   +0.13%     
==========================================
  Files         413      413              
  Lines       33484    33487       +3     
==========================================
+ Hits        27821    27868      +47     
+ Misses       4240     4211      -29     
+ Partials     1423     1408      -15     
Files with missing lines Coverage Δ
internal/xds/resolver/xds_resolver.go 86.64% <53.33%> (-1.60%) ⬇️

... and 32 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Comment thread internal/xds/resolver/cluster_specifier_plugin_test.go Outdated
Comment thread internal/xds/resolver/cluster_specifier_plugin_test.go Outdated
Comment thread internal/xds/resolver/cluster_specifier_plugin_test.go Outdated
Comment thread internal/xds/resolver/xds_resolver.go Outdated
Comment on lines 420 to 437
interceptor, err := r.newInterceptor(r.xdsConfig.Listener.APIListener.HTTPFilters, nil, rt.HTTPFilterConfigOverride, r.xdsConfig.VirtualHost.HTTPFilterConfigOverride)
if err != nil {
// Clean up any interceptors that were successfully built
// for the current route before this error occurred. Note
// that this is not handled by the call to cs.stop() in the
// deferred function.
for _, i := range interceptors {
i.Close()
}
return nil, err
}
clusters.Add(&routeCluster{
name: clusterName,
interceptor: interceptor,
}, 1)
interceptors = append(interceptors, interceptor)
ci := r.addOrGetActiveClusterInfo(clusterName, "")
ci.cfg = xdsChildConfig{ChildPolicy: balancerConfig(r.xdsConfig.RouteConfig.ClusterSpecifierPlugins[rt.ClusterSpecifierPlugin])}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lines 420-437 can be shared with the weighted cluster case if we pull it out into a method/helper function. So, let's do that please.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have refactored it and move common logic in a seperate method.

Comment on lines +412 to +430
{
"loadBalancingConfig": [
{
"xds_cluster_manager_experimental": {
"children": {
"cluster_specifier_plugin:cspA": {
"childPolicy": [
{
"csp_experimental": {
"arbitrary_field": "anything"
}
}
]
}
}
}
}
]
}`
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: Can we have this whole block indented one tabspace to the left. That way, it will align with the code instead of sticking out.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

Comment on lines +446 to +447
_, err = res.Interceptor.NewStream(ctx, iresolver.RPCInfo{Method: "/service/method", Context: ctx}, func() {}, newStream)
if err != nil {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: Single line

Comment on lines +442 to +444
newStream := func(context.Context, func()) (iresolver.ClientStream, error) {
return nil, nil
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why significance does this empty function have? Why does it return nil, nil? Can a nil function be passed?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, we cannot pass nil here. The documentation for the NewStream method in the ClientInterceptor interface explicitly states: The caller must ensure done is non-nil. Passing nil would cause a panic.

Comment on lines +464 to +475
// Verify that second filter receives the base path.
cfg, err = newStreamChan.Receive(ctx)
if err != nil {
t.Fatalf("Timeout waiting for second filter to receive config: %v", err)
}
ofc = cfg.(overallFilterConfig)
if ofc.BasePath != "filter-path-2" {
t.Fatalf("Unexpected base path for second filter, got: %q, want: %q", ofc.BasePath, "filter-path-2")
}
if ofc.OverridePath != "" {
t.Fatalf("Unexpected override path for second filter, got: %q, want: %q", ofc.OverridePath, "")
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the point of having two filters? What extra test coverage does it provide over having a single filter?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It verifies that the system can correctly build and execute a chain of multiple interceptors. A single filter would only test that an interceptor works in isolation, not that the chaining mechanism itself is functional. It also ensures that configuration overrides are applied correctly on a per-filter basis.
In this test, test-filter-1 is configured with an override, while test-filter-2 uses its base configuration. This allows us to verify that the override for one filter does not affect the configuration of other filters in the chain.

@easwars easwars assigned Pranjali-2501 and unassigned easwars May 5, 2026
@Pranjali-2501 Pranjali-2501 requested a review from easwars May 8, 2026 16:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Area: xDS Includes everything xDS related, including LB policies used with xDS. Type: Bug

Projects

None yet

Development

Successfully merging this pull request may close these issues.

xds: build the interceptor chain for routes with cluster_specifier_plugins as well

3 participants