google.golang.org/grpc@v1.72.2/xds/googledirectpath/googlec2p_test.go (about)

     1  /*
     2   *
     3   * Copyright 2021 gRPC authors.
     4   *
     5   * Licensed under the Apache License, Version 2.0 (the "License");
     6   * you may not use this file except in compliance with the License.
     7   * You may obtain a copy of the License at
     8   *
     9   *     http://www.apache.org/licenses/LICENSE-2.0
    10   *
    11   * Unless required by applicable law or agreed to in writing, software
    12   * distributed under the License is distributed on an "AS IS" BASIS,
    13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    14   * See the License for the specific language governing permissions and
    15   * limitations under the License.
    16   *
    17   */
    18  
    19  package googledirectpath
    20  
    21  import (
    22  	"context"
    23  	"encoding/json"
    24  	"strconv"
    25  	"strings"
    26  	"testing"
    27  	"time"
    28  
    29  	"github.com/google/go-cmp/cmp"
    30  	"google.golang.org/grpc"
    31  	"google.golang.org/grpc/credentials/insecure"
    32  	"google.golang.org/grpc/internal/envconfig"
    33  	"google.golang.org/grpc/internal/grpctest"
    34  	"google.golang.org/grpc/internal/xds/bootstrap"
    35  	testgrpc "google.golang.org/grpc/interop/grpc_testing"
    36  	testpb "google.golang.org/grpc/interop/grpc_testing"
    37  	"google.golang.org/grpc/resolver"
    38  	"google.golang.org/grpc/xds/internal/xdsclient"
    39  )
    40  
    41  const defaultTestTimeout = 5 * time.Second
    42  
    43  type s struct {
    44  	grpctest.Tester
    45  }
    46  
    47  func Test(t *testing.T) {
    48  	grpctest.RunSubTests(t, s{})
    49  }
    50  
    51  type emptyResolver struct {
    52  	resolver.Resolver
    53  	scheme string
    54  }
    55  
    56  func (er *emptyResolver) Build(_ resolver.Target, _ resolver.ClientConn, _ resolver.BuildOptions) (resolver.Resolver, error) {
    57  	return er, nil
    58  }
    59  
    60  func (er *emptyResolver) Scheme() string {
    61  	return er.scheme
    62  }
    63  
    64  func (er *emptyResolver) Close() {}
    65  
    66  var (
    67  	testDNSResolver = &emptyResolver{scheme: "dns"}
    68  	testXDSResolver = &emptyResolver{scheme: "xds"}
    69  )
    70  
    71  // replaceResolvers unregisters the real resolvers for schemes `dns` and `xds`
    72  // and registers test resolvers instead. This allows the test to verify that
    73  // expected resolvers are built.
    74  func replaceResolvers(t *testing.T) {
    75  	oldDNS := resolver.Get("dns")
    76  	resolver.Register(testDNSResolver)
    77  	oldXDS := resolver.Get("xds")
    78  	resolver.Register(testXDSResolver)
    79  	t.Cleanup(func() {
    80  		resolver.Register(oldDNS)
    81  		resolver.Register(oldXDS)
    82  	})
    83  }
    84  
    85  func simulateRunningOnGCE(t *testing.T, gce bool) {
    86  	oldOnGCE := onGCE
    87  	onGCE = func() bool { return gce }
    88  	t.Cleanup(func() { onGCE = oldOnGCE })
    89  }
    90  
    91  // ensure universeDomain is set to the expected default,
    92  // and clean it up again after the test.
    93  func useCleanUniverseDomain(t *testing.T) {
    94  	universeDomainMu.Lock()
    95  	defer universeDomainMu.Unlock()
    96  	if universeDomain != "" {
    97  		t.Fatalf("universe domain unexpectedly initialized: %v", universeDomain)
    98  	}
    99  	t.Cleanup(func() {
   100  		universeDomainMu.Lock()
   101  		universeDomain = ""
   102  		universeDomainMu.Unlock()
   103  	})
   104  }
   105  
   106  // Tests the scenario where the bootstrap env vars are set and we're running on
   107  // GCE. The test builds a google-c2p resolver and verifies that an xDS resolver
   108  // is built and that we don't fallback to DNS (because federation is enabled by
   109  // default).
   110  func (s) TestBuildWithBootstrapEnvSet(t *testing.T) {
   111  	replaceResolvers(t)
   112  	simulateRunningOnGCE(t, true)
   113  	useCleanUniverseDomain(t)
   114  
   115  	builder := resolver.Get(c2pScheme)
   116  	for i, envP := range []*string{&envconfig.XDSBootstrapFileName, &envconfig.XDSBootstrapFileContent} {
   117  		t.Run(strconv.Itoa(i), func(t *testing.T) {
   118  			// Set bootstrap config env var.
   119  			oldEnv := *envP
   120  			*envP = "does not matter"
   121  			defer func() { *envP = oldEnv }()
   122  
   123  			// Override xDS client pool.
   124  			oldXdsClientPool := xdsClientPool
   125  			xdsClientPool = xdsclient.NewPool(nil)
   126  			defer func() { xdsClientPool = oldXdsClientPool }()
   127  
   128  			// Build the google-c2p resolver.
   129  			r, err := builder.Build(resolver.Target{}, nil, resolver.BuildOptions{})
   130  			if err != nil {
   131  				t.Fatalf("failed to build resolver: %v", err)
   132  			}
   133  			defer r.Close()
   134  
   135  			// Build should return xDS, not DNS.
   136  			if r != testXDSResolver {
   137  				t.Fatalf("Build() returned %#v, want xds resolver", r)
   138  			}
   139  		})
   140  	}
   141  }
   142  
   143  // Tests the scenario where we are not running on GCE.  The test builds a
   144  // google-c2p resolver and verifies that we fallback to DNS.
   145  func (s) TestBuildNotOnGCE(t *testing.T) {
   146  	replaceResolvers(t)
   147  	simulateRunningOnGCE(t, false)
   148  	useCleanUniverseDomain(t)
   149  	builder := resolver.Get(c2pScheme)
   150  
   151  	// Build the google-c2p resolver.
   152  	r, err := builder.Build(resolver.Target{}, nil, resolver.BuildOptions{})
   153  	if err != nil {
   154  		t.Fatalf("failed to build resolver: %v", err)
   155  	}
   156  	defer r.Close()
   157  
   158  	// Build should return DNS, not xDS.
   159  	if r != testDNSResolver {
   160  		t.Fatalf("Build() returned %#v, want dns resolver", r)
   161  	}
   162  }
   163  
   164  func bootstrapConfig(t *testing.T, opts bootstrap.ConfigOptionsForTesting) *bootstrap.Config {
   165  	t.Helper()
   166  
   167  	contents, err := bootstrap.NewContentsForTesting(opts)
   168  	if err != nil {
   169  		t.Fatalf("Failed to create bootstrap contents: %v", err)
   170  	}
   171  	cfg, err := bootstrap.NewConfigFromContents(contents)
   172  	if err != nil {
   173  		t.Fatalf("Failed to create bootstrap config: %v", err)
   174  	}
   175  	return cfg
   176  }
   177  
   178  // Test that when a google-c2p resolver is built, the xDS client is built with
   179  // the expected config.
   180  func (s) TestBuildXDS(t *testing.T) {
   181  	replaceResolvers(t)
   182  	simulateRunningOnGCE(t, true)
   183  	useCleanUniverseDomain(t)
   184  	builder := resolver.Get(c2pScheme)
   185  
   186  	// Override the zone returned by the metadata server.
   187  	oldGetZone := getZone
   188  	getZone = func(time.Duration) string { return "test-zone" }
   189  	defer func() { getZone = oldGetZone }()
   190  
   191  	// Override the random func used in the node ID.
   192  	origRandInd := randInt
   193  	randInt = func() int { return 666 }
   194  	defer func() { randInt = origRandInd }()
   195  
   196  	for _, tt := range []struct {
   197  		desc                string
   198  		ipv6Capable         bool
   199  		tdURIOverride       string
   200  		wantBootstrapConfig *bootstrap.Config
   201  	}{
   202  		{
   203  			desc: "ipv6 false",
   204  			wantBootstrapConfig: bootstrapConfig(t, bootstrap.ConfigOptionsForTesting{
   205  				Servers: []byte(`[{
   206  					"server_uri": "dns:///directpath-pa.googleapis.com",
   207  					"channel_creds": [{"type": "google_default"}],
   208  					"server_features": ["ignore_resource_deletion"]
   209    				}]`),
   210  				Authorities: map[string]json.RawMessage{
   211  					"traffic-director-c2p.xds.googleapis.com": []byte(`{
   212  							"xds_servers": [
   213    								{
   214  								    "server_uri": "dns:///directpath-pa.googleapis.com",
   215  								    "channel_creds": [{"type": "google_default"}],
   216  								    "server_features": ["ignore_resource_deletion"]
   217    								}
   218  							]
   219  						}`),
   220  				},
   221  				Node: []byte(`{
   222  					  "id": "C2P-666",
   223  					  "locality": {"zone": "test-zone"}
   224  					}`),
   225  			}),
   226  		},
   227  		{
   228  			desc:        "ipv6 true",
   229  			ipv6Capable: true,
   230  			wantBootstrapConfig: bootstrapConfig(t, bootstrap.ConfigOptionsForTesting{
   231  				Servers: []byte(`[{
   232  					"server_uri": "dns:///directpath-pa.googleapis.com",
   233  					"channel_creds": [{"type": "google_default"}],
   234  					"server_features": ["ignore_resource_deletion"]
   235    				}]`),
   236  				Authorities: map[string]json.RawMessage{
   237  					"traffic-director-c2p.xds.googleapis.com": []byte(`{
   238  							"xds_servers": [
   239    								{
   240  								    "server_uri": "dns:///directpath-pa.googleapis.com",
   241  								    "channel_creds": [{"type": "google_default"}],
   242  								    "server_features": ["ignore_resource_deletion"]
   243    								}
   244  							]
   245  						}`),
   246  				},
   247  				Node: []byte(`{
   248  					  "id": "C2P-666",
   249  					  "locality": {"zone": "test-zone"},
   250  			  			"metadata": {
   251  							"TRAFFICDIRECTOR_DIRECTPATH_C2P_IPV6_CAPABLE": true
   252  			  			}
   253  					}`),
   254  			}),
   255  		},
   256  		{
   257  			desc:          "override TD URI",
   258  			ipv6Capable:   true,
   259  			tdURIOverride: "test-uri",
   260  			wantBootstrapConfig: bootstrapConfig(t, bootstrap.ConfigOptionsForTesting{
   261  				Servers: []byte(`[{
   262  					"server_uri": "test-uri",
   263  					"channel_creds": [{"type": "google_default"}],
   264  					"server_features": ["ignore_resource_deletion"]
   265    				}]`),
   266  				Authorities: map[string]json.RawMessage{
   267  					"traffic-director-c2p.xds.googleapis.com": []byte(`{
   268  							"xds_servers": [
   269    								{
   270  								    "server_uri": "test-uri",
   271  								    "channel_creds": [{"type": "google_default"}],
   272  								    "server_features": ["ignore_resource_deletion"]
   273    								}
   274  							]
   275  						}`),
   276  				},
   277  				Node: []byte(`{
   278  					  "id": "C2P-666",
   279  					  "locality": {"zone": "test-zone"},
   280  			  			"metadata": {
   281  							"TRAFFICDIRECTOR_DIRECTPATH_C2P_IPV6_CAPABLE": true
   282  			  			}
   283  					}`),
   284  			}),
   285  		},
   286  	} {
   287  		t.Run(tt.desc, func(t *testing.T) {
   288  			// Override IPv6 capability returned by the metadata server.
   289  			oldGetIPv6Capability := getIPv6Capable
   290  			getIPv6Capable = func(time.Duration) bool { return tt.ipv6Capable }
   291  			defer func() { getIPv6Capable = oldGetIPv6Capability }()
   292  
   293  			// Override TD URI test only env var.
   294  			if tt.tdURIOverride != "" {
   295  				oldURI := envconfig.C2PResolverTestOnlyTrafficDirectorURI
   296  				envconfig.C2PResolverTestOnlyTrafficDirectorURI = tt.tdURIOverride
   297  				defer func() { envconfig.C2PResolverTestOnlyTrafficDirectorURI = oldURI }()
   298  			}
   299  
   300  			// Override xDS client pool.
   301  			oldXdsClientPool := xdsClientPool
   302  			xdsClientPool = xdsclient.NewPool(nil)
   303  			defer func() { xdsClientPool = oldXdsClientPool }()
   304  
   305  			getIPv6Capable = func(time.Duration) bool { return tt.ipv6Capable }
   306  			defer func() { getIPv6Capable = oldGetIPv6Capability }()
   307  
   308  			// Build the google-c2p resolver.
   309  			r, err := builder.Build(resolver.Target{}, nil, resolver.BuildOptions{})
   310  			if err != nil {
   311  				t.Fatalf("failed to build resolver: %v", err)
   312  			}
   313  			defer r.Close()
   314  
   315  			// Build should return xDS, not DNS.
   316  			if r != testXDSResolver {
   317  				t.Fatalf("Build() returned %#v, want xds resolver", r)
   318  			}
   319  
   320  			gotConfig := xdsClientPool.BootstrapConfigForTesting()
   321  			if gotConfig == nil {
   322  				t.Fatalf("Failed to get bootstrap config: %v", err)
   323  			}
   324  			if diff := cmp.Diff(tt.wantBootstrapConfig, gotConfig); diff != "" {
   325  				t.Fatalf("Unexpected diff in bootstrap config (-want +got):\n%s", diff)
   326  			}
   327  		})
   328  	}
   329  }
   330  
   331  // TestDialFailsWhenTargetContainsAuthority attempts to Dial a target URI of
   332  // google-c2p scheme with a non-empty authority and verifies that it fails with
   333  // an expected error.
   334  func (s) TestBuildFailsWhenCalledWithAuthority(t *testing.T) {
   335  	useCleanUniverseDomain(t)
   336  	uri := "google-c2p://an-authority/resource"
   337  	cc, err := grpc.NewClient(uri, grpc.WithTransportCredentials(insecure.NewCredentials()))
   338  	if err != nil {
   339  		t.Fatalf("failed to create a client for server: %v", err)
   340  	}
   341  	defer func() {
   342  		if cc != nil {
   343  			cc.Close()
   344  		}
   345  	}()
   346  	ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout)
   347  	defer cancel()
   348  	client := testgrpc.NewTestServiceClient(cc)
   349  	_, err = client.EmptyCall(ctx, &testpb.Empty{})
   350  	wantErr := "google-c2p URI scheme does not support authorities"
   351  	if err == nil || !strings.Contains(err.Error(), wantErr) {
   352  		t.Fatalf("client.EmptyCall(%s) returned error: %v, want: %v", uri, err, wantErr)
   353  	}
   354  }
   355  
   356  func (s) TestSetUniverseDomainNonDefault(t *testing.T) {
   357  	replaceResolvers(t)
   358  	simulateRunningOnGCE(t, true)
   359  	useCleanUniverseDomain(t)
   360  	builder := resolver.Get(c2pScheme)
   361  
   362  	// Override the zone returned by the metadata server.
   363  	oldGetZone := getZone
   364  	getZone = func(time.Duration) string { return "test-zone" }
   365  	defer func() { getZone = oldGetZone }()
   366  
   367  	// Override IPv6 capability returned by the metadata server.
   368  	oldGetIPv6Capability := getIPv6Capable
   369  	getIPv6Capable = func(time.Duration) bool { return false }
   370  	defer func() { getIPv6Capable = oldGetIPv6Capability }()
   371  
   372  	// Override the random func used in the node ID.
   373  	origRandInd := randInt
   374  	randInt = func() int { return 666 }
   375  	defer func() { randInt = origRandInd }()
   376  
   377  	// Set the universe domain
   378  	testUniverseDomain := "test-universe-domain.test"
   379  	if err := SetUniverseDomain(testUniverseDomain); err != nil {
   380  		t.Fatalf("SetUniverseDomain(%s) failed: %v", testUniverseDomain, err)
   381  	}
   382  
   383  	// Now set universe domain to something different, it should fail
   384  	domain := "test-universe-domain-2.test"
   385  	err := SetUniverseDomain(domain)
   386  	wantErr := "already set"
   387  	if err == nil || !strings.Contains(err.Error(), wantErr) {
   388  		t.Fatalf("googlec2p.SetUniverseDomain(%s) returned error: %v, want: %v", domain, err, wantErr)
   389  	}
   390  
   391  	// Now explicitly set universe domain to the default, it should also fail
   392  	domain = "googleapis.com"
   393  	err = SetUniverseDomain(domain)
   394  	wantErr = "already set"
   395  	if err == nil || !strings.Contains(err.Error(), wantErr) {
   396  		t.Fatalf("googlec2p.SetUniverseDomain(%s) returned error: %v, want: %v", domain, err, wantErr)
   397  	}
   398  
   399  	// Now set universe domain to the original value, it should work
   400  	if err := SetUniverseDomain(testUniverseDomain); err != nil {
   401  		t.Fatalf("googlec2p.SetUniverseDomain(%s) failed: %v", testUniverseDomain, err)
   402  	}
   403  
   404  	// Override xDS client pool.
   405  	oldXdsClientPool := xdsClientPool
   406  	xdsClientPool = xdsclient.NewPool(nil)
   407  	defer func() { xdsClientPool = oldXdsClientPool }()
   408  
   409  	// Build the google-c2p resolver.
   410  	r, err := builder.Build(resolver.Target{}, nil, resolver.BuildOptions{})
   411  	if err != nil {
   412  		t.Fatalf("failed to build resolver: %v", err)
   413  	}
   414  	defer r.Close()
   415  
   416  	// Build should return xDS, not DNS.
   417  	if r != testXDSResolver {
   418  		t.Fatalf("Build() returned %#v, want xds resolver", r)
   419  	}
   420  
   421  	gotConfig := xdsClientPool.BootstrapConfigForTesting()
   422  	if gotConfig == nil {
   423  		t.Fatalf("Failed to get bootstrap config: %v", err)
   424  	}
   425  
   426  	// Check that we use directpath-pa.test-universe-domain.test in the
   427  	// bootstrap config.
   428  	wantBootstrapConfig := bootstrapConfig(t, bootstrap.ConfigOptionsForTesting{
   429  		Servers: []byte(`[{
   430  					"server_uri": "dns:///directpath-pa.test-universe-domain.test",
   431  					"channel_creds": [{"type": "google_default"}],
   432  					"server_features": ["ignore_resource_deletion"]
   433    				}]`),
   434  		Authorities: map[string]json.RawMessage{
   435  			"traffic-director-c2p.xds.googleapis.com": []byte(`{
   436  							"xds_servers": [
   437    								{
   438  								    "server_uri": "dns:///directpath-pa.test-universe-domain.test",
   439  								    "channel_creds": [{"type": "google_default"}],
   440  								    "server_features": ["ignore_resource_deletion"]
   441    								}
   442  							]
   443  						}`),
   444  		},
   445  		Node: []byte(`{
   446  					  "id": "C2P-666",
   447  					  "locality": {"zone": "test-zone"}
   448  					}`),
   449  	})
   450  	if diff := cmp.Diff(wantBootstrapConfig, gotConfig); diff != "" {
   451  		t.Fatalf("Unexpected diff in bootstrap config (-want +got):\n%s", diff)
   452  	}
   453  }
   454  
   455  func (s) TestDefaultUniverseDomain(t *testing.T) {
   456  	replaceResolvers(t)
   457  	simulateRunningOnGCE(t, true)
   458  	useCleanUniverseDomain(t)
   459  	builder := resolver.Get(c2pScheme)
   460  
   461  	// Override the zone returned by the metadata server.
   462  	oldGetZone := getZone
   463  	getZone = func(time.Duration) string { return "test-zone" }
   464  	defer func() { getZone = oldGetZone }()
   465  
   466  	// Override IPv6 capability returned by the metadata server.
   467  	oldGetIPv6Capability := getIPv6Capable
   468  	getIPv6Capable = func(time.Duration) bool { return false }
   469  	defer func() { getIPv6Capable = oldGetIPv6Capability }()
   470  
   471  	// Override the random func used in the node ID.
   472  	origRandInd := randInt
   473  	randInt = func() int { return 666 }
   474  	defer func() { randInt = origRandInd }()
   475  
   476  	// Override xDS client pool.
   477  	oldXdsClientPool := xdsClientPool
   478  	xdsClientPool = xdsclient.NewPool(nil)
   479  	defer func() { xdsClientPool = oldXdsClientPool }()
   480  
   481  	// Build the google-c2p resolver.
   482  	r, err := builder.Build(resolver.Target{}, nil, resolver.BuildOptions{})
   483  	if err != nil {
   484  		t.Fatalf("failed to build resolver: %v", err)
   485  	}
   486  	defer r.Close()
   487  
   488  	// Build should return xDS, not DNS.
   489  	if r != testXDSResolver {
   490  		t.Fatalf("Build() returned %#v, want xds resolver", r)
   491  	}
   492  
   493  	gotConfig := xdsClientPool.BootstrapConfigForTesting()
   494  	if gotConfig == nil {
   495  		t.Fatalf("Failed to get bootstrap config: %v", err)
   496  	}
   497  
   498  	// Check that we use directpath-pa.googleapis.com in the bootstrap config
   499  	wantBootstrapConfig := bootstrapConfig(t, bootstrap.ConfigOptionsForTesting{
   500  		Servers: []byte(`[{
   501  					"server_uri": "dns:///directpath-pa.googleapis.com",
   502  					"channel_creds": [{"type": "google_default"}],
   503  					"server_features": ["ignore_resource_deletion"]
   504    				}]`),
   505  		Authorities: map[string]json.RawMessage{
   506  			"traffic-director-c2p.xds.googleapis.com": []byte(`{
   507  							"xds_servers": [
   508    								{
   509  								    "server_uri": "dns:///directpath-pa.googleapis.com",
   510  								    "channel_creds": [{"type": "google_default"}],
   511  								    "server_features": ["ignore_resource_deletion"]
   512    								}
   513  							]
   514  						}`),
   515  		},
   516  		Node: []byte(`{
   517  					  "id": "C2P-666",
   518  					  "locality": {"zone": "test-zone"}
   519  					}`),
   520  	})
   521  	if diff := cmp.Diff(wantBootstrapConfig, gotConfig); diff != "" {
   522  		t.Fatalf("Unexpected diff in bootstrap config (-want +got):\n%s", diff)
   523  	}
   524  
   525  	// Now set universe domain to something different than the default, it should fail
   526  	domain := "test-universe-domain.test"
   527  	err = SetUniverseDomain(domain)
   528  	wantErr := "already set"
   529  	if err == nil || !strings.Contains(err.Error(), wantErr) {
   530  		t.Fatalf("googlec2p.SetUniverseDomain(%s) returned error: %v, want: %v", domain, err, wantErr)
   531  	}
   532  
   533  	// Now explicitly set universe domain to the default, it should work
   534  	domain = "googleapis.com"
   535  	if err := SetUniverseDomain(domain); err != nil {
   536  		t.Fatalf("googlec2p.SetUniverseDomain(%s) failed: %v", domain, err)
   537  	}
   538  }
   539  
   540  func (s) TestSetUniverseDomainEmptyString(t *testing.T) {
   541  	replaceResolvers(t)
   542  	simulateRunningOnGCE(t, true)
   543  	useCleanUniverseDomain(t)
   544  	wantErr := "cannot be empty"
   545  	err := SetUniverseDomain("")
   546  	if err == nil || !strings.Contains(err.Error(), wantErr) {
   547  		t.Fatalf("googlec2p.SetUniverseDomain(\"\") returned error: %v, want: %v", err, wantErr)
   548  	}
   549  }