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