blob: d759d25c851913a06e6ab16802f11837a3250c57 [file] [log] [blame]
/*
*
* 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"
"time"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/google"
"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" // To register xds resolvers and balancers.
"google.golang.org/grpc/xds/internal/xdsclient"
"google.golang.org/grpc/xds/internal/xdsclient/bootstrap"
"google.golang.org/grpc/xds/internal/xdsclient/xdsresource/version"
"google.golang.org/protobuf/types/known/structpb"
v3corepb "github.com/envoyproxy/go-control-plane/envoy/config/core/v3"
)
const (
c2pScheme = "google-c2p-experimental"
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, error) {
return xdsclient.NewWithConfig(config)
}
logger = internalgrpclog.NewPrefixLogger(grpclog.Component("directpath"), logPrefix)
)
func init() {
resolver.Register(c2pResolverBuilder{})
}
type c2pResolverBuilder struct{}
func (c2pResolverBuilder) Build(t resolver.Target, cc resolver.ClientConn, opts resolver.BuildOptions) (resolver.Resolver, error) {
if !runDirectPath() {
// If not xDS, fallback to DNS.
t.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
}
config := &bootstrap.Config{
XDSServer: &bootstrap.ServerConfig{
ServerURI: balancerName,
Creds: grpc.WithCredentialsBundle(google.NewDefaultCredentials()),
TransportAPI: version.TransportV3,
NodeProto: newNode(<-zoneCh, <-ipv6CapableCh),
},
ClientDefaultListenerResourceNameTemplate: "%s",
}
// Create singleton xds client with this config. The xds client will be
// used by the xds resolver later.
xdsC, 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
xdsR, err := resolver.Get(xdsName).Build(t, cc, opts)
if err != nil {
xdsC.Close()
return nil, err
}
return &c2pResolver{
Resolver: xdsR,
client: xdsC,
}, nil
}
func (c2pResolverBuilder) Scheme() string {
return c2pScheme
}
type c2pResolver struct {
resolver.Resolver
client xdsclient.XDSClient
}
func (r *c2pResolver) Close() {
r.Resolver.Close()
r.client.Close()
}
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).
func runDirectPath() bool {
return envconfig.XDSBootstrapFileName == "" && envconfig.XDSBootstrapFileContent == "" && onGCE()
}