github.com/hhrutter/nomad@v0.6.0-rc2.0.20170723054333-80c4b03f0705/command/eval_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 EvalStatusCommand struct {
    12  	Meta
    13  }
    14  
    15  func (c *EvalStatusCommand) Help() string {
    16  	helpText := `
    17  Usage: nomad eval-status [options] <evaluation>
    18  
    19    Display information about evaluations. This command can be used to inspect the
    20    current status of an evaluation as well as determine the reason an evaluation
    21    did not place all allocations.
    22  
    23  General Options:
    24  
    25    ` + generalOptionsUsage() + `
    26  
    27  Eval Status Options:
    28  
    29    -monitor
    30      Monitor an outstanding evaluation
    31  
    32    -verbose
    33      Show full information.
    34  
    35    -json
    36      Output the evaluation in its JSON format.
    37  
    38    -t
    39      Format and display evaluation using a Go template.
    40  `
    41  
    42  	return strings.TrimSpace(helpText)
    43  }
    44  
    45  func (c *EvalStatusCommand) Synopsis() string {
    46  	return "Display evaluation status and placement failure reasons"
    47  }
    48  
    49  func (c *EvalStatusCommand) Run(args []string) int {
    50  	var monitor, verbose, json bool
    51  	var tmpl string
    52  
    53  	flags := c.Meta.FlagSet("eval-status", FlagSetClient)
    54  	flags.Usage = func() { c.Ui.Output(c.Help()) }
    55  	flags.BoolVar(&monitor, "monitor", false, "")
    56  	flags.BoolVar(&verbose, "verbose", false, "")
    57  	flags.BoolVar(&json, "json", false, "")
    58  	flags.StringVar(&tmpl, "t", "", "")
    59  
    60  	if err := flags.Parse(args); err != nil {
    61  		return 1
    62  	}
    63  
    64  	// Check that we got exactly one evaluation ID
    65  	args = flags.Args()
    66  
    67  	// Get the HTTP client
    68  	client, err := c.Meta.Client()
    69  	if err != nil {
    70  		c.Ui.Error(fmt.Sprintf("Error initializing client: %s", err))
    71  		return 1
    72  	}
    73  
    74  	// If args not specified but output format is specified, format and output the evaluations data list
    75  	if len(args) == 0 && json || len(tmpl) > 0 {
    76  		evals, _, err := client.Evaluations().List(nil)
    77  		if err != nil {
    78  			c.Ui.Error(fmt.Sprintf("Error querying evaluations: %v", err))
    79  			return 1
    80  		}
    81  
    82  		out, err := Format(json, tmpl, evals)
    83  		if err != nil {
    84  			c.Ui.Error(err.Error())
    85  			return 1
    86  		}
    87  
    88  		c.Ui.Output(out)
    89  		return 0
    90  	}
    91  
    92  	if len(args) != 1 {
    93  		c.Ui.Error(c.Help())
    94  		return 1
    95  	}
    96  
    97  	evalID := args[0]
    98  
    99  	// Truncate the id unless full length is requested
   100  	length := shortId
   101  	if verbose {
   102  		length = fullId
   103  	}
   104  
   105  	// Query the allocation info
   106  	if len(evalID) == 1 {
   107  		c.Ui.Error(fmt.Sprintf("Identifier must contain at least two characters."))
   108  		return 1
   109  	}
   110  	if len(evalID)%2 == 1 {
   111  		// Identifiers must be of even length, so we strip off the last byte
   112  		// to provide a consistent user experience.
   113  		evalID = evalID[:len(evalID)-1]
   114  	}
   115  
   116  	evals, _, err := client.Evaluations().PrefixList(evalID)
   117  	if err != nil {
   118  		c.Ui.Error(fmt.Sprintf("Error querying evaluation: %v", err))
   119  		return 1
   120  	}
   121  	if len(evals) == 0 {
   122  		c.Ui.Error(fmt.Sprintf("No evaluation(s) with prefix or id %q found", evalID))
   123  		return 1
   124  	}
   125  
   126  	if len(evals) > 1 {
   127  		// Format the evals
   128  		out := make([]string, len(evals)+1)
   129  		out[0] = "ID|Priority|Triggered By|Status|Placement Failures"
   130  		for i, eval := range evals {
   131  			failures, _ := evalFailureStatus(eval)
   132  			out[i+1] = fmt.Sprintf("%s|%d|%s|%s|%s",
   133  				limit(eval.ID, length),
   134  				eval.Priority,
   135  				eval.TriggeredBy,
   136  				eval.Status,
   137  				failures,
   138  			)
   139  		}
   140  		c.Ui.Error(fmt.Sprintf("Prefix matched multiple evaluations\n\n%s", formatList(out)))
   141  		return 1
   142  	}
   143  
   144  	// If we are in monitor mode, monitor and exit
   145  	if monitor {
   146  		mon := newMonitor(c.Ui, client, length)
   147  		return mon.monitor(evals[0].ID, true)
   148  	}
   149  
   150  	// Prefix lookup matched a single evaluation
   151  	eval, _, err := client.Evaluations().Info(evals[0].ID, nil)
   152  	if err != nil {
   153  		c.Ui.Error(fmt.Sprintf("Error querying evaluation: %s", err))
   154  		return 1
   155  	}
   156  
   157  	// If output format is specified, format and output the data
   158  	if json || len(tmpl) > 0 {
   159  		out, err := Format(json, tmpl, eval)
   160  		if err != nil {
   161  			c.Ui.Error(err.Error())
   162  			return 1
   163  		}
   164  
   165  		c.Ui.Output(out)
   166  		return 0
   167  	}
   168  
   169  	failureString, failures := evalFailureStatus(eval)
   170  	triggerNoun, triggerSubj := getTriggerDetails(eval)
   171  	statusDesc := eval.StatusDescription
   172  	if statusDesc == "" {
   173  		statusDesc = eval.Status
   174  	}
   175  
   176  	// Format the evaluation data
   177  	basic := []string{
   178  		fmt.Sprintf("ID|%s", limit(eval.ID, length)),
   179  		fmt.Sprintf("Status|%s", eval.Status),
   180  		fmt.Sprintf("Status Description|%s", statusDesc),
   181  		fmt.Sprintf("Type|%s", eval.Type),
   182  		fmt.Sprintf("TriggeredBy|%s", eval.TriggeredBy),
   183  		fmt.Sprintf("%s|%s", triggerNoun, triggerSubj),
   184  		fmt.Sprintf("Priority|%d", eval.Priority),
   185  		fmt.Sprintf("Placement Failures|%s", failureString),
   186  	}
   187  
   188  	if verbose {
   189  		// NextEval, PreviousEval, BlockedEval
   190  		basic = append(basic,
   191  			fmt.Sprintf("Previous Eval|%s", eval.PreviousEval),
   192  			fmt.Sprintf("Next Eval|%s", eval.NextEval),
   193  			fmt.Sprintf("Blocked Eval|%s", eval.BlockedEval))
   194  	}
   195  	c.Ui.Output(formatKV(basic))
   196  
   197  	if failures {
   198  		c.Ui.Output(c.Colorize().Color("\n[bold]Failed Placements[reset]"))
   199  		sorted := sortedTaskGroupFromMetrics(eval.FailedTGAllocs)
   200  		for _, tg := range sorted {
   201  			metrics := eval.FailedTGAllocs[tg]
   202  
   203  			noun := "allocation"
   204  			if metrics.CoalescedFailures > 0 {
   205  				noun += "s"
   206  			}
   207  			c.Ui.Output(fmt.Sprintf("Task Group %q (failed to place %d %s):", tg, metrics.CoalescedFailures+1, noun))
   208  			c.Ui.Output(formatAllocMetrics(metrics, false, "  "))
   209  			c.Ui.Output("")
   210  		}
   211  
   212  		if eval.BlockedEval != "" {
   213  			c.Ui.Output(fmt.Sprintf("Evaluation %q waiting for additional capacity to place remainder",
   214  				limit(eval.BlockedEval, length)))
   215  		}
   216  	}
   217  
   218  	return 0
   219  }
   220  
   221  func sortedTaskGroupFromMetrics(groups map[string]*api.AllocationMetric) []string {
   222  	tgs := make([]string, 0, len(groups))
   223  	for tg, _ := range groups {
   224  		tgs = append(tgs, tg)
   225  	}
   226  	sort.Strings(tgs)
   227  	return tgs
   228  }
   229  
   230  func getTriggerDetails(eval *api.Evaluation) (noun, subject string) {
   231  	switch eval.TriggeredBy {
   232  	case "job-register", "job-deregister", "periodic-job", "rolling-update":
   233  		return "Job ID", eval.JobID
   234  	case "node-update":
   235  		return "Node ID", eval.NodeID
   236  	case "max-plan-attempts":
   237  		return "Previous Eval", eval.PreviousEval
   238  	default:
   239  		return "", ""
   240  	}
   241  }