github.com/djenriquez/nomad-1@v0.8.1/command/node_status.go (about) 1 package command 2 3 import ( 4 "fmt" 5 "math" 6 "sort" 7 "strings" 8 "time" 9 10 humanize "github.com/dustin/go-humanize" 11 "github.com/posener/complete" 12 13 "github.com/hashicorp/nomad/api" 14 "github.com/hashicorp/nomad/api/contexts" 15 "github.com/hashicorp/nomad/helper" 16 ) 17 18 const ( 19 // floatFormat is a format string for formatting floats. 20 floatFormat = "#,###.##" 21 22 // bytesPerMegabyte is the number of bytes per MB 23 bytesPerMegabyte = 1024 * 1024 24 ) 25 26 type NodeStatusCommand struct { 27 Meta 28 length int 29 short bool 30 verbose bool 31 list_allocs bool 32 self bool 33 stats bool 34 json bool 35 tmpl string 36 } 37 38 func (c *NodeStatusCommand) Help() string { 39 helpText := ` 40 Usage: nomad node status [options] <node> 41 42 Display status information about a given node. The list of nodes 43 returned includes only nodes which jobs may be scheduled to, and 44 includes status and other high-level information. 45 46 If a node ID is passed, information for that specific node will be displayed, 47 including resource usage statistics. If no node ID's are passed, then a 48 short-hand list of all nodes will be displayed. The -self flag is useful to 49 quickly access the status of the local node. 50 51 General Options: 52 53 ` + generalOptionsUsage() + ` 54 55 Node Status Options: 56 57 -self 58 Query the status of the local node. 59 60 -stats 61 Display detailed resource usage statistics. 62 63 -allocs 64 Display a count of running allocations for each node. 65 66 -short 67 Display short output. Used only when a single node is being 68 queried, and drops verbose output about node allocations. 69 70 -verbose 71 Display full information. 72 73 -json 74 Output the node in its JSON format. 75 76 -t 77 Format and display node using a Go template. 78 ` 79 return strings.TrimSpace(helpText) 80 } 81 82 func (c *NodeStatusCommand) Synopsis() string { 83 return "Display status information about nodes" 84 } 85 86 func (c *NodeStatusCommand) AutocompleteFlags() complete.Flags { 87 return mergeAutocompleteFlags(c.Meta.AutocompleteFlags(FlagSetClient), 88 complete.Flags{ 89 "-allocs": complete.PredictNothing, 90 "-json": complete.PredictNothing, 91 "-self": complete.PredictNothing, 92 "-short": complete.PredictNothing, 93 "-stats": complete.PredictNothing, 94 "-t": complete.PredictAnything, 95 "-verbose": complete.PredictNothing, 96 }) 97 } 98 99 func (c *NodeStatusCommand) AutocompleteArgs() complete.Predictor { 100 return complete.PredictFunc(func(a complete.Args) []string { 101 client, err := c.Meta.Client() 102 if err != nil { 103 return nil 104 } 105 106 resp, _, err := client.Search().PrefixSearch(a.Last, contexts.Nodes, nil) 107 if err != nil { 108 return []string{} 109 } 110 return resp.Matches[contexts.Nodes] 111 }) 112 } 113 114 func (c *NodeStatusCommand) Run(args []string) int { 115 116 flags := c.Meta.FlagSet("node-status", FlagSetClient) 117 flags.Usage = func() { c.Ui.Output(c.Help()) } 118 flags.BoolVar(&c.short, "short", false, "") 119 flags.BoolVar(&c.verbose, "verbose", false, "") 120 flags.BoolVar(&c.list_allocs, "allocs", false, "") 121 flags.BoolVar(&c.self, "self", false, "") 122 flags.BoolVar(&c.stats, "stats", false, "") 123 flags.BoolVar(&c.json, "json", false, "") 124 flags.StringVar(&c.tmpl, "t", "", "") 125 126 if err := flags.Parse(args); err != nil { 127 return 1 128 } 129 130 // Check that we got either a single node or none 131 args = flags.Args() 132 if len(args) > 1 { 133 c.Ui.Error(c.Help()) 134 return 1 135 } 136 137 // Truncate the id unless full length is requested 138 c.length = shortId 139 if c.verbose { 140 c.length = fullId 141 } 142 143 // Get the HTTP client 144 client, err := c.Meta.Client() 145 if err != nil { 146 c.Ui.Error(fmt.Sprintf("Error initializing client: %s", err)) 147 return 1 148 } 149 150 // Use list mode if no node name was provided 151 if len(args) == 0 && !c.self { 152 153 // Query the node info 154 nodes, _, err := client.Nodes().List(nil) 155 if err != nil { 156 c.Ui.Error(fmt.Sprintf("Error querying node status: %s", err)) 157 return 1 158 } 159 160 // If output format is specified, format and output the node data list 161 if c.json || len(c.tmpl) > 0 { 162 out, err := Format(c.json, c.tmpl, nodes) 163 if err != nil { 164 c.Ui.Error(err.Error()) 165 return 1 166 } 167 168 c.Ui.Output(out) 169 return 0 170 } 171 172 // Return nothing if no nodes found 173 if len(nodes) == 0 { 174 return 0 175 } 176 177 // Format the nodes list 178 out := make([]string, len(nodes)+1) 179 180 out[0] = "ID|DC|Name|Class|" 181 182 if c.verbose { 183 out[0] += "Address|Version|" 184 } 185 186 out[0] += "Drain|Eligibility|Status" 187 188 if c.list_allocs { 189 out[0] += "|Running Allocs" 190 } 191 192 for i, node := range nodes { 193 out[i+1] = fmt.Sprintf("%s|%s|%s|%s", 194 limit(node.ID, c.length), 195 node.Datacenter, 196 node.Name, 197 node.NodeClass) 198 if c.verbose { 199 out[i+1] += fmt.Sprintf("|%s|%s", 200 node.Address, node.Version) 201 } 202 out[i+1] += fmt.Sprintf("|%v|%s|%s", 203 node.Drain, 204 node.SchedulingEligibility, 205 node.Status) 206 207 if c.list_allocs { 208 numAllocs, err := getRunningAllocs(client, node.ID) 209 if err != nil { 210 c.Ui.Error(fmt.Sprintf("Error querying node allocations: %s", err)) 211 return 1 212 } 213 out[i+1] += fmt.Sprintf("|%v", 214 len(numAllocs)) 215 } 216 } 217 218 // Dump the output 219 c.Ui.Output(formatList(out)) 220 return 0 221 } 222 223 // Query the specific node 224 var nodeID string 225 if !c.self { 226 nodeID = args[0] 227 } else { 228 var err error 229 if nodeID, err = getLocalNodeID(client); err != nil { 230 c.Ui.Error(err.Error()) 231 return 1 232 } 233 } 234 if len(nodeID) == 1 { 235 c.Ui.Error(fmt.Sprintf("Identifier must contain at least two characters.")) 236 return 1 237 } 238 239 nodeID = sanitizeUUIDPrefix(nodeID) 240 nodes, _, err := client.Nodes().PrefixList(nodeID) 241 if err != nil { 242 c.Ui.Error(fmt.Sprintf("Error querying node info: %s", err)) 243 return 1 244 } 245 // Return error if no nodes are found 246 if len(nodes) == 0 { 247 c.Ui.Error(fmt.Sprintf("No node(s) with prefix %q found", nodeID)) 248 return 1 249 } 250 if len(nodes) > 1 { 251 // Dump the output 252 c.Ui.Error(fmt.Sprintf("Prefix matched multiple nodes\n\n%s", 253 formatNodeStubList(nodes, c.verbose))) 254 return 1 255 } 256 257 // Prefix lookup matched a single node 258 node, _, err := client.Nodes().Info(nodes[0].ID, nil) 259 if err != nil { 260 c.Ui.Error(fmt.Sprintf("Error querying node info: %s", err)) 261 return 1 262 } 263 264 // If output format is specified, format and output the data 265 if c.json || len(c.tmpl) > 0 { 266 out, err := Format(c.json, c.tmpl, node) 267 if err != nil { 268 c.Ui.Error(err.Error()) 269 return 1 270 } 271 272 c.Ui.Output(out) 273 return 0 274 } 275 276 return c.formatNode(client, node) 277 } 278 279 func nodeDrivers(n *api.Node) []string { 280 var drivers []string 281 for k, v := range n.Attributes { 282 // driver.docker = 1 283 parts := strings.Split(k, ".") 284 if len(parts) != 2 { 285 continue 286 } else if parts[0] != "driver" { 287 continue 288 } else if v != "1" { 289 continue 290 } 291 292 drivers = append(drivers, parts[1]) 293 } 294 295 sort.Strings(drivers) 296 return drivers 297 } 298 299 func (c *NodeStatusCommand) formatNode(client *api.Client, node *api.Node) int { 300 // Format the header output 301 basic := []string{ 302 fmt.Sprintf("ID|%s", limit(node.ID, c.length)), 303 fmt.Sprintf("Name|%s", node.Name), 304 fmt.Sprintf("Class|%s", node.NodeClass), 305 fmt.Sprintf("DC|%s", node.Datacenter), 306 fmt.Sprintf("Drain|%v", node.Drain), 307 fmt.Sprintf("Eligibility|%s", node.SchedulingEligibility), 308 fmt.Sprintf("Status|%s", node.Status), 309 } 310 311 if c.short { 312 basic = append(basic, fmt.Sprintf("Drivers|%s", strings.Join(nodeDrivers(node), ","))) 313 c.Ui.Output(c.Colorize().Color(formatKV(basic))) 314 } else { 315 // Get the host stats 316 hostStats, nodeStatsErr := client.Nodes().Stats(node.ID, nil) 317 if nodeStatsErr != nil { 318 c.Ui.Output("") 319 c.Ui.Error(fmt.Sprintf("error fetching node stats: %v", nodeStatsErr)) 320 } 321 if hostStats != nil { 322 uptime := time.Duration(hostStats.Uptime * uint64(time.Second)) 323 basic = append(basic, fmt.Sprintf("Uptime|%s", uptime.String())) 324 } 325 326 // Emit the driver info 327 if !c.verbose { 328 driverStatus := fmt.Sprintf("Driver Status| %s", c.outputTruncatedNodeDriverInfo(node)) 329 basic = append(basic, driverStatus) 330 } 331 332 c.Ui.Output(c.Colorize().Color(formatKV(basic))) 333 334 if c.verbose { 335 c.outputNodeDriverInfo(node) 336 } 337 338 // Emit node events 339 c.outputNodeStatusEvents(node) 340 341 // Get list of running allocations on the node 342 runningAllocs, err := getRunningAllocs(client, node.ID) 343 if err != nil { 344 c.Ui.Error(fmt.Sprintf("Error querying node for running allocations: %s", err)) 345 return 1 346 } 347 348 allocatedResources := getAllocatedResources(client, runningAllocs, node) 349 c.Ui.Output(c.Colorize().Color("\n[bold]Allocated Resources[reset]")) 350 c.Ui.Output(formatList(allocatedResources)) 351 352 actualResources, err := getActualResources(client, runningAllocs, node) 353 if err == nil { 354 c.Ui.Output(c.Colorize().Color("\n[bold]Allocation Resource Utilization[reset]")) 355 c.Ui.Output(formatList(actualResources)) 356 } 357 358 hostResources, err := getHostResources(hostStats, node) 359 if err != nil { 360 c.Ui.Output("") 361 c.Ui.Error(fmt.Sprintf("error fetching node stats: %v", err)) 362 } 363 if err == nil { 364 c.Ui.Output(c.Colorize().Color("\n[bold]Host Resource Utilization[reset]")) 365 c.Ui.Output(formatList(hostResources)) 366 } 367 368 if hostStats != nil && c.stats { 369 c.Ui.Output(c.Colorize().Color("\n[bold]CPU Stats[reset]")) 370 c.printCpuStats(hostStats) 371 c.Ui.Output(c.Colorize().Color("\n[bold]Memory Stats[reset]")) 372 c.printMemoryStats(hostStats) 373 c.Ui.Output(c.Colorize().Color("\n[bold]Disk Stats[reset]")) 374 c.printDiskStats(hostStats) 375 } 376 } 377 378 nodeAllocs, _, err := client.Nodes().Allocations(node.ID, nil) 379 if err != nil { 380 c.Ui.Error(fmt.Sprintf("Error querying node allocations: %s", err)) 381 return 1 382 } 383 384 c.Ui.Output(c.Colorize().Color("\n[bold]Allocations[reset]")) 385 c.Ui.Output(formatAllocList(nodeAllocs, c.verbose, c.length)) 386 387 if c.verbose { 388 c.formatAttributes(node) 389 c.formatMeta(node) 390 } 391 return 0 392 393 } 394 395 func (c *NodeStatusCommand) outputTruncatedNodeDriverInfo(node *api.Node) string { 396 drivers := make([]string, 0, len(node.Drivers)) 397 398 for driverName, driverInfo := range node.Drivers { 399 if !driverInfo.Detected { 400 continue 401 } 402 403 if !driverInfo.Healthy { 404 drivers = append(drivers, fmt.Sprintf("%s (unhealthy)", driverName)) 405 } else { 406 drivers = append(drivers, driverName) 407 } 408 } 409 return strings.Trim(strings.Join(drivers, ","), ", ") 410 } 411 412 func (c *NodeStatusCommand) outputNodeDriverInfo(node *api.Node) { 413 c.Ui.Output(c.Colorize().Color("\n[bold]Drivers")) 414 415 size := len(node.Drivers) 416 nodeDrivers := make([]string, 0, size+1) 417 418 nodeDrivers = append(nodeDrivers, "Driver|Detected|Healthy|Message|Time") 419 420 drivers := make([]string, 0, len(node.Drivers)) 421 for driver := range node.Drivers { 422 drivers = append(drivers, driver) 423 } 424 sort.Strings(drivers) 425 426 for _, driver := range drivers { 427 info := node.Drivers[driver] 428 timestamp := formatTime(info.UpdateTime) 429 nodeDrivers = append(nodeDrivers, fmt.Sprintf("%s|%v|%v|%s|%s", driver, info.Detected, info.Healthy, info.HealthDescription, timestamp)) 430 } 431 c.Ui.Output(formatList(nodeDrivers)) 432 } 433 434 func (c *NodeStatusCommand) outputNodeStatusEvents(node *api.Node) { 435 c.Ui.Output(c.Colorize().Color("\n[bold]Node Events")) 436 c.outputNodeEvent(node.Events) 437 } 438 439 func (c *NodeStatusCommand) outputNodeEvent(events []*api.NodeEvent) { 440 size := len(events) 441 nodeEvents := make([]string, size+1) 442 if c.verbose { 443 nodeEvents[0] = "Time|Subsystem|Message|Details" 444 } else { 445 nodeEvents[0] = "Time|Subsystem|Message" 446 } 447 448 for i, event := range events { 449 timestamp := formatTime(event.Timestamp) 450 subsystem := formatEventSubsystem(event.Subsystem, event.Details["driver"]) 451 msg := event.Message 452 if c.verbose { 453 details := formatEventDetails(event.Details) 454 nodeEvents[size-i] = fmt.Sprintf("%s|%s|%s|%s", timestamp, subsystem, msg, details) 455 } else { 456 nodeEvents[size-i] = fmt.Sprintf("%s|%s|%s", timestamp, subsystem, msg) 457 } 458 } 459 c.Ui.Output(formatList(nodeEvents)) 460 } 461 462 func formatEventSubsystem(subsystem, driverName string) string { 463 if driverName == "" { 464 return subsystem 465 } 466 467 // If this event is for a driver, append the driver name to make the message 468 // clearer 469 return fmt.Sprintf("Driver: %s", driverName) 470 } 471 472 func formatEventDetails(details map[string]string) string { 473 output := make([]string, 0, len(details)) 474 for k, v := range details { 475 output = append(output, fmt.Sprintf("%s: %s ", k, v)) 476 } 477 return strings.Join(output, ", ") 478 } 479 480 func (c *NodeStatusCommand) formatAttributes(node *api.Node) { 481 // Print the attributes 482 keys := make([]string, len(node.Attributes)) 483 for k := range node.Attributes { 484 keys = append(keys, k) 485 } 486 sort.Strings(keys) 487 488 var attributes []string 489 for _, k := range keys { 490 if k != "" { 491 attributes = append(attributes, fmt.Sprintf("%s|%s", k, node.Attributes[k])) 492 } 493 } 494 c.Ui.Output(c.Colorize().Color("\n[bold]Attributes[reset]")) 495 c.Ui.Output(formatKV(attributes)) 496 } 497 498 func (c *NodeStatusCommand) formatMeta(node *api.Node) { 499 // Print the meta 500 keys := make([]string, 0, len(node.Meta)) 501 for k := range node.Meta { 502 keys = append(keys, k) 503 } 504 sort.Strings(keys) 505 506 var meta []string 507 for _, k := range keys { 508 if k != "" { 509 meta = append(meta, fmt.Sprintf("%s|%s", k, node.Meta[k])) 510 } 511 } 512 c.Ui.Output(c.Colorize().Color("\n[bold]Meta[reset]")) 513 c.Ui.Output(formatKV(meta)) 514 } 515 516 func (c *NodeStatusCommand) printCpuStats(hostStats *api.HostStats) { 517 l := len(hostStats.CPU) 518 for i, cpuStat := range hostStats.CPU { 519 cpuStatsAttr := make([]string, 4) 520 cpuStatsAttr[0] = fmt.Sprintf("CPU|%v", cpuStat.CPU) 521 cpuStatsAttr[1] = fmt.Sprintf("User|%v%%", humanize.FormatFloat(floatFormat, cpuStat.User)) 522 cpuStatsAttr[2] = fmt.Sprintf("System|%v%%", humanize.FormatFloat(floatFormat, cpuStat.System)) 523 cpuStatsAttr[3] = fmt.Sprintf("Idle|%v%%", humanize.FormatFloat(floatFormat, cpuStat.Idle)) 524 c.Ui.Output(formatKV(cpuStatsAttr)) 525 if i+1 < l { 526 c.Ui.Output("") 527 } 528 } 529 } 530 531 func (c *NodeStatusCommand) printMemoryStats(hostStats *api.HostStats) { 532 memoryStat := hostStats.Memory 533 memStatsAttr := make([]string, 4) 534 memStatsAttr[0] = fmt.Sprintf("Total|%v", humanize.IBytes(memoryStat.Total)) 535 memStatsAttr[1] = fmt.Sprintf("Available|%v", humanize.IBytes(memoryStat.Available)) 536 memStatsAttr[2] = fmt.Sprintf("Used|%v", humanize.IBytes(memoryStat.Used)) 537 memStatsAttr[3] = fmt.Sprintf("Free|%v", humanize.IBytes(memoryStat.Free)) 538 c.Ui.Output(formatKV(memStatsAttr)) 539 } 540 541 func (c *NodeStatusCommand) printDiskStats(hostStats *api.HostStats) { 542 l := len(hostStats.DiskStats) 543 for i, diskStat := range hostStats.DiskStats { 544 diskStatsAttr := make([]string, 7) 545 diskStatsAttr[0] = fmt.Sprintf("Device|%s", diskStat.Device) 546 diskStatsAttr[1] = fmt.Sprintf("MountPoint|%s", diskStat.Mountpoint) 547 diskStatsAttr[2] = fmt.Sprintf("Size|%s", humanize.IBytes(diskStat.Size)) 548 diskStatsAttr[3] = fmt.Sprintf("Used|%s", humanize.IBytes(diskStat.Used)) 549 diskStatsAttr[4] = fmt.Sprintf("Available|%s", humanize.IBytes(diskStat.Available)) 550 diskStatsAttr[5] = fmt.Sprintf("Used Percent|%v%%", humanize.FormatFloat(floatFormat, diskStat.UsedPercent)) 551 diskStatsAttr[6] = fmt.Sprintf("Inodes Percent|%v%%", humanize.FormatFloat(floatFormat, diskStat.InodesUsedPercent)) 552 c.Ui.Output(formatKV(diskStatsAttr)) 553 if i+1 < l { 554 c.Ui.Output("") 555 } 556 } 557 } 558 559 // getRunningAllocs returns a slice of allocation id's running on the node 560 func getRunningAllocs(client *api.Client, nodeID string) ([]*api.Allocation, error) { 561 var allocs []*api.Allocation 562 563 // Query the node allocations 564 nodeAllocs, _, err := client.Nodes().Allocations(nodeID, nil) 565 // Filter list to only running allocations 566 for _, alloc := range nodeAllocs { 567 if alloc.ClientStatus == "running" { 568 allocs = append(allocs, alloc) 569 } 570 } 571 return allocs, err 572 } 573 574 // getAllocatedResources returns the resource usage of the node. 575 func getAllocatedResources(client *api.Client, runningAllocs []*api.Allocation, node *api.Node) []string { 576 // Compute the total 577 total := computeNodeTotalResources(node) 578 579 // Get Resources 580 var cpu, mem, disk, iops int 581 for _, alloc := range runningAllocs { 582 cpu += *alloc.Resources.CPU 583 mem += *alloc.Resources.MemoryMB 584 disk += *alloc.Resources.DiskMB 585 iops += *alloc.Resources.IOPS 586 } 587 588 resources := make([]string, 2) 589 resources[0] = "CPU|Memory|Disk|IOPS" 590 resources[1] = fmt.Sprintf("%d/%d MHz|%s/%s|%s/%s|%d/%d", 591 cpu, 592 *total.CPU, 593 humanize.IBytes(uint64(mem*bytesPerMegabyte)), 594 humanize.IBytes(uint64(*total.MemoryMB*bytesPerMegabyte)), 595 humanize.IBytes(uint64(disk*bytesPerMegabyte)), 596 humanize.IBytes(uint64(*total.DiskMB*bytesPerMegabyte)), 597 iops, 598 *total.IOPS) 599 600 return resources 601 } 602 603 // computeNodeTotalResources returns the total allocatable resources (resources 604 // minus reserved) 605 func computeNodeTotalResources(node *api.Node) api.Resources { 606 total := api.Resources{} 607 608 r := node.Resources 609 res := node.Reserved 610 if res == nil { 611 res = &api.Resources{} 612 } 613 total.CPU = helper.IntToPtr(*r.CPU - *res.CPU) 614 total.MemoryMB = helper.IntToPtr(*r.MemoryMB - *res.MemoryMB) 615 total.DiskMB = helper.IntToPtr(*r.DiskMB - *res.DiskMB) 616 total.IOPS = helper.IntToPtr(*r.IOPS - *res.IOPS) 617 return total 618 } 619 620 // getActualResources returns the actual resource usage of the allocations. 621 func getActualResources(client *api.Client, runningAllocs []*api.Allocation, node *api.Node) ([]string, error) { 622 // Compute the total 623 total := computeNodeTotalResources(node) 624 625 // Get Resources 626 var cpu float64 627 var mem uint64 628 for _, alloc := range runningAllocs { 629 // Make the call to the client to get the actual usage. 630 stats, err := client.Allocations().Stats(alloc, nil) 631 if err != nil { 632 return nil, err 633 } 634 635 cpu += stats.ResourceUsage.CpuStats.TotalTicks 636 mem += stats.ResourceUsage.MemoryStats.RSS 637 } 638 639 resources := make([]string, 2) 640 resources[0] = "CPU|Memory" 641 resources[1] = fmt.Sprintf("%v/%d MHz|%v/%v", 642 math.Floor(cpu), 643 *total.CPU, 644 humanize.IBytes(mem), 645 humanize.IBytes(uint64(*total.MemoryMB*bytesPerMegabyte))) 646 647 return resources, nil 648 } 649 650 // getHostResources returns the actual resource usage of the node. 651 func getHostResources(hostStats *api.HostStats, node *api.Node) ([]string, error) { 652 if hostStats == nil { 653 return nil, fmt.Errorf("actual resource usage not present") 654 } 655 var resources []string 656 657 // calculate disk usage 658 storageDevice := node.Attributes["unique.storage.volume"] 659 var diskUsed, diskSize uint64 660 var physical bool 661 for _, disk := range hostStats.DiskStats { 662 if disk.Device == storageDevice { 663 diskUsed = disk.Used 664 diskSize = disk.Size 665 physical = true 666 } 667 } 668 669 resources = make([]string, 2) 670 resources[0] = "CPU|Memory|Disk" 671 if physical { 672 resources[1] = fmt.Sprintf("%v/%d MHz|%s/%s|%s/%s", 673 math.Floor(hostStats.CPUTicksConsumed), 674 *node.Resources.CPU, 675 humanize.IBytes(hostStats.Memory.Used), 676 humanize.IBytes(hostStats.Memory.Total), 677 humanize.IBytes(diskUsed), 678 humanize.IBytes(diskSize), 679 ) 680 } else { 681 // If non-physical device are used, output device name only, 682 // since nomad doesn't collect the stats data. 683 resources[1] = fmt.Sprintf("%v/%d MHz|%s/%s|(%s)", 684 math.Floor(hostStats.CPUTicksConsumed), 685 *node.Resources.CPU, 686 humanize.IBytes(hostStats.Memory.Used), 687 humanize.IBytes(hostStats.Memory.Total), 688 storageDevice, 689 ) 690 } 691 return resources, nil 692 } 693 694 // formatNodeStubList is used to return a table format of a list of node stubs. 695 func formatNodeStubList(nodes []*api.NodeListStub, verbose bool) string { 696 // Return error if no nodes are found 697 if len(nodes) == 0 { 698 return "" 699 } 700 // Truncate the id unless full length is requested 701 length := shortId 702 if verbose { 703 length = fullId 704 } 705 706 // Format the nodes list that matches the prefix so that the user 707 // can create a more specific request 708 out := make([]string, len(nodes)+1) 709 out[0] = "ID|DC|Name|Class|Drain|Eligibility|Status" 710 for i, node := range nodes { 711 out[i+1] = fmt.Sprintf("%s|%s|%s|%s|%v|%s|%s", 712 limit(node.ID, length), 713 node.Datacenter, 714 node.Name, 715 node.NodeClass, 716 node.Drain, 717 node.SchedulingEligibility, 718 node.Status) 719 } 720 721 return formatList(out) 722 }