-
Notifications
You must be signed in to change notification settings - Fork 1.9k
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
Add CLI and API support for forcing rescheduling of failed allocs #4274
Changes from 10 commits
242cc19
3b7d23f
268a99e
1bad719
4f9d92c
2d0e273
b5e18b6
879c2c9
53c05c5
e2f13d2
b2006cc
ae5d8fd
2ea09b8
a5ca379
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -90,8 +90,25 @@ func (s *HTTPServer) jobForceEvaluate(resp http.ResponseWriter, req *http.Reques | |
if req.Method != "PUT" && req.Method != "POST" { | ||
return nil, CodedError(405, ErrInvalidMethod) | ||
} | ||
args := structs.JobEvaluateRequest{ | ||
JobID: jobName, | ||
var args structs.JobEvaluateRequest | ||
|
||
// TODO(preetha): remove in 0.9 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 0.10? i guess this code will go out in 0.9 :) |
||
// COMPAT: For backwards compatibility allow using this endpoint without a payload | ||
if req.ContentLength == 0 { | ||
args = structs.JobEvaluateRequest{ | ||
JobID: jobName, | ||
} | ||
} else { | ||
if err := decodeBody(req, &args); err != nil { | ||
return nil, CodedError(400, err.Error()) | ||
} | ||
if args.JobID == "" { | ||
return nil, CodedError(400, "Job ID must be specified") | ||
} | ||
|
||
if jobName != "" && args.JobID != jobName { | ||
return nil, CodedError(400, "JobID not same as job name") | ||
} | ||
} | ||
s.parseWriteRequest(req, &args.WriteRequest) | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,127 @@ | ||
package command | ||
|
||
import ( | ||
"fmt" | ||
"strings" | ||
|
||
"github.com/hashicorp/nomad/api" | ||
"github.com/hashicorp/nomad/api/contexts" | ||
"github.com/posener/complete" | ||
) | ||
|
||
type JobEvalCommand struct { | ||
Meta | ||
forceRescheduling bool | ||
} | ||
|
||
func (c *JobEvalCommand) Help() string { | ||
helpText := ` | ||
Usage: nomad job eval [options] <job_id> | ||
|
||
Force an evaluation of the provided job ID. Forcing an evaluation will trigger the scheduler | ||
to re-evaluate the job. The force flags allow operators to force the scheduler to create | ||
new allocations under certain scenarios. | ||
|
||
General Options: | ||
|
||
` + generalOptionsUsage() + ` | ||
|
||
Eval Options: | ||
|
||
-force-reschedule | ||
Force reschedule failed allocations even if they are not currently | ||
eligible for rescheduling. | ||
-detach | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Blank line above |
||
Return immediately instead of entering monitor mode. After deployment | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. After deployment resume? |
||
resume, 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. | ||
` | ||
return strings.TrimSpace(helpText) | ||
} | ||
|
||
func (c *JobEvalCommand) Synopsis() string { | ||
return "Force an evaluation for the job using its job ID" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Remove " using its job ID" |
||
} | ||
|
||
func (c *JobEvalCommand) AutocompleteFlags() complete.Flags { | ||
return mergeAutocompleteFlags(c.Meta.AutocompleteFlags(FlagSetClient), | ||
complete.Flags{ | ||
"-force-reschedule": complete.PredictNothing, | ||
"-detach": complete.PredictNothing, | ||
"-verbose": complete.PredictNothing, | ||
}) | ||
} | ||
|
||
func (c *JobEvalCommand) AutocompleteArgs() complete.Predictor { | ||
return complete.PredictFunc(func(a complete.Args) []string { | ||
client, err := c.Meta.Client() | ||
if err != nil { | ||
return nil | ||
} | ||
|
||
resp, _, err := client.Search().PrefixSearch(a.Last, contexts.Jobs, nil) | ||
if err != nil { | ||
return []string{} | ||
} | ||
return resp.Matches[contexts.Jobs] | ||
}) | ||
} | ||
|
||
func (c *JobEvalCommand) Name() string { return "job eval" } | ||
|
||
func (c *JobEvalCommand) Run(args []string) int { | ||
var detach, verbose bool | ||
|
||
flags := c.Meta.FlagSet(c.Name(), FlagSetClient) | ||
flags.Usage = func() { c.Ui.Output(c.Help()) } | ||
flags.BoolVar(&c.forceRescheduling, "force-reschedule", false, "") | ||
flags.BoolVar(&detach, "detach", false, "") | ||
flags.BoolVar(&verbose, "verbose", false, "") | ||
|
||
if err := flags.Parse(args); err != nil { | ||
return 1 | ||
} | ||
|
||
// Check that we either got no jobs or exactly one. | ||
args = flags.Args() | ||
if len(args) != 1 { | ||
c.Ui.Error("This command takes one argument: <job>") | ||
c.Ui.Error(commandErrorText(c)) | ||
return 1 | ||
} | ||
|
||
// 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 | ||
} | ||
// Call eval endpoint | ||
jobID := args[0] | ||
|
||
opts := api.EvalOptions{ | ||
ForceReschedule: c.forceRescheduling, | ||
} | ||
evalId, _, err := client.Jobs().ForceEvaluate(jobID, opts, nil) | ||
if err != nil { | ||
c.Ui.Error(fmt.Sprintf("Error evaluating job: %s", err)) | ||
return 1 | ||
} | ||
c.Ui.Output(fmt.Sprintf("Created eval ID: %q ", limit(evalId, length))) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This should be in the detach if |
||
if detach { | ||
return 0 | ||
} | ||
|
||
mon := newMonitor(c.Ui, client, length) | ||
return mon.monitor(evalId, false) | ||
return 0 | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Worth renaming to EvaluateWithOpts?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I modeled this after
JobDeregisterOptions
.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I meant the name of the function. Since it can be more than just force given it takes a generic options object