diff --git a/command/client_servers.go b/command/client_servers.go new file mode 100644 index 000000000..d74cd408e --- /dev/null +++ b/command/client_servers.go @@ -0,0 +1,96 @@ +package command + +import ( + "fmt" + "strings" +) + +type ClientServersCommand struct { + Meta +} + +func (c *ClientServersCommand) Help() string { + helpText := ` +Usage: nomad client-servers [options] [
...] + + Query or modify the list of servers on a client node. Client + nodes do not participate in the gossip protocol, and register + with server nodes periodically over the network. This command + can be used to list or modify the set of servers Nomad speaks + to during this process. + +Examples + + $ nomad client-servers -update foo:4647 bar:4647 baz:4647 + +General Options: + + ` + generalOptionsUsage() + ` + +Client Server Options + + -update + Updates the client's server list using the provided + arguments. Multiple server addresses may be passed using + multiple arguments. IMPORTANT: When updating the servers + list, you must specify ALL of the server nodes you wish + to configure. The set is updated atomically. +` + return strings.TrimSpace(helpText) +} + +func (c *ClientServersCommand) Synopsis() string { + return "Query or modify the list of client servers" +} + +func (c *ClientServersCommand) Run(args []string) int { + var update bool + + flags := c.Meta.FlagSet("client-servers", FlagSetClient) + flags.Usage = func() { c.Ui.Output(c.Help()) } + flags.BoolVar(&update, "update", false, "") + + if err := flags.Parse(args); err != nil { + return 1 + } + + // Check the flags for misuse + args = flags.Args() + if len(args) > 0 && !update { + c.Ui.Error(c.Help()) + return 1 + } + if len(args) == 0 && update { + c.Ui.Error(c.Help()) + return 1 + } + + // Get the HTTP client + client, err := c.Meta.Client() + if err != nil { + c.Ui.Error(fmt.Sprintf("Error initializing client: %s", err)) + return 1 + } + + if update { + // Set the servers list + if err := client.Agent().SetServers(args); err != nil { + c.Ui.Error(fmt.Sprintf("Error updating server list: %s", err)) + return 1 + } + return 0 + } + + // Query the current server list + servers, err := client.Agent().Servers() + if err != nil { + c.Ui.Error(fmt.Sprintf("Error querying server list: %s", err)) + return 1 + } + + // Print the results + for _, server := range servers { + c.Ui.Output(server) + } + return 0 +} diff --git a/command/client_servers_test.go b/command/client_servers_test.go new file mode 100644 index 000000000..1e3a4e430 --- /dev/null +++ b/command/client_servers_test.go @@ -0,0 +1,73 @@ +package command + +import ( + "strings" + "testing" + + "github.com/hashicorp/nomad/testutil" + "github.com/mitchellh/cli" +) + +func TestClientServersCommand_Implements(t *testing.T) { + var _ cli.Command = &ClientServersCommand{} +} + +func TestClientServersCommand_Run(t *testing.T) { + srv, _, url := testServer(t, func(c *testutil.TestServerConfig) { + c.Client.Enabled = true + c.Server.BootstrapExpect = 0 + }) + defer srv.Stop() + + ui := new(cli.MockUi) + cmd := &ClientServersCommand{Meta: Meta{Ui: ui}} + + // Set the servers list + code := cmd.Run([]string{"-address=" + url, "-update", "foo", "bar"}) + if code != 0 { + t.Fatalf("expected exit 0, got: %d", code) + } + + // Query the servers list + code = cmd.Run([]string{"-address=" + url}) + if code != 0 { + t.Fatalf("expect exit 0, got: %d", code) + } + out := ui.OutputWriter.String() + if !strings.Contains(out, "foo") { + t.Fatalf("missing foo") + } + if !strings.Contains(out, "bar") { + t.Fatalf("missing bar") + } +} + +func TestClientServersCommand_Fails(t *testing.T) { + ui := new(cli.MockUi) + cmd := &ClientServersCommand{Meta: Meta{Ui: ui}} + + // Fails on misuse + if code := cmd.Run([]string{"some", "bad", "args"}); code != 1 { + t.Fatalf("expected exit code 1, got: %d", code) + } + if out := ui.ErrorWriter.String(); !strings.Contains(out, cmd.Help()) { + t.Fatalf("expected help output, got: %s", out) + } + ui.ErrorWriter.Reset() + + // Fails if updating with no servers + if code := cmd.Run([]string{"-update"}); code != 1 { + t.Fatalf("expected exit 1, got: %d", code) + } + if out := ui.ErrorWriter.String(); !strings.Contains(out, cmd.Help()) { + t.Fatalf("expected help output, got: %s", out) + } + + // Fails on connection failure + if code := cmd.Run([]string{"-address=nope"}); code != 1 { + t.Fatalf("expected exit code 1, got: %d", code) + } + if out := ui.ErrorWriter.String(); !strings.Contains(out, "Error querying server list") { + t.Fatalf("expected failed query error, got: %s", out) + } +} diff --git a/commands.go b/commands.go index d7c74d008..38622bfd3 100644 --- a/commands.go +++ b/commands.go @@ -40,6 +40,12 @@ func Commands(metaPtr *command.Meta) map[string]cli.CommandFactory { }, nil }, + "client-servers": func() (cli.Command, error) { + return &command.ClientServersCommand{ + Meta: meta, + }, nil + }, + "eval-monitor": func() (cli.Command, error) { return &command.EvalMonitorCommand{ Meta: meta,