Skip to content

Commit ddd377f

Browse files
authored
xds/server: fix RDS handling for non-inline route configs (#6915)
1 parent 8b455de commit ddd377f

21 files changed

+1359
-1129
lines changed

internal/internal.go

+9-5
Original file line numberDiff line numberDiff line change
@@ -68,11 +68,6 @@ var (
6868
// This is used in the 1.0 release of gcp/observability, and thus must not be
6969
// deleted or changed.
7070
CanonicalString any // func (codes.Code) string
71-
// DrainServerTransports initiates a graceful close of existing connections
72-
// on a gRPC server accepted on the provided listener address. An
73-
// xDS-enabled server invokes this method on a grpc.Server when a particular
74-
// listener moves to "not-serving" mode.
75-
DrainServerTransports any // func(*grpc.Server, string)
7671
// IsRegisteredMethod returns whether the passed in method is registered as
7772
// a method on the server.
7873
IsRegisteredMethod any // func(*grpc.Server, string) bool
@@ -188,6 +183,15 @@ var (
188183
ExitIdleModeForTesting any // func(*grpc.ClientConn) error
189184

190185
ChannelzTurnOffForTesting func()
186+
187+
// TriggerXDSResourceNameNotFoundForTesting triggers the resource-not-found
188+
// error for a given resource type and name. This is usually triggered when
189+
// the associated watch timer fires. For testing purposes, having this
190+
// function makes events more predictable than relying on timer events.
191+
TriggerXDSResourceNameNotFoundForTesting any // func(func(xdsresource.Type, string), string, string) error
192+
// TriggerXDSResourceNotFoundClient invokes the testing xDS Client singleton
193+
// to invoke resource not found for a resource type name and resource name.
194+
TriggerXDSResourceNameNotFoundClient any // func(string, string) error
191195
)
192196

193197
// HealthChecker defines the signature of the client-side LB channel health checking function.

internal/testutils/xds/e2e/clientresources.go

+128-8
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,72 @@ func marshalAny(m proto.Message) *anypb.Any {
137137
return a
138138
}
139139

140+
// filterChainWontMatch returns a filter chain that won't match if running the
141+
// test locally.
142+
func filterChainWontMatch(routeName string, addressPrefix string, srcPorts []uint32) *v3listenerpb.FilterChain {
143+
hcm := &v3httppb.HttpConnectionManager{
144+
RouteSpecifier: &v3httppb.HttpConnectionManager_Rds{
145+
Rds: &v3httppb.Rds{
146+
ConfigSource: &v3corepb.ConfigSource{
147+
ConfigSourceSpecifier: &v3corepb.ConfigSource_Ads{Ads: &v3corepb.AggregatedConfigSource{}},
148+
},
149+
RouteConfigName: routeName,
150+
},
151+
},
152+
HttpFilters: []*v3httppb.HttpFilter{RouterHTTPFilter},
153+
}
154+
return &v3listenerpb.FilterChain{
155+
Name: routeName + "-wont-match",
156+
FilterChainMatch: &v3listenerpb.FilterChainMatch{
157+
PrefixRanges: []*v3corepb.CidrRange{
158+
{
159+
AddressPrefix: addressPrefix,
160+
PrefixLen: &wrapperspb.UInt32Value{
161+
Value: uint32(0),
162+
},
163+
},
164+
},
165+
SourceType: v3listenerpb.FilterChainMatch_SAME_IP_OR_LOOPBACK,
166+
SourcePorts: srcPorts,
167+
SourcePrefixRanges: []*v3corepb.CidrRange{
168+
{
169+
AddressPrefix: addressPrefix,
170+
PrefixLen: &wrapperspb.UInt32Value{
171+
Value: uint32(0),
172+
},
173+
},
174+
},
175+
},
176+
Filters: []*v3listenerpb.Filter{
177+
{
178+
Name: "filter-1",
179+
ConfigType: &v3listenerpb.Filter_TypedConfig{TypedConfig: marshalAny(hcm)},
180+
},
181+
},
182+
}
183+
}
184+
185+
// ListenerResourceThreeRouteResources returns a listener resource that points
186+
// to three route configurations. Only the filter chain that points to the first
187+
// route config can be matched to.
188+
func ListenerResourceThreeRouteResources(host string, port uint32, secLevel SecurityLevel, routeName string) *v3listenerpb.Listener {
189+
lis := defaultServerListenerCommon(host, port, secLevel, routeName, false)
190+
lis.FilterChains = append(lis.FilterChains, filterChainWontMatch("routeName2", "1.1.1.1", []uint32{1}))
191+
lis.FilterChains = append(lis.FilterChains, filterChainWontMatch("routeName3", "2.2.2.2", []uint32{2}))
192+
return lis
193+
}
194+
195+
// ListenerResourceFallbackToDefault returns a listener resource that contains a
196+
// filter chain that will never get chosen to process traffic and a default
197+
// filter chain. The default filter chain points to routeName2.
198+
func ListenerResourceFallbackToDefault(host string, port uint32, secLevel SecurityLevel) *v3listenerpb.Listener {
199+
lis := defaultServerListenerCommon(host, port, secLevel, "", false)
200+
lis.FilterChains = nil
201+
lis.FilterChains = append(lis.FilterChains, filterChainWontMatch("routeName", "1.1.1.1", []uint32{1}))
202+
lis.DefaultFilterChain = filterChainWontMatch("routeName2", "2.2.2.2", []uint32{2})
203+
return lis
204+
}
205+
140206
// DefaultServerListener returns a basic xds Listener resource to be used on the
141207
// server side. The returned Listener resource contains an inline route
142208
// configuration with the name of routeName.
@@ -290,13 +356,6 @@ func defaultServerListenerCommon(host string, port uint32, secLevel SecurityLeve
290356
}
291357
}
292358

293-
// DefaultServerListenerWithRouteConfigName returns a basic xds Listener
294-
// resource to be used on the server side. The returned Listener resource
295-
// contains a RouteCongiguration resource name that needs to be resolved.
296-
func DefaultServerListenerWithRouteConfigName(host string, port uint32, secLevel SecurityLevel, routeName string) *v3listenerpb.Listener {
297-
return defaultServerListenerCommon(host, port, secLevel, routeName, false)
298-
}
299-
300359
// HTTPFilter constructs an xds HttpFilter with the provided name and config.
301360
func HTTPFilter(name string, config proto.Message) *v3httppb.HttpFilter {
302361
return &v3httppb.HttpFilter{
@@ -356,7 +415,6 @@ type RouteConfigOptions struct {
356415
ListenerName string
357416
// ClusterSpecifierType determines the cluster specifier type.
358417
ClusterSpecifierType RouteConfigClusterSpecifierType
359-
360418
// ClusterName is name of the cluster resource used when the cluster
361419
// specifier type is set to RouteConfigClusterSpecifierTypeCluster.
362420
//
@@ -722,3 +780,65 @@ func EndpointResourceWithOptions(opts EndpointOptions) *v3endpointpb.ClusterLoad
722780
}
723781
return cla
724782
}
783+
784+
// DefaultServerListenerWithRouteConfigName returns a basic xds Listener
785+
// resource to be used on the server side. The returned Listener resource
786+
// contains a RouteCongiguration resource name that needs to be resolved.
787+
func DefaultServerListenerWithRouteConfigName(host string, port uint32, secLevel SecurityLevel, routeName string) *v3listenerpb.Listener {
788+
return defaultServerListenerCommon(host, port, secLevel, routeName, false)
789+
}
790+
791+
// RouteConfigNoRouteMatch returns an xDS RouteConfig resource which a route
792+
// with no route match. This will be NACKed by the xDS Client.
793+
func RouteConfigNoRouteMatch(routeName string) *v3routepb.RouteConfiguration {
794+
return &v3routepb.RouteConfiguration{
795+
Name: routeName,
796+
VirtualHosts: []*v3routepb.VirtualHost{{
797+
// This "*" string matches on any incoming authority. This is to ensure any
798+
// incoming RPC matches to Route_NonForwardingAction and will proceed as
799+
// normal.
800+
Domains: []string{"*"},
801+
Routes: []*v3routepb.Route{{
802+
Action: &v3routepb.Route_NonForwardingAction{},
803+
}}}}}
804+
}
805+
806+
// RouteConfigNonForwardingAction returns an xDS RouteConfig resource which
807+
// specifies to route to a route specifying non forwarding action. This is
808+
// intended to be used on the server side for RDS requests, and corresponds to
809+
// the inline route configuration in DefaultServerListener.
810+
func RouteConfigNonForwardingAction(routeName string) *v3routepb.RouteConfiguration {
811+
return &v3routepb.RouteConfiguration{
812+
Name: routeName,
813+
VirtualHosts: []*v3routepb.VirtualHost{{
814+
// This "*" string matches on any incoming authority. This is to ensure any
815+
// incoming RPC matches to Route_NonForwardingAction and will proceed as
816+
// normal.
817+
Domains: []string{"*"},
818+
Routes: []*v3routepb.Route{{
819+
Match: &v3routepb.RouteMatch{
820+
PathSpecifier: &v3routepb.RouteMatch_Prefix{Prefix: "/"},
821+
},
822+
Action: &v3routepb.Route_NonForwardingAction{},
823+
}}}}}
824+
}
825+
826+
// RouteConfigFilterAction returns an xDS RouteConfig resource which specifies
827+
// to route to a route specifying route filter action. Since this is not type
828+
// non forwarding action, this should fail requests that match to this server
829+
// side.
830+
func RouteConfigFilterAction(routeName string) *v3routepb.RouteConfiguration {
831+
return &v3routepb.RouteConfiguration{
832+
Name: routeName,
833+
VirtualHosts: []*v3routepb.VirtualHost{{
834+
// This "*" string matches on any incoming authority. This is to
835+
// ensure any incoming RPC matches to Route_Route and will fail with
836+
// UNAVAILABLE.
837+
Domains: []string{"*"},
838+
Routes: []*v3routepb.Route{{
839+
Match: &v3routepb.RouteMatch{
840+
PathSpecifier: &v3routepb.RouteMatch_Prefix{Prefix: "/"},
841+
},
842+
Action: &v3routepb.Route_FilterAction{},
843+
}}}}}
844+
}

server.go

+6-12
Original file line numberDiff line numberDiff line change
@@ -74,9 +74,6 @@ func init() {
7474
return srv.isRegisteredMethod(method)
7575
}
7676
internal.ServerFromContext = serverFromContext
77-
internal.DrainServerTransports = func(srv *Server, addr string) {
78-
srv.drainServerTransports(addr)
79-
}
8077
internal.AddGlobalServerOptions = func(opt ...ServerOption) {
8178
globalServerOptions = append(globalServerOptions, opt...)
8279
}
@@ -932,6 +929,12 @@ func (s *Server) handleRawConn(lisAddr string, rawConn net.Conn) {
932929
return
933930
}
934931

932+
if cc, ok := rawConn.(interface {
933+
PassServerTransport(transport.ServerTransport)
934+
}); ok {
935+
cc.PassServerTransport(st)
936+
}
937+
935938
if !s.addConn(lisAddr, st) {
936939
return
937940
}
@@ -941,15 +944,6 @@ func (s *Server) handleRawConn(lisAddr string, rawConn net.Conn) {
941944
}()
942945
}
943946

944-
func (s *Server) drainServerTransports(addr string) {
945-
s.mu.Lock()
946-
conns := s.conns[addr]
947-
for st := range conns {
948-
st.Drain("")
949-
}
950-
s.mu.Unlock()
951-
}
952-
953947
// newHTTP2Transport sets up a http/2 transport (using the
954948
// gRPC http2 server transport in transport/http2_server.go).
955949
func (s *Server) newHTTP2Transport(c net.Conn) transport.ServerTransport {

test/xds/xds_server_certificate_providers_test.go

+2-14
Original file line numberDiff line numberDiff line change
@@ -28,14 +28,12 @@ import (
2828

2929
"github.com/google/uuid"
3030
"google.golang.org/grpc"
31-
"google.golang.org/grpc/codes"
3231
"google.golang.org/grpc/connectivity"
3332
"google.golang.org/grpc/credentials/insecure"
3433
xdscreds "google.golang.org/grpc/credentials/xds"
3534
"google.golang.org/grpc/internal/testutils"
3635
"google.golang.org/grpc/internal/testutils/xds/bootstrap"
3736
"google.golang.org/grpc/internal/testutils/xds/e2e"
38-
"google.golang.org/grpc/status"
3937
"google.golang.org/grpc/xds"
4038
"google.golang.org/protobuf/types/known/wrapperspb"
4139

@@ -233,12 +231,7 @@ func (s) TestServerSideXDS_WithNoCertificateProvidersInBootstrap_Failure(t *test
233231
}
234232
defer cc.Close()
235233

236-
client := testgrpc.NewTestServiceClient(cc)
237-
sCtx, sCancel := context.WithTimeout(ctx, defaultTestShortTimeout)
238-
defer sCancel()
239-
if _, err := client.EmptyCall(sCtx, &testpb.Empty{}); status.Code(err) != codes.DeadlineExceeded {
240-
t.Fatalf("EmptyCall() failed: %v, wantCode: %s", err, codes.DeadlineExceeded)
241-
}
234+
waitForFailedRPCWithStatus(ctx, t, cc, errAcceptAndClose)
242235
}
243236

244237
// Tests the case where the bootstrap configuration contains one certificate
@@ -484,10 +477,5 @@ func (s) TestServerSideXDS_WithValidAndInvalidSecurityConfiguration(t *testing.T
484477
}
485478
defer cc2.Close()
486479

487-
client2 := testgrpc.NewTestServiceClient(cc2)
488-
sCtx, sCancel := context.WithTimeout(ctx, defaultTestShortTimeout)
489-
defer sCancel()
490-
if _, err := client2.EmptyCall(sCtx, &testpb.Empty{}); status.Code(err) != codes.DeadlineExceeded {
491-
t.Fatalf("EmptyCall() failed: %v, wantCode: %s", err, codes.DeadlineExceeded)
492-
}
480+
waitForFailedRPCWithStatus(ctx, t, cc2, errAcceptAndClose)
493481
}

test/xds/xds_server_integration_test.go

+10
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ package xds_test
2121
import (
2222
"context"
2323
"fmt"
24+
"io"
2425
"net"
2526
"strconv"
2627
"testing"
@@ -50,6 +51,15 @@ func (*testService) UnaryCall(context.Context, *testpb.SimpleRequest) (*testpb.S
5051
return &testpb.SimpleResponse{}, nil
5152
}
5253

54+
func (*testService) FullDuplexCall(stream testgrpc.TestService_FullDuplexCallServer) error {
55+
for {
56+
_, err := stream.Recv() // hangs here forever if stream doesn't shut down...doesn't receive EOF without any errors
57+
if err == io.EOF {
58+
return nil
59+
}
60+
}
61+
}
62+
5363
func testModeChangeServerOption(t *testing.T) grpc.ServerOption {
5464
// Create a server option to get notified about serving mode changes. We don't
5565
// do anything other than throwing a log entry here. But this is required,

test/xds/xds_server_serving_mode_test.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -386,7 +386,7 @@ func waitForFailedRPC(ctx context.Context, t *testing.T, cc *grpc.ClientConn) {
386386
return
387387
}
388388

389-
ticker := time.NewTimer(1 * time.Second)
389+
ticker := time.NewTicker(10 * time.Millisecond)
390390
defer ticker.Stop()
391391
for {
392392
select {

0 commit comments

Comments
 (0)