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 }