snapshot of https://github.com/grpc/grpc-go.git (master, 2026-09-15; blobs > 131072B omitted)
Some checks failed
codecov / upload (push) Failing after 13m59s
CodeQL / Analyze (push) Failing after 1s
Testing / vet-proto (push) Failing after 1s
Testing / tests (arm, latest) (push) Waiting to run
Testing / extras (latest) (push) Failing after 1s
Testing / static checks (latest-1) (push) Failing after 1s
Testing / tests (-race, latest) (push) Failing after 1s
Testing / tests (i386, latest) (push) Failing after 1s
Testing / tests (latest) (push) Failing after 1s
Testing / tests (latest-1) (push) Failing after 1s
Lock Threads / lock (push) Failing after 1s
govulncheck / govulncheck (.) (push) Failing after 1s
govulncheck / govulncheck (./cmd/protoc-gen-go-grpc) (push) Failing after 1s
govulncheck / govulncheck (./gcp/observability) (push) Failing after 1s
govulncheck / govulncheck (./security/advancedtls) (push) Failing after 1s
govulncheck / govulncheck (./stats/opencensus) (push) Failing after 1s
Stale bot / stale (push) Failing after 1s
Some checks failed
codecov / upload (push) Failing after 13m59s
CodeQL / Analyze (push) Failing after 1s
Testing / vet-proto (push) Failing after 1s
Testing / tests (arm, latest) (push) Waiting to run
Testing / extras (latest) (push) Failing after 1s
Testing / static checks (latest-1) (push) Failing after 1s
Testing / tests (-race, latest) (push) Failing after 1s
Testing / tests (i386, latest) (push) Failing after 1s
Testing / tests (latest) (push) Failing after 1s
Testing / tests (latest-1) (push) Failing after 1s
Lock Threads / lock (push) Failing after 1s
govulncheck / govulncheck (.) (push) Failing after 1s
govulncheck / govulncheck (./cmd/protoc-gen-go-grpc) (push) Failing after 1s
govulncheck / govulncheck (./gcp/observability) (push) Failing after 1s
govulncheck / govulncheck (./security/advancedtls) (push) Failing after 1s
govulncheck / govulncheck (./stats/opencensus) (push) Failing after 1s
Stale bot / stale (push) Failing after 1s
This commit is contained in:
250
xds/googledirectpath/googlec2p.go
Normal file
250
xds/googledirectpath/googlec2p.go
Normal file
@@ -0,0 +1,250 @@
|
||||
/*
|
||||
*
|
||||
* 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 (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
rand "math/rand/v2"
|
||||
"net/url"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"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/xds/bootstrap"
|
||||
"google.golang.org/grpc/internal/xds/xdsclient"
|
||||
"google.golang.org/grpc/resolver"
|
||||
|
||||
_ "google.golang.org/grpc/xds" // To register xds resolvers and balancers.
|
||||
)
|
||||
|
||||
const (
|
||||
c2pScheme = "google-c2p"
|
||||
c2pAuthority = "traffic-director-c2p.xds.googleapis.com"
|
||||
|
||||
defaultUniverseDomain = "googleapis.com"
|
||||
zoneURL = "http://metadata.google.internal/computeMetadata/v1/instance/zone"
|
||||
ipv6URL = "http://metadata.google.internal/computeMetadata/v1/instance/network-interfaces/0/ipv6s"
|
||||
ipv6CapableMetadataName = "TRAFFICDIRECTOR_DIRECTPATH_C2P_IPV6_CAPABLE"
|
||||
httpReqTimeout = 10 * time.Second
|
||||
|
||||
logPrefix = "[google-c2p-resolver]"
|
||||
dnsName, xdsName = "dns", "xds"
|
||||
)
|
||||
|
||||
var (
|
||||
logger = internalgrpclog.NewPrefixLogger(grpclog.Component("directpath"), logPrefix)
|
||||
universeDomainMu sync.Mutex
|
||||
universeDomain = ""
|
||||
// For overriding in unittests.
|
||||
onGCE = googlecloud.OnGCE
|
||||
randInt = rand.Int
|
||||
xdsClientPool = xdsclient.DefaultPool
|
||||
)
|
||||
|
||||
func init() {
|
||||
resolver.Register(c2pResolverBuilder{})
|
||||
}
|
||||
|
||||
// SetUniverseDomain informs the gRPC library of the universe domain
|
||||
// in which the process is running (for example, "googleapis.com").
|
||||
// It is the caller's responsibility to ensure that the domain is correct.
|
||||
//
|
||||
// This setting is used by the "google-c2p" resolver (the resolver used
|
||||
// for URIs with the "google-c2p" scheme) to configure its dependencies.
|
||||
//
|
||||
// If a gRPC channel is created with the "google-c2p" URI scheme and this
|
||||
// function has NOT been called, then gRPC configures the universe domain as
|
||||
// "googleapis.com".
|
||||
//
|
||||
// Returns nil if either:
|
||||
//
|
||||
// a) The universe domain has not yet been configured.
|
||||
// b) The universe domain has been configured and matches the provided value.
|
||||
//
|
||||
// Otherwise, returns an error.
|
||||
func SetUniverseDomain(domain string) error {
|
||||
universeDomainMu.Lock()
|
||||
defer universeDomainMu.Unlock()
|
||||
if domain == "" {
|
||||
return fmt.Errorf("universe domain cannot be empty")
|
||||
}
|
||||
if universeDomain == "" {
|
||||
universeDomain = domain
|
||||
return nil
|
||||
}
|
||||
if universeDomain != domain {
|
||||
return fmt.Errorf("universe domain cannot be set to %s, already set to different value: %s", domain, universeDomain)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func getXdsServerURI() string {
|
||||
universeDomainMu.Lock()
|
||||
defer universeDomainMu.Unlock()
|
||||
if universeDomain == "" {
|
||||
universeDomain = defaultUniverseDomain
|
||||
}
|
||||
// Put env var override logic after default value logic so
|
||||
// that tests still run the default value logic.
|
||||
if envconfig.C2PResolverTestOnlyTrafficDirectorURI != "" {
|
||||
return envconfig.C2PResolverTestOnlyTrafficDirectorURI
|
||||
}
|
||||
return fmt.Sprintf("dns:///directpath-pa.%s", universeDomain)
|
||||
}
|
||||
|
||||
type c2pResolverWrapper struct {
|
||||
resolver.Resolver
|
||||
cancel func() // Release the reference to the xDS client that was created in Build().
|
||||
}
|
||||
|
||||
func (r *c2pResolverWrapper) Close() {
|
||||
r.Resolver.Close()
|
||||
r.cancel()
|
||||
}
|
||||
|
||||
type c2pResolverBuilder struct{}
|
||||
|
||||
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")
|
||||
}
|
||||
|
||||
isGCE := onGCE()
|
||||
// We check only for the presence of the "force-xds" query parameter key,
|
||||
// and do not evaluate its value.
|
||||
_, forceXDS := t.URL.Query()["force-xds"]
|
||||
if !forceXDS && !isGCE {
|
||||
// If not xDS, fallback to DNS.
|
||||
t.URL.Scheme = dnsName
|
||||
return resolver.Get(dnsName).Build(t, cc, opts)
|
||||
}
|
||||
|
||||
var zone string
|
||||
var ipv6Capable bool
|
||||
if forceXDS {
|
||||
// If the force-xds query parameter is present, GCE metadata server
|
||||
// queries are bypassed.
|
||||
zone = ""
|
||||
ipv6Capable = true
|
||||
} else {
|
||||
// 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).
|
||||
var wg sync.WaitGroup
|
||||
wg.Go(func() { zone = getZone(httpReqTimeout) })
|
||||
wg.Go(func() { ipv6Capable = getIPv6Capable(httpReqTimeout) })
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
xdsServerURI := getXdsServerURI()
|
||||
nodeCfg := newNodeConfig(zone, ipv6Capable, forceXDS)
|
||||
xdsServerCfg := newXdsServerConfig(xdsServerURI)
|
||||
authoritiesCfg := newAuthoritiesConfig(xdsServerCfg)
|
||||
|
||||
cfg := map[string]any{
|
||||
"xds_servers": []any{xdsServerCfg},
|
||||
"client_default_listener_resource_name_template": "%s",
|
||||
"authorities": authoritiesCfg,
|
||||
"node": nodeCfg,
|
||||
}
|
||||
cfgJSON, err := json.Marshal(cfg)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to marshal bootstrap configuration: %v", err)
|
||||
}
|
||||
config, err := bootstrap.NewConfigFromContents(cfgJSON)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse bootstrap contents: %s, %v", string(cfgJSON), err)
|
||||
}
|
||||
|
||||
t = resolver.Target{
|
||||
URL: url.URL{
|
||||
Scheme: xdsName,
|
||||
Host: c2pAuthority,
|
||||
Path: t.URL.Path,
|
||||
},
|
||||
}
|
||||
|
||||
// Create a new xDS client for this target using the provided bootstrap
|
||||
// configuration. This client is stored in the xdsclient pool’s internal
|
||||
// cache, keeping it alive and associated with this resolver until Closed().
|
||||
// While the c2p resolver itself does not directly use the client, creating
|
||||
// it ensures that when the xDS resolver later requests a client for the
|
||||
// same target, the existing instance will be reused.
|
||||
_, cancel, err := xdsClientPool.NewClientWithConfig(t.String(), opts.MetricsRecorder, config)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create xds client: %v", err)
|
||||
}
|
||||
|
||||
r, err := resolver.Get(xdsName).Build(t, cc, opts)
|
||||
if err != nil {
|
||||
cancel()
|
||||
return nil, err
|
||||
}
|
||||
return &c2pResolverWrapper{Resolver: r, cancel: cancel}, nil
|
||||
}
|
||||
|
||||
func (b c2pResolverBuilder) Scheme() string {
|
||||
return c2pScheme
|
||||
}
|
||||
|
||||
func newNodeConfig(zone string, ipv6Capable bool, forceXDS bool) map[string]any {
|
||||
prefix := "C2P"
|
||||
if forceXDS {
|
||||
prefix = "C2P-non-gcp"
|
||||
}
|
||||
node := map[string]any{
|
||||
"id": fmt.Sprintf("%s-%d", prefix, randInt()),
|
||||
}
|
||||
if zone != "" {
|
||||
node["locality"] = map[string]any{"zone": zone}
|
||||
}
|
||||
// Enable dualstack endpoints in TD.
|
||||
if ipv6Capable {
|
||||
node["metadata"] = map[string]any{ipv6CapableMetadataName: true}
|
||||
}
|
||||
return node
|
||||
}
|
||||
|
||||
func newAuthoritiesConfig(serverCfg map[string]any) map[string]any {
|
||||
return map[string]any{
|
||||
c2pAuthority: map[string]any{"xds_servers": []any{serverCfg}},
|
||||
}
|
||||
}
|
||||
|
||||
func newXdsServerConfig(uri string) map[string]any {
|
||||
return map[string]any{
|
||||
"server_uri": uri,
|
||||
"channel_creds": []map[string]any{{"type": "google_default"}},
|
||||
"server_features": []any{"ignore_resource_deletion"},
|
||||
}
|
||||
}
|
||||
768
xds/googledirectpath/googlec2p_test.go
Normal file
768
xds/googledirectpath/googlec2p_test.go
Normal file
@@ -0,0 +1,768 @@
|
||||
/*
|
||||
*
|
||||
* 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
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/credentials/insecure"
|
||||
"google.golang.org/grpc/internal/envconfig"
|
||||
"google.golang.org/grpc/internal/grpctest"
|
||||
"google.golang.org/grpc/internal/testutils"
|
||||
"google.golang.org/grpc/internal/xds/bootstrap"
|
||||
"google.golang.org/grpc/internal/xds/xdsclient"
|
||||
testgrpc "google.golang.org/grpc/interop/grpc_testing"
|
||||
testpb "google.golang.org/grpc/interop/grpc_testing"
|
||||
"google.golang.org/grpc/resolver"
|
||||
)
|
||||
|
||||
const defaultTestTimeout = 5 * time.Second
|
||||
|
||||
type s struct {
|
||||
grpctest.Tester
|
||||
}
|
||||
|
||||
func Test(t *testing.T) {
|
||||
grpctest.RunSubTests(t, s{})
|
||||
}
|
||||
|
||||
type emptyResolver struct {
|
||||
resolver.Resolver
|
||||
scheme string
|
||||
}
|
||||
|
||||
func (er *emptyResolver) Build(_ resolver.Target, _ resolver.ClientConn, _ resolver.BuildOptions) (resolver.Resolver, error) {
|
||||
return er, nil
|
||||
}
|
||||
|
||||
func (er *emptyResolver) Scheme() string {
|
||||
return er.scheme
|
||||
}
|
||||
|
||||
func (er *emptyResolver) Close() {}
|
||||
|
||||
var (
|
||||
testDNSResolver = &emptyResolver{scheme: "dns"}
|
||||
testXDSResolver = &emptyResolver{scheme: "xds"}
|
||||
)
|
||||
|
||||
// replaceResolvers unregisters the real resolvers for schemes `dns` and `xds`
|
||||
// and registers test resolvers instead. This allows the test to verify that
|
||||
// expected resolvers are built.
|
||||
func replaceResolvers(t *testing.T) {
|
||||
oldDNS := resolver.Get("dns")
|
||||
resolver.Register(testDNSResolver)
|
||||
oldXDS := resolver.Get("xds")
|
||||
resolver.Register(testXDSResolver)
|
||||
t.Cleanup(func() {
|
||||
resolver.Register(oldDNS)
|
||||
resolver.Register(oldXDS)
|
||||
})
|
||||
}
|
||||
|
||||
func simulateRunningOnGCE(t *testing.T, gce bool) {
|
||||
oldOnGCE := onGCE
|
||||
onGCE = func() bool { return gce }
|
||||
t.Cleanup(func() { onGCE = oldOnGCE })
|
||||
}
|
||||
|
||||
// ensure universeDomain is set to the expected default,
|
||||
// and clean it up again after the test.
|
||||
func useCleanUniverseDomain(t *testing.T) {
|
||||
universeDomainMu.Lock()
|
||||
defer universeDomainMu.Unlock()
|
||||
if universeDomain != "" {
|
||||
t.Fatalf("universe domain unexpectedly initialized: %v", universeDomain)
|
||||
}
|
||||
t.Cleanup(func() {
|
||||
universeDomainMu.Lock()
|
||||
universeDomain = ""
|
||||
universeDomainMu.Unlock()
|
||||
})
|
||||
}
|
||||
|
||||
// verifyXDSClientBootstrapConfig checks that an xDS client with the given name
|
||||
// exists in the pool and that its bootstrap config matches the expected config.
|
||||
func verifyXDSClientBootstrapConfig(t *testing.T, pool *xdsclient.Pool, name string, wantConfig *bootstrap.Config) {
|
||||
t.Helper()
|
||||
client, close, err := pool.GetClientForTesting(name)
|
||||
if err != nil {
|
||||
t.Fatalf("GetClientForTesting(%q) failed to get xds client: %v", name, err)
|
||||
}
|
||||
defer close()
|
||||
|
||||
gotConfig := client.BootstrapConfig()
|
||||
if gotConfig == nil {
|
||||
t.Fatalf("Failed to get bootstrap config: %v", err)
|
||||
}
|
||||
if diff := cmp.Diff(wantConfig, gotConfig); diff != "" {
|
||||
t.Fatalf("xdsClient.BootstrapConfig() for client %q returned unexpected diff (-want +got):\n%s", name, diff)
|
||||
}
|
||||
}
|
||||
|
||||
// Tests the scenario where the bootstrap env vars are set and we're running on
|
||||
// GCE. The test builds a google-c2p resolver and verifies that an xDS resolver
|
||||
// is built and that we don't fallback to DNS (because federation is enabled by
|
||||
// default).
|
||||
func (s) TestBuildWithBootstrapEnvSet(t *testing.T) {
|
||||
replaceResolvers(t)
|
||||
simulateRunningOnGCE(t, true)
|
||||
useCleanUniverseDomain(t)
|
||||
|
||||
builder := resolver.Get(c2pScheme)
|
||||
for i, envP := range []*string{&envconfig.XDSBootstrapFileName, &envconfig.XDSBootstrapFileContent} {
|
||||
t.Run(strconv.Itoa(i), func(t *testing.T) {
|
||||
// Set bootstrap config env var.
|
||||
oldEnv := *envP
|
||||
*envP = "does not matter"
|
||||
defer func() { *envP = oldEnv }()
|
||||
|
||||
// Override xDS client pool.
|
||||
oldXdsClientPool := xdsClientPool
|
||||
xdsClientPool = xdsclient.NewPool(nil)
|
||||
defer func() { xdsClientPool = oldXdsClientPool }()
|
||||
|
||||
// Build the google-c2p resolver.
|
||||
r, err := builder.Build(resolver.Target{}, nil, resolver.BuildOptions{})
|
||||
if err != nil {
|
||||
t.Fatalf("failed to build resolver: %v", err)
|
||||
}
|
||||
defer r.Close()
|
||||
|
||||
// Build should return wrapped xDS resolver, not DNS.
|
||||
if r, ok := r.(*c2pResolverWrapper); !ok || r.Resolver != testXDSResolver {
|
||||
t.Fatalf("Build() returned %#v, want c2pResolverWrapper", r)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Tests the scenario where we are not running on GCE. The test builds a
|
||||
// google-c2p resolver and verifies that we fallback to DNS.
|
||||
func (s) TestBuildNotOnGCE(t *testing.T) {
|
||||
replaceResolvers(t)
|
||||
simulateRunningOnGCE(t, false)
|
||||
useCleanUniverseDomain(t)
|
||||
builder := resolver.Get(c2pScheme)
|
||||
|
||||
// Build the google-c2p resolver.
|
||||
r, err := builder.Build(resolver.Target{}, nil, resolver.BuildOptions{})
|
||||
if err != nil {
|
||||
t.Fatalf("failed to build resolver: %v", err)
|
||||
}
|
||||
defer r.Close()
|
||||
|
||||
// Build should return DNS, not xDS.
|
||||
if r != testDNSResolver {
|
||||
t.Fatalf("Build() returned %#v, want dns resolver", r)
|
||||
}
|
||||
}
|
||||
|
||||
func bootstrapConfig(t *testing.T, opts bootstrap.ConfigOptionsForTesting) *bootstrap.Config {
|
||||
t.Helper()
|
||||
|
||||
contents, err := bootstrap.NewContentsForTesting(opts)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create bootstrap contents: %v", err)
|
||||
}
|
||||
cfg, err := bootstrap.NewConfigFromContents(contents)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create bootstrap config: %v", err)
|
||||
}
|
||||
return cfg
|
||||
}
|
||||
|
||||
// Test that when a google-c2p resolver is built, the xDS client is built with
|
||||
// the expected config.
|
||||
func (s) TestBuildXDS(t *testing.T) {
|
||||
replaceResolvers(t)
|
||||
simulateRunningOnGCE(t, true)
|
||||
useCleanUniverseDomain(t)
|
||||
builder := resolver.Get(c2pScheme)
|
||||
|
||||
// Override the zone returned by the metadata server.
|
||||
oldGetZone := getZone
|
||||
getZone = func(time.Duration) string { return "test-zone" }
|
||||
defer func() { getZone = oldGetZone }()
|
||||
|
||||
// Override the random func used in the node ID.
|
||||
origRandInd := randInt
|
||||
randInt = func() int { return 666 }
|
||||
defer func() { randInt = origRandInd }()
|
||||
|
||||
for _, tt := range []struct {
|
||||
desc string
|
||||
ipv6Capable bool
|
||||
tdURIOverride string
|
||||
wantBootstrapConfig *bootstrap.Config
|
||||
}{
|
||||
{
|
||||
desc: "ipv6 false",
|
||||
wantBootstrapConfig: bootstrapConfig(t, bootstrap.ConfigOptionsForTesting{
|
||||
Servers: []byte(`[{
|
||||
"server_uri": "dns:///directpath-pa.googleapis.com",
|
||||
"channel_creds": [{"type": "google_default"}],
|
||||
"server_features": ["ignore_resource_deletion"]
|
||||
}]`),
|
||||
Authorities: map[string]json.RawMessage{
|
||||
"traffic-director-c2p.xds.googleapis.com": []byte(`{
|
||||
"xds_servers": [
|
||||
{
|
||||
"server_uri": "dns:///directpath-pa.googleapis.com",
|
||||
"channel_creds": [{"type": "google_default"}],
|
||||
"server_features": ["ignore_resource_deletion"]
|
||||
}
|
||||
]
|
||||
}`),
|
||||
},
|
||||
Node: []byte(`{
|
||||
"id": "C2P-666",
|
||||
"locality": {
|
||||
"zone": "test-zone"
|
||||
}
|
||||
}`),
|
||||
}),
|
||||
},
|
||||
{
|
||||
desc: "ipv6 true",
|
||||
ipv6Capable: true,
|
||||
wantBootstrapConfig: bootstrapConfig(t, bootstrap.ConfigOptionsForTesting{
|
||||
Servers: []byte(`[{
|
||||
"server_uri": "dns:///directpath-pa.googleapis.com",
|
||||
"channel_creds": [{"type": "google_default"}],
|
||||
"server_features": ["ignore_resource_deletion"]
|
||||
}]`),
|
||||
Authorities: map[string]json.RawMessage{
|
||||
"traffic-director-c2p.xds.googleapis.com": []byte(`{
|
||||
"xds_servers": [
|
||||
{
|
||||
"server_uri": "dns:///directpath-pa.googleapis.com",
|
||||
"channel_creds": [{"type": "google_default"}],
|
||||
"server_features": ["ignore_resource_deletion"]
|
||||
}
|
||||
]
|
||||
}`),
|
||||
},
|
||||
Node: []byte(`{
|
||||
"id": "C2P-666",
|
||||
"locality": {
|
||||
"zone": "test-zone"
|
||||
},
|
||||
"metadata": {
|
||||
"TRAFFICDIRECTOR_DIRECTPATH_C2P_IPV6_CAPABLE": true
|
||||
}
|
||||
}`),
|
||||
}),
|
||||
},
|
||||
{
|
||||
desc: "override TD URI",
|
||||
ipv6Capable: true,
|
||||
tdURIOverride: "test-uri",
|
||||
wantBootstrapConfig: bootstrapConfig(t, bootstrap.ConfigOptionsForTesting{
|
||||
Servers: []byte(`[{
|
||||
"server_uri": "test-uri",
|
||||
"channel_creds": [{"type": "google_default"}],
|
||||
"server_features": ["ignore_resource_deletion"]
|
||||
}]`),
|
||||
Authorities: map[string]json.RawMessage{
|
||||
"traffic-director-c2p.xds.googleapis.com": []byte(`{
|
||||
"xds_servers": [
|
||||
{
|
||||
"server_uri": "test-uri",
|
||||
"channel_creds": [{"type": "google_default"}],
|
||||
"server_features": ["ignore_resource_deletion"]
|
||||
}
|
||||
]
|
||||
}`),
|
||||
},
|
||||
Node: []byte(`{
|
||||
"id": "C2P-666",
|
||||
"locality": {
|
||||
"zone": "test-zone"
|
||||
},
|
||||
"metadata": {
|
||||
"TRAFFICDIRECTOR_DIRECTPATH_C2P_IPV6_CAPABLE": true
|
||||
}
|
||||
}`),
|
||||
}),
|
||||
},
|
||||
} {
|
||||
t.Run(tt.desc, func(t *testing.T) {
|
||||
// Override IPv6 capability returned by the metadata server.
|
||||
oldGetIPv6Capability := getIPv6Capable
|
||||
getIPv6Capable = func(time.Duration) bool { return tt.ipv6Capable }
|
||||
defer func() { getIPv6Capable = oldGetIPv6Capability }()
|
||||
|
||||
// Override TD URI test only env var.
|
||||
if tt.tdURIOverride != "" {
|
||||
oldURI := envconfig.C2PResolverTestOnlyTrafficDirectorURI
|
||||
envconfig.C2PResolverTestOnlyTrafficDirectorURI = tt.tdURIOverride
|
||||
defer func() { envconfig.C2PResolverTestOnlyTrafficDirectorURI = oldURI }()
|
||||
}
|
||||
|
||||
// Override xDS client pool.
|
||||
oldXdsClientPool := xdsClientPool
|
||||
xdsClientPool = xdsclient.NewPool(nil)
|
||||
defer func() { xdsClientPool = oldXdsClientPool }()
|
||||
|
||||
getIPv6Capable = func(time.Duration) bool { return tt.ipv6Capable }
|
||||
defer func() { getIPv6Capable = oldGetIPv6Capability }()
|
||||
|
||||
// Build the google-c2p resolver.
|
||||
target := resolver.Target{URL: url.URL{Scheme: c2pScheme, Path: "test-path"}}
|
||||
r, err := builder.Build(target, nil, resolver.BuildOptions{})
|
||||
if err != nil {
|
||||
t.Fatalf("failed to build resolver: %v", err)
|
||||
}
|
||||
defer r.Close()
|
||||
|
||||
// Build should return wrapped xDS resolver, not DNS.
|
||||
if r, ok := r.(*c2pResolverWrapper); !ok || r.Resolver != testXDSResolver {
|
||||
t.Fatalf("Build() returned %#v, want c2pResolverWrapper", r)
|
||||
}
|
||||
|
||||
xdsTarget := resolver.Target{URL: url.URL{Scheme: xdsName, Host: c2pAuthority, Path: target.URL.Path}}
|
||||
verifyXDSClientBootstrapConfig(t, xdsClientPool, xdsTarget.String(), tt.wantBootstrapConfig)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestDialFailsWhenTargetContainsAuthority attempts to Dial a target URI of
|
||||
// google-c2p scheme with a non-empty authority and verifies that it fails with
|
||||
// an expected error.
|
||||
func (s) TestBuildFailsWhenCalledWithAuthority(t *testing.T) {
|
||||
useCleanUniverseDomain(t)
|
||||
uri := "google-c2p://an-authority/resource"
|
||||
cc, err := grpc.NewClient(uri, grpc.WithTransportCredentials(insecure.NewCredentials()))
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create a client for server: %v", err)
|
||||
}
|
||||
defer func() {
|
||||
if cc != nil {
|
||||
cc.Close()
|
||||
}
|
||||
}()
|
||||
ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout)
|
||||
defer cancel()
|
||||
client := testgrpc.NewTestServiceClient(cc)
|
||||
_, err = client.EmptyCall(ctx, &testpb.Empty{})
|
||||
wantErr := "google-c2p URI scheme does not support authorities"
|
||||
if err == nil || !strings.Contains(err.Error(), wantErr) {
|
||||
t.Fatalf("client.EmptyCall(%s) returned error: %v, want: %v", uri, err, wantErr)
|
||||
}
|
||||
}
|
||||
|
||||
func (s) TestSetUniverseDomainNonDefault(t *testing.T) {
|
||||
replaceResolvers(t)
|
||||
simulateRunningOnGCE(t, true)
|
||||
useCleanUniverseDomain(t)
|
||||
builder := resolver.Get(c2pScheme)
|
||||
|
||||
// Override the zone returned by the metadata server.
|
||||
oldGetZone := getZone
|
||||
getZone = func(time.Duration) string { return "test-zone" }
|
||||
defer func() { getZone = oldGetZone }()
|
||||
|
||||
// Override IPv6 capability returned by the metadata server.
|
||||
oldGetIPv6Capability := getIPv6Capable
|
||||
getIPv6Capable = func(time.Duration) bool { return false }
|
||||
defer func() { getIPv6Capable = oldGetIPv6Capability }()
|
||||
|
||||
// Override the random func used in the node ID.
|
||||
origRandInd := randInt
|
||||
randInt = func() int { return 666 }
|
||||
defer func() { randInt = origRandInd }()
|
||||
|
||||
// Set the universe domain
|
||||
testUniverseDomain := "test-universe-domain.test"
|
||||
if err := SetUniverseDomain(testUniverseDomain); err != nil {
|
||||
t.Fatalf("SetUniverseDomain(%s) failed: %v", testUniverseDomain, err)
|
||||
}
|
||||
|
||||
// Now set universe domain to something different, it should fail
|
||||
domain := "test-universe-domain-2.test"
|
||||
err := SetUniverseDomain(domain)
|
||||
wantErr := "already set"
|
||||
if err == nil || !strings.Contains(err.Error(), wantErr) {
|
||||
t.Fatalf("googlec2p.SetUniverseDomain(%s) returned error: %v, want: %v", domain, err, wantErr)
|
||||
}
|
||||
|
||||
// Now explicitly set universe domain to the default, it should also fail
|
||||
domain = "googleapis.com"
|
||||
err = SetUniverseDomain(domain)
|
||||
wantErr = "already set"
|
||||
if err == nil || !strings.Contains(err.Error(), wantErr) {
|
||||
t.Fatalf("googlec2p.SetUniverseDomain(%s) returned error: %v, want: %v", domain, err, wantErr)
|
||||
}
|
||||
|
||||
// Now set universe domain to the original value, it should work
|
||||
if err := SetUniverseDomain(testUniverseDomain); err != nil {
|
||||
t.Fatalf("googlec2p.SetUniverseDomain(%s) failed: %v", testUniverseDomain, err)
|
||||
}
|
||||
|
||||
// Override xDS client pool.
|
||||
oldXdsClientPool := xdsClientPool
|
||||
xdsClientPool = xdsclient.NewPool(nil)
|
||||
defer func() { xdsClientPool = oldXdsClientPool }()
|
||||
|
||||
// Build the google-c2p resolver.
|
||||
target := resolver.Target{URL: url.URL{Scheme: c2pScheme, Path: "test-path"}}
|
||||
r, err := builder.Build(target, nil, resolver.BuildOptions{})
|
||||
if err != nil {
|
||||
t.Fatalf("failed to build resolver: %v", err)
|
||||
}
|
||||
defer r.Close()
|
||||
|
||||
// Build should return wrapped xDS resolver, not DNS.
|
||||
if r, ok := r.(*c2pResolverWrapper); !ok || r.Resolver != testXDSResolver {
|
||||
t.Fatalf("Build() returned %#v, want c2pResolverWrapper", r)
|
||||
}
|
||||
|
||||
// Check that we use directpath-pa.test-universe-domain.test in the
|
||||
// bootstrap config.
|
||||
wantBootstrapConfig := bootstrapConfig(t, bootstrap.ConfigOptionsForTesting{
|
||||
Servers: []byte(`[{
|
||||
"server_uri": "dns:///directpath-pa.test-universe-domain.test",
|
||||
"channel_creds": [{"type": "google_default"}],
|
||||
"server_features": ["ignore_resource_deletion"]
|
||||
}]`),
|
||||
Authorities: map[string]json.RawMessage{
|
||||
"traffic-director-c2p.xds.googleapis.com": []byte(`{
|
||||
"xds_servers": [
|
||||
{
|
||||
"server_uri": "dns:///directpath-pa.test-universe-domain.test",
|
||||
"channel_creds": [{"type": "google_default"}],
|
||||
"server_features": ["ignore_resource_deletion"]
|
||||
}
|
||||
]
|
||||
}`),
|
||||
},
|
||||
Node: []byte(`{
|
||||
"id": "C2P-666",
|
||||
"locality": {
|
||||
"zone": "test-zone"
|
||||
}
|
||||
}`),
|
||||
})
|
||||
|
||||
xdsTarget := resolver.Target{URL: url.URL{Scheme: xdsName, Host: c2pAuthority, Path: target.URL.Path}}
|
||||
verifyXDSClientBootstrapConfig(t, xdsClientPool, xdsTarget.String(), wantBootstrapConfig)
|
||||
}
|
||||
|
||||
func (s) TestDefaultUniverseDomain(t *testing.T) {
|
||||
replaceResolvers(t)
|
||||
simulateRunningOnGCE(t, true)
|
||||
useCleanUniverseDomain(t)
|
||||
builder := resolver.Get(c2pScheme)
|
||||
|
||||
// Override the zone returned by the metadata server.
|
||||
oldGetZone := getZone
|
||||
getZone = func(time.Duration) string { return "test-zone" }
|
||||
defer func() { getZone = oldGetZone }()
|
||||
|
||||
// Override IPv6 capability returned by the metadata server.
|
||||
oldGetIPv6Capability := getIPv6Capable
|
||||
getIPv6Capable = func(time.Duration) bool { return false }
|
||||
defer func() { getIPv6Capable = oldGetIPv6Capability }()
|
||||
|
||||
// Override the random func used in the node ID.
|
||||
origRandInd := randInt
|
||||
randInt = func() int { return 666 }
|
||||
defer func() { randInt = origRandInd }()
|
||||
|
||||
// Override xDS client pool.
|
||||
oldXdsClientPool := xdsClientPool
|
||||
xdsClientPool = xdsclient.NewPool(nil)
|
||||
defer func() { xdsClientPool = oldXdsClientPool }()
|
||||
|
||||
// Build the google-c2p resolver.
|
||||
target := resolver.Target{URL: url.URL{Scheme: c2pScheme, Path: "test-path"}}
|
||||
r, err := builder.Build(target, nil, resolver.BuildOptions{})
|
||||
if err != nil {
|
||||
t.Fatalf("failed to build resolver: %v", err)
|
||||
}
|
||||
defer r.Close()
|
||||
|
||||
// Build should return xDS, not DNS.
|
||||
if r, ok := r.(*c2pResolverWrapper); !ok || r.Resolver != testXDSResolver {
|
||||
t.Fatalf("Build() returned %#v, want c2pResolverWrapper", r)
|
||||
}
|
||||
|
||||
// Check that we use directpath-pa.googleapis.com in the bootstrap config
|
||||
wantBootstrapConfig := bootstrapConfig(t, bootstrap.ConfigOptionsForTesting{
|
||||
Servers: []byte(`[{
|
||||
"server_uri": "dns:///directpath-pa.googleapis.com",
|
||||
"channel_creds": [{"type": "google_default"}],
|
||||
"server_features": ["ignore_resource_deletion"]
|
||||
}]`),
|
||||
Authorities: map[string]json.RawMessage{
|
||||
"traffic-director-c2p.xds.googleapis.com": []byte(`{
|
||||
"xds_servers": [
|
||||
{
|
||||
"server_uri": "dns:///directpath-pa.googleapis.com",
|
||||
"channel_creds": [{"type": "google_default"}],
|
||||
"server_features": ["ignore_resource_deletion"]
|
||||
}
|
||||
]
|
||||
}`),
|
||||
},
|
||||
Node: []byte(`{
|
||||
"id": "C2P-666",
|
||||
"locality": {
|
||||
"zone": "test-zone"
|
||||
}
|
||||
}`),
|
||||
})
|
||||
xdsTarget := resolver.Target{URL: url.URL{Scheme: xdsName, Host: c2pAuthority, Path: target.URL.Path}}
|
||||
verifyXDSClientBootstrapConfig(t, xdsClientPool, xdsTarget.String(), wantBootstrapConfig)
|
||||
|
||||
// Now set universe domain to something different than the default, it should fail
|
||||
domain := "test-universe-domain.test"
|
||||
err = SetUniverseDomain(domain)
|
||||
wantErr := "already set"
|
||||
if err == nil || !strings.Contains(err.Error(), wantErr) {
|
||||
t.Fatalf("googlec2p.SetUniverseDomain(%s) returned error: %v, want: %v", domain, err, wantErr)
|
||||
}
|
||||
|
||||
// Now explicitly set universe domain to the default, it should work
|
||||
domain = "googleapis.com"
|
||||
if err := SetUniverseDomain(domain); err != nil {
|
||||
t.Fatalf("googlec2p.SetUniverseDomain(%s) failed: %v", domain, err)
|
||||
}
|
||||
}
|
||||
|
||||
func (s) TestSetUniverseDomainEmptyString(t *testing.T) {
|
||||
replaceResolvers(t)
|
||||
simulateRunningOnGCE(t, true)
|
||||
useCleanUniverseDomain(t)
|
||||
wantErr := "cannot be empty"
|
||||
err := SetUniverseDomain("")
|
||||
if err == nil || !strings.Contains(err.Error(), wantErr) {
|
||||
t.Fatalf("googlec2p.SetUniverseDomain(\"\") returned error: %v, want: %v", err, wantErr)
|
||||
}
|
||||
}
|
||||
|
||||
// TestCreateMultipleXDSClients validates that multiple xds clients with
|
||||
// different bootstrap config coexist in the same pool. It confirms that
|
||||
// a client created by the google-c2p resolver does not interfere with an
|
||||
// explicitly created client using a different bootstrap configuration.
|
||||
func (s) TestCreateMultipleXDSClients(t *testing.T) {
|
||||
replaceResolvers(t)
|
||||
simulateRunningOnGCE(t, true)
|
||||
useCleanUniverseDomain(t)
|
||||
|
||||
// Override the zone returned by the metadata server.
|
||||
oldGetZone := getZone
|
||||
getZone = func(time.Duration) string { return "test-zone" }
|
||||
defer func() { getZone = oldGetZone }()
|
||||
|
||||
// Override the random func used in the node ID.
|
||||
origRandInd := randInt
|
||||
randInt = func() int { return 666 }
|
||||
defer func() { randInt = origRandInd }()
|
||||
|
||||
// Override IPv6 capability returned by the metadata server.
|
||||
oldGetIPv6Capability := getIPv6Capable
|
||||
getIPv6Capable = func(time.Duration) bool { return false }
|
||||
defer func() { getIPv6Capable = oldGetIPv6Capability }()
|
||||
|
||||
// Define bootstrap config for generic xds resolver
|
||||
genericXDSConfig := bootstrapConfig(t, bootstrap.ConfigOptionsForTesting{
|
||||
Servers: []byte(`[{
|
||||
"server_uri": "dns:///regular-xds.googleapis.com",
|
||||
"channel_creds": [{"type": "google_default"}],
|
||||
"server_features": []
|
||||
}]`),
|
||||
Node: []byte(`{"id": "regular-xds-node", "locality": {"zone": "test-zone"}}`),
|
||||
})
|
||||
|
||||
// Override xDS client pool.
|
||||
oldXdsClientPool := xdsClientPool
|
||||
xdsClientPool = xdsclient.NewPool(genericXDSConfig)
|
||||
defer func() { xdsClientPool = oldXdsClientPool }()
|
||||
|
||||
// Create generic xds client.
|
||||
xdsTarget := resolver.Target{URL: *testutils.MustParseURL("xds:///target")}
|
||||
_, closeGeneric, err := xdsClientPool.NewClientForTesting(xdsclient.OptionsForTesting{
|
||||
Name: xdsTarget.String(),
|
||||
Config: genericXDSConfig,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("xdsClientPool.NewClientForTesting(%q) failed: %v", xdsTarget.String(), err)
|
||||
}
|
||||
defer closeGeneric()
|
||||
|
||||
verifyXDSClientBootstrapConfig(t, xdsClientPool, xdsTarget.String(), genericXDSConfig)
|
||||
|
||||
// Build the google-c2p resolver.
|
||||
c2pBuilder := resolver.Get(c2pScheme)
|
||||
c2pTarget := resolver.Target{URL: url.URL{Scheme: c2pScheme, Path: "test-path"}}
|
||||
tcc := &testutils.ResolverClientConn{Logger: t}
|
||||
c2pRes, err := c2pBuilder.Build(c2pTarget, tcc, resolver.BuildOptions{})
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to build resolver: %v", err)
|
||||
}
|
||||
defer c2pRes.Close()
|
||||
|
||||
// Bootstrap config for c2p resolver.
|
||||
c2pConfig := bootstrapConfig(t, bootstrap.ConfigOptionsForTesting{
|
||||
Servers: []byte(`[{
|
||||
"server_uri": "dns:///directpath-pa.googleapis.com",
|
||||
"channel_creds": [{"type": "google_default"}],
|
||||
"server_features": ["ignore_resource_deletion"]
|
||||
}]`),
|
||||
Authorities: map[string]json.RawMessage{
|
||||
"traffic-director-c2p.xds.googleapis.com": []byte(`{
|
||||
"xds_servers": [{
|
||||
"server_uri": "dns:///directpath-pa.googleapis.com",
|
||||
"channel_creds": [{"type": "google_default"}],
|
||||
"server_features": ["ignore_resource_deletion"]
|
||||
}]
|
||||
}`),
|
||||
},
|
||||
Node: []byte(`{
|
||||
"id": "C2P-666",
|
||||
"locality": {
|
||||
"zone": "test-zone"
|
||||
}
|
||||
}`),
|
||||
})
|
||||
|
||||
c2pXDSTarget := resolver.Target{URL: url.URL{Scheme: xdsName, Host: c2pAuthority, Path: c2pTarget.URL.Path}}
|
||||
verifyXDSClientBootstrapConfig(t, xdsClientPool, c2pXDSTarget.String(), c2pConfig)
|
||||
}
|
||||
|
||||
// Test validates that the C2P resolver handles non-GCP (Google Cloud
|
||||
// Interconnect) clients correctly when the force-xds query parameter is
|
||||
// provided in various valid formats.
|
||||
// It verifies that:
|
||||
// - GCE metadata server queries for zone and ipv6 capability are bypassed.
|
||||
// - The node ID is formatted using the prefix ("C2P-non-gcp").
|
||||
// - Zone information is completely omitted from the node config.
|
||||
// - Dualstack IPv6 capability (TRAFFICDIRECTOR_DIRECTPATH_C2P_IPV6_CAPABLE)
|
||||
// is set to true.
|
||||
func (s) TestBuild_NotOnGCE_WithForceXDS(t *testing.T) {
|
||||
wantBootstrapConfig := bootstrapConfig(t, bootstrap.ConfigOptionsForTesting{
|
||||
Servers: []byte(`[{
|
||||
"server_uri": "dns:///directpath-pa.googleapis.com",
|
||||
"channel_creds": [{"type": "google_default"}],
|
||||
"server_features": ["ignore_resource_deletion"]
|
||||
}]`),
|
||||
Authorities: map[string]json.RawMessage{
|
||||
"traffic-director-c2p.xds.googleapis.com": []byte(`{
|
||||
"xds_servers": [
|
||||
{
|
||||
"server_uri": "dns:///directpath-pa.googleapis.com",
|
||||
"channel_creds": [{"type": "google_default"}],
|
||||
"server_features": ["ignore_resource_deletion"]
|
||||
}
|
||||
]
|
||||
}`),
|
||||
},
|
||||
Node: []byte(`{
|
||||
"id": "C2P-non-gcp-666",
|
||||
"metadata": {
|
||||
"TRAFFICDIRECTOR_DIRECTPATH_C2P_IPV6_CAPABLE": true
|
||||
}
|
||||
}`),
|
||||
})
|
||||
tests := []struct {
|
||||
desc string
|
||||
rawQuery string
|
||||
onGCE bool
|
||||
}{
|
||||
{
|
||||
desc: "query_param_key_only",
|
||||
rawQuery: "force-xds",
|
||||
},
|
||||
{
|
||||
desc: "query_param_trailing_equals_value_empty",
|
||||
rawQuery: "force-xds=",
|
||||
},
|
||||
{
|
||||
desc: "query_param_value_true",
|
||||
rawQuery: "force-xds=true",
|
||||
},
|
||||
{
|
||||
desc: "query_param_value_false",
|
||||
rawQuery: "force-xds=false",
|
||||
},
|
||||
{
|
||||
desc: "query_param_multiple_query_params",
|
||||
rawQuery: "foo=bar&force-xds",
|
||||
},
|
||||
{
|
||||
desc: "onGCE_with_force-xds",
|
||||
rawQuery: "force-xds",
|
||||
onGCE: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.desc, func(t *testing.T) {
|
||||
replaceResolvers(t)
|
||||
simulateRunningOnGCE(t, tt.onGCE)
|
||||
useCleanUniverseDomain(t)
|
||||
|
||||
// Override the random func used in the node ID.
|
||||
origRandInd := randInt
|
||||
randInt = func() int { return 666 }
|
||||
defer func() { randInt = origRandInd }()
|
||||
|
||||
// Override xDS client pool.
|
||||
oldXdsClientPool := xdsClientPool
|
||||
xdsClientPool = xdsclient.NewPool(nil)
|
||||
defer func() { xdsClientPool = oldXdsClientPool }()
|
||||
|
||||
// Target URI with force-xds query parameter.
|
||||
builder := resolver.Get(c2pScheme)
|
||||
target := resolver.Target{URL: url.URL{
|
||||
Scheme: c2pScheme,
|
||||
Path: "test-path",
|
||||
RawQuery: tt.rawQuery,
|
||||
}}
|
||||
|
||||
// Build the google-c2p resolver.
|
||||
res, err := builder.Build(target, nil, resolver.BuildOptions{})
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to build resolver: %v", err)
|
||||
}
|
||||
defer res.Close()
|
||||
|
||||
// Query parameters are omitted since the downstream xDS target is
|
||||
// configured using only active fields.
|
||||
xdsTarget := resolver.Target{
|
||||
URL: url.URL{
|
||||
Scheme: xdsName,
|
||||
Host: c2pAuthority,
|
||||
Path: target.URL.Path,
|
||||
},
|
||||
}
|
||||
verifyXDSClientBootstrapConfig(t, xdsClientPool, xdsTarget.String(), wantBootstrapConfig)
|
||||
})
|
||||
}
|
||||
}
|
||||
101
xds/googledirectpath/utils.go
Normal file
101
xds/googledirectpath/utils.go
Normal file
@@ -0,0 +1,101 @@
|
||||
/*
|
||||
*
|
||||
* 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
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
func getFromMetadata(timeout time.Duration, urlStr string) ([]byte, error) {
|
||||
parsedURL, err := url.Parse(urlStr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
client := &http.Client{Timeout: timeout}
|
||||
req := &http.Request{
|
||||
Method: http.MethodGet,
|
||||
URL: parsedURL,
|
||||
Header: http.Header{"Metadata-Flavor": {"Google"}},
|
||||
}
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed communicating with metadata server: %v", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return nil, fmt.Errorf("metadata server returned resp with non-OK: %v", resp)
|
||||
}
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed reading from metadata server: %v", err)
|
||||
}
|
||||
return body, nil
|
||||
}
|
||||
|
||||
var (
|
||||
zone string
|
||||
zoneOnce sync.Once
|
||||
)
|
||||
|
||||
// Defined as var to be overridden in tests.
|
||||
var getZone = func(timeout time.Duration) string {
|
||||
zoneOnce.Do(func() {
|
||||
qualifiedZone, err := getFromMetadata(timeout, zoneURL)
|
||||
if err != nil {
|
||||
logger.Warningf("could not discover instance zone: %v", err)
|
||||
return
|
||||
}
|
||||
i := bytes.LastIndexByte(qualifiedZone, '/')
|
||||
if i == -1 {
|
||||
logger.Warningf("could not parse zone from metadata server: %s", qualifiedZone)
|
||||
return
|
||||
}
|
||||
zone = string(qualifiedZone[i+1:])
|
||||
})
|
||||
return zone
|
||||
}
|
||||
|
||||
var (
|
||||
ipv6Capable bool
|
||||
ipv6CapableOnce sync.Once
|
||||
)
|
||||
|
||||
// Defined as var to be overridden in tests.
|
||||
var getIPv6Capable = func(timeout time.Duration) bool {
|
||||
ipv6CapableOnce.Do(func() {
|
||||
addr, err := getFromMetadata(timeout, ipv6URL)
|
||||
if err != nil {
|
||||
logger.Warningf("could not discover ipv6 capability: %v", err)
|
||||
return
|
||||
}
|
||||
if trimmedAddr := strings.TrimSpace(string(addr)); trimmedAddr == "" {
|
||||
logger.Warningf("metadata server returned empty ipv6 address")
|
||||
return
|
||||
}
|
||||
ipv6Capable = true
|
||||
})
|
||||
return ipv6Capable
|
||||
}
|
||||
Reference in New Issue
Block a user