...

Source file src/google.golang.org/grpc/xds/googledirectpath/googlec2p_test.go

Documentation: google.golang.org/grpc/xds/googledirectpath

     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  	"fmt"
    23  	"strconv"
    24  	"strings"
    25  	"testing"
    26  	"time"
    27  
    28  	"github.com/google/go-cmp/cmp"
    29  	"github.com/google/go-cmp/cmp/cmpopts"
    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/xds/bootstrap"
    34  	"google.golang.org/grpc/resolver"
    35  	"google.golang.org/grpc/xds/internal/xdsclient"
    36  	"google.golang.org/protobuf/testing/protocmp"
    37  	"google.golang.org/protobuf/types/known/structpb"
    38  
    39  	v3corepb "github.com/envoyproxy/go-control-plane/envoy/config/core/v3"
    40  )
    41  
    42  type emptyResolver struct {
    43  	resolver.Resolver
    44  	scheme string
    45  }
    46  
    47  func (er *emptyResolver) Build(_ resolver.Target, _ resolver.ClientConn, _ resolver.BuildOptions) (resolver.Resolver, error) {
    48  	return er, nil
    49  }
    50  
    51  func (er *emptyResolver) Scheme() string {
    52  	return er.scheme
    53  }
    54  
    55  func (er *emptyResolver) Close() {}
    56  
    57  var (
    58  	testDNSResolver = &emptyResolver{scheme: "dns"}
    59  	testXDSResolver = &emptyResolver{scheme: "xds"}
    60  )
    61  
    62  func replaceResolvers() func() {
    63  	oldDNS := resolver.Get("dns")
    64  	resolver.Register(testDNSResolver)
    65  	oldXDS := resolver.Get("xds")
    66  	resolver.Register(testXDSResolver)
    67  	return func() {
    68  		resolver.Register(oldDNS)
    69  		resolver.Register(oldXDS)
    70  	}
    71  }
    72  
    73  type testXDSClient struct {
    74  	xdsclient.XDSClient
    75  	closed chan struct{}
    76  }
    77  
    78  func (c *testXDSClient) Close() {
    79  	c.closed <- struct{}{}
    80  }
    81  
    82  // Test that when bootstrap env is set and we're running on GCE, don't fallback to DNS (because
    83  // federation is enabled by default).
    84  func TestBuildWithBootstrapEnvSet(t *testing.T) {
    85  	defer replaceResolvers()()
    86  	builder := resolver.Get(c2pScheme)
    87  
    88  	// make the test behave the ~same whether it's running on or off GCE
    89  	oldOnGCE := onGCE
    90  	onGCE = func() bool { return true }
    91  	defer func() { onGCE = oldOnGCE }()
    92  
    93  	// don't actually read the bootstrap file contents
    94  	xdsClient := &testXDSClient{closed: make(chan struct{}, 1)}
    95  	oldNewClient := newClientWithConfig
    96  	newClientWithConfig = func(config *bootstrap.Config) (xdsclient.XDSClient, func(), error) {
    97  		return xdsClient, func() { xdsClient.Close() }, nil
    98  	}
    99  	defer func() { newClientWithConfig = oldNewClient }()
   100  
   101  	for i, envP := range []*string{&envconfig.XDSBootstrapFileName, &envconfig.XDSBootstrapFileContent} {
   102  		t.Run(strconv.Itoa(i), func(t *testing.T) {
   103  			// Set bootstrap config env var.
   104  			oldEnv := *envP
   105  			*envP = "does not matter"
   106  			defer func() { *envP = oldEnv }()
   107  
   108  			// Build should return xDS, not DNS.
   109  			r, err := builder.Build(resolver.Target{}, nil, resolver.BuildOptions{})
   110  			if err != nil {
   111  				t.Fatalf("failed to build resolver: %v", err)
   112  			}
   113  			rr := r.(*c2pResolver)
   114  			if rrr := rr.Resolver; rrr != testXDSResolver {
   115  				t.Fatalf("want xds resolver, got %#v", rrr)
   116  			}
   117  		})
   118  	}
   119  }
   120  
   121  // Test that when not on GCE, fallback to DNS.
   122  func TestBuildNotOnGCE(t *testing.T) {
   123  	defer replaceResolvers()()
   124  	builder := resolver.Get(c2pScheme)
   125  
   126  	oldOnGCE := onGCE
   127  	onGCE = func() bool { return false }
   128  	defer func() { onGCE = oldOnGCE }()
   129  
   130  	// Build should return DNS, not xDS.
   131  	r, err := builder.Build(resolver.Target{}, nil, resolver.BuildOptions{})
   132  	if err != nil {
   133  		t.Fatalf("failed to build resolver: %v", err)
   134  	}
   135  	if r != testDNSResolver {
   136  		t.Fatalf("want dns resolver, got %#v", r)
   137  	}
   138  }
   139  
   140  // Test that when xDS is built, the client is built with the correct config.
   141  func TestBuildXDS(t *testing.T) {
   142  	defer replaceResolvers()()
   143  	builder := resolver.Get(c2pScheme)
   144  
   145  	oldOnGCE := onGCE
   146  	onGCE = func() bool { return true }
   147  	defer func() { onGCE = oldOnGCE }()
   148  
   149  	const testZone = "test-zone"
   150  	oldGetZone := getZone
   151  	getZone = func(time.Duration) string { return testZone }
   152  	defer func() { getZone = oldGetZone }()
   153  
   154  	for _, tt := range []struct {
   155  		name  string
   156  		ipv6  bool
   157  		tdURI string // traffic director URI will be overridden if this is set.
   158  	}{
   159  		{name: "ipv6 true", ipv6: true},
   160  		{name: "ipv6 false", ipv6: false},
   161  		{name: "override TD URI", ipv6: true, tdURI: "test-uri"},
   162  	} {
   163  		t.Run(tt.name, func(t *testing.T) {
   164  			oldGetIPv6Capability := getIPv6Capable
   165  			getIPv6Capable = func(time.Duration) bool { return tt.ipv6 }
   166  			defer func() { getIPv6Capable = oldGetIPv6Capability }()
   167  
   168  			if tt.tdURI != "" {
   169  				oldURI := envconfig.C2PResolverTestOnlyTrafficDirectorURI
   170  				envconfig.C2PResolverTestOnlyTrafficDirectorURI = tt.tdURI
   171  				defer func() {
   172  					envconfig.C2PResolverTestOnlyTrafficDirectorURI = oldURI
   173  				}()
   174  			}
   175  
   176  			tXDSClient := &testXDSClient{closed: make(chan struct{}, 1)}
   177  
   178  			configCh := make(chan *bootstrap.Config, 1)
   179  			oldNewClient := newClientWithConfig
   180  			newClientWithConfig = func(config *bootstrap.Config) (xdsclient.XDSClient, func(), error) {
   181  				configCh <- config
   182  				return tXDSClient, func() { tXDSClient.Close() }, nil
   183  			}
   184  			defer func() { newClientWithConfig = oldNewClient }()
   185  
   186  			// Build should return DNS, not xDS.
   187  			r, err := builder.Build(resolver.Target{}, nil, resolver.BuildOptions{})
   188  			if err != nil {
   189  				t.Fatalf("failed to build resolver: %v", err)
   190  			}
   191  			rr := r.(*c2pResolver)
   192  			if rrr := rr.Resolver; rrr != testXDSResolver {
   193  				t.Fatalf("want xds resolver, got %#v, ", rrr)
   194  			}
   195  
   196  			wantNode := &v3corepb.Node{
   197  				Id:                   id,
   198  				Metadata:             nil,
   199  				Locality:             &v3corepb.Locality{Zone: testZone},
   200  				UserAgentName:        gRPCUserAgentName,
   201  				UserAgentVersionType: &v3corepb.Node_UserAgentVersion{UserAgentVersion: grpc.Version},
   202  				ClientFeatures:       []string{clientFeatureNoOverprovisioning, clientFeatureResourceWrapper},
   203  			}
   204  			if tt.ipv6 {
   205  				wantNode.Metadata = &structpb.Struct{
   206  					Fields: map[string]*structpb.Value{
   207  						ipv6CapableMetadataName: {
   208  							Kind: &structpb.Value_BoolValue{BoolValue: true},
   209  						},
   210  					},
   211  				}
   212  			}
   213  			wantServerConfig, err := bootstrap.ServerConfigFromJSON([]byte(fmt.Sprintf(`{
   214  				"server_uri": "%s",
   215  				"channel_creds": [{"type": "google_default"}],
   216  				"server_features": ["xds_v3", "ignore_resource_deletion", "xds.config.resource-in-sotw"]
   217  			}`, tdURL)))
   218  			if err != nil {
   219  				t.Fatalf("Failed to build server bootstrap config: %v", err)
   220  			}
   221  			wantConfig := &bootstrap.Config{
   222  				XDSServer: wantServerConfig,
   223  				ClientDefaultListenerResourceNameTemplate: "%s",
   224  				Authorities: map[string]*bootstrap.Authority{
   225  					"traffic-director-c2p.xds.googleapis.com": {
   226  						XDSServer:                          wantServerConfig,
   227  						ClientListenerResourceNameTemplate: "xdstp://traffic-director-c2p.xds.googleapis.com/envoy.config.listener.v3.Listener/%s",
   228  					},
   229  				},
   230  				NodeProto: wantNode,
   231  			}
   232  			if tt.tdURI != "" {
   233  				wantConfig.XDSServer.ServerURI = tt.tdURI
   234  			}
   235  			cmpOpts := cmp.Options{
   236  				cmpopts.IgnoreFields(bootstrap.ServerConfig{}, "Creds"),
   237  				cmp.AllowUnexported(bootstrap.ServerConfig{}),
   238  				protocmp.Transform(),
   239  			}
   240  			select {
   241  			case gotConfig := <-configCh:
   242  				if diff := cmp.Diff(wantConfig, gotConfig, cmpOpts); diff != "" {
   243  					t.Fatalf("Unexpected diff in bootstrap config (-want +got):\n%s", diff)
   244  				}
   245  			case <-time.After(time.Second):
   246  				t.Fatalf("timeout waiting for client config")
   247  			}
   248  
   249  			r.Close()
   250  			select {
   251  			case <-tXDSClient.closed:
   252  			case <-time.After(time.Second):
   253  				t.Fatalf("timeout waiting for client close")
   254  			}
   255  		})
   256  	}
   257  }
   258  
   259  // TestDialFailsWhenTargetContainsAuthority attempts to Dial a target URI of
   260  // google-c2p scheme with a non-empty authority and verifies that it fails with
   261  // an expected error.
   262  func TestBuildFailsWhenCalledWithAuthority(t *testing.T) {
   263  	uri := "google-c2p://an-authority/resource"
   264  	cc, err := grpc.Dial(uri, grpc.WithTransportCredentials(insecure.NewCredentials()))
   265  	defer func() {
   266  		if cc != nil {
   267  			cc.Close()
   268  		}
   269  	}()
   270  	wantErr := "google-c2p URI scheme does not support authorities"
   271  	if err == nil || !strings.Contains(err.Error(), wantErr) {
   272  		t.Fatalf("grpc.Dial(%s) returned error: %v, want: %v", uri, err, wantErr)
   273  	}
   274  }
   275  

View as plain text