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