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

Plan exit code map to whether allocations would change + bug fix #1502

Merged
merged 3 commits into from
Aug 3, 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
35 changes: 27 additions & 8 deletions command/plan.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,11 @@ Usage: nomad plan [options] <file>
If the job has specified the region, the -region flag and NOMAD_REGION
environment variable are overridden and the the job's region is used.

Plan will return one of the following exit codes:
* 0: No allocations created or destroyed.
* 1: Allocations created or destroyed.
* 255: Error determining plan results.

General Options:

` + generalOptionsUsage() + `
Expand Down Expand Up @@ -85,14 +90,14 @@ func (c *PlanCommand) Run(args []string) int {
flags.BoolVar(&verbose, "verbose", false, "")

if err := flags.Parse(args); err != nil {
return 1
return 255
}

// Check that we got exactly one job
args = flags.Args()
if len(args) != 1 {
c.Ui.Error(c.Help())
return 1
return 255
}

// Read the Jobfile
Expand All @@ -112,7 +117,7 @@ func (c *PlanCommand) Run(args []string) int {
defer file.Close()
if err != nil {
c.Ui.Error(fmt.Sprintf("Error opening file %q: %v", path, err))
return 1
return 255
}
f = file
}
Expand All @@ -121,7 +126,7 @@ func (c *PlanCommand) Run(args []string) int {
job, err := jobspec.Parse(f)
if err != nil {
c.Ui.Error(fmt.Sprintf("Error parsing job file %s: %v", path, err))
return 1
return 255
}

// Initialize any fields that need to be.
Expand All @@ -130,21 +135,21 @@ func (c *PlanCommand) Run(args []string) int {
// Check that the job is valid
if err := job.Validate(); err != nil {
c.Ui.Error(fmt.Sprintf("Error validating job: %s", err))
return 1
return 255
}

// Convert it to something we can use
apiJob, err := convertStructJob(job)
if err != nil {
c.Ui.Error(fmt.Sprintf("Error converting job: %s", err))
return 1
return 255
}

// Get the HTTP client
client, err := c.Meta.Client()
if err != nil {
c.Ui.Error(fmt.Sprintf("Error initializing client: %s", err))
return 1
return 255
}

// Force the region to be that of the job.
Expand All @@ -156,7 +161,7 @@ func (c *PlanCommand) Run(args []string) int {
resp, _, err := client.Jobs().Plan(apiJob, diff, nil)
if err != nil {
c.Ui.Error(fmt.Sprintf("Error during plan: %s", err))
return 1
return 255
}

// Print the diff if not disabled
Expand All @@ -172,6 +177,20 @@ func (c *PlanCommand) Run(args []string) int {

// Print the job index info
c.Ui.Output(c.Colorize().Color(formatJobModifyIndex(resp.JobModifyIndex, path)))
return getExitCode(resp)
}

// getExitCode returns 0:
// * 0: No allocations created or destroyed.
// * 1: Allocations created or destroyed.
func getExitCode(resp *api.JobPlanResponse) int {
// Check for changes
for _, d := range resp.Annotations.DesiredTGUpdates {
if d.Stop+d.Place+d.Migrate+d.DestructiveUpdate > 0 {
return 1
}
}

return 0
}

Expand Down
12 changes: 6 additions & 6 deletions command/plan_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ func TestPlanCommand_Fails(t *testing.T) {
cmd := &PlanCommand{Meta: Meta{Ui: ui}}

// Fails on misuse
if code := cmd.Run([]string{"some", "bad", "args"}); code != 1 {
if code := cmd.Run([]string{"some", "bad", "args"}); code != 255 {
t.Fatalf("expected exit code 1, got: %d", code)
}
if out := ui.ErrorWriter.String(); !strings.Contains(out, cmd.Help()) {
Expand All @@ -27,7 +27,7 @@ func TestPlanCommand_Fails(t *testing.T) {
ui.ErrorWriter.Reset()

// Fails when specified file does not exist
if code := cmd.Run([]string{"/unicorns/leprechauns"}); code != 1 {
if code := cmd.Run([]string{"/unicorns/leprechauns"}); code != 255 {
t.Fatalf("expect exit 1, got: %d", code)
}
if out := ui.ErrorWriter.String(); !strings.Contains(out, "Error opening") {
Expand All @@ -44,7 +44,7 @@ func TestPlanCommand_Fails(t *testing.T) {
if _, err := fh1.WriteString("nope"); err != nil {
t.Fatalf("err: %s", err)
}
if code := cmd.Run([]string{fh1.Name()}); code != 1 {
if code := cmd.Run([]string{fh1.Name()}); code != 255 {
t.Fatalf("expect exit 1, got: %d", code)
}
if out := ui.ErrorWriter.String(); !strings.Contains(out, "Error parsing") {
Expand All @@ -61,7 +61,7 @@ func TestPlanCommand_Fails(t *testing.T) {
if _, err := fh2.WriteString(`job "job1" {}`); err != nil {
t.Fatalf("err: %s", err)
}
if code := cmd.Run([]string{fh2.Name()}); code != 1 {
if code := cmd.Run([]string{fh2.Name()}); code != 255 {
t.Fatalf("expect exit 1, got: %d", code)
}
if out := ui.ErrorWriter.String(); !strings.Contains(out, "Error validating") {
Expand Down Expand Up @@ -94,7 +94,7 @@ job "job1" {
if err != nil {
t.Fatalf("err: %s", err)
}
if code := cmd.Run([]string{"-address=nope", fh3.Name()}); code != 1 {
if code := cmd.Run([]string{"-address=nope", fh3.Name()}); code != 255 {
t.Fatalf("expected exit code 1, got: %d", code)
}
if out := ui.ErrorWriter.String(); !strings.Contains(out, "Error during plan") {
Expand Down Expand Up @@ -135,7 +135,7 @@ job "job1" {
}()

args := []string{"-"}
if code := cmd.Run(args); code != 1 {
if code := cmd.Run(args); code != 255 {
t.Fatalf("expected exit code 1, got %d: %q", code, ui.ErrorWriter.String())
}

Expand Down
29 changes: 19 additions & 10 deletions scheduler/annotate.go
Original file line number Diff line number Diff line change
Expand Up @@ -159,24 +159,33 @@ func annotateTask(diff *structs.TaskDiff, parent *structs.TaskGroupDiff) {
}
}

// All changes to primitive fields result in a destructive update.
// All changes to primitive fields result in a destructive update except
// KillTimeout
destructive := false
if len(diff.Fields) != 0 {
destructive = true
}

// Changes that can be done in-place are log configs, services and
// constraints.
for _, oDiff := range diff.Objects {
switch oDiff.Name {
case "LogConfig", "Service", "Constraint":
for _, fDiff := range diff.Fields {
switch fDiff.Name {
case "KillTimeout":
continue
default:
destructive = true
break
}
}

// Object changes that can be done in-place are log configs, services,
// constraints.
if !destructive {
for _, oDiff := range diff.Objects {
switch oDiff.Name {
case "LogConfig", "Service", "Constraint":
continue
default:
destructive = true
break
}
}
}

if destructive {
diff.Annotations = append(diff.Annotations, AnnotationForcesDestructiveUpdate)
} else {
Expand Down
15 changes: 15 additions & 0 deletions scheduler/annotate_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -347,6 +347,21 @@ func TestAnnotateTask(t *testing.T) {
Parent: &structs.TaskGroupDiff{Type: structs.DiffTypeEdited},
Desired: AnnotationForcesInplaceUpdate,
},
{
Diff: &structs.TaskDiff{
Type: structs.DiffTypeEdited,
Fields: []*structs.FieldDiff{
{
Type: structs.DiffTypeEdited,
Name: "KillTimeout",
Old: "200",
New: "2000000",
},
},
},
Parent: &structs.TaskGroupDiff{Type: structs.DiffTypeEdited},
Desired: AnnotationForcesInplaceUpdate,
},
// Task deleted new parent
{
Diff: &structs.TaskDiff{
Expand Down
6 changes: 6 additions & 0 deletions website/source/docs/commands/plan.html.md.erb
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,12 @@ give insight into what the scheduler will attempt to do and why.
If the job has specified the region, the `-region` flag and `NOMAD_REGION`
environment variable are overridden and the the job's region is used.

Plan will return one of the following exit codes:

* 0: No allocations created or destroyed.
* 1: Allocations created or destroyed.
* 255: Error determining plan results.

## General Options

<%= general_options_usage %>
Expand Down