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