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 }