123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218 |
- /*
- *
- * Copyright 2021 gRPC authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- */
- // Package googledirectpath implements a resolver that configures xds to make
- // cloud to prod directpath connection.
- //
- // It's a combo of DNS and xDS resolvers. It delegates to DNS if
- // - not on GCE, or
- // - xDS bootstrap env var is set (so this client needs to do normal xDS, not
- // direct path, and clients with this scheme is not part of the xDS mesh).
- package googledirectpath
- import (
- "fmt"
- "net/url"
- "time"
- "google.golang.org/grpc"
- "google.golang.org/grpc/grpclog"
- "google.golang.org/grpc/internal/envconfig"
- "google.golang.org/grpc/internal/googlecloud"
- internalgrpclog "google.golang.org/grpc/internal/grpclog"
- "google.golang.org/grpc/internal/grpcrand"
- "google.golang.org/grpc/resolver"
- "google.golang.org/grpc/xds/internal/xdsclient"
- "google.golang.org/grpc/xds/internal/xdsclient/bootstrap"
- "google.golang.org/protobuf/types/known/structpb"
- v3corepb "github.com/envoyproxy/go-control-plane/envoy/config/core/v3"
- _ "google.golang.org/grpc/xds" // To register xds resolvers and balancers.
- )
- const (
- c2pScheme = "google-c2p"
- c2pExperimentalScheme = "google-c2p-experimental"
- c2pAuthority = "traffic-director-c2p.xds.googleapis.com"
- tdURL = "dns:///directpath-pa.googleapis.com"
- httpReqTimeout = 10 * time.Second
- zoneURL = "http://metadata.google.internal/computeMetadata/v1/instance/zone"
- ipv6URL = "http://metadata.google.internal/computeMetadata/v1/instance/network-interfaces/0/ipv6s"
- gRPCUserAgentName = "gRPC Go"
- clientFeatureNoOverprovisioning = "envoy.lb.does_not_support_overprovisioning"
- ipv6CapableMetadataName = "TRAFFICDIRECTOR_DIRECTPATH_C2P_IPV6_CAPABLE"
- logPrefix = "[google-c2p-resolver]"
- dnsName, xdsName = "dns", "xds"
- )
- // For overriding in unittests.
- var (
- onGCE = googlecloud.OnGCE
- newClientWithConfig = func(config *bootstrap.Config) (xdsclient.XDSClient, func(), error) {
- return xdsclient.NewWithConfig(config)
- }
- logger = internalgrpclog.NewPrefixLogger(grpclog.Component("directpath"), logPrefix)
- )
- func init() {
- resolver.Register(c2pResolverBuilder{
- scheme: c2pScheme,
- })
- // TODO(apolcyn): remove this experimental scheme before the 1.52 release
- resolver.Register(c2pResolverBuilder{
- scheme: c2pExperimentalScheme,
- })
- }
- type c2pResolverBuilder struct {
- scheme string
- }
- func (c2pResolverBuilder) Build(t resolver.Target, cc resolver.ClientConn, opts resolver.BuildOptions) (resolver.Resolver, error) {
- if t.URL.Host != "" {
- return nil, fmt.Errorf("google-c2p URI scheme does not support authorities")
- }
- if !runDirectPath() {
- // If not xDS, fallback to DNS.
- t.Scheme = dnsName
- t.URL.Scheme = dnsName
- return resolver.Get(dnsName).Build(t, cc, opts)
- }
- // Note that the following calls to getZone() and getIPv6Capable() does I/O,
- // and has 10 seconds timeout each.
- //
- // This should be fine in most of the cases. In certain error cases, this
- // could block Dial() for up to 10 seconds (each blocking call has its own
- // goroutine).
- zoneCh, ipv6CapableCh := make(chan string), make(chan bool)
- go func() { zoneCh <- getZone(httpReqTimeout) }()
- go func() { ipv6CapableCh <- getIPv6Capable(httpReqTimeout) }()
- balancerName := envconfig.C2PResolverTestOnlyTrafficDirectorURI
- if balancerName == "" {
- balancerName = tdURL
- }
- serverConfig, err := bootstrap.ServerConfigFromJSON([]byte(fmt.Sprintf(`
- {
- "server_uri": "%s",
- "channel_creds": [{"type": "google_default"}],
- "server_features": ["xds_v3", "ignore_resource_deletion"]
- }`, balancerName)))
- if err != nil {
- return nil, fmt.Errorf("failed to build bootstrap configuration: %v", err)
- }
- config := &bootstrap.Config{
- XDSServer: serverConfig,
- ClientDefaultListenerResourceNameTemplate: "%s",
- Authorities: map[string]*bootstrap.Authority{
- c2pAuthority: {
- XDSServer: serverConfig,
- },
- },
- NodeProto: newNode(<-zoneCh, <-ipv6CapableCh),
- }
- // Create singleton xds client with this config. The xds client will be
- // used by the xds resolver later.
- _, close, err := newClientWithConfig(config)
- if err != nil {
- return nil, fmt.Errorf("failed to start xDS client: %v", err)
- }
- // Create and return an xDS resolver.
- t.Scheme = xdsName
- t.URL.Scheme = xdsName
- if envconfig.XDSFederation {
- t = resolver.Target{
- URL: url.URL{
- Scheme: xdsName,
- Host: c2pAuthority,
- Path: t.URL.Path,
- },
- }
- }
- xdsR, err := resolver.Get(xdsName).Build(t, cc, opts)
- if err != nil {
- close()
- return nil, err
- }
- return &c2pResolver{
- Resolver: xdsR,
- clientCloseFunc: close,
- }, nil
- }
- func (b c2pResolverBuilder) Scheme() string {
- return b.scheme
- }
- type c2pResolver struct {
- resolver.Resolver
- clientCloseFunc func()
- }
- func (r *c2pResolver) Close() {
- r.Resolver.Close()
- r.clientCloseFunc()
- }
- var ipv6EnabledMetadata = &structpb.Struct{
- Fields: map[string]*structpb.Value{
- ipv6CapableMetadataName: structpb.NewBoolValue(true),
- },
- }
- var id = fmt.Sprintf("C2P-%d", grpcrand.Int())
- // newNode makes a copy of defaultNode, and populate it's Metadata and
- // Locality fields.
- func newNode(zone string, ipv6Capable bool) *v3corepb.Node {
- ret := &v3corepb.Node{
- // Not all required fields are set in defaultNote. Metadata will be set
- // if ipv6 is enabled. Locality will be set to the value from metadata.
- Id: id,
- UserAgentName: gRPCUserAgentName,
- UserAgentVersionType: &v3corepb.Node_UserAgentVersion{UserAgentVersion: grpc.Version},
- ClientFeatures: []string{clientFeatureNoOverprovisioning},
- }
- ret.Locality = &v3corepb.Locality{Zone: zone}
- if ipv6Capable {
- ret.Metadata = ipv6EnabledMetadata
- }
- return ret
- }
- // runDirectPath returns whether this resolver should use direct path.
- //
- // direct path is enabled if this client is running on GCE, and the normal xDS
- // is not used (bootstrap env vars are not set) or federation is enabled.
- func runDirectPath() bool {
- if !onGCE() {
- return false
- }
- return envconfig.XDSFederation || envconfig.XDSBootstrapFileName == "" && envconfig.XDSBootstrapFileContent == ""
- }
|