mirror of
https://github.com/kemko/nomad.git
synced 2026-01-06 10:25:42 +03:00
Add eval-status and remove eval-monitor
This commit is contained in:
184
command/eval_status.go
Normal file
184
command/eval_status.go
Normal file
@@ -0,0 +1,184 @@
|
||||
package command
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/hashicorp/nomad/api"
|
||||
)
|
||||
|
||||
type EvalStatusCommand struct {
|
||||
Meta
|
||||
}
|
||||
|
||||
func (c *EvalStatusCommand) Help() string {
|
||||
helpText := `
|
||||
Usage: nomad eval-status [options] <evaluation-id>
|
||||
|
||||
Display information about evaluations. This command can be used to inspect the
|
||||
current status of an evaluation as well as determine the reason an evaluation
|
||||
did not place all allocations.
|
||||
|
||||
General Options:
|
||||
|
||||
` + generalOptionsUsage() + `
|
||||
|
||||
Eval Status Options:
|
||||
|
||||
-monitor
|
||||
Monitor an outstanding evaluation
|
||||
|
||||
-verbose
|
||||
Show full information.
|
||||
`
|
||||
|
||||
return strings.TrimSpace(helpText)
|
||||
}
|
||||
|
||||
func (c *EvalStatusCommand) Synopsis() string {
|
||||
return "Display evaluation status information and placement failure reasons"
|
||||
}
|
||||
|
||||
func (c *EvalStatusCommand) Run(args []string) int {
|
||||
var monitor, verbose bool
|
||||
|
||||
flags := c.Meta.FlagSet("eval-status", FlagSetClient)
|
||||
flags.Usage = func() { c.Ui.Output(c.Help()) }
|
||||
flags.BoolVar(&monitor, "monitor", false, "")
|
||||
flags.BoolVar(&verbose, "verbose", false, "")
|
||||
|
||||
if err := flags.Parse(args); err != nil {
|
||||
return 1
|
||||
}
|
||||
|
||||
// Check that we got exactly one evaluation ID
|
||||
args = flags.Args()
|
||||
if len(args) != 1 {
|
||||
c.Ui.Error(c.Help())
|
||||
return 1
|
||||
}
|
||||
evalID := args[0]
|
||||
|
||||
// Get the HTTP client
|
||||
client, err := c.Meta.Client()
|
||||
if err != nil {
|
||||
c.Ui.Error(fmt.Sprintf("Error initializing client: %s", err))
|
||||
return 1
|
||||
}
|
||||
|
||||
// Truncate the id unless full length is requested
|
||||
length := shortId
|
||||
if verbose {
|
||||
length = fullId
|
||||
}
|
||||
|
||||
// Query the allocation info
|
||||
if len(evalID) == 1 {
|
||||
c.Ui.Error(fmt.Sprintf("Identifier must contain at least two characters."))
|
||||
return 1
|
||||
}
|
||||
if len(evalID)%2 == 1 {
|
||||
// Identifiers must be of even length, so we strip off the last byte
|
||||
// to provide a consistent user experience.
|
||||
evalID = evalID[:len(evalID)-1]
|
||||
}
|
||||
|
||||
evals, _, err := client.Evaluations().PrefixList(evalID)
|
||||
if err != nil {
|
||||
c.Ui.Error(fmt.Sprintf("Error querying evaluation: %v", err))
|
||||
return 1
|
||||
}
|
||||
if len(evals) == 0 {
|
||||
c.Ui.Error(fmt.Sprintf("No evaluation(s) with prefix or id %q found", evalID))
|
||||
return 1
|
||||
}
|
||||
if len(evals) > 1 {
|
||||
// Format the evals
|
||||
out := make([]string, len(evals)+1)
|
||||
out[0] = "ID|Priority|Triggered By|Status|Placement Failures"
|
||||
for i, eval := range evals {
|
||||
out[i+1] = fmt.Sprintf("%s|%d|%s|%s|%t",
|
||||
limit(eval.ID, length),
|
||||
eval.Priority,
|
||||
eval.TriggeredBy,
|
||||
eval.Status,
|
||||
len(eval.FailedTGAllocs) != 0,
|
||||
)
|
||||
}
|
||||
c.Ui.Output(fmt.Sprintf("Prefix matched multiple evaluations\n\n%s", formatList(out)))
|
||||
return 0
|
||||
}
|
||||
|
||||
// If we are in monitor mode, monitor and exit
|
||||
if monitor {
|
||||
mon := newMonitor(c.Ui, client, length)
|
||||
return mon.monitor(evals[0].ID, true)
|
||||
}
|
||||
|
||||
// Prefix lookup matched a single evaluation
|
||||
eval, _, err := client.Evaluations().Info(evals[0].ID, nil)
|
||||
if err != nil {
|
||||
c.Ui.Error(fmt.Sprintf("Error querying evaluation: %s", err))
|
||||
return 1
|
||||
}
|
||||
|
||||
failures := len(eval.FailedTGAllocs) != 0
|
||||
triggerNoun, triggerSubj := getTriggerDetails(eval)
|
||||
statusDesc := eval.StatusDescription
|
||||
if statusDesc == "" {
|
||||
statusDesc = eval.Status
|
||||
}
|
||||
|
||||
// Format the allocation data
|
||||
basic := []string{
|
||||
fmt.Sprintf("ID|%s", limit(eval.ID, length)),
|
||||
fmt.Sprintf("Status|%s", eval.Status),
|
||||
fmt.Sprintf("Status Description|%s", statusDesc),
|
||||
fmt.Sprintf("Type|%s", eval.Type),
|
||||
fmt.Sprintf("TriggeredBy|%s", eval.TriggeredBy),
|
||||
fmt.Sprintf("%s|%s", triggerNoun, triggerSubj),
|
||||
fmt.Sprintf("Priority|%d", eval.Priority),
|
||||
fmt.Sprintf("Placement Failures|%t", failures),
|
||||
}
|
||||
|
||||
if verbose {
|
||||
// NextEval, PreviousEval, BlockedEval
|
||||
basic = append(basic,
|
||||
fmt.Sprintf("Previous Eval|%s", eval.PreviousEval),
|
||||
fmt.Sprintf("Next Eval|%s", eval.NextEval),
|
||||
fmt.Sprintf("Blocked Eval|%s", eval.BlockedEval))
|
||||
}
|
||||
c.Ui.Output(formatKV(basic))
|
||||
|
||||
if failures {
|
||||
c.Ui.Output("\n==> Failed Allocations")
|
||||
for tg, metrics := range eval.FailedTGAllocs {
|
||||
noun := "allocation"
|
||||
if metrics.CoalescedFailures > 0 {
|
||||
noun += "s"
|
||||
}
|
||||
c.Ui.Output(fmt.Sprintf("Task Group %q (failed to place %d %s):", tg, metrics.CoalescedFailures+1, noun))
|
||||
dumpAllocMetrics(c.Ui, metrics, false)
|
||||
}
|
||||
|
||||
if eval.BlockedEval != "" {
|
||||
c.Ui.Output(fmt.Sprintf("\nEvaluation %q waiting for additional capacity to place remainder",
|
||||
limit(eval.BlockedEval, length)))
|
||||
}
|
||||
}
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
func getTriggerDetails(eval *api.Evaluation) (noun, subject string) {
|
||||
switch eval.TriggeredBy {
|
||||
case "job-register", "job-deregister", "periodic-job", "rolling-update":
|
||||
return "Job ID", eval.JobID
|
||||
case "node-update":
|
||||
return "Node ID", eval.NodeID
|
||||
case "max-plan-attempts":
|
||||
return "Previous Eval", eval.PreviousEval
|
||||
default:
|
||||
return "", ""
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user