nomad: adding job validation

This commit is contained in:
Armon Dadgar 2015-09-15 10:46:10 -07:00
parent cd50f9112b
commit 3ccf1d98d7

View file

@ -2,10 +2,12 @@ package structs
import ( import (
"bytes" "bytes"
"errors"
"fmt" "fmt"
"time" "time"
"github.com/hashicorp/go-msgpack/codec" "github.com/hashicorp/go-msgpack/codec"
"github.com/hashicorp/go-multierror"
) )
var ( var (
@ -679,8 +681,11 @@ const (
// is further composed of tasks. A task group (TG) is the unit of scheduling // is further composed of tasks. A task group (TG) is the unit of scheduling
// however. // however.
type Job struct { type Job struct {
// ID is a unique identifier for the job. It can be the same as // Region is the Nomad region that handles scheduling this job
// the job name, or alternatively a UUID may be used. Region string
// ID is a unique identifier for the job per region. It can be
// specified hierarchically like LineOfBiz/OrgName/Team/Project
ID string ID string
// Name is the logical name of the job used to refer to it. This is unique // Name is the logical name of the job used to refer to it. This is unique
@ -705,9 +710,6 @@ type Job struct {
// Datacenters contains all the datacenters this job is allowed to span // Datacenters contains all the datacenters this job is allowed to span
Datacenters []string Datacenters []string
// Region is the Nomad region that handles scheduling this job
Region string
// Constraints can be specified at a job level and apply to // Constraints can be specified at a job level and apply to
// all the task groups and tasks. // all the task groups and tasks.
Constraints []*Constraint Constraints []*Constraint
@ -734,6 +736,50 @@ type Job struct {
ModifyIndex uint64 ModifyIndex uint64
} }
// Validate is used to sanity check a job input
func (j *Job) Validate() error {
var mErr multierror.Error
if j.Region == "" {
mErr.Errors = append(mErr.Errors, errors.New("Missing job region"))
}
if j.ID == "" {
mErr.Errors = append(mErr.Errors, errors.New("Missing job ID"))
}
if j.Name == "" {
mErr.Errors = append(mErr.Errors, errors.New("Missing job name"))
}
if j.Type == "" {
mErr.Errors = append(mErr.Errors, errors.New("Missing job type"))
}
if j.Priority < JobMinPriority || j.Priority > JobMaxPriority {
mErr.Errors = append(mErr.Errors, fmt.Errorf("Job priority must be between [%d, %d]", JobMinPriority, JobMaxPriority))
}
if len(j.Datacenters) == 0 {
mErr.Errors = append(mErr.Errors, errors.New("Missing job datacenters"))
}
if len(j.TaskGroups) == 0 {
mErr.Errors = append(mErr.Errors, errors.New("Missing job task groups"))
}
// Check for duplicate task groups
taskGroups := make(map[string]int)
for idx, tg := range j.TaskGroups {
if tg.Name == "" {
mErr.Errors = append(mErr.Errors, fmt.Errorf("Job task group %d missing name", idx+1))
} else if existing, ok := taskGroups[tg.Name]; ok {
mErr.Errors = append(mErr.Errors, fmt.Errorf("Job task group %d redefines '%s' from group %d", idx+1, tg.Name, existing+1))
} else {
taskGroups[tg.Name] = idx
}
// Validate the task group
if err := tg.Validate(); err != nil {
mErr.Errors = append(mErr.Errors, err)
}
}
return mErr.ErrorOrNil()
}
// LookupTaskGroup finds a task group by name // LookupTaskGroup finds a task group by name
func (j *Job) LookupTaskGroup(name string) *TaskGroup { func (j *Job) LookupTaskGroup(name string) *TaskGroup {
for _, tg := range j.TaskGroups { for _, tg := range j.TaskGroups {
@ -808,6 +854,38 @@ type TaskGroup struct {
Meta map[string]string Meta map[string]string
} }
// Validate is used to sanity check a task group
func (tg *TaskGroup) Validate() error {
var mErr multierror.Error
if tg.Name == "" {
mErr.Errors = append(mErr.Errors, errors.New("Missing task group name"))
}
if tg.Count < 0 {
mErr.Errors = append(mErr.Errors, errors.New("Task group count cannot be negative"))
}
if len(tg.Tasks) == 0 {
mErr.Errors = append(mErr.Errors, errors.New("Missing tasks for task group"))
}
// Check for duplicate tasks
tasks := make(map[string]int)
for idx, task := range tg.Tasks {
if task.Name == "" {
mErr.Errors = append(mErr.Errors, fmt.Errorf("Task %d missing name", idx+1))
} else if existing, ok := tasks[task.Name]; ok {
mErr.Errors = append(mErr.Errors, fmt.Errorf("Task %d redefines '%s' from task %d", idx+1, task.Name, existing+1))
} else {
tasks[task.Name] = idx
}
// Validate the tasks
if err := task.Validate(); err != nil {
mErr.Errors = append(mErr.Errors, err)
}
}
return mErr.ErrorOrNil()
}
// LookupTask finds a task by name // LookupTask finds a task by name
func (tg *TaskGroup) LookupTask(name string) *Task { func (tg *TaskGroup) LookupTask(name string) *Task {
for _, t := range tg.Tasks { for _, t := range tg.Tasks {
@ -849,6 +927,21 @@ func (t *Task) GoString() string {
return fmt.Sprintf("*%#v", *t) return fmt.Sprintf("*%#v", *t)
} }
// Validate is used to sanity check a task group
func (t *Task) Validate() error {
var mErr multierror.Error
if t.Name == "" {
mErr.Errors = append(mErr.Errors, errors.New("Missing task name"))
}
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"))
}
return mErr.ErrorOrNil()
}
// Constraints are used to restrict placement options in the case of // Constraints are used to restrict placement options in the case of
// a hard constraint, and used to prefer a placement in the case of // a hard constraint, and used to prefer a placement in the case of
// a soft constraint. // a soft constraint.