command: start implementing eval monitoring for run

This commit is contained in:
Ryan Uber
2015-09-16 13:58:33 -07:00
parent 6532abdf68
commit 3bb508c0b5
3 changed files with 150 additions and 7 deletions

118
command/monitor.go Normal file
View File

@@ -0,0 +1,118 @@
package command
import (
"fmt"
"sync"
"time"
"github.com/hashicorp/nomad/api"
"github.com/hashicorp/nomad/nomad/structs"
"github.com/mitchellh/cli"
)
const (
// dateFmt is the format we use when printing the date in
// status update messages during monitoring.
dateFmt = "2006/01/02 15:04:05"
)
// monitor wraps an evaluation monitor and holds metadata and
// state information.
type monitor struct {
ui cli.Ui
client *api.Client
state *monitorState
sync.Mutex
}
// newMonitor returns a new monitor. The returned monitor will
// write output information to the provided ui.
func newMonitor(ui cli.Ui, client *api.Client) *monitor {
return &monitor{
ui: ui,
client: client,
state: new(monitorState),
}
}
// output is used to write informational messages to the ui.
func (m *monitor) output(msg string) {
m.ui.Output(fmt.Sprintf("%s %s", time.Now().Format(dateFmt), msg))
}
// monitorState is used to store the current "state of the world"
// in the context of monitoring an evaluation.
type monitorState struct {
status string
nodeID string
wait time.Duration
}
// update is used to update our monitor with new state. It can be
// called whether the passed information is new or not, and will
// only dump update messages when state changes.
func (m *monitor) update(eval *api.Evaluation) {
m.Lock()
defer m.Unlock()
existing := m.state
// Create the new state
update := &monitorState{
status: eval.Status,
nodeID: eval.NodeID,
wait: eval.Wait,
}
defer func() { m.state = update }()
// Check if the status changed
if existing.status != update.status {
m.output(fmt.Sprintf("Evaluation changed status from %q to %q",
existing.status, eval.Status))
}
// Check if the wait time is different
if existing.wait == 0 && update.wait != 0 {
m.output(fmt.Sprintf("Waiting %s before running eval",
eval.Wait))
}
// Check if the nodeID changed
if existing.nodeID == "" && update.nodeID != "" {
m.output(fmt.Sprintf("Evaluation was assigned node ID %q",
eval.NodeID))
}
}
// monitor is used to start monitoring the given evaluation ID. It
// writes output directly to the monitor's ui, and returns the
// exit code for the command. The return code is 0 if monitoring
// succeeded and exited successfully, or 1 if an error was encountered
// or the eval status was returned as failed.
func (m *monitor) monitor(evalID string) int {
for {
// Check the current state of things
eval, _, err := m.client.Evaluations().Info(evalID, nil)
if err != nil {
m.ui.Error(fmt.Sprintf("Error reading evaluation: %s", err))
return 1
}
// Update the state
m.update(eval)
// Check if the eval is complete
switch eval.Status {
case structs.EvalStatusComplete:
return 0
case structs.EvalStatusFailed:
return 1
}
// Wait for the next poll
time.Sleep(time.Second)
}
return 0
}

View File

@@ -24,7 +24,16 @@ Usage: nomad run [options] <file>
General Options:
` + generalOptionsUsage()
` + generalOptionsUsage() + `
Run Options:
-monitor
On successful job completion, immediately begin monitoring the
evaluation created by the job registration. This mode will
enter an interactive session where status is printed to the
screen, similar to the "tail" UNIX command.
`
return strings.TrimSpace(helpText)
}
@@ -33,8 +42,12 @@ func (c *RunCommand) Synopsis() string {
}
func (c *RunCommand) Run(args []string) int {
var monitor bool
flags := c.Meta.FlagSet("run", FlagSetClient)
flags.Usage = func() { c.Ui.Output(c.Help()) }
flags.BoolVar(&monitor, "monitor", false, "")
if err := flags.Parse(args); err != nil {
return 1
}
@@ -75,9 +88,16 @@ func (c *RunCommand) Run(args []string) int {
return 1
}
c.Ui.Info("Job registered successfully!\n")
c.Ui.Info("JobID = " + job.ID)
c.Ui.Info("EvalID = " + evalID)
// Check if we should enter monitor mode
if monitor {
mon := newMonitor(c.Ui, client)
return mon.monitor(evalID)
}
// By default just print some info and return
c.Ui.Output("Job registered successfully!\n")
c.Ui.Output("JobID = " + job.ID)
c.Ui.Output("EvalID = " + evalID)
return 0
}

View File

@@ -17,9 +17,14 @@ func Commands(metaPtr *command.Meta) map[string]cli.CommandFactory {
meta := *metaPtr
if meta.Ui == nil {
meta.Ui = &cli.BasicUi{
Writer: os.Stdout,
ErrorWriter: os.Stderr,
meta.Ui = &cli.PrefixedUi{
InfoPrefix: "==> ",
OutputPrefix: "",
ErrorPrefix: "",
Ui: &cli.BasicUi{
Writer: os.Stdout,
ErrorWriter: os.Stderr,
},
}
}