diff --git a/xds/internal/server/listener_wrapper_test.go b/xds/internal/server/listener_wrapper_test.go index 8a79e2321..088fb01d7 100644 --- a/xds/internal/server/listener_wrapper_test.go +++ b/xds/internal/server/listener_wrapper_test.go @@ -30,6 +30,7 @@ import ( 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" v3tlspb "github.com/envoyproxy/go-control-plane/envoy/extensions/transport_sockets/tls/v3" wrapperspb "github.com/golang/protobuf/ptypes/wrappers" @@ -87,7 +88,20 @@ var listenerWithFilterChains = &v3listenerpb.Listener{ { Name: "filter-1", ConfigType: &v3listenerpb.Filter_TypedConfig{ - TypedConfig: testutils.MarshalAny(&v3httppb.HttpConnectionManager{}), + TypedConfig: testutils.MarshalAny(&v3httppb.HttpConnectionManager{ + RouteSpecifier: &v3httppb.HttpConnectionManager_RouteConfig{ + RouteConfig: &v3routepb.RouteConfiguration{ + Name: "routeName", + VirtualHosts: []*v3routepb.VirtualHost{{ + Domains: []string{"lds.target.good:3333"}, + Routes: []*v3routepb.Route{{ + Match: &v3routepb.RouteMatch{ + PathSpecifier: &v3routepb.RouteMatch_Prefix{Prefix: "/"}, + }, + Action: &v3routepb.Route_NonForwardingAction{}, + }}}}}, + }, + }), }, }, }, diff --git a/xds/internal/testutils/e2e/clientresources.go b/xds/internal/testutils/e2e/clientresources.go index 7c8311a51..808953461 100644 --- a/xds/internal/testutils/e2e/clientresources.go +++ b/xds/internal/testutils/e2e/clientresources.go @@ -199,7 +199,20 @@ func DefaultServerListener(host string, port uint32, secLevel SecurityLevel) *v3 { Name: "filter-1", ConfigType: &v3listenerpb.Filter_TypedConfig{ - TypedConfig: testutils.MarshalAny(&v3httppb.HttpConnectionManager{}), + TypedConfig: testutils.MarshalAny(&v3httppb.HttpConnectionManager{ + RouteSpecifier: &v3httppb.HttpConnectionManager_RouteConfig{ + RouteConfig: &v3routepb.RouteConfiguration{ + Name: "routeName", + VirtualHosts: []*v3routepb.VirtualHost{{ + Domains: []string{"lds.target.good:3333"}, + Routes: []*v3routepb.Route{{ + Match: &v3routepb.RouteMatch{ + PathSpecifier: &v3routepb.RouteMatch_Prefix{Prefix: "/"}, + }, + Action: &v3routepb.Route_NonForwardingAction{}, + }}}}}, + }, + }), }, }, }, @@ -230,7 +243,20 @@ func DefaultServerListener(host string, port uint32, secLevel SecurityLevel) *v3 { Name: "filter-1", ConfigType: &v3listenerpb.Filter_TypedConfig{ - TypedConfig: testutils.MarshalAny(&v3httppb.HttpConnectionManager{}), + TypedConfig: testutils.MarshalAny(&v3httppb.HttpConnectionManager{ + RouteSpecifier: &v3httppb.HttpConnectionManager_RouteConfig{ + RouteConfig: &v3routepb.RouteConfiguration{ + Name: "routeName", + VirtualHosts: []*v3routepb.VirtualHost{{ + Domains: []string{"lds.target.good:3333"}, + Routes: []*v3routepb.Route{{ + Match: &v3routepb.RouteMatch{ + PathSpecifier: &v3routepb.RouteMatch_Prefix{Prefix: "/"}, + }, + Action: &v3routepb.Route_NonForwardingAction{}, + }}}}}, + }, + }), }, }, }, diff --git a/xds/internal/xdsclient/client.go b/xds/internal/xdsclient/client.go index a7de226cd..85dbf0ee8 100644 --- a/xds/internal/xdsclient/client.go +++ b/xds/internal/xdsclient/client.go @@ -294,6 +294,25 @@ type HashPolicy struct { RegexSubstitution string } +// RouteAction is the action of the route from a received RDS response. +type RouteAction int + +const ( + // RouteActionUnsupported are routing types currently unsupported by grpc. + // According to A36, "A Route with an inappropriate action causes RPCs + // matching that route to fail." + RouteActionUnsupported RouteAction = iota + // RouteActionRoute is the expected route type on the client side. Route + // represents routing a request to some upstream cluster. On the client + // side, if an RPC matches to a route that is not RouteActionRoute, the RPC + // will fail according to A36. + RouteActionRoute + // RouteActionNonForwardingAction is the expected route type on the server + // side. NonForwardingAction represents when a route will generate a + // response directly, without forwarding to an upstream host. + RouteActionNonForwardingAction +) + // Route is both a specification of how to match a request as well as an // indication of the action to take upon match. type Route struct { @@ -321,6 +340,8 @@ type Route struct { // unused if the matching WeightedCluster contains an override for that // filter. HTTPFilterConfigOverride map[string]httpfilter.FilterConfig + + RouteAction RouteAction } // WeightedCluster contains settings for an xds RouteAction.WeightedCluster. diff --git a/xds/internal/xdsclient/filter_chain.go b/xds/internal/xdsclient/filter_chain.go index 7089b9759..49ebe887c 100644 --- a/xds/internal/xdsclient/filter_chain.go +++ b/xds/internal/xdsclient/filter_chain.go @@ -24,8 +24,10 @@ import ( "net" v3listenerpb "github.com/envoyproxy/go-control-plane/envoy/config/listener/v3" + v3httppb "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/network/http_connection_manager/v3" v3tlspb "github.com/envoyproxy/go-control-plane/envoy/extensions/transport_sockets/tls/v3" "github.com/golang/protobuf/proto" + "github.com/golang/protobuf/ptypes" "google.golang.org/grpc/xds/internal/version" ) @@ -54,6 +56,15 @@ type FilterChain struct { SecurityCfg *SecurityConfig // HTTPFilters represent the HTTP Filters that comprise this FilterChain. HTTPFilters []HTTPFilter + // RouteConfigName is the route configuration name for this FilterChain. + // + // Only one of RouteConfigName and InlineRouteConfig is set. + RouteConfigName string + // InlineRouteConfig is the inline route configuration (RDS response) + // returned for this filter chain. + // + // Only one of RouteConfigName and InlineRouteConfig is set. + InlineRouteConfig *RouteConfigUpdate } // SourceType specifies the connection source IP match type. @@ -109,6 +120,11 @@ type FilterChainManager struct { dstPrefixes []*destPrefixEntry def *FilterChain // Default filter chain, if specified. + + // RouteConfigNames are the route configuration names which need to be + // dynamically queried for RDS Configuration for any FilterChains which + // specify to load RDS Configuration dynamically. + RouteConfigNames map[string]bool } // destPrefixEntry is the value type of the map indexed on destination prefixes. @@ -158,7 +174,10 @@ type sourcePrefixEntry struct { // create a FilterChainManager. func NewFilterChainManager(lis *v3listenerpb.Listener) (*FilterChainManager, error) { // Parse all the filter chains and build the internal data structures. - fci := &FilterChainManager{dstPrefixMap: make(map[string]*destPrefixEntry)} + fci := &FilterChainManager{ + dstPrefixMap: make(map[string]*destPrefixEntry), + RouteConfigNames: make(map[string]bool), + } if err := fci.addFilterChains(lis.GetFilterChains()); err != nil { return nil, err } @@ -187,7 +206,7 @@ func NewFilterChainManager(lis *v3listenerpb.Listener) (*FilterChainManager, err var def *FilterChain if dfc := lis.GetDefaultFilterChain(); dfc != nil { var err error - if def, err = filterChainFromProto(dfc); err != nil { + if def, err = fci.filterChainFromProto(dfc); err != nil { return nil, err } } @@ -368,7 +387,7 @@ func (fci *FilterChainManager) addFilterChainsForSourcePorts(srcEntry *sourcePre srcPorts = append(srcPorts, int(port)) } - fc, err := filterChainFromProto(fcProto) + fc, err := fci.filterChainFromProto(fcProto) if err != nil { return err } @@ -391,13 +410,19 @@ func (fci *FilterChainManager) addFilterChainsForSourcePorts(srcEntry *sourcePre } // filterChainFromProto extracts the relevant information from the FilterChain -// proto and stores it in our internal representation. -func filterChainFromProto(fc *v3listenerpb.FilterChain) (*FilterChain, error) { - httpFilters, err := processNetworkFilters(fc.GetFilters()) +// proto and stores it in our internal representation. It also persists any +// RouteNames which need to be queried dynamically via RDS. +func (fci *FilterChainManager) filterChainFromProto(fc *v3listenerpb.FilterChain) (*FilterChain, error) { + filterChain, err := processNetworkFilters(fc.GetFilters()) if err != nil { return nil, err } - filterChain := &FilterChain{HTTPFilters: httpFilters} + // These route names will be dynamically queried via RDS in the wrapped + // listener, which receives the LDS response, if specified for the filter + // chain. + if filterChain.RouteConfigName != "" { + fci.RouteConfigNames[filterChain.RouteConfigName] = true + } // If the transport_socket field is not specified, it means that the control // plane has not sent us any security config. This is fine and the server // will use the fallback credentials configured as part of the @@ -435,6 +460,93 @@ func filterChainFromProto(fc *v3listenerpb.FilterChain) (*FilterChain, error) { return filterChain, nil } +func processNetworkFilters(filters []*v3listenerpb.Filter) (*FilterChain, error) { + filterChain := &FilterChain{} + seenNames := make(map[string]bool, len(filters)) + seenHCM := false + for _, filter := range filters { + name := filter.GetName() + if name == "" { + return nil, fmt.Errorf("network filters {%+v} is missing name field in filter: {%+v}", filters, filter) + } + if seenNames[name] { + return nil, fmt.Errorf("network filters {%+v} has duplicate filter name %q", filters, name) + } + seenNames[name] = true + + // Network filters have a oneof field named `config_type` where we + // only support `TypedConfig` variant. + switch typ := filter.GetConfigType().(type) { + case *v3listenerpb.Filter_TypedConfig: + // The typed_config field has an `anypb.Any` proto which could + // directly contain the serialized bytes of the actual filter + // configuration, or it could be encoded as a `TypedStruct`. + // TODO: Add support for `TypedStruct`. + tc := filter.GetTypedConfig() + + // The only network filter that we currently support is the v3 + // HttpConnectionManager. So, we can directly check the type_url + // and unmarshal the config. + // TODO: Implement a registry of supported network filters (like + // we have for HTTP filters), when we have to support network + // filters other than HttpConnectionManager. + if tc.GetTypeUrl() != version.V3HTTPConnManagerURL { + return nil, fmt.Errorf("network filters {%+v} has unsupported network filter %q in filter {%+v}", filters, tc.GetTypeUrl(), filter) + } + hcm := &v3httppb.HttpConnectionManager{} + if err := ptypes.UnmarshalAny(tc, hcm); err != nil { + return nil, fmt.Errorf("network filters {%+v} failed unmarshaling of network filter {%+v}: %v", filters, filter, err) + } + // "Any filters after HttpConnectionManager should be ignored during + // connection processing but still be considered for validity. + // HTTPConnectionManager must have valid http_filters." - A36 + filters, err := processHTTPFilters(hcm.GetHttpFilters(), true) + if err != nil { + return nil, fmt.Errorf("network filters {%+v} had invalid server side HTTP Filters {%+v}", filters, hcm.GetHttpFilters()) + } + if !seenHCM { + // TODO: Implement terminal filter logic, as per A36. + filterChain.HTTPFilters = filters + seenHCM = true + switch hcm.RouteSpecifier.(type) { + case *v3httppb.HttpConnectionManager_Rds: + if hcm.GetRds().GetConfigSource().GetAds() == nil { + return nil, fmt.Errorf("ConfigSource is not ADS: %+v", hcm) + } + name := hcm.GetRds().GetRouteConfigName() + if name == "" { + return nil, fmt.Errorf("empty route_config_name: %+v", hcm) + } + filterChain.RouteConfigName = name + case *v3httppb.HttpConnectionManager_RouteConfig: + // "RouteConfiguration validation logic inherits all + // previous validations made for client-side usage as RDS + // does not distinguish between client-side and + // server-side." - A36 + // Can specify v3 here, as will never get to this function + // if v2. + routeU, err := generateRDSUpdateFromRouteConfiguration(hcm.GetRouteConfig(), nil, false) + if err != nil { + return nil, fmt.Errorf("failed to parse inline RDS resp: %v", err) + } + filterChain.InlineRouteConfig = &routeU + case nil: + // No-op, as no route specifier is a valid configuration on + // the server side. + default: + return nil, fmt.Errorf("unsupported type %T for RouteSpecifier", hcm.RouteSpecifier) + } + } + default: + return nil, fmt.Errorf("network filters {%+v} has unsupported config_type %T in filter %s", filters, typ, filter.GetName()) + } + } + if !seenHCM { + return nil, fmt.Errorf("network filters {%+v} missing HttpConnectionManager filter", filters) + } + return filterChain, nil +} + // FilterChainLookupParams wraps parameters to be passed to Lookup. type FilterChainLookupParams struct { // IsUnspecified indicates whether the server is listening on a wildcard diff --git a/xds/internal/xdsclient/filter_chain_test.go b/xds/internal/xdsclient/filter_chain_test.go index e330a73a1..525c865c7 100644 --- a/xds/internal/xdsclient/filter_chain_test.go +++ b/xds/internal/xdsclient/filter_chain_test.go @@ -28,6 +28,7 @@ import ( 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" v3tlspb "github.com/envoyproxy/go-control-plane/envoy/extensions/transport_sockets/tls/v3" "github.com/google/go-cmp/cmp" @@ -41,11 +42,30 @@ import ( ) var ( + routeConfig = &v3routepb.RouteConfiguration{ + Name: "routeName", + VirtualHosts: []*v3routepb.VirtualHost{{ + Domains: []string{"lds.target.good:3333"}, + Routes: []*v3routepb.Route{{ + Match: &v3routepb.RouteMatch{ + PathSpecifier: &v3routepb.RouteMatch_Prefix{Prefix: "/"}, + }, + Action: &v3routepb.Route_NonForwardingAction{}, + }}}}} + inlineRouteConfig = &RouteConfigUpdate{ + VirtualHosts: []*VirtualHost{{ + Domains: []string{"lds.target.good:3333"}, + Routes: []*Route{{Prefix: newStringP("/"), RouteAction: RouteActionNonForwardingAction}}, + }}} emptyValidNetworkFilters = []*v3listenerpb.Filter{ { Name: "filter-1", ConfigType: &v3listenerpb.Filter_TypedConfig{ - TypedConfig: testutils.MarshalAny(&v3httppb.HttpConnectionManager{}), + TypedConfig: testutils.MarshalAny(&v3httppb.HttpConnectionManager{ + RouteSpecifier: &v3httppb.HttpConnectionManager_RouteConfig{ + RouteConfig: routeConfig, + }, + }), }, }, } @@ -249,8 +269,7 @@ func TestNewFilterChainImpl_Failure_OverlappingMatchingRules(t *testing.T) { const wantErr = "multiple filter chains with overlapping matching rules are defined" for _, test := range tests { t.Run(test.desc, func(t *testing.T) { - _, err := NewFilterChainManager(test.lis) - if err == nil || !strings.Contains(err.Error(), wantErr) { + if _, err := NewFilterChainManager(test.lis); err == nil || !strings.Contains(err.Error(), wantErr) { t.Fatalf("NewFilterChainManager() returned err: %v, wantErr: %s", err, wantErr) } }) @@ -417,6 +436,323 @@ func TestNewFilterChainImpl_Failure_BadSecurityConfig(t *testing.T) { } } +// TestNewFilterChainImpl_Success_RouteUpdate tests the construction of the +// filter chain with valid HTTP Filters present. +func TestNewFilterChainImpl_Success_RouteUpdate(t *testing.T) { + tests := []struct { + name string + lis *v3listenerpb.Listener + wantFC *FilterChainManager + }{ + { + name: "rds", + lis: &v3listenerpb.Listener{ + FilterChains: []*v3listenerpb.FilterChain{ + { + Name: "filter-chain-1", + Filters: []*v3listenerpb.Filter{ + { + Name: "hcm", + ConfigType: &v3listenerpb.Filter_TypedConfig{ + TypedConfig: testutils.MarshalAny(&v3httppb.HttpConnectionManager{ + RouteSpecifier: &v3httppb.HttpConnectionManager_Rds{ + Rds: &v3httppb.Rds{ + ConfigSource: &v3corepb.ConfigSource{ + ConfigSourceSpecifier: &v3corepb.ConfigSource_Ads{Ads: &v3corepb.AggregatedConfigSource{}}, + }, + RouteConfigName: "route-1", + }, + }, + }), + }, + }, + }, + }, + }, + DefaultFilterChain: &v3listenerpb.FilterChain{ + Filters: []*v3listenerpb.Filter{ + { + Name: "hcm", + ConfigType: &v3listenerpb.Filter_TypedConfig{ + TypedConfig: testutils.MarshalAny(&v3httppb.HttpConnectionManager{ + RouteSpecifier: &v3httppb.HttpConnectionManager_Rds{ + Rds: &v3httppb.Rds{ + ConfigSource: &v3corepb.ConfigSource{ + ConfigSourceSpecifier: &v3corepb.ConfigSource_Ads{Ads: &v3corepb.AggregatedConfigSource{}}, + }, + RouteConfigName: "route-1", + }, + }, + }), + }, + }, + }, + }, + }, + wantFC: &FilterChainManager{ + dstPrefixMap: map[string]*destPrefixEntry{ + unspecifiedPrefixMapKey: { + srcTypeArr: [3]*sourcePrefixes{ + { + srcPrefixMap: map[string]*sourcePrefixEntry{ + unspecifiedPrefixMapKey: { + srcPortMap: map[int]*FilterChain{ + 0: { + RouteConfigName: "route-1", + }, + }, + }, + }, + }, + }, + }, + }, + def: &FilterChain{ + RouteConfigName: "route-1", + }, + RouteConfigNames: map[string]bool{"route-1": true}, + }, + }, + { + name: "inline route config", + lis: &v3listenerpb.Listener{ + FilterChains: []*v3listenerpb.FilterChain{ + { + Name: "filter-chain-1", + Filters: []*v3listenerpb.Filter{ + { + Name: "hcm", + ConfigType: &v3listenerpb.Filter_TypedConfig{ + TypedConfig: testutils.MarshalAny(&v3httppb.HttpConnectionManager{ + RouteSpecifier: &v3httppb.HttpConnectionManager_RouteConfig{ + RouteConfig: routeConfig, + }, + }), + }, + }, + }, + }, + }, + DefaultFilterChain: &v3listenerpb.FilterChain{ + Filters: []*v3listenerpb.Filter{ + { + Name: "hcm", + ConfigType: &v3listenerpb.Filter_TypedConfig{ + TypedConfig: testutils.MarshalAny(&v3httppb.HttpConnectionManager{ + RouteSpecifier: &v3httppb.HttpConnectionManager_RouteConfig{ + RouteConfig: routeConfig, + }, + }), + }, + }, + }, + }, + }, + wantFC: &FilterChainManager{ + dstPrefixMap: map[string]*destPrefixEntry{ + unspecifiedPrefixMapKey: { + srcTypeArr: [3]*sourcePrefixes{ + { + srcPrefixMap: map[string]*sourcePrefixEntry{ + unspecifiedPrefixMapKey: { + srcPortMap: map[int]*FilterChain{ + 0: { + InlineRouteConfig: inlineRouteConfig, + }, + }, + }, + }, + }, + }, + }, + }, + def: &FilterChain{ + InlineRouteConfig: inlineRouteConfig, + }, + }, + }, + // two rds tests whether the Filter Chain Manager successfully persists + // the two RDS names that need to be dynamically queried. + { + name: "two rds", + lis: &v3listenerpb.Listener{ + FilterChains: []*v3listenerpb.FilterChain{ + { + Name: "filter-chain-1", + Filters: []*v3listenerpb.Filter{ + { + Name: "hcm", + ConfigType: &v3listenerpb.Filter_TypedConfig{ + TypedConfig: testutils.MarshalAny(&v3httppb.HttpConnectionManager{ + RouteSpecifier: &v3httppb.HttpConnectionManager_Rds{ + Rds: &v3httppb.Rds{ + ConfigSource: &v3corepb.ConfigSource{ + ConfigSourceSpecifier: &v3corepb.ConfigSource_Ads{Ads: &v3corepb.AggregatedConfigSource{}}, + }, + RouteConfigName: "route-1", + }, + }, + }), + }, + }, + }, + }, + }, + DefaultFilterChain: &v3listenerpb.FilterChain{ + Filters: []*v3listenerpb.Filter{ + { + Name: "hcm", + ConfigType: &v3listenerpb.Filter_TypedConfig{ + TypedConfig: testutils.MarshalAny(&v3httppb.HttpConnectionManager{ + RouteSpecifier: &v3httppb.HttpConnectionManager_Rds{ + Rds: &v3httppb.Rds{ + ConfigSource: &v3corepb.ConfigSource{ + ConfigSourceSpecifier: &v3corepb.ConfigSource_Ads{Ads: &v3corepb.AggregatedConfigSource{}}, + }, + RouteConfigName: "route-2", + }, + }, + }), + }, + }, + }, + }, + }, + wantFC: &FilterChainManager{ + dstPrefixMap: map[string]*destPrefixEntry{ + unspecifiedPrefixMapKey: { + srcTypeArr: [3]*sourcePrefixes{ + { + srcPrefixMap: map[string]*sourcePrefixEntry{ + unspecifiedPrefixMapKey: { + srcPortMap: map[int]*FilterChain{ + 0: { + RouteConfigName: "route-1", + }, + }, + }, + }, + }, + }, + }, + }, + def: &FilterChain{ + RouteConfigName: "route-2", + }, + RouteConfigNames: map[string]bool{ + "route-1": true, + "route-2": true, + }, + }, + }, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + gotFC, err := NewFilterChainManager(test.lis) + if err != nil { + t.Fatalf("NewFilterChainManager() returned err: %v, wantErr: nil", err) + } + if !cmp.Equal(gotFC, test.wantFC, cmp.AllowUnexported(FilterChainManager{}, destPrefixEntry{}, sourcePrefixes{}, sourcePrefixEntry{}), cmpOpts) { + t.Fatalf("NewFilterChainManager() returned %+v, want: %+v", gotFC, test.wantFC) + } + }) + } +} + +// TestNewFilterChainImpl_Failure_BadRouteUpdate verifies cases where the Route +// Update in the filter chain are invalid. +func TestNewFilterChainImpl_Failure_BadRouteUpdate(t *testing.T) { + tests := []struct { + name string + lis *v3listenerpb.Listener + wantErr string + }{ + { + name: "not-ads", + lis: &v3listenerpb.Listener{ + FilterChains: []*v3listenerpb.FilterChain{ + { + Name: "filter-chain-1", + Filters: []*v3listenerpb.Filter{ + { + Name: "hcm", + ConfigType: &v3listenerpb.Filter_TypedConfig{ + TypedConfig: testutils.MarshalAny(&v3httppb.HttpConnectionManager{ + RouteSpecifier: &v3httppb.HttpConnectionManager_Rds{ + Rds: &v3httppb.Rds{ + RouteConfigName: "route-1", + }, + }, + }), + }, + }, + }, + }, + }, + DefaultFilterChain: &v3listenerpb.FilterChain{ + Filters: []*v3listenerpb.Filter{ + { + Name: "hcm", + ConfigType: &v3listenerpb.Filter_TypedConfig{ + TypedConfig: testutils.MarshalAny(&v3httppb.HttpConnectionManager{ + RouteSpecifier: &v3httppb.HttpConnectionManager_Rds{ + Rds: &v3httppb.Rds{ + RouteConfigName: "route-1", + }, + }, + }), + }, + }, + }, + }, + }, + wantErr: "ConfigSource is not ADS", + }, + { + name: "unsupported-route-specifier", + lis: &v3listenerpb.Listener{ + FilterChains: []*v3listenerpb.FilterChain{ + { + Name: "filter-chain-1", + Filters: []*v3listenerpb.Filter{ + { + Name: "hcm", + ConfigType: &v3listenerpb.Filter_TypedConfig{ + TypedConfig: testutils.MarshalAny(&v3httppb.HttpConnectionManager{ + RouteSpecifier: &v3httppb.HttpConnectionManager_ScopedRoutes{}, + }), + }, + }, + }, + }, + }, + DefaultFilterChain: &v3listenerpb.FilterChain{ + Filters: []*v3listenerpb.Filter{ + { + Name: "hcm", + ConfigType: &v3listenerpb.Filter_TypedConfig{ + TypedConfig: testutils.MarshalAny(&v3httppb.HttpConnectionManager{ + RouteSpecifier: &v3httppb.HttpConnectionManager_ScopedRoutes{}, + }), + }, + }, + }, + }, + }, + wantErr: "unsupported type", + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + _, err := NewFilterChainManager(test.lis) + if err == nil || !strings.Contains(err.Error(), test.wantErr) { + t.Fatalf("NewFilterChainManager() returned err: %v, wantErr: %s", err, test.wantErr) + } + }) + } +} + // TestNewFilterChainImpl_Failure_BadHTTPFilters verifies cases where the HTTP // Filters in the filter chain are invalid. func TestNewFilterChainImpl_Failure_BadHTTPFilters(t *testing.T) { @@ -513,6 +849,9 @@ func TestNewFilterChainImpl_Success_HTTPFilters(t *testing.T) { HttpFilters: []*v3httppb.HttpFilter{ validServerSideHTTPFilter1, }, + RouteSpecifier: &v3httppb.HttpConnectionManager_RouteConfig{ + RouteConfig: routeConfig, + }, }), }, }, @@ -528,6 +867,9 @@ func TestNewFilterChainImpl_Success_HTTPFilters(t *testing.T) { HttpFilters: []*v3httppb.HttpFilter{ validServerSideHTTPFilter1, }, + RouteSpecifier: &v3httppb.HttpConnectionManager_RouteConfig{ + RouteConfig: routeConfig, + }, }), }, }, @@ -548,7 +890,9 @@ func TestNewFilterChainImpl_Success_HTTPFilters(t *testing.T) { Filter: serverOnlyHTTPFilter{}, Config: filterConfig{Cfg: serverOnlyCustomFilterConfig}, }, - }}, + }, + InlineRouteConfig: inlineRouteConfig, + }, }, }, }, @@ -556,13 +900,14 @@ func TestNewFilterChainImpl_Success_HTTPFilters(t *testing.T) { }, }, }, - def: &FilterChain{HTTPFilters: []HTTPFilter{ - { + def: &FilterChain{ + HTTPFilters: []HTTPFilter{{ Name: "serverOnlyCustomFilter", Filter: serverOnlyHTTPFilter{}, Config: filterConfig{Cfg: serverOnlyCustomFilterConfig}, - }, - }}, + }}, + InlineRouteConfig: inlineRouteConfig, + }, }, }, { @@ -580,6 +925,9 @@ func TestNewFilterChainImpl_Success_HTTPFilters(t *testing.T) { validServerSideHTTPFilter1, validServerSideHTTPFilter2, }, + RouteSpecifier: &v3httppb.HttpConnectionManager_RouteConfig{ + RouteConfig: routeConfig, + }, }), }, }, @@ -596,6 +944,9 @@ func TestNewFilterChainImpl_Success_HTTPFilters(t *testing.T) { validServerSideHTTPFilter1, validServerSideHTTPFilter2, }, + RouteSpecifier: &v3httppb.HttpConnectionManager_RouteConfig{ + RouteConfig: routeConfig, + }, }), }, }, @@ -621,7 +972,9 @@ func TestNewFilterChainImpl_Success_HTTPFilters(t *testing.T) { Filter: serverOnlyHTTPFilter{}, Config: filterConfig{Cfg: serverOnlyCustomFilterConfig}, }, - }}, + }, + InlineRouteConfig: inlineRouteConfig, + }, }, }, }, @@ -641,6 +994,7 @@ func TestNewFilterChainImpl_Success_HTTPFilters(t *testing.T) { Config: filterConfig{Cfg: serverOnlyCustomFilterConfig}, }, }, + InlineRouteConfig: inlineRouteConfig, }, }, }, @@ -661,6 +1015,9 @@ func TestNewFilterChainImpl_Success_HTTPFilters(t *testing.T) { validServerSideHTTPFilter1, validServerSideHTTPFilter2, }, + RouteSpecifier: &v3httppb.HttpConnectionManager_RouteConfig{ + RouteConfig: routeConfig, + }, }), }, }, @@ -671,6 +1028,9 @@ func TestNewFilterChainImpl_Success_HTTPFilters(t *testing.T) { HttpFilters: []*v3httppb.HttpFilter{ validServerSideHTTPFilter1, }, + RouteSpecifier: &v3httppb.HttpConnectionManager_RouteConfig{ + RouteConfig: routeConfig, + }, }), }, }, @@ -687,6 +1047,9 @@ func TestNewFilterChainImpl_Success_HTTPFilters(t *testing.T) { validServerSideHTTPFilter1, validServerSideHTTPFilter2, }, + RouteSpecifier: &v3httppb.HttpConnectionManager_RouteConfig{ + RouteConfig: routeConfig, + }, }), }, }, @@ -697,6 +1060,9 @@ func TestNewFilterChainImpl_Success_HTTPFilters(t *testing.T) { HttpFilters: []*v3httppb.HttpFilter{ validServerSideHTTPFilter1, }, + RouteSpecifier: &v3httppb.HttpConnectionManager_RouteConfig{ + RouteConfig: routeConfig, + }, }), }, }, @@ -722,7 +1088,9 @@ func TestNewFilterChainImpl_Success_HTTPFilters(t *testing.T) { Filter: serverOnlyHTTPFilter{}, Config: filterConfig{Cfg: serverOnlyCustomFilterConfig}, }, - }}, + }, + InlineRouteConfig: inlineRouteConfig, + }, }, }, }, @@ -742,6 +1110,7 @@ func TestNewFilterChainImpl_Success_HTTPFilters(t *testing.T) { Config: filterConfig{Cfg: serverOnlyCustomFilterConfig}, }, }, + InlineRouteConfig: inlineRouteConfig, }, }, }, @@ -789,7 +1158,7 @@ func TestNewFilterChainImpl_Success_SecurityConfig(t *testing.T) { srcPrefixMap: map[string]*sourcePrefixEntry{ unspecifiedPrefixMapKey: { srcPortMap: map[int]*FilterChain{ - 0: {}, + 0: {InlineRouteConfig: inlineRouteConfig}, }, }, }, @@ -797,7 +1166,7 @@ func TestNewFilterChainImpl_Success_SecurityConfig(t *testing.T) { }, }, }, - def: &FilterChain{}, + def: &FilterChain{InlineRouteConfig: inlineRouteConfig}, }, }, { @@ -851,6 +1220,7 @@ func TestNewFilterChainImpl_Success_SecurityConfig(t *testing.T) { IdentityInstanceName: "identityPluginInstance", IdentityCertName: "identityCertName", }, + InlineRouteConfig: inlineRouteConfig, }, }, }, @@ -864,6 +1234,7 @@ func TestNewFilterChainImpl_Success_SecurityConfig(t *testing.T) { IdentityInstanceName: "defaultIdentityPluginInstance", IdentityCertName: "defaultIdentityCertName", }, + InlineRouteConfig: inlineRouteConfig, }, }, }, @@ -936,6 +1307,7 @@ func TestNewFilterChainImpl_Success_SecurityConfig(t *testing.T) { IdentityCertName: "identityCertName", RequireClientCert: true, }, + InlineRouteConfig: inlineRouteConfig, }, }, }, @@ -952,6 +1324,7 @@ func TestNewFilterChainImpl_Success_SecurityConfig(t *testing.T) { IdentityCertName: "defaultIdentityCertName", RequireClientCert: true, }, + InlineRouteConfig: inlineRouteConfig, }, }, }, @@ -982,7 +1355,7 @@ func TestNewFilterChainImpl_Success_UnsupportedMatchFields(t *testing.T) { srcPrefixMap: map[string]*sourcePrefixEntry{ unspecifiedPrefixMapKey: { srcPortMap: map[int]*FilterChain{ - 0: {}, + 0: {InlineRouteConfig: inlineRouteConfig}, }, }, }, @@ -1018,7 +1391,7 @@ func TestNewFilterChainImpl_Success_UnsupportedMatchFields(t *testing.T) { dstPrefixMap: map[string]*destPrefixEntry{ unspecifiedPrefixMapKey: unspecifiedEntry, }, - def: &FilterChain{}, + def: &FilterChain{InlineRouteConfig: inlineRouteConfig}, }, }, { @@ -1047,7 +1420,7 @@ func TestNewFilterChainImpl_Success_UnsupportedMatchFields(t *testing.T) { net: ipNetFromCIDR("192.168.2.2/16"), }, }, - def: &FilterChain{}, + def: &FilterChain{InlineRouteConfig: inlineRouteConfig}, }, }, { @@ -1076,7 +1449,7 @@ func TestNewFilterChainImpl_Success_UnsupportedMatchFields(t *testing.T) { net: ipNetFromCIDR("192.168.2.2/16"), }, }, - def: &FilterChain{}, + def: &FilterChain{InlineRouteConfig: inlineRouteConfig}, }, }, { @@ -1105,7 +1478,7 @@ func TestNewFilterChainImpl_Success_UnsupportedMatchFields(t *testing.T) { net: ipNetFromCIDR("192.168.2.2/16"), }, }, - def: &FilterChain{}, + def: &FilterChain{InlineRouteConfig: inlineRouteConfig}, }, }, } @@ -1175,7 +1548,7 @@ func TestNewFilterChainImpl_Success_AllCombinations(t *testing.T) { srcPrefixMap: map[string]*sourcePrefixEntry{ unspecifiedPrefixMapKey: { srcPortMap: map[int]*FilterChain{ - 0: {}, + 0: {InlineRouteConfig: inlineRouteConfig}, }, }, }, @@ -1191,7 +1564,7 @@ func TestNewFilterChainImpl_Success_AllCombinations(t *testing.T) { srcPrefixMap: map[string]*sourcePrefixEntry{ unspecifiedPrefixMapKey: { srcPortMap: map[int]*FilterChain{ - 0: {}, + 0: {InlineRouteConfig: inlineRouteConfig}, }, }, }, @@ -1207,7 +1580,7 @@ func TestNewFilterChainImpl_Success_AllCombinations(t *testing.T) { srcPrefixMap: map[string]*sourcePrefixEntry{ unspecifiedPrefixMapKey: { srcPortMap: map[int]*FilterChain{ - 0: {}, + 0: {InlineRouteConfig: inlineRouteConfig}, }, }, }, @@ -1221,7 +1594,7 @@ func TestNewFilterChainImpl_Success_AllCombinations(t *testing.T) { srcPrefixMap: map[string]*sourcePrefixEntry{ unspecifiedPrefixMapKey: { srcPortMap: map[int]*FilterChain{ - 0: {}, + 0: {InlineRouteConfig: inlineRouteConfig}, }, }, }, @@ -1235,7 +1608,7 @@ func TestNewFilterChainImpl_Success_AllCombinations(t *testing.T) { srcPrefixMap: map[string]*sourcePrefixEntry{ unspecifiedPrefixMapKey: { srcPortMap: map[int]*FilterChain{ - 0: {}, + 0: {InlineRouteConfig: inlineRouteConfig}, }, }, }, @@ -1243,7 +1616,7 @@ func TestNewFilterChainImpl_Success_AllCombinations(t *testing.T) { }, }, }, - def: &FilterChain{}, + def: &FilterChain{InlineRouteConfig: inlineRouteConfig}, }, }, { @@ -1273,7 +1646,7 @@ func TestNewFilterChainImpl_Success_AllCombinations(t *testing.T) { srcPrefixMap: map[string]*sourcePrefixEntry{ unspecifiedPrefixMapKey: { srcPortMap: map[int]*FilterChain{ - 0: {}, + 0: {InlineRouteConfig: inlineRouteConfig}, }, }, }, @@ -1289,7 +1662,7 @@ func TestNewFilterChainImpl_Success_AllCombinations(t *testing.T) { srcPrefixMap: map[string]*sourcePrefixEntry{ unspecifiedPrefixMapKey: { srcPortMap: map[int]*FilterChain{ - 0: {}, + 0: {InlineRouteConfig: inlineRouteConfig}, }, }, }, @@ -1297,7 +1670,7 @@ func TestNewFilterChainImpl_Success_AllCombinations(t *testing.T) { }, }, }, - def: &FilterChain{}, + def: &FilterChain{InlineRouteConfig: inlineRouteConfig}, }, }, { @@ -1327,7 +1700,7 @@ func TestNewFilterChainImpl_Success_AllCombinations(t *testing.T) { "10.0.0.0/8": { net: ipNetFromCIDR("10.0.0.0/8"), srcPortMap: map[int]*FilterChain{ - 0: {}, + 0: {InlineRouteConfig: inlineRouteConfig}, }, }, }, @@ -1342,7 +1715,7 @@ func TestNewFilterChainImpl_Success_AllCombinations(t *testing.T) { "192.168.0.0/16": { net: ipNetFromCIDR("192.168.0.0/16"), srcPortMap: map[int]*FilterChain{ - 0: {}, + 0: {InlineRouteConfig: inlineRouteConfig}, }, }, }, @@ -1350,7 +1723,7 @@ func TestNewFilterChainImpl_Success_AllCombinations(t *testing.T) { }, }, }, - def: &FilterChain{}, + def: &FilterChain{InlineRouteConfig: inlineRouteConfig}, }, }, { @@ -1381,9 +1754,9 @@ func TestNewFilterChainImpl_Success_AllCombinations(t *testing.T) { srcPrefixMap: map[string]*sourcePrefixEntry{ unspecifiedPrefixMapKey: { srcPortMap: map[int]*FilterChain{ - 1: {}, - 2: {}, - 3: {}, + 1: {InlineRouteConfig: inlineRouteConfig}, + 2: {InlineRouteConfig: inlineRouteConfig}, + 3: {InlineRouteConfig: inlineRouteConfig}, }, }, }, @@ -1400,9 +1773,9 @@ func TestNewFilterChainImpl_Success_AllCombinations(t *testing.T) { "192.168.0.0/16": { net: ipNetFromCIDR("192.168.0.0/16"), srcPortMap: map[int]*FilterChain{ - 1: {}, - 2: {}, - 3: {}, + 1: {InlineRouteConfig: inlineRouteConfig}, + 2: {InlineRouteConfig: inlineRouteConfig}, + 3: {InlineRouteConfig: inlineRouteConfig}, }, }, }, @@ -1410,7 +1783,7 @@ func TestNewFilterChainImpl_Success_AllCombinations(t *testing.T) { }, }, }, - def: &FilterChain{}, + def: &FilterChain{InlineRouteConfig: inlineRouteConfig}, }, }, { @@ -1484,7 +1857,7 @@ func TestNewFilterChainImpl_Success_AllCombinations(t *testing.T) { srcPrefixMap: map[string]*sourcePrefixEntry{ unspecifiedPrefixMapKey: { srcPortMap: map[int]*FilterChain{ - 0: {}, + 0: {InlineRouteConfig: inlineRouteConfig}, }, }, }, @@ -1498,7 +1871,7 @@ func TestNewFilterChainImpl_Success_AllCombinations(t *testing.T) { srcPrefixMap: map[string]*sourcePrefixEntry{ unspecifiedPrefixMapKey: { srcPortMap: map[int]*FilterChain{ - 0: {}, + 0: {InlineRouteConfig: inlineRouteConfig}, }, }, }, @@ -1512,7 +1885,7 @@ func TestNewFilterChainImpl_Success_AllCombinations(t *testing.T) { srcPrefixMap: map[string]*sourcePrefixEntry{ unspecifiedPrefixMapKey: { srcPortMap: map[int]*FilterChain{ - 0: {}, + 0: {InlineRouteConfig: inlineRouteConfig}, }, }, }, @@ -1532,7 +1905,7 @@ func TestNewFilterChainImpl_Success_AllCombinations(t *testing.T) { srcTypeArr: [3]*sourcePrefixes{}, }, }, - def: &FilterChain{}, + def: &FilterChain{InlineRouteConfig: inlineRouteConfig}, }, }, } @@ -1804,7 +2177,10 @@ func TestLookup_Successes(t *testing.T) { IsUnspecifiedListener: true, DestAddr: net.IPv4(10, 1, 1, 1), }, - wantFC: &FilterChain{SecurityCfg: &SecurityConfig{IdentityInstanceName: "default"}}, + wantFC: &FilterChain{ + SecurityCfg: &SecurityConfig{IdentityInstanceName: "default"}, + InlineRouteConfig: inlineRouteConfig, + }, }, { desc: "unspecified destination match", @@ -1815,7 +2191,10 @@ func TestLookup_Successes(t *testing.T) { SourceAddr: net.IPv4(10, 1, 1, 1), SourcePort: 1, }, - wantFC: &FilterChain{SecurityCfg: &SecurityConfig{IdentityInstanceName: "unspecified-dest-and-source-prefix"}}, + wantFC: &FilterChain{ + SecurityCfg: &SecurityConfig{IdentityInstanceName: "unspecified-dest-and-source-prefix"}, + InlineRouteConfig: inlineRouteConfig, + }, }, { desc: "wildcard destination match v4", @@ -1826,7 +2205,10 @@ func TestLookup_Successes(t *testing.T) { SourceAddr: net.IPv4(10, 1, 1, 1), SourcePort: 1, }, - wantFC: &FilterChain{SecurityCfg: &SecurityConfig{IdentityInstanceName: "wildcard-prefixes-v4"}}, + wantFC: &FilterChain{ + SecurityCfg: &SecurityConfig{IdentityInstanceName: "wildcard-prefixes-v4"}, + InlineRouteConfig: inlineRouteConfig, + }, }, { desc: "wildcard source match v6", @@ -1837,7 +2219,10 @@ func TestLookup_Successes(t *testing.T) { SourceAddr: net.ParseIP("2001:68::2"), SourcePort: 1, }, - wantFC: &FilterChain{SecurityCfg: &SecurityConfig{IdentityInstanceName: "wildcard-source-prefix-v6"}}, + wantFC: &FilterChain{ + SecurityCfg: &SecurityConfig{IdentityInstanceName: "wildcard-source-prefix-v6"}, + InlineRouteConfig: inlineRouteConfig, + }, }, { desc: "specific destination and wildcard source type match", @@ -1848,7 +2233,10 @@ func TestLookup_Successes(t *testing.T) { SourceAddr: net.IPv4(192, 168, 100, 1), SourcePort: 80, }, - wantFC: &FilterChain{SecurityCfg: &SecurityConfig{IdentityInstanceName: "specific-destination-prefix-unspecified-source-type"}}, + wantFC: &FilterChain{ + SecurityCfg: &SecurityConfig{IdentityInstanceName: "specific-destination-prefix-unspecified-source-type"}, + InlineRouteConfig: inlineRouteConfig, + }, }, { desc: "specific destination and source type match", @@ -1859,7 +2247,10 @@ func TestLookup_Successes(t *testing.T) { SourceAddr: net.IPv4(10, 1, 1, 1), SourcePort: 80, }, - wantFC: &FilterChain{SecurityCfg: &SecurityConfig{IdentityInstanceName: "specific-destination-prefix-specific-source-type"}}, + wantFC: &FilterChain{ + SecurityCfg: &SecurityConfig{IdentityInstanceName: "specific-destination-prefix-specific-source-type"}, + InlineRouteConfig: inlineRouteConfig, + }, }, { desc: "specific destination source type and source prefix", @@ -1870,7 +2261,10 @@ func TestLookup_Successes(t *testing.T) { SourceAddr: net.IPv4(192, 168, 92, 100), SourcePort: 70, }, - wantFC: &FilterChain{SecurityCfg: &SecurityConfig{IdentityInstanceName: "specific-destination-prefix-specific-source-type-specific-source-prefix"}}, + wantFC: &FilterChain{ + SecurityCfg: &SecurityConfig{IdentityInstanceName: "specific-destination-prefix-specific-source-type-specific-source-prefix"}, + InlineRouteConfig: inlineRouteConfig, + }, }, { desc: "specific destination source type source prefix and source port", @@ -1881,7 +2275,10 @@ func TestLookup_Successes(t *testing.T) { SourceAddr: net.IPv4(192, 168, 92, 100), SourcePort: 80, }, - wantFC: &FilterChain{SecurityCfg: &SecurityConfig{IdentityInstanceName: "specific-destination-prefix-specific-source-type-specific-source-prefix-specific-source-port"}}, + wantFC: &FilterChain{ + SecurityCfg: &SecurityConfig{IdentityInstanceName: "specific-destination-prefix-specific-source-type-specific-source-prefix-specific-source-port"}, + InlineRouteConfig: inlineRouteConfig, + }, }, } @@ -1918,6 +2315,8 @@ func (fci *FilterChainManager) Equal(other *FilterChainManager) bool { // TODO: Support comparing dstPrefixes slice? case !cmp.Equal(fci.def, other.def, cmpopts.EquateEmpty(), protocmp.Transform()): return false + case !cmp.Equal(fci.RouteConfigNames, other.RouteConfigNames, cmpopts.EquateEmpty()): + return false } return true } diff --git a/xds/internal/xdsclient/lds_test.go b/xds/internal/xdsclient/lds_test.go index 012efd16d..c04f92393 100644 --- a/xds/internal/xdsclient/lds_test.go +++ b/xds/internal/xdsclient/lds_test.go @@ -493,7 +493,7 @@ func (s) TestUnmarshalListener_ClientSide(t *testing.T) { InlineRouteConfig: &RouteConfigUpdate{ VirtualHosts: []*VirtualHost{{ Domains: []string{v3LDSTarget}, - Routes: []*Route{{Prefix: newStringP("/"), WeightedClusters: map[string]WeightedCluster{clusterName: {Weight: 1}}}}, + Routes: []*Route{{Prefix: newStringP("/"), WeightedClusters: map[string]WeightedCluster{clusterName: {Weight: 1}}, RouteAction: RouteActionRoute}}, }}}, MaxStreamDuration: time.Second, Raw: v3LisWithInlineRoute, @@ -563,11 +563,30 @@ func (s) TestUnmarshalListener_ServerSide(t *testing.T) { ) var ( + routeConfig = &v3routepb.RouteConfiguration{ + Name: "routeName", + VirtualHosts: []*v3routepb.VirtualHost{{ + Domains: []string{"lds.target.good:3333"}, + Routes: []*v3routepb.Route{{ + Match: &v3routepb.RouteMatch{ + PathSpecifier: &v3routepb.RouteMatch_Prefix{Prefix: "/"}, + }, + Action: &v3routepb.Route_NonForwardingAction{}, + }}}}} + inlineRouteConfig = &RouteConfigUpdate{ + VirtualHosts: []*VirtualHost{{ + Domains: []string{"lds.target.good:3333"}, + Routes: []*Route{{Prefix: newStringP("/"), RouteAction: RouteActionNonForwardingAction}}, + }}} emptyValidNetworkFilters = []*v3listenerpb.Filter{ { Name: "filter-1", ConfigType: &v3listenerpb.Filter_TypedConfig{ - TypedConfig: testutils.MarshalAny(&v3httppb.HttpConnectionManager{}), + TypedConfig: testutils.MarshalAny(&v3httppb.HttpConnectionManager{ + RouteSpecifier: &v3httppb.HttpConnectionManager_RouteConfig{ + RouteConfig: routeConfig, + }, + }), }, }, } @@ -806,13 +825,21 @@ func (s) TestUnmarshalListener_ServerSide(t *testing.T) { { Name: "name", ConfigType: &v3listenerpb.Filter_TypedConfig{ - TypedConfig: testutils.MarshalAny(&v3httppb.HttpConnectionManager{}), + TypedConfig: testutils.MarshalAny(&v3httppb.HttpConnectionManager{ + RouteSpecifier: &v3httppb.HttpConnectionManager_RouteConfig{ + RouteConfig: routeConfig, + }, + }), }, }, { Name: "name", ConfigType: &v3listenerpb.Filter_TypedConfig{ - TypedConfig: testutils.MarshalAny(&v3httppb.HttpConnectionManager{}), + TypedConfig: testutils.MarshalAny(&v3httppb.HttpConnectionManager{ + RouteSpecifier: &v3httppb.HttpConnectionManager_RouteConfig{ + RouteConfig: routeConfig, + }, + }), }, }, }, @@ -1051,7 +1078,7 @@ func (s) TestUnmarshalListener_ServerSide(t *testing.T) { srcPrefixMap: map[string]*sourcePrefixEntry{ unspecifiedPrefixMapKey: { srcPortMap: map[int]*FilterChain{ - 0: {}, + 0: {InlineRouteConfig: inlineRouteConfig}, }, }, }, @@ -1144,6 +1171,7 @@ func (s) TestUnmarshalListener_ServerSide(t *testing.T) { IdentityInstanceName: "identityPluginInstance", IdentityCertName: "identityCertName", }, + InlineRouteConfig: inlineRouteConfig, }, }, }, @@ -1157,6 +1185,7 @@ func (s) TestUnmarshalListener_ServerSide(t *testing.T) { IdentityInstanceName: "defaultIdentityPluginInstance", IdentityCertName: "defaultIdentityCertName", }, + InlineRouteConfig: inlineRouteConfig, }, }, }, @@ -1192,6 +1221,7 @@ func (s) TestUnmarshalListener_ServerSide(t *testing.T) { IdentityCertName: "identityCertName", RequireClientCert: true, }, + InlineRouteConfig: inlineRouteConfig, }, }, }, @@ -1208,6 +1238,7 @@ func (s) TestUnmarshalListener_ServerSide(t *testing.T) { IdentityCertName: "defaultIdentityCertName", RequireClientCert: true, }, + InlineRouteConfig: inlineRouteConfig, }, }, }, diff --git a/xds/internal/xdsclient/rds_test.go b/xds/internal/xdsclient/rds_test.go index 15a2afee1..11edabce5 100644 --- a/xds/internal/xdsclient/rds_test.go +++ b/xds/internal/xdsclient/rds_test.go @@ -76,6 +76,7 @@ func (s) TestRDSGenerateRDSUpdateFromRouteConfiguration(t *testing.T) { Routes: []*Route{{ Prefix: newStringP("/"), WeightedClusters: map[string]WeightedCluster{clusterName: {Weight: 1}}, + RouteAction: RouteActionRoute, }}, HTTPFilterConfigOverride: cfgs, }}, @@ -178,7 +179,10 @@ func (s) TestRDSGenerateRDSUpdateFromRouteConfiguration(t *testing.T) { VirtualHosts: []*VirtualHost{ { Domains: []string{ldsTarget}, - Routes: []*Route{{Prefix: newStringP("/"), CaseInsensitive: true, WeightedClusters: map[string]WeightedCluster{clusterName: {Weight: 1}}}}, + Routes: []*Route{{Prefix: newStringP("/"), + CaseInsensitive: true, + WeightedClusters: map[string]WeightedCluster{clusterName: {Weight: 1}}, + RouteAction: RouteActionRoute}}, }, }, }, @@ -220,11 +224,15 @@ func (s) TestRDSGenerateRDSUpdateFromRouteConfiguration(t *testing.T) { VirtualHosts: []*VirtualHost{ { Domains: []string{uninterestingDomain}, - Routes: []*Route{{Prefix: newStringP(""), WeightedClusters: map[string]WeightedCluster{uninterestingClusterName: {Weight: 1}}}}, + Routes: []*Route{{Prefix: newStringP(""), + WeightedClusters: map[string]WeightedCluster{uninterestingClusterName: {Weight: 1}}, + RouteAction: RouteActionRoute}}, }, { Domains: []string{ldsTarget}, - Routes: []*Route{{Prefix: newStringP(""), WeightedClusters: map[string]WeightedCluster{clusterName: {Weight: 1}}}}, + Routes: []*Route{{Prefix: newStringP(""), + WeightedClusters: map[string]WeightedCluster{clusterName: {Weight: 1}}, + RouteAction: RouteActionRoute}}, }, }, }, @@ -254,7 +262,9 @@ func (s) TestRDSGenerateRDSUpdateFromRouteConfiguration(t *testing.T) { VirtualHosts: []*VirtualHost{ { Domains: []string{ldsTarget}, - Routes: []*Route{{Prefix: newStringP("/"), WeightedClusters: map[string]WeightedCluster{clusterName: {Weight: 1}}}}, + Routes: []*Route{{Prefix: newStringP("/"), + WeightedClusters: map[string]WeightedCluster{clusterName: {Weight: 1}}, + RouteAction: RouteActionRoute}}, }, }, }, @@ -331,6 +341,7 @@ func (s) TestRDSGenerateRDSUpdateFromRouteConfiguration(t *testing.T) { "b": {Weight: 3}, "c": {Weight: 5}, }, + RouteAction: RouteActionRoute, }}, }, }, @@ -365,6 +376,7 @@ func (s) TestRDSGenerateRDSUpdateFromRouteConfiguration(t *testing.T) { Prefix: newStringP("/"), WeightedClusters: map[string]WeightedCluster{clusterName: {Weight: 1}}, MaxStreamDuration: newDurationP(time.Second), + RouteAction: RouteActionRoute, }}, }, }, @@ -399,6 +411,7 @@ func (s) TestRDSGenerateRDSUpdateFromRouteConfiguration(t *testing.T) { Prefix: newStringP("/"), WeightedClusters: map[string]WeightedCluster{clusterName: {Weight: 1}}, MaxStreamDuration: newDurationP(time.Second), + RouteAction: RouteActionRoute, }}, }, }, @@ -433,6 +446,7 @@ func (s) TestRDSGenerateRDSUpdateFromRouteConfiguration(t *testing.T) { Prefix: newStringP("/"), WeightedClusters: map[string]WeightedCluster{clusterName: {Weight: 1}}, MaxStreamDuration: newDurationP(0), + RouteAction: RouteActionRoute, }}, }, }, @@ -621,11 +635,15 @@ func (s) TestUnmarshalRouteConfig(t *testing.T) { VirtualHosts: []*VirtualHost{ { Domains: []string{uninterestingDomain}, - Routes: []*Route{{Prefix: newStringP(""), WeightedClusters: map[string]WeightedCluster{uninterestingClusterName: {Weight: 1}}}}, + Routes: []*Route{{Prefix: newStringP(""), + WeightedClusters: map[string]WeightedCluster{uninterestingClusterName: {Weight: 1}}, + RouteAction: RouteActionRoute}}, }, { Domains: []string{ldsTarget}, - Routes: []*Route{{Prefix: newStringP(""), WeightedClusters: map[string]WeightedCluster{v2ClusterName: {Weight: 1}}}}, + Routes: []*Route{{Prefix: newStringP(""), + WeightedClusters: map[string]WeightedCluster{v2ClusterName: {Weight: 1}}, + RouteAction: RouteActionRoute}}, }, }, Raw: v2RouteConfig, @@ -644,11 +662,15 @@ func (s) TestUnmarshalRouteConfig(t *testing.T) { VirtualHosts: []*VirtualHost{ { Domains: []string{uninterestingDomain}, - Routes: []*Route{{Prefix: newStringP(""), WeightedClusters: map[string]WeightedCluster{uninterestingClusterName: {Weight: 1}}}}, + Routes: []*Route{{Prefix: newStringP(""), + WeightedClusters: map[string]WeightedCluster{uninterestingClusterName: {Weight: 1}}, + RouteAction: RouteActionRoute}}, }, { Domains: []string{ldsTarget}, - Routes: []*Route{{Prefix: newStringP(""), WeightedClusters: map[string]WeightedCluster{v3ClusterName: {Weight: 1}}}}, + Routes: []*Route{{Prefix: newStringP(""), + WeightedClusters: map[string]WeightedCluster{v3ClusterName: {Weight: 1}}, + RouteAction: RouteActionRoute}}, }, }, Raw: v3RouteConfig, @@ -667,11 +689,15 @@ func (s) TestUnmarshalRouteConfig(t *testing.T) { VirtualHosts: []*VirtualHost{ { Domains: []string{uninterestingDomain}, - Routes: []*Route{{Prefix: newStringP(""), WeightedClusters: map[string]WeightedCluster{uninterestingClusterName: {Weight: 1}}}}, + Routes: []*Route{{Prefix: newStringP(""), + WeightedClusters: map[string]WeightedCluster{uninterestingClusterName: {Weight: 1}}, + RouteAction: RouteActionRoute}}, }, { Domains: []string{ldsTarget}, - Routes: []*Route{{Prefix: newStringP(""), WeightedClusters: map[string]WeightedCluster{v3ClusterName: {Weight: 1}}}}, + Routes: []*Route{{Prefix: newStringP(""), + WeightedClusters: map[string]WeightedCluster{v3ClusterName: {Weight: 1}}, + RouteAction: RouteActionRoute}}, }, }, Raw: v3RouteConfig, @@ -680,11 +706,15 @@ func (s) TestUnmarshalRouteConfig(t *testing.T) { VirtualHosts: []*VirtualHost{ { Domains: []string{uninterestingDomain}, - Routes: []*Route{{Prefix: newStringP(""), WeightedClusters: map[string]WeightedCluster{uninterestingClusterName: {Weight: 1}}}}, + Routes: []*Route{{Prefix: newStringP(""), + WeightedClusters: map[string]WeightedCluster{uninterestingClusterName: {Weight: 1}}, + RouteAction: RouteActionRoute}}, }, { Domains: []string{ldsTarget}, - Routes: []*Route{{Prefix: newStringP(""), WeightedClusters: map[string]WeightedCluster{v2ClusterName: {Weight: 1}}}}, + Routes: []*Route{{Prefix: newStringP(""), + WeightedClusters: map[string]WeightedCluster{v2ClusterName: {Weight: 1}}, + RouteAction: RouteActionRoute}}, }, }, Raw: v2RouteConfig, @@ -714,11 +744,15 @@ func (s) TestUnmarshalRouteConfig(t *testing.T) { VirtualHosts: []*VirtualHost{ { Domains: []string{uninterestingDomain}, - Routes: []*Route{{Prefix: newStringP(""), WeightedClusters: map[string]WeightedCluster{uninterestingClusterName: {Weight: 1}}}}, + Routes: []*Route{{Prefix: newStringP(""), + WeightedClusters: map[string]WeightedCluster{uninterestingClusterName: {Weight: 1}}, + RouteAction: RouteActionRoute}}, }, { Domains: []string{ldsTarget}, - Routes: []*Route{{Prefix: newStringP(""), WeightedClusters: map[string]WeightedCluster{v3ClusterName: {Weight: 1}}}}, + Routes: []*Route{{Prefix: newStringP(""), + WeightedClusters: map[string]WeightedCluster{v3ClusterName: {Weight: 1}}, + RouteAction: RouteActionRoute}}, }, }, Raw: v3RouteConfig, @@ -727,11 +761,15 @@ func (s) TestUnmarshalRouteConfig(t *testing.T) { VirtualHosts: []*VirtualHost{ { Domains: []string{uninterestingDomain}, - Routes: []*Route{{Prefix: newStringP(""), WeightedClusters: map[string]WeightedCluster{uninterestingClusterName: {Weight: 1}}}}, + Routes: []*Route{{Prefix: newStringP(""), + WeightedClusters: map[string]WeightedCluster{uninterestingClusterName: {Weight: 1}}, + RouteAction: RouteActionRoute}}, }, { Domains: []string{ldsTarget}, - Routes: []*Route{{Prefix: newStringP(""), WeightedClusters: map[string]WeightedCluster{v2ClusterName: {Weight: 1}}}}, + Routes: []*Route{{Prefix: newStringP(""), + WeightedClusters: map[string]WeightedCluster{v2ClusterName: {Weight: 1}}, + RouteAction: RouteActionRoute}}, }, }, Raw: v2RouteConfig, @@ -794,6 +832,7 @@ func (s) TestRoutesProtoToSlice(t *testing.T) { CaseInsensitive: true, WeightedClusters: map[string]WeightedCluster{"A": {Weight: 40}, "B": {Weight: 60, HTTPFilterConfigOverride: cfgs}}, HTTPFilterConfigOverride: cfgs, + RouteAction: RouteActionRoute, }} } ) @@ -833,6 +872,7 @@ func (s) TestRoutesProtoToSlice(t *testing.T) { Prefix: newStringP("/"), CaseInsensitive: true, WeightedClusters: map[string]WeightedCluster{"A": {Weight: 40}, "B": {Weight: 60}}, + RouteAction: RouteActionRoute, }}, }, { @@ -880,6 +920,7 @@ func (s) TestRoutesProtoToSlice(t *testing.T) { }, Fraction: newUInt32P(10000), WeightedClusters: map[string]WeightedCluster{"A": {Weight: 40}, "B": {Weight: 60}}, + RouteAction: RouteActionRoute, }}, wantErr: false, }, @@ -925,6 +966,7 @@ func (s) TestRoutesProtoToSlice(t *testing.T) { }, Fraction: newUInt32P(10000), WeightedClusters: map[string]WeightedCluster{"A": {Weight: 40}, "B": {Weight: 60}}, + RouteAction: RouteActionRoute, }}, wantErr: false, }, @@ -959,6 +1001,7 @@ func (s) TestRoutesProtoToSlice(t *testing.T) { wantRoutes: []*Route{{ Prefix: newStringP("/a/"), WeightedClusters: map[string]WeightedCluster{"A": {Weight: 40}, "B": {Weight: 60}}, + RouteAction: RouteActionRoute, }}, wantErr: false, }, @@ -1126,6 +1169,7 @@ func (s) TestRoutesProtoToSlice(t *testing.T) { wantRoutes: []*Route{{ Prefix: newStringP("/a/"), WeightedClusters: map[string]WeightedCluster{"A": {Weight: 40}, "B": {Weight: 60}}, + RouteAction: RouteActionRoute, }}, wantErr: false, }, @@ -1151,6 +1195,7 @@ func (s) TestRoutesProtoToSlice(t *testing.T) { wantRoutes: []*Route{{ Prefix: newStringP("/a/"), WeightedClusters: map[string]WeightedCluster{"A": {Weight: 20}, "B": {Weight: 30}}, + RouteAction: RouteActionRoute, }}, wantErr: false, }, @@ -1206,6 +1251,7 @@ func (s) TestRoutesProtoToSlice(t *testing.T) { HashPolicies: []*HashPolicy{ {HashPolicyType: HashPolicyTypeChannelID}, }, + RouteAction: RouteActionRoute, }}, wantErr: false, }, @@ -1264,6 +1310,7 @@ func (s) TestRoutesProtoToSlice(t *testing.T) { {HashPolicyType: HashPolicyTypeHeader, HeaderName: ":path"}, }, + RouteAction: RouteActionRoute, }}, wantErr: false, }, diff --git a/xds/internal/xdsclient/v2/rds_test.go b/xds/internal/xdsclient/v2/rds_test.go index 745308c4e..57058aa36 100644 --- a/xds/internal/xdsclient/v2/rds_test.go +++ b/xds/internal/xdsclient/v2/rds_test.go @@ -111,11 +111,16 @@ func (s) TestRDSHandleResponseWithRouting(t *testing.T) { VirtualHosts: []*xdsclient.VirtualHost{ { Domains: []string{uninterestingDomain}, - Routes: []*xdsclient.Route{{Prefix: newStringP(""), WeightedClusters: map[string]xdsclient.WeightedCluster{uninterestingClusterName: {Weight: 1}}}}, + Routes: []*xdsclient.Route{{Prefix: newStringP(""), + WeightedClusters: map[string]xdsclient.WeightedCluster{uninterestingClusterName: {Weight: 1}}, + RouteAction: xdsclient.RouteActionRoute}}, }, { Domains: []string{goodLDSTarget1}, - Routes: []*xdsclient.Route{{Prefix: newStringP(""), WeightedClusters: map[string]xdsclient.WeightedCluster{goodClusterName2: {Weight: 1}}}}, + Routes: []*xdsclient.Route{{ + Prefix: newStringP(""), + WeightedClusters: map[string]xdsclient.WeightedCluster{goodClusterName2: {Weight: 1}}, + RouteAction: xdsclient.RouteActionRoute}}, }, }, Raw: marshaledGoodRouteConfig2, @@ -136,11 +141,16 @@ func (s) TestRDSHandleResponseWithRouting(t *testing.T) { VirtualHosts: []*xdsclient.VirtualHost{ { Domains: []string{uninterestingDomain}, - Routes: []*xdsclient.Route{{Prefix: newStringP(""), WeightedClusters: map[string]xdsclient.WeightedCluster{uninterestingClusterName: {Weight: 1}}}}, + Routes: []*xdsclient.Route{{ + Prefix: newStringP(""), + WeightedClusters: map[string]xdsclient.WeightedCluster{uninterestingClusterName: {Weight: 1}}, + RouteAction: xdsclient.RouteActionRoute}}, }, { Domains: []string{goodLDSTarget1}, - Routes: []*xdsclient.Route{{Prefix: newStringP(""), WeightedClusters: map[string]xdsclient.WeightedCluster{goodClusterName1: {Weight: 1}}}}, + Routes: []*xdsclient.Route{{Prefix: newStringP(""), + WeightedClusters: map[string]xdsclient.WeightedCluster{goodClusterName1: {Weight: 1}}, + RouteAction: xdsclient.RouteActionRoute}}, }, }, Raw: marshaledGoodRouteConfig1, diff --git a/xds/internal/xdsclient/xds.go b/xds/internal/xdsclient/xds.go index 79c2efcd2..c3b090fe4 100644 --- a/xds/internal/xdsclient/xds.go +++ b/xds/internal/xdsclient/xds.go @@ -277,65 +277,6 @@ func processServerSideListener(lis *v3listenerpb.Listener) (*ListenerUpdate, err return lu, nil } -func processNetworkFilters(filters []*v3listenerpb.Filter) ([]HTTPFilter, error) { - seenNames := make(map[string]bool, len(filters)) - seenHCM := false - var httpFilters []HTTPFilter - for _, filter := range filters { - name := filter.GetName() - if name == "" { - return nil, fmt.Errorf("network filters {%+v} is missing name field in filter: {%+v}", filters, filter) - } - if seenNames[name] { - return nil, fmt.Errorf("network filters {%+v} has duplicate filter name %q", filters, name) - } - seenNames[name] = true - - // Network filters have a oneof field named `config_type` where we - // only support `TypedConfig` variant. - switch typ := filter.GetConfigType().(type) { - case *v3listenerpb.Filter_TypedConfig: - // The typed_config field has an `anypb.Any` proto which could - // directly contain the serialized bytes of the actual filter - // configuration, or it could be encoded as a `TypedStruct`. - // TODO: Add support for `TypedStruct`. - tc := filter.GetTypedConfig() - - // The only network filter that we currently support is the v3 - // HttpConnectionManager. So, we can directly check the type_url - // and unmarshal the config. - // TODO: Implement a registry of supported network filters (like - // we have for HTTP filters), when we have to support network - // filters other than HttpConnectionManager. - if tc.GetTypeUrl() != version.V3HTTPConnManagerURL { - return nil, fmt.Errorf("network filters {%+v} has unsupported network filter %q in filter {%+v}", filters, tc.GetTypeUrl(), filter) - } - hcm := &v3httppb.HttpConnectionManager{} - if err := ptypes.UnmarshalAny(tc, hcm); err != nil { - return nil, fmt.Errorf("network filters {%+v} failed unmarshaling of network filter {%+v}: %v", filters, filter, err) - } - // "Any filters after HttpConnectionManager should be ignored during - // connection processing but still be considered for validity. - // HTTPConnectionManager must have valid http_filters." - A36 - filters, err := processHTTPFilters(hcm.GetHttpFilters(), true) - if err != nil { - return nil, fmt.Errorf("network filters {%+v} had invalid server side HTTP Filters {%+v}", filters, hcm.GetHttpFilters()) - } - if !seenHCM { - // TODO: Implement terminal filter logic, as per A36. - httpFilters = filters - seenHCM = true - } - default: - return nil, fmt.Errorf("network filters {%+v} has unsupported config_type %T in filter %s", filters, typ, filter.GetName()) - } - } - if !seenHCM { - return nil, fmt.Errorf("network filters {%+v} missing HttpConnectionManager filter", filters) - } - return httpFilters, nil -} - // UnmarshalRouteConfig processes resources received in an RDS response, // validates them, and transforms them into a native struct which contains only // fields we are interested in. The provided hostname determines the route @@ -491,65 +432,74 @@ func routesProtoToSlice(routes []*v3routepb.Route, logger *grpclog.PrefixLogger, route.Fraction = &n } - route.WeightedClusters = make(map[string]WeightedCluster) - action := r.GetRoute() + switch r.GetAction().(type) { + case *v3routepb.Route_Route: + route.WeightedClusters = make(map[string]WeightedCluster) + action := r.GetRoute() - // Hash Policies are only applicable for a Ring Hash LB. - if env.RingHashSupport { - hp, err := hashPoliciesProtoToSlice(action.HashPolicy, logger) - if err != nil { - return nil, err - } - route.HashPolicies = hp - } - - switch a := action.GetClusterSpecifier().(type) { - case *v3routepb.RouteAction_Cluster: - route.WeightedClusters[a.Cluster] = WeightedCluster{Weight: 1} - case *v3routepb.RouteAction_WeightedClusters: - wcs := a.WeightedClusters - var totalWeight uint32 - for _, c := range wcs.Clusters { - w := c.GetWeight().GetValue() - if w == 0 { - continue + // Hash Policies are only applicable for a Ring Hash LB. + if env.RingHashSupport { + hp, err := hashPoliciesProtoToSlice(action.HashPolicy, logger) + if err != nil { + return nil, err } - wc := WeightedCluster{Weight: w} - if !v2 { - cfgs, err := processHTTPFilterOverrides(c.GetTypedPerFilterConfig()) - if err != nil { - return nil, fmt.Errorf("route %+v, action %+v: %v", r, a, err) + route.HashPolicies = hp + } + + switch a := action.GetClusterSpecifier().(type) { + case *v3routepb.RouteAction_Cluster: + route.WeightedClusters[a.Cluster] = WeightedCluster{Weight: 1} + case *v3routepb.RouteAction_WeightedClusters: + wcs := a.WeightedClusters + var totalWeight uint32 + for _, c := range wcs.Clusters { + w := c.GetWeight().GetValue() + if w == 0 { + continue } - wc.HTTPFilterConfigOverride = cfgs + wc := WeightedCluster{Weight: w} + if !v2 { + cfgs, err := processHTTPFilterOverrides(c.GetTypedPerFilterConfig()) + if err != nil { + return nil, fmt.Errorf("route %+v, action %+v: %v", r, a, err) + } + wc.HTTPFilterConfigOverride = cfgs + } + route.WeightedClusters[c.GetName()] = wc + totalWeight += w } - route.WeightedClusters[c.GetName()] = wc - totalWeight += w + // envoy xds doc + // default TotalWeight https://www.envoyproxy.io/docs/envoy/latest/api-v3/config/route/v3/route_components.proto.html#envoy-v3-api-field-config-route-v3-weightedcluster-total-weight + wantTotalWeight := uint32(100) + if tw := wcs.GetTotalWeight(); tw != nil { + wantTotalWeight = tw.GetValue() + } + if totalWeight != wantTotalWeight { + return nil, fmt.Errorf("route %+v, action %+v, weights of clusters do not add up to total total weight, got: %v, expected total weight from response: %v", r, a, totalWeight, wantTotalWeight) + } + if totalWeight == 0 { + return nil, fmt.Errorf("route %+v, action %+v, has no valid cluster in WeightedCluster action", r, a) + } + case *v3routepb.RouteAction_ClusterHeader: + continue } - // envoy xds doc - // default TotalWeight https://www.envoyproxy.io/docs/envoy/latest/api-v3/config/route/v3/route_components.proto.html#envoy-v3-api-field-config-route-v3-weightedcluster-total-weight - wantTotalWeight := uint32(100) - if tw := wcs.GetTotalWeight(); tw != nil { - wantTotalWeight = tw.GetValue() - } - if totalWeight != wantTotalWeight { - return nil, fmt.Errorf("route %+v, action %+v, weights of clusters do not add up to total total weight, got: %v, expected total weight from response: %v", r, a, totalWeight, wantTotalWeight) - } - if totalWeight == 0 { - return nil, fmt.Errorf("route %+v, action %+v, has no valid cluster in WeightedCluster action", r, a) - } - case *v3routepb.RouteAction_ClusterHeader: - continue - } - msd := action.GetMaxStreamDuration() - // Prefer grpc_timeout_header_max, if set. - dur := msd.GetGrpcTimeoutHeaderMax() - if dur == nil { - dur = msd.GetMaxStreamDuration() - } - if dur != nil { - d := dur.AsDuration() - route.MaxStreamDuration = &d + msd := action.GetMaxStreamDuration() + // Prefer grpc_timeout_header_max, if set. + dur := msd.GetGrpcTimeoutHeaderMax() + if dur == nil { + dur = msd.GetMaxStreamDuration() + } + if dur != nil { + d := dur.AsDuration() + route.MaxStreamDuration = &d + } + route.RouteAction = RouteActionRoute + case *v3routepb.Route_NonForwardingAction: + // Expected to be used on server side. + route.RouteAction = RouteActionNonForwardingAction + default: + route.RouteAction = RouteActionUnsupported } if !v2 { diff --git a/xds/server_test.go b/xds/server_test.go index 00b8518fa..df002dbab 100644 --- a/xds/server_test.go +++ b/xds/server_test.go @@ -32,6 +32,7 @@ import ( 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" v3tlspb "github.com/envoyproxy/go-control-plane/envoy/extensions/transport_sockets/tls/v3" "google.golang.org/grpc" @@ -688,7 +689,20 @@ func (s) TestHandleListenerUpdate_NoXDSCreds(t *testing.T) { { Name: "filter-1", ConfigType: &v3listenerpb.Filter_TypedConfig{ - TypedConfig: testutils.MarshalAny(&v3httppb.HttpConnectionManager{}), + TypedConfig: testutils.MarshalAny(&v3httppb.HttpConnectionManager{ + RouteSpecifier: &v3httppb.HttpConnectionManager_RouteConfig{ + RouteConfig: &v3routepb.RouteConfiguration{ + Name: "routeName", + VirtualHosts: []*v3routepb.VirtualHost{{ + Domains: []string{"lds.target.good:3333"}, + Routes: []*v3routepb.Route{{ + Match: &v3routepb.RouteMatch{ + PathSpecifier: &v3routepb.RouteMatch_Prefix{Prefix: "/"}, + }, + Action: &v3routepb.Route_NonForwardingAction{}, + }}}}}, + }, + }), }, }, },