github.com/dkerwin/nomad@v0.3.3-0.20160525181927-74554135514b/command/node_status.go (about) 1 package command 2 3 import ( 4 "fmt" 5 "sort" 6 "strings" 7 8 "github.com/hashicorp/nomad/api" 9 ) 10 11 type NodeStatusCommand struct { 12 Meta 13 } 14 15 func (c *NodeStatusCommand) Help() string { 16 helpText := ` 17 Usage: nomad node-status [options] <node> 18 19 Display status information about a given node. The list of nodes 20 returned includes only nodes which jobs may be scheduled to, and 21 includes status and other high-level information. 22 23 If a node ID is passed, information for that specific node will 24 be displayed. If no node ID's are passed, then a short-hand 25 list of all nodes will be displayed. The -self flag is useful to 26 quickly access the status of the local node. 27 28 General Options: 29 30 ` + generalOptionsUsage() + ` 31 32 Node Status Options: 33 34 -short 35 Display short output. Used only when a single node is being 36 queried, and drops verbose output about node allocations. 37 38 -verbose 39 Display full information. 40 41 -self 42 Query the status of the local node. 43 44 -allocs 45 Display a count of running allocations for each node. 46 ` 47 return strings.TrimSpace(helpText) 48 } 49 50 func (c *NodeStatusCommand) Synopsis() string { 51 return "Display status information about nodes" 52 } 53 54 func (c *NodeStatusCommand) Run(args []string) int { 55 var short, verbose, list_allocs, self bool 56 57 flags := c.Meta.FlagSet("node-status", FlagSetClient) 58 flags.Usage = func() { c.Ui.Output(c.Help()) } 59 flags.BoolVar(&short, "short", false, "") 60 flags.BoolVar(&verbose, "verbose", false, "") 61 flags.BoolVar(&list_allocs, "allocs", false, "") 62 flags.BoolVar(&self, "self", false, "") 63 64 if err := flags.Parse(args); err != nil { 65 return 1 66 } 67 68 // Check that we got either a single node or none 69 args = flags.Args() 70 if len(args) > 1 { 71 c.Ui.Error(c.Help()) 72 return 1 73 } 74 75 // Truncate the id unless full length is requested 76 length := shortId 77 if verbose { 78 length = fullId 79 } 80 81 // Get the HTTP client 82 client, err := c.Meta.Client() 83 if err != nil { 84 c.Ui.Error(fmt.Sprintf("Error initializing client: %s", err)) 85 return 1 86 } 87 88 // Use list mode if no node name was provided 89 if len(args) == 0 && !self { 90 // Query the node info 91 nodes, _, err := client.Nodes().List(nil) 92 if err != nil { 93 c.Ui.Error(fmt.Sprintf("Error querying node status: %s", err)) 94 return 1 95 } 96 97 // Return nothing if no nodes found 98 if len(nodes) == 0 { 99 return 0 100 } 101 102 // Format the nodes list 103 out := make([]string, len(nodes)+1) 104 if list_allocs { 105 out[0] = "ID|DC|Name|Class|Drain|Status|Running Allocs" 106 } else { 107 out[0] = "ID|DC|Name|Class|Drain|Status" 108 } 109 for i, node := range nodes { 110 if list_allocs { 111 numAllocs, err := getRunningAllocs(client, node.ID) 112 if err != nil { 113 c.Ui.Error(fmt.Sprintf("Error querying node allocations: %s", err)) 114 return 1 115 } 116 out[i+1] = fmt.Sprintf("%s|%s|%s|%s|%v|%s|%v", 117 limit(node.ID, length), 118 node.Datacenter, 119 node.Name, 120 node.NodeClass, 121 node.Drain, 122 node.Status, 123 len(numAllocs)) 124 } else { 125 out[i+1] = fmt.Sprintf("%s|%s|%s|%s|%v|%s", 126 limit(node.ID, length), 127 node.Datacenter, 128 node.Name, 129 node.NodeClass, 130 node.Drain, 131 node.Status) 132 } 133 } 134 135 // Dump the output 136 c.Ui.Output(formatList(out)) 137 return 0 138 } 139 140 // Query the specific node 141 nodeID := "" 142 if !self { 143 nodeID = args[0] 144 } else { 145 var err error 146 if nodeID, err = getLocalNodeID(client); err != nil { 147 c.Ui.Error(err.Error()) 148 return 1 149 } 150 } 151 if len(nodeID) == 1 { 152 c.Ui.Error(fmt.Sprintf("Identifier must contain at least two characters.")) 153 return 1 154 } 155 if len(nodeID)%2 == 1 { 156 // Identifiers must be of even length, so we strip off the last byte 157 // to provide a consistent user experience. 158 nodeID = nodeID[:len(nodeID)-1] 159 } 160 161 nodes, _, err := client.Nodes().PrefixList(nodeID) 162 if err != nil { 163 c.Ui.Error(fmt.Sprintf("Error querying node info: %s", err)) 164 return 1 165 } 166 // Return error if no nodes are found 167 if len(nodes) == 0 { 168 c.Ui.Error(fmt.Sprintf("No node(s) with prefix %q found", nodeID)) 169 return 1 170 } 171 if len(nodes) > 1 { 172 // Format the nodes list that matches the prefix so that the user 173 // can create a more specific request 174 out := make([]string, len(nodes)+1) 175 out[0] = "ID|DC|Name|Class|Drain|Status" 176 for i, node := range nodes { 177 out[i+1] = fmt.Sprintf("%s|%s|%s|%s|%v|%s", 178 limit(node.ID, length), 179 node.Datacenter, 180 node.Name, 181 node.NodeClass, 182 node.Drain, 183 node.Status) 184 } 185 // Dump the output 186 c.Ui.Output(fmt.Sprintf("Prefix matched multiple nodes\n\n%s", formatList(out))) 187 return 0 188 } 189 // Prefix lookup matched a single node 190 node, _, err := client.Nodes().Info(nodes[0].ID, nil) 191 if err != nil { 192 c.Ui.Error(fmt.Sprintf("Error querying node info: %s", err)) 193 return 1 194 } 195 196 // Format the output 197 basic := []string{ 198 fmt.Sprintf("ID|%s", limit(node.ID, length)), 199 fmt.Sprintf("Name|%s", node.Name), 200 fmt.Sprintf("Class|%s", node.NodeClass), 201 fmt.Sprintf("DC|%s", node.Datacenter), 202 fmt.Sprintf("Drain|%v", node.Drain), 203 fmt.Sprintf("Status|%s", node.Status), 204 } 205 c.Ui.Output(formatKV(basic)) 206 207 if !short { 208 resources, err := getResources(client, node) 209 if err != nil { 210 c.Ui.Error(fmt.Sprintf("Error querying node resources: %s", err)) 211 return 1 212 } 213 c.Ui.Output("\n==> Resource Utilization") 214 c.Ui.Output(formatList(resources)) 215 216 allocs, err := getAllocs(client, node, length) 217 if err != nil { 218 c.Ui.Error(fmt.Sprintf("Error querying node allocations: %s", err)) 219 return 1 220 } 221 222 if len(allocs) > 1 { 223 c.Ui.Output("\n==> Allocations") 224 c.Ui.Output(formatList(allocs)) 225 } 226 } 227 228 if verbose { 229 // Print the attributes 230 keys := make([]string, len(node.Attributes)) 231 for k := range node.Attributes { 232 keys = append(keys, k) 233 } 234 sort.Strings(keys) 235 236 var attributes []string 237 for _, k := range keys { 238 if k != "" { 239 attributes = append(attributes, fmt.Sprintf("%s|%s", k, node.Attributes[k])) 240 } 241 } 242 c.Ui.Output("\n==> Attributes") 243 c.Ui.Output(formatKV(attributes)) 244 } 245 246 return 0 247 } 248 249 // getRunningAllocs returns a slice of allocation id's running on the node 250 func getRunningAllocs(client *api.Client, nodeID string) ([]*api.Allocation, error) { 251 var allocs []*api.Allocation 252 253 // Query the node allocations 254 nodeAllocs, _, err := client.Nodes().Allocations(nodeID, nil) 255 // Filter list to only running allocations 256 for _, alloc := range nodeAllocs { 257 if alloc.ClientStatus == "running" { 258 allocs = append(allocs, alloc) 259 } 260 } 261 return allocs, err 262 } 263 264 // getAllocs returns information about every running allocation on the node 265 func getAllocs(client *api.Client, node *api.Node, length int) ([]string, error) { 266 var allocs []string 267 // Query the node allocations 268 nodeAllocs, _, err := client.Nodes().Allocations(node.ID, nil) 269 // Format the allocations 270 allocs = make([]string, len(nodeAllocs)+1) 271 allocs[0] = "ID|Eval ID|Job ID|Task Group|Desired Status|Client Status" 272 for i, alloc := range nodeAllocs { 273 allocs[i+1] = fmt.Sprintf("%s|%s|%s|%s|%s|%s", 274 limit(alloc.ID, length), 275 limit(alloc.EvalID, length), 276 alloc.JobID, 277 alloc.TaskGroup, 278 alloc.DesiredStatus, 279 alloc.ClientStatus) 280 } 281 return allocs, err 282 } 283 284 // getResources returns the resource usage of the node. 285 func getResources(client *api.Client, node *api.Node) ([]string, error) { 286 var resources []string 287 var cpu, mem, disk, iops int 288 var totalCpu, totalMem, totalDisk, totalIops int 289 290 // Compute the total 291 r := node.Resources 292 res := node.Reserved 293 if res == nil { 294 res = &api.Resources{} 295 } 296 totalCpu = r.CPU - res.CPU 297 totalMem = r.MemoryMB - res.MemoryMB 298 totalDisk = r.DiskMB - res.DiskMB 299 totalIops = r.IOPS - res.IOPS 300 301 // Get list of running allocations on the node 302 runningAllocs, err := getRunningAllocs(client, node.ID) 303 304 // Get Resources 305 for _, alloc := range runningAllocs { 306 cpu += alloc.Resources.CPU 307 mem += alloc.Resources.MemoryMB 308 disk += alloc.Resources.DiskMB 309 iops += alloc.Resources.IOPS 310 } 311 312 resources = make([]string, 2) 313 resources[0] = "CPU|Memory MB|Disk MB|IOPS" 314 resources[1] = fmt.Sprintf("%v/%v|%v/%v|%v/%v|%v/%v", 315 cpu, 316 totalCpu, 317 mem, 318 totalMem, 319 disk, 320 totalDisk, 321 iops, 322 totalIops) 323 324 return resources, err 325 }