diff --git a/jobspec/parse.go b/jobspec/parse.go index 37e96b0b0..5d791835d 100644 --- a/jobspec/parse.go +++ b/jobspec/parse.go @@ -6,6 +6,7 @@ import ( "io" "os" "path/filepath" + "time" "github.com/hashicorp/hcl" hclobj "github.com/hashicorp/hcl/hcl" @@ -76,6 +77,7 @@ func parseJob(result *structs.Job, obj *hclobj.Object) error { } delete(m, "constraint") delete(m, "meta") + delete(m, "update") // Set the ID and name to the object key result.ID = obj.Key @@ -98,6 +100,13 @@ func parseJob(result *structs.Job, obj *hclobj.Object) error { } } + // If we have an update strategy, then parse that + if o := obj.Get("update", false); o != nil { + if err := parseUpdate(&result.Update, o); err != nil { + return err + } + } + // Parse out meta fields. These are in HCL as a list so we need // to iterate over them and merge them. if metaO := obj.Get("meta", false); metaO != nil { @@ -221,6 +230,11 @@ func parseConstraints(result *[]*structs.Constraint, obj *hclobj.Object) error { m["RTarget"] = m["value"] m["Operand"] = m["operator"] + // Default constraint to being hard + if _, ok := m["hard"]; !ok { + m["hard"] = true + } + // Build the constraint var c structs.Constraint if err := mapstructure.WeakDecode(m, &c); err != nil { @@ -362,3 +376,38 @@ func parseResources(result *structs.Resources, obj *hclobj.Object) error { return nil } + +func parseUpdate(result *structs.UpdateStrategy, obj *hclobj.Object) error { + if obj.Len() > 1 { + return fmt.Errorf("only one 'update' block allowed per job") + } + + for _, o := range obj.Elem(false) { + var m map[string]interface{} + if err := hcl.DecodeObject(&m, o); err != nil { + return err + } + for _, key := range []string{"stagger", "Stagger"} { + if raw, ok := m[key]; ok { + switch v := raw.(type) { + case string: + dur, err := time.ParseDuration(v) + if err != nil { + return fmt.Errorf("invalid stagger time '%s'", raw) + } + m[key] = dur + case int: + m[key] = time.Duration(v) * time.Second + default: + return fmt.Errorf("invalid type for stagger time '%s'", + raw) + } + } + } + + if err := mapstructure.WeakDecode(m, result); err != nil { + return err + } + } + return nil +} diff --git a/jobspec/parse_test.go b/jobspec/parse_test.go index 93eca1330..6f4589344 100644 --- a/jobspec/parse_test.go +++ b/jobspec/parse_test.go @@ -4,6 +4,7 @@ import ( "path/filepath" "reflect" "testing" + "time" "github.com/hashicorp/nomad/nomad/structs" ) @@ -31,12 +32,18 @@ func TestParse(t *testing.T) { Constraints: []*structs.Constraint{ &structs.Constraint{ + Hard: true, LTarget: "kernel.os", RTarget: "windows", Operand: "=", }, }, + Update: structs.UpdateStrategy{ + Stagger: 60 * time.Second, + MaxParallel: 2, + }, + TaskGroups: []*structs.TaskGroup{ &structs.TaskGroup{ Name: "outside", @@ -60,6 +67,7 @@ func TestParse(t *testing.T) { Count: 5, Constraints: []*structs.Constraint{ &structs.Constraint{ + Hard: true, LTarget: "kernel.os", RTarget: "linux", Operand: "=", @@ -101,6 +109,7 @@ func TestParse(t *testing.T) { }, Constraints: []*structs.Constraint{ &structs.Constraint{ + Hard: true, LTarget: "kernel.arch", RTarget: "amd64", Operand: "=", diff --git a/jobspec/test-fixtures/basic.hcl b/jobspec/test-fixtures/basic.hcl index bc107ecc4..e81f1e9bf 100644 --- a/jobspec/test-fixtures/basic.hcl +++ b/jobspec/test-fixtures/basic.hcl @@ -14,6 +14,11 @@ job "binstore-storagelocker" { value = "windows" } + update { + stagger = "60s" + max_parallel = 2 + } + task "outside" { driver = "java" config {