mirror of
https://github.com/kemko/nomad.git
synced 2026-01-08 03:15:42 +03:00
command: start implementing eval monitoring for run
This commit is contained in:
118
command/monitor.go
Normal file
118
command/monitor.go
Normal 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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
11
commands.go
11
commands.go
@@ -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,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user