dubbo.apache.org/dubbo-go/v3@v3.1.1/xds/balancer/cdsbalancer/cluster_handler.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 2021 gRPC authors. 21 * 22 */ 23 24 package cdsbalancer 25 26 import ( 27 "errors" 28 "sync" 29 ) 30 31 import ( 32 "dubbo.apache.org/dubbo-go/v3/xds/client" 33 "dubbo.apache.org/dubbo-go/v3/xds/client/resource" 34 ) 35 36 var errNotReceivedUpdate = errors.New("tried to construct a cluster update on a cluster that has not received an update") 37 38 // clusterHandlerUpdate wraps the information received from the registered CDS 39 // watcher. A non-nil error is propagated to the underlying cluster_resolver 40 // balancer. A valid update results in creating a new cluster_resolver balancer 41 // (if one doesn't already exist) and pushing the update to it. 42 type clusterHandlerUpdate struct { 43 // securityCfg is the Security Config from the top (root) cluster. 44 securityCfg *resource.SecurityConfig 45 // lbPolicy is the lb policy from the top (root) cluster. 46 // 47 // Currently, we only support roundrobin or ringhash, and since roundrobin 48 // does need configs, this is only set to the ringhash config, if the policy 49 // is ringhash. In the future, if we support more policies, we can make this 50 // an interface, and set it to config of the other policies. 51 lbPolicy *resource.ClusterLBPolicyRingHash 52 53 // updates is a list of ClusterUpdates from all the leaf clusters. 54 updates []resource.ClusterUpdate 55 err error 56 } 57 58 // clusterHandler will be given a name representing a cluster. It will then 59 // update the CDS policy constantly with a list of Clusters to pass down to 60 // XdsClusterResolverLoadBalancingPolicyConfig in a stream like fashion. 61 type clusterHandler struct { 62 parent *cdsBalancer 63 64 // A mutex to protect entire tree of clusters. 65 clusterMutex sync.Mutex 66 root *clusterNode 67 rootClusterName string 68 69 // A way to ping CDS Balancer about any updates or errors to a Node in the 70 // tree. This will either get called from this handler constructing an 71 // update or from a child with an error. Capacity of one as the only update 72 // CDS Balancer cares about is the most recent update. 73 updateChannel chan clusterHandlerUpdate 74 } 75 76 func newClusterHandler(parent *cdsBalancer) *clusterHandler { 77 return &clusterHandler{ 78 parent: parent, 79 updateChannel: make(chan clusterHandlerUpdate, 1), 80 } 81 } 82 83 func (ch *clusterHandler) updateRootCluster(rootClusterName string) { 84 ch.clusterMutex.Lock() 85 defer ch.clusterMutex.Unlock() 86 if ch.root == nil { 87 // Construct a root node on first update. 88 ch.root = createClusterNode(rootClusterName, ch.parent.xdsClient, ch) 89 ch.rootClusterName = rootClusterName 90 return 91 } 92 // Check if root cluster was changed. If it was, delete old one and start 93 // new one, if not do nothing. 94 if rootClusterName != ch.rootClusterName { 95 ch.root.delete() 96 ch.root = createClusterNode(rootClusterName, ch.parent.xdsClient, ch) 97 ch.rootClusterName = rootClusterName 98 } 99 } 100 101 // This function tries to construct a cluster update to send to CDS. 102 func (ch *clusterHandler) constructClusterUpdate() { 103 if ch.root == nil { 104 // If root is nil, this handler is closed, ignore the update. 105 return 106 } 107 clusterUpdate, err := ch.root.constructClusterUpdate() 108 if err != nil { 109 // If there was an error received no op, as this simply means one of the 110 // children hasn't received an update yet. 111 return 112 } 113 // For a ClusterUpdate, the only update CDS cares about is the most 114 // recent one, so opportunistically drain the update channel before 115 // sending the new update. 116 select { 117 case <-ch.updateChannel: 118 default: 119 } 120 ch.updateChannel <- clusterHandlerUpdate{ 121 securityCfg: ch.root.clusterUpdate.SecurityCfg, 122 lbPolicy: ch.root.clusterUpdate.LBPolicy, 123 updates: clusterUpdate, 124 } 125 } 126 127 // close() is meant to be called by CDS when the CDS balancer is closed, and it 128 // cancels the watches for every cluster in the cluster tree. 129 func (ch *clusterHandler) close() { 130 ch.clusterMutex.Lock() 131 defer ch.clusterMutex.Unlock() 132 if ch.root == nil { 133 return 134 } 135 ch.root.delete() 136 ch.root = nil 137 ch.rootClusterName = "" 138 } 139 140 // This logically represents a cluster. This handles all the logic for starting 141 // and stopping a cluster watch, handling any updates, and constructing a list 142 // recursively for the ClusterHandler. 143 type clusterNode struct { 144 // A way to cancel the watch for the cluster. 145 cancelFunc func() 146 147 // A list of children, as the Node can be an aggregate Cluster. 148 children []*clusterNode 149 150 // A ClusterUpdate in order to build a list of cluster updates for CDS to 151 // send down to child XdsClusterResolverLoadBalancingPolicy. 152 clusterUpdate resource.ClusterUpdate 153 154 // This boolean determines whether this Node has received an update or not. 155 // This isn't the best practice, but this will protect a list of Cluster 156 // Updates from being constructed if a cluster in the tree has not received 157 // an update yet. 158 receivedUpdate bool 159 160 clusterHandler *clusterHandler 161 } 162 163 // CreateClusterNode creates a cluster node from a given clusterName. This will 164 // also start the watch for that cluster. 165 func createClusterNode(clusterName string, xdsClient client.XDSClient, topLevelHandler *clusterHandler) *clusterNode { 166 c := &clusterNode{ 167 clusterHandler: topLevelHandler, 168 } 169 // Communicate with the xds client here. 170 topLevelHandler.parent.logger.Infof("CDS watch started on %v", clusterName) 171 cancel := xdsClient.WatchCluster(clusterName, c.handleResp) 172 c.cancelFunc = func() { 173 topLevelHandler.parent.logger.Infof("CDS watch canceled on %v", clusterName) 174 cancel() 175 } 176 return c 177 } 178 179 // This function cancels the cluster watch on the cluster and all of it's 180 // children. 181 func (c *clusterNode) delete() { 182 c.cancelFunc() 183 for _, child := range c.children { 184 child.delete() 185 } 186 } 187 188 // Construct cluster update (potentially a list of ClusterUpdates) for a node. 189 func (c *clusterNode) constructClusterUpdate() ([]resource.ClusterUpdate, error) { 190 // If the cluster has not yet received an update, the cluster update is not 191 // yet ready. 192 if !c.receivedUpdate { 193 return nil, errNotReceivedUpdate 194 } 195 196 // Base case - LogicalDNS or EDS. Both of these cluster types will be tied 197 // to a single ClusterUpdate. 198 if c.clusterUpdate.ClusterType != resource.ClusterTypeAggregate { 199 return []resource.ClusterUpdate{c.clusterUpdate}, nil 200 } 201 202 // If an aggregate construct a list by recursively calling down to all of 203 // it's children. 204 var childrenUpdates []resource.ClusterUpdate 205 for _, child := range c.children { 206 childUpdateList, err := child.constructClusterUpdate() 207 if err != nil { 208 return nil, err 209 } 210 childrenUpdates = append(childrenUpdates, childUpdateList...) 211 } 212 return childrenUpdates, nil 213 } 214 215 // handleResp handles a xds response for a particular cluster. This function 216 // also handles any logic with regards to any child state that may have changed. 217 // At the end of the handleResp(), the clusterUpdate will be pinged in certain 218 // situations to try and construct an update to send back to CDS. 219 func (c *clusterNode) handleResp(clusterUpdate resource.ClusterUpdate, err error) { 220 c.clusterHandler.clusterMutex.Lock() 221 defer c.clusterHandler.clusterMutex.Unlock() 222 if err != nil { // Write this error for run() to pick up in CDS LB policy. 223 // For a ClusterUpdate, the only update CDS cares about is the most 224 // recent one, so opportunistically drain the update channel before 225 // sending the new update. 226 select { 227 case <-c.clusterHandler.updateChannel: 228 default: 229 } 230 c.clusterHandler.updateChannel <- clusterHandlerUpdate{err: err} 231 return 232 } 233 234 c.receivedUpdate = true 235 c.clusterUpdate = clusterUpdate 236 237 // If the cluster was a leaf node, if the cluster update received had change 238 // in the cluster update then the overall cluster update would change and 239 // there is a possibility for the overall update to build so ping cluster 240 // handler to return. Also, if there was any children from previously, 241 // delete the children, as the cluster type is no longer an aggregate 242 // cluster. 243 if clusterUpdate.ClusterType != resource.ClusterTypeAggregate { 244 for _, child := range c.children { 245 child.delete() 246 } 247 c.children = nil 248 // This is an update in the one leaf node, should try to send an update 249 // to the parent CDS balancer. 250 // 251 // Note that this update might be a duplicate from the previous one. 252 // Because the update contains not only the cluster name to watch, but 253 // also the extra fields (e.g. security config). There's no good way to 254 // compare all the fields. 255 c.clusterHandler.constructClusterUpdate() 256 return 257 } 258 259 // Aggregate cluster handling. 260 newChildren := make(map[string]bool) 261 for _, childName := range clusterUpdate.PrioritizedClusterNames { 262 newChildren[childName] = true 263 } 264 265 // These booleans help determine whether this callback will ping the overall 266 // clusterHandler to try and construct an update to send back to CDS. This 267 // will be determined by whether there would be a change in the overall 268 // clusterUpdate for the whole tree (ex. change in clusterUpdate for current 269 // cluster or a deleted child) and also if there's even a possibility for 270 // the update to build (ex. if a child is created and a watch is started, 271 // that child hasn't received an update yet due to the mutex lock on this 272 // callback). 273 var createdChild, deletedChild bool 274 275 // This map will represent the current children of the cluster. It will be 276 // first added to in order to represent the new children. It will then have 277 // any children deleted that are no longer present. Then, from the cluster 278 // update received, will be used to construct the new child list. 279 mapCurrentChildren := make(map[string]*clusterNode) 280 for _, child := range c.children { 281 mapCurrentChildren[child.clusterUpdate.ClusterName] = child 282 } 283 284 // Add and construct any new child nodes. 285 for child := range newChildren { 286 if _, inChildrenAlready := mapCurrentChildren[child]; !inChildrenAlready { 287 createdChild = true 288 mapCurrentChildren[child] = createClusterNode(child, c.clusterHandler.parent.xdsClient, c.clusterHandler) 289 } 290 } 291 292 // Delete any child nodes no longer in the aggregate cluster's children. 293 for child := range mapCurrentChildren { 294 if _, stillAChild := newChildren[child]; !stillAChild { 295 deletedChild = true 296 mapCurrentChildren[child].delete() 297 delete(mapCurrentChildren, child) 298 } 299 } 300 301 // The order of the children list matters, so use the clusterUpdate from 302 // xdsclient as the ordering, and use that logical ordering for the new 303 // children list. This will be a mixture of child nodes which are all 304 // already constructed in the mapCurrentChildrenMap. 305 var children = make([]*clusterNode, 0, len(clusterUpdate.PrioritizedClusterNames)) 306 307 for _, orderedChild := range clusterUpdate.PrioritizedClusterNames { 308 // The cluster's already have watches started for them in xds client, so 309 // you can use these pointers to construct the new children list, you 310 // just have to put them in the correct order using the original cluster 311 // update. 312 currentChild := mapCurrentChildren[orderedChild] 313 children = append(children, currentChild) 314 } 315 316 c.children = children 317 318 // If the cluster is an aggregate cluster, if this callback created any new 319 // child cluster nodes, then there's no possibility for a full cluster 320 // update to successfully build, as those created children will not have 321 // received an update yet. However, if there was simply a child deleted, 322 // then there is a possibility that it will have a full cluster update to 323 // build and also will have a changed overall cluster update from the 324 // deleted child. 325 if deletedChild && !createdChild { 326 c.clusterHandler.constructClusterUpdate() 327 } 328 }