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 }