open-nomad/command/server_members.go

209 lines
4.8 KiB
Go
Raw Normal View History

2015-09-11 19:58:16 +00:00
package command
import (
"fmt"
"net"
"sort"
2015-09-11 19:58:16 +00:00
"strings"
multierror "github.com/hashicorp/go-multierror"
2015-09-11 19:58:16 +00:00
"github.com/hashicorp/nomad/api"
2017-08-18 15:09:52 +00:00
"github.com/posener/complete"
2015-09-11 19:58:16 +00:00
"github.com/ryanuber/columnize"
)
type ServerMembersCommand struct {
Meta
2015-09-11 19:58:16 +00:00
}
func (c *ServerMembersCommand) Help() string {
2015-09-11 19:58:16 +00:00
helpText := `
2018-03-21 00:37:28 +00:00
Usage: nomad server members [options]
2015-09-11 19:58:16 +00:00
Display a list of the known servers and their status. Only Nomad servers are
able to service this command.
2015-09-11 19:58:16 +00:00
If ACLs are enabled, this option requires a token with the 'node:read'
capability.
General Options:
` + generalOptionsUsage(usageOptsDefault|usageOptsNoNamespace) + `
Server Members Options:
2015-09-11 19:58:16 +00:00
-detailed
Show detailed information about each member. This dumps
a raw set of tags which shows more information than the
default output format.
`
return strings.TrimSpace(helpText)
}
2017-08-18 15:09:52 +00:00
func (c *ServerMembersCommand) AutocompleteFlags() complete.Flags {
2017-08-23 21:56:21 +00:00
return mergeAutocompleteFlags(c.Meta.AutocompleteFlags(FlagSetClient),
complete.Flags{
"-detailed": complete.PredictNothing,
})
2017-08-18 15:09:52 +00:00
}
func (c *ServerMembersCommand) AutocompleteArgs() complete.Predictor {
return complete.PredictNothing
}
func (c *ServerMembersCommand) Synopsis() string {
return "Display a list of known servers and their status"
2015-09-11 19:58:16 +00:00
}
func (c *ServerMembersCommand) Name() string { return "server members" }
func (c *ServerMembersCommand) Run(args []string) int {
2015-09-11 19:58:16 +00:00
var detailed bool
flags := c.Meta.FlagSet(c.Name(), FlagSetClient)
2015-09-11 19:58:16 +00:00
flags.Usage = func() { c.Ui.Output(c.Help()) }
flags.BoolVar(&detailed, "detailed", false, "Show detailed output")
if err := flags.Parse(args); err != nil {
return 1
}
// Check for extra arguments
args = flags.Args()
if len(args) != 0 {
c.Ui.Error("This command takes no arguments")
c.Ui.Error(commandErrorText(c))
2015-09-11 19:58:16 +00:00
return 1
}
// Get the HTTP client
client, err := c.Meta.Client()
2015-09-11 19:58:16 +00:00
if err != nil {
c.Ui.Error(fmt.Sprintf("Error initializing client: %s", err))
2015-09-11 19:58:16 +00:00
return 1
}
// Query the members
srvMembers, err := client.Agent().Members()
2015-09-11 19:58:16 +00:00
if err != nil {
c.Ui.Error(fmt.Sprintf("Error querying servers: %s", err))
2015-09-11 19:58:16 +00:00
return 1
}
if srvMembers == nil {
c.Ui.Error("Agent doesn't know about server members")
return 0
}
// Sort the members
sort.Sort(api.AgentMembersNameSort(srvMembers.Members))
2016-03-17 23:04:09 +00:00
// Determine the leaders per region.
leaders, leaderErr := regionLeaders(client, srvMembers.Members)
2016-03-17 23:04:09 +00:00
2015-09-11 19:58:16 +00:00
// Format the list
var out []string
if detailed {
out = detailedOutput(srvMembers.Members)
2015-09-11 19:58:16 +00:00
} else {
out = standardOutput(srvMembers.Members, leaders)
2015-09-11 19:58:16 +00:00
}
// Dump the list
c.Ui.Output(columnize.SimpleFormat(out))
// If there were leader errors display a warning
if leaderErr != nil {
c.Ui.Output("")
c.Ui.Warn(fmt.Sprintf("Error determining leaders: %s", leaderErr))
return 1
}
2015-09-11 19:58:16 +00:00
return 0
}
2016-03-17 23:04:09 +00:00
func standardOutput(mem []*api.AgentMember, leaders map[string]string) []string {
2015-09-11 19:58:16 +00:00
// Format the members list
members := make([]string, len(mem)+1)
2016-03-17 23:04:09 +00:00
members[0] = "Name|Address|Port|Status|Leader|Protocol|Build|Datacenter|Region"
2015-09-11 19:58:16 +00:00
for i, member := range mem {
2016-03-17 23:04:09 +00:00
reg := member.Tags["region"]
regLeader, ok := leaders[reg]
isLeader := false
if ok {
if regLeader == net.JoinHostPort(member.Addr, member.Tags["port"]) {
2016-03-17 23:04:09 +00:00
isLeader = true
}
}
members[i+1] = fmt.Sprintf("%s|%s|%d|%s|%t|%d|%s|%s|%s",
2015-09-11 19:58:16 +00:00
member.Name,
member.Addr,
member.Port,
member.Status,
2016-03-17 23:04:09 +00:00
isLeader,
2015-09-11 19:58:16 +00:00
member.ProtocolCur,
member.Tags["build"],
member.Tags["dc"],
2015-09-13 18:39:49 +00:00
member.Tags["region"])
2015-09-11 19:58:16 +00:00
}
return members
}
func detailedOutput(mem []*api.AgentMember) []string {
// Format the members list
members := make([]string, len(mem)+1)
members[0] = "Name|Address|Port|Tags"
2015-09-11 19:58:16 +00:00
for i, member := range mem {
// Format the tags
tagPairs := make([]string, 0, len(member.Tags))
for k, v := range member.Tags {
tagPairs = append(tagPairs, fmt.Sprintf("%s=%s", k, v))
}
tags := strings.Join(tagPairs, ",")
2015-09-13 18:39:49 +00:00
members[i+1] = fmt.Sprintf("%s|%s|%d|%s",
2015-09-11 19:58:16 +00:00
member.Name,
member.Addr,
member.Port,
2015-09-13 18:39:49 +00:00
tags)
2015-09-11 19:58:16 +00:00
}
return members
}
2016-03-17 23:04:09 +00:00
// regionLeaders returns a map of regions to the IP of the member that is the
// leader.
func regionLeaders(client *api.Client, mem []*api.AgentMember) (map[string]string, error) {
// Determine the unique regions.
leaders := make(map[string]string)
regions := make(map[string]struct{})
for _, m := range mem {
// Ignore left members
// This prevents querying for leader status on regions where all members have left
if m.Status == "left" {
continue
}
2016-03-17 23:04:09 +00:00
regions[m.Tags["region"]] = struct{}{}
}
if len(regions) == 0 {
return leaders, nil
}
var mErr multierror.Error
2016-03-17 23:04:09 +00:00
status := client.Status()
for reg := range regions {
l, err := status.RegionLeader(reg)
if err != nil {
_ = multierror.Append(&mErr, fmt.Errorf("Region %q: %v", reg, err))
continue
2016-03-17 23:04:09 +00:00
}
2016-04-22 22:59:56 +00:00
2016-03-17 23:04:09 +00:00
leaders[reg] = l
}
return leaders, mErr.ErrorOrNil()
2016-03-17 23:04:09 +00:00
}