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 }