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  }