github.com/dkerwin/nomad@v0.3.3-0.20160525181927-74554135514b/command/alloc_status.go (about)

     1  package command
     2  
     3  import (
     4  	"fmt"
     5  	"sort"
     6  	"strings"
     7  	"time"
     8  
     9  	"github.com/hashicorp/nomad/api"
    10  	"github.com/hashicorp/nomad/client"
    11  )
    12  
    13  type AllocStatusCommand struct {
    14  	Meta
    15  }
    16  
    17  func (c *AllocStatusCommand) Help() string {
    18  	helpText := `
    19  Usage: nomad alloc-status [options] <allocation>
    20  
    21    Display information about existing allocations and its tasks. This command can
    22    be used to inspect the current status of all allocation, including its running
    23    status, metadata, and verbose failure messages reported by internal
    24    subsystems.
    25  
    26  General Options:
    27  
    28    ` + generalOptionsUsage() + `
    29  
    30  
    31    -short
    32      Display short output. Shows only the most recent task event.
    33  
    34    -verbose
    35      Show full information.
    36  `
    37  
    38  	return strings.TrimSpace(helpText)
    39  }
    40  
    41  func (c *AllocStatusCommand) Synopsis() string {
    42  	return "Display allocation status information and metadata"
    43  }
    44  
    45  func (c *AllocStatusCommand) Run(args []string) int {
    46  	var short, verbose bool
    47  
    48  	flags := c.Meta.FlagSet("alloc-status", FlagSetClient)
    49  	flags.Usage = func() { c.Ui.Output(c.Help()) }
    50  	flags.BoolVar(&short, "short", false, "")
    51  	flags.BoolVar(&verbose, "verbose", false, "")
    52  
    53  	if err := flags.Parse(args); err != nil {
    54  		return 1
    55  	}
    56  
    57  	// Check that we got exactly one allocation ID
    58  	args = flags.Args()
    59  	if len(args) != 1 {
    60  		c.Ui.Error(c.Help())
    61  		return 1
    62  	}
    63  	allocID := args[0]
    64  
    65  	// Get the HTTP client
    66  	client, err := c.Meta.Client()
    67  	if err != nil {
    68  		c.Ui.Error(fmt.Sprintf("Error initializing client: %s", err))
    69  		return 1
    70  	}
    71  
    72  	// Truncate the id unless full length is requested
    73  	length := shortId
    74  	if verbose {
    75  		length = fullId
    76  	}
    77  
    78  	// Query the allocation info
    79  	if len(allocID) == 1 {
    80  		c.Ui.Error(fmt.Sprintf("Identifier must contain at least two characters."))
    81  		return 1
    82  	}
    83  	if len(allocID)%2 == 1 {
    84  		// Identifiers must be of even length, so we strip off the last byte
    85  		// to provide a consistent user experience.
    86  		allocID = allocID[:len(allocID)-1]
    87  	}
    88  
    89  	allocs, _, err := client.Allocations().PrefixList(allocID)
    90  	if err != nil {
    91  		c.Ui.Error(fmt.Sprintf("Error querying allocation: %v", err))
    92  		return 1
    93  	}
    94  	if len(allocs) == 0 {
    95  		c.Ui.Error(fmt.Sprintf("No allocation(s) with prefix or id %q found", allocID))
    96  		return 1
    97  	}
    98  	if len(allocs) > 1 {
    99  		// Format the allocs
   100  		out := make([]string, len(allocs)+1)
   101  		out[0] = "ID|Eval ID|Job ID|Task Group|Desired Status|Client Status"
   102  		for i, alloc := range allocs {
   103  			out[i+1] = fmt.Sprintf("%s|%s|%s|%s|%s|%s",
   104  				limit(alloc.ID, length),
   105  				limit(alloc.EvalID, length),
   106  				alloc.JobID,
   107  				alloc.TaskGroup,
   108  				alloc.DesiredStatus,
   109  				alloc.ClientStatus,
   110  			)
   111  		}
   112  		c.Ui.Output(fmt.Sprintf("Prefix matched multiple allocations\n\n%s", formatList(out)))
   113  		return 0
   114  	}
   115  	// Prefix lookup matched a single allocation
   116  	alloc, _, err := client.Allocations().Info(allocs[0].ID, nil)
   117  	if err != nil {
   118  		c.Ui.Error(fmt.Sprintf("Error querying allocation: %s", err))
   119  		return 1
   120  	}
   121  
   122  	// Format the allocation data
   123  	basic := []string{
   124  		fmt.Sprintf("ID|%s", limit(alloc.ID, length)),
   125  		fmt.Sprintf("Eval ID|%s", limit(alloc.EvalID, length)),
   126  		fmt.Sprintf("Name|%s", alloc.Name),
   127  		fmt.Sprintf("Node ID|%s", limit(alloc.NodeID, length)),
   128  		fmt.Sprintf("Job ID|%s", alloc.JobID),
   129  		fmt.Sprintf("Client Status|%s", alloc.ClientStatus),
   130  	}
   131  
   132  	if verbose {
   133  		basic = append(basic,
   134  			fmt.Sprintf("Evaluated Nodes|%d", alloc.Metrics.NodesEvaluated),
   135  			fmt.Sprintf("Filtered Nodes|%d", alloc.Metrics.NodesFiltered),
   136  			fmt.Sprintf("Exhausted Nodes|%d", alloc.Metrics.NodesExhausted),
   137  			fmt.Sprintf("Allocation Time|%s", alloc.Metrics.AllocationTime),
   138  			fmt.Sprintf("Failures|%d", alloc.Metrics.CoalescedFailures))
   139  	}
   140  	c.Ui.Output(formatKV(basic))
   141  
   142  	if !short {
   143  		c.taskResources(alloc)
   144  	}
   145  
   146  	// Print the state of each task.
   147  	if short {
   148  		c.shortTaskStatus(alloc)
   149  	} else {
   150  		c.taskStatus(alloc)
   151  	}
   152  
   153  	// Format the detailed status
   154  	if verbose || alloc.DesiredStatus == "failed" {
   155  		c.Ui.Output("\n==> Status")
   156  		dumpAllocStatus(c.Ui, alloc, length)
   157  	}
   158  
   159  	return 0
   160  }
   161  
   162  // shortTaskStatus prints out the current state of each task.
   163  func (c *AllocStatusCommand) shortTaskStatus(alloc *api.Allocation) {
   164  	tasks := make([]string, 0, len(alloc.TaskStates)+1)
   165  	tasks = append(tasks, "Name|State|Last Event|Time")
   166  	for task := range c.sortedTaskStateIterator(alloc.TaskStates) {
   167  		state := alloc.TaskStates[task]
   168  		lastState := state.State
   169  		var lastEvent, lastTime string
   170  
   171  		l := len(state.Events)
   172  		if l != 0 {
   173  			last := state.Events[l-1]
   174  			lastEvent = last.Type
   175  			lastTime = c.formatUnixNanoTime(last.Time)
   176  		}
   177  
   178  		tasks = append(tasks, fmt.Sprintf("%s|%s|%s|%s",
   179  			task, lastState, lastEvent, lastTime))
   180  	}
   181  
   182  	c.Ui.Output("\n==> Tasks")
   183  	c.Ui.Output(formatList(tasks))
   184  }
   185  
   186  // taskStatus prints out the most recent events for each task.
   187  func (c *AllocStatusCommand) taskStatus(alloc *api.Allocation) {
   188  	for task := range c.sortedTaskStateIterator(alloc.TaskStates) {
   189  		state := alloc.TaskStates[task]
   190  		events := make([]string, len(state.Events)+1)
   191  		events[0] = "Time|Type|Description"
   192  
   193  		size := len(state.Events)
   194  		for i, event := range state.Events {
   195  			formatedTime := c.formatUnixNanoTime(event.Time)
   196  
   197  			// Build up the description based on the event type.
   198  			var desc string
   199  			switch event.Type {
   200  			case api.TaskStarted:
   201  				desc = "Task started by client"
   202  			case api.TaskReceived:
   203  				desc = "Task received by client"
   204  			case api.TaskFailedValidation:
   205  				if event.ValidationError != "" {
   206  					desc = event.ValidationError
   207  				} else {
   208  					desc = "Validation of task failed"
   209  				}
   210  			case api.TaskDriverFailure:
   211  				if event.DriverError != "" {
   212  					desc = event.DriverError
   213  				} else {
   214  					desc = "Failed to start task"
   215  				}
   216  			case api.TaskDownloadingArtifacts:
   217  				desc = "Client is downloading artifacts"
   218  			case api.TaskArtifactDownloadFailed:
   219  				if event.DownloadError != "" {
   220  					desc = event.DownloadError
   221  				} else {
   222  					desc = "Failed to download artifacts"
   223  				}
   224  			case api.TaskKilled:
   225  				if event.KillError != "" {
   226  					desc = event.KillError
   227  				} else {
   228  					desc = "Task successfully killed"
   229  				}
   230  			case api.TaskTerminated:
   231  				var parts []string
   232  				parts = append(parts, fmt.Sprintf("Exit Code: %d", event.ExitCode))
   233  
   234  				if event.Signal != 0 {
   235  					parts = append(parts, fmt.Sprintf("Signal: %d", event.Signal))
   236  				}
   237  
   238  				if event.Message != "" {
   239  					parts = append(parts, fmt.Sprintf("Exit Message: %q", event.Message))
   240  				}
   241  				desc = strings.Join(parts, ", ")
   242  			case api.TaskRestarting:
   243  				in := fmt.Sprintf("Task restarting in %v", time.Duration(event.StartDelay))
   244  				if event.RestartReason != "" && event.RestartReason != client.ReasonWithinPolicy {
   245  					desc = fmt.Sprintf("%s - %s", event.RestartReason, in)
   246  				} else {
   247  					desc = in
   248  				}
   249  			case api.TaskNotRestarting:
   250  				if event.RestartReason != "" {
   251  					desc = event.RestartReason
   252  				} else {
   253  					desc = "Task exceeded restart policy"
   254  				}
   255  			}
   256  
   257  			// Reverse order so we are sorted by time
   258  			events[size-i] = fmt.Sprintf("%s|%s|%s", formatedTime, event.Type, desc)
   259  		}
   260  
   261  		c.Ui.Output(fmt.Sprintf("\n==> Task %q is %q\nRecent Events:", task, state.State))
   262  		c.Ui.Output(formatList(events))
   263  	}
   264  }
   265  
   266  // formatUnixNanoTime is a helper for formating time for output.
   267  func (c *AllocStatusCommand) formatUnixNanoTime(nano int64) string {
   268  	t := time.Unix(0, nano)
   269  	return formatTime(t)
   270  }
   271  
   272  // sortedTaskStateIterator is a helper that takes the task state map and returns a
   273  // channel that returns the keys in a sorted order.
   274  func (c *AllocStatusCommand) sortedTaskStateIterator(m map[string]*api.TaskState) <-chan string {
   275  	output := make(chan string, len(m))
   276  	keys := make([]string, len(m))
   277  	i := 0
   278  	for k := range m {
   279  		keys[i] = k
   280  		i++
   281  	}
   282  	sort.Strings(keys)
   283  
   284  	for _, key := range keys {
   285  		output <- key
   286  	}
   287  
   288  	close(output)
   289  	return output
   290  }
   291  
   292  // allocResources prints out the allocation current resource usage
   293  func (c *AllocStatusCommand) allocResources(alloc *api.Allocation) {
   294  	resources := make([]string, 2)
   295  	resources[0] = "CPU|Memory MB|Disk MB|IOPS"
   296  	resources[1] = fmt.Sprintf("%v|%v|%v|%v",
   297  		alloc.Resources.CPU,
   298  		alloc.Resources.MemoryMB,
   299  		alloc.Resources.DiskMB,
   300  		alloc.Resources.IOPS)
   301  	c.Ui.Output(formatList(resources))
   302  }
   303  
   304  // taskResources prints out the tasks current resource usage
   305  func (c *AllocStatusCommand) taskResources(alloc *api.Allocation) {
   306  	if len(alloc.TaskResources) == 0 {
   307  		return
   308  	}
   309  
   310  	// Sort the tasks.
   311  	tasks := make([]string, 0, len(alloc.TaskResources))
   312  	for task := range alloc.TaskResources {
   313  		tasks = append(tasks, task)
   314  	}
   315  	sort.Strings(tasks)
   316  
   317  	c.Ui.Output("\n==> Task Resources")
   318  	firstLine := true
   319  	for _, task := range tasks {
   320  		resource := alloc.TaskResources[task]
   321  
   322  		header := fmt.Sprintf("\nTask: %q", task)
   323  		if firstLine {
   324  			header = fmt.Sprintf("Task: %q", task)
   325  			firstLine = false
   326  		}
   327  		c.Ui.Output(header)
   328  		var addr []string
   329  		for _, nw := range resource.Networks {
   330  			ports := append(nw.DynamicPorts, nw.ReservedPorts...)
   331  			for _, port := range ports {
   332  				addr = append(addr, fmt.Sprintf("%v: %v:%v\n", port.Label, nw.IP, port.Value))
   333  			}
   334  		}
   335  		var resourcesOutput []string
   336  		resourcesOutput = append(resourcesOutput, "CPU|Memory MB|Disk MB|IOPS|Addresses")
   337  		firstAddr := ""
   338  		if len(addr) > 0 {
   339  			firstAddr = addr[0]
   340  		}
   341  		resourcesOutput = append(resourcesOutput, fmt.Sprintf("%v|%v|%v|%v|%v",
   342  			resource.CPU,
   343  			resource.MemoryMB,
   344  			resource.DiskMB,
   345  			resource.IOPS,
   346  			firstAddr))
   347  		for i := 1; i < len(addr); i++ {
   348  			resourcesOutput = append(resourcesOutput, fmt.Sprintf("||||%v", addr[i]))
   349  		}
   350  		c.Ui.Output(formatListWithSpaces(resourcesOutput))
   351  	}
   352  }