@@ -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