github.com/diptanu/nomad@v0.5.7-0.20170516172507-d72e86cbe3d9/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 {
    76  		var format string
    77  		if json && len(tmpl) > 0 {
    78  			c.Ui.Error("Both -json and -t are not allowed")
    79  			return 1
    80  		} else if json {
    81  			format = "json"
    82  		} else if len(tmpl) > 0 {
    83  			format = "template"
    84  		}
    85  		if len(format) > 0 {
    86  			evals, _, err := client.Evaluations().List(nil)
    87  			if err != nil {
    88  				c.Ui.Error(fmt.Sprintf("Error querying evaluations: %v", err))
    89  				return 1
    90  			}
    91  			// Return nothing if no evaluations found
    92  			if len(evals) == 0 {
    93  				return 0
    94  			}
    95  
    96  			f, err := DataFormat(format, tmpl)
    97  			if err != nil {
    98  				c.Ui.Error(fmt.Sprintf("Error getting formatter: %s", err))
    99  				return 1
   100  			}
   101  
   102  			out, err := f.TransformData(evals)
   103  			if err != nil {
   104  				c.Ui.Error(fmt.Sprintf("Error formatting the data: %s", err))
   105  				return 1
   106  			}
   107  			c.Ui.Output(out)
   108  			return 0
   109  		}
   110  	}
   111  
   112  	if len(args) != 1 {
   113  		c.Ui.Error(c.Help())
   114  		return 1
   115  	}
   116  
   117  	evalID := args[0]
   118  
   119  	// Truncate the id unless full length is requested
   120  	length := shortId
   121  	if verbose {
   122  		length = fullId
   123  	}
   124  
   125  	// Query the allocation info
   126  	if len(evalID) == 1 {
   127  		c.Ui.Error(fmt.Sprintf("Identifier must contain at least two characters."))
   128  		return 1
   129  	}
   130  	if len(evalID)%2 == 1 {
   131  		// Identifiers must be of even length, so we strip off the last byte
   132  		// to provide a consistent user experience.
   133  		evalID = evalID[:len(evalID)-1]
   134  	}
   135  
   136  	evals, _, err := client.Evaluations().PrefixList(evalID)
   137  	if err != nil {
   138  		c.Ui.Error(fmt.Sprintf("Error querying evaluation: %v", err))
   139  		return 1
   140  	}
   141  	if len(evals) == 0 {
   142  		c.Ui.Error(fmt.Sprintf("No evaluation(s) with prefix or id %q found", evalID))
   143  		return 1
   144  	}
   145  
   146  	if len(evals) > 1 {
   147  		// Format the evals
   148  		out := make([]string, len(evals)+1)
   149  		out[0] = "ID|Priority|Triggered By|Status|Placement Failures"
   150  		for i, eval := range evals {
   151  			failures, _ := evalFailureStatus(eval)
   152  			out[i+1] = fmt.Sprintf("%s|%d|%s|%s|%s",
   153  				limit(eval.ID, length),
   154  				eval.Priority,
   155  				eval.TriggeredBy,
   156  				eval.Status,
   157  				failures,
   158  			)
   159  		}
   160  		c.Ui.Output(fmt.Sprintf("Prefix matched multiple evaluations\n\n%s", formatList(out)))
   161  		return 0
   162  	}
   163  
   164  	// If we are in monitor mode, monitor and exit
   165  	if monitor {
   166  		mon := newMonitor(c.Ui, client, length)
   167  		return mon.monitor(evals[0].ID, true)
   168  	}
   169  
   170  	// Prefix lookup matched a single evaluation
   171  	eval, _, err := client.Evaluations().Info(evals[0].ID, nil)
   172  	if err != nil {
   173  		c.Ui.Error(fmt.Sprintf("Error querying evaluation: %s", err))
   174  		return 1
   175  	}
   176  
   177  	// If output format is specified, format and output the data
   178  	var format string
   179  	if json {
   180  		format = "json"
   181  	} else if len(tmpl) > 0 {
   182  		format = "template"
   183  	}
   184  	if len(format) > 0 {
   185  		f, err := DataFormat(format, tmpl)
   186  		if err != nil {
   187  			c.Ui.Error(fmt.Sprintf("Error getting formatter: %s", err))
   188  			return 1
   189  		}
   190  
   191  		out, err := f.TransformData(eval)
   192  		if err != nil {
   193  			c.Ui.Error(fmt.Sprintf("Error formatting the data: %s", err))
   194  			return 1
   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 the evaluation data
   208  	basic := []string{
   209  		fmt.Sprintf("ID|%s", limit(eval.ID, length)),
   210  		fmt.Sprintf("Status|%s", eval.Status),
   211  		fmt.Sprintf("Status Description|%s", statusDesc),
   212  		fmt.Sprintf("Type|%s", eval.Type),
   213  		fmt.Sprintf("TriggeredBy|%s", eval.TriggeredBy),
   214  		fmt.Sprintf("%s|%s", triggerNoun, triggerSubj),
   215  		fmt.Sprintf("Priority|%d", eval.Priority),
   216  		fmt.Sprintf("Placement Failures|%s", failureString),
   217  	}
   218  
   219  	if verbose {
   220  		// NextEval, PreviousEval, BlockedEval
   221  		basic = append(basic,
   222  			fmt.Sprintf("Previous Eval|%s", eval.PreviousEval),
   223  			fmt.Sprintf("Next Eval|%s", eval.NextEval),
   224  			fmt.Sprintf("Blocked Eval|%s", eval.BlockedEval))
   225  	}
   226  	c.Ui.Output(formatKV(basic))
   227  
   228  	if failures {
   229  		c.Ui.Output(c.Colorize().Color("\n[bold]Failed Placements[reset]"))
   230  		sorted := sortedTaskGroupFromMetrics(eval.FailedTGAllocs)
   231  		for _, tg := range sorted {
   232  			metrics := eval.FailedTGAllocs[tg]
   233  
   234  			noun := "allocation"
   235  			if metrics.CoalescedFailures > 0 {
   236  				noun += "s"
   237  			}
   238  			c.Ui.Output(fmt.Sprintf("Task Group %q (failed to place %d %s):", tg, metrics.CoalescedFailures+1, noun))
   239  			c.Ui.Output(formatAllocMetrics(metrics, false, "  "))
   240  			c.Ui.Output("")
   241  		}
   242  
   243  		if eval.BlockedEval != "" {
   244  			c.Ui.Output(fmt.Sprintf("Evaluation %q waiting for additional capacity to place remainder",
   245  				limit(eval.BlockedEval, length)))
   246  		}
   247  	}
   248  
   249  	return 0
   250  }
   251  
   252  func sortedTaskGroupFromMetrics(groups map[string]*api.AllocationMetric) []string {
   253  	tgs := make([]string, 0, len(groups))
   254  	for tg, _ := range groups {
   255  		tgs = append(tgs, tg)
   256  	}
   257  	sort.Strings(tgs)
   258  	return tgs
   259  }
   260  
   261  func getTriggerDetails(eval *api.Evaluation) (noun, subject string) {
   262  	switch eval.TriggeredBy {
   263  	case "job-register", "job-deregister", "periodic-job", "rolling-update":
   264  		return "Job ID", eval.JobID
   265  	case "node-update":
   266  		return "Node ID", eval.NodeID
   267  	case "max-plan-attempts":
   268  		return "Previous Eval", eval.PreviousEval
   269  	default:
   270  		return "", ""
   271  	}
   272  }