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  }