xds: resubmit xds client pool changes from #7898 along with fix to set fallback bootstrap config from googledirectpath to xdsclient pool (#8050)

This commit is contained in:
Purnesh Dixit 2025-02-04 23:59:01 +05:30 committed by GitHub
parent 947e2a4be2
commit 79b6830e4b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
55 changed files with 1425 additions and 981 deletions

View File

@ -151,8 +151,8 @@ var (
// other features, including the CSDS service.
NewXDSResolverWithConfigForTesting any // func([]byte) (resolver.Builder, error)
// NewXDSResolverWithClientForTesting creates a new xDS resolver builder
// using the provided xDS client instead of creating a new one using the
// NewXDSResolverWithPoolForTesting creates a new xDS resolver builder
// using the provided xDS pool instead of creating a new one using the
// bootstrap configuration specified by the supported environment variables.
// The resolver.Builder is meant to be used in conjunction with the
// grpc.WithResolvers DialOption. The resolver.Builder does not take
@ -163,7 +163,7 @@ var (
//
// This function should ONLY be used for testing and may not work with some
// other features, including the CSDS service.
NewXDSResolverWithClientForTesting any // func(xdsclient.XDSClient) (resolver.Builder, error)
NewXDSResolverWithPoolForTesting any // func(*xdsclient.Pool) (resolver.Builder, error)
// RegisterRLSClusterSpecifierPluginForTesting registers the RLS Cluster
// Specifier Plugin for testing purposes, regardless of the XDSRLS environment

View File

@ -49,13 +49,12 @@ func ManagementServerAndResolver(t *testing.T) (*e2e.ManagementServer, string, [
bc := e2e.DefaultBootstrapContents(t, nodeID, xdsServer.Address)
// Create an xDS resolver with the above bootstrap configuration.
var r resolver.Builder
var err error
if newResolver := internal.NewXDSResolverWithConfigForTesting; newResolver != nil {
r, err = newResolver.(func([]byte) (resolver.Builder, error))(bc)
if err != nil {
t.Fatalf("Failed to create xDS resolver for testing: %v", err)
}
if internal.NewXDSResolverWithConfigForTesting == nil {
t.Fatalf("internal.NewXDSResolverWithConfigForTesting is nil")
}
r, err := internal.NewXDSResolverWithConfigForTesting.(func([]byte) (resolver.Builder, error))(bc)
if err != nil {
t.Fatalf("Failed to create xDS resolver for testing: %v", err)
}
return xdsServer, nodeID, bc, r

View File

@ -31,7 +31,6 @@ import (
"os"
"slices"
"strings"
"sync"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/tls/certprovider"
@ -578,9 +577,6 @@ func (c *Config) UnmarshalJSON(data []byte) error {
// specified at ${GRPC_XDS_BOOTSTRAP_CONFIG}. If both env vars are set, the
// former is preferred.
//
// If none of the env vars are set, this function returns the fallback
// configuration if it is not nil. Else, it returns an error.
//
// This function tries to process as much of the bootstrap file as possible (in
// the presence of the errors) and may return a Config object with certain
// fields left unspecified, in which case the caller should use some sane
@ -597,27 +593,22 @@ func GetConfiguration() (*Config, error) {
if err != nil {
return nil, fmt.Errorf("xds: failed to read bootstrap config from file %q: %v", fName, err)
}
return newConfigFromContents(cfg)
return NewConfigFromContents(cfg)
}
if fContent != "" {
if logger.V(2) {
logger.Infof("Using bootstrap contents from GRPC_XDS_BOOTSTRAP_CONFIG environment variable")
}
return newConfigFromContents([]byte(fContent))
return NewConfigFromContents([]byte(fContent))
}
if cfg := fallbackBootstrapConfig(); cfg != nil {
if logger.V(2) {
logger.Infof("Using bootstrap contents from fallback config")
}
return cfg, nil
}
return nil, fmt.Errorf("bootstrap environment variables (%q or %q) not defined, and no fallback config set", envconfig.XDSBootstrapFileNameEnv, envconfig.XDSBootstrapFileContentEnv)
return nil, fmt.Errorf("bootstrap environment variables (%q or %q) not defined", envconfig.XDSBootstrapFileNameEnv, envconfig.XDSBootstrapFileContentEnv)
}
func newConfigFromContents(data []byte) (*Config, error) {
// NewConfigFromContents creates a new bootstrap configuration from the provided
// contents.
func NewConfigFromContents(data []byte) (*Config, error) {
// Normalize the input configuration.
buf := bytes.Buffer{}
err := json.Indent(&buf, data, "", "")
@ -700,14 +691,6 @@ func NewContentsForTesting(opts ConfigOptionsForTesting) ([]byte, error) {
return contents, nil
}
// NewConfigForTesting creates a new bootstrap configuration from the provided
// contents, for testing purposes.
//
// # Testing-Only
func NewConfigForTesting(contents []byte) (*Config, error) {
return newConfigFromContents(contents)
}
// certproviderNameAndConfig is the internal representation of
// the`certificate_providers` field in the bootstrap configuration.
type certproviderNameAndConfig struct {
@ -810,44 +793,3 @@ func (n node) toProto() *v3corepb.Node {
ClientFeatures: slices.Clone(n.clientFeatures),
}
}
// SetFallbackBootstrapConfig sets the fallback bootstrap configuration to be
// used when the bootstrap environment variables are unset.
//
// The provided configuration must be valid JSON. Returns a non-nil error if
// parsing the provided configuration fails.
func SetFallbackBootstrapConfig(cfgJSON []byte) error {
config, err := newConfigFromContents(cfgJSON)
if err != nil {
return err
}
configMu.Lock()
defer configMu.Unlock()
fallbackBootstrapCfg = config
return nil
}
// UnsetFallbackBootstrapConfigForTesting unsets the fallback bootstrap
// configuration to be used when the bootstrap environment variables are unset.
//
// # Testing-Only
func UnsetFallbackBootstrapConfigForTesting() {
configMu.Lock()
defer configMu.Unlock()
fallbackBootstrapCfg = nil
}
// fallbackBootstrapConfig returns the fallback bootstrap configuration
// that will be used by the xDS client when the bootstrap environment
// variables are unset.
func fallbackBootstrapConfig() *Config {
configMu.Lock()
defer configMu.Unlock()
return fallbackBootstrapCfg
}
var (
configMu sync.Mutex
fallbackBootstrapCfg *Config
)

View File

@ -131,12 +131,12 @@ func (s) TestClientResourceVersionAfterStreamRestart(t *testing.T) {
bootstrapContents := e2e.DefaultBootstrapContents(t, nodeID, managementServer.Address)
// Create an xDS resolver with the above bootstrap configuration.
var xdsResolver resolver.Builder
if newResolver := internal.NewXDSResolverWithConfigForTesting; newResolver != nil {
xdsResolver, err = newResolver.(func([]byte) (resolver.Builder, error))(bootstrapContents)
if err != nil {
t.Fatalf("Failed to create xDS resolver for testing: %v", err)
}
if internal.NewXDSResolverWithConfigForTesting == nil {
t.Fatalf("internal.NewXDSResolverWithConfigForTesting is nil")
}
xdsResolver, err := internal.NewXDSResolverWithConfigForTesting.(func([]byte) (resolver.Builder, error))(bootstrapContents)
if err != nil {
t.Fatalf("Failed to create xDS resolver for testing: %v", err)
}
server := stubserver.StartTestService(t, nil)

View File

@ -129,12 +129,12 @@ func (s) TestClientSideXDS_WithNoCertificateProvidersInBootstrap_Failure(t *test
}
// Create an xDS resolver with the above bootstrap configuration.
var resolverBuilder resolver.Builder
if newResolver := internal.NewXDSResolverWithConfigForTesting; newResolver != nil {
resolverBuilder, err = newResolver.(func([]byte) (resolver.Builder, error))(bc)
if err != nil {
t.Fatalf("Failed to create xDS resolver for testing: %v", err)
}
if internal.NewXDSResolverWithConfigForTesting == nil {
t.Fatalf("internal.NewXDSResolverWithConfigForTesting is nil")
}
resolverBuilder, err := internal.NewXDSResolverWithConfigForTesting.(func([]byte) (resolver.Builder, error))(bc)
if err != nil {
t.Fatalf("Failed to create xDS resolver for testing: %v", err)
}
// Spin up a test backend.

View File

@ -105,12 +105,12 @@ func (s) TestClientCustomDialerFromCredentialsBundle(t *testing.T) {
}
// Create an xDS resolver with the above bootstrap configuration.
var resolverBuilder resolver.Builder
if newResolver := internal.NewXDSResolverWithConfigForTesting; newResolver != nil {
resolverBuilder, err = newResolver.(func([]byte) (resolver.Builder, error))(bc)
if err != nil {
t.Fatalf("Failed to create xDS resolver for testing: %v", err)
}
if internal.NewXDSResolverWithConfigForTesting == nil {
t.Fatalf("internal.NewXDSResolverWithConfigForTesting is nil")
}
resolverBuilder, err := internal.NewXDSResolverWithConfigForTesting.(func([]byte) (resolver.Builder, error))(bc)
if err != nil {
t.Fatalf("Failed to create xDS resolver for testing: %v", err)
}
// Spin up a test backend.

View File

@ -87,8 +87,10 @@ func (s) TestClientSideFederation(t *testing.T) {
t.Fatalf("Failed to create bootstrap file: %v", err)
}
resolverBuilder := internal.NewXDSResolverWithConfigForTesting.(func([]byte) (resolver.Builder, error))
resolver, err := resolverBuilder(bootstrapContents)
if internal.NewXDSResolverWithConfigForTesting == nil {
t.Fatalf("internal.NewXDSResolverWithConfigForTesting is nil")
}
resolver, err := internal.NewXDSResolverWithConfigForTesting.(func([]byte) (resolver.Builder, error))(bootstrapContents)
if err != nil {
t.Fatalf("Failed to create xDS resolver for testing: %v", err)
}
@ -181,8 +183,10 @@ func (s) TestClientSideFederationWithOnlyXDSTPStyleLDS(t *testing.T) {
t.Fatalf("Failed to create bootstrap file: %v", err)
}
resolverBuilder := internal.NewXDSResolverWithConfigForTesting.(func([]byte) (resolver.Builder, error))
resolver, err := resolverBuilder(bootstrapContents)
if internal.NewXDSResolverWithConfigForTesting == nil {
t.Fatalf("internal.NewXDSResolverWithConfigForTesting is nil")
}
resolver, err := internal.NewXDSResolverWithConfigForTesting.(func([]byte) (resolver.Builder, error))(bootstrapContents)
if err != nil {
t.Fatalf("Failed to create xDS resolver for testing: %v", err)
}

View File

@ -289,8 +289,10 @@ func generateBootstrapContents(t *testing.T, serverURI string, ignoreResourceDel
// as parameter.
func xdsResolverBuilder(t *testing.T, bs []byte) resolver.Builder {
t.Helper()
resolverBuilder := internal.NewXDSResolverWithConfigForTesting.(func([]byte) (resolver.Builder, error))
xdsR, err := resolverBuilder(bs)
if internal.NewXDSResolverWithConfigForTesting == nil {
t.Fatalf("internal.NewXDSResolverWithConfigForTesting is nil")
}
xdsR, err := internal.NewXDSResolverWithConfigForTesting.(func([]byte) (resolver.Builder, error))(bs)
if err != nil {
t.Fatalf("Creating xDS resolver for testing failed for config %q: %v", string(bs), err)
}

View File

@ -329,13 +329,12 @@ func (s) TestUnmarshalCluster_WithUpdateValidatorFunc(t *testing.T) {
bootstrapContents := e2e.DefaultBootstrapContents(t, nodeID, managementServer.Address)
// Create an xDS resolver with the above bootstrap configuration.
var xdsResolver resolver.Builder
if newResolver := internal.NewXDSResolverWithConfigForTesting; newResolver != nil {
var err error
xdsResolver, err = newResolver.(func([]byte) (resolver.Builder, error))(bootstrapContents)
if err != nil {
t.Fatalf("Failed to create xDS resolver for testing: %v", err)
}
if internal.NewXDSResolverWithConfigForTesting == nil {
t.Fatalf("internal.NewXDSResolverWithConfigForTesting is nil")
}
xdsResolver, err := internal.NewXDSResolverWithConfigForTesting.(func([]byte) (resolver.Builder, error))(bootstrapContents)
if err != nil {
t.Fatalf("Failed to create xDS resolver for testing: %v", err)
}
server := stubserver.StartTestService(t, nil)

View File

@ -325,13 +325,12 @@ func (s) TestServerSideXDS_SecurityConfigChange(t *testing.T) {
bootstrapContents := e2e.DefaultBootstrapContents(t, nodeID, managementServer.Address)
// Create an xDS resolver with the above bootstrap configuration.
var xdsResolver resolver.Builder
if newResolver := internal.NewXDSResolverWithConfigForTesting; newResolver != nil {
var err error
xdsResolver, err = newResolver.(func([]byte) (resolver.Builder, error))(bootstrapContents)
if err != nil {
t.Fatalf("Failed to create xDS resolver for testing: %v", err)
}
if internal.NewXDSResolverWithConfigForTesting == nil {
t.Fatalf("internal.NewXDSResolverWithConfigForTesting is nil")
}
xdsResolver, err := internal.NewXDSResolverWithConfigForTesting.(func([]byte) (resolver.Builder, error))(bootstrapContents)
if err != nil {
t.Fatalf("Failed to create xDS resolver for testing: %v", err)
}
lis, cleanup2 := setupGRPCServer(t, bootstrapContents)

View File

@ -35,6 +35,7 @@ import (
"google.golang.org/grpc/internal/pretty"
"google.golang.org/grpc/internal/testutils"
"google.golang.org/grpc/internal/testutils/xds/e2e"
"google.golang.org/grpc/internal/xds/bootstrap"
"google.golang.org/grpc/xds/csds"
"google.golang.org/grpc/xds/internal/xdsclient"
"google.golang.org/grpc/xds/internal/xdsclient/xdsresource"
@ -220,22 +221,28 @@ func (s) TestCSDS(t *testing.T) {
// Create a bootstrap contents pointing to the above management server.
nodeID := uuid.New().String()
bootstrapContents := e2e.DefaultBootstrapContents(t, nodeID, mgmtServer.Address)
config, err := bootstrap.NewConfigFromContents(bootstrapContents)
if err != nil {
t.Fatalf("Failed to parse bootstrap contents: %s, %v", string(bootstrapContents), err)
}
// We use the default xDS client pool here because the CSDS service reports
// on the state of the default xDS client which is implicitly managed
// within the xdsclient.DefaultPool.
xdsclient.DefaultPool.SetFallbackBootstrapConfig(config)
defer func() { xdsclient.DefaultPool.UnsetBootstrapConfigForTesting() }()
// Create two xDS clients, with different names. These should end up
// creating two different xDS clients.
const xdsClient1Name = "xds-csds-client-1"
xdsClient1, xdsClose1, err := xdsclient.NewForTesting(xdsclient.OptionsForTesting{
Name: xdsClient1Name,
Contents: bootstrapContents,
xdsClient1, xdsClose1, err := xdsclient.DefaultPool.NewClientForTesting(xdsclient.OptionsForTesting{
Name: xdsClient1Name,
})
if err != nil {
t.Fatalf("Failed to create xDS client: %v", err)
}
defer xdsClose1()
const xdsClient2Name = "xds-csds-client-2"
xdsClient2, xdsClose2, err := xdsclient.NewForTesting(xdsclient.OptionsForTesting{
Name: xdsClient2Name,
Contents: bootstrapContents,
xdsClient2, xdsClose2, err := xdsclient.DefaultPool.NewClientForTesting(xdsclient.OptionsForTesting{
Name: xdsClient2Name,
})
if err != nil {
t.Fatalf("Failed to create xDS client: %v", err)
@ -417,22 +424,28 @@ func (s) TestCSDS_NACK(t *testing.T) {
// Create a bootstrap contents pointing to the above management server.
nodeID := uuid.New().String()
bootstrapContents := e2e.DefaultBootstrapContents(t, nodeID, mgmtServer.Address)
config, err := bootstrap.NewConfigFromContents(bootstrapContents)
if err != nil {
t.Fatalf("Failed to parse bootstrap contents: %s, %v", string(bootstrapContents), err)
}
// We use the default xDS client pool here because the CSDS service reports
// on the state of the default xDS client which is implicitly managed
// within the xdsclient.DefaultPool.
xdsclient.DefaultPool.SetFallbackBootstrapConfig(config)
defer func() { xdsclient.DefaultPool.UnsetBootstrapConfigForTesting() }()
// Create two xDS clients, with different names. These should end up
// creating two different xDS clients.
const xdsClient1Name = "xds-csds-client-1"
xdsClient1, xdsClose1, err := xdsclient.NewForTesting(xdsclient.OptionsForTesting{
Name: xdsClient1Name,
Contents: bootstrapContents,
xdsClient1, xdsClose1, err := xdsclient.DefaultPool.NewClientForTesting(xdsclient.OptionsForTesting{
Name: xdsClient1Name,
})
if err != nil {
t.Fatalf("Failed to create xDS client: %v", err)
}
defer xdsClose1()
const xdsClient2Name = "xds-csds-client-2"
xdsClient2, xdsClose2, err := xdsclient.NewForTesting(xdsclient.OptionsForTesting{
Name: xdsClient2Name,
Contents: bootstrapContents,
xdsClient2, xdsClose2, err := xdsclient.DefaultPool.NewClientForTesting(xdsclient.OptionsForTesting{
Name: xdsClient2Name,
})
if err != nil {
t.Fatalf("Failed to create xDS client: %v", err)

View File

@ -39,6 +39,7 @@ import (
internalgrpclog "google.golang.org/grpc/internal/grpclog"
"google.golang.org/grpc/internal/xds/bootstrap"
"google.golang.org/grpc/resolver"
"google.golang.org/grpc/xds/internal/xdsclient"
_ "google.golang.org/grpc/xds" // To register xds resolvers and balancers.
)
@ -62,8 +63,9 @@ var (
universeDomainMu sync.Mutex
universeDomain = ""
// For overriding in unittests.
onGCE = googlecloud.OnGCE
randInt = rand.Int
onGCE = googlecloud.OnGCE
randInt = rand.Int
xdsClientPool = xdsclient.DefaultPool
)
func init() {
@ -155,9 +157,11 @@ func (c2pResolverBuilder) Build(t resolver.Target, cc resolver.ClientConn, opts
if err != nil {
return nil, fmt.Errorf("failed to marshal bootstrap configuration: %v", err)
}
if err := bootstrap.SetFallbackBootstrapConfig(cfgJSON); err != nil {
return nil, fmt.Errorf("failed to set fallback 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)
}
xdsClientPool.SetFallbackBootstrapConfig(config)
t = resolver.Target{
URL: url.URL{

View File

@ -32,6 +32,7 @@ import (
"google.golang.org/grpc/internal/grpctest"
"google.golang.org/grpc/internal/xds/bootstrap"
"google.golang.org/grpc/resolver"
"google.golang.org/grpc/xds/internal/xdsclient"
)
type s struct {
@ -114,6 +115,11 @@ func (s) TestBuildWithBootstrapEnvSet(t *testing.T) {
*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 {
@ -157,7 +163,7 @@ func bootstrapConfig(t *testing.T, opts bootstrap.ConfigOptionsForTesting) *boot
if err != nil {
t.Fatalf("Failed to create bootstrap contents: %v", err)
}
cfg, err := bootstrap.NewConfigForTesting(contents)
cfg, err := bootstrap.NewConfigFromContents(contents)
if err != nil {
t.Fatalf("Failed to create bootstrap config: %v", err)
}
@ -286,6 +292,14 @@ func (s) TestBuildXDS(t *testing.T) {
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.
r, err := builder.Build(resolver.Target{}, nil, resolver.BuildOptions{})
if err != nil {
@ -298,8 +312,8 @@ func (s) TestBuildXDS(t *testing.T) {
t.Fatalf("Build() returned %#v, want xds resolver", r)
}
gotConfig, err := bootstrap.GetConfiguration()
if err != nil {
gotConfig := xdsClientPool.BootstrapConfigForTesting()
if gotConfig == nil {
t.Fatalf("Failed to get bootstrap config: %v", err)
}
if diff := cmp.Diff(tt.wantBootstrapConfig, gotConfig); diff != "" {
@ -375,6 +389,11 @@ func (s) TestSetUniverseDomainNonDefault(t *testing.T) {
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.
r, err := builder.Build(resolver.Target{}, nil, resolver.BuildOptions{})
if err != nil {
@ -387,8 +406,8 @@ func (s) TestSetUniverseDomainNonDefault(t *testing.T) {
t.Fatalf("Build() returned %#v, want xds resolver", r)
}
gotConfig, err := bootstrap.GetConfiguration()
if err != nil {
gotConfig := xdsClientPool.BootstrapConfigForTesting()
if gotConfig == nil {
t.Fatalf("Failed to get bootstrap config: %v", err)
}
@ -442,6 +461,11 @@ func (s) TestDefaultUniverseDomain(t *testing.T) {
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.
r, err := builder.Build(resolver.Target{}, nil, resolver.BuildOptions{})
if err != nil {
@ -454,8 +478,8 @@ func (s) TestDefaultUniverseDomain(t *testing.T) {
t.Fatalf("Build() returned %#v, want xds resolver", r)
}
gotConfig, err := bootstrap.GetConfiguration()
if err != nil {
gotConfig := xdsClientPool.BootstrapConfigForTesting()
if gotConfig == nil {
t.Fatalf("Failed to get bootstrap config: %v", err)
}

View File

@ -139,9 +139,13 @@ func registerWrappedCDSPolicyWithNewSubConnOverride(t *testing.T, ch chan *xdscr
func setupForSecurityTests(t *testing.T, bootstrapContents []byte, clientCreds, serverCreds credentials.TransportCredentials) (*grpc.ClientConn, string) {
t.Helper()
xdsClient, xdsClose, err := xdsclient.NewForTesting(xdsclient.OptionsForTesting{
Name: t.Name(),
Contents: bootstrapContents,
config, err := bootstrap.NewConfigFromContents(bootstrapContents)
if err != nil {
t.Fatalf("Failed to parse bootstrap contents: %s, %v", string(bootstrapContents), err)
}
pool := xdsclient.NewPool(config)
xdsClient, xdsClose, err := pool.NewClientForTesting(xdsclient.OptionsForTesting{
Name: t.Name(),
})
if err != nil {
t.Fatalf("Failed to create xDS client: %v", err)

View File

@ -241,9 +241,13 @@ func setupWithManagementServerAndListener(t *testing.T, lis net.Listener) (*e2e.
nodeID := uuid.New().String()
bc := e2e.DefaultBootstrapContents(t, nodeID, mgmtServer.Address)
xdsC, xdsClose, err := xdsclient.NewForTesting(xdsclient.OptionsForTesting{
Name: t.Name(),
Contents: bc,
config, err := bootstrap.NewConfigFromContents(bc)
if err != nil {
t.Fatalf("Failed to parse bootstrap contents: %s, %v", string(bc), err)
}
pool := xdsclient.NewPool(config)
xdsC, xdsClose, err := pool.NewClientForTesting(xdsclient.OptionsForTesting{
Name: t.Name(),
})
if err != nil {
t.Fatalf("Failed to create xDS client: %v", err)
@ -364,9 +368,13 @@ func (s) TestConfigurationUpdate_EmptyCluster(t *testing.T) {
nodeID := uuid.New().String()
bc := e2e.DefaultBootstrapContents(t, nodeID, mgmtServer.Address)
xdsClient, xdsClose, err := xdsclient.NewForTesting(xdsclient.OptionsForTesting{
Name: t.Name(),
Contents: bc,
config, err := bootstrap.NewConfigFromContents(bc)
if err != nil {
t.Fatalf("Failed to parse bootstrap contents: %s, %v", string(bc), err)
}
pool := xdsclient.NewPool(config)
xdsClient, xdsClose, err := pool.NewClientForTesting(xdsclient.OptionsForTesting{
Name: t.Name(),
})
if err != nil {
t.Fatalf("Failed to create xDS client: %v", err)

View File

@ -36,6 +36,7 @@ import (
"google.golang.org/grpc/internal/stubserver"
"google.golang.org/grpc/internal/testutils/pickfirst"
"google.golang.org/grpc/internal/testutils/xds/e2e"
"google.golang.org/grpc/internal/xds/bootstrap"
"google.golang.org/grpc/peer"
"google.golang.org/grpc/resolver"
"google.golang.org/grpc/resolver/manual"
@ -1180,9 +1181,13 @@ func (s) TestAggregateCluster_Fallback_EDS_ResourceNotFound(t *testing.T) {
// Create an xDS client talking to the above management server, configured
// with a short watch expiry timeout.
xdsClient, close, err := xdsclient.NewForTesting(xdsclient.OptionsForTesting{
config, err := bootstrap.NewConfigFromContents(bootstrapContents)
if err != nil {
t.Fatalf("Failed to parse bootstrap contents: %s, %v", string(bootstrapContents), err)
}
pool := xdsclient.NewPool(config)
xdsClient, close, err := pool.NewClientForTesting(xdsclient.OptionsForTesting{
Name: t.Name(),
Contents: bootstrapContents,
WatchExpiryTimeout: defaultTestWatchExpiryTimeout,
})
if err != nil {

View File

@ -38,6 +38,7 @@ import (
"google.golang.org/grpc/internal/stubserver"
"google.golang.org/grpc/internal/testutils"
"google.golang.org/grpc/internal/testutils/xds/e2e"
"google.golang.org/grpc/internal/xds/bootstrap"
"google.golang.org/grpc/resolver"
"google.golang.org/grpc/resolver/manual"
"google.golang.org/grpc/serviceconfig"
@ -74,9 +75,13 @@ func setupAndDial(t *testing.T, bootstrapContents []byte) (*grpc.ClientConn, fun
t.Helper()
// Create an xDS client for use by the cluster_resolver LB policy.
xdsC, xdsClose, err := xdsclient.NewForTesting(xdsclient.OptionsForTesting{
Name: t.Name(),
Contents: bootstrapContents,
config, err := bootstrap.NewConfigFromContents(bootstrapContents)
if err != nil {
t.Fatalf("Failed to parse bootstrap contents: %s, %v", string(bootstrapContents), err)
}
pool := xdsclient.NewPool(config)
xdsC, xdsClose, err := pool.NewClientForTesting(xdsclient.OptionsForTesting{
Name: t.Name(),
})
if err != nil {
t.Fatalf("Failed to create xDS client: %v", err)

View File

@ -40,6 +40,7 @@ import (
"google.golang.org/grpc/internal/testutils"
rrutil "google.golang.org/grpc/internal/testutils/roundrobin"
"google.golang.org/grpc/internal/testutils/xds/e2e"
"google.golang.org/grpc/internal/xds/bootstrap"
"google.golang.org/grpc/peer"
"google.golang.org/grpc/resolver"
"google.golang.org/grpc/resolver/manual"
@ -155,9 +156,13 @@ func (s) TestEDS_OneLocality(t *testing.T) {
}
// Create an xDS client for use by the cluster_resolver LB policy.
client, close, err := xdsclient.NewForTesting(xdsclient.OptionsForTesting{
Name: t.Name(),
Contents: bootstrapContents,
config, err := bootstrap.NewConfigFromContents(bootstrapContents)
if err != nil {
t.Fatalf("Failed to parse bootstrap contents: %s, %v", string(bootstrapContents), err)
}
pool := xdsclient.NewPool(config)
client, close, err := pool.NewClientForTesting(xdsclient.OptionsForTesting{
Name: t.Name(),
})
if err != nil {
t.Fatalf("Failed to create xDS client: %v", err)
@ -290,9 +295,13 @@ func (s) TestEDS_MultipleLocalities(t *testing.T) {
}
// Create an xDS client for use by the cluster_resolver LB policy.
client, close, err := xdsclient.NewForTesting(xdsclient.OptionsForTesting{
Name: t.Name(),
Contents: bootstrapContents,
config, err := bootstrap.NewConfigFromContents(bootstrapContents)
if err != nil {
t.Fatalf("Failed to parse bootstrap contents: %s, %v", string(bootstrapContents), err)
}
pool := xdsclient.NewPool(config)
client, close, err := pool.NewClientForTesting(xdsclient.OptionsForTesting{
Name: t.Name(),
})
if err != nil {
t.Fatalf("Failed to create xDS client: %v", err)
@ -454,9 +463,13 @@ func (s) TestEDS_EndpointsHealth(t *testing.T) {
}
// Create an xDS client for use by the cluster_resolver LB policy.
client, close, err := xdsclient.NewForTesting(xdsclient.OptionsForTesting{
Name: t.Name(),
Contents: bootstrapContents,
config, err := bootstrap.NewConfigFromContents(bootstrapContents)
if err != nil {
t.Fatalf("Failed to parse bootstrap contents: %s, %v", string(bootstrapContents), err)
}
pool := xdsclient.NewPool(config)
client, close, err := pool.NewClientForTesting(xdsclient.OptionsForTesting{
Name: t.Name(),
})
if err != nil {
t.Fatalf("Failed to create xDS client: %v", err)
@ -527,9 +540,13 @@ func (s) TestEDS_EmptyUpdate(t *testing.T) {
}
// Create an xDS client for use by the cluster_resolver LB policy.
client, close, err := xdsclient.NewForTesting(xdsclient.OptionsForTesting{
Name: t.Name(),
Contents: bootstrapContents,
config, err := bootstrap.NewConfigFromContents(bootstrapContents)
if err != nil {
t.Fatalf("Failed to parse bootstrap contents: %s, %v", string(bootstrapContents), err)
}
pool := xdsclient.NewPool(config)
client, close, err := pool.NewClientForTesting(xdsclient.OptionsForTesting{
Name: t.Name(),
})
if err != nil {
t.Fatalf("Failed to create xDS client: %v", err)
@ -926,9 +943,13 @@ func (s) TestEDS_BadUpdateWithoutPreviousGoodUpdate(t *testing.T) {
}
// Create an xDS client for use by the cluster_resolver LB policy.
xdsClient, close, err := xdsclient.NewForTesting(xdsclient.OptionsForTesting{
Name: t.Name(),
Contents: bootstrapContents,
config, err := bootstrap.NewConfigFromContents(bootstrapContents)
if err != nil {
t.Fatalf("Failed to parse bootstrap contents: %s, %v", string(bootstrapContents), err)
}
pool := xdsclient.NewPool(config)
xdsClient, close, err := pool.NewClientForTesting(xdsclient.OptionsForTesting{
Name: t.Name(),
})
if err != nil {
t.Fatalf("Failed to create xDS client: %v", err)
@ -998,9 +1019,13 @@ func (s) TestEDS_BadUpdateWithPreviousGoodUpdate(t *testing.T) {
}
// Create an xDS client for use by the cluster_resolver LB policy.
xdsClient, close, err := xdsclient.NewForTesting(xdsclient.OptionsForTesting{
Name: t.Name(),
Contents: bootstrapContents,
config, err := bootstrap.NewConfigFromContents(bootstrapContents)
if err != nil {
t.Fatalf("Failed to parse bootstrap contents: %s, %v", string(bootstrapContents), err)
}
pool := xdsclient.NewPool(config)
xdsClient, close, err := pool.NewClientForTesting(xdsclient.OptionsForTesting{
Name: t.Name(),
})
if err != nil {
t.Fatalf("Failed to create xDS client: %v", err)
@ -1070,9 +1095,13 @@ func (s) TestEDS_ResourceNotFound(t *testing.T) {
// with a short watch expiry timeout.
nodeID := uuid.New().String()
bc := e2e.DefaultBootstrapContents(t, nodeID, mgmtServer.Address)
xdsClient, close, err := xdsclient.NewForTesting(xdsclient.OptionsForTesting{
config, err := bootstrap.NewConfigFromContents(bc)
if err != nil {
t.Fatalf("Failed to parse bootstrap contents: %s, %v", string(bc), err)
}
pool := xdsclient.NewPool(config)
xdsClient, close, err := pool.NewClientForTesting(xdsclient.OptionsForTesting{
Name: t.Name(),
Contents: bc,
WatchExpiryTimeout: defaultTestWatchExpiryTimeout,
})
if err != nil {
@ -1244,6 +1273,11 @@ func (s) TestEDS_EndpointWithMultipleAddresses(t *testing.T) {
// Create bootstrap configuration pointing to the above management server.
nodeID := uuid.New().String()
bootstrapContents := e2e.DefaultBootstrapContents(t, nodeID, mgmtServer.Address)
config, err := bootstrap.NewConfigFromContents(bootstrapContents)
if err != nil {
t.Fatalf("Failed to parse bootstrap contents: %s, %v", string(bootstrapContents), err)
}
pool := xdsclient.NewPool(config)
// Create xDS resources for consumption by the test. We start off with a
// single backend in a single EDS locality.
@ -1260,9 +1294,8 @@ func (s) TestEDS_EndpointWithMultipleAddresses(t *testing.T) {
// Create an xDS client talking to the above management server, configured
// with a short watch expiry timeout.
xdsClient, close, err := xdsclient.NewForTesting(xdsclient.OptionsForTesting{
Name: t.Name(),
Contents: bootstrapContents,
xdsClient, close, err := pool.NewClientForTesting(xdsclient.OptionsForTesting{
Name: t.Name(),
})
if err != nil {
t.Fatalf("Failed to create an xDS client: %v", err)

View File

@ -265,13 +265,12 @@ func setupManagementServerAndResolver(t *testing.T) (*e2e.ManagementServer, stri
bc := e2e.DefaultBootstrapContents(t, nodeID, xdsServer.Address)
// Create an xDS resolver with the above bootstrap configuration.
var r resolver.Builder
var err error
if newResolver := internal.NewXDSResolverWithConfigForTesting; newResolver != nil {
r, err = newResolver.(func([]byte) (resolver.Builder, error))(bc)
if err != nil {
t.Fatalf("Failed to create xDS resolver for testing: %v", err)
}
if internal.NewXDSResolverWithConfigForTesting == nil {
t.Fatalf("internal.NewXDSResolverWithConfigForTesting is nil")
}
r, err := internal.NewXDSResolverWithConfigForTesting.(func([]byte) (resolver.Builder, error))(bc)
if err != nil {
t.Fatalf("Failed to create xDS resolver for testing: %v", err)
}
return xdsServer, nodeID, r

View File

@ -36,11 +36,13 @@ import (
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/credentials/insecure"
"google.golang.org/grpc/internal"
"google.golang.org/grpc/internal/grpctest"
"google.golang.org/grpc/internal/stubserver"
"google.golang.org/grpc/internal/testutils"
"google.golang.org/grpc/internal/testutils/xds/e2e"
"google.golang.org/grpc/metadata"
"google.golang.org/grpc/resolver"
"google.golang.org/grpc/status"
"google.golang.org/protobuf/types/known/durationpb"
"google.golang.org/protobuf/types/known/wrapperspb"
@ -76,17 +78,18 @@ func Test(t *testing.T) {
// Returns the following:
// - the management server: tests use this to configure resources
// - nodeID expected by the management server: this is set in the Node proto
// sent by the xdsClient for queries.
// sent by the xdsClient for queries
// - the port the server is listening on
// - cleanup function to be invoked by the tests when done
func clientSetup(t *testing.T) (*e2e.ManagementServer, string, uint32) {
// - contents of the bootstrap configuration pointing to xDS management
// server
func clientSetup(t *testing.T) (*e2e.ManagementServer, string, uint32, []byte) {
// Spin up a xDS management server on a local port.
nodeID := uuid.New().String()
managementServer := e2e.StartManagementServer(t, e2e.ManagementServerOptions{})
// Create a bootstrap file in a temporary directory.
// Create a bootstrap config pointing to the above management server with
// the nodeID.
bootstrapContents := e2e.DefaultBootstrapContents(t, nodeID, managementServer.Address)
testutils.CreateBootstrapFileForTesting(t, bootstrapContents)
// Create a local listener.
lis, err := testutils.LocalTCPListener()
@ -114,7 +117,7 @@ func clientSetup(t *testing.T) (*e2e.ManagementServer, string, uint32) {
stubserver.StartTestService(t, stub)
t.Cleanup(stub.S.Stop)
return managementServer, nodeID, uint32(lis.Addr().(*net.TCPAddr).Port)
return managementServer, nodeID, uint32(lis.Addr().(*net.TCPAddr).Port), bootstrapContents
}
func (s) TestFaultInjection_Unary(t *testing.T) {
@ -456,7 +459,15 @@ func (s) TestFaultInjection_Unary(t *testing.T) {
}},
}}
fs, nodeID, port := clientSetup(t)
fs, nodeID, port, bc := clientSetup(t)
// Create an xDS resolver with the above bootstrap configuration.
if internal.NewXDSResolverWithConfigForTesting == nil {
t.Fatalf("internal.NewXDSResolverWithConfigForTesting is nil")
}
xdsResolver, err := internal.NewXDSResolverWithConfigForTesting.(func([]byte) (resolver.Builder, error))(bc)
if err != nil {
t.Fatalf("Failed to create xDS resolver for testing: %v", err)
}
for tcNum, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
@ -506,7 +517,7 @@ func (s) TestFaultInjection_Unary(t *testing.T) {
}
// Create a ClientConn and run the test case.
cc, err := grpc.NewClient("xds:///"+serviceName, grpc.WithTransportCredentials(insecure.NewCredentials()))
cc, err := grpc.NewClient("xds:///"+serviceName, grpc.WithTransportCredentials(insecure.NewCredentials()), grpc.WithResolvers(xdsResolver))
if err != nil {
t.Fatalf("failed to dial local test server: %v", err)
}
@ -538,7 +549,15 @@ func (s) TestFaultInjection_Unary(t *testing.T) {
}
func (s) TestFaultInjection_MaxActiveFaults(t *testing.T) {
fs, nodeID, port := clientSetup(t)
fs, nodeID, port, bc := clientSetup(t)
// Create an xDS resolver with the above bootstrap configuration.
if internal.NewXDSResolverWithConfigForTesting == nil {
t.Fatalf("internal.NewXDSResolverWithConfigForTesting is nil")
}
xdsResolver, err := internal.NewXDSResolverWithConfigForTesting.(func([]byte) (resolver.Builder, error))(bc)
if err != nil {
t.Fatalf("Failed to create xDS resolver for testing: %v", err)
}
resources := e2e.DefaultClientResources(e2e.ResourceParams{
DialTarget: "myservice",
NodeID: nodeID,
@ -548,8 +567,7 @@ func (s) TestFaultInjection_MaxActiveFaults(t *testing.T) {
})
hcm := new(v3httppb.HttpConnectionManager)
lis := resources.Listeners[0].GetApiListener().GetApiListener()
err := lis.UnmarshalTo(hcm)
if err != nil {
if err = lis.UnmarshalTo(hcm); err != nil {
t.Fatal(err)
}
@ -581,7 +599,7 @@ func (s) TestFaultInjection_MaxActiveFaults(t *testing.T) {
}
// Create a ClientConn
cc, err := grpc.NewClient("xds:///myservice", grpc.WithTransportCredentials(insecure.NewCredentials()))
cc, err := grpc.NewClient("xds:///myservice", grpc.WithTransportCredentials(insecure.NewCredentials()), grpc.WithResolvers(xdsResolver))
if err != nil {
t.Fatalf("failed to dial local test server: %v", err)
}

View File

@ -115,7 +115,7 @@ func (s) TestResolverClusterSpecifierPlugin(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout)
defer cancel()
nodeID := uuid.New().String()
mgmtServer, _, _ := setupManagementServerForTest(ctx, t, nodeID)
mgmtServer, _, _, bc := setupManagementServerForTest(ctx, t, nodeID)
// Configure resources on the management server.
listeners := []*v3listenerpb.Listener{e2e.DefaultClientListener(defaultTestServiceName, defaultTestRouteConfigName)}
@ -128,7 +128,7 @@ func (s) TestResolverClusterSpecifierPlugin(t *testing.T) {
})}
configureResourcesOnManagementServer(ctx, t, mgmtServer, nodeID, listeners, routes)
stateCh, _, _ := buildResolverForTarget(t, resolver.Target{URL: *testutils.MustParseURL("xds:///" + defaultTestServiceName)})
stateCh, _, _ := buildResolverForTarget(t, resolver.Target{URL: *testutils.MustParseURL("xds:///" + defaultTestServiceName)}, bc)
// Wait for an update from the resolver, and verify the service config.
wantSC := `
@ -205,7 +205,7 @@ func (s) TestXDSResolverDelayedOnCommittedCSP(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout)
defer cancel()
nodeID := uuid.New().String()
mgmtServer, _, _ := setupManagementServerForTest(ctx, t, nodeID)
mgmtServer, _, _, bc := setupManagementServerForTest(ctx, t, nodeID)
// Configure resources on the management server.
listeners := []*v3listenerpb.Listener{e2e.DefaultClientListener(defaultTestServiceName, defaultTestRouteConfigName)}
@ -218,7 +218,7 @@ func (s) TestXDSResolverDelayedOnCommittedCSP(t *testing.T) {
})}
configureResourcesOnManagementServer(ctx, t, mgmtServer, nodeID, listeners, routes)
stateCh, _, _ := buildResolverForTarget(t, resolver.Target{URL: *testutils.MustParseURL("xds:///" + defaultTestServiceName)})
stateCh, _, _ := buildResolverForTarget(t, resolver.Target{URL: *testutils.MustParseURL("xds:///" + defaultTestServiceName)}, bc)
// Wait for an update from the resolver, and verify the service config.
wantSC := `

View File

@ -78,17 +78,33 @@ var wantDefaultServiceConfig = fmt.Sprintf(`{
}]
}`, defaultTestClusterName, defaultTestClusterName)
// buildResolverForTarget builds an xDS resolver for the given target. It
// returns the following:
// buildResolverForTarget builds an xDS resolver for the given target. If
// the bootstrap contents are provided, it build the xDS resolver using them
// otherwise, it uses the default xDS resolver.
//
// It returns the following:
// - a channel to read updates from the resolver
// - a channel to read errors from the resolver
// - the newly created xDS resolver
func buildResolverForTarget(t *testing.T, target resolver.Target) (chan resolver.State, chan error, resolver.Resolver) {
func buildResolverForTarget(t *testing.T, target resolver.Target, bootstrapContents []byte) (chan resolver.State, chan error, resolver.Resolver) {
t.Helper()
builder := resolver.Get(xdsresolver.Scheme)
if builder == nil {
t.Fatalf("Scheme %q is not registered", xdsresolver.Scheme)
var builder resolver.Builder
if bootstrapContents != nil {
// Create an xDS resolver with the provided bootstrap configuration.
if internal.NewXDSResolverWithConfigForTesting == nil {
t.Fatalf("internal.NewXDSResolverWithConfigForTesting is nil")
}
var err error
builder, err = internal.NewXDSResolverWithConfigForTesting.(func([]byte) (resolver.Builder, error))(bootstrapContents)
if err != nil {
t.Fatalf("Failed to create xDS resolver for testing: %v", err)
}
} else {
builder = resolver.Get(xdsresolver.Scheme)
if builder == nil {
t.Fatalf("Scheme %q is not registered", xdsresolver.Scheme)
}
}
stateCh := make(chan resolver.State, 1)
@ -186,7 +202,9 @@ func verifyErrorFromResolver(ctx context.Context, t *testing.T, errCh chan error
// - A reference to the xDS management server
// - A channel to read requested Listener resource names
// - A channel to read requested RouteConfiguration resource names
func setupManagementServerForTest(ctx context.Context, t *testing.T, nodeID string) (*e2e.ManagementServer, chan []string, chan []string) {
// - Contents of the bootstrap configuration pointing to xDS management
// server
func setupManagementServerForTest(ctx context.Context, t *testing.T, nodeID string) (*e2e.ManagementServer, chan []string, chan []string, []byte) {
t.Helper()
listenerResourceNamesCh := make(chan []string, 1)
@ -224,8 +242,7 @@ func setupManagementServerForTest(ctx context.Context, t *testing.T, nodeID stri
// Create a bootstrap configuration specifying the above management server.
bootstrapContents := e2e.DefaultBootstrapContents(t, nodeID, mgmtServer.Address)
testutils.CreateBootstrapFileForTesting(t, bootstrapContents)
return mgmtServer, listenerResourceNamesCh, routeConfigResourceNamesCh
return mgmtServer, listenerResourceNamesCh, routeConfigResourceNamesCh, bootstrapContents
}
// Spins up an xDS management server and configures it with a default listener

View File

@ -46,14 +46,14 @@ func (s) TestServiceWatch_ListenerPointsToNewRouteConfiguration(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout)
defer cancel()
nodeID := uuid.New().String()
mgmtServer, lisCh, routeCfgCh := setupManagementServerForTest(ctx, t, nodeID)
mgmtServer, lisCh, routeCfgCh, bc := setupManagementServerForTest(ctx, t, nodeID)
// Configure resources on the management server.
listeners := []*v3listenerpb.Listener{e2e.DefaultClientListener(defaultTestServiceName, defaultTestRouteConfigName)}
routes := []*v3routepb.RouteConfiguration{e2e.DefaultRouteConfig(defaultTestRouteConfigName, defaultTestServiceName, defaultTestClusterName)}
configureResourcesOnManagementServer(ctx, t, mgmtServer, nodeID, listeners, routes)
stateCh, _, _ := buildResolverForTarget(t, resolver.Target{URL: *testutils.MustParseURL("xds:///" + defaultTestServiceName)})
stateCh, _, _ := buildResolverForTarget(t, resolver.Target{URL: *testutils.MustParseURL("xds:///" + defaultTestServiceName)}, bc)
// Verify initial update from the resolver.
waitForResourceNames(ctx, t, lisCh, []string{defaultTestServiceName})
@ -103,14 +103,14 @@ func (s) TestServiceWatch_ListenerPointsToInlineRouteConfiguration(t *testing.T)
ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout)
defer cancel()
nodeID := uuid.New().String()
mgmtServer, lisCh, routeCfgCh := setupManagementServerForTest(ctx, t, nodeID)
mgmtServer, lisCh, routeCfgCh, bc := setupManagementServerForTest(ctx, t, nodeID)
// Configure resources on the management server.
listeners := []*v3listenerpb.Listener{e2e.DefaultClientListener(defaultTestServiceName, defaultTestRouteConfigName)}
routes := []*v3routepb.RouteConfiguration{e2e.DefaultRouteConfig(defaultTestRouteConfigName, defaultTestServiceName, defaultTestClusterName)}
configureResourcesOnManagementServer(ctx, t, mgmtServer, nodeID, listeners, routes)
stateCh, _, _ := buildResolverForTarget(t, resolver.Target{URL: *testutils.MustParseURL("xds:///" + defaultTestServiceName)})
stateCh, _, _ := buildResolverForTarget(t, resolver.Target{URL: *testutils.MustParseURL("xds:///" + defaultTestServiceName)}, bc)
// Verify initial update from the resolver.
waitForResourceNames(ctx, t, lisCh, []string{defaultTestServiceName})

View File

@ -45,25 +45,29 @@ import (
const Scheme = "xds"
// newBuilderWithConfigForTesting creates a new xds resolver builder using a
// specific xds bootstrap config, so tests can use multiple xds clients in
// different ClientConns at the same time.
// specific xds bootstrap config, so tests can use multiple xDS clients in
// different ClientConns at the same time. The builder creates a new pool with
// the provided config and a new xDS client in that pool.
func newBuilderWithConfigForTesting(config []byte) (resolver.Builder, error) {
return &xdsResolverBuilder{
newXDSClient: func(name string) (xdsclient.XDSClient, func(), error) {
return xdsclient.NewForTesting(xdsclient.OptionsForTesting{Name: name, Contents: config})
config, err := bootstrap.NewConfigFromContents(config)
if err != nil {
return nil, nil, err
}
pool := xdsclient.NewPool(config)
return pool.NewClientForTesting(xdsclient.OptionsForTesting{Name: name})
},
}, nil
}
// newBuilderWithClientForTesting creates a new xds resolver builder using the
// specific xDS client, so that tests have complete control over the exact
// specific xDS client being used.
func newBuilderWithClientForTesting(client xdsclient.XDSClient) (resolver.Builder, error) {
// newBuilderWithPoolForTesting creates a new xds resolver builder using the
// specific xds client pool, so that tests have complete control over the exact
// specific xds client pool being used.
func newBuilderWithPoolForTesting(pool *xdsclient.Pool) (resolver.Builder, error) {
return &xdsResolverBuilder{
newXDSClient: func(string) (xdsclient.XDSClient, func(), error) {
// Returning an empty close func here means that the responsibility
// of closing the client lies with the caller.
return client, func() {}, nil
newXDSClient: func(name string) (xdsclient.XDSClient, func(), error) {
return pool.NewClientForTesting(xdsclient.OptionsForTesting{Name: name})
},
}, nil
}
@ -71,10 +75,10 @@ func newBuilderWithClientForTesting(client xdsclient.XDSClient) (resolver.Builde
func init() {
resolver.Register(&xdsResolverBuilder{})
internal.NewXDSResolverWithConfigForTesting = newBuilderWithConfigForTesting
internal.NewXDSResolverWithClientForTesting = newBuilderWithClientForTesting
internal.NewXDSResolverWithPoolForTesting = newBuilderWithPoolForTesting
rinternal.NewWRR = wrr.NewRandom
rinternal.NewXDSClient = xdsclient.New
rinternal.NewXDSClient = xdsclient.DefaultPool.NewClient
}
type xdsResolverBuilder struct {
@ -83,7 +87,7 @@ type xdsResolverBuilder struct {
// Build helps implement the resolver.Builder interface.
//
// The xds bootstrap process is performed (and a new xds client is built) every
// The xds bootstrap process is performed (and a new xDS client is built) every
// time an xds resolver is built.
func (b *xdsResolverBuilder) Build(target resolver.Target, cc resolver.ClientConn, opts resolver.BuildOptions) (_ resolver.Resolver, retErr error) {
r := &xdsResolver{

View File

@ -44,7 +44,6 @@ import (
"google.golang.org/grpc/xds/internal/balancer/clustermanager"
"google.golang.org/grpc/xds/internal/balancer/ringhash"
"google.golang.org/grpc/xds/internal/httpfilter"
xdsresolver "google.golang.org/grpc/xds/internal/resolver"
rinternal "google.golang.org/grpc/xds/internal/resolver/internal"
"google.golang.org/grpc/xds/internal/xdsclient"
"google.golang.org/grpc/xds/internal/xdsclient/xdsresource/version"
@ -67,18 +66,24 @@ import (
)
// Tests the case where xDS client creation is expected to fail because the
// bootstrap configuration is not specified. The test verifies that xDS resolver
// build fails as well.
// bootstrap configuration for the xDS client pool is not specified. The test
// verifies that xDS resolver build fails as well.
func (s) TestResolverBuilder_ClientCreationFails_NoBootstrap(t *testing.T) {
// Build an xDS resolver without specifying bootstrap env vars.
builder := resolver.Get(xdsresolver.Scheme)
if builder == nil {
t.Fatalf("Scheme %q is not registered", xdsresolver.Scheme)
// Build an xDS resolver specifying nil for bootstrap configuration for the
// xDS client pool.
pool := xdsclient.NewPool(nil)
var xdsResolver resolver.Builder
if newResolver := internal.NewXDSResolverWithPoolForTesting; newResolver != nil {
var err error
xdsResolver, err = newResolver.(func(*xdsclient.Pool) (resolver.Builder, error))(pool)
if err != nil {
t.Fatalf("Failed to create xDS resolver for testing: %v", err)
}
}
target := resolver.Target{URL: *testutils.MustParseURL("xds:///target")}
if _, err := builder.Build(target, nil, resolver.BuildOptions{}); err == nil {
t.Fatalf("xds Resolver Build(%v) succeeded when expected to fail, because there is not bootstrap configuration for the xDS client", target)
if _, err := xdsResolver.Build(target, nil, resolver.BuildOptions{}); err == nil {
t.Fatalf("xds Resolver Build(%v) succeeded when expected to fail, because there is no bootstrap configuration for the xDS client pool", pool)
}
}
@ -87,17 +92,19 @@ func (s) TestResolverBuilder_ClientCreationFails_NoBootstrap(t *testing.T) {
// fails with the expected error string.
func (s) TestResolverBuilder_AuthorityNotDefinedInBootstrap(t *testing.T) {
contents := e2e.DefaultBootstrapContents(t, "node-id", "dummy-management-server")
testutils.CreateBootstrapFileForTesting(t, contents)
builder := resolver.Get(xdsresolver.Scheme)
if builder == nil {
t.Fatalf("Scheme %q is not registered", xdsresolver.Scheme)
// Create an xDS resolver with the above bootstrap configuration.
if internal.NewXDSResolverWithConfigForTesting == nil {
t.Fatalf("internal.NewXDSResolverWithConfigForTesting is nil")
}
xdsResolver, err := internal.NewXDSResolverWithConfigForTesting.(func([]byte) (resolver.Builder, error))(contents)
if err != nil {
t.Fatalf("Failed to create xDS resolver for testing: %v", err)
}
target := resolver.Target{URL: *testutils.MustParseURL("xds://non-existing-authority/target")}
const wantErr = `authority "non-existing-authority" specified in dial target "xds://non-existing-authority/target" is not found in the bootstrap file`
r, err := builder.Build(target, &testutils.ResolverClientConn{Logger: t}, resolver.BuildOptions{})
r, err := xdsResolver.Build(target, &testutils.ResolverClientConn{Logger: t}, resolver.BuildOptions{})
if r != nil {
r.Close()
}
@ -157,7 +164,7 @@ func (s) TestResolverResourceName(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout)
defer cancel()
nodeID := uuid.New().String()
mgmtServer, lisCh, _ := setupManagementServerForTest(ctx, t, nodeID)
mgmtServer, lisCh, _, _ := setupManagementServerForTest(ctx, t, nodeID)
// Create a bootstrap configuration with test options.
opts := bootstrap.ConfigOptionsForTesting{
@ -183,9 +190,8 @@ func (s) TestResolverResourceName(t *testing.T) {
if err != nil {
t.Fatalf("Failed to create bootstrap configuration: %v", err)
}
testutils.CreateBootstrapFileForTesting(t, contents)
buildResolverForTarget(t, resolver.Target{URL: *testutils.MustParseURL(tt.dialTarget)})
buildResolverForTarget(t, resolver.Target{URL: *testutils.MustParseURL(tt.dialTarget)}, contents)
waitForResourceNames(ctx, t, lisCh, tt.wantResourceNames)
})
}
@ -222,7 +228,6 @@ func (s) TestResolverWatchCallbackAfterClose(t *testing.T) {
// Create a bootstrap configuration specifying the above management server.
nodeID := uuid.New().String()
contents := e2e.DefaultBootstrapContents(t, nodeID, mgmtServer.Address)
testutils.CreateBootstrapFileForTesting(t, contents)
// Configure resources on the management server.
listeners := []*v3listenerpb.Listener{e2e.DefaultClientListener(defaultTestServiceName, defaultTestRouteConfigName)}
@ -232,7 +237,7 @@ func (s) TestResolverWatchCallbackAfterClose(t *testing.T) {
configureResourcesOnManagementServer(ctx, t, mgmtServer, nodeID, listeners, routes)
// Wait for a discovery request for a route configuration resource.
stateCh, _, r := buildResolverForTarget(t, resolver.Target{URL: *testutils.MustParseURL("xds:///" + defaultTestServiceName)})
stateCh, _, r := buildResolverForTarget(t, resolver.Target{URL: *testutils.MustParseURL("xds:///" + defaultTestServiceName)}, contents)
waitForResourceNames(ctx, t, routeConfigResourceNamesCh, []string{defaultTestRouteConfigName})
// Close the resolver and unblock the management server.
@ -254,9 +259,16 @@ func (s) TestResolverCloseClosesXDSClient(t *testing.T) {
closeCh := make(chan struct{})
rinternal.NewXDSClient = func(string) (xdsclient.XDSClient, func(), error) {
bc := e2e.DefaultBootstrapContents(t, uuid.New().String(), "dummy-management-server-address")
c, cancel, err := xdsclient.NewForTesting(xdsclient.OptionsForTesting{
config, err := bootstrap.NewConfigFromContents(bc)
if err != nil {
t.Fatalf("Failed to parse bootstrap contents: %s, %v", string(bc), err)
}
pool := xdsclient.NewPool(config)
if err != nil {
t.Fatalf("Failed to create an xDS client pool: %v", err)
}
c, cancel, err := pool.NewClientForTesting(xdsclient.OptionsForTesting{
Name: t.Name(),
Contents: bc,
WatchExpiryTimeout: defaultTestTimeout,
})
return c, sync.OnceFunc(func() {
@ -266,7 +278,7 @@ func (s) TestResolverCloseClosesXDSClient(t *testing.T) {
}
defer func() { rinternal.NewXDSClient = origNewClient }()
_, _, r := buildResolverForTarget(t, resolver.Target{URL: *testutils.MustParseURL("xds:///my-service-client-side-xds")})
_, _, r := buildResolverForTarget(t, resolver.Target{URL: *testutils.MustParseURL("xds:///my-service-client-side-xds")}, nil)
r.Close()
select {
@ -287,7 +299,7 @@ func (s) TestResolverBadServiceUpdate(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout)
defer cancel()
nodeID := uuid.New().String()
mgmtServer, _, _ := setupManagementServerForTest(ctx, t, nodeID)
mgmtServer, _, _, bc := setupManagementServerForTest(ctx, t, nodeID)
// Configure a listener resource that is expected to be NACKed because it
// does not contain the `RouteSpecifier` field in the HTTPConnectionManager.
@ -308,7 +320,7 @@ func (s) TestResolverBadServiceUpdate(t *testing.T) {
configureResourcesOnManagementServer(ctx, t, mgmtServer, nodeID, []*v3listenerpb.Listener{lis}, nil)
// Build the resolver and expect an error update from it.
stateCh, errCh, _ := buildResolverForTarget(t, resolver.Target{URL: *testutils.MustParseURL("xds:///" + defaultTestServiceName)})
stateCh, errCh, _ := buildResolverForTarget(t, resolver.Target{URL: *testutils.MustParseURL("xds:///" + defaultTestServiceName)}, bc)
wantErr := "no RouteSpecifier"
verifyErrorFromResolver(ctx, t, errCh, wantErr)
@ -391,7 +403,7 @@ func (s) TestResolverGoodServiceUpdate(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout)
defer cancel()
nodeID := uuid.New().String()
mgmtServer, _, _ := setupManagementServerForTest(ctx, t, nodeID)
mgmtServer, _, _, bc := setupManagementServerForTest(ctx, t, nodeID)
// Configure the management server with a good listener resource and a
// route configuration resource, as specified by the test case.
@ -399,7 +411,7 @@ func (s) TestResolverGoodServiceUpdate(t *testing.T) {
routes := []*v3routepb.RouteConfiguration{tt.routeConfig}
configureResourcesOnManagementServer(ctx, t, mgmtServer, nodeID, listeners, routes)
stateCh, _, _ := buildResolverForTarget(t, resolver.Target{URL: *testutils.MustParseURL("xds:///" + defaultTestServiceName)})
stateCh, _, _ := buildResolverForTarget(t, resolver.Target{URL: *testutils.MustParseURL("xds:///" + defaultTestServiceName)}, bc)
// Read the update pushed by the resolver to the ClientConn.
cs := verifyUpdateFromResolver(ctx, t, stateCh, tt.wantServiceConfig)
@ -432,7 +444,7 @@ func (s) TestResolverRequestHash(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout)
defer cancel()
nodeID := uuid.New().String()
mgmtServer, _, _ := setupManagementServerForTest(ctx, t, nodeID)
mgmtServer, _, _, bc := setupManagementServerForTest(ctx, t, nodeID)
// Configure the management server with a good listener resource and a
// route configuration resource that specifies a hash policy.
@ -467,7 +479,7 @@ func (s) TestResolverRequestHash(t *testing.T) {
configureResourcesOnManagementServer(ctx, t, mgmtServer, nodeID, listeners, routes)
// Build the resolver and read the config selector out of it.
stateCh, _, _ := buildResolverForTarget(t, resolver.Target{URL: *testutils.MustParseURL("xds:///" + defaultTestServiceName)})
stateCh, _, _ := buildResolverForTarget(t, resolver.Target{URL: *testutils.MustParseURL("xds:///" + defaultTestServiceName)}, bc)
cs := verifyUpdateFromResolver(ctx, t, stateCh, "")
// Selecting a config when there was a hash policy specified in the route
@ -495,14 +507,14 @@ func (s) TestResolverRemovedWithRPCs(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout)
defer cancel()
nodeID := uuid.New().String()
mgmtServer, _, _ := setupManagementServerForTest(ctx, t, nodeID)
mgmtServer, _, _, bc := setupManagementServerForTest(ctx, t, nodeID)
// Configure resources on the management server.
listeners := []*v3listenerpb.Listener{e2e.DefaultClientListener(defaultTestServiceName, defaultTestRouteConfigName)}
routes := []*v3routepb.RouteConfiguration{e2e.DefaultRouteConfig(defaultTestRouteConfigName, defaultTestServiceName, defaultTestClusterName)}
configureResourcesOnManagementServer(ctx, t, mgmtServer, nodeID, listeners, routes)
stateCh, _, _ := buildResolverForTarget(t, resolver.Target{URL: *testutils.MustParseURL("xds:///" + defaultTestServiceName)})
stateCh, _, _ := buildResolverForTarget(t, resolver.Target{URL: *testutils.MustParseURL("xds:///" + defaultTestServiceName)}, bc)
// Read the update pushed by the resolver to the ClientConn.
cs := verifyUpdateFromResolver(ctx, t, stateCh, wantDefaultServiceConfig)
@ -597,14 +609,14 @@ func (s) TestResolverRemovedResource(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout)
defer cancel()
nodeID := uuid.New().String()
mgmtServer, _, _ := setupManagementServerForTest(ctx, t, nodeID)
mgmtServer, _, _, bc := setupManagementServerForTest(ctx, t, nodeID)
// Configure resources on the management server.
listeners := []*v3listenerpb.Listener{e2e.DefaultClientListener(defaultTestServiceName, defaultTestRouteConfigName)}
routes := []*v3routepb.RouteConfiguration{e2e.DefaultRouteConfig(defaultTestRouteConfigName, defaultTestServiceName, defaultTestClusterName)}
configureResourcesOnManagementServer(ctx, t, mgmtServer, nodeID, listeners, routes)
stateCh, _, _ := buildResolverForTarget(t, resolver.Target{URL: *testutils.MustParseURL("xds:///" + defaultTestServiceName)})
stateCh, _, _ := buildResolverForTarget(t, resolver.Target{URL: *testutils.MustParseURL("xds:///" + defaultTestServiceName)}, bc)
// Read the update pushed by the resolver to the ClientConn.
cs := verifyUpdateFromResolver(ctx, t, stateCh, wantDefaultServiceConfig)
@ -661,9 +673,9 @@ func (s) TestResolverMaxStreamDuration(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout)
defer cancel()
nodeID := uuid.New().String()
mgmtServer, _, _ := setupManagementServerForTest(ctx, t, nodeID)
mgmtServer, _, _, bc := setupManagementServerForTest(ctx, t, nodeID)
stateCh, _, _ := buildResolverForTarget(t, resolver.Target{URL: *testutils.MustParseURL("xds:///" + defaultTestServiceName)})
stateCh, _, _ := buildResolverForTarget(t, resolver.Target{URL: *testutils.MustParseURL("xds:///" + defaultTestServiceName)}, bc)
// Configure the management server with a listener resource that specifies a
// max stream duration as part of its HTTP connection manager. Also
@ -794,14 +806,14 @@ func (s) TestResolverDelayedOnCommitted(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout)
defer cancel()
nodeID := uuid.New().String()
mgmtServer, _, _ := setupManagementServerForTest(ctx, t, nodeID)
mgmtServer, _, _, bc := setupManagementServerForTest(ctx, t, nodeID)
// Configure resources on the management server.
listeners := []*v3listenerpb.Listener{e2e.DefaultClientListener(defaultTestServiceName, defaultTestRouteConfigName)}
routes := []*v3routepb.RouteConfiguration{e2e.DefaultRouteConfig(defaultTestRouteConfigName, defaultTestServiceName, defaultTestClusterName)}
configureResourcesOnManagementServer(ctx, t, mgmtServer, nodeID, listeners, routes)
stateCh, _, _ := buildResolverForTarget(t, resolver.Target{URL: *testutils.MustParseURL("xds:///" + defaultTestServiceName)})
stateCh, _, _ := buildResolverForTarget(t, resolver.Target{URL: *testutils.MustParseURL("xds:///" + defaultTestServiceName)}, bc)
// Read the update pushed by the resolver to the ClientConn.
cs := verifyUpdateFromResolver(ctx, t, stateCh, wantDefaultServiceConfig)
@ -904,14 +916,14 @@ func (s) TestResolverMultipleLDSUpdates(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout)
defer cancel()
nodeID := uuid.New().String()
mgmtServer, _, _ := setupManagementServerForTest(ctx, t, nodeID)
mgmtServer, _, _, bc := setupManagementServerForTest(ctx, t, nodeID)
// Configure the management server with a listener resource, but no route
// configuration resource.
listeners := []*v3listenerpb.Listener{e2e.DefaultClientListener(defaultTestServiceName, defaultTestRouteConfigName)}
configureResourcesOnManagementServer(ctx, t, mgmtServer, nodeID, listeners, nil)
stateCh, _, _ := buildResolverForTarget(t, resolver.Target{URL: *testutils.MustParseURL("xds:///" + defaultTestServiceName)})
stateCh, _, _ := buildResolverForTarget(t, resolver.Target{URL: *testutils.MustParseURL("xds:///" + defaultTestServiceName)}, bc)
// Ensure there is no update from the resolver.
verifyNoUpdateFromResolver(ctx, t, stateCh)
@ -962,9 +974,9 @@ func (s) TestResolverWRR(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout)
defer cancel()
nodeID := uuid.New().String()
mgmtServer, _, _ := setupManagementServerForTest(ctx, t, nodeID)
mgmtServer, _, _, bc := setupManagementServerForTest(ctx, t, nodeID)
stateCh, _, _ := buildResolverForTarget(t, resolver.Target{URL: *testutils.MustParseURL("xds:///" + defaultTestServiceName)})
stateCh, _, _ := buildResolverForTarget(t, resolver.Target{URL: *testutils.MustParseURL("xds:///" + defaultTestServiceName)}, bc)
// Configure resources on the management server.
listeners := []*v3listenerpb.Listener{e2e.DefaultClientListener(defaultTestServiceName, defaultTestRouteConfigName)}
@ -1161,10 +1173,10 @@ func (s) TestConfigSelector_FailureCases(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout)
defer cancel()
nodeID := uuid.New().String()
mgmtServer, _, _ := setupManagementServerForTest(ctx, t, nodeID)
mgmtServer, _, _, bc := setupManagementServerForTest(ctx, t, nodeID)
// Build an xDS resolver.
stateCh, _, _ := buildResolverForTarget(t, resolver.Target{URL: *testutils.MustParseURL("xds:///" + defaultTestServiceName)})
stateCh, _, _ := buildResolverForTarget(t, resolver.Target{URL: *testutils.MustParseURL("xds:///" + defaultTestServiceName)}, bc)
// Update the management server with a listener resource that
// contains inline route configuration.
@ -1400,10 +1412,10 @@ func (s) TestXDSResolverHTTPFilters(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout)
defer cancel()
nodeID := uuid.New().String()
mgmtServer, _, _ := setupManagementServerForTest(ctx, t, nodeID)
mgmtServer, _, _, bc := setupManagementServerForTest(ctx, t, nodeID)
// Build an xDS resolver.
stateCh, _, _ := buildResolverForTarget(t, resolver.Target{URL: *testutils.MustParseURL("xds:///" + defaultTestServiceName)})
stateCh, _, _ := buildResolverForTarget(t, resolver.Target{URL: *testutils.MustParseURL("xds:///" + defaultTestServiceName)}, bc)
// Update the management server with a listener resource that
// contains an inline route configuration.

View File

@ -29,6 +29,7 @@ import (
"github.com/google/uuid"
"google.golang.org/grpc/internal/grpctest"
"google.golang.org/grpc/internal/testutils/xds/e2e"
"google.golang.org/grpc/internal/xds/bootstrap"
"google.golang.org/grpc/xds/internal/xdsclient"
"google.golang.org/grpc/xds/internal/xdsclient/xdsresource/version"
@ -111,9 +112,13 @@ func xdsSetupForTests(t *testing.T) (*e2e.ManagementServer, string, chan []strin
nodeID := uuid.New().String()
bootstrapContents := e2e.DefaultBootstrapContents(t, nodeID, mgmtServer.Address)
xdsC, cancel, err := xdsclient.NewForTesting(xdsclient.OptionsForTesting{
Name: t.Name(),
Contents: bootstrapContents,
config, err := bootstrap.NewConfigFromContents(bootstrapContents)
if err != nil {
t.Fatalf("Failed to parse bootstrap contents: %s, %v", string(bootstrapContents), err)
}
pool := xdsclient.NewPool(config)
xdsC, cancel, err := pool.NewClientForTesting(xdsclient.OptionsForTesting{
Name: t.Name(),
})
if err != nil {
t.Fatal(err)

View File

@ -86,7 +86,7 @@ func xdsChannelForTest(t *testing.T, serverURI, nodeID string, watchExpiryTimeou
if err != nil {
t.Fatalf("Failed to create bootstrap contents: %v", err)
}
bootstrapCfg, err := bootstrap.NewConfigForTesting(contents)
bootstrapCfg, err := bootstrap.NewConfigFromContents(contents)
if err != nil {
t.Fatalf("Failed to create bootstrap configuration: %v", err)
}

View File

@ -21,6 +21,7 @@
package xdsclient
import (
v3statuspb "github.com/envoyproxy/go-control-plane/envoy/service/status/v3"
"google.golang.org/grpc/internal/xds/bootstrap"
"google.golang.org/grpc/xds/internal/xdsclient/load"
"google.golang.org/grpc/xds/internal/xdsclient/xdsresource"
@ -50,3 +51,9 @@ type XDSClient interface {
BootstrapConfig() *bootstrap.Config
}
// DumpResources returns the status and contents of all xDS resources. It uses
// xDS clients from the default pool.
func DumpResources() *v3statuspb.ClientStatusResponse {
return DefaultPool.DumpResources()
}

View File

@ -1,200 +0,0 @@
/*
*
* Copyright 2022 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 xdsclient
import (
"context"
"fmt"
"sync"
"time"
"google.golang.org/grpc/internal"
"google.golang.org/grpc/internal/backoff"
"google.golang.org/grpc/internal/grpcsync"
"google.golang.org/grpc/internal/xds/bootstrap"
xdsclientinternal "google.golang.org/grpc/xds/internal/xdsclient/internal"
"google.golang.org/grpc/xds/internal/xdsclient/transport/ads"
"google.golang.org/grpc/xds/internal/xdsclient/transport/grpctransport"
"google.golang.org/grpc/xds/internal/xdsclient/xdsresource"
)
// NameForServer represents the value to be passed as name when creating an xDS
// client from xDS-enabled gRPC servers. This is a well-known dedicated key
// value, and is defined in gRFC A71.
const NameForServer = "#server"
// New returns an xDS client configured with bootstrap configuration specified
// by the ordered list:
// - file name containing the configuration specified by GRPC_XDS_BOOTSTRAP
// - actual configuration specified by GRPC_XDS_BOOTSTRAP_CONFIG
// - fallback configuration set using bootstrap.SetFallbackBootstrapConfig
//
// gRPC client implementations are expected to pass the channel's target URI for
// the name field, while server implementations are expected to pass a dedicated
// well-known value "#server", as specified in gRFC A71. The returned client is
// a reference counted implementation shared among callers using the same name.
//
// The second return value represents a close function which releases the
// caller's reference on the returned client. The caller is expected to invoke
// it once they are done using the client. The underlying client will be closed
// only when all references are released, and it is safe for the caller to
// invoke this close function multiple times.
func New(name string) (XDSClient, func(), error) {
config, err := bootstrap.GetConfiguration()
if err != nil {
return nil, nil, fmt.Errorf("xds: failed to get xDS bootstrap config: %v", err)
}
return newRefCounted(name, config, defaultWatchExpiryTimeout, backoff.DefaultExponential.Backoff)
}
// newClientImpl returns a new xdsClient with the given config.
func newClientImpl(config *bootstrap.Config, watchExpiryTimeout time.Duration, streamBackoff func(int) time.Duration) (*clientImpl, error) {
ctx, cancel := context.WithCancel(context.Background())
c := &clientImpl{
done: grpcsync.NewEvent(),
authorities: make(map[string]*authority),
config: config,
watchExpiryTimeout: watchExpiryTimeout,
backoff: streamBackoff,
serializer: grpcsync.NewCallbackSerializer(ctx),
serializerClose: cancel,
transportBuilder: &grpctransport.Builder{},
resourceTypes: newResourceTypeRegistry(),
xdsActiveChannels: make(map[string]*channelState),
}
for name, cfg := range config.Authorities() {
// If server configs are specified in the authorities map, use that.
// Else, use the top-level server configs.
serverCfg := config.XDSServers()
if len(cfg.XDSServers) >= 1 {
serverCfg = cfg.XDSServers
}
c.authorities[name] = newAuthority(authorityBuildOptions{
serverConfigs: serverCfg,
name: name,
serializer: c.serializer,
getChannelForADS: c.getChannelForADS,
logPrefix: clientPrefix(c),
})
}
c.topLevelAuthority = newAuthority(authorityBuildOptions{
serverConfigs: config.XDSServers(),
name: "",
serializer: c.serializer,
getChannelForADS: c.getChannelForADS,
logPrefix: clientPrefix(c),
})
c.logger = prefixLogger(c)
return c, nil
}
// OptionsForTesting contains options to configure xDS client creation for
// testing purposes only.
type OptionsForTesting struct {
// Name is a unique name for this xDS client.
Name string
// Contents contain a JSON representation of the bootstrap configuration to
// be used when creating the xDS client.
Contents []byte
// WatchExpiryTimeout is the timeout for xDS resource watch expiry. If
// unspecified, uses the default value used in non-test code.
WatchExpiryTimeout time.Duration
// StreamBackoffAfterFailure is the backoff function used to determine the
// backoff duration after stream failures.
// If unspecified, uses the default value used in non-test code.
StreamBackoffAfterFailure func(int) time.Duration
}
// NewForTesting returns an xDS client configured with the provided options.
//
// The second return value represents a close function which the caller is
// expected to invoke once they are done using the client. It is safe for the
// caller to invoke this close function multiple times.
//
// # Testing Only
//
// This function should ONLY be used for testing purposes.
func NewForTesting(opts OptionsForTesting) (XDSClient, func(), error) {
if opts.Name == "" {
return nil, nil, fmt.Errorf("opts.Name field must be non-empty")
}
if opts.WatchExpiryTimeout == 0 {
opts.WatchExpiryTimeout = defaultWatchExpiryTimeout
}
if opts.StreamBackoffAfterFailure == nil {
opts.StreamBackoffAfterFailure = defaultStreamBackoffFunc
}
config, err := bootstrap.NewConfigForTesting(opts.Contents)
if err != nil {
return nil, nil, err
}
return newRefCounted(opts.Name, config, opts.WatchExpiryTimeout, opts.StreamBackoffAfterFailure)
}
// GetForTesting returns an xDS client created earlier using the given name.
//
// The second return value represents a close function which the caller is
// expected to invoke once they are done using the client. It is safe for the
// caller to invoke this close function multiple times.
//
// # Testing Only
//
// This function should ONLY be used for testing purposes.
func GetForTesting(name string) (XDSClient, func(), error) {
clientsMu.Lock()
defer clientsMu.Unlock()
c, ok := clients[name]
if !ok {
return nil, nil, fmt.Errorf("xDS client with name %q not found", name)
}
c.incrRef()
return c, sync.OnceFunc(func() { clientRefCountedClose(name) }), nil
}
func init() {
internal.TriggerXDSResourceNotFoundForTesting = triggerXDSResourceNotFoundForTesting
xdsclientinternal.ResourceWatchStateForTesting = resourceWatchStateForTesting
}
func triggerXDSResourceNotFoundForTesting(client XDSClient, typ xdsresource.Type, name string) error {
crc, ok := client.(*clientRefCounted)
if !ok {
return fmt.Errorf("xDS client is of type %T, want %T", client, &clientRefCounted{})
}
return crc.clientImpl.triggerResourceNotFoundForTesting(typ, name)
}
func resourceWatchStateForTesting(client XDSClient, typ xdsresource.Type, name string) (ads.ResourceWatchState, error) {
crc, ok := client.(*clientRefCounted)
if !ok {
return ads.ResourceWatchState{}, fmt.Errorf("xDS client is of type %T, want %T", client, &clientRefCounted{})
}
return crc.clientImpl.resourceWatchStateForTesting(typ, name)
}
var (
clients = map[string]*clientRefCounted{}
clientsMu sync.Mutex
)

View File

@ -1,104 +0,0 @@
/*
*
* Copyright 2020 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 xdsclient
import (
"sync"
"sync/atomic"
"time"
"google.golang.org/grpc/internal/backoff"
"google.golang.org/grpc/internal/xds/bootstrap"
)
const defaultWatchExpiryTimeout = 15 * time.Second
var (
// The following functions are no-ops in the actual code, but can be
// overridden in tests to give them visibility into certain events.
xdsClientImplCreateHook = func(string) {}
xdsClientImplCloseHook = func(string) {}
defaultStreamBackoffFunc = backoff.DefaultExponential.Backoff
)
func clientRefCountedClose(name string) {
clientsMu.Lock()
client, ok := clients[name]
if !ok {
logger.Errorf("Attempt to close a non-existent xDS client with name %s", name)
clientsMu.Unlock()
return
}
if client.decrRef() != 0 {
clientsMu.Unlock()
return
}
delete(clients, name)
clientsMu.Unlock()
// This attempts to close the transport to the management server and could
// theoretically call back into the xdsclient package again and deadlock.
// Hence, this needs to be called without holding the lock.
client.clientImpl.close()
xdsClientImplCloseHook(name)
}
// newRefCounted creates a new reference counted xDS client implementation for
// name, if one does not exist already. If an xDS client for the given name
// exists, it gets a reference to it and returns it.
func newRefCounted(name string, config *bootstrap.Config, watchExpiryTimeout time.Duration, streamBackoff func(int) time.Duration) (XDSClient, func(), error) {
clientsMu.Lock()
defer clientsMu.Unlock()
if c := clients[name]; c != nil {
c.incrRef()
return c, sync.OnceFunc(func() { clientRefCountedClose(name) }), nil
}
// Create the new client implementation.
c, err := newClientImpl(config, watchExpiryTimeout, streamBackoff)
if err != nil {
return nil, nil, err
}
c.logger.Infof("Created client with name %q and bootstrap configuration:\n %s", name, config)
client := &clientRefCounted{clientImpl: c, refCount: 1}
clients[name] = client
xdsClientImplCreateHook(name)
logger.Infof("xDS node ID: %s", config.Node().GetId())
return client, sync.OnceFunc(func() { clientRefCountedClose(name) }), nil
}
// clientRefCounted is ref-counted, and to be shared by the xds resolver and
// balancer implementations, across multiple ClientConns and Servers.
type clientRefCounted struct {
*clientImpl
refCount int32 // accessed atomically
}
func (c *clientRefCounted) incrRef() int32 {
return atomic.AddInt32(&c.refCount, 1)
}
func (c *clientRefCounted) decrRef() int32 {
return atomic.AddInt32(&c.refCount, -1)
}

View File

@ -26,6 +26,7 @@ import (
"github.com/google/uuid"
"google.golang.org/grpc/internal/testutils"
"google.golang.org/grpc/internal/testutils/xds/e2e"
"google.golang.org/grpc/internal/xds/bootstrap"
)
// Tests that multiple calls to New() with the same name returns the same
@ -36,7 +37,11 @@ func (s) TestClientNew_Single(t *testing.T) {
// directory, and set the bootstrap env vars to point to it.
nodeID := uuid.New().String()
contents := e2e.DefaultBootstrapContents(t, nodeID, "non-existent-server-address")
testutils.CreateBootstrapFileForTesting(t, contents)
config, err := bootstrap.NewConfigFromContents(contents)
if err != nil {
t.Fatalf("Failed to parse bootstrap contents: %s, %v", contents, err)
}
pool := NewPool(config)
// Override the client creation hook to get notified.
origClientImplCreateHook := xdsClientImplCreateHook
@ -55,7 +60,7 @@ func (s) TestClientNew_Single(t *testing.T) {
defer func() { xdsClientImplCloseHook = origClientImplCloseHook }()
// The first call to New() should create a new client.
_, closeFunc, err := New(t.Name())
_, closeFunc, err := pool.NewClient(t.Name())
if err != nil {
t.Fatalf("Failed to create xDS client: %v", err)
}
@ -71,7 +76,7 @@ func (s) TestClientNew_Single(t *testing.T) {
closeFuncs := make([]func(), count)
for i := 0; i < count; i++ {
func() {
_, closeFuncs[i], err = New(t.Name())
_, closeFuncs[i], err = pool.NewClient(t.Name())
if err != nil {
t.Fatalf("%d-th call to New() failed with error: %v", i, err)
}
@ -109,7 +114,7 @@ func (s) TestClientNew_Single(t *testing.T) {
// Calling New() again, after the previous Client was actually closed,
// should create a new one.
_, closeFunc, err = New(t.Name())
_, closeFunc, err = pool.NewClient(t.Name())
if err != nil {
t.Fatalf("Failed to create xDS client: %v", err)
}
@ -127,7 +132,11 @@ func (s) TestClientNew_Multiple(t *testing.T) {
// directory, and set the bootstrap env vars to point to it.
nodeID := uuid.New().String()
contents := e2e.DefaultBootstrapContents(t, nodeID, "non-existent-server-address")
testutils.CreateBootstrapFileForTesting(t, contents)
config, err := bootstrap.NewConfigFromContents(contents)
if err != nil {
t.Fatalf("Failed to parse bootstrap contents: %s, %v", contents, err)
}
pool := NewPool(config)
// Override the client creation hook to get notified.
origClientImplCreateHook := xdsClientImplCreateHook
@ -147,7 +156,7 @@ func (s) TestClientNew_Multiple(t *testing.T) {
// Create two xDS clients.
client1Name := t.Name() + "-1"
_, closeFunc1, err := New(client1Name)
_, closeFunc1, err := pool.NewClient(client1Name)
if err != nil {
t.Fatalf("Failed to create xDS client: %v", err)
}
@ -162,7 +171,7 @@ func (s) TestClientNew_Multiple(t *testing.T) {
}
client2Name := t.Name() + "-2"
_, closeFunc2, err := New(client2Name)
_, closeFunc2, err := pool.NewClient(client2Name)
if err != nil {
t.Fatalf("Failed to create xDS client: %v", err)
}
@ -184,7 +193,7 @@ func (s) TestClientNew_Multiple(t *testing.T) {
defer wg.Done()
for i := 0; i < count; i++ {
var err error
_, closeFuncs1[i], err = New(client1Name)
_, closeFuncs1[i], err = pool.NewClient(client1Name)
if err != nil {
t.Errorf("%d-th call to New() failed with error: %v", i, err)
}
@ -194,7 +203,7 @@ func (s) TestClientNew_Multiple(t *testing.T) {
defer wg.Done()
for i := 0; i < count; i++ {
var err error
_, closeFuncs2[i], err = New(client2Name)
_, closeFuncs2[i], err = pool.NewClient(client2Name)
if err != nil {
t.Errorf("%d-th call to New() failed with error: %v", i, err)
}

View File

@ -19,26 +19,50 @@
package xdsclient
import (
"context"
"errors"
"fmt"
"sync"
"sync/atomic"
"time"
v3statuspb "github.com/envoyproxy/go-control-plane/envoy/service/status/v3"
"google.golang.org/grpc/internal"
"google.golang.org/grpc/internal/backoff"
"google.golang.org/grpc/internal/grpclog"
"google.golang.org/grpc/internal/grpcsync"
"google.golang.org/grpc/internal/xds/bootstrap"
xdsclientinternal "google.golang.org/grpc/xds/internal/xdsclient/internal"
"google.golang.org/grpc/xds/internal/xdsclient/transport"
"google.golang.org/grpc/xds/internal/xdsclient/transport/ads"
"google.golang.org/grpc/xds/internal/xdsclient/transport/grpctransport"
"google.golang.org/grpc/xds/internal/xdsclient/xdsresource"
)
var _ XDSClient = &clientImpl{}
const (
// NameForServer represents the value to be passed as name when creating an xDS
// client from xDS-enabled gRPC servers. This is a well-known dedicated key
// value, and is defined in gRFC A71.
NameForServer = "#server"
// ErrClientClosed is returned when the xDS client is closed.
var ErrClientClosed = errors.New("xds: the xDS client is closed")
defaultWatchExpiryTimeout = 15 * time.Second
)
// clientImpl is the real implementation of the xds client. The exported Client
var (
_ XDSClient = &clientImpl{}
// ErrClientClosed is returned when the xDS client is closed.
ErrClientClosed = errors.New("xds: the xDS client is closed")
// The following functions are no-ops in the actual code, but can be
// overridden in tests to give them visibility into certain events.
xdsClientImplCreateHook = func(string) {}
xdsClientImplCloseHook = func(string) {}
defaultExponentialBackoff = backoff.DefaultExponential.Backoff
)
// clientImpl is the real implementation of the xDS client. The exported Client
// is a wrapper of this struct with a ref count.
type clientImpl struct {
// The following fields are initialized at creation time and are read-only
@ -67,70 +91,65 @@ type clientImpl struct {
xdsActiveChannels map[string]*channelState // Map from server config to in-use xdsChannels.
}
// channelState represents the state of an xDS channel. It tracks the number of
// LRS references, the authorities interested in the channel, and the server
// configuration used for the channel.
//
// It receives callbacks for events on the underlying ADS stream and invokes
// corresponding callbacks on interested authoririties.
type channelState struct {
parent *clientImpl
serverConfig *bootstrap.ServerConfig
func init() {
internal.TriggerXDSResourceNotFoundForTesting = triggerXDSResourceNotFoundForTesting
xdsclientinternal.ResourceWatchStateForTesting = resourceWatchStateForTesting
// Access to the following fields should be protected by the parent's
// channelsMu.
channel *xdsChannel
lrsRefs int
interestedAuthorities map[*authority]bool
}
func (cs *channelState) adsStreamFailure(err error) {
if cs.parent.done.HasFired() {
return
}
cs.parent.channelsMu.Lock()
defer cs.parent.channelsMu.Unlock()
for authority := range cs.interestedAuthorities {
authority.adsStreamFailure(cs.serverConfig, err)
}
}
func (cs *channelState) adsResourceUpdate(typ xdsresource.Type, updates map[string]ads.DataAndErrTuple, md xdsresource.UpdateMetadata, onDone func()) {
if cs.parent.done.HasFired() {
return
}
cs.parent.channelsMu.Lock()
defer cs.parent.channelsMu.Unlock()
if len(cs.interestedAuthorities) == 0 {
onDone()
return
}
authorityCnt := new(atomic.Int64)
authorityCnt.Add(int64(len(cs.interestedAuthorities)))
done := func() {
if authorityCnt.Add(-1) == 0 {
onDone()
// DefaultPool is initialized with bootstrap configuration from one of the
// supported environment variables. If the environment variables are not
// set, then fallback bootstrap configuration should be set before
// attempting to create an xDS client, else xDS client creation will fail.
config, err := bootstrap.GetConfiguration()
if err != nil {
if logger.V(2) {
logger.Infof("Failed to read xDS bootstrap config from env vars: %v", err)
}
}
for authority := range cs.interestedAuthorities {
authority.adsResourceUpdate(cs.serverConfig, typ, updates, md, done)
}
}
func (cs *channelState) adsResourceDoesNotExist(typ xdsresource.Type, resourceName string) {
if cs.parent.done.HasFired() {
DefaultPool = &Pool{clients: make(map[string]*clientRefCounted)}
return
}
DefaultPool = &Pool{clients: make(map[string]*clientRefCounted), config: config}
}
cs.parent.channelsMu.Lock()
defer cs.parent.channelsMu.Unlock()
for authority := range cs.interestedAuthorities {
authority.adsResourceDoesNotExist(typ, resourceName)
// newClientImpl returns a new xdsClient with the given config.
func newClientImpl(config *bootstrap.Config, watchExpiryTimeout time.Duration, streamBackoff func(int) time.Duration) (*clientImpl, error) {
ctx, cancel := context.WithCancel(context.Background())
c := &clientImpl{
done: grpcsync.NewEvent(),
authorities: make(map[string]*authority),
config: config,
watchExpiryTimeout: watchExpiryTimeout,
backoff: streamBackoff,
serializer: grpcsync.NewCallbackSerializer(ctx),
serializerClose: cancel,
transportBuilder: &grpctransport.Builder{},
resourceTypes: newResourceTypeRegistry(),
xdsActiveChannels: make(map[string]*channelState),
}
for name, cfg := range config.Authorities() {
// If server configs are specified in the authorities map, use that.
// Else, use the top-level server configs.
serverCfg := config.XDSServers()
if len(cfg.XDSServers) >= 1 {
serverCfg = cfg.XDSServers
}
c.authorities[name] = newAuthority(authorityBuildOptions{
serverConfigs: serverCfg,
name: name,
serializer: c.serializer,
getChannelForADS: c.getChannelForADS,
logPrefix: clientPrefix(c),
})
}
c.topLevelAuthority = newAuthority(authorityBuildOptions{
serverConfigs: config.XDSServers(),
name: "",
serializer: c.serializer,
getChannelForADS: c.getChannelForADS,
logPrefix: clientPrefix(c),
})
c.logger = prefixLogger(c)
return c, nil
}
// BootstrapConfig returns the configuration read from the bootstrap file.
@ -285,7 +304,7 @@ func (c *clientImpl) getOrCreateChannel(serverConfig *bootstrap.ServerConfig, in
// map of xdsChannels.
tr, err := c.transportBuilder.Build(transport.BuildOptions{ServerConfig: serverConfig})
if err != nil {
return nil, func() {}, fmt.Errorf("failed to create transport for server config %s: %v", serverConfig, err)
return nil, func() {}, fmt.Errorf("xds: failed to create transport for server config %s: %v", serverConfig, err)
}
state := &channelState{
parent: c,
@ -303,7 +322,7 @@ func (c *clientImpl) getOrCreateChannel(serverConfig *bootstrap.ServerConfig, in
logPrefix: clientPrefix(c),
})
if err != nil {
return nil, func() {}, fmt.Errorf("failed to create xdsChannel for server config %s: %v", serverConfig, err)
return nil, func() {}, fmt.Errorf("xds: failed to create xdsChannel for server config %s: %v", serverConfig, err)
}
state.channel = channel
c.xdsActiveChannels[serverConfig.String()] = state
@ -350,3 +369,114 @@ func (c *clientImpl) releaseChannel(serverConfig *bootstrap.ServerConfig, state
channelToClose.close()
})
}
// dumpResources returns the status and contents of all xDS resources.
func (c *clientImpl) dumpResources() *v3statuspb.ClientConfig {
retCfg := c.topLevelAuthority.dumpResources()
for _, a := range c.authorities {
retCfg = append(retCfg, a.dumpResources()...)
}
return &v3statuspb.ClientConfig{
Node: c.config.Node(),
GenericXdsConfigs: retCfg,
}
}
// channelState represents the state of an xDS channel. It tracks the number of
// LRS references, the authorities interested in the channel, and the server
// configuration used for the channel.
//
// It receives callbacks for events on the underlying ADS stream and invokes
// corresponding callbacks on interested authorities.
type channelState struct {
parent *clientImpl
serverConfig *bootstrap.ServerConfig
// Access to the following fields should be protected by the parent's
// channelsMu.
channel *xdsChannel
lrsRefs int
interestedAuthorities map[*authority]bool
}
func (cs *channelState) adsStreamFailure(err error) {
if cs.parent.done.HasFired() {
return
}
cs.parent.channelsMu.Lock()
defer cs.parent.channelsMu.Unlock()
for authority := range cs.interestedAuthorities {
authority.adsStreamFailure(cs.serverConfig, err)
}
}
func (cs *channelState) adsResourceUpdate(typ xdsresource.Type, updates map[string]ads.DataAndErrTuple, md xdsresource.UpdateMetadata, onDone func()) {
if cs.parent.done.HasFired() {
return
}
cs.parent.channelsMu.Lock()
defer cs.parent.channelsMu.Unlock()
if len(cs.interestedAuthorities) == 0 {
onDone()
return
}
authorityCnt := new(atomic.Int64)
authorityCnt.Add(int64(len(cs.interestedAuthorities)))
done := func() {
if authorityCnt.Add(-1) == 0 {
onDone()
}
}
for authority := range cs.interestedAuthorities {
authority.adsResourceUpdate(cs.serverConfig, typ, updates, md, done)
}
}
func (cs *channelState) adsResourceDoesNotExist(typ xdsresource.Type, resourceName string) {
if cs.parent.done.HasFired() {
return
}
cs.parent.channelsMu.Lock()
defer cs.parent.channelsMu.Unlock()
for authority := range cs.interestedAuthorities {
authority.adsResourceDoesNotExist(typ, resourceName)
}
}
// clientRefCounted is ref-counted, and to be shared by the xds resolver and
// balancer implementations, across multiple ClientConns and Servers.
type clientRefCounted struct {
*clientImpl
refCount int32 // accessed atomically
}
func (c *clientRefCounted) incrRef() int32 {
return atomic.AddInt32(&c.refCount, 1)
}
func (c *clientRefCounted) decrRef() int32 {
return atomic.AddInt32(&c.refCount, -1)
}
func triggerXDSResourceNotFoundForTesting(client XDSClient, typ xdsresource.Type, name string) error {
crc, ok := client.(*clientRefCounted)
if !ok {
return fmt.Errorf("xds: xDS client is of type %T, want %T", client, &clientRefCounted{})
}
return crc.clientImpl.triggerResourceNotFoundForTesting(typ, name)
}
func resourceWatchStateForTesting(client XDSClient, typ xdsresource.Type, name string) (ads.ResourceWatchState, error) {
crc, ok := client.(*clientRefCounted)
if !ok {
return ads.ResourceWatchState{}, fmt.Errorf("xds: xDS client is of type %T, want %T", client, &clientRefCounted{})
}
return crc.clientImpl.resourceWatchStateForTesting(typ, name)
}

View File

@ -1,50 +0,0 @@
/*
*
* 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 xdsclient
import (
v3statuspb "github.com/envoyproxy/go-control-plane/envoy/service/status/v3"
)
// dumpResources returns the status and contents of all xDS resources.
func (c *clientImpl) dumpResources() *v3statuspb.ClientConfig {
retCfg := c.topLevelAuthority.dumpResources()
for _, a := range c.authorities {
retCfg = append(retCfg, a.dumpResources()...)
}
return &v3statuspb.ClientConfig{
Node: c.config.Node(),
GenericXdsConfigs: retCfg,
}
}
// DumpResources returns the status and contents of all xDS resources.
func DumpResources() *v3statuspb.ClientStatusResponse {
clientsMu.Lock()
defer clientsMu.Unlock()
resp := &v3statuspb.ClientStatusResponse{}
for key, client := range clients {
cfg := client.dumpResources()
cfg.ClientScope = key
resp.Config = append(resp.Config, cfg)
}
return resp
}

View File

@ -0,0 +1,235 @@
/*
*
* Copyright 2024 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 xdsclient
import (
"fmt"
"sync"
"time"
v3statuspb "github.com/envoyproxy/go-control-plane/envoy/service/status/v3"
"google.golang.org/grpc/internal/backoff"
"google.golang.org/grpc/internal/xds/bootstrap"
)
var (
// DefaultPool is the default pool for xDS clients. It is created at init
// time by reading bootstrap configuration from env vars.
DefaultPool *Pool
)
// Pool represents a pool of xDS clients that share the same bootstrap
// configuration.
type Pool struct {
// Note that mu should ideally only have to guard clients. But here, we need
// it to guard config as well since SetFallbackBootstrapConfig writes to
// config.
mu sync.Mutex
clients map[string]*clientRefCounted
config *bootstrap.Config
}
// OptionsForTesting contains options to configure xDS client creation for
// testing purposes only.
type OptionsForTesting struct {
// Name is a unique name for this xDS client.
Name string
// WatchExpiryTimeout is the timeout for xDS resource watch expiry. If
// unspecified, uses the default value used in non-test code.
WatchExpiryTimeout time.Duration
// StreamBackoffAfterFailure is the backoff function used to determine the
// backoff duration after stream failures.
// If unspecified, uses the default value used in non-test code.
StreamBackoffAfterFailure func(int) time.Duration
}
// NewPool creates a new xDS client pool with the given bootstrap config.
//
// If a nil bootstrap config is passed and SetFallbackBootstrapConfig is not
// called before a call to NewClient, the latter will fail. i.e. if there is an
// attempt to create an xDS client from the pool without specifying bootstrap
// configuration (either at pool creation time or by setting the fallback
// bootstrap configuration), xDS client creation will fail.
func NewPool(config *bootstrap.Config) *Pool {
return &Pool{
clients: make(map[string]*clientRefCounted),
config: config,
}
}
// NewClient returns an xDS client with the given name from the pool. If the
// client doesn't already exist, it creates a new xDS client and adds it to the
// pool.
//
// The second return value represents a close function which the caller is
// expected to invoke once they are done using the client. It is safe for the
// caller to invoke this close function multiple times.
func (p *Pool) NewClient(name string) (XDSClient, func(), error) {
return p.newRefCounted(name, defaultWatchExpiryTimeout, backoff.DefaultExponential.Backoff)
}
// NewClientForTesting returns an xDS client configured with the provided
// options from the pool. If the client doesn't already exist, it creates a new
// xDS client and adds it to the pool.
//
// The second return value represents a close function which the caller is
// expected to invoke once they are done using the client. It is safe for the
// caller to invoke this close function multiple times.
//
// # Testing Only
//
// This function should ONLY be used for testing purposes.
func (p *Pool) NewClientForTesting(opts OptionsForTesting) (XDSClient, func(), error) {
if opts.Name == "" {
return nil, nil, fmt.Errorf("xds: opts.Name field must be non-empty")
}
if opts.WatchExpiryTimeout == 0 {
opts.WatchExpiryTimeout = defaultWatchExpiryTimeout
}
if opts.StreamBackoffAfterFailure == nil {
opts.StreamBackoffAfterFailure = defaultExponentialBackoff
}
return p.newRefCounted(opts.Name, opts.WatchExpiryTimeout, opts.StreamBackoffAfterFailure)
}
// GetClientForTesting returns an xDS client created earlier using the given
// name from the pool. If the client with the given name doesn't already exist,
// it returns an error.
//
// The second return value represents a close function which the caller is
// expected to invoke once they are done using the client. It is safe for the
// caller to invoke this close function multiple times.
//
// # Testing Only
//
// This function should ONLY be used for testing purposes.
func (p *Pool) GetClientForTesting(name string) (XDSClient, func(), error) {
p.mu.Lock()
defer p.mu.Unlock()
c, ok := p.clients[name]
if !ok {
return nil, nil, fmt.Errorf("xds:: xDS client with name %q not found", name)
}
c.incrRef()
return c, sync.OnceFunc(func() { p.clientRefCountedClose(name) }), nil
}
// SetFallbackBootstrapConfig is used to specify a bootstrap configuration
// that will be used as a fallback when the bootstrap environment variables
// are not defined.
func (p *Pool) SetFallbackBootstrapConfig(config *bootstrap.Config) {
p.mu.Lock()
defer p.mu.Unlock()
if p.config != nil {
logger.Error("Attempt to set a bootstrap configuration even though one is already set via environment variables.")
return
}
p.config = config
}
// DumpResources returns the status and contents of all xDS resources.
func (p *Pool) DumpResources() *v3statuspb.ClientStatusResponse {
p.mu.Lock()
defer p.mu.Unlock()
resp := &v3statuspb.ClientStatusResponse{}
for key, client := range p.clients {
cfg := client.dumpResources()
cfg.ClientScope = key
resp.Config = append(resp.Config, cfg)
}
return resp
}
// BootstrapConfigForTesting returns the bootstrap configuration used by the
// pool. The caller should not mutate the returned config.
//
// To be used only for testing purposes.
func (p *Pool) BootstrapConfigForTesting() *bootstrap.Config {
p.mu.Lock()
defer p.mu.Unlock()
return p.config
}
// UnsetBootstrapConfigForTesting unsets the bootstrap configuration used by
// the pool.
//
// To be used only for testing purposes.
func (p *Pool) UnsetBootstrapConfigForTesting() {
p.mu.Lock()
defer p.mu.Unlock()
p.config = nil
}
func (p *Pool) clientRefCountedClose(name string) {
p.mu.Lock()
client, ok := p.clients[name]
if !ok {
logger.Errorf("Attempt to close a non-existent xDS client with name %s", name)
p.mu.Unlock()
return
}
if client.decrRef() != 0 {
p.mu.Unlock()
return
}
delete(p.clients, name)
p.mu.Unlock()
// This attempts to close the transport to the management server and could
// theoretically call back into the xdsclient package again and deadlock.
// Hence, this needs to be called without holding the lock.
client.clientImpl.close()
xdsClientImplCloseHook(name)
}
// newRefCounted creates a new reference counted xDS client implementation for
// name, if one does not exist already. If an xDS client for the given name
// exists, it gets a reference to it and returns it.
func (p *Pool) newRefCounted(name string, watchExpiryTimeout time.Duration, streamBackoff func(int) time.Duration) (XDSClient, func(), error) {
p.mu.Lock()
defer p.mu.Unlock()
if p.config == nil {
return nil, nil, fmt.Errorf("xds: bootstrap configuration not set in the pool")
}
if c := p.clients[name]; c != nil {
c.incrRef()
return c, sync.OnceFunc(func() { p.clientRefCountedClose(name) }), nil
}
c, err := newClientImpl(p.config, watchExpiryTimeout, streamBackoff)
if err != nil {
return nil, nil, err
}
if logger.V(2) {
c.logger.Infof("Created client with name %q and bootstrap configuration:\n %s", name, p.config)
}
client := &clientRefCounted{clientImpl: c, refCount: 1}
p.clients[name] = client
xdsClientImplCreateHook(name)
logger.Infof("xDS node ID: %s", p.config.Node().GetId())
return client, sync.OnceFunc(func() { p.clientRefCountedClose(name) }), nil
}

View File

@ -31,6 +31,7 @@ import (
"google.golang.org/grpc"
"google.golang.org/grpc/internal/testutils"
"google.golang.org/grpc/internal/testutils/xds/e2e"
"google.golang.org/grpc/internal/xds/bootstrap"
"google.golang.org/grpc/xds/internal/xdsclient"
"google.golang.org/grpc/xds/internal/xdsclient/xdsresource"
"google.golang.org/protobuf/proto"
@ -85,7 +86,6 @@ func (s) TestADS_ACK_NACK_Simple(t *testing.T) {
// Create an xDS client with bootstrap pointing to the above server.
bc := e2e.DefaultBootstrapContents(t, nodeID, mgmtServer.Address)
testutils.CreateBootstrapFileForTesting(t, bc)
client := createXDSClient(t, bc)
// Register a watch for a listener resource.
@ -272,7 +272,6 @@ func (s) TestADS_NACK_InvalidFirstResponse(t *testing.T) {
// Create an xDS client with bootstrap pointing to the above server.
bc := e2e.DefaultBootstrapContents(t, nodeID, mgmtServer.Address)
testutils.CreateBootstrapFileForTesting(t, bc)
client := createXDSClient(t, bc)
// Register a watch for a listener resource.
@ -380,10 +379,13 @@ func (s) TestADS_ACK_NACK_ResourceIsNotRequestedAnymore(t *testing.T) {
// Create an xDS client with bootstrap pointing to the above server.
bc := e2e.DefaultBootstrapContents(t, nodeID, mgmtServer.Address)
testutils.CreateBootstrapFileForTesting(t, bc)
client, close, err := xdsclient.NewForTesting(xdsclient.OptionsForTesting{
Name: t.Name(),
Contents: bc,
config, err := bootstrap.NewConfigFromContents(bc)
if err != nil {
t.Fatalf("Failed to parse bootstrap contents: %s, %v", string(bc), err)
}
pool := xdsclient.NewPool(config)
client, close, err := pool.NewClientForTesting(xdsclient.OptionsForTesting{
Name: t.Name(),
})
if err != nil {
t.Fatalf("Failed to create xDS client: %v", err)

View File

@ -32,6 +32,7 @@ import (
"google.golang.org/grpc"
"google.golang.org/grpc/internal/testutils"
"google.golang.org/grpc/internal/testutils/xds/e2e"
"google.golang.org/grpc/internal/xds/bootstrap"
"google.golang.org/grpc/xds/internal/xdsclient"
"google.golang.org/grpc/xds/internal/xdsclient/xdsresource"
"google.golang.org/grpc/xds/internal/xdsclient/xdsresource/version"
@ -46,10 +47,14 @@ import (
func createXDSClientWithBackoff(t *testing.T, bootstrapContents []byte, streamBackoff func(int) time.Duration) xdsclient.XDSClient {
t.Helper()
client, close, err := xdsclient.NewForTesting(xdsclient.OptionsForTesting{
config, err := bootstrap.NewConfigFromContents(bootstrapContents)
if err != nil {
t.Fatalf("Failed to parse bootstrap contents: %s, %v", string(bootstrapContents), err)
}
pool := xdsclient.NewPool(config)
client, close, err := pool.NewClientForTesting(xdsclient.OptionsForTesting{
Name: t.Name(),
StreamBackoffAfterFailure: streamBackoff,
Contents: bootstrapContents,
})
if err != nil {
t.Fatalf("Failed to create xDS client: %v", err)
@ -110,7 +115,6 @@ func (s) TestADS_BackoffAfterStreamFailure(t *testing.T) {
// Create an xDS client with bootstrap pointing to the above server.
nodeID := uuid.New().String()
bc := e2e.DefaultBootstrapContents(t, nodeID, mgmtServer.Address)
testutils.CreateBootstrapFileForTesting(t, bc)
client := createXDSClientWithBackoff(t, bc, streamBackoff)
// Register a watch for a listener resource.
@ -221,7 +225,6 @@ func (s) TestADS_RetriesAfterBrokenStream(t *testing.T) {
// Create an xDS client with bootstrap pointing to the above server.
bc := e2e.DefaultBootstrapContents(t, nodeID, mgmtServer.Address)
testutils.CreateBootstrapFileForTesting(t, bc)
client := createXDSClientWithBackoff(t, bc, streamBackoff)
// Register a watch for a listener resource.
@ -383,7 +386,6 @@ func (s) TestADS_ResourceRequestedBeforeStreamCreation(t *testing.T) {
// Create an xDS client with bootstrap pointing to the above server.
nodeID := uuid.New().String()
bc := e2e.DefaultBootstrapContents(t, nodeID, mgmtServer.Address)
testutils.CreateBootstrapFileForTesting(t, bc)
client := createXDSClientWithBackoff(t, bc, streamBackoff)
// Register a watch for a listener resource.

View File

@ -28,8 +28,8 @@ import (
"github.com/google/uuid"
"google.golang.org/grpc"
"google.golang.org/grpc/internal/testutils"
"google.golang.org/grpc/internal/testutils/xds/e2e"
"google.golang.org/grpc/internal/xds/bootstrap"
"google.golang.org/grpc/xds/internal/xdsclient"
xdsclientinternal "google.golang.org/grpc/xds/internal/xdsclient/internal"
"google.golang.org/grpc/xds/internal/xdsclient/xdsresource"
@ -150,9 +150,13 @@ func overrideADSStreamCreation(t *testing.T) chan *wrappedADSStream {
func createXDSClient(t *testing.T, bootstrapContents []byte) xdsclient.XDSClient {
t.Helper()
client, close, err := xdsclient.NewForTesting(xdsclient.OptionsForTesting{
Name: t.Name(),
Contents: bootstrapContents,
config, err := bootstrap.NewConfigFromContents(bootstrapContents)
if err != nil {
t.Fatalf("Failed to parse bootstrap contents: %s, %v", string(bootstrapContents), err)
}
pool := xdsclient.NewPool(config)
client, close, err := pool.NewClientForTesting(xdsclient.OptionsForTesting{
Name: t.Name(),
})
if err != nil {
t.Fatalf("Failed to create xDS client: %v", err)
@ -186,7 +190,6 @@ func (s) TestADSFlowControl_ResourceUpdates_SingleResource(t *testing.T) {
// Create bootstrap configuration pointing to the above management server.
nodeID := uuid.New().String()
bc := e2e.DefaultBootstrapContents(t, nodeID, mgmtServer.Address)
testutils.CreateBootstrapFileForTesting(t, bc)
// Create an xDS client with the above bootstrap contents.
client := createXDSClient(t, bc)
@ -335,7 +338,6 @@ func (s) TestADSFlowControl_ResourceUpdates_MultipleResources(t *testing.T) {
// Create bootstrap configuration pointing to the above management server.
nodeID := uuid.New().String()
bc := e2e.DefaultBootstrapContents(t, nodeID, mgmtServer.Address)
testutils.CreateBootstrapFileForTesting(t, bc)
// Create an xDS client with the above bootstrap contents.
client := createXDSClient(t, bc)
@ -452,7 +454,6 @@ func (s) TestADSFlowControl_ResourceErrors(t *testing.T) {
// Create bootstrap configuration pointing to the above management server.
nodeID := uuid.New().String()
bc := e2e.DefaultBootstrapContents(t, nodeID, mgmtServer.Address)
testutils.CreateBootstrapFileForTesting(t, bc)
// Create an xDS client with the above bootstrap contents.
client := createXDSClient(t, bc)
@ -533,7 +534,6 @@ func (s) TestADSFlowControl_ResourceDoesNotExist(t *testing.T) {
// Create bootstrap configuration pointing to the above management server.
nodeID := uuid.New().String()
bc := e2e.DefaultBootstrapContents(t, nodeID, mgmtServer.Address)
testutils.CreateBootstrapFileForTesting(t, bc)
// Create an xDS client with the above bootstrap contents.
client := createXDSClient(t, bc)

View File

@ -25,6 +25,7 @@ import (
"github.com/google/uuid"
"google.golang.org/grpc/internal/testutils"
"google.golang.org/grpc/internal/testutils/xds/e2e"
"google.golang.org/grpc/internal/xds/bootstrap"
"google.golang.org/grpc/xds/internal/xdsclient"
"google.golang.org/grpc/xds/internal/xdsclient/xdsresource"
"google.golang.org/grpc/xds/internal/xdsclient/xdsresource/version"
@ -112,9 +113,13 @@ func (s) TestADS_ResourcesAreRequestedAfterStreamRestart(t *testing.T) {
bootstrapContents := e2e.DefaultBootstrapContents(t, nodeID, mgmtServer.Address)
// Create an xDS client with the above bootstrap configuration.
client, close, err := xdsclient.NewForTesting(xdsclient.OptionsForTesting{
Name: t.Name(),
Contents: bootstrapContents,
config, err := bootstrap.NewConfigFromContents(bootstrapContents)
if err != nil {
t.Fatalf("Failed to parse bootstrap contents: %s, %v", string(bootstrapContents), err)
}
pool := xdsclient.NewPool(config)
client, close, err := pool.NewClientForTesting(xdsclient.OptionsForTesting{
Name: t.Name(),
})
if err != nil {
t.Fatalf("Failed to create xDS client: %v", err)

View File

@ -27,6 +27,7 @@ import (
"github.com/google/uuid"
"google.golang.org/grpc/internal/testutils"
"google.golang.org/grpc/internal/testutils/xds/e2e"
"google.golang.org/grpc/internal/xds/bootstrap"
xdsinternal "google.golang.org/grpc/xds/internal"
"google.golang.org/grpc/xds/internal/xdsclient"
"google.golang.org/grpc/xds/internal/xdsclient/internal"
@ -55,7 +56,6 @@ func (s) TestADS_WatchState_StreamBreaks(t *testing.T) {
// Create an xDS client with bootstrap pointing to the above server.
nodeID := uuid.New().String()
bc := e2e.DefaultBootstrapContents(t, nodeID, mgmtServer.Address)
testutils.CreateBootstrapFileForTesting(t, bc)
client := createXDSClient(t, bc)
// Create a watch for the first listener resource and verify that the timer
@ -146,10 +146,13 @@ func (s) TestADS_WatchState_TimerFires(t *testing.T) {
// short resource expiry timeout.
nodeID := uuid.New().String()
bc := e2e.DefaultBootstrapContents(t, nodeID, mgmtServer.Address)
testutils.CreateBootstrapFileForTesting(t, bc)
client, close, err := xdsclient.NewForTesting(xdsclient.OptionsForTesting{
config, err := bootstrap.NewConfigFromContents(bc)
if err != nil {
t.Fatalf("Failed to parse bootstrap contents: %s, %v", string(bc), err)
}
pool := xdsclient.NewPool(config)
client, close, err := pool.NewClientForTesting(xdsclient.OptionsForTesting{
Name: t.Name(),
Contents: bc,
WatchExpiryTimeout: defaultTestWatchExpiryTimeout,
})
if err != nil {

View File

@ -100,9 +100,13 @@ func setupForAuthorityTests(ctx context.Context, t *testing.T) (*testutils.Liste
if err != nil {
t.Fatalf("Failed to create bootstrap configuration: %v", err)
}
client, close, err := xdsclient.NewForTesting(xdsclient.OptionsForTesting{
config, err := bootstrap.NewConfigFromContents(bootstrapContents)
if err != nil {
t.Fatalf("Failed to parse bootstrap contents: %s, %v", string(bootstrapContents), err)
}
pool := xdsclient.NewPool(config)
client, close, err := pool.NewClientForTesting(xdsclient.OptionsForTesting{
Name: t.Name(),
Contents: bootstrapContents,
WatchExpiryTimeout: defaultTestWatchExpiryTimeout,
})
if err != nil {

View File

@ -207,12 +207,15 @@ func (s) TestCDSWatch(t *testing.T) {
if err != nil {
t.Fatalf("Failed to create bootstrap configuration: %v", err)
}
testutils.CreateBootstrapFileForTesting(t, bc)
// Create an xDS client with the above bootstrap contents.
client, close, err := xdsclient.NewForTesting(xdsclient.OptionsForTesting{
Name: t.Name(),
Contents: bc,
config, err := bootstrap.NewConfigFromContents(bc)
if err != nil {
t.Fatalf("Failed to parse bootstrap contents: %s, %v", string(bc), err)
}
pool := xdsclient.NewPool(config)
client, close, err := pool.NewClientForTesting(xdsclient.OptionsForTesting{
Name: t.Name(),
})
if err != nil {
t.Fatalf("Failed to create xDS client: %v", err)
@ -357,12 +360,15 @@ func (s) TestCDSWatch_TwoWatchesForSameResourceName(t *testing.T) {
if err != nil {
t.Fatalf("Failed to create bootstrap configuration: %v", err)
}
testutils.CreateBootstrapFileForTesting(t, bc)
// Create an xDS client with the above bootstrap contents.
client, close, err := xdsclient.NewForTesting(xdsclient.OptionsForTesting{
Name: t.Name(),
Contents: bc,
config, err := bootstrap.NewConfigFromContents(bc)
if err != nil {
t.Fatalf("Failed to parse bootstrap contents: %s, %v", string(bc), err)
}
pool := xdsclient.NewPool(config)
client, close, err := pool.NewClientForTesting(xdsclient.OptionsForTesting{
Name: t.Name(),
})
if err != nil {
t.Fatalf("Failed to create xDS client: %v", err)
@ -460,12 +466,15 @@ func (s) TestCDSWatch_ThreeWatchesForDifferentResourceNames(t *testing.T) {
if err != nil {
t.Fatalf("Failed to create bootstrap configuration: %v", err)
}
testutils.CreateBootstrapFileForTesting(t, bc)
// Create an xDS client with the above bootstrap contents.
client, close, err := xdsclient.NewForTesting(xdsclient.OptionsForTesting{
Name: t.Name(),
Contents: bc,
config, err := bootstrap.NewConfigFromContents(bc)
if err != nil {
t.Fatalf("Failed to parse bootstrap contents: %s, %v", string(bc), err)
}
pool := xdsclient.NewPool(config)
client, close, err := pool.NewClientForTesting(xdsclient.OptionsForTesting{
Name: t.Name(),
})
if err != nil {
t.Fatalf("Failed to create xDS client: %v", err)
@ -557,12 +566,15 @@ func (s) TestCDSWatch_ResourceCaching(t *testing.T) {
nodeID := uuid.New().String()
bc := e2e.DefaultBootstrapContents(t, nodeID, mgmtServer.Address)
testutils.CreateBootstrapFileForTesting(t, bc)
// Create an xDS client with the above bootstrap contents.
client, close, err := xdsclient.NewForTesting(xdsclient.OptionsForTesting{
Name: t.Name(),
Contents: bc,
config, err := bootstrap.NewConfigFromContents(bc)
if err != nil {
t.Fatalf("Failed to parse bootstrap contents: %s, %v", string(bc), err)
}
pool := xdsclient.NewPool(config)
client, close, err := pool.NewClientForTesting(xdsclient.OptionsForTesting{
Name: t.Name(),
})
if err != nil {
t.Fatalf("Failed to create xDS client: %v", err)
@ -631,11 +643,14 @@ func (s) TestCDSWatch_ExpiryTimerFiresBeforeResponse(t *testing.T) {
nodeID := uuid.New().String()
bc := e2e.DefaultBootstrapContents(t, nodeID, mgmtServer.Address)
testutils.CreateBootstrapFileForTesting(t, bc)
client, close, err := xdsclient.NewForTesting(xdsclient.OptionsForTesting{
config, err := bootstrap.NewConfigFromContents(bc)
if err != nil {
t.Fatalf("Failed to parse bootstrap contents: %s, %v", string(bc), err)
}
pool := xdsclient.NewPool(config)
client, close, err := pool.NewClientForTesting(xdsclient.OptionsForTesting{
Name: t.Name(),
Contents: bc,
WatchExpiryTimeout: defaultTestWatchExpiryTimeout,
})
if err != nil {
@ -671,11 +686,14 @@ func (s) TestCDSWatch_ValidResponseCancelsExpiryTimerBehavior(t *testing.T) {
// Create an xDS client talking to the above management server.
nodeID := uuid.New().String()
bc := e2e.DefaultBootstrapContents(t, nodeID, mgmtServer.Address)
testutils.CreateBootstrapFileForTesting(t, bc)
client, close, err := xdsclient.NewForTesting(xdsclient.OptionsForTesting{
config, err := bootstrap.NewConfigFromContents(bc)
if err != nil {
t.Fatalf("Failed to parse bootstrap contents: %s, %v", string(bc), err)
}
pool := xdsclient.NewPool(config)
client, close, err := pool.NewClientForTesting(xdsclient.OptionsForTesting{
Name: t.Name(),
Contents: bc,
WatchExpiryTimeout: defaultTestWatchExpiryTimeout,
})
if err != nil {
@ -753,12 +771,15 @@ func (s) TestCDSWatch_ResourceRemoved(t *testing.T) {
if err != nil {
t.Fatalf("Failed to create bootstrap configuration: %v", err)
}
testutils.CreateBootstrapFileForTesting(t, bc)
// Create an xDS client with the above bootstrap contents.
client, close, err := xdsclient.NewForTesting(xdsclient.OptionsForTesting{
Name: t.Name(),
Contents: bc,
config, err := bootstrap.NewConfigFromContents(bc)
if err != nil {
t.Fatalf("Failed to parse bootstrap contents: %s, %v", string(bc), err)
}
pool := xdsclient.NewPool(config)
client, close, err := pool.NewClientForTesting(xdsclient.OptionsForTesting{
Name: t.Name(),
})
if err != nil {
t.Fatalf("Failed to create xDS client: %v", err)
@ -865,12 +886,15 @@ func (s) TestCDSWatch_NACKError(t *testing.T) {
nodeID := uuid.New().String()
bc := e2e.DefaultBootstrapContents(t, nodeID, mgmtServer.Address)
testutils.CreateBootstrapFileForTesting(t, bc)
// Create an xDS client with the above bootstrap contents.
client, close, err := xdsclient.NewForTesting(xdsclient.OptionsForTesting{
Name: t.Name(),
Contents: bc,
config, err := bootstrap.NewConfigFromContents(bc)
if err != nil {
t.Fatalf("Failed to parse bootstrap contents: %s, %v", string(bc), err)
}
pool := xdsclient.NewPool(config)
client, close, err := pool.NewClientForTesting(xdsclient.OptionsForTesting{
Name: t.Name(),
})
if err != nil {
t.Fatalf("Failed to create xDS client: %v", err)
@ -934,12 +958,15 @@ func (s) TestCDSWatch_PartialValid(t *testing.T) {
if err != nil {
t.Fatalf("Failed to create bootstrap configuration: %v", err)
}
testutils.CreateBootstrapFileForTesting(t, bc)
// Create an xDS client with the above bootstrap contents.
client, close, err := xdsclient.NewForTesting(xdsclient.OptionsForTesting{
Name: t.Name(),
Contents: bc,
config, err := bootstrap.NewConfigFromContents(bc)
if err != nil {
t.Fatalf("Failed to parse bootstrap contents: %s, %v", string(bc), err)
}
pool := xdsclient.NewPool(config)
client, close, err := pool.NewClientForTesting(xdsclient.OptionsForTesting{
Name: t.Name(),
})
if err != nil {
t.Fatalf("Failed to create xDS client: %v", err)
@ -1026,12 +1053,15 @@ func (s) TestCDSWatch_PartialResponse(t *testing.T) {
if err != nil {
t.Fatalf("Failed to create bootstrap configuration: %v", err)
}
testutils.CreateBootstrapFileForTesting(t, bc)
// Create an xDS client with the above bootstrap contents.
client, close, err := xdsclient.NewForTesting(xdsclient.OptionsForTesting{
Name: t.Name(),
Contents: bc,
config, err := bootstrap.NewConfigFromContents(bc)
if err != nil {
t.Fatalf("Failed to parse bootstrap contents: %s, %v", string(bc), err)
}
pool := xdsclient.NewPool(config)
client, close, err := pool.NewClientForTesting(xdsclient.OptionsForTesting{
Name: t.Name(),
})
if err != nil {
t.Fatalf("Failed to create xDS client: %v", err)

View File

@ -62,7 +62,7 @@ func makeGenericXdsConfig(typeURL, name, version string, status v3adminpb.Client
}
}
func checkResourceDump(ctx context.Context, want *v3statuspb.ClientStatusResponse) error {
func checkResourceDump(ctx context.Context, want *v3statuspb.ClientStatusResponse, pool *xdsclient.Pool) error {
var cmpOpts = cmp.Options{
protocmp.Transform(),
protocmp.IgnoreFields((*v3statuspb.ClientConfig_GenericXdsConfig)(nil), "last_updated"),
@ -71,7 +71,7 @@ func checkResourceDump(ctx context.Context, want *v3statuspb.ClientStatusRespons
var lastErr error
for ; ctx.Err() == nil; <-time.After(defaultTestShortTimeout) {
got := xdsclient.DumpResources()
got := pool.DumpResources()
// Sort the client configs based on the `client_scope` field.
slices.SortFunc(got.GetConfig(), func(a, b *v3statuspb.ClientConfig) int {
return strings.Compare(a.ClientScope, b.ClientScope)
@ -136,22 +136,23 @@ func (s) TestDumpResources_ManyToOne(t *testing.T) {
nodeID := uuid.New().String()
bc := e2e.DefaultBootstrapContents(t, nodeID, mgmtServer.Address)
testutils.CreateBootstrapFileForTesting(t, bc)
config, err := bootstrap.NewConfigFromContents(bc)
if err != nil {
t.Fatalf("Failed to parse bootstrap contents: %s, %v", string(bc), err)
}
pool := xdsclient.NewPool(config)
// Create two xDS clients with the above bootstrap contents.
client1Name := t.Name() + "-1"
client1, close1, err := xdsclient.NewForTesting(xdsclient.OptionsForTesting{
Name: client1Name,
Contents: bc,
client1, close1, err := pool.NewClientForTesting(xdsclient.OptionsForTesting{
Name: client1Name,
})
if err != nil {
t.Fatalf("Failed to create xDS client: %v", err)
}
defer close1()
client2Name := t.Name() + "-2"
client2, close2, err := xdsclient.NewForTesting(xdsclient.OptionsForTesting{
Name: client2Name,
Contents: bc,
client2, close2, err := pool.NewClientForTesting(xdsclient.OptionsForTesting{
Name: client2Name,
})
if err != nil {
t.Fatalf("Failed to create xDS client: %v", err)
@ -179,7 +180,7 @@ func (s) TestDumpResources_ManyToOne(t *testing.T) {
},
},
}
if err := checkResourceDump(ctx, wantResp); err != nil {
if err := checkResourceDump(ctx, wantResp, pool); err != nil {
t.Fatal(err)
}
@ -222,7 +223,7 @@ func (s) TestDumpResources_ManyToOne(t *testing.T) {
},
},
}
if err := checkResourceDump(ctx, wantResp); err != nil {
if err := checkResourceDump(ctx, wantResp, pool); err != nil {
t.Fatal(err)
}
@ -262,7 +263,7 @@ func (s) TestDumpResources_ManyToOne(t *testing.T) {
},
},
}
if err := checkResourceDump(ctx, wantResp); err != nil {
if err := checkResourceDump(ctx, wantResp, pool); err != nil {
t.Fatal(err)
}
@ -322,7 +323,7 @@ func (s) TestDumpResources_ManyToOne(t *testing.T) {
},
},
}
if err := checkResourceDump(ctx, wantResp); err != nil {
if err := checkResourceDump(ctx, wantResp, pool); err != nil {
t.Fatal(err)
}
}
@ -396,7 +397,7 @@ func (s) TestDumpResources_ManyToMany(t *testing.T) {
Node: []byte(fmt.Sprintf(`{"id": "%s"}`, nodeID)),
Authorities: map[string]json.RawMessage{
authority: []byte(fmt.Sprintf(`{
"xds_servers": [{
"xds_servers": [{
"server_uri": %q,
"channel_creds": [{"type": "insecure"}]
}]}`, mgmtServer2.Address)),
@ -405,22 +406,24 @@ func (s) TestDumpResources_ManyToMany(t *testing.T) {
if err != nil {
t.Fatalf("Failed to create bootstrap configuration: %v", err)
}
testutils.CreateBootstrapFileForTesting(t, bc)
config, err := bootstrap.NewConfigFromContents(bc)
if err != nil {
t.Fatalf("Failed to parse bootstrap contents: %s, %v", string(bc), err)
}
pool := xdsclient.NewPool(config)
// Create two xDS clients with the above bootstrap contents.
client1Name := t.Name() + "-1"
client1, close1, err := xdsclient.NewForTesting(xdsclient.OptionsForTesting{
Name: client1Name,
Contents: bc,
client1, close1, err := pool.NewClientForTesting(xdsclient.OptionsForTesting{
Name: client1Name,
})
if err != nil {
t.Fatalf("Failed to create xDS client: %v", err)
}
defer close1()
client2Name := t.Name() + "-2"
client2, close2, err := xdsclient.NewForTesting(xdsclient.OptionsForTesting{
Name: client2Name,
Contents: bc,
client2, close2, err := pool.NewClientForTesting(xdsclient.OptionsForTesting{
Name: client2Name,
})
if err != nil {
t.Fatalf("Failed to create xDS client: %v", err)
@ -449,7 +452,7 @@ func (s) TestDumpResources_ManyToMany(t *testing.T) {
},
},
}
if err := checkResourceDump(ctx, wantResp); err != nil {
if err := checkResourceDump(ctx, wantResp, pool); err != nil {
t.Fatal(err)
}
@ -492,7 +495,7 @@ func (s) TestDumpResources_ManyToMany(t *testing.T) {
},
},
}
if err := checkResourceDump(ctx, wantResp); err != nil {
if err := checkResourceDump(ctx, wantResp, pool); err != nil {
t.Fatal(err)
}
@ -529,7 +532,7 @@ func (s) TestDumpResources_ManyToMany(t *testing.T) {
},
},
}
if err := checkResourceDump(ctx, wantResp); err != nil {
if err := checkResourceDump(ctx, wantResp, pool); err != nil {
t.Fatal(err)
}
@ -566,7 +569,7 @@ func (s) TestDumpResources_ManyToMany(t *testing.T) {
},
},
}
if err := checkResourceDump(ctx, wantResp); err != nil {
if err := checkResourceDump(ctx, wantResp, pool); err != nil {
t.Fatal(err)
}
}

View File

@ -237,12 +237,15 @@ func (s) TestEDSWatch(t *testing.T) {
if err != nil {
t.Fatalf("Failed to create bootstrap configuration: %v", err)
}
testutils.CreateBootstrapFileForTesting(t, bc)
// Create an xDS client with the above bootstrap contents.
client, close, err := xdsclient.NewForTesting(xdsclient.OptionsForTesting{
Name: t.Name(),
Contents: bc,
config, err := bootstrap.NewConfigFromContents(bc)
if err != nil {
t.Fatalf("Failed to parse bootstrap contents: %s, %v", string(bc), err)
}
pool := xdsclient.NewPool(config)
client, close, err := pool.NewClientForTesting(xdsclient.OptionsForTesting{
Name: t.Name(),
})
if err != nil {
t.Fatalf("Failed to create xDS client: %v", err)
@ -427,12 +430,15 @@ func (s) TestEDSWatch_TwoWatchesForSameResourceName(t *testing.T) {
if err != nil {
t.Fatalf("Failed to create bootstrap configuration: %v", err)
}
testutils.CreateBootstrapFileForTesting(t, bc)
// Create an xDS client with the above bootstrap contents.
client, close, err := xdsclient.NewForTesting(xdsclient.OptionsForTesting{
Name: t.Name(),
Contents: bc,
config, err := bootstrap.NewConfigFromContents(bc)
if err != nil {
t.Fatalf("Failed to parse bootstrap contents: %s, %v", string(bc), err)
}
pool := xdsclient.NewPool(config)
client, close, err := pool.NewClientForTesting(xdsclient.OptionsForTesting{
Name: t.Name(),
})
if err != nil {
t.Fatalf("Failed to create xDS client: %v", err)
@ -531,12 +537,15 @@ func (s) TestEDSWatch_ThreeWatchesForDifferentResourceNames(t *testing.T) {
if err != nil {
t.Fatalf("Failed to create bootstrap configuration: %v", err)
}
testutils.CreateBootstrapFileForTesting(t, bc)
// Create an xDS client with the above bootstrap contents.
client, close, err := xdsclient.NewForTesting(xdsclient.OptionsForTesting{
Name: t.Name(),
Contents: bc,
config, err := bootstrap.NewConfigFromContents(bc)
if err != nil {
t.Fatalf("Failed to parse bootstrap contents: %s, %v", string(bc), err)
}
pool := xdsclient.NewPool(config)
client, close, err := pool.NewClientForTesting(xdsclient.OptionsForTesting{
Name: t.Name(),
})
if err != nil {
t.Fatalf("Failed to create xDS client: %v", err)
@ -633,12 +642,15 @@ func (s) TestEDSWatch_ResourceCaching(t *testing.T) {
nodeID := uuid.New().String()
bc := e2e.DefaultBootstrapContents(t, nodeID, mgmtServer.Address)
testutils.CreateBootstrapFileForTesting(t, bc)
// Create an xDS client with the above bootstrap contents.
client, close, err := xdsclient.NewForTesting(xdsclient.OptionsForTesting{
Name: t.Name(),
Contents: bc,
config, err := bootstrap.NewConfigFromContents(bc)
if err != nil {
t.Fatalf("Failed to parse bootstrap contents: %s, %v", string(bc), err)
}
pool := xdsclient.NewPool(config)
client, close, err := pool.NewClientForTesting(xdsclient.OptionsForTesting{
Name: t.Name(),
})
if err != nil {
t.Fatalf("Failed to create xDS client: %v", err)
@ -718,11 +730,14 @@ func (s) TestEDSWatch_ExpiryTimerFiresBeforeResponse(t *testing.T) {
nodeID := uuid.New().String()
bc := e2e.DefaultBootstrapContents(t, nodeID, mgmtServer.Address)
testutils.CreateBootstrapFileForTesting(t, bc)
client, close, err := xdsclient.NewForTesting(xdsclient.OptionsForTesting{
config, err := bootstrap.NewConfigFromContents(bc)
if err != nil {
t.Fatalf("Failed to parse bootstrap contents: %s, %v", string(bc), err)
}
pool := xdsclient.NewPool(config)
client, close, err := pool.NewClientForTesting(xdsclient.OptionsForTesting{
Name: t.Name(),
Contents: bc,
WatchExpiryTimeout: defaultTestWatchExpiryTimeout,
})
if err != nil {
@ -758,12 +773,15 @@ func (s) TestEDSWatch_ValidResponseCancelsExpiryTimerBehavior(t *testing.T) {
// Create an xDS client talking to the above management server.
nodeID := uuid.New().String()
bc := e2e.DefaultBootstrapContents(t, nodeID, mgmtServer.Address)
testutils.CreateBootstrapFileForTesting(t, bc)
// Create an xDS client talking to the above management server.
client, close, err := xdsclient.NewForTesting(xdsclient.OptionsForTesting{
config, err := bootstrap.NewConfigFromContents(bc)
if err != nil {
t.Fatalf("Failed to parse bootstrap contents: %s, %v", string(bc), err)
}
pool := xdsclient.NewPool(config)
client, close, err := pool.NewClientForTesting(xdsclient.OptionsForTesting{
Name: t.Name(),
Contents: bc,
WatchExpiryTimeout: defaultTestWatchExpiryTimeout,
})
if err != nil {
@ -827,12 +845,15 @@ func (s) TestEDSWatch_NACKError(t *testing.T) {
nodeID := uuid.New().String()
bc := e2e.DefaultBootstrapContents(t, nodeID, mgmtServer.Address)
testutils.CreateBootstrapFileForTesting(t, bc)
// Create an xDS client with the above bootstrap contents.
client, close, err := xdsclient.NewForTesting(xdsclient.OptionsForTesting{
Name: t.Name(),
Contents: bc,
config, err := bootstrap.NewConfigFromContents(bc)
if err != nil {
t.Fatalf("Failed to parse bootstrap contents: %s, %v", string(bc), err)
}
pool := xdsclient.NewPool(config)
client, close, err := pool.NewClientForTesting(xdsclient.OptionsForTesting{
Name: t.Name(),
})
if err != nil {
t.Fatalf("Failed to create xDS client: %v", err)
@ -896,12 +917,15 @@ func (s) TestEDSWatch_PartialValid(t *testing.T) {
if err != nil {
t.Fatalf("Failed to create bootstrap configuration: %v", err)
}
testutils.CreateBootstrapFileForTesting(t, bc)
// Create an xDS client with the above bootstrap contents.
client, close, err := xdsclient.NewForTesting(xdsclient.OptionsForTesting{
Name: t.Name(),
Contents: bc,
config, err := bootstrap.NewConfigFromContents(bc)
if err != nil {
t.Fatalf("Failed to parse bootstrap contents: %s, %v", string(bc), err)
}
pool := xdsclient.NewPool(config)
client, close, err := pool.NewClientForTesting(xdsclient.OptionsForTesting{
Name: t.Name(),
})
if err != nil {
t.Fatalf("Failed to create xDS client: %v", err)

View File

@ -155,18 +155,18 @@ func (s) TestFallback_OnStartup(t *testing.T) {
}
// Create an xDS client with the above bootstrap configuration.
xdsC, close, err := xdsclient.NewForTesting(xdsclient.OptionsForTesting{
Name: t.Name(),
Contents: bootstrapContents,
})
config, err := bootstrap.NewConfigFromContents(bootstrapContents)
if err != nil {
t.Fatalf("Failed to parse bootstrap contents: %s, %v", string(bootstrapContents), err)
}
pool := xdsclient.NewPool(config)
if err != nil {
t.Fatalf("Failed to create xDS client: %v", err)
}
defer close()
// Get the xDS resolver to use the above xDS client.
resolverBuilder := internal.NewXDSResolverWithClientForTesting.(func(xdsclient.XDSClient) (resolver.Builder, error))
resolver, err := resolverBuilder(xdsC)
resolverBuilder := internal.NewXDSResolverWithPoolForTesting.(func(*xdsclient.Pool) (resolver.Builder, error))
resolver, err := resolverBuilder(pool)
if err != nil {
t.Fatalf("Failed to create xDS resolver for testing: %v", err)
}
@ -350,18 +350,18 @@ func (s) TestFallback_MidUpdate(t *testing.T) {
}
// Create an xDS client with the above bootstrap configuration.
xdsC, close, err := xdsclient.NewForTesting(xdsclient.OptionsForTesting{
Name: t.Name(),
Contents: bootstrapContents,
})
config, err := bootstrap.NewConfigFromContents(bootstrapContents)
if err != nil {
t.Fatalf("Failed to parse bootstrap contents: %s, %v", string(bootstrapContents), err)
}
pool := xdsclient.NewPool(config)
if err != nil {
t.Fatalf("Failed to create xDS client: %v", err)
}
defer close()
// Get the xDS resolver to use the above xDS client.
resolverBuilder := internal.NewXDSResolverWithClientForTesting.(func(xdsclient.XDSClient) (resolver.Builder, error))
resolver, err := resolverBuilder(xdsC)
resolverBuilder := internal.NewXDSResolverWithPoolForTesting.(func(*xdsclient.Pool) (resolver.Builder, error))
resolver, err := resolverBuilder(pool)
if err != nil {
t.Fatalf("Failed to create xDS resolver for testing: %v", err)
}
@ -535,18 +535,18 @@ func (s) TestFallback_MidStartup(t *testing.T) {
}
// Create an xDS client with the above bootstrap configuration.
xdsC, close, err := xdsclient.NewForTesting(xdsclient.OptionsForTesting{
Name: t.Name(),
Contents: bootstrapContents,
})
config, err := bootstrap.NewConfigFromContents(bootstrapContents)
if err != nil {
t.Fatalf("Failed to parse bootstrap contents: %s, %v", string(bootstrapContents), err)
}
pool := xdsclient.NewPool(config)
if err != nil {
t.Fatalf("Failed to create xDS client: %v", err)
}
defer close()
// Get the xDS resolver to use the above xDS client.
resolverBuilder := internal.NewXDSResolverWithClientForTesting.(func(xdsclient.XDSClient) (resolver.Builder, error))
resolver, err := resolverBuilder(xdsC)
resolverBuilder := internal.NewXDSResolverWithPoolForTesting.(func(*xdsclient.Pool) (resolver.Builder, error))
resolver, err := resolverBuilder(pool)
if err != nil {
t.Fatalf("Failed to create xDS resolver for testing: %v", err)
}

View File

@ -69,9 +69,13 @@ func setupForFederationWatchersTest(t *testing.T) (*e2e.ManagementServer, string
t.Fatalf("Failed to create bootstrap configuration: %v", err)
}
// Create an xDS client with the above bootstrap contents.
client, close, err := xdsclient.NewForTesting(xdsclient.OptionsForTesting{
Name: t.Name(),
Contents: bootstrapContents,
config, err := bootstrap.NewConfigFromContents(bootstrapContents)
if err != nil {
t.Fatalf("Failed to parse bootstrap contents: %s, %v", string(bootstrapContents), err)
}
pool := xdsclient.NewPool(config)
client, close, err := pool.NewClientForTesting(xdsclient.OptionsForTesting{
Name: t.Name(),
})
if err != nil {
t.Fatalf("Failed to create xDS client: %v", err)

View File

@ -263,12 +263,15 @@ func (s) TestLDSWatch(t *testing.T) {
if err != nil {
t.Fatalf("Failed to create bootstrap configuration: %v", err)
}
testutils.CreateBootstrapFileForTesting(t, bc)
// Create an xDS client with the above bootstrap contents.
client, close, err := xdsclient.NewForTesting(xdsclient.OptionsForTesting{
Name: t.Name(),
Contents: bc,
config, err := bootstrap.NewConfigFromContents(bc)
if err != nil {
t.Fatalf("Failed to parse bootstrap contents: %s, %v", string(bc), err)
}
pool := xdsclient.NewPool(config)
client, close, err := pool.NewClientForTesting(xdsclient.OptionsForTesting{
Name: t.Name(),
})
if err != nil {
t.Fatalf("Failed to create xDS client: %v", err)
@ -413,12 +416,15 @@ func (s) TestLDSWatch_TwoWatchesForSameResourceName(t *testing.T) {
if err != nil {
t.Fatalf("Failed to create bootstrap configuration: %v", err)
}
testutils.CreateBootstrapFileForTesting(t, bc)
// Create an xDS client with the above bootstrap contents.
client, close, err := xdsclient.NewForTesting(xdsclient.OptionsForTesting{
Name: t.Name(),
Contents: bc,
config, err := bootstrap.NewConfigFromContents(bc)
if err != nil {
t.Fatalf("Failed to parse bootstrap contents: %s, %v", string(bc), err)
}
pool := xdsclient.NewPool(config)
client, close, err := pool.NewClientForTesting(xdsclient.OptionsForTesting{
Name: t.Name(),
})
if err != nil {
t.Fatalf("Failed to create xDS client: %v", err)
@ -517,12 +523,15 @@ func (s) TestLDSWatch_ThreeWatchesForDifferentResourceNames(t *testing.T) {
if err != nil {
t.Fatalf("Failed to create bootstrap configuration: %v", err)
}
testutils.CreateBootstrapFileForTesting(t, bc)
// Create an xDS client with the above bootstrap contents.
client, close, err := xdsclient.NewForTesting(xdsclient.OptionsForTesting{
Name: t.Name(),
Contents: bc,
config, err := bootstrap.NewConfigFromContents(bc)
if err != nil {
t.Fatalf("Failed to parse bootstrap contents: %s, %v", string(bc), err)
}
pool := xdsclient.NewPool(config)
client, close, err := pool.NewClientForTesting(xdsclient.OptionsForTesting{
Name: t.Name(),
})
if err != nil {
t.Fatalf("Failed to create xDS client: %v", err)
@ -609,12 +618,15 @@ func (s) TestLDSWatch_ResourceCaching(t *testing.T) {
nodeID := uuid.New().String()
bc := e2e.DefaultBootstrapContents(t, nodeID, mgmtServer.Address)
testutils.CreateBootstrapFileForTesting(t, bc)
// Create an xDS client with the above bootstrap contents.
client, close, err := xdsclient.NewForTesting(xdsclient.OptionsForTesting{
Name: t.Name(),
Contents: bc,
config, err := bootstrap.NewConfigFromContents(bc)
if err != nil {
t.Fatalf("Failed to parse bootstrap contents: %s, %v", string(bc), err)
}
pool := xdsclient.NewPool(config)
client, close, err := pool.NewClientForTesting(xdsclient.OptionsForTesting{
Name: t.Name(),
})
if err != nil {
t.Fatalf("Failed to create xDS client: %v", err)
@ -683,12 +695,15 @@ func (s) TestLDSWatch_ExpiryTimerFiresBeforeResponse(t *testing.T) {
nodeID := uuid.New().String()
bc := e2e.DefaultBootstrapContents(t, nodeID, mgmtServer.Address)
testutils.CreateBootstrapFileForTesting(t, bc)
// Create an xDS client talking to the above management server.
client, close, err := xdsclient.NewForTesting(xdsclient.OptionsForTesting{
config, err := bootstrap.NewConfigFromContents(bc)
if err != nil {
t.Fatalf("Failed to parse bootstrap contents: %s, %v", string(bc), err)
}
pool := xdsclient.NewPool(config)
client, close, err := pool.NewClientForTesting(xdsclient.OptionsForTesting{
Name: t.Name(),
Contents: bc,
WatchExpiryTimeout: defaultTestWatchExpiryTimeout,
})
if err != nil {
@ -723,12 +738,15 @@ func (s) TestLDSWatch_ValidResponseCancelsExpiryTimerBehavior(t *testing.T) {
nodeID := uuid.New().String()
bc := e2e.DefaultBootstrapContents(t, nodeID, mgmtServer.Address)
testutils.CreateBootstrapFileForTesting(t, bc)
// Create an xDS client talking to the above management server.
client, close, err := xdsclient.NewForTesting(xdsclient.OptionsForTesting{
config, err := bootstrap.NewConfigFromContents(bc)
if err != nil {
t.Fatalf("Failed to parse bootstrap contents: %s, %v", string(bc), err)
}
pool := xdsclient.NewPool(config)
client, close, err := pool.NewClientForTesting(xdsclient.OptionsForTesting{
Name: t.Name(),
Contents: bc,
WatchExpiryTimeout: defaultTestWatchExpiryTimeout,
})
if err != nil {
@ -807,12 +825,15 @@ func (s) TestLDSWatch_ResourceRemoved(t *testing.T) {
if err != nil {
t.Fatalf("Failed to create bootstrap configuration: %v", err)
}
testutils.CreateBootstrapFileForTesting(t, bc)
// Create an xDS client with the above bootstrap contents.
client, close, err := xdsclient.NewForTesting(xdsclient.OptionsForTesting{
Name: t.Name(),
Contents: bc,
config, err := bootstrap.NewConfigFromContents(bc)
if err != nil {
t.Fatalf("Failed to parse bootstrap contents: %s, %v", string(bc), err)
}
pool := xdsclient.NewPool(config)
client, close, err := pool.NewClientForTesting(xdsclient.OptionsForTesting{
Name: t.Name(),
})
if err != nil {
t.Fatalf("Failed to create xDS client: %v", err)
@ -924,9 +945,13 @@ func (s) TestLDSWatch_NewWatcherForRemovedResource(t *testing.T) {
bc := e2e.DefaultBootstrapContents(t, nodeID, mgmtServer.Address)
// Create an xDS client with the above bootstrap contents.
client, close, err := xdsclient.NewForTesting(xdsclient.OptionsForTesting{
Name: t.Name(),
Contents: bc,
config, err := bootstrap.NewConfigFromContents(bc)
if err != nil {
t.Fatalf("Failed to parse bootstrap contents: %s, %v", string(bc), err)
}
pool := xdsclient.NewPool(config)
client, close, err := pool.NewClientForTesting(xdsclient.OptionsForTesting{
Name: t.Name(),
})
if err != nil {
t.Fatalf("Failed to create xDS client: %v", err)
@ -998,12 +1023,15 @@ func (s) TestLDSWatch_NACKError(t *testing.T) {
nodeID := uuid.New().String()
bc := e2e.DefaultBootstrapContents(t, nodeID, mgmtServer.Address)
testutils.CreateBootstrapFileForTesting(t, bc)
// Create an xDS client with the above bootstrap contents.
client, close, err := xdsclient.NewForTesting(xdsclient.OptionsForTesting{
Name: t.Name(),
Contents: bc,
config, err := bootstrap.NewConfigFromContents(bc)
if err != nil {
t.Fatalf("Failed to parse bootstrap contents: %s, %v", string(bc), err)
}
pool := xdsclient.NewPool(config)
client, close, err := pool.NewClientForTesting(xdsclient.OptionsForTesting{
Name: t.Name(),
})
if err != nil {
t.Fatalf("Failed to create xDS client: %v", err)
@ -1055,9 +1083,13 @@ func (s) TestLDSWatch_ResourceCaching_NACKError(t *testing.T) {
bc := e2e.DefaultBootstrapContents(t, nodeID, mgmtServer.Address)
// Create an xDS client with the above bootstrap contents.
client, close, err := xdsclient.NewForTesting(xdsclient.OptionsForTesting{
Name: t.Name(),
Contents: bc,
config, err := bootstrap.NewConfigFromContents(bc)
if err != nil {
t.Fatalf("Failed to parse bootstrap contents: %s, %v", string(bc), err)
}
pool := xdsclient.NewPool(config)
client, close, err := pool.NewClientForTesting(xdsclient.OptionsForTesting{
Name: t.Name(),
})
if err != nil {
t.Fatalf("Failed to create xDS client: %v", err)
@ -1151,12 +1183,15 @@ func (s) TestLDSWatch_PartialValid(t *testing.T) {
if err != nil {
t.Fatalf("Failed to create bootstrap configuration: %v", err)
}
testutils.CreateBootstrapFileForTesting(t, bc)
// Create an xDS client with the above bootstrap contents.
client, close, err := xdsclient.NewForTesting(xdsclient.OptionsForTesting{
Name: t.Name(),
Contents: bc,
config, err := bootstrap.NewConfigFromContents(bc)
if err != nil {
t.Fatalf("Failed to parse bootstrap contents: %s, %v", string(bc), err)
}
pool := xdsclient.NewPool(config)
client, close, err := pool.NewClientForTesting(xdsclient.OptionsForTesting{
Name: t.Name(),
})
if err != nil {
t.Fatalf("Failed to create xDS client: %v", err)
@ -1240,12 +1275,15 @@ func (s) TestLDSWatch_PartialResponse(t *testing.T) {
if err != nil {
t.Fatalf("Failed to create bootstrap configuration: %v", err)
}
testutils.CreateBootstrapFileForTesting(t, bc)
// Create an xDS client with the above bootstrap contents.
client, close, err := xdsclient.NewForTesting(xdsclient.OptionsForTesting{
Name: t.Name(),
Contents: bc,
config, err := bootstrap.NewConfigFromContents(bc)
if err != nil {
t.Fatalf("Failed to parse bootstrap contents: %s, %v", string(bc), err)
}
pool := xdsclient.NewPool(config)
client, close, err := pool.NewClientForTesting(xdsclient.OptionsForTesting{
Name: t.Name(),
})
if err != nil {
t.Fatalf("Failed to create xDS client: %v", err)

View File

@ -119,16 +119,16 @@ func (s) TestReportLoad_ConnectionCreation(t *testing.T) {
nodeID := uuid.New().String()
bc, err := bootstrap.NewContentsForTesting(bootstrap.ConfigOptionsForTesting{
Servers: []byte(fmt.Sprintf(`[{
"server_uri": %q,
"channel_creds": [{"type": "insecure"}]
}]`, mgmtServer1.Address)),
"server_uri": %q,
"channel_creds": [{"type": "insecure"}]
}]`, mgmtServer1.Address)),
Node: []byte(fmt.Sprintf(`{"id": "%s"}`, nodeID)),
Authorities: map[string]json.RawMessage{
"test-authority": []byte(fmt.Sprintf(`{
"xds_servers": [{
"server_uri": %q,
"channel_creds": [{"type": "insecure"}]
}]}`, mgmtServer2.Address)),
"xds_servers": [{
"server_uri": %q,
"channel_creds": [{"type": "insecure"}]
}]}`, mgmtServer2.Address)),
},
})
if err != nil {
@ -266,7 +266,6 @@ func (s) TestReportLoad_StreamCreation(t *testing.T) {
// Create an xDS client with bootstrap pointing to the above server.
nodeID := uuid.New().String()
bc := e2e.DefaultBootstrapContents(t, nodeID, mgmtServer.Address)
testutils.CreateBootstrapFileForTesting(t, bc)
client := createXDSClient(t, bc)
// Call the load reporting API, and ensure that an LRS stream is created.

View File

@ -123,12 +123,15 @@ func (s) TestWatchCallAnotherWatch(t *testing.T) {
if err != nil {
t.Fatalf("Failed to create bootstrap configuration: %v", err)
}
testutils.CreateBootstrapFileForTesting(t, bc)
// Create an xDS client with the above bootstrap contents.
client, close, err := xdsclient.NewForTesting(xdsclient.OptionsForTesting{
Name: t.Name(),
Contents: bc,
config, err := bootstrap.NewConfigFromContents(bc)
if err != nil {
t.Fatalf("Failed to parse bootstrap contents: %s, %v", string(bc), err)
}
pool := xdsclient.NewPool(config)
client, close, err := pool.NewClientForTesting(xdsclient.OptionsForTesting{
Name: t.Name(),
})
if err != nil {
t.Fatalf("Failed to create xDS client: %v", err)
@ -236,9 +239,13 @@ func (s) TestNodeProtoSentOnlyInFirstRequest(t *testing.T) {
bc := e2e.DefaultBootstrapContents(t, nodeID, mgmtServer.Address)
// Create an xDS client with the above bootstrap contents.
client, close, err := xdsclient.NewForTesting(xdsclient.OptionsForTesting{
Name: t.Name(),
Contents: bc,
config, err := bootstrap.NewConfigFromContents(bc)
if err != nil {
t.Fatalf("Failed to parse bootstrap contents: %s, %v", string(bc), err)
}
pool := xdsclient.NewPool(config)
client, close, err := pool.NewClientForTesting(xdsclient.OptionsForTesting{
Name: t.Name(),
})
if err != nil {
t.Fatalf("Failed to create xDS client: %v", err)

View File

@ -239,12 +239,15 @@ func (s) TestRDSWatch(t *testing.T) {
if err != nil {
t.Fatalf("Failed to create bootstrap configuration: %v", err)
}
testutils.CreateBootstrapFileForTesting(t, bc)
// Create an xDS client with the above bootstrap contents.
client, close, err := xdsclient.NewForTesting(xdsclient.OptionsForTesting{
Name: t.Name(),
Contents: bc,
config, err := bootstrap.NewConfigFromContents(bc)
if err != nil {
t.Fatalf("Failed to parse bootstrap contents: %s, %v", string(bc), err)
}
pool := xdsclient.NewPool(config)
client, close, err := pool.NewClientForTesting(xdsclient.OptionsForTesting{
Name: t.Name(),
})
if err != nil {
t.Fatalf("Failed to create xDS client: %v", err)
@ -429,12 +432,15 @@ func (s) TestRDSWatch_TwoWatchesForSameResourceName(t *testing.T) {
if err != nil {
t.Fatalf("Failed to create bootstrap configuration: %v", err)
}
testutils.CreateBootstrapFileForTesting(t, bc)
// Create an xDS client with the above bootstrap contents.
client, close, err := xdsclient.NewForTesting(xdsclient.OptionsForTesting{
Name: t.Name(),
Contents: bc,
config, err := bootstrap.NewConfigFromContents(bc)
if err != nil {
t.Fatalf("Failed to parse bootstrap contents: %s, %v", string(bc), err)
}
pool := xdsclient.NewPool(config)
client, close, err := pool.NewClientForTesting(xdsclient.OptionsForTesting{
Name: t.Name(),
})
if err != nil {
t.Fatalf("Failed to create xDS client: %v", err)
@ -533,12 +539,15 @@ func (s) TestRDSWatch_ThreeWatchesForDifferentResourceNames(t *testing.T) {
if err != nil {
t.Fatalf("Failed to create bootstrap configuration: %v", err)
}
testutils.CreateBootstrapFileForTesting(t, bc)
// Create an xDS client with the above bootstrap contents.
client, close, err := xdsclient.NewForTesting(xdsclient.OptionsForTesting{
Name: t.Name(),
Contents: bc,
config, err := bootstrap.NewConfigFromContents(bc)
if err != nil {
t.Fatalf("Failed to parse bootstrap contents: %s, %v", string(bc), err)
}
pool := xdsclient.NewPool(config)
client, close, err := pool.NewClientForTesting(xdsclient.OptionsForTesting{
Name: t.Name(),
})
if err != nil {
t.Fatalf("Failed to create xDS client: %v", err)
@ -635,12 +644,15 @@ func (s) TestRDSWatch_ResourceCaching(t *testing.T) {
nodeID := uuid.New().String()
bc := e2e.DefaultBootstrapContents(t, nodeID, mgmtServer.Address)
testutils.CreateBootstrapFileForTesting(t, bc)
// Create an xDS client with the above bootstrap contents.
client, close, err := xdsclient.NewForTesting(xdsclient.OptionsForTesting{
Name: t.Name(),
Contents: bc,
config, err := bootstrap.NewConfigFromContents(bc)
if err != nil {
t.Fatalf("Failed to parse bootstrap contents: %s, %v", string(bc), err)
}
pool := xdsclient.NewPool(config)
client, close, err := pool.NewClientForTesting(xdsclient.OptionsForTesting{
Name: t.Name(),
})
if err != nil {
t.Fatalf("Failed to create xDS client: %v", err)
@ -719,12 +731,15 @@ func (s) TestRDSWatch_ExpiryTimerFiresBeforeResponse(t *testing.T) {
nodeID := uuid.New().String()
bc := e2e.DefaultBootstrapContents(t, nodeID, mgmtServer.Address)
testutils.CreateBootstrapFileForTesting(t, bc)
// Create an xDS client talking to the above management server.
client, close, err := xdsclient.NewForTesting(xdsclient.OptionsForTesting{
config, err := bootstrap.NewConfigFromContents(bc)
if err != nil {
t.Fatalf("Failed to parse bootstrap contents: %s, %v", string(bc), err)
}
pool := xdsclient.NewPool(config)
client, close, err := pool.NewClientForTesting(xdsclient.OptionsForTesting{
Name: t.Name(),
Contents: bc,
WatchExpiryTimeout: defaultTestWatchExpiryTimeout,
})
if err != nil {
@ -759,12 +774,15 @@ func (s) TestRDSWatch_ValidResponseCancelsExpiryTimerBehavior(t *testing.T) {
nodeID := uuid.New().String()
bc := e2e.DefaultBootstrapContents(t, nodeID, mgmtServer.Address)
testutils.CreateBootstrapFileForTesting(t, bc)
// Create an xDS client talking to the above management server.
client, close, err := xdsclient.NewForTesting(xdsclient.OptionsForTesting{
config, err := bootstrap.NewConfigFromContents(bc)
if err != nil {
t.Fatalf("Failed to parse bootstrap contents: %s, %v", string(bc), err)
}
pool := xdsclient.NewPool(config)
client, close, err := pool.NewClientForTesting(xdsclient.OptionsForTesting{
Name: t.Name(),
Contents: bc,
WatchExpiryTimeout: defaultTestWatchExpiryTimeout,
})
if err != nil {
@ -828,12 +846,15 @@ func (s) TestRDSWatch_NACKError(t *testing.T) {
nodeID := uuid.New().String()
bc := e2e.DefaultBootstrapContents(t, nodeID, mgmtServer.Address)
testutils.CreateBootstrapFileForTesting(t, bc)
// Create an xDS client with the above bootstrap contents.
client, close, err := xdsclient.NewForTesting(xdsclient.OptionsForTesting{
Name: t.Name(),
Contents: bc,
config, err := bootstrap.NewConfigFromContents(bc)
if err != nil {
t.Fatalf("Failed to parse bootstrap contents: %s, %v", string(bc), err)
}
pool := xdsclient.NewPool(config)
client, close, err := pool.NewClientForTesting(xdsclient.OptionsForTesting{
Name: t.Name(),
})
if err != nil {
t.Fatalf("Failed to create xDS client: %v", err)
@ -897,12 +918,15 @@ func (s) TestRDSWatch_PartialValid(t *testing.T) {
if err != nil {
t.Fatalf("Failed to create bootstrap configuration: %v", err)
}
testutils.CreateBootstrapFileForTesting(t, bc)
// Create an xDS client with the above bootstrap contents.
client, close, err := xdsclient.NewForTesting(xdsclient.OptionsForTesting{
Name: t.Name(),
Contents: bc,
config, err := bootstrap.NewConfigFromContents(bc)
if err != nil {
t.Fatalf("Failed to parse bootstrap contents: %s, %v", string(bc), err)
}
pool := xdsclient.NewPool(config)
client, close, err := pool.NewClientForTesting(xdsclient.OptionsForTesting{
Name: t.Name(),
})
if err != nil {
t.Fatalf("Failed to create xDS client: %v", err)

View File

@ -283,9 +283,13 @@ func (s) TestHandleListenerResponseFromManagementServer(t *testing.T) {
// Create an xDS client talking to the above management server.
nodeID := uuid.New().String()
bc := e2e.DefaultBootstrapContents(t, nodeID, mgmtServer.Address)
client, close, err := xdsclient.NewForTesting(xdsclient.OptionsForTesting{
config, err := bootstrap.NewConfigFromContents(bc)
if err != nil {
t.Fatalf("Failed to parse bootstrap contents: %s, %v", string(bc), err)
}
pool := xdsclient.NewPool(config)
client, close, err := pool.NewClientForTesting(xdsclient.OptionsForTesting{
Name: t.Name(),
Contents: bc,
WatchExpiryTimeout: defaultTestWatchExpiryTimeout,
})
if err != nil {
@ -349,7 +353,7 @@ func (s) TestHandleListenerResponseFromManagementServer(t *testing.T) {
if diff := cmp.Diff(test.wantUpdate, gotUpdate, cmpOpts...); diff != "" {
t.Fatalf("Unexpected diff in metadata, diff (-want +got):\n%s", diff)
}
if err := compareUpdateMetadata(ctx, xdsclient.DumpResources, test.wantGenericXDSConfig); err != nil {
if err := compareUpdateMetadata(ctx, pool.DumpResources, test.wantGenericXDSConfig); err != nil {
t.Fatal(err)
}
})
@ -559,9 +563,13 @@ func (s) TestHandleRouteConfigResponseFromManagementServer(t *testing.T) {
// Create an xDS client talking to the above management server.
nodeID := uuid.New().String()
bc := e2e.DefaultBootstrapContents(t, nodeID, mgmtServer.Address)
client, close, err := xdsclient.NewForTesting(xdsclient.OptionsForTesting{
config, err := bootstrap.NewConfigFromContents(bc)
if err != nil {
t.Fatalf("Failed to parse bootstrap contents: %s, %v", string(bc), err)
}
pool := xdsclient.NewPool(config)
client, close, err := pool.NewClientForTesting(xdsclient.OptionsForTesting{
Name: t.Name(),
Contents: bc,
WatchExpiryTimeout: defaultTestWatchExpiryTimeout,
})
if err != nil {
@ -624,7 +632,7 @@ func (s) TestHandleRouteConfigResponseFromManagementServer(t *testing.T) {
if diff := cmp.Diff(test.wantUpdate, gotUpdate, cmpOpts...); diff != "" {
t.Fatalf("Unexpected diff in metadata, diff (-want +got):\n%s", diff)
}
if err := compareUpdateMetadata(ctx, xdsclient.DumpResources, test.wantGenericXDSConfig); err != nil {
if err := compareUpdateMetadata(ctx, pool.DumpResources, test.wantGenericXDSConfig); err != nil {
t.Fatal(err)
}
})
@ -796,9 +804,13 @@ func (s) TestHandleClusterResponseFromManagementServer(t *testing.T) {
// Create an xDS client talking to the above management server.
nodeID := uuid.New().String()
bc := e2e.DefaultBootstrapContents(t, nodeID, mgmtServer.Address)
client, close, err := xdsclient.NewForTesting(xdsclient.OptionsForTesting{
config, err := bootstrap.NewConfigFromContents(bc)
if err != nil {
t.Fatalf("Failed to parse bootstrap contents: %s, %v", string(bc), err)
}
pool := xdsclient.NewPool(config)
client, close, err := pool.NewClientForTesting(xdsclient.OptionsForTesting{
Name: t.Name(),
Contents: bc,
WatchExpiryTimeout: defaultTestWatchExpiryTimeout,
})
if err != nil {
@ -875,7 +887,7 @@ func (s) TestHandleClusterResponseFromManagementServer(t *testing.T) {
if diff := cmp.Diff(test.wantUpdate, gotUpdate, cmpOpts...); diff != "" {
t.Fatalf("Unexpected diff in metadata, diff (-want +got):\n%s", diff)
}
if err := compareUpdateMetadata(ctx, xdsclient.DumpResources, test.wantGenericXDSConfig); err != nil {
if err := compareUpdateMetadata(ctx, pool.DumpResources, test.wantGenericXDSConfig); err != nil {
t.Fatal(err)
}
})
@ -1145,9 +1157,13 @@ func (s) TestHandleEndpointsResponseFromManagementServer(t *testing.T) {
// Create an xDS client talking to the above management server.
nodeID := uuid.New().String()
bc := e2e.DefaultBootstrapContents(t, nodeID, mgmtServer.Address)
client, close, err := xdsclient.NewForTesting(xdsclient.OptionsForTesting{
config, err := bootstrap.NewConfigFromContents(bc)
if err != nil {
t.Fatalf("Failed to parse bootstrap contents: %s, %v", string(bc), err)
}
pool := xdsclient.NewPool(config)
client, close, err := pool.NewClientForTesting(xdsclient.OptionsForTesting{
Name: t.Name(),
Contents: bc,
WatchExpiryTimeout: defaultTestWatchExpiryTimeout,
})
if err != nil {
@ -1210,7 +1226,7 @@ func (s) TestHandleEndpointsResponseFromManagementServer(t *testing.T) {
if diff := cmp.Diff(test.wantUpdate, gotUpdate, cmpOpts...); diff != "" {
t.Fatalf("Unexpected diff in metadata, diff (-want +got):\n%s", diff)
}
if err := compareUpdateMetadata(ctx, xdsclient.DumpResources, test.wantGenericXDSConfig); err != nil {
if err := compareUpdateMetadata(ctx, pool.DumpResources, test.wantGenericXDSConfig); err != nil {
t.Fatal(err)
}
})

View File

@ -51,7 +51,7 @@ func (e *xdsClientError) Error() string {
return e.desc
}
// NewErrorf creates an xds client error. The callbacks are called with this
// NewErrorf creates an xDS client error. The callbacks are called with this
// error, to pass additional information about the error.
func NewErrorf(t ErrorType, format string, args ...any) error {
return &xdsClientError{t: t, desc: fmt.Sprintf(format, args...)}

View File

@ -42,10 +42,8 @@ import (
const serverPrefix = "[xds-server %p] "
var (
// These new functions will be overridden in unit tests.
newXDSClient = func(name string) (xdsclient.XDSClient, func(), error) {
return xdsclient.New(name)
}
// These will be overridden in unit tests.
xdsClientPool = xdsclient.DefaultPool
newGRPCServer = func(opts ...grpc.ServerOption) grpcServer {
return grpc.NewServer(opts...)
}
@ -92,17 +90,11 @@ func NewGRPCServer(opts ...grpc.ServerOption) (*GRPCServer, error) {
// Initializing the xDS client upfront (instead of at serving time)
// simplifies the code by eliminating the need for a mutex to protect the
// xdsC and xdsClientClose fields.
newXDSClient := newXDSClient
if s.opts.bootstrapContentsForTesting != nil {
// Bootstrap file contents may be specified as a server option for tests.
newXDSClient = func(name string) (xdsclient.XDSClient, func(), error) {
return xdsclient.NewForTesting(xdsclient.OptionsForTesting{
Name: name,
Contents: s.opts.bootstrapContentsForTesting,
})
}
pool := xdsClientPool
if s.opts.clientPoolForTesting != nil {
pool = s.opts.clientPoolForTesting
}
xdsClient, xdsClientClose, err := newXDSClient(xdsclient.NameForServer)
xdsClient, xdsClientClose, err := pool.NewClient(xdsclient.NameForServer)
if err != nil {
return nil, fmt.Errorf("xDS client creation failed: %v", err)
}

View File

@ -38,6 +38,8 @@ import (
"google.golang.org/grpc/internal/stubserver"
"google.golang.org/grpc/internal/testutils"
"google.golang.org/grpc/internal/testutils/xds/e2e"
"google.golang.org/grpc/internal/xds/bootstrap"
"google.golang.org/grpc/peer"
"google.golang.org/grpc/status"
"google.golang.org/grpc/xds"
xdsinternal "google.golang.org/grpc/xds/internal"
@ -142,7 +144,12 @@ func (s) TestServingModeChanges(t *testing.T) {
}
},
}
sopts := []grpc.ServerOption{grpc.Creds(insecure.NewCredentials()), modeChangeOpt, xds.BootstrapContentsForTesting(bootstrapContents)}
config, err := bootstrap.NewConfigFromContents(bootstrapContents)
if err != nil {
t.Fatalf("Failed to parse bootstrap contents: %s, %v", string(bootstrapContents), err)
}
pool := xdsclient.NewPool(config)
sopts := []grpc.ServerOption{grpc.Creds(insecure.NewCredentials()), modeChangeOpt, xds.ClientPoolForTesting(pool)}
if stub.S, err = xds.NewGRPCServer(sopts...); err != nil {
t.Fatalf("Failed to create an xDS enabled gRPC server: %v", err)
}
@ -174,7 +181,7 @@ func (s) TestServingModeChanges(t *testing.T) {
// A unary RPC should work once it transitions into serving. (need this same
// assertion from LDS resource not found triggering it).
waitForSuccessfulRPC(ctx, t, cc)
waitForSuccessfulRPC(ctx, t, cc, grpc.WaitForReady(true))
// Start a stream before switching the server to not serving. Due to the
// stream being created before the graceful stop of the underlying
@ -188,7 +195,7 @@ func (s) TestServingModeChanges(t *testing.T) {
// Lookup the xDS client in use based on the dedicated well-known key, as
// defined in A71, used by the xDS enabled gRPC server.
xdsC, close, err := xdsclient.GetForTesting(xdsclient.NameForServer)
xdsC, close, err := pool.GetClientForTesting(xdsclient.NameForServer)
if err != nil {
t.Fatalf("Failed to find xDS client for configuration: %v", string(bootstrapContents))
}
@ -217,6 +224,140 @@ func (s) TestServingModeChanges(t *testing.T) {
waitForFailedRPCWithStatus(ctx, t, cc, errAcceptAndClose)
}
// TestMultipleServers_DifferentBootstrapConfigurations verifies that multiple
// xDS-enabled gRPC servers can be created with different bootstrap
// configurations, and that they correctly request different LDS resources from
// the management server based on their respective listening ports. It also
// ensures that gRPC clients can connect to the intended server and that RPCs
// function correctly. The test uses the grpc.Peer() call option to validate
// that the client is connected to the correct server.
func (s) TestMultipleServers_DifferentBootstrapConfigurations(t *testing.T) {
// Setup an xDS management server.
mgmtServer := e2e.StartManagementServer(t, e2e.ManagementServerOptions{})
// Create two bootstrap configurations pointing to the above management server.
nodeID1 := uuid.New().String()
bootstrapContents1 := e2e.DefaultBootstrapContents(t, nodeID1, mgmtServer.Address)
nodeID2 := uuid.New().String()
bootstrapContents2 := e2e.DefaultBootstrapContents(t, nodeID2, mgmtServer.Address)
// Create two xDS-enabled gRPC servers using the above bootstrap configs.
lis1, err := testutils.LocalTCPListener()
if err != nil {
t.Fatalf("testutils.LocalTCPListener() failed: %v", err)
}
lis2, err := testutils.LocalTCPListener()
if err != nil {
t.Fatalf("testutils.LocalTCPListener() failed: %v", err)
}
serving1 := grpcsync.NewEvent()
modeChangeOpt1 := xds.ServingModeCallback(func(addr net.Addr, args xds.ServingModeChangeArgs) {
t.Logf("Serving mode for listener %q changed to %q, err: %v", addr.String(), args.Mode, args.Err)
if args.Mode == connectivity.ServingModeServing {
serving1.Fire()
}
})
serving2 := grpcsync.NewEvent()
modeChangeOpt2 := xds.ServingModeCallback(func(addr net.Addr, args xds.ServingModeChangeArgs) {
t.Logf("Serving mode for listener %q changed to %q, err: %v", addr.String(), args.Mode, args.Err)
if args.Mode == connectivity.ServingModeServing {
serving2.Fire()
}
})
stub1 := &stubserver.StubServer{
Listener: lis1,
EmptyCallF: func(ctx context.Context, _ *testpb.Empty) (*testpb.Empty, error) {
return &testpb.Empty{}, nil
},
}
if stub1.S, err = xds.NewGRPCServer(xds.BootstrapContentsForTesting(bootstrapContents1), modeChangeOpt1); err != nil {
t.Fatalf("Failed to create first xDS enabled gRPC server: %v", err)
}
stubserver.StartTestService(t, stub1)
defer stub1.S.Stop()
stub2 := &stubserver.StubServer{
Listener: lis2,
EmptyCallF: func(ctx context.Context, _ *testpb.Empty) (*testpb.Empty, error) {
return &testpb.Empty{}, nil
},
}
if stub2.S, err = xds.NewGRPCServer(xds.BootstrapContentsForTesting(bootstrapContents2), modeChangeOpt2); err != nil {
t.Fatalf("Failed to create second xDS enabled gRPC server: %v", err)
}
stubserver.StartTestService(t, stub2)
defer stub2.S.Stop()
// Update the management server with the listener resources pointing to the
// corresponding gRPC servers.
ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout)
defer cancel()
host1, port1, err := hostPortFromListener(lis1)
if err != nil {
t.Fatalf("Failed to retrieve host and port of server: %v", err)
}
host2, port2, err := hostPortFromListener(lis2)
if err != nil {
t.Fatalf("Failed to retrieve host and port of server: %v", err)
}
resources1 := e2e.UpdateOptions{
NodeID: nodeID1,
Listeners: []*v3listenerpb.Listener{e2e.DefaultServerListener(host1, port1, e2e.SecurityLevelNone, "routeName")},
}
if err := mgmtServer.Update(ctx, resources1); err != nil {
t.Fatal(err)
}
resources2 := e2e.UpdateOptions{
NodeID: nodeID2,
Listeners: []*v3listenerpb.Listener{e2e.DefaultServerListener(host2, port2, e2e.SecurityLevelNone, "routeName")},
}
if err := mgmtServer.Update(ctx, resources2); err != nil {
t.Fatal(err)
}
select {
case <-ctx.Done():
t.Fatal("Timeout waiting for the server 1 to go Serving")
case <-serving1.Done():
}
select {
case <-ctx.Done():
t.Fatal("Timeout waiting for the server 2 to go Serving")
case <-serving2.Done():
}
// Create two gRPC clients, one for each server.
cc1, err := grpc.NewClient(lis1.Addr().String(), grpc.WithTransportCredentials(insecure.NewCredentials()))
if err != nil {
t.Fatalf("Failed to create client for test server 1: %s, %v", lis1.Addr().String(), err)
}
defer cc1.Close()
cc2, err := grpc.NewClient(lis2.Addr().String(), grpc.WithTransportCredentials(insecure.NewCredentials()))
if err != nil {
t.Fatalf("Failed to create client for test server 2: %s, %v", lis2.Addr().String(), err)
}
defer cc2.Close()
// Both unary RPCs should work once the servers transitions into serving.
var peer1 peer.Peer
waitForSuccessfulRPC(ctx, t, cc1, grpc.Peer(&peer1))
if peer1.Addr.String() != lis1.Addr().String() {
t.Errorf("Connected to wrong peer: %s, want %s", peer1.Addr, lis1.Addr())
}
var peer2 peer.Peer
waitForSuccessfulRPC(ctx, t, cc2, grpc.Peer(&peer2))
if peer2.Addr.String() != lis2.Addr().String() {
t.Errorf("Connected to wrong peer: %s, want %s", peer2.Addr, lis2.Addr())
}
}
// TestResourceNotFoundRDS tests the case where an LDS points to an RDS which
// returns resource not found. Before getting the resource not found, the xDS
// Server has not received all configuration needed, so it should Accept and
@ -280,7 +421,12 @@ func (s) TestResourceNotFoundRDS(t *testing.T) {
}
},
}
sopts := []grpc.ServerOption{grpc.Creds(insecure.NewCredentials()), modeChangeOpt, xds.BootstrapContentsForTesting(bootstrapContents)}
config, err := bootstrap.NewConfigFromContents(bootstrapContents)
if err != nil {
t.Fatalf("Failed to parse bootstrap contents: %s, %v", string(bootstrapContents), err)
}
pool := xdsclient.NewPool(config)
sopts := []grpc.ServerOption{grpc.Creds(insecure.NewCredentials()), modeChangeOpt, xds.ClientPoolForTesting(pool)}
if stub.S, err = xds.NewGRPCServer(sopts...); err != nil {
t.Fatalf("Failed to create an xDS enabled gRPC server: %v", err)
}
@ -297,7 +443,7 @@ func (s) TestResourceNotFoundRDS(t *testing.T) {
// Lookup the xDS client in use based on the dedicated well-known key, as
// defined in A71, used by the xDS enabled gRPC server.
xdsC, close, err := xdsclient.GetForTesting(xdsclient.NameForServer)
xdsC, close, err := pool.GetClientForTesting(xdsclient.NameForServer)
if err != nil {
t.Fatalf("Failed to find xDS client for configuration: %v", string(bootstrapContents))
}
@ -325,11 +471,11 @@ loop:
waitForFailedRPCWithStatus(ctx, t, cc, status.New(codes.Unavailable, "error from xDS configuration for matched route configuration"))
}
func waitForSuccessfulRPC(ctx context.Context, t *testing.T, cc *grpc.ClientConn) {
func waitForSuccessfulRPC(ctx context.Context, t *testing.T, cc *grpc.ClientConn, opts ...grpc.CallOption) {
t.Helper()
c := testgrpc.NewTestServiceClient(cc)
if _, err := c.EmptyCall(ctx, &testpb.Empty{}, grpc.WaitForReady(true)); err != nil {
if _, err := c.EmptyCall(ctx, &testpb.Empty{}, opts...); err != nil {
t.Fatalf("rpc EmptyCall() failed: %v", err)
}
}

View File

@ -23,11 +23,13 @@ import (
"google.golang.org/grpc"
"google.golang.org/grpc/connectivity"
"google.golang.org/grpc/internal/xds/bootstrap"
"google.golang.org/grpc/xds/internal/xdsclient"
)
type serverOptions struct {
modeCallback ServingModeCallbackFunc
bootstrapContentsForTesting []byte
modeCallback ServingModeCallbackFunc
clientPoolForTesting *xdsclient.Pool
}
type serverOption struct {
@ -71,6 +73,28 @@ type ServingModeChangeArgs struct {
//
// Notice: This API is EXPERIMENTAL and may be changed or removed in a
// later release.
func BootstrapContentsForTesting(contents []byte) grpc.ServerOption {
return &serverOption{apply: func(o *serverOptions) { o.bootstrapContentsForTesting = contents }}
func BootstrapContentsForTesting(bootstrapContents []byte) grpc.ServerOption {
config, err := bootstrap.NewConfigFromContents(bootstrapContents)
if err != nil {
logger.Warningf("Failed to parse bootstrap contents %s for server options: %v", string(bootstrapContents), err)
return &serverOption{apply: func(o *serverOptions) { o.clientPoolForTesting = nil }}
}
return ClientPoolForTesting(xdsclient.NewPool(config))
}
// ClientPoolForTesting returns a grpc.ServerOption with the pool for xds
// clients. It allows users to set a pool for xDS clients sharing the bootstrap
// contents for this server.
//
// # Testing Only
//
// This function should ONLY be used for testing and may not work with some
// other features, including the CSDS service.
//
// # Experimental
//
// Notice: This API is EXPERIMENTAL and may be changed or removed in a
// later release.
func ClientPoolForTesting(pool *xdsclient.Pool) grpc.ServerOption {
return &serverOption{apply: func(o *serverOptions) { o.clientPoolForTesting = pool }}
}

View File

@ -177,14 +177,14 @@ func (s) TestNewServer_Failure(t *testing.T) {
}{
{
desc: "bootstrap env var not set",
serverOpts: []grpc.ServerOption{grpc.Creds(xdsCreds)},
wantErr: "failed to get xDS bootstrap config",
serverOpts: []grpc.ServerOption{grpc.Creds(xdsCreds), BootstrapContentsForTesting(nil)},
wantErr: "bootstrap configuration not set in the pool",
},
{
desc: "empty bootstrap config",
serverOpts: []grpc.ServerOption{
grpc.Creds(xdsCreds),
BootstrapContentsForTesting([]byte(`{}`)),
BootstrapContentsForTesting(nil),
},
wantErr: "xDS client creation failed",
},
@ -474,11 +474,9 @@ func (s) TestServeSuccess(t *testing.T) {
// TestNewServer_ClientCreationFailure tests the case where the xDS client
// creation fails and verifies that the call to NewGRPCServer() fails.
func (s) TestNewServer_ClientCreationFailure(t *testing.T) {
origNewXDSClient := newXDSClient
newXDSClient = func(string) (xdsclient.XDSClient, func(), error) {
return nil, nil, errors.New("xdsClient creation failed")
}
defer func() { newXDSClient = origNewXDSClient }()
origXDSClientPool := xdsClientPool
xdsClientPool = xdsclient.NewPool(nil)
defer func() { xdsClientPool = origXDSClientPool }()
if _, err := NewGRPCServer(); err == nil {
t.Fatal("NewGRPCServer() succeeded when expected to fail")