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 }