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