googlec2p_test.go 7.9 KB

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