google.golang.org/grpc@v1.62.1/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 "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/resolver" 34 "google.golang.org/grpc/xds/internal/xdsclient" 35 "google.golang.org/grpc/xds/internal/xdsclient/bootstrap" 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}, 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"] 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 }, 228 }, 229 NodeProto: wantNode, 230 } 231 if tt.tdURI != "" { 232 wantConfig.XDSServer.ServerURI = tt.tdURI 233 } 234 cmpOpts := cmp.Options{ 235 cmpopts.IgnoreFields(bootstrap.ServerConfig{}, "Creds"), 236 cmp.AllowUnexported(bootstrap.ServerConfig{}), 237 protocmp.Transform(), 238 } 239 select { 240 case gotConfig := <-configCh: 241 if diff := cmp.Diff(wantConfig, gotConfig, cmpOpts); diff != "" { 242 t.Fatalf("Unexpected diff in bootstrap config (-want +got):\n%s", diff) 243 } 244 case <-time.After(time.Second): 245 t.Fatalf("timeout waiting for client config") 246 } 247 248 r.Close() 249 select { 250 case <-tXDSClient.closed: 251 case <-time.After(time.Second): 252 t.Fatalf("timeout waiting for client close") 253 } 254 }) 255 } 256 } 257 258 // TestDialFailsWhenTargetContainsAuthority attempts to Dial a target URI of 259 // google-c2p scheme with a non-empty authority and verifies that it fails with 260 // an expected error. 261 func TestBuildFailsWhenCalledWithAuthority(t *testing.T) { 262 uri := "google-c2p://an-authority/resource" 263 cc, err := grpc.Dial(uri, grpc.WithTransportCredentials(insecure.NewCredentials())) 264 defer func() { 265 if cc != nil { 266 cc.Close() 267 } 268 }() 269 wantErr := "google-c2p URI scheme does not support authorities" 270 if err == nil || !strings.Contains(err.Error(), wantErr) { 271 t.Fatalf("grpc.Dial(%s) returned error: %v, want: %v", uri, err, wantErr) 272 } 273 }