github.com/hxx258456/ccgo@v0.0.5-0.20230213014102-48b35f46f66f/grpc/xds/internal/resolver/xds_resolver.go (about) 1 /* 2 * Copyright 2019 gRPC authors. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 * 16 */ 17 18 // Package resolver implements the xds resolver, that does LDS and RDS to find 19 // the cluster to use. 20 package resolver 21 22 import ( 23 "errors" 24 "fmt" 25 "strings" 26 27 "github.com/hxx258456/ccgo/grpc/credentials" 28 "github.com/hxx258456/ccgo/grpc/internal/grpclog" 29 "github.com/hxx258456/ccgo/grpc/internal/grpcsync" 30 "github.com/hxx258456/ccgo/grpc/internal/pretty" 31 iresolver "github.com/hxx258456/ccgo/grpc/internal/resolver" 32 "github.com/hxx258456/ccgo/grpc/resolver" 33 "github.com/hxx258456/ccgo/grpc/xds/internal/xdsclient" 34 "github.com/hxx258456/ccgo/grpc/xds/internal/xdsclient/bootstrap" 35 "github.com/hxx258456/ccgo/grpc/xds/internal/xdsclient/xdsresource" 36 ) 37 38 const xdsScheme = "xds" 39 40 // NewBuilder creates a new xds resolver builder using a specific xds bootstrap 41 // config, so tests can use multiple xds clients in different ClientConns at 42 // the same time. 43 func NewBuilder(config []byte) (resolver.Builder, error) { 44 return &xdsResolverBuilder{ 45 newXDSClient: func() (xdsclient.XDSClient, error) { 46 return xdsclient.NewClientWithBootstrapContents(config) 47 }, 48 }, nil 49 } 50 51 // For overriding in unittests. 52 var newXDSClient = func() (xdsclient.XDSClient, error) { return xdsclient.New() } 53 54 func init() { 55 resolver.Register(&xdsResolverBuilder{}) 56 } 57 58 type xdsResolverBuilder struct { 59 newXDSClient func() (xdsclient.XDSClient, error) 60 } 61 62 // Build helps implement the resolver.Builder interface. 63 // 64 // The xds bootstrap process is performed (and a new xds client is built) every 65 // time an xds resolver is built. 66 func (b *xdsResolverBuilder) Build(t resolver.Target, cc resolver.ClientConn, opts resolver.BuildOptions) (_ resolver.Resolver, retErr error) { 67 r := &xdsResolver{ 68 target: t, 69 cc: cc, 70 closed: grpcsync.NewEvent(), 71 updateCh: make(chan suWithError, 1), 72 activeClusters: make(map[string]*clusterInfo), 73 } 74 defer func() { 75 if retErr != nil { 76 r.Close() 77 } 78 }() 79 r.logger = prefixLogger(r) 80 r.logger.Infof("Creating resolver for target: %+v", t) 81 82 newXDSClient := newXDSClient 83 if b.newXDSClient != nil { 84 newXDSClient = b.newXDSClient 85 } 86 87 client, err := newXDSClient() 88 if err != nil { 89 return nil, fmt.Errorf("xds: failed to create xds-client: %v", err) 90 } 91 r.client = client 92 bootstrapConfig := client.BootstrapConfig() 93 if bootstrapConfig == nil { 94 return nil, errors.New("bootstrap configuration is empty") 95 } 96 97 // If xds credentials were specified by the user, but bootstrap configs do 98 // not contain any certificate provider configuration, it is better to fail 99 // right now rather than failing when attempting to create certificate 100 // providers after receiving an CDS response with security configuration. 101 var creds credentials.TransportCredentials 102 switch { 103 case opts.DialCreds != nil: 104 creds = opts.DialCreds 105 case opts.CredsBundle != nil: 106 creds = opts.CredsBundle.TransportCredentials() 107 } 108 if xc, ok := creds.(interface{ UsesXDS() bool }); ok && xc.UsesXDS() { 109 if len(bootstrapConfig.CertProviderConfigs) == 0 { 110 return nil, errors.New("xds: xdsCreds specified but certificate_providers config missing in bootstrap file") 111 } 112 } 113 114 // Find the client listener template to use from the bootstrap config: 115 // - If authority is not set in the target, use the top level template 116 // - If authority is set, use the template from the authority map. 117 template := bootstrapConfig.ClientDefaultListenerResourceNameTemplate 118 if authority := r.target.URL.Host; authority != "" { 119 a := bootstrapConfig.Authorities[authority] 120 if a == nil { 121 return nil, fmt.Errorf("xds: authority %q is not found in the bootstrap file", authority) 122 } 123 if a.ClientListenerResourceNameTemplate != "" { 124 // This check will never be false, because 125 // ClientListenerResourceNameTemplate is required to start with 126 // xdstp://, and has a default value (not an empty string) if unset. 127 template = a.ClientListenerResourceNameTemplate 128 } 129 } 130 endpoint := r.target.URL.Path 131 if endpoint == "" { 132 endpoint = r.target.URL.Opaque 133 } 134 endpoint = strings.TrimPrefix(endpoint, "/") 135 resourceName := bootstrap.PopulateResourceTemplate(template, endpoint) 136 137 // Register a watch on the xdsClient for the user's dial target. 138 cancelWatch := watchService(r.client, resourceName, r.handleServiceUpdate, r.logger) 139 r.logger.Infof("Watch started on resource name %v with xds-client %p", r.target.Endpoint, r.client) 140 r.cancelWatch = func() { 141 cancelWatch() 142 r.logger.Infof("Watch cancel on resource name %v with xds-client %p", r.target.Endpoint, r.client) 143 } 144 145 go r.run() 146 return r, nil 147 } 148 149 // Name helps implement the resolver.Builder interface. 150 func (*xdsResolverBuilder) Scheme() string { 151 return xdsScheme 152 } 153 154 // suWithError wraps the ServiceUpdate and error received through a watch API 155 // callback, so that it can pushed onto the update channel as a single entity. 156 type suWithError struct { 157 su serviceUpdate 158 emptyUpdate bool 159 err error 160 } 161 162 // xdsResolver implements the resolver.Resolver interface. 163 // 164 // It registers a watcher for ServiceConfig updates with the xdsClient object 165 // (which performs LDS/RDS queries for the same), and passes the received 166 // updates to the ClientConn. 167 type xdsResolver struct { 168 target resolver.Target 169 cc resolver.ClientConn 170 closed *grpcsync.Event 171 172 logger *grpclog.PrefixLogger 173 174 // The underlying xdsClient which performs all xDS requests and responses. 175 client xdsclient.XDSClient 176 // A channel for the watch API callback to write service updates on to. The 177 // updates are read by the run goroutine and passed on to the ClientConn. 178 updateCh chan suWithError 179 // cancelWatch is the function to cancel the watcher. 180 cancelWatch func() 181 182 // activeClusters is a map from cluster name to a ref count. Only read or 183 // written during a service update (synchronous). 184 activeClusters map[string]*clusterInfo 185 186 curConfigSelector *configSelector 187 } 188 189 // sendNewServiceConfig prunes active clusters, generates a new service config 190 // based on the current set of active clusters, and sends an update to the 191 // channel with that service config and the provided config selector. Returns 192 // false if an error occurs while generating the service config and the update 193 // cannot be sent. 194 func (r *xdsResolver) sendNewServiceConfig(cs *configSelector) bool { 195 // Delete entries from r.activeClusters with zero references; 196 // otherwise serviceConfigJSON will generate a config including 197 // them. 198 r.pruneActiveClusters() 199 200 if cs == nil && len(r.activeClusters) == 0 { 201 // There are no clusters and we are sending a failing configSelector. 202 // Send an empty config, which picks pick-first, with no address, and 203 // puts the ClientConn into transient failure. 204 r.cc.UpdateState(resolver.State{ServiceConfig: r.cc.ParseServiceConfig("{}")}) 205 return true 206 } 207 208 sc, err := serviceConfigJSON(r.activeClusters) 209 if err != nil { 210 // JSON marshal error; should never happen. 211 r.logger.Errorf("%v", err) 212 r.cc.ReportError(err) 213 return false 214 } 215 r.logger.Infof("Received update on resource %v from xds-client %p, generated service config: %v", r.target.Endpoint, r.client, pretty.FormatJSON(sc)) 216 217 // Send the update to the ClientConn. 218 state := iresolver.SetConfigSelector(resolver.State{ 219 ServiceConfig: r.cc.ParseServiceConfig(string(sc)), 220 }, cs) 221 r.cc.UpdateState(xdsclient.SetClient(state, r.client)) 222 return true 223 } 224 225 // run is a long running goroutine which blocks on receiving service updates 226 // and passes it on the ClientConn. 227 func (r *xdsResolver) run() { 228 for { 229 select { 230 case <-r.closed.Done(): 231 return 232 case update := <-r.updateCh: 233 if update.err != nil { 234 r.logger.Warningf("Watch error on resource %v from xds-client %p, %v", r.target.Endpoint, r.client, update.err) 235 if xdsresource.ErrType(update.err) == xdsresource.ErrorTypeResourceNotFound { 236 // If error is resource-not-found, it means the LDS 237 // resource was removed. Ultimately send an empty service 238 // config, which picks pick-first, with no address, and 239 // puts the ClientConn into transient failure. Before we 240 // can do that, we may need to send a normal service config 241 // along with an erroring (nil) config selector. 242 r.sendNewServiceConfig(nil) 243 // Stop and dereference the active config selector, if one exists. 244 r.curConfigSelector.stop() 245 r.curConfigSelector = nil 246 continue 247 } 248 // Send error to ClientConn, and balancers, if error is not 249 // resource not found. No need to update resolver state if we 250 // can keep using the old config. 251 r.cc.ReportError(update.err) 252 continue 253 } 254 if update.emptyUpdate { 255 r.sendNewServiceConfig(r.curConfigSelector) 256 continue 257 } 258 259 // Create the config selector for this update. 260 cs, err := r.newConfigSelector(update.su) 261 if err != nil { 262 r.logger.Warningf("Error parsing update on resource %v from xds-client %p: %v", r.target.Endpoint, r.client, err) 263 r.cc.ReportError(err) 264 continue 265 } 266 267 if !r.sendNewServiceConfig(cs) { 268 // JSON error creating the service config (unexpected); erase 269 // this config selector and ignore this update, continuing with 270 // the previous config selector. 271 cs.stop() 272 continue 273 } 274 275 // Decrement references to the old config selector and assign the 276 // new one as the current one. 277 r.curConfigSelector.stop() 278 r.curConfigSelector = cs 279 } 280 } 281 } 282 283 // handleServiceUpdate is the callback which handles service updates. It writes 284 // the received update to the update channel, which is picked by the run 285 // goroutine. 286 func (r *xdsResolver) handleServiceUpdate(su serviceUpdate, err error) { 287 if r.closed.HasFired() { 288 // Do not pass updates to the ClientConn once the resolver is closed. 289 return 290 } 291 // Remove any existing entry in updateCh and replace with the new one. 292 select { 293 case <-r.updateCh: 294 default: 295 } 296 r.updateCh <- suWithError{su: su, err: err} 297 } 298 299 // ResolveNow is a no-op at this point. 300 func (*xdsResolver) ResolveNow(o resolver.ResolveNowOptions) {} 301 302 // Close closes the resolver, and also closes the underlying xdsClient. 303 func (r *xdsResolver) Close() { 304 // Note that Close needs to check for nils even if some of them are always 305 // set in the constructor. This is because the constructor defers Close() in 306 // error cases, and the fields might not be set when the error happens. 307 if r.cancelWatch != nil { 308 r.cancelWatch() 309 } 310 if r.client != nil { 311 r.client.Close() 312 } 313 r.closed.Fire() 314 r.logger.Infof("Shutdown") 315 }