github.com/djenriquez/nomad-1@v0.8.1/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 = sanitizeUUIDPrefix(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  	}
   211  
   212  	if triggerNoun != "" && triggerSubj != "" {
   213  		basic = append(basic, fmt.Sprintf("%s|%s", triggerNoun, triggerSubj))
   214  	}
   215  
   216  	basic = append(basic,
   217  		fmt.Sprintf("Priority|%d", eval.Priority),
   218  		fmt.Sprintf("Placement Failures|%s", failureString))
   219  
   220  	if !eval.WaitUntil.IsZero() {
   221  		basic = append(basic,
   222  			fmt.Sprintf("Wait Until|%s", formatTime(eval.WaitUntil)))
   223  	}
   224  
   225  	if verbose {
   226  		// NextEval, PreviousEval, BlockedEval
   227  		basic = append(basic,
   228  			fmt.Sprintf("Previous Eval|%s", eval.PreviousEval),
   229  			fmt.Sprintf("Next Eval|%s", eval.NextEval),
   230  			fmt.Sprintf("Blocked Eval|%s", eval.BlockedEval))
   231  	}
   232  	c.Ui.Output(formatKV(basic))
   233  
   234  	if failures {
   235  		c.Ui.Output(c.Colorize().Color("\n[bold]Failed Placements[reset]"))
   236  		sorted := sortedTaskGroupFromMetrics(eval.FailedTGAllocs)
   237  		for _, tg := range sorted {
   238  			metrics := eval.FailedTGAllocs[tg]
   239  
   240  			noun := "allocation"
   241  			if metrics.CoalescedFailures > 0 {
   242  				noun += "s"
   243  			}
   244  			c.Ui.Output(fmt.Sprintf("Task Group %q (failed to place %d %s):", tg, metrics.CoalescedFailures+1, noun))
   245  			c.Ui.Output(formatAllocMetrics(metrics, false, "  "))
   246  			c.Ui.Output("")
   247  		}
   248  
   249  		if eval.BlockedEval != "" {
   250  			c.Ui.Output(fmt.Sprintf("Evaluation %q waiting for additional capacity to place remainder",
   251  				limit(eval.BlockedEval, length)))
   252  		}
   253  	}
   254  
   255  	return 0
   256  }
   257  
   258  func sortedTaskGroupFromMetrics(groups map[string]*api.AllocationMetric) []string {
   259  	tgs := make([]string, 0, len(groups))
   260  	for tg := range groups {
   261  		tgs = append(tgs, tg)
   262  	}
   263  	sort.Strings(tgs)
   264  	return tgs
   265  }
   266  
   267  func getTriggerDetails(eval *api.Evaluation) (noun, subject string) {
   268  	switch eval.TriggeredBy {
   269  	case "job-register", "job-deregister", "periodic-job", "rolling-update", "deployment-watcher":
   270  		return "Job ID", eval.JobID
   271  	case "node-update":
   272  		return "Node ID", eval.NodeID
   273  	case "max-plan-attempts":
   274  		return "Previous Eval", eval.PreviousEval
   275  	default:
   276  		return "", ""
   277  	}
   278  }