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  }