github.com/Ilhicas/nomad@v1.0.4-0.20210304152020-e86851182bc3/command/server_members.go (about)

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