-
Notifications
You must be signed in to change notification settings - Fork 4.7k
xds/resolver: support HTTP filters for routes using cluster specifier plugins #9100
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
09860eb
2472785
38a6048
05efd9b
8fac701
26ccfe8
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -31,14 +31,17 @@ import ( | |
| "google.golang.org/grpc/internal/testutils/xds/e2e" | ||
| "google.golang.org/grpc/internal/xds/balancer/clustermanager" | ||
| "google.golang.org/grpc/internal/xds/clusterspecifier" | ||
| "google.golang.org/grpc/internal/xds/httpfilter" | ||
| "google.golang.org/grpc/resolver" | ||
| "google.golang.org/grpc/serviceconfig" | ||
| "google.golang.org/protobuf/proto" | ||
| "google.golang.org/protobuf/types/known/anypb" | ||
| "google.golang.org/protobuf/types/known/wrapperspb" | ||
|
|
||
| v3corepb "github.com/envoyproxy/go-control-plane/envoy/config/core/v3" | ||
| v3listenerpb "github.com/envoyproxy/go-control-plane/envoy/config/listener/v3" | ||
| v3routepb "github.com/envoyproxy/go-control-plane/envoy/config/route/v3" | ||
| v3httppb "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/network/http_connection_manager/v3" | ||
| ) | ||
|
|
||
| func init() { | ||
|
|
@@ -337,3 +340,136 @@ func (s) TestXDSResolverDelayedOnCommittedCSP(t *testing.T) { | |
| }` | ||
| verifyUpdateFromResolver(ctx, t, stateCh, wantSC) | ||
| } | ||
|
|
||
| // TestResolverClusterSpecifierPlugin_WithFilters tests the case where a route | ||
| // configuration containing cluster specifier plugins is sent by the management | ||
| // server, and HTTP filters are configured. The test verifies that the | ||
| // interceptor chain is built for routes matching cluster specifier plugins. | ||
| func (s) TestResolverClusterSpecifierPlugin_WithFilters(t *testing.T) { | ||
| // Register custom httpFilter builders for the test. | ||
| testFilterTypeURL1 := "test-filter-type-url-1" + uuid.New().String() | ||
| testFilterTypeURL2 := "test-filter-type-url-2" + uuid.New().String() | ||
| newStreamChan := testutils.NewChannelWithSize(2) | ||
| fb1 := &testHTTPFilterWithRPCMetadata{ | ||
| logger: t, | ||
| typeURL: testFilterTypeURL1, | ||
| newStreamChan: newStreamChan, | ||
| } | ||
| fb2 := &testHTTPFilterWithRPCMetadata{ | ||
| logger: t, | ||
| typeURL: testFilterTypeURL2, | ||
| newStreamChan: newStreamChan, | ||
| } | ||
| httpfilter.Register(fb1) | ||
| httpfilter.Register(fb2) | ||
| defer httpfilter.UnregisterForTesting(fb1.typeURL) | ||
| defer httpfilter.UnregisterForTesting(fb2.typeURL) | ||
|
|
||
| // Spin up an xDS management server. | ||
| ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout) | ||
| defer cancel() | ||
| nodeID := uuid.New().String() | ||
| mgmtServer, _, _, bc := setupManagementServerForTest(t, nodeID) | ||
|
|
||
| // Configure resources on the management server. | ||
| // We need a listener with the filter, and a route with ClusterSpecifierPlugin. | ||
| listeners := []*v3listenerpb.Listener{{ | ||
| Name: defaultTestServiceName, | ||
| ApiListener: &v3listenerpb.ApiListener{ | ||
| ApiListener: testutils.MarshalAny(t, &v3httppb.HttpConnectionManager{ | ||
| RouteSpecifier: &v3httppb.HttpConnectionManager_Rds{Rds: &v3httppb.Rds{ | ||
| ConfigSource: &v3corepb.ConfigSource{ | ||
| ConfigSourceSpecifier: &v3corepb.ConfigSource_Ads{Ads: &v3corepb.AggregatedConfigSource{}}, | ||
| }, | ||
| RouteConfigName: defaultTestRouteConfigName, | ||
| }}, | ||
| HttpFilters: []*v3httppb.HttpFilter{ | ||
| newHTTPFilter(t, "test-filter-1", testFilterTypeURL1, "filter-path-1", ""), | ||
| newHTTPFilter(t, "test-filter-2", testFilterTypeURL2, "filter-path-2", ""), | ||
| e2e.RouterHTTPFilter, | ||
| }, | ||
| }), | ||
| }, | ||
| }} | ||
|
|
||
| routes := []*v3routepb.RouteConfiguration{e2e.RouteConfigResourceWithOptions(e2e.RouteConfigOptions{ | ||
| RouteConfigName: defaultTestRouteConfigName, | ||
| ListenerName: defaultTestServiceName, | ||
| ClusterSpecifierType: e2e.RouteConfigClusterSpecifierTypeClusterSpecifierPlugin, | ||
| ClusterSpecifierPluginName: "cspA", | ||
| ClusterSpecifierPluginConfig: testutils.MarshalAny(t, &wrapperspb.StringValue{Value: "anything"}), | ||
| })} | ||
| // Override the configuration for "test-filter-1" in the route. | ||
| routes[0].VirtualHosts[0].Routes[0].TypedPerFilterConfig = map[string]*anypb.Any{ | ||
| "test-filter-1": newHTTPFilter(t, "test-filter-1", testFilterTypeURL1, "override-path-1", "").GetTypedConfig(), | ||
| } | ||
| configureResources(ctx, t, mgmtServer, nodeID, listeners, routes, nil, nil) | ||
|
|
||
| stateCh, _, _ := buildResolverForTarget(t, resolver.Target{URL: *testutils.MustParseURL("xds:///" + defaultTestServiceName)}, bc) | ||
|
|
||
| // Wait for an update from the resolver, and verify the service config. | ||
| wantSC := ` | ||
| { | ||
| "loadBalancingConfig": [ | ||
| { | ||
| "xds_cluster_manager_experimental": { | ||
| "children": { | ||
| "cluster_specifier_plugin:cspA": { | ||
| "childPolicy": [ | ||
| { | ||
| "csp_experimental": { | ||
| "arbitrary_field": "anything" | ||
| } | ||
| } | ||
| ] | ||
| } | ||
| } | ||
| } | ||
| } | ||
| ] | ||
| }` | ||
| cs := verifyUpdateFromResolver(ctx, t, stateCh, wantSC) | ||
| res, err := cs.SelectConfig(iresolver.RPCInfo{Context: ctx, Method: "/service/method"}) | ||
| if err != nil { | ||
| t.Fatalf("cs.SelectConfig(): %v", err) | ||
| } | ||
|
|
||
| // Verify that the interceptor is not nil. | ||
| if res.Interceptor == nil { | ||
| t.Fatal("RPCInfo does not contain interceptors list") | ||
| } | ||
|
|
||
| newStream := func(context.Context, func()) (iresolver.ClientStream, error) { | ||
| return nil, nil | ||
| } | ||
|
Comment on lines
+442
to
+444
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why significance does this empty function have? Why does it return
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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: |
||
|
|
||
| if _, err = res.Interceptor.NewStream(ctx, iresolver.RPCInfo{Method: "/service/method", Context: ctx}, func() {}, newStream); err != nil { | ||
| t.Fatalf("NewStream() failed with error: %v", err) | ||
| } | ||
|
|
||
| // Verify that first filter receives the config. | ||
| cfg, err := newStreamChan.Receive(ctx) | ||
| if err != nil { | ||
| t.Fatalf("Timeout waiting for first filter to receive config: %v", err) | ||
| } | ||
| ofc := cfg.(overallFilterConfig) | ||
| if ofc.BasePath != "filter-path-1" { | ||
| t.Fatalf("Unexpected base path for first filter, got: %q, want: %q", ofc.BasePath, "filter-path-1") | ||
| } | ||
| if ofc.OverridePath != "override-path-1" { | ||
| t.Fatalf("Unexpected override path for first filter, got: %q, want: %q", ofc.OverridePath, "override-path-1") | ||
| } | ||
|
|
||
| // 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, "") | ||
| } | ||
|
Comment on lines
+463
to
+474
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. |
||
| } | ||
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.