github.com/hernad/nomad@v1.6.112/command/server_members.go (about)

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