grpc-go/xds/internal/xdsclient/client_new.go

159 lines
5.7 KiB
Go

/*
*
* 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/cache"
"google.golang.org/grpc/internal/grpcsync"
"google.golang.org/grpc/internal/xds/bootstrap"
"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) {
return newRefCounted(name, defaultWatchExpiryTimeout, defaultIdleAuthorityDeleteTimeout)
}
// newClientImpl returns a new xdsClient with the given config.
func newClientImpl(config *bootstrap.Config, watchExpiryTimeout time.Duration, idleAuthorityDeleteTimeout time.Duration) (*clientImpl, error) {
ctx, cancel := context.WithCancel(context.Background())
c := &clientImpl{
done: grpcsync.NewEvent(),
config: config,
watchExpiryTimeout: watchExpiryTimeout,
serializer: grpcsync.NewCallbackSerializer(ctx),
serializerClose: cancel,
resourceTypes: newResourceTypeRegistry(),
authorities: make(map[string]*authority),
idleAuthorities: cache.NewTimeoutCache(idleAuthorityDeleteTimeout),
}
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
// AuthorityIdleTimeout is the timeout before idle authorities are deleted.
// If unspecified, uses the default value used in non-test code.
AuthorityIdleTimeout 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.AuthorityIdleTimeout == 0 {
opts.AuthorityIdleTimeout = defaultIdleAuthorityDeleteTimeout
}
if err := bootstrap.SetFallbackBootstrapConfig(opts.Contents); err != nil {
return nil, nil, err
}
client, cancel, err := newRefCounted(opts.Name, opts.WatchExpiryTimeout, opts.AuthorityIdleTimeout)
return client, func() { bootstrap.UnsetFallbackBootstrapConfigForTesting(); cancel() }, err
}
// 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, grpcsync.OnceFunc(func() { clientRefCountedClose(name) }), nil
}
func init() {
internal.TriggerXDSResourceNotFoundForTesting = triggerXDSResourceNotFoundForTesting
}
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)
}
var (
clients = map[string]*clientRefCounted{}
clientsMu sync.Mutex
)