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 }