Skip to content

Commit 6e74938

Browse files
authored
xds/resolver: cleanup tests to use real xDS client (#5950)
1 parent 9b9b381 commit 6e74938

File tree

1 file changed

+183
-119
lines changed

1 file changed

+183
-119
lines changed

xds/internal/resolver/xds_resolver_test.go

Lines changed: 183 additions & 119 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import (
2929

3030
xxhash "github.com/cespare/xxhash/v2"
3131
"github.com/google/go-cmp/cmp"
32+
"google.golang.org/grpc"
3233
"google.golang.org/grpc/codes"
3334
"google.golang.org/grpc/credentials"
3435
"google.golang.org/grpc/credentials/insecure"
@@ -40,6 +41,8 @@ import (
4041
"google.golang.org/grpc/internal/grpctest"
4142
iresolver "google.golang.org/grpc/internal/resolver"
4243
"google.golang.org/grpc/internal/testutils"
44+
xdsbootstrap "google.golang.org/grpc/internal/testutils/xds/bootstrap"
45+
"google.golang.org/grpc/internal/testutils/xds/e2e"
4346
"google.golang.org/grpc/internal/wrr"
4447
"google.golang.org/grpc/metadata"
4548
"google.golang.org/grpc/resolver"
@@ -53,6 +56,9 @@ import (
5356
"google.golang.org/grpc/xds/internal/xdsclient"
5457
"google.golang.org/grpc/xds/internal/xdsclient/bootstrap"
5558
"google.golang.org/grpc/xds/internal/xdsclient/xdsresource"
59+
"google.golang.org/grpc/xds/internal/xdsclient/xdsresource/version"
60+
61+
v3discoverypb "github.com/envoyproxy/go-control-plane/envoy/service/discovery/v3"
5662

5763
_ "google.golang.org/grpc/xds/internal/balancer/cdsbalancer" // To parse LB config
5864
)
@@ -113,62 +119,65 @@ func newTestClientConn() *testClientConn {
113119
}
114120
}
115121

116-
// TestResolverBuilder tests the resolver builder's Build() method with
117-
// different parameters.
118-
func (s) TestResolverBuilder(t *testing.T) {
122+
// TestResolverBuilder_ClientCreationFails tests the case where xDS client
123+
// creation fails, and verifies that xDS resolver build fails as well.
124+
func (s) TestResolverBuilder_ClientCreationFails(t *testing.T) {
125+
// Override xDS client creation function and return an error.
126+
origNewClient := newXDSClient
127+
newXDSClient = func() (xdsclient.XDSClient, func(), error) {
128+
return nil, nil, errors.New("failed to create xDS client")
129+
}
130+
defer func() {
131+
newXDSClient = origNewClient
132+
}()
133+
134+
// Build an xDS resolver and expect it to fail.
135+
builder := resolver.Get(xdsScheme)
136+
if builder == nil {
137+
t.Fatalf("resolver.Get(%v) returned nil", xdsScheme)
138+
}
139+
if _, err := builder.Build(target, newTestClientConn(), resolver.BuildOptions{}); err == nil {
140+
t.Fatalf("builder.Build(%v) succeeded when expected to fail", target)
141+
}
142+
}
143+
144+
// TestResolverBuilder_DifferentBootstrapConfigs tests the resolver builder's
145+
// Build() method with different xDS bootstrap configurations.
146+
func (s) TestResolverBuilder_DifferentBootstrapConfigs(t *testing.T) {
119147
tests := []struct {
120-
name string
121-
xdsClientFunc func(closeCh chan struct{}) (xdsclient.XDSClient, func(), error)
122-
target resolver.Target
123-
buildOpts resolver.BuildOptions
124-
wantErr bool
148+
name string
149+
bootstrapCfg *bootstrap.Config // Empty top-level xDS server config, will be set by test logic.
150+
target resolver.Target
151+
buildOpts resolver.BuildOptions
152+
wantErr string
125153
}{
126154
{
127-
name: "good",
128-
xdsClientFunc: func(closeCh chan struct{}) (xdsclient.XDSClient, func(), error) {
129-
return fakeclient.NewClient(), func() { close(closeCh) }, nil
130-
},
131-
target: target,
132-
wantErr: false,
133-
},
134-
{
135-
name: "xDS client creation fails",
136-
xdsClientFunc: func(closeCh chan struct{}) (xdsclient.XDSClient, func(), error) {
137-
return nil, func() { close(closeCh) }, errors.New("failed to create xDS client")
138-
},
139-
target: target,
140-
wantErr: true,
155+
name: "good",
156+
bootstrapCfg: &bootstrap.Config{},
157+
target: target,
141158
},
142159
{
143160
name: "authority not defined in bootstrap",
144-
xdsClientFunc: func(closeCh chan struct{}) (xdsclient.XDSClient, func(), error) {
145-
c := fakeclient.NewClient()
146-
c.SetBootstrapConfig(&bootstrap.Config{
147-
ClientDefaultListenerResourceNameTemplate: "%s",
148-
Authorities: map[string]*bootstrap.Authority{
149-
"test-authority": {
150-
ClientListenerResourceNameTemplate: "xdstp://test-authority/%s",
151-
},
161+
bootstrapCfg: &bootstrap.Config{
162+
ClientDefaultListenerResourceNameTemplate: "%s",
163+
Authorities: map[string]*bootstrap.Authority{
164+
"test-authority": {
165+
ClientListenerResourceNameTemplate: "xdstp://test-authority/%s",
152166
},
153-
})
154-
return c, func() { close(closeCh) }, nil
167+
},
155168
},
156169
target: resolver.Target{
157170
URL: url.URL{
158171
Host: "non-existing-authority",
159172
Path: "/" + targetStr,
160173
},
161174
},
162-
wantErr: true,
175+
wantErr: `authority "non-existing-authority" is not found in the bootstrap file`,
163176
},
164177
{
165-
name: "xDS creds specified without certificate providers in bootstrap",
166-
xdsClientFunc: func(closeCh chan struct{}) (xdsclient.XDSClient, func(), error) {
167-
c := fakeclient.NewClient()
168-
c.SetBootstrapConfig(&bootstrap.Config{})
169-
return c, func() { close(closeCh) }, nil
170-
},
171-
target: target,
178+
name: "xDS creds specified without certificate providers in bootstrap",
179+
bootstrapCfg: &bootstrap.Config{},
180+
target: target,
172181
buildOpts: resolver.BuildOptions{
173182
DialCreds: func() credentials.TransportCredentials {
174183
creds, err := xdscreds.NewClientCredentials(xdscreds.ClientOptions{FallbackCreds: insecure.NewCredentials()})
@@ -178,17 +187,36 @@ func (s) TestResolverBuilder(t *testing.T) {
178187
return creds
179188
}(),
180189
},
181-
wantErr: true,
190+
wantErr: `xdsCreds specified but certificate_providers config missing in bootstrap file`,
182191
},
183192
}
184193
for _, test := range tests {
185194
t.Run(test.name, func(t *testing.T) {
186-
// Use a fake xDS client that closes the below channel when closed.
187-
closeCh := make(chan struct{})
188-
oldClientMaker := newXDSClient
189-
newXDSClient = func() (xdsclient.XDSClient, func(), error) { return test.xdsClientFunc(closeCh) }
195+
mgmtServer, err := e2e.StartManagementServer(e2e.ManagementServerOptions{})
196+
if err != nil {
197+
t.Fatalf("Starting xDS management server: %v", err)
198+
}
199+
defer mgmtServer.Stop()
200+
201+
// Add top-level xDS server config corresponding to the above
202+
// management server.
203+
test.bootstrapCfg.XDSServer = &bootstrap.ServerConfig{
204+
ServerURI: mgmtServer.Address,
205+
Creds: grpc.WithTransportCredentials(insecure.NewCredentials()),
206+
TransportAPI: version.TransportV3,
207+
}
208+
209+
// Override xDS client creation to use bootstrap configuration
210+
// specified by the test.
211+
origNewClient := newXDSClient
212+
newXDSClient = func() (xdsclient.XDSClient, func(), error) {
213+
// The watch timeout and idle authority timeout values passed to
214+
// NewWithConfigForTesing() are immaterial for this test, as we
215+
// are only testing the resolver build functionality.
216+
return xdsclient.NewWithConfigForTesting(test.bootstrapCfg, defaultTestTimeout, defaultTestTimeout)
217+
}
190218
defer func() {
191-
newXDSClient = oldClientMaker
219+
newXDSClient = origNewClient
192220
}()
193221

194222
builder := resolver.Get(xdsScheme)
@@ -197,20 +225,17 @@ func (s) TestResolverBuilder(t *testing.T) {
197225
}
198226

199227
r, err := builder.Build(test.target, newTestClientConn(), test.buildOpts)
200-
if (err != nil) != test.wantErr {
228+
if gotErr, wantErr := err != nil, test.wantErr != ""; gotErr != wantErr {
229+
t.Fatalf("builder.Build(%v) returned err: %v, wantErr: %v", target, err, test.wantErr)
230+
}
231+
if test.wantErr != "" && !strings.Contains(err.Error(), test.wantErr) {
201232
t.Fatalf("builder.Build(%v) returned err: %v, wantErr: %v", target, err, test.wantErr)
202233
}
203234
if err != nil {
204235
// This is the case where we expect an error and got it.
205236
return
206237
}
207238
r.Close()
208-
209-
select {
210-
case <-closeCh:
211-
case <-time.After(defaultTestTimeout):
212-
t.Fatal("Timeout when waiting for xDS client to be closed")
213-
}
214239
})
215240
}
216241
}
@@ -288,89 +313,128 @@ func waitForWatchRouteConfig(ctx context.Context, t *testing.T, xdsC *fakeclient
288313
}
289314
}
290315

291-
// TestXDSResolverResourceNameToWatch tests that the correct resource name is
292-
// used to watch for the service. This covers cases with different bootstrap
293-
// config, and different authority.
294-
func (s) TestXDSResolverResourceNameToWatch(t *testing.T) {
316+
// TestResolverResourceName builds an xDS resolver and verifies that the
317+
// resource name specified in the discovery request matches expectations.
318+
func (s) TestResolverResourceName(t *testing.T) {
319+
// Federation support is required when new style names are used.
320+
oldXDSFederation := envconfig.XDSFederation
321+
envconfig.XDSFederation = true
322+
defer func() { envconfig.XDSFederation = oldXDSFederation }()
323+
295324
tests := []struct {
296-
name string
297-
bc *bootstrap.Config
298-
target resolver.Target
299-
want string
325+
name string
326+
listenerResourceNameTemplate string
327+
extraAuthority string
328+
dialTarget string
329+
wantResourceName string
300330
}{
301331
{
302-
name: "default %s old style",
303-
bc: &bootstrap.Config{
304-
ClientDefaultListenerResourceNameTemplate: "%s",
305-
},
306-
target: resolver.Target{
307-
URL: url.URL{Path: "/" + targetStr},
308-
},
309-
want: targetStr,
332+
name: "default %s old style",
333+
listenerResourceNameTemplate: "%s",
334+
dialTarget: "xds:///target",
335+
wantResourceName: "target",
310336
},
311337
{
312-
name: "old style no percent encoding",
313-
bc: &bootstrap.Config{
314-
ClientDefaultListenerResourceNameTemplate: "/path/to/%s",
315-
},
316-
target: resolver.Target{
317-
URL: url.URL{Path: "/" + targetStr},
318-
},
319-
want: "/path/to/" + targetStr,
338+
name: "old style no percent encoding",
339+
listenerResourceNameTemplate: "/path/to/%s",
340+
dialTarget: "xds:///target",
341+
wantResourceName: "/path/to/target",
320342
},
321343
{
322-
name: "new style with %s",
323-
bc: &bootstrap.Config{
324-
ClientDefaultListenerResourceNameTemplate: "xdstp://authority.com/%s",
325-
Authorities: nil,
326-
},
327-
target: resolver.Target{
328-
URL: url.URL{Path: "/0.0.0.0:8080"},
329-
},
330-
want: "xdstp://authority.com/0.0.0.0:8080",
344+
name: "new style with %s",
345+
listenerResourceNameTemplate: "xdstp://authority.com/%s",
346+
dialTarget: "xds:///0.0.0.0:8080",
347+
wantResourceName: "xdstp://authority.com/0.0.0.0:8080",
331348
},
332349
{
333-
name: "new style percent encoding",
334-
bc: &bootstrap.Config{
335-
ClientDefaultListenerResourceNameTemplate: "xdstp://authority.com/%s",
336-
Authorities: nil,
337-
},
338-
target: resolver.Target{
339-
URL: url.URL{Path: "/[::1]:8080"},
340-
},
341-
want: "xdstp://authority.com/%5B::1%5D:8080",
350+
name: "new style percent encoding",
351+
listenerResourceNameTemplate: "xdstp://authority.com/%s",
352+
dialTarget: "xds:///[::1]:8080",
353+
wantResourceName: "xdstp://authority.com/%5B::1%5D:8080",
342354
},
343355
{
344-
name: "new style different authority",
345-
bc: &bootstrap.Config{
346-
ClientDefaultListenerResourceNameTemplate: "xdstp://authority.com/%s",
347-
Authorities: map[string]*bootstrap.Authority{
348-
"test-authority": {
349-
ClientListenerResourceNameTemplate: "xdstp://test-authority/%s",
350-
},
351-
},
352-
},
353-
target: resolver.Target{
354-
URL: url.URL{
355-
Host: "test-authority",
356-
Path: "/" + targetStr,
357-
},
358-
},
359-
want: "xdstp://test-authority/" + targetStr,
356+
name: "new style different authority",
357+
listenerResourceNameTemplate: "xdstp://authority.com/%s",
358+
extraAuthority: "test-authority",
359+
dialTarget: "xds://test-authority/target",
360+
wantResourceName: "xdstp://test-authority/envoy.config.listener.v3.Listener/target",
360361
},
361362
}
362363
for _, tt := range tests {
363364
t.Run(tt.name, func(t *testing.T) {
364-
xdsR, xdsC, _, cancel := testSetup(t, setupOpts{
365-
bootstrapC: tt.bc,
366-
target: tt.target,
365+
// Setup the management server to push the requested resource name
366+
// on to a channel. No resources are configured on the management
367+
// server as part of this test, as we are only interested in the
368+
// resource name being requested.
369+
resourceNameCh := make(chan string, 1)
370+
mgmtServer, err := e2e.StartManagementServer(e2e.ManagementServerOptions{
371+
OnStreamRequest: func(_ int64, req *v3discoverypb.DiscoveryRequest) error {
372+
// When the resolver is being closed, the watch associated
373+
// with the listener resource will be cancelled, and it
374+
// might result in a discovery request with no resource
375+
// names. Hence, we only consider requests which contain a
376+
// resource name.
377+
var name string
378+
if len(req.GetResourceNames()) == 1 {
379+
name = req.GetResourceNames()[0]
380+
}
381+
select {
382+
case resourceNameCh <- name:
383+
default:
384+
}
385+
return nil
386+
},
367387
})
368-
defer cancel()
369-
defer xdsR.Close()
388+
if err != nil {
389+
t.Fatalf("Failed to start xDS management server: %v", err)
390+
}
391+
defer mgmtServer.Stop()
370392

371-
ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout)
372-
defer cancel()
373-
waitForWatchListener(ctx, t, xdsC, tt.want)
393+
// Create a bootstrap configuration with test options.
394+
opts := xdsbootstrap.Options{
395+
ServerURI: mgmtServer.Address,
396+
Version: xdsbootstrap.TransportV3,
397+
ClientDefaultListenerResourceNameTemplate: tt.listenerResourceNameTemplate,
398+
}
399+
if tt.extraAuthority != "" {
400+
// In this test, we really don't care about having multiple
401+
// management servers. All we need to verify is whether the
402+
// resource name matches expectation.
403+
opts.Authorities = map[string]string{
404+
tt.extraAuthority: mgmtServer.Address,
405+
}
406+
}
407+
bootstrapContents, err := xdsbootstrap.Contents(opts)
408+
if err != nil {
409+
t.Fatal(err)
410+
}
411+
412+
// Build an xDS resolver that uses the above bootstrap configuration
413+
// and pass it to grpc.Dial(). Creating the xDS resolver should
414+
// result in creation of the xDS client.
415+
newResolver := internal.NewXDSResolverWithConfigForTesting
416+
if newResolver == nil {
417+
t.Fatal("internal.NewXDSResolverWithConfigForTesting is nil")
418+
}
419+
resolver, err := newResolver.(func([]byte) (resolver.Builder, error))(bootstrapContents)
420+
if err != nil {
421+
t.Fatalf("Failed to create xDS resolver for testing: %v", err)
422+
}
423+
cc, err := grpc.Dial(tt.dialTarget, grpc.WithTransportCredentials(insecure.NewCredentials()), grpc.WithResolvers(resolver))
424+
if err != nil {
425+
t.Fatalf("failed to dial local test server: %v", err)
426+
}
427+
defer cc.Close()
428+
429+
// Verify the resource name in the discovery request being sent out.
430+
select {
431+
case gotResourceName := <-resourceNameCh:
432+
if gotResourceName != tt.wantResourceName {
433+
t.Fatalf("Received discovery request with resource name: %v, want %v", gotResourceName, tt.wantResourceName)
434+
}
435+
case <-time.After(defaultTestTimeout):
436+
t.Fatalf("Timeout when waiting for discovery request")
437+
}
374438
})
375439
}
376440
}

0 commit comments

Comments
 (0)