1package command
2
3import (
4	"fmt"
5	"strings"
6
7	"github.com/hashicorp/nomad/api/contexts"
8	"github.com/posener/complete"
9)
10
11type NodeEligibilityCommand struct {
12	Meta
13}
14
15func (c *NodeEligibilityCommand) Help() string {
16	helpText := `
17Usage: nomad node eligibility [options] <node>
18
19  Toggles the nodes scheduling eligibility. When a node is marked as ineligible,
20  no new allocations will be placed on it but existing allocations will remain.
21  To remove existing allocations, use the node drain command.
22
23  It is required that either -enable or -disable is specified, but not both.
24  The -self flag is useful to set the scheduling eligibility of the local node.
25
26General Options:
27
28  ` + generalOptionsUsage() + `
29
30Node Eligibility Options:
31
32  -disable
33    Mark the specified node as ineligible for new allocations.
34
35  -enable
36    Mark the specified node as eligible for new allocations.
37
38  -self
39    Set the eligibility of the local node.
40`
41	return strings.TrimSpace(helpText)
42}
43
44func (c *NodeEligibilityCommand) Synopsis() string {
45	return "Toggle scheduling eligibility for a given node"
46}
47
48func (c *NodeEligibilityCommand) AutocompleteFlags() complete.Flags {
49	return mergeAutocompleteFlags(c.Meta.AutocompleteFlags(FlagSetClient),
50		complete.Flags{
51			"-disable": complete.PredictNothing,
52			"-enable":  complete.PredictNothing,
53			"-self":    complete.PredictNothing,
54		})
55}
56
57func (c *NodeEligibilityCommand) AutocompleteArgs() complete.Predictor {
58	return complete.PredictFunc(func(a complete.Args) []string {
59		client, err := c.Meta.Client()
60		if err != nil {
61			return nil
62		}
63
64		resp, _, err := client.Search().PrefixSearch(a.Last, contexts.Nodes, nil)
65		if err != nil {
66			return []string{}
67		}
68		return resp.Matches[contexts.Nodes]
69	})
70}
71
72func (c *NodeEligibilityCommand) Name() string { return "node-eligibility" }
73
74func (c *NodeEligibilityCommand) Run(args []string) int {
75	var enable, disable, self bool
76
77	flags := c.Meta.FlagSet(c.Name(), FlagSetClient)
78	flags.Usage = func() { c.Ui.Output(c.Help()) }
79	flags.BoolVar(&enable, "enable", false, "Mark node as eligibile for scheduling")
80	flags.BoolVar(&disable, "disable", false, "Mark node as ineligibile for scheduling")
81	flags.BoolVar(&self, "self", false, "")
82
83	if err := flags.Parse(args); err != nil {
84		return 1
85	}
86
87	// Check that we got either enable or disable, but not both.
88	if (enable && disable) || (!enable && !disable) {
89		c.Ui.Error("Ethier the '-enable' or '-disable' flag must be set")
90		c.Ui.Error(commandErrorText(c))
91		return 1
92	}
93
94	// Check that we got a node ID
95	args = flags.Args()
96	if l := len(args); self && l != 0 || !self && l != 1 {
97		c.Ui.Error("Node ID must be specified if -self isn't being used")
98		c.Ui.Error(commandErrorText(c))
99		return 1
100	}
101
102	// Get the HTTP client
103	client, err := c.Meta.Client()
104	if err != nil {
105		c.Ui.Error(fmt.Sprintf("Error initializing client: %s", err))
106		return 1
107	}
108
109	// If -self flag is set then determine the current node.
110	var nodeID string
111	if !self {
112		nodeID = args[0]
113	} else {
114		var err error
115		if nodeID, err = getLocalNodeID(client); err != nil {
116			c.Ui.Error(err.Error())
117			return 1
118		}
119	}
120
121	// Check if node exists
122	if len(nodeID) == 1 {
123		c.Ui.Error(fmt.Sprintf("Identifier must contain at least two characters."))
124		return 1
125	}
126
127	nodeID = sanitizeUUIDPrefix(nodeID)
128	nodes, _, err := client.Nodes().PrefixList(nodeID)
129	if err != nil {
130		c.Ui.Error(fmt.Sprintf("Error updating scheduling eligibility: %s", err))
131		return 1
132	}
133	// Return error if no nodes are found
134	if len(nodes) == 0 {
135		c.Ui.Error(fmt.Sprintf("No node(s) with prefix or id %q found", nodeID))
136		return 1
137	}
138	if len(nodes) > 1 {
139		c.Ui.Error(fmt.Sprintf("Prefix matched multiple nodes\n\n%s",
140			formatNodeStubList(nodes, true)))
141		return 1
142	}
143
144	// Prefix lookup matched a single node
145	node, _, err := client.Nodes().Info(nodes[0].ID, nil)
146	if err != nil {
147		c.Ui.Error(fmt.Sprintf("Error updating scheduling eligibility: %s", err))
148		return 1
149	}
150
151	// Toggle node eligibility
152	if _, err := client.Nodes().ToggleEligibility(node.ID, enable, nil); err != nil {
153		c.Ui.Error(fmt.Sprintf("Error updating scheduling eligibility: %s", err))
154		return 1
155	}
156
157	if enable {
158		c.Ui.Output(fmt.Sprintf("Node %q scheduling eligibility set: eligible for scheduling", node.ID))
159	} else {
160		c.Ui.Output(fmt.Sprintf("Node %q scheduling eligibility set: ineligible for scheduling", node.ID))
161	}
162	return 0
163}
164