Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Introduce eval-status and deprecate eval-monitor #1206

Merged
merged 7 commits into from
May 31, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions command/alloc_status.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ General Options:

` + generalOptionsUsage() + `

Alloc Status Options:

-short
Display short output. Shows only the most recent task event.
Expand Down
81 changes: 0 additions & 81 deletions command/eval_monitor.go

This file was deleted.

198 changes: 198 additions & 0 deletions command/eval_status.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
package command

import (
"fmt"
"sort"
"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 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 evaluation 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 Placements")
sorted := sortedTaskGroupFromMetrics(eval.FailedTGAllocs)
for _, tg := range sorted {
metrics := eval.FailedTGAllocs[tg]

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)
c.Ui.Output("")
}

if eval.BlockedEval != "" {
c.Ui.Output(fmt.Sprintf("Evaluation %q waiting for additional capacity to place remainder",
limit(eval.BlockedEval, length)))
}
}

return 0
}

func sortedTaskGroupFromMetrics(groups map[string]*api.AllocationMetric) []string {
tgs := make([]string, 0, len(groups))
for tg, _ := range groups {
tgs = append(tgs, tg)
}
sort.Strings(tgs)
return tgs
}

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 "", ""
}
}
10 changes: 5 additions & 5 deletions command/eval_monitor_test.go → command/eval_status_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,16 @@ import (
"github.com/mitchellh/cli"
)

func TestEvalMonitorCommand_Implements(t *testing.T) {
var _ cli.Command = &EvalMonitorCommand{}
func TestEvalStatusCommand_Implements(t *testing.T) {
var _ cli.Command = &EvalStatusCommand{}
}

func TestEvalMonitorCommand_Fails(t *testing.T) {
func TestEvalStatusCommand_Fails(t *testing.T) {
srv, _, url := testServer(t, nil)
defer srv.Stop()

ui := new(cli.MockUi)
cmd := &EvalMonitorCommand{Meta: Meta{Ui: ui}}
cmd := &EvalStatusCommand{Meta: Meta{Ui: ui}}

// Fails on misuse
if code := cmd.Run([]string{"some", "bad", "args"}); code != 1 {
Expand All @@ -40,7 +40,7 @@ func TestEvalMonitorCommand_Fails(t *testing.T) {
if code := cmd.Run([]string{"-address=nope", "12345678-abcd-efab-cdef-123456789abc"}); code != 1 {
t.Fatalf("expected exit code 1, got: %d", code)
}
if out := ui.ErrorWriter.String(); !strings.Contains(out, "Error reading evaluation") {
if out := ui.ErrorWriter.String(); !strings.Contains(out, "Error querying evaluation") {
t.Fatalf("expected failed query error, got: %s", out)
}
}
2 changes: 2 additions & 0 deletions command/fs.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ General Options:

` + generalOptionsUsage() + `

FS Specific Options:

-H
Machine friendly output.

Expand Down
2 changes: 1 addition & 1 deletion command/plan.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ General Options:

` + generalOptionsUsage() + `

Run Options:
Plan Options:

-diff
Defaults to true, but can be toggled off to omit diff output.
Expand Down
7 changes: 3 additions & 4 deletions command/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,10 +44,9 @@ General Options:
Run Options:

-detach
Return immediately instead of entering monitor mode. After job
submission, the evaluation ID will be printed to the screen.
You can use this ID to start a monitor using the eval-monitor
command later if needed.
Return immediately instead of entering monitor mode. After job submission,
the evaluation ID will be printed to the screen, which can be used to
examine the evaluation using the eval-status command.

-verbose
Display full information.
Expand Down
2 changes: 1 addition & 1 deletion command/server_members.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ General Options:

` + generalOptionsUsage() + `

Agent Members Options:
Server Members Options:

-detailed
Show detailed information about each member. This dumps
Expand Down
Loading