github.com/blixtra/nomad@v0.7.2-0.20171221000451-da9a1d7bb050/command/server_members.go (about)

     1  package command
     2  
     3  import (
     4  	"fmt"
     5  	"net"
     6  	"sort"
     7  	"strings"
     8  
     9  	"github.com/hashicorp/nomad/api"
    10  	"github.com/posener/complete"
    11  	"github.com/ryanuber/columnize"
    12  )
    13  
    14  type ServerMembersCommand struct {
    15  	Meta
    16  }
    17  
    18  func (c *ServerMembersCommand) Help() string {
    19  	helpText := `
    20  Usage: nomad server-members [options]
    21  
    22    Display a list of the known servers and their status. Only Nomad servers are
    23    able to service this command.
    24  
    25  General Options:
    26  
    27    ` + generalOptionsUsage() + `
    28  
    29  Server Members Options:
    30  
    31    -detailed
    32      Show detailed information about each member. This dumps
    33      a raw set of tags which shows more information than the
    34      default output format.
    35  `
    36  	return strings.TrimSpace(helpText)
    37  }
    38  
    39  func (c *ServerMembersCommand) AutocompleteFlags() complete.Flags {
    40  	return mergeAutocompleteFlags(c.Meta.AutocompleteFlags(FlagSetClient),
    41  		complete.Flags{
    42  			"-detailed": complete.PredictNothing,
    43  		})
    44  }
    45  
    46  func (c *ServerMembersCommand) AutocompleteArgs() complete.Predictor {
    47  	return complete.PredictNothing
    48  }
    49  
    50  func (c *ServerMembersCommand) Synopsis() string {
    51  	return "Display a list of known servers and their status"
    52  }
    53  
    54  func (c *ServerMembersCommand) Run(args []string) int {
    55  	var detailed bool
    56  
    57  	flags := c.Meta.FlagSet("server-members", FlagSetClient)
    58  	flags.Usage = func() { c.Ui.Output(c.Help()) }
    59  	flags.BoolVar(&detailed, "detailed", false, "Show detailed output")
    60  
    61  	if err := flags.Parse(args); err != nil {
    62  		return 1
    63  	}
    64  
    65  	// Check for extra arguments
    66  	args = flags.Args()
    67  	if len(args) != 0 {
    68  		c.Ui.Error(c.Help())
    69  		return 1
    70  	}
    71  
    72  	// Get the HTTP client
    73  	client, err := c.Meta.Client()
    74  	if err != nil {
    75  		c.Ui.Error(fmt.Sprintf("Error initializing client: %s", err))
    76  		return 1
    77  	}
    78  
    79  	// Query the members
    80  	srvMembers, err := client.Agent().Members()
    81  	if err != nil {
    82  		c.Ui.Error(fmt.Sprintf("Error querying servers: %s", err))
    83  		return 1
    84  	}
    85  
    86  	if srvMembers == nil {
    87  		c.Ui.Error("Agent doesn't know about server members")
    88  		return 0
    89  	}
    90  
    91  	// Sort the members
    92  	sort.Sort(api.AgentMembersNameSort(srvMembers.Members))
    93  
    94  	// Determine the leaders per region.
    95  	leaders, err := regionLeaders(client, srvMembers.Members)
    96  	if err != nil {
    97  		c.Ui.Error(fmt.Sprintf("Error determining leaders: %s", err))
    98  		return 1
    99  	}
   100  
   101  	// Format the list
   102  	var out []string
   103  	if detailed {
   104  		out = detailedOutput(srvMembers.Members)
   105  	} else {
   106  		out = standardOutput(srvMembers.Members, leaders)
   107  	}
   108  
   109  	// Dump the list
   110  	c.Ui.Output(columnize.SimpleFormat(out))
   111  	return 0
   112  }
   113  
   114  func standardOutput(mem []*api.AgentMember, leaders map[string]string) []string {
   115  	// Format the members list
   116  	members := make([]string, len(mem)+1)
   117  	members[0] = "Name|Address|Port|Status|Leader|Protocol|Build|Datacenter|Region"
   118  	for i, member := range mem {
   119  		reg := member.Tags["region"]
   120  		regLeader, ok := leaders[reg]
   121  		isLeader := false
   122  		if ok {
   123  			if regLeader == net.JoinHostPort(member.Addr, member.Tags["port"]) {
   124  
   125  				isLeader = true
   126  			}
   127  		}
   128  
   129  		members[i+1] = fmt.Sprintf("%s|%s|%d|%s|%t|%d|%s|%s|%s",
   130  			member.Name,
   131  			member.Addr,
   132  			member.Port,
   133  			member.Status,
   134  			isLeader,
   135  			member.ProtocolCur,
   136  			member.Tags["build"],
   137  			member.Tags["dc"],
   138  			member.Tags["region"])
   139  	}
   140  	return members
   141  }
   142  
   143  func detailedOutput(mem []*api.AgentMember) []string {
   144  	// Format the members list
   145  	members := make([]string, len(mem)+1)
   146  	members[0] = "Name|Address|Port|Tags"
   147  	for i, member := range mem {
   148  		// Format the tags
   149  		tagPairs := make([]string, 0, len(member.Tags))
   150  		for k, v := range member.Tags {
   151  			tagPairs = append(tagPairs, fmt.Sprintf("%s=%s", k, v))
   152  		}
   153  		tags := strings.Join(tagPairs, ",")
   154  
   155  		members[i+1] = fmt.Sprintf("%s|%s|%d|%s",
   156  			member.Name,
   157  			member.Addr,
   158  			member.Port,
   159  			tags)
   160  	}
   161  	return members
   162  }
   163  
   164  // regionLeaders returns a map of regions to the IP of the member that is the
   165  // leader.
   166  func regionLeaders(client *api.Client, mem []*api.AgentMember) (map[string]string, error) {
   167  	// Determine the unique regions.
   168  	leaders := make(map[string]string)
   169  	regions := make(map[string]struct{})
   170  	for _, m := range mem {
   171  		regions[m.Tags["region"]] = struct{}{}
   172  	}
   173  
   174  	if len(regions) == 0 {
   175  		return leaders, nil
   176  	}
   177  
   178  	status := client.Status()
   179  	for reg := range regions {
   180  		l, err := status.RegionLeader(reg)
   181  		if err != nil {
   182  			// This error means that region has no leader.
   183  			if strings.Contains(err.Error(), "No cluster leader") {
   184  				continue
   185  			}
   186  			return nil, err
   187  		}
   188  
   189  		leaders[reg] = l
   190  	}
   191  
   192  	return leaders, nil
   193  }