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