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  }