1/* 2 * 3 * Copyright 2020 gRPC authors. 4 * 5 * Licensed under the Apache License, Version 2.0 (the "License"); 6 * you may not use this file except in compliance with the License. 7 * You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 * 17 */ 18 19package client 20 21import ( 22 "fmt" 23 "sync" 24 "time" 25 26 "google.golang.org/grpc/xds/internal/client/bootstrap" 27) 28 29const defaultWatchExpiryTimeout = 15 * time.Second 30 31// This is the Client returned by New(). It contains one client implementation, 32// and maintains the refcount. 33var singletonClient = &Client{} 34 35// To override in tests. 36var bootstrapNewConfig = bootstrap.NewConfig 37 38// Client is a full fledged gRPC client which queries a set of discovery APIs 39// (collectively termed as xDS) on a remote management server, to discover 40// various dynamic resources. 41// 42// The xds client is a singleton. It will be shared by the xds resolver and 43// balancer implementations, across multiple ClientConns and Servers. 44type Client struct { 45 *clientImpl 46 47 // This mu protects all the fields, including the embedded clientImpl above. 48 mu sync.Mutex 49 refCount int 50} 51 52// New returns a new xdsClient configured by the bootstrap file specified in env 53// variable GRPC_XDS_BOOTSTRAP or GRPC_XDS_BOOTSTRAP_CONFIG. 54// 55// The returned xdsClient is a singleton. This function creates the xds client 56// if it doesn't already exist. 57// 58// Note that the first invocation of New() or NewWithConfig() sets the client 59// singleton. The following calls will return the singleton xds client without 60// checking or using the config. 61func New() (*Client, error) { 62 singletonClient.mu.Lock() 63 defer singletonClient.mu.Unlock() 64 // If the client implementation was created, increment ref count and return 65 // the client. 66 if singletonClient.clientImpl != nil { 67 singletonClient.refCount++ 68 return singletonClient, nil 69 } 70 71 // Create the new client implementation. 72 config, err := bootstrapNewConfig() 73 if err != nil { 74 return nil, fmt.Errorf("xds: failed to read bootstrap file: %v", err) 75 } 76 c, err := newWithConfig(config, defaultWatchExpiryTimeout) 77 if err != nil { 78 return nil, err 79 } 80 81 singletonClient.clientImpl = c 82 singletonClient.refCount++ 83 return singletonClient, nil 84} 85 86// NewWithConfig returns a new xdsClient configured by the given config. 87// 88// The returned xdsClient is a singleton. This function creates the xds client 89// if it doesn't already exist. 90// 91// Note that the first invocation of New() or NewWithConfig() sets the client 92// singleton. The following calls will return the singleton xds client without 93// checking or using the config. 94// 95// This function is internal only, for c2p resolver to use. DO NOT use this 96// elsewhere. Use New() instead. 97func NewWithConfig(config *bootstrap.Config) (*Client, error) { 98 singletonClient.mu.Lock() 99 defer singletonClient.mu.Unlock() 100 // If the client implementation was created, increment ref count and return 101 // the client. 102 if singletonClient.clientImpl != nil { 103 singletonClient.refCount++ 104 return singletonClient, nil 105 } 106 107 // Create the new client implementation. 108 c, err := newWithConfig(config, defaultWatchExpiryTimeout) 109 if err != nil { 110 return nil, err 111 } 112 113 singletonClient.clientImpl = c 114 singletonClient.refCount++ 115 return singletonClient, nil 116} 117 118// Close closes the client. It does ref count of the xds client implementation, 119// and closes the gRPC connection to the management server when ref count 120// reaches 0. 121func (c *Client) Close() { 122 c.mu.Lock() 123 defer c.mu.Unlock() 124 c.refCount-- 125 if c.refCount == 0 { 126 c.clientImpl.Close() 127 // Set clientImpl back to nil. So if New() is called after this, a new 128 // implementation will be created. 129 c.clientImpl = nil 130 } 131} 132 133// NewWithConfigForTesting is exported for testing only. 134// 135// Note that this function doesn't set the singleton, so that the testing states 136// don't leak. 137func NewWithConfigForTesting(config *bootstrap.Config, watchExpiryTimeout time.Duration) (*Client, error) { 138 cl, err := newWithConfig(config, watchExpiryTimeout) 139 if err != nil { 140 return nil, err 141 } 142 return &Client{clientImpl: cl, refCount: 1}, nil 143} 144