googlec2p.go 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218
  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 implements a resolver that configures xds to make
  19. // cloud to prod directpath connection.
  20. //
  21. // It's a combo of DNS and xDS resolvers. It delegates to DNS if
  22. // - not on GCE, or
  23. // - xDS bootstrap env var is set (so this client needs to do normal xDS, not
  24. // direct path, and clients with this scheme is not part of the xDS mesh).
  25. package googledirectpath
  26. import (
  27. "fmt"
  28. "net/url"
  29. "time"
  30. "google.golang.org/grpc"
  31. "google.golang.org/grpc/grpclog"
  32. "google.golang.org/grpc/internal/envconfig"
  33. "google.golang.org/grpc/internal/googlecloud"
  34. internalgrpclog "google.golang.org/grpc/internal/grpclog"
  35. "google.golang.org/grpc/internal/grpcrand"
  36. "google.golang.org/grpc/resolver"
  37. "google.golang.org/grpc/xds/internal/xdsclient"
  38. "google.golang.org/grpc/xds/internal/xdsclient/bootstrap"
  39. "google.golang.org/protobuf/types/known/structpb"
  40. v3corepb "github.com/envoyproxy/go-control-plane/envoy/config/core/v3"
  41. _ "google.golang.org/grpc/xds" // To register xds resolvers and balancers.
  42. )
  43. const (
  44. c2pScheme = "google-c2p"
  45. c2pExperimentalScheme = "google-c2p-experimental"
  46. c2pAuthority = "traffic-director-c2p.xds.googleapis.com"
  47. tdURL = "dns:///directpath-pa.googleapis.com"
  48. httpReqTimeout = 10 * time.Second
  49. zoneURL = "http://metadata.google.internal/computeMetadata/v1/instance/zone"
  50. ipv6URL = "http://metadata.google.internal/computeMetadata/v1/instance/network-interfaces/0/ipv6s"
  51. gRPCUserAgentName = "gRPC Go"
  52. clientFeatureNoOverprovisioning = "envoy.lb.does_not_support_overprovisioning"
  53. ipv6CapableMetadataName = "TRAFFICDIRECTOR_DIRECTPATH_C2P_IPV6_CAPABLE"
  54. logPrefix = "[google-c2p-resolver]"
  55. dnsName, xdsName = "dns", "xds"
  56. )
  57. // For overriding in unittests.
  58. var (
  59. onGCE = googlecloud.OnGCE
  60. newClientWithConfig = func(config *bootstrap.Config) (xdsclient.XDSClient, func(), error) {
  61. return xdsclient.NewWithConfig(config)
  62. }
  63. logger = internalgrpclog.NewPrefixLogger(grpclog.Component("directpath"), logPrefix)
  64. )
  65. func init() {
  66. resolver.Register(c2pResolverBuilder{
  67. scheme: c2pScheme,
  68. })
  69. // TODO(apolcyn): remove this experimental scheme before the 1.52 release
  70. resolver.Register(c2pResolverBuilder{
  71. scheme: c2pExperimentalScheme,
  72. })
  73. }
  74. type c2pResolverBuilder struct {
  75. scheme string
  76. }
  77. func (c2pResolverBuilder) Build(t resolver.Target, cc resolver.ClientConn, opts resolver.BuildOptions) (resolver.Resolver, error) {
  78. if t.URL.Host != "" {
  79. return nil, fmt.Errorf("google-c2p URI scheme does not support authorities")
  80. }
  81. if !runDirectPath() {
  82. // If not xDS, fallback to DNS.
  83. t.Scheme = dnsName
  84. t.URL.Scheme = dnsName
  85. return resolver.Get(dnsName).Build(t, cc, opts)
  86. }
  87. // Note that the following calls to getZone() and getIPv6Capable() does I/O,
  88. // and has 10 seconds timeout each.
  89. //
  90. // This should be fine in most of the cases. In certain error cases, this
  91. // could block Dial() for up to 10 seconds (each blocking call has its own
  92. // goroutine).
  93. zoneCh, ipv6CapableCh := make(chan string), make(chan bool)
  94. go func() { zoneCh <- getZone(httpReqTimeout) }()
  95. go func() { ipv6CapableCh <- getIPv6Capable(httpReqTimeout) }()
  96. balancerName := envconfig.C2PResolverTestOnlyTrafficDirectorURI
  97. if balancerName == "" {
  98. balancerName = tdURL
  99. }
  100. serverConfig, err := bootstrap.ServerConfigFromJSON([]byte(fmt.Sprintf(`
  101. {
  102. "server_uri": "%s",
  103. "channel_creds": [{"type": "google_default"}],
  104. "server_features": ["xds_v3", "ignore_resource_deletion"]
  105. }`, balancerName)))
  106. if err != nil {
  107. return nil, fmt.Errorf("failed to build bootstrap configuration: %v", err)
  108. }
  109. config := &bootstrap.Config{
  110. XDSServer: serverConfig,
  111. ClientDefaultListenerResourceNameTemplate: "%s",
  112. Authorities: map[string]*bootstrap.Authority{
  113. c2pAuthority: {
  114. XDSServer: serverConfig,
  115. },
  116. },
  117. NodeProto: newNode(<-zoneCh, <-ipv6CapableCh),
  118. }
  119. // Create singleton xds client with this config. The xds client will be
  120. // used by the xds resolver later.
  121. _, close, err := newClientWithConfig(config)
  122. if err != nil {
  123. return nil, fmt.Errorf("failed to start xDS client: %v", err)
  124. }
  125. // Create and return an xDS resolver.
  126. t.Scheme = xdsName
  127. t.URL.Scheme = xdsName
  128. if envconfig.XDSFederation {
  129. t = resolver.Target{
  130. URL: url.URL{
  131. Scheme: xdsName,
  132. Host: c2pAuthority,
  133. Path: t.URL.Path,
  134. },
  135. }
  136. }
  137. xdsR, err := resolver.Get(xdsName).Build(t, cc, opts)
  138. if err != nil {
  139. close()
  140. return nil, err
  141. }
  142. return &c2pResolver{
  143. Resolver: xdsR,
  144. clientCloseFunc: close,
  145. }, nil
  146. }
  147. func (b c2pResolverBuilder) Scheme() string {
  148. return b.scheme
  149. }
  150. type c2pResolver struct {
  151. resolver.Resolver
  152. clientCloseFunc func()
  153. }
  154. func (r *c2pResolver) Close() {
  155. r.Resolver.Close()
  156. r.clientCloseFunc()
  157. }
  158. var ipv6EnabledMetadata = &structpb.Struct{
  159. Fields: map[string]*structpb.Value{
  160. ipv6CapableMetadataName: structpb.NewBoolValue(true),
  161. },
  162. }
  163. var id = fmt.Sprintf("C2P-%d", grpcrand.Int())
  164. // newNode makes a copy of defaultNode, and populate it's Metadata and
  165. // Locality fields.
  166. func newNode(zone string, ipv6Capable bool) *v3corepb.Node {
  167. ret := &v3corepb.Node{
  168. // Not all required fields are set in defaultNote. Metadata will be set
  169. // if ipv6 is enabled. Locality will be set to the value from metadata.
  170. Id: id,
  171. UserAgentName: gRPCUserAgentName,
  172. UserAgentVersionType: &v3corepb.Node_UserAgentVersion{UserAgentVersion: grpc.Version},
  173. ClientFeatures: []string{clientFeatureNoOverprovisioning},
  174. }
  175. ret.Locality = &v3corepb.Locality{Zone: zone}
  176. if ipv6Capable {
  177. ret.Metadata = ipv6EnabledMetadata
  178. }
  179. return ret
  180. }
  181. // runDirectPath returns whether this resolver should use direct path.
  182. //
  183. // direct path is enabled if this client is running on GCE, and the normal xDS
  184. // is not used (bootstrap env vars are not set) or federation is enabled.
  185. func runDirectPath() bool {
  186. if !onGCE() {
  187. return false
  188. }
  189. return envconfig.XDSFederation || envconfig.XDSBootstrapFileName == "" && envconfig.XDSBootstrapFileContent == ""
  190. }