Skip to content

Commit

Permalink
Merge pull request #1502 from hashicorp/f-plan-exit-code
Browse files Browse the repository at this point in the history
Plan exit code map to whether allocations would change + bug fix
  • Loading branch information
dadgar authored Aug 3, 2016
2 parents 0cd511c + 7ee4880 commit 2c48b7b
Show file tree
Hide file tree
Showing 5 changed files with 73 additions and 24 deletions.
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

0 comments on commit 2c48b7b

Please sign in to comment.