github.com/Ilhicas/nomad@v1.0.4-0.20210304152020-e86851182bc3/command/node_drain.go (about) 1 package command 2 3 import ( 4 "context" 5 "fmt" 6 "strings" 7 "time" 8 9 "github.com/hashicorp/nomad/api" 10 "github.com/hashicorp/nomad/api/contexts" 11 "github.com/posener/complete" 12 ) 13 14 var ( 15 // defaultDrainDuration is the default drain duration if it is not specified 16 // explicitly 17 defaultDrainDuration = 1 * time.Hour 18 ) 19 20 type NodeDrainCommand struct { 21 Meta 22 } 23 24 func (c *NodeDrainCommand) Help() string { 25 helpText := ` 26 Usage: nomad node drain [options] <node> 27 28 Toggles node draining on a specified node. It is required that either 29 -enable or -disable is specified, but not both. The -self flag is useful to 30 drain the local node. 31 32 If ACLs are enabled, this option requires a token with the 'node:write' 33 capability. 34 35 General Options: 36 37 ` + generalOptionsUsage(usageOptsDefault|usageOptsNoNamespace) + ` 38 39 Node Drain Options: 40 41 -disable 42 Disable draining for the specified node. 43 44 -enable 45 Enable draining for the specified node. 46 47 -deadline <duration> 48 Set the deadline by which all allocations must be moved off the node. 49 Remaining allocations after the deadline are forced removed from the node. 50 If unspecified, a default deadline of one hour is applied. 51 52 -detach 53 Return immediately instead of entering monitor mode. 54 55 -monitor 56 Enter monitor mode directly without modifying the drain status. 57 58 -force 59 Force remove allocations off the node immediately. 60 61 -no-deadline 62 No deadline allows the allocations to drain off the node without being force 63 stopped after a certain deadline. 64 65 -ignore-system 66 Ignore system allows the drain to complete without stopping system job 67 allocations. By default system jobs are stopped last. 68 69 -keep-ineligible 70 Keep ineligible will maintain the node's scheduling ineligibility even if 71 the drain is being disabled. This is useful when an existing drain is being 72 cancelled but additional scheduling on the node is not desired. 73 74 -self 75 Set the drain status of the local node. 76 77 -yes 78 Automatic yes to prompts. 79 ` 80 return strings.TrimSpace(helpText) 81 } 82 83 func (c *NodeDrainCommand) Synopsis() string { 84 return "Toggle drain mode on a given node" 85 } 86 87 func (c *NodeDrainCommand) AutocompleteFlags() complete.Flags { 88 return mergeAutocompleteFlags(c.Meta.AutocompleteFlags(FlagSetClient), 89 complete.Flags{ 90 "-disable": complete.PredictNothing, 91 "-enable": complete.PredictNothing, 92 "-deadline": complete.PredictAnything, 93 "-detach": complete.PredictNothing, 94 "-force": complete.PredictNothing, 95 "-no-deadline": complete.PredictNothing, 96 "-ignore-system": complete.PredictNothing, 97 "-keep-ineligible": complete.PredictNothing, 98 "-self": complete.PredictNothing, 99 "-yes": complete.PredictNothing, 100 }) 101 } 102 103 func (c *NodeDrainCommand) AutocompleteArgs() complete.Predictor { 104 return complete.PredictFunc(func(a complete.Args) []string { 105 client, err := c.Meta.Client() 106 if err != nil { 107 return nil 108 } 109 110 resp, _, err := client.Search().PrefixSearch(a.Last, contexts.Nodes, nil) 111 if err != nil { 112 return []string{} 113 } 114 return resp.Matches[contexts.Nodes] 115 }) 116 } 117 118 func (c *NodeDrainCommand) Name() string { return "node-drain" } 119 120 func (c *NodeDrainCommand) Run(args []string) int { 121 var enable, disable, detach, force, 122 noDeadline, ignoreSystem, keepIneligible, 123 self, autoYes, monitor bool 124 var deadline string 125 126 flags := c.Meta.FlagSet(c.Name(), FlagSetClient) 127 flags.Usage = func() { c.Ui.Output(c.Help()) } 128 flags.BoolVar(&enable, "enable", false, "Enable drain mode") 129 flags.BoolVar(&disable, "disable", false, "Disable drain mode") 130 flags.StringVar(&deadline, "deadline", "", "Deadline after which allocations are force stopped") 131 flags.BoolVar(&detach, "detach", false, "") 132 flags.BoolVar(&force, "force", false, "Force immediate drain") 133 flags.BoolVar(&noDeadline, "no-deadline", false, "Drain node with no deadline") 134 flags.BoolVar(&ignoreSystem, "ignore-system", false, "Do not drain system job allocations from the node") 135 flags.BoolVar(&keepIneligible, "keep-ineligible", false, "Do not update the nodes scheduling eligibility") 136 flags.BoolVar(&self, "self", false, "") 137 flags.BoolVar(&autoYes, "yes", false, "Automatic yes to prompts.") 138 flags.BoolVar(&monitor, "monitor", false, "Monitor drain status.") 139 140 if err := flags.Parse(args); err != nil { 141 return 1 142 } 143 144 // Check that enable or disable is not set with monitor 145 if monitor && (enable || disable) { 146 c.Ui.Error("The -monitor flag cannot be used with the '-enable' or '-disable' flags") 147 c.Ui.Error(commandErrorText(c)) 148 return 1 149 } 150 151 // Check that we got either enable or disable, but not both. 152 if (enable && disable) || (!monitor && !enable && !disable) { 153 c.Ui.Error("Either the '-enable' or '-disable' flag must be set, unless using '-monitor'") 154 c.Ui.Error(commandErrorText(c)) 155 return 1 156 } 157 158 // Check that we got a node ID 159 args = flags.Args() 160 if l := len(args); self && l != 0 || !self && l != 1 { 161 c.Ui.Error("Node ID must be specified if -self isn't being used") 162 c.Ui.Error(commandErrorText(c)) 163 return 1 164 } 165 166 // Validate a compatible set of flags were set 167 if disable && (deadline != "" || force || noDeadline || ignoreSystem) { 168 c.Ui.Error("-disable can't be combined with flags configuring drain strategy") 169 c.Ui.Error(commandErrorText(c)) 170 return 1 171 } 172 if deadline != "" && (force || noDeadline) { 173 c.Ui.Error("-deadline can't be combined with -force or -no-deadline") 174 c.Ui.Error(commandErrorText(c)) 175 return 1 176 } 177 if force && noDeadline { 178 c.Ui.Error("-force and -no-deadline are mutually exclusive") 179 c.Ui.Error(commandErrorText(c)) 180 return 1 181 } 182 183 // Parse the duration 184 var d time.Duration 185 if force { 186 d = -1 * time.Second 187 } else if noDeadline { 188 d = 0 189 } else if deadline != "" { 190 dur, err := time.ParseDuration(deadline) 191 if err != nil { 192 c.Ui.Error(fmt.Sprintf("Failed to parse deadline %q: %v", deadline, err)) 193 return 1 194 } 195 if dur <= 0 { 196 c.Ui.Error("A positive drain duration must be given") 197 return 1 198 } 199 200 d = dur 201 } else { 202 d = defaultDrainDuration 203 } 204 205 // Get the HTTP client 206 client, err := c.Meta.Client() 207 if err != nil { 208 c.Ui.Error(fmt.Sprintf("Error initializing client: %s", err)) 209 return 1 210 } 211 212 // If -self flag is set then determine the current node. 213 var nodeID string 214 if !self { 215 nodeID = args[0] 216 } else { 217 var err error 218 if nodeID, err = getLocalNodeID(client); err != nil { 219 c.Ui.Error(err.Error()) 220 return 1 221 } 222 } 223 224 // Check if node exists 225 if len(nodeID) == 1 { 226 c.Ui.Error("Identifier must contain at least two characters.") 227 return 1 228 } 229 230 nodeID = sanitizeUUIDPrefix(nodeID) 231 nodes, _, err := client.Nodes().PrefixList(nodeID) 232 if err != nil { 233 c.Ui.Error(fmt.Sprintf("Error toggling drain mode: %s", err)) 234 return 1 235 } 236 // Return error if no nodes are found 237 if len(nodes) == 0 { 238 c.Ui.Error(fmt.Sprintf("No node(s) with prefix or id %q found", nodeID)) 239 return 1 240 } 241 if len(nodes) > 1 { 242 c.Ui.Error(fmt.Sprintf("Prefix matched multiple nodes\n\n%s", 243 formatNodeStubList(nodes, true))) 244 return 1 245 } 246 247 // Prefix lookup matched a single node 248 node, meta, err := client.Nodes().Info(nodes[0].ID, nil) 249 if err != nil { 250 c.Ui.Error(fmt.Sprintf("Error toggling drain mode: %s", err)) 251 return 1 252 } 253 254 // If monitoring the drain start the montior and return when done 255 if monitor { 256 if node.DrainStrategy == nil { 257 c.Ui.Warn("No drain strategy set") 258 return 0 259 } 260 c.Ui.Info(fmt.Sprintf("%s: Monitoring node %q: Ctrl-C to detach monitoring", formatTime(time.Now()), node.ID)) 261 c.monitorDrain(client, context.Background(), node, meta.LastIndex, ignoreSystem) 262 return 0 263 } 264 265 // Confirm drain if the node was a prefix match. 266 if nodeID != node.ID && !autoYes { 267 verb := "enable" 268 if disable { 269 verb = "disable" 270 } 271 question := fmt.Sprintf("Are you sure you want to %s drain mode for node %q? [y/N]", verb, node.ID) 272 answer, err := c.Ui.Ask(question) 273 if err != nil { 274 c.Ui.Error(fmt.Sprintf("Failed to parse answer: %v", err)) 275 return 1 276 } 277 278 if answer == "" || strings.ToLower(answer)[0] == 'n' { 279 // No case 280 c.Ui.Output("Canceling drain toggle") 281 return 0 282 } else if strings.ToLower(answer)[0] == 'y' && len(answer) > 1 { 283 // Non exact match yes 284 c.Ui.Output("For confirmation, an exact ‘y’ is required.") 285 return 0 286 } else if answer != "y" { 287 c.Ui.Output("No confirmation detected. For confirmation, an exact 'y' is required.") 288 return 1 289 } 290 } 291 292 var spec *api.DrainSpec 293 if enable { 294 spec = &api.DrainSpec{ 295 Deadline: d, 296 IgnoreSystemJobs: ignoreSystem, 297 } 298 } 299 300 // Toggle node draining 301 updateMeta, err := client.Nodes().UpdateDrain(node.ID, spec, !keepIneligible, nil) 302 if err != nil { 303 c.Ui.Error(fmt.Sprintf("Error updating drain specification: %s", err)) 304 return 1 305 } 306 307 if !enable || detach { 308 if enable { 309 c.Ui.Output(fmt.Sprintf("Node %q drain strategy set", node.ID)) 310 } else { 311 c.Ui.Output(fmt.Sprintf("Node %q drain strategy unset", node.ID)) 312 } 313 } 314 315 if enable && !detach { 316 now := time.Now() 317 c.Ui.Info(fmt.Sprintf("%s: Ctrl-C to stop monitoring: will not cancel the node drain", formatTime(now))) 318 c.Ui.Output(fmt.Sprintf("%s: Node %q drain strategy set", formatTime(now), node.ID)) 319 c.monitorDrain(client, context.Background(), node, updateMeta.LastIndex, ignoreSystem) 320 } 321 return 0 322 } 323 324 func (c *NodeDrainCommand) monitorDrain(client *api.Client, ctx context.Context, node *api.Node, index uint64, ignoreSystem bool) { 325 outCh := client.Nodes().MonitorDrain(ctx, node.ID, index, ignoreSystem) 326 for msg := range outCh { 327 switch msg.Level { 328 case api.MonitorMsgLevelInfo: 329 c.Ui.Info(fmt.Sprintf("%s: %s", formatTime(time.Now()), msg)) 330 case api.MonitorMsgLevelWarn: 331 c.Ui.Warn(fmt.Sprintf("%s: %s", formatTime(time.Now()), msg)) 332 case api.MonitorMsgLevelError: 333 c.Ui.Error(fmt.Sprintf("%s: %s", formatTime(time.Now()), msg)) 334 default: 335 c.Ui.Output(fmt.Sprintf("%s: %s", formatTime(time.Now()), msg)) 336 } 337 } 338 }