github.com/hernad/nomad@v1.6.112/nomad/drainer/watch_nodes.go (about)

     1  // Copyright (c) HashiCorp, Inc.
     2  // SPDX-License-Identifier: MPL-2.0
     3  
     4  package drainer
     5  
     6  import (
     7  	"context"
     8  
     9  	log "github.com/hashicorp/go-hclog"
    10  	memdb "github.com/hashicorp/go-memdb"
    11  	"github.com/hernad/nomad/helper"
    12  
    13  	"github.com/hernad/nomad/nomad/state"
    14  	"github.com/hernad/nomad/nomad/structs"
    15  	"golang.org/x/time/rate"
    16  )
    17  
    18  // DrainingNodeWatcher is the interface for watching for draining nodes.
    19  type DrainingNodeWatcher interface{}
    20  
    21  // TrackedNodes returns the set of tracked nodes
    22  func (n *NodeDrainer) TrackedNodes() map[string]*structs.Node {
    23  	n.l.RLock()
    24  	defer n.l.RUnlock()
    25  
    26  	t := make(map[string]*structs.Node, len(n.nodes))
    27  	for n, d := range n.nodes {
    28  		t[n] = d.GetNode()
    29  	}
    30  
    31  	return t
    32  }
    33  
    34  // Remove removes the given node from being tracked
    35  func (n *NodeDrainer) Remove(nodeID string) {
    36  	n.l.Lock()
    37  	defer n.l.Unlock()
    38  
    39  	// Remove it from being tracked and remove it from the dealiner
    40  	delete(n.nodes, nodeID)
    41  	n.deadlineNotifier.Remove(nodeID)
    42  }
    43  
    44  // Update updates the node, either updating the tracked version or starting to
    45  // track the node.
    46  func (n *NodeDrainer) Update(node *structs.Node) {
    47  	n.l.Lock()
    48  	defer n.l.Unlock()
    49  
    50  	if node == nil {
    51  		return
    52  	}
    53  
    54  	draining, ok := n.nodes[node.ID]
    55  	if !ok {
    56  		draining = NewDrainingNode(node, n.state)
    57  		n.nodes[node.ID] = draining
    58  	} else {
    59  		// Update it
    60  		draining.Update(node)
    61  	}
    62  
    63  	if inf, deadline := node.DrainStrategy.DeadlineTime(); !inf {
    64  		n.deadlineNotifier.Watch(node.ID, deadline)
    65  	} else {
    66  		// There is an infinite deadline so it shouldn't be tracked for
    67  		// deadlining
    68  		n.deadlineNotifier.Remove(node.ID)
    69  	}
    70  
    71  	// Register interest in the draining jobs.
    72  	jobs, err := draining.DrainingJobs()
    73  	if err != nil {
    74  		n.logger.Error("error retrieving draining jobs on node", "node_id", node.ID, "error", err)
    75  		return
    76  	}
    77  	n.logger.Trace("node has draining jobs on it", "node_id", node.ID, "num_jobs", len(jobs))
    78  	n.jobWatcher.RegisterJobs(jobs)
    79  
    80  	// Check if the node is done such that if an operator drains a node with
    81  	// nothing on it we unset drain
    82  	done, err := draining.IsDone()
    83  	if err != nil {
    84  		n.logger.Error("failed to check if node is done draining", "node_id", node.ID, "error", err)
    85  		return
    86  	}
    87  
    88  	if done {
    89  		// Node is done draining. Stop remaining system allocs before marking
    90  		// node as complete.
    91  		remaining, err := draining.RemainingAllocs()
    92  		if err != nil {
    93  			n.logger.Error("error getting remaining allocs on drained node", "node_id", node.ID, "error", err)
    94  		} else if len(remaining) > 0 {
    95  			future := structs.NewBatchFuture()
    96  			n.drainAllocs(future, remaining)
    97  			if err := future.Wait(); err != nil {
    98  				n.logger.Error("failed to drain remaining allocs from done node", "num_allocs", len(remaining), "node_id", node.ID, "error", err)
    99  			}
   100  		}
   101  
   102  		// Create the node event
   103  		event := structs.NewNodeEvent().
   104  			SetSubsystem(structs.NodeEventSubsystemDrain).
   105  			SetMessage(NodeDrainEventComplete)
   106  
   107  		index, err := n.raft.NodesDrainComplete([]string{node.ID}, event)
   108  		if err != nil {
   109  			n.logger.Error("failed to unset drain for node", "node_id", node.ID, "error", err)
   110  		} else {
   111  			n.logger.Info("node completed draining at index", "node_id", node.ID, "index", index)
   112  		}
   113  	}
   114  }
   115  
   116  // nodeDrainWatcher is used to watch nodes that are entering, leaving or
   117  // changing their drain strategy.
   118  type nodeDrainWatcher struct {
   119  	ctx    context.Context
   120  	logger log.Logger
   121  
   122  	// state is the state that is watched for state changes.
   123  	state *state.StateStore
   124  
   125  	// limiter is used to limit the rate of blocking queries
   126  	limiter *rate.Limiter
   127  
   128  	// tracker is the object that is tracking the nodes and provides us with the
   129  	// needed callbacks
   130  	tracker NodeTracker
   131  }
   132  
   133  // NewNodeDrainWatcher returns a new node drain watcher.
   134  func NewNodeDrainWatcher(ctx context.Context, limiter *rate.Limiter, state *state.StateStore, logger log.Logger, tracker NodeTracker) *nodeDrainWatcher {
   135  	w := &nodeDrainWatcher{
   136  		ctx:     ctx,
   137  		limiter: limiter,
   138  		logger:  logger.Named("node_watcher"),
   139  		tracker: tracker,
   140  		state:   state,
   141  	}
   142  
   143  	go w.watch()
   144  	return w
   145  }
   146  
   147  // watch is the long lived watching routine that detects node changes.
   148  func (w *nodeDrainWatcher) watch() {
   149  	timer, stop := helper.NewSafeTimer(stateReadErrorDelay)
   150  	defer stop()
   151  
   152  	nindex := uint64(1)
   153  
   154  	for {
   155  		timer.Reset(stateReadErrorDelay)
   156  		nodes, index, err := w.getNodes(nindex)
   157  		if err != nil {
   158  			if err == context.Canceled {
   159  				return
   160  			}
   161  
   162  			w.logger.Error("error watching node updates at index", "index", nindex, "error", err)
   163  			select {
   164  			case <-w.ctx.Done():
   165  				return
   166  			case <-timer.C:
   167  				continue
   168  			}
   169  		}
   170  
   171  		// update index for next run
   172  		nindex = index
   173  
   174  		tracked := w.tracker.TrackedNodes()
   175  		for nodeID, node := range nodes {
   176  			newDraining := node.DrainStrategy != nil
   177  			currentNode, tracked := tracked[nodeID]
   178  
   179  			switch {
   180  
   181  			case tracked && !newDraining:
   182  				// If the node is tracked but not draining, untrack
   183  				w.tracker.Remove(nodeID)
   184  
   185  			case !tracked && newDraining:
   186  				// If the node is not being tracked but is draining, track
   187  				w.tracker.Update(node)
   188  
   189  			case tracked && newDraining && !currentNode.DrainStrategy.Equal(node.DrainStrategy):
   190  				// If the node is being tracked but has changed, update
   191  				w.tracker.Update(node)
   192  
   193  			default:
   194  				// note that down/disconnected nodes are handled the same as any
   195  				// other node here, because we don't want to stop draining a
   196  				// node that might heartbeat again. The job watcher will let us
   197  				// know if we can stop watching the node when all the allocs are
   198  				// evicted
   199  			}
   200  		}
   201  
   202  		for nodeID := range tracked {
   203  			if _, ok := nodes[nodeID]; !ok {
   204  				w.tracker.Remove(nodeID)
   205  			}
   206  		}
   207  	}
   208  }
   209  
   210  // getNodes returns all nodes blocking until the nodes are after the given index.
   211  func (w *nodeDrainWatcher) getNodes(minIndex uint64) (map[string]*structs.Node, uint64, error) {
   212  	if err := w.limiter.Wait(w.ctx); err != nil {
   213  		return nil, 0, err
   214  	}
   215  
   216  	resp, index, err := w.state.BlockingQuery(w.getNodesImpl, minIndex, w.ctx)
   217  	if err != nil {
   218  		return nil, 0, err
   219  	}
   220  
   221  	return resp.(map[string]*structs.Node), index, nil
   222  }
   223  
   224  // getNodesImpl is used to get nodes from the state store, returning the set of
   225  // nodes and the current node table index.
   226  func (w *nodeDrainWatcher) getNodesImpl(ws memdb.WatchSet, state *state.StateStore) (interface{}, uint64, error) {
   227  	iter, err := state.Nodes(ws)
   228  	if err != nil {
   229  		return nil, 0, err
   230  	}
   231  
   232  	index, err := state.Index("nodes")
   233  	if err != nil {
   234  		return nil, 0, err
   235  	}
   236  
   237  	resp := make(map[string]*structs.Node, 64)
   238  	for {
   239  		raw := iter.Next()
   240  		if raw == nil {
   241  			break
   242  		}
   243  
   244  		node := raw.(*structs.Node)
   245  		resp[node.ID] = node
   246  	}
   247  
   248  	return resp, index, nil
   249  }