github.com/ferranbt/nomad@v0.9.3-0.20190607002617-85c449b7667c/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) Name() string { return "eval status" }
    81  
    82  func (c *EvalStatusCommand) Run(args []string) int {
    83  	var monitor, verbose, json bool
    84  	var tmpl string
    85  
    86  	flags := c.Meta.FlagSet(c.Name(), FlagSetClient)
    87  	flags.Usage = func() { c.Ui.Output(c.Help()) }
    88  	flags.BoolVar(&monitor, "monitor", false, "")
    89  	flags.BoolVar(&verbose, "verbose", false, "")
    90  	flags.BoolVar(&json, "json", false, "")
    91  	flags.StringVar(&tmpl, "t", "", "")
    92  
    93  	if err := flags.Parse(args); err != nil {
    94  		return 1
    95  	}
    96  
    97  	// Check that we got exactly one evaluation ID
    98  	args = flags.Args()
    99  
   100  	// Get the HTTP client
   101  	client, err := c.Meta.Client()
   102  	if err != nil {
   103  		c.Ui.Error(fmt.Sprintf("Error initializing client: %s", err))
   104  		return 1
   105  	}
   106  
   107  	// If args not specified but output format is specified, format and output the evaluations data list
   108  	if len(args) == 0 && json || len(tmpl) > 0 {
   109  		evals, _, err := client.Evaluations().List(nil)
   110  		if err != nil {
   111  			c.Ui.Error(fmt.Sprintf("Error querying evaluations: %v", err))
   112  			return 1
   113  		}
   114  
   115  		out, err := Format(json, tmpl, evals)
   116  		if err != nil {
   117  			c.Ui.Error(err.Error())
   118  			return 1
   119  		}
   120  
   121  		c.Ui.Output(out)
   122  		return 0
   123  	}
   124  
   125  	if len(args) != 1 {
   126  		c.Ui.Error("This command takes one argument")
   127  		c.Ui.Error(commandErrorText(c))
   128  		return 1
   129  	}
   130  
   131  	evalID := args[0]
   132  
   133  	// Truncate the id unless full length is requested
   134  	length := shortId
   135  	if verbose {
   136  		length = fullId
   137  	}
   138  
   139  	// Query the allocation info
   140  	if len(evalID) == 1 {
   141  		c.Ui.Error(fmt.Sprintf("Identifier must contain at least two characters."))
   142  		return 1
   143  	}
   144  
   145  	evalID = sanitizeUUIDPrefix(evalID)
   146  	evals, _, err := client.Evaluations().PrefixList(evalID)
   147  	if err != nil {
   148  		c.Ui.Error(fmt.Sprintf("Error querying evaluation: %v", err))
   149  		return 1
   150  	}
   151  	if len(evals) == 0 {
   152  		c.Ui.Error(fmt.Sprintf("No evaluation(s) with prefix or id %q found", evalID))
   153  		return 1
   154  	}
   155  
   156  	if len(evals) > 1 {
   157  		// Format the evals
   158  		out := make([]string, len(evals)+1)
   159  		out[0] = "ID|Priority|Triggered By|Status|Placement Failures"
   160  		for i, eval := range evals {
   161  			failures, _ := evalFailureStatus(eval)
   162  			out[i+1] = fmt.Sprintf("%s|%d|%s|%s|%s",
   163  				limit(eval.ID, length),
   164  				eval.Priority,
   165  				eval.TriggeredBy,
   166  				eval.Status,
   167  				failures,
   168  			)
   169  		}
   170  		c.Ui.Error(fmt.Sprintf("Prefix matched multiple evaluations\n\n%s", formatList(out)))
   171  		return 1
   172  	}
   173  
   174  	// If we are in monitor mode, monitor and exit
   175  	if monitor {
   176  		mon := newMonitor(c.Ui, client, length)
   177  		return mon.monitor(evals[0].ID, true)
   178  	}
   179  
   180  	// Prefix lookup matched a single evaluation
   181  	eval, _, err := client.Evaluations().Info(evals[0].ID, nil)
   182  	if err != nil {
   183  		c.Ui.Error(fmt.Sprintf("Error querying evaluation: %s", err))
   184  		return 1
   185  	}
   186  
   187  	// If output format is specified, format and output the data
   188  	if json || len(tmpl) > 0 {
   189  		out, err := Format(json, tmpl, eval)
   190  		if err != nil {
   191  			c.Ui.Error(err.Error())
   192  			return 1
   193  		}
   194  
   195  		c.Ui.Output(out)
   196  		return 0
   197  	}
   198  
   199  	failureString, failures := evalFailureStatus(eval)
   200  	triggerNoun, triggerSubj := getTriggerDetails(eval)
   201  	statusDesc := eval.StatusDescription
   202  	if statusDesc == "" {
   203  		statusDesc = eval.Status
   204  	}
   205  
   206  	// Format the evaluation data
   207  	basic := []string{
   208  		fmt.Sprintf("ID|%s", limit(eval.ID, length)),
   209  		fmt.Sprintf("Status|%s", eval.Status),
   210  		fmt.Sprintf("Status Description|%s", statusDesc),
   211  		fmt.Sprintf("Type|%s", eval.Type),
   212  		fmt.Sprintf("TriggeredBy|%s", eval.TriggeredBy),
   213  	}
   214  
   215  	if triggerNoun != "" && triggerSubj != "" {
   216  		basic = append(basic, fmt.Sprintf("%s|%s", triggerNoun, triggerSubj))
   217  	}
   218  
   219  	basic = append(basic,
   220  		fmt.Sprintf("Priority|%d", eval.Priority),
   221  		fmt.Sprintf("Placement Failures|%s", failureString))
   222  
   223  	if !eval.WaitUntil.IsZero() {
   224  		basic = append(basic,
   225  			fmt.Sprintf("Wait Until|%s", formatTime(eval.WaitUntil)))
   226  	}
   227  
   228  	if verbose {
   229  		// NextEval, PreviousEval, BlockedEval
   230  		basic = append(basic,
   231  			fmt.Sprintf("Previous Eval|%s", eval.PreviousEval),
   232  			fmt.Sprintf("Next Eval|%s", eval.NextEval),
   233  			fmt.Sprintf("Blocked Eval|%s", eval.BlockedEval))
   234  	}
   235  	c.Ui.Output(formatKV(basic))
   236  
   237  	if failures {
   238  		c.Ui.Output(c.Colorize().Color("\n[bold]Failed Placements[reset]"))
   239  		sorted := sortedTaskGroupFromMetrics(eval.FailedTGAllocs)
   240  		for _, tg := range sorted {
   241  			metrics := eval.FailedTGAllocs[tg]
   242  
   243  			noun := "allocation"
   244  			if metrics.CoalescedFailures > 0 {
   245  				noun += "s"
   246  			}
   247  			c.Ui.Output(fmt.Sprintf("Task Group %q (failed to place %d %s):", tg, metrics.CoalescedFailures+1, noun))
   248  			c.Ui.Output(formatAllocMetrics(metrics, false, "  "))
   249  			c.Ui.Output("")
   250  		}
   251  
   252  		if eval.BlockedEval != "" {
   253  			c.Ui.Output(fmt.Sprintf("Evaluation %q waiting for additional capacity to place remainder",
   254  				limit(eval.BlockedEval, length)))
   255  		}
   256  	}
   257  
   258  	return 0
   259  }
   260  
   261  func sortedTaskGroupFromMetrics(groups map[string]*api.AllocationMetric) []string {
   262  	tgs := make([]string, 0, len(groups))
   263  	for tg := range groups {
   264  		tgs = append(tgs, tg)
   265  	}
   266  	sort.Strings(tgs)
   267  	return tgs
   268  }
   269  
   270  func getTriggerDetails(eval *api.Evaluation) (noun, subject string) {
   271  	switch eval.TriggeredBy {
   272  	case "job-register", "job-deregister", "periodic-job", "rolling-update", "deployment-watcher":
   273  		return "Job ID", eval.JobID
   274  	case "node-update":
   275  		return "Node ID", eval.NodeID
   276  	case "max-plan-attempts":
   277  		return "Previous Eval", eval.PreviousEval
   278  	default:
   279  		return "", ""
   280  	}
   281  }