diff --git a/api/util_test.go b/api/util_test.go index 408d93a54d0..30feafdd3f4 100644 --- a/api/util_test.go +++ b/api/util_test.go @@ -21,7 +21,12 @@ func assertWriteMeta(t *testing.T, wm *WriteMeta) { func testJob() *Job { task := NewTask("task1", "exec"). - Require(&Resources{MemoryMB: 256}) + Require(&Resources{ + CPU: 100, + MemoryMB: 256, + DiskMB: 25, + IOPS: 10, + }) group := NewTaskGroup("group1", 1). AddTask(task) diff --git a/command/util_test.go b/command/util_test.go index 5515a386552..c9bd741f6d0 100644 --- a/command/util_test.go +++ b/command/util_test.go @@ -40,7 +40,11 @@ func testServer( func testJob(jobID string) *api.Job { task := api.NewTask("task1", "exec"). - Require(&api.Resources{MemoryMB: 256}) + Require(&api.Resources{ + MemoryMB: 256, + DiskMB: 20, + CPU: 100, + }) group := api.NewTaskGroup("group1", 1). AddTask(task) diff --git a/jobspec/parse.go b/jobspec/parse.go index 8f69ee8b2e6..8623a7e4378 100644 --- a/jobspec/parse.go +++ b/jobspec/parse.go @@ -619,6 +619,10 @@ func parseResources(result *structs.Resources, list *ast.ObjectList) error { result.Networks = []*structs.NetworkResource{&r} } + // Combine the parsed resources with a default resource block. + min := structs.DefaultResources() + min.Merge(result) + *result = *min return nil } diff --git a/jobspec/parse_test.go b/jobspec/parse_test.go index 31a9edbbceb..e45203757a4 100644 --- a/jobspec/parse_test.go +++ b/jobspec/parse_test.go @@ -113,6 +113,8 @@ func TestParse(t *testing.T) { Resources: &structs.Resources{ CPU: 500, MemoryMB: 128, + DiskMB: 10, + IOPS: 1, Networks: []*structs.NetworkResource{ &structs.NetworkResource{ MBits: 100, @@ -132,6 +134,8 @@ func TestParse(t *testing.T) { Resources: &structs.Resources{ CPU: 500, MemoryMB: 128, + DiskMB: 10, + IOPS: 30, }, Constraints: []*structs.Constraint{ &structs.Constraint{ diff --git a/jobspec/test-fixtures/basic.hcl b/jobspec/test-fixtures/basic.hcl index 50e91d441ac..d7a313a3302 100644 --- a/jobspec/test-fixtures/basic.hcl +++ b/jobspec/test-fixtures/basic.hcl @@ -89,6 +89,7 @@ job "binstore-storagelocker" { resources { cpu = 500 memory = 128 + IOPS = 30 } constraint { attribute = "kernel.arch" diff --git a/nomad/mock/mock.go b/nomad/mock/mock.go index 13083588744..f6fa7b1bebb 100644 --- a/nomad/mock/mock.go +++ b/nomad/mock/mock.go @@ -106,6 +106,7 @@ func Job() *structs.Job { Resources: &structs.Resources{ CPU: 500, MemoryMB: 256, + DiskMB: 100, Networks: []*structs.NetworkResource{ &structs.NetworkResource{ MBits: 50, diff --git a/nomad/structs/structs.go b/nomad/structs/structs.go index 7f2820b6362..52e65fa8780 100644 --- a/nomad/structs/structs.go +++ b/nomad/structs/structs.go @@ -592,6 +592,60 @@ type Resources struct { Networks []*NetworkResource } +// DefaultResources returns the minimum resources a task can use and be valid. +func DefaultResources() *Resources { + return &Resources{ + CPU: 100, + MemoryMB: 10, + DiskMB: 10, + IOPS: 1, + } +} + +// Merge merges this resource with another resource. +func (r *Resources) Merge(other *Resources) { + if other.CPU != 0 { + r.CPU = other.CPU + } + if other.MemoryMB != 0 { + r.MemoryMB = other.MemoryMB + } + if other.DiskMB != 0 { + r.DiskMB = other.DiskMB + } + if other.IOPS != 0 { + r.IOPS = other.IOPS + } + if len(other.Networks) != 0 { + r.Networks = other.Networks + } +} + +// MeetsMinResources returns an error if the resources specified are less than +// the minimum allowed. +func (r *Resources) MeetsMinResources() error { + var mErr multierror.Error + if r.CPU < 100 { + mErr.Errors = append(mErr.Errors, fmt.Errorf("minimum CPU value is 100; got %d", r.CPU)) + } + if r.MemoryMB < 10 { + mErr.Errors = append(mErr.Errors, fmt.Errorf("minimum MemoryMB value is 10; got %d", r.MemoryMB)) + } + if r.DiskMB < 10 { + mErr.Errors = append(mErr.Errors, fmt.Errorf("minimum DiskMB value is 10; got %d", r.DiskMB)) + } + if r.IOPS < 0 { + mErr.Errors = append(mErr.Errors, fmt.Errorf("minimum IOPS value is 0; got %d", r.IOPS)) + } + for i, n := range r.Networks { + if err := n.MeetsMinResources(); err != nil { + mErr.Errors = append(mErr.Errors, fmt.Errorf("network resource at index %d failed: %v", i, err)) + } + } + + return mErr.ErrorOrNil() +} + // Copy returns a deep copy of the resources func (r *Resources) Copy() *Resources { newR := new(Resources) @@ -676,6 +730,16 @@ type NetworkResource struct { DynamicPorts []Port // Dynamically assigned ports } +// MeetsMinResources returns an error if the resources specified are less than +// the minimum allowed. +func (n *NetworkResource) MeetsMinResources() error { + var mErr multierror.Error + if n.MBits < 1 { + mErr.Errors = append(mErr.Errors, fmt.Errorf("minimum MBits value is 1; got %d", n.MBits)) + } + return mErr.ErrorOrNil() +} + // Copy returns a deep copy of the network resource func (n *NetworkResource) Copy() *NetworkResource { newR := new(NetworkResource) @@ -1545,12 +1609,17 @@ func (t *Task) Validate() error { if t.Driver == "" { mErr.Errors = append(mErr.Errors, errors.New("Missing task driver")) } - if t.Resources == nil { - mErr.Errors = append(mErr.Errors, errors.New("Missing task resources")) - } if t.KillTimeout.Nanoseconds() < 0 { mErr.Errors = append(mErr.Errors, errors.New("KillTimeout must be a positive value")) } + + // Validate the resources. + if t.Resources == nil { + mErr.Errors = append(mErr.Errors, errors.New("Missing task resources")) + } else if err := t.Resources.MeetsMinResources(); err != nil { + mErr.Errors = append(mErr.Errors, err) + } + for idx, constr := range t.Constraints { if err := constr.Validate(); err != nil { outer := fmt.Errorf("Constraint %d validation failed: %s", idx+1, err) diff --git a/nomad/structs/structs_test.go b/nomad/structs/structs_test.go index 22e5dfea914..25b54311894 100644 --- a/nomad/structs/structs_test.go +++ b/nomad/structs/structs_test.go @@ -252,9 +252,14 @@ func TestTask_Validate(t *testing.T) { } task = &Task{ - Name: "web", - Driver: "docker", - Resources: &Resources{}, + Name: "web", + Driver: "docker", + Resources: &Resources{ + CPU: 100, + DiskMB: 100, + MemoryMB: 100, + IOPS: 10, + }, } err = task.Validate() if err != nil {