github.com/anth0d/nomad@v0.0.0-20221214183521-ae3a0a2cad06/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    -verbose
    36      Show detailed information about each member. This dumps a raw set of tags
    37      which shows more information than the default output format.
    38  `
    39  	return strings.TrimSpace(helpText)
    40  }
    41  
    42  func (c *ServerMembersCommand) AutocompleteFlags() complete.Flags {
    43  	return mergeAutocompleteFlags(c.Meta.AutocompleteFlags(FlagSetClient),
    44  		complete.Flags{
    45  			"-detailed": complete.PredictNothing,
    46  		})
    47  }
    48  
    49  func (c *ServerMembersCommand) AutocompleteArgs() complete.Predictor {
    50  	return complete.PredictNothing
    51  }
    52  
    53  func (c *ServerMembersCommand) Synopsis() string {
    54  	return "Display a list of known servers and their status"
    55  }
    56  
    57  func (c *ServerMembersCommand) Name() string { return "server members" }
    58  
    59  func (c *ServerMembersCommand) Run(args []string) int {
    60  	var detailed, verbose bool
    61  
    62  	flags := c.Meta.FlagSet(c.Name(), FlagSetClient)
    63  	flags.Usage = func() { c.Ui.Output(c.Help()) }
    64  	flags.BoolVar(&detailed, "detailed", false, "Show detailed output")
    65  	flags.BoolVar(&verbose, "verbose", 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  	// Keep support for previous flag name
    80  	if detailed {
    81  		verbose = true
    82  	}
    83  
    84  	// Get the HTTP client
    85  	client, err := c.Meta.Client()
    86  	if err != nil {
    87  		c.Ui.Error(fmt.Sprintf("Error initializing client: %s", err))
    88  		return 1
    89  	}
    90  
    91  	// Query the members
    92  	srvMembers, err := client.Agent().Members()
    93  	if err != nil {
    94  		c.Ui.Error(fmt.Sprintf("Error querying servers: %s", err))
    95  		return 1
    96  	}
    97  
    98  	if srvMembers == nil {
    99  		c.Ui.Error("Agent doesn't know about server members")
   100  		return 0
   101  	}
   102  
   103  	// Sort the members
   104  	sort.Sort(api.AgentMembersNameSort(srvMembers.Members))
   105  
   106  	// Determine the leaders per region.
   107  	leaders, leaderErr := regionLeaders(client, srvMembers.Members)
   108  
   109  	// Format the list
   110  	var out []string
   111  	if verbose {
   112  		out = verboseOutput(srvMembers.Members, leaders)
   113  	} else {
   114  		out = standardOutput(srvMembers.Members, leaders)
   115  	}
   116  
   117  	// Dump the list
   118  	c.Ui.Output(columnize.SimpleFormat(out))
   119  
   120  	// If there were leader errors display a warning
   121  	if leaderErr != nil {
   122  		c.Ui.Output("")
   123  		c.Ui.Warn(fmt.Sprintf("Error determining leaders: %s", leaderErr))
   124  		return 1
   125  	}
   126  
   127  	return 0
   128  }
   129  
   130  func standardOutput(mem []*api.AgentMember, leaders map[string]string) []string {
   131  	// Format the members list
   132  	members := make([]string, len(mem)+1)
   133  	members[0] = "Name|Address|Port|Status|Leader|Raft Version|Build|Datacenter|Region"
   134  	for i, member := range mem {
   135  		members[i+1] = fmt.Sprintf("%s|%s|%d|%s|%t|%s|%s|%s|%s",
   136  			member.Name,
   137  			member.Addr,
   138  			member.Port,
   139  			member.Status,
   140  			isLeader(member, leaders),
   141  			member.Tags["raft_vsn"],
   142  			member.Tags["build"],
   143  			member.Tags["dc"],
   144  			member.Tags["region"])
   145  	}
   146  	return members
   147  }
   148  
   149  func verboseOutput(mem []*api.AgentMember, leaders map[string]string) []string {
   150  	// Format the members list
   151  	members := make([]string, len(mem)+1)
   152  	members[0] = "Name|Address|Port|Status|Leader|Protocol|Raft Version|Build|Datacenter|Region|Tags"
   153  	for i, member := range mem {
   154  		// Format the tags
   155  		tagPairs := make([]string, 0, len(member.Tags))
   156  		for k, v := range member.Tags {
   157  			tagPairs = append(tagPairs, fmt.Sprintf("%s=%s", k, v))
   158  		}
   159  		tags := strings.Join(tagPairs, ",")
   160  
   161  		members[i+1] = fmt.Sprintf("%s|%s|%d|%s|%t|%d|%s|%s|%s|%s|%s",
   162  			member.Name,
   163  			member.Addr,
   164  			member.Port,
   165  			member.Status,
   166  			isLeader(member, leaders),
   167  			member.ProtocolCur,
   168  			member.Tags["raft_vsn"],
   169  			member.Tags["build"],
   170  			member.Tags["dc"],
   171  			member.Tags["region"],
   172  			tags,
   173  		)
   174  	}
   175  	return members
   176  }
   177  
   178  // regionLeaders returns a map of regions to the IP of the member that is the
   179  // leader.
   180  func regionLeaders(client *api.Client, mem []*api.AgentMember) (map[string]string, error) {
   181  	// Determine the unique regions.
   182  	leaders := make(map[string]string)
   183  	regions := make(map[string]struct{})
   184  	for _, m := range mem {
   185  		// Ignore left members
   186  		// This prevents querying for leader status on regions where all members have left
   187  		if m.Status == "left" {
   188  			continue
   189  		}
   190  
   191  		regions[m.Tags["region"]] = struct{}{}
   192  	}
   193  
   194  	if len(regions) == 0 {
   195  		return leaders, nil
   196  	}
   197  
   198  	var mErr multierror.Error
   199  	status := client.Status()
   200  	for reg := range regions {
   201  		l, err := status.RegionLeader(reg)
   202  		if err != nil {
   203  			_ = multierror.Append(&mErr, fmt.Errorf("Region %q: %v", reg, err))
   204  			continue
   205  		}
   206  
   207  		leaders[reg] = l
   208  	}
   209  
   210  	return leaders, mErr.ErrorOrNil()
   211  }
   212  
   213  func isLeader(member *api.AgentMember, leaders map[string]string) bool {
   214  	addr := net.JoinHostPort(member.Addr, member.Tags["port"])
   215  	reg := member.Tags["region"]
   216  	regLeader, ok := leaders[reg]
   217  	return ok && regLeader == addr
   218  }