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