github.com/blixtra/nomad@v0.7.2-0.20171221000451-da9a1d7bb050/api/jobs.go (about)

     1  package api
     2  
     3  import (
     4  	"fmt"
     5  	"net/url"
     6  	"sort"
     7  	"strconv"
     8  	"time"
     9  
    10  	"github.com/gorhill/cronexpr"
    11  	"github.com/hashicorp/nomad/helper"
    12  )
    13  
    14  const (
    15  	// JobTypeService indicates a long-running processes
    16  	JobTypeService = "service"
    17  
    18  	// JobTypeBatch indicates a short-lived process
    19  	JobTypeBatch = "batch"
    20  
    21  	// PeriodicSpecCron is used for a cron spec.
    22  	PeriodicSpecCron = "cron"
    23  
    24  	// DefaultNamespace is the default namespace.
    25  	DefaultNamespace = "default"
    26  )
    27  
    28  const (
    29  	// RegisterEnforceIndexErrPrefix is the prefix to use in errors caused by
    30  	// enforcing the job modify index during registers.
    31  	RegisterEnforceIndexErrPrefix = "Enforcing job modify index"
    32  )
    33  
    34  // Jobs is used to access the job-specific endpoints.
    35  type Jobs struct {
    36  	client *Client
    37  }
    38  
    39  // Jobs returns a handle on the jobs endpoints.
    40  func (c *Client) Jobs() *Jobs {
    41  	return &Jobs{client: c}
    42  }
    43  
    44  func (j *Jobs) Validate(job *Job, q *WriteOptions) (*JobValidateResponse, *WriteMeta, error) {
    45  	var resp JobValidateResponse
    46  	req := &JobValidateRequest{Job: job}
    47  	if q != nil {
    48  		req.WriteRequest = WriteRequest{Region: q.Region}
    49  	}
    50  	wm, err := j.client.write("/v1/validate/job", req, &resp, q)
    51  	return &resp, wm, err
    52  }
    53  
    54  // RegisterOptions is used to pass through job registration parameters
    55  type RegisterOptions struct {
    56  	EnforceIndex   bool
    57  	ModifyIndex    uint64
    58  	PolicyOverride bool
    59  }
    60  
    61  // Register is used to register a new job. It returns the ID
    62  // of the evaluation, along with any errors encountered.
    63  func (j *Jobs) Register(job *Job, q *WriteOptions) (*JobRegisterResponse, *WriteMeta, error) {
    64  	return j.RegisterOpts(job, nil, q)
    65  }
    66  
    67  // EnforceRegister is used to register a job enforcing its job modify index.
    68  func (j *Jobs) EnforceRegister(job *Job, modifyIndex uint64, q *WriteOptions) (*JobRegisterResponse, *WriteMeta, error) {
    69  	opts := RegisterOptions{EnforceIndex: true, ModifyIndex: modifyIndex}
    70  	return j.RegisterOpts(job, &opts, q)
    71  }
    72  
    73  // Register is used to register a new job. It returns the ID
    74  // of the evaluation, along with any errors encountered.
    75  func (j *Jobs) RegisterOpts(job *Job, opts *RegisterOptions, q *WriteOptions) (*JobRegisterResponse, *WriteMeta, error) {
    76  	// Format the request
    77  	req := &RegisterJobRequest{
    78  		Job: job,
    79  	}
    80  	if opts != nil {
    81  		if opts.EnforceIndex {
    82  			req.EnforceIndex = true
    83  			req.JobModifyIndex = opts.ModifyIndex
    84  		}
    85  		if opts.PolicyOverride {
    86  			req.PolicyOverride = true
    87  		}
    88  	}
    89  
    90  	var resp JobRegisterResponse
    91  	wm, err := j.client.write("/v1/jobs", req, &resp, q)
    92  	if err != nil {
    93  		return nil, nil, err
    94  	}
    95  	return &resp, wm, nil
    96  }
    97  
    98  // List is used to list all of the existing jobs.
    99  func (j *Jobs) List(q *QueryOptions) ([]*JobListStub, *QueryMeta, error) {
   100  	var resp []*JobListStub
   101  	qm, err := j.client.query("/v1/jobs", &resp, q)
   102  	if err != nil {
   103  		return nil, qm, err
   104  	}
   105  	sort.Sort(JobIDSort(resp))
   106  	return resp, qm, nil
   107  }
   108  
   109  // PrefixList is used to list all existing jobs that match the prefix.
   110  func (j *Jobs) PrefixList(prefix string) ([]*JobListStub, *QueryMeta, error) {
   111  	return j.List(&QueryOptions{Prefix: prefix})
   112  }
   113  
   114  // Info is used to retrieve information about a particular
   115  // job given its unique ID.
   116  func (j *Jobs) Info(jobID string, q *QueryOptions) (*Job, *QueryMeta, error) {
   117  	var resp Job
   118  	qm, err := j.client.query("/v1/job/"+jobID, &resp, q)
   119  	if err != nil {
   120  		return nil, nil, err
   121  	}
   122  	return &resp, qm, nil
   123  }
   124  
   125  // Versions is used to retrieve all versions of a particular job given its
   126  // unique ID.
   127  func (j *Jobs) Versions(jobID string, diffs bool, q *QueryOptions) ([]*Job, []*JobDiff, *QueryMeta, error) {
   128  	var resp JobVersionsResponse
   129  	qm, err := j.client.query(fmt.Sprintf("/v1/job/%s/versions?diffs=%v", jobID, diffs), &resp, q)
   130  	if err != nil {
   131  		return nil, nil, nil, err
   132  	}
   133  	return resp.Versions, resp.Diffs, qm, nil
   134  }
   135  
   136  // Allocations is used to return the allocs for a given job ID.
   137  func (j *Jobs) Allocations(jobID string, allAllocs bool, q *QueryOptions) ([]*AllocationListStub, *QueryMeta, error) {
   138  	var resp []*AllocationListStub
   139  	u, err := url.Parse("/v1/job/" + jobID + "/allocations")
   140  	if err != nil {
   141  		return nil, nil, err
   142  	}
   143  
   144  	v := u.Query()
   145  	v.Add("all", strconv.FormatBool(allAllocs))
   146  	u.RawQuery = v.Encode()
   147  
   148  	qm, err := j.client.query(u.String(), &resp, q)
   149  	if err != nil {
   150  		return nil, nil, err
   151  	}
   152  	sort.Sort(AllocIndexSort(resp))
   153  	return resp, qm, nil
   154  }
   155  
   156  // Deployments is used to query the deployments associated with the given job
   157  // ID.
   158  func (j *Jobs) Deployments(jobID string, q *QueryOptions) ([]*Deployment, *QueryMeta, error) {
   159  	var resp []*Deployment
   160  	qm, err := j.client.query("/v1/job/"+jobID+"/deployments", &resp, q)
   161  	if err != nil {
   162  		return nil, nil, err
   163  	}
   164  	sort.Sort(DeploymentIndexSort(resp))
   165  	return resp, qm, nil
   166  }
   167  
   168  // LatestDeployment is used to query for the latest deployment associated with
   169  // the given job ID.
   170  func (j *Jobs) LatestDeployment(jobID string, q *QueryOptions) (*Deployment, *QueryMeta, error) {
   171  	var resp *Deployment
   172  	qm, err := j.client.query("/v1/job/"+jobID+"/deployment", &resp, q)
   173  	if err != nil {
   174  		return nil, nil, err
   175  	}
   176  	return resp, qm, nil
   177  }
   178  
   179  // Evaluations is used to query the evaluations associated with the given job
   180  // ID.
   181  func (j *Jobs) Evaluations(jobID string, q *QueryOptions) ([]*Evaluation, *QueryMeta, error) {
   182  	var resp []*Evaluation
   183  	qm, err := j.client.query("/v1/job/"+jobID+"/evaluations", &resp, q)
   184  	if err != nil {
   185  		return nil, nil, err
   186  	}
   187  	sort.Sort(EvalIndexSort(resp))
   188  	return resp, qm, nil
   189  }
   190  
   191  // Deregister is used to remove an existing job. If purge is set to true, the job
   192  // is deregistered and purged from the system versus still being queryable and
   193  // eventually GC'ed from the system. Most callers should not specify purge.
   194  func (j *Jobs) Deregister(jobID string, purge bool, q *WriteOptions) (string, *WriteMeta, error) {
   195  	var resp JobDeregisterResponse
   196  	wm, err := j.client.delete(fmt.Sprintf("/v1/job/%v?purge=%t", jobID, purge), &resp, q)
   197  	if err != nil {
   198  		return "", nil, err
   199  	}
   200  	return resp.EvalID, wm, nil
   201  }
   202  
   203  // ForceEvaluate is used to force-evaluate an existing job.
   204  func (j *Jobs) ForceEvaluate(jobID string, q *WriteOptions) (string, *WriteMeta, error) {
   205  	var resp JobRegisterResponse
   206  	wm, err := j.client.write("/v1/job/"+jobID+"/evaluate", nil, &resp, q)
   207  	if err != nil {
   208  		return "", nil, err
   209  	}
   210  	return resp.EvalID, wm, nil
   211  }
   212  
   213  // PeriodicForce spawns a new instance of the periodic job and returns the eval ID
   214  func (j *Jobs) PeriodicForce(jobID string, q *WriteOptions) (string, *WriteMeta, error) {
   215  	var resp periodicForceResponse
   216  	wm, err := j.client.write("/v1/job/"+jobID+"/periodic/force", nil, &resp, q)
   217  	if err != nil {
   218  		return "", nil, err
   219  	}
   220  	return resp.EvalID, wm, nil
   221  }
   222  
   223  // PlanOptions is used to pass through job planning parameters
   224  type PlanOptions struct {
   225  	Diff           bool
   226  	PolicyOverride bool
   227  }
   228  
   229  func (j *Jobs) Plan(job *Job, diff bool, q *WriteOptions) (*JobPlanResponse, *WriteMeta, error) {
   230  	opts := PlanOptions{Diff: diff}
   231  	return j.PlanOpts(job, &opts, q)
   232  }
   233  
   234  func (j *Jobs) PlanOpts(job *Job, opts *PlanOptions, q *WriteOptions) (*JobPlanResponse, *WriteMeta, error) {
   235  	if job == nil {
   236  		return nil, nil, fmt.Errorf("must pass non-nil job")
   237  	}
   238  
   239  	// Setup the request
   240  	req := &JobPlanRequest{
   241  		Job: job,
   242  	}
   243  	if opts != nil {
   244  		req.Diff = opts.Diff
   245  		req.PolicyOverride = opts.PolicyOverride
   246  	}
   247  
   248  	var resp JobPlanResponse
   249  	wm, err := j.client.write("/v1/job/"+*job.ID+"/plan", req, &resp, q)
   250  	if err != nil {
   251  		return nil, nil, err
   252  	}
   253  	return &resp, wm, nil
   254  }
   255  
   256  func (j *Jobs) Summary(jobID string, q *QueryOptions) (*JobSummary, *QueryMeta, error) {
   257  	var resp JobSummary
   258  	qm, err := j.client.query("/v1/job/"+jobID+"/summary", &resp, q)
   259  	if err != nil {
   260  		return nil, nil, err
   261  	}
   262  	return &resp, qm, nil
   263  }
   264  
   265  func (j *Jobs) Dispatch(jobID string, meta map[string]string,
   266  	payload []byte, q *WriteOptions) (*JobDispatchResponse, *WriteMeta, error) {
   267  	var resp JobDispatchResponse
   268  	req := &JobDispatchRequest{
   269  		JobID:   jobID,
   270  		Meta:    meta,
   271  		Payload: payload,
   272  	}
   273  	wm, err := j.client.write("/v1/job/"+jobID+"/dispatch", req, &resp, q)
   274  	if err != nil {
   275  		return nil, nil, err
   276  	}
   277  	return &resp, wm, nil
   278  }
   279  
   280  // Revert is used to revert the given job to the passed version. If
   281  // enforceVersion is set, the job is only reverted if the current version is at
   282  // the passed version.
   283  func (j *Jobs) Revert(jobID string, version uint64, enforcePriorVersion *uint64,
   284  	q *WriteOptions) (*JobRegisterResponse, *WriteMeta, error) {
   285  
   286  	var resp JobRegisterResponse
   287  	req := &JobRevertRequest{
   288  		JobID:               jobID,
   289  		JobVersion:          version,
   290  		EnforcePriorVersion: enforcePriorVersion,
   291  	}
   292  	wm, err := j.client.write("/v1/job/"+jobID+"/revert", req, &resp, q)
   293  	if err != nil {
   294  		return nil, nil, err
   295  	}
   296  	return &resp, wm, nil
   297  }
   298  
   299  // Stable is used to mark a job version's stability.
   300  func (j *Jobs) Stable(jobID string, version uint64, stable bool,
   301  	q *WriteOptions) (*JobStabilityResponse, *WriteMeta, error) {
   302  
   303  	var resp JobStabilityResponse
   304  	req := &JobStabilityRequest{
   305  		JobID:      jobID,
   306  		JobVersion: version,
   307  		Stable:     stable,
   308  	}
   309  	wm, err := j.client.write("/v1/job/"+jobID+"/stable", req, &resp, q)
   310  	if err != nil {
   311  		return nil, nil, err
   312  	}
   313  	return &resp, wm, nil
   314  }
   315  
   316  // periodicForceResponse is used to deserialize a force response
   317  type periodicForceResponse struct {
   318  	EvalID string
   319  }
   320  
   321  // UpdateStrategy defines a task groups update strategy.
   322  type UpdateStrategy struct {
   323  	Stagger         *time.Duration `mapstructure:"stagger"`
   324  	MaxParallel     *int           `mapstructure:"max_parallel"`
   325  	HealthCheck     *string        `mapstructure:"health_check"`
   326  	MinHealthyTime  *time.Duration `mapstructure:"min_healthy_time"`
   327  	HealthyDeadline *time.Duration `mapstructure:"healthy_deadline"`
   328  	AutoRevert      *bool          `mapstructure:"auto_revert"`
   329  	Canary          *int           `mapstructure:"canary"`
   330  }
   331  
   332  // DefaultUpdateStrategy provides a baseline that can be used to upgrade
   333  // jobs with the old policy or for populating field defaults.
   334  func DefaultUpdateStrategy() *UpdateStrategy {
   335  	return &UpdateStrategy{
   336  		Stagger:         helper.TimeToPtr(30 * time.Second),
   337  		MaxParallel:     helper.IntToPtr(1),
   338  		HealthCheck:     helper.StringToPtr("checks"),
   339  		MinHealthyTime:  helper.TimeToPtr(10 * time.Second),
   340  		HealthyDeadline: helper.TimeToPtr(5 * time.Minute),
   341  		AutoRevert:      helper.BoolToPtr(false),
   342  		Canary:          helper.IntToPtr(0),
   343  	}
   344  }
   345  
   346  func (u *UpdateStrategy) Copy() *UpdateStrategy {
   347  	if u == nil {
   348  		return nil
   349  	}
   350  
   351  	copy := new(UpdateStrategy)
   352  
   353  	if u.Stagger != nil {
   354  		copy.Stagger = helper.TimeToPtr(*u.Stagger)
   355  	}
   356  
   357  	if u.MaxParallel != nil {
   358  		copy.MaxParallel = helper.IntToPtr(*u.MaxParallel)
   359  	}
   360  
   361  	if u.HealthCheck != nil {
   362  		copy.HealthCheck = helper.StringToPtr(*u.HealthCheck)
   363  	}
   364  
   365  	if u.MinHealthyTime != nil {
   366  		copy.MinHealthyTime = helper.TimeToPtr(*u.MinHealthyTime)
   367  	}
   368  
   369  	if u.HealthyDeadline != nil {
   370  		copy.HealthyDeadline = helper.TimeToPtr(*u.HealthyDeadline)
   371  	}
   372  
   373  	if u.AutoRevert != nil {
   374  		copy.AutoRevert = helper.BoolToPtr(*u.AutoRevert)
   375  	}
   376  
   377  	if u.Canary != nil {
   378  		copy.Canary = helper.IntToPtr(*u.Canary)
   379  	}
   380  
   381  	return copy
   382  }
   383  
   384  func (u *UpdateStrategy) Merge(o *UpdateStrategy) {
   385  	if o == nil {
   386  		return
   387  	}
   388  
   389  	if o.Stagger != nil {
   390  		u.Stagger = helper.TimeToPtr(*o.Stagger)
   391  	}
   392  
   393  	if o.MaxParallel != nil {
   394  		u.MaxParallel = helper.IntToPtr(*o.MaxParallel)
   395  	}
   396  
   397  	if o.HealthCheck != nil {
   398  		u.HealthCheck = helper.StringToPtr(*o.HealthCheck)
   399  	}
   400  
   401  	if o.MinHealthyTime != nil {
   402  		u.MinHealthyTime = helper.TimeToPtr(*o.MinHealthyTime)
   403  	}
   404  
   405  	if o.HealthyDeadline != nil {
   406  		u.HealthyDeadline = helper.TimeToPtr(*o.HealthyDeadline)
   407  	}
   408  
   409  	if o.AutoRevert != nil {
   410  		u.AutoRevert = helper.BoolToPtr(*o.AutoRevert)
   411  	}
   412  
   413  	if o.Canary != nil {
   414  		u.Canary = helper.IntToPtr(*o.Canary)
   415  	}
   416  }
   417  
   418  func (u *UpdateStrategy) Canonicalize() {
   419  	d := DefaultUpdateStrategy()
   420  
   421  	if u.MaxParallel == nil {
   422  		u.MaxParallel = d.MaxParallel
   423  	}
   424  
   425  	if u.Stagger == nil {
   426  		u.Stagger = d.Stagger
   427  	}
   428  
   429  	if u.HealthCheck == nil {
   430  		u.HealthCheck = d.HealthCheck
   431  	}
   432  
   433  	if u.HealthyDeadline == nil {
   434  		u.HealthyDeadline = d.HealthyDeadline
   435  	}
   436  
   437  	if u.MinHealthyTime == nil {
   438  		u.MinHealthyTime = d.MinHealthyTime
   439  	}
   440  
   441  	if u.AutoRevert == nil {
   442  		u.AutoRevert = d.AutoRevert
   443  	}
   444  
   445  	if u.Canary == nil {
   446  		u.Canary = d.Canary
   447  	}
   448  }
   449  
   450  // Empty returns whether the UpdateStrategy is empty or has user defined values.
   451  func (u *UpdateStrategy) Empty() bool {
   452  	if u == nil {
   453  		return true
   454  	}
   455  
   456  	if u.Stagger != nil && *u.Stagger != 0 {
   457  		return false
   458  	}
   459  
   460  	if u.MaxParallel != nil && *u.MaxParallel != 0 {
   461  		return false
   462  	}
   463  
   464  	if u.HealthCheck != nil && *u.HealthCheck != "" {
   465  		return false
   466  	}
   467  
   468  	if u.MinHealthyTime != nil && *u.MinHealthyTime != 0 {
   469  		return false
   470  	}
   471  
   472  	if u.HealthyDeadline != nil && *u.HealthyDeadline != 0 {
   473  		return false
   474  	}
   475  
   476  	if u.AutoRevert != nil && *u.AutoRevert {
   477  		return false
   478  	}
   479  
   480  	if u.Canary != nil && *u.Canary != 0 {
   481  		return false
   482  	}
   483  
   484  	return true
   485  }
   486  
   487  // PeriodicConfig is for serializing periodic config for a job.
   488  type PeriodicConfig struct {
   489  	Enabled         *bool
   490  	Spec            *string
   491  	SpecType        *string
   492  	ProhibitOverlap *bool   `mapstructure:"prohibit_overlap"`
   493  	TimeZone        *string `mapstructure:"time_zone"`
   494  }
   495  
   496  func (p *PeriodicConfig) Canonicalize() {
   497  	if p.Enabled == nil {
   498  		p.Enabled = helper.BoolToPtr(true)
   499  	}
   500  	if p.Spec == nil {
   501  		p.Spec = helper.StringToPtr("")
   502  	}
   503  	if p.SpecType == nil {
   504  		p.SpecType = helper.StringToPtr(PeriodicSpecCron)
   505  	}
   506  	if p.ProhibitOverlap == nil {
   507  		p.ProhibitOverlap = helper.BoolToPtr(false)
   508  	}
   509  	if p.TimeZone == nil || *p.TimeZone == "" {
   510  		p.TimeZone = helper.StringToPtr("UTC")
   511  	}
   512  }
   513  
   514  // Next returns the closest time instant matching the spec that is after the
   515  // passed time. If no matching instance exists, the zero value of time.Time is
   516  // returned. The `time.Location` of the returned value matches that of the
   517  // passed time.
   518  func (p *PeriodicConfig) Next(fromTime time.Time) time.Time {
   519  	if *p.SpecType == PeriodicSpecCron {
   520  		if e, err := cronexpr.Parse(*p.Spec); err == nil {
   521  			return e.Next(fromTime)
   522  		}
   523  	}
   524  
   525  	return time.Time{}
   526  }
   527  
   528  func (p *PeriodicConfig) GetLocation() (*time.Location, error) {
   529  	if p.TimeZone == nil || *p.TimeZone == "" {
   530  		return time.UTC, nil
   531  	}
   532  
   533  	return time.LoadLocation(*p.TimeZone)
   534  }
   535  
   536  // ParameterizedJobConfig is used to configure the parameterized job.
   537  type ParameterizedJobConfig struct {
   538  	Payload      string
   539  	MetaRequired []string `mapstructure:"meta_required"`
   540  	MetaOptional []string `mapstructure:"meta_optional"`
   541  }
   542  
   543  // Job is used to serialize a job.
   544  type Job struct {
   545  	Stop              *bool
   546  	Region            *string
   547  	Namespace         *string
   548  	ID                *string
   549  	ParentID          *string
   550  	Name              *string
   551  	Type              *string
   552  	Priority          *int
   553  	AllAtOnce         *bool `mapstructure:"all_at_once"`
   554  	Datacenters       []string
   555  	Constraints       []*Constraint
   556  	TaskGroups        []*TaskGroup
   557  	Update            *UpdateStrategy
   558  	Periodic          *PeriodicConfig
   559  	ParameterizedJob  *ParameterizedJobConfig
   560  	Payload           []byte
   561  	Meta              map[string]string
   562  	VaultToken        *string `mapstructure:"vault_token"`
   563  	Status            *string
   564  	StatusDescription *string
   565  	Stable            *bool
   566  	Version           *uint64
   567  	SubmitTime        *int64
   568  	CreateIndex       *uint64
   569  	ModifyIndex       *uint64
   570  	JobModifyIndex    *uint64
   571  }
   572  
   573  // IsPeriodic returns whether a job is periodic.
   574  func (j *Job) IsPeriodic() bool {
   575  	return j.Periodic != nil
   576  }
   577  
   578  // IsParameterized returns whether a job is parameterized job.
   579  func (j *Job) IsParameterized() bool {
   580  	return j.ParameterizedJob != nil
   581  }
   582  
   583  func (j *Job) Canonicalize() {
   584  	if j.ID == nil {
   585  		j.ID = helper.StringToPtr("")
   586  	}
   587  	if j.Name == nil {
   588  		j.Name = helper.StringToPtr(*j.ID)
   589  	}
   590  	if j.ParentID == nil {
   591  		j.ParentID = helper.StringToPtr("")
   592  	}
   593  	if j.Namespace == nil {
   594  		j.Namespace = helper.StringToPtr(DefaultNamespace)
   595  	}
   596  	if j.Priority == nil {
   597  		j.Priority = helper.IntToPtr(50)
   598  	}
   599  	if j.Stop == nil {
   600  		j.Stop = helper.BoolToPtr(false)
   601  	}
   602  	if j.Region == nil {
   603  		j.Region = helper.StringToPtr("global")
   604  	}
   605  	if j.Namespace == nil {
   606  		j.Namespace = helper.StringToPtr("default")
   607  	}
   608  	if j.Type == nil {
   609  		j.Type = helper.StringToPtr("service")
   610  	}
   611  	if j.AllAtOnce == nil {
   612  		j.AllAtOnce = helper.BoolToPtr(false)
   613  	}
   614  	if j.VaultToken == nil {
   615  		j.VaultToken = helper.StringToPtr("")
   616  	}
   617  	if j.Status == nil {
   618  		j.Status = helper.StringToPtr("")
   619  	}
   620  	if j.StatusDescription == nil {
   621  		j.StatusDescription = helper.StringToPtr("")
   622  	}
   623  	if j.Stable == nil {
   624  		j.Stable = helper.BoolToPtr(false)
   625  	}
   626  	if j.Version == nil {
   627  		j.Version = helper.Uint64ToPtr(0)
   628  	}
   629  	if j.CreateIndex == nil {
   630  		j.CreateIndex = helper.Uint64ToPtr(0)
   631  	}
   632  	if j.ModifyIndex == nil {
   633  		j.ModifyIndex = helper.Uint64ToPtr(0)
   634  	}
   635  	if j.JobModifyIndex == nil {
   636  		j.JobModifyIndex = helper.Uint64ToPtr(0)
   637  	}
   638  	if j.Periodic != nil {
   639  		j.Periodic.Canonicalize()
   640  	}
   641  	if j.Update != nil {
   642  		j.Update.Canonicalize()
   643  	}
   644  
   645  	for _, tg := range j.TaskGroups {
   646  		tg.Canonicalize(j)
   647  	}
   648  }
   649  
   650  // JobSummary summarizes the state of the allocations of a job
   651  type JobSummary struct {
   652  	JobID     string
   653  	Namespace string
   654  	Summary   map[string]TaskGroupSummary
   655  	Children  *JobChildrenSummary
   656  
   657  	// Raft Indexes
   658  	CreateIndex uint64
   659  	ModifyIndex uint64
   660  }
   661  
   662  // JobChildrenSummary contains the summary of children job status
   663  type JobChildrenSummary struct {
   664  	Pending int64
   665  	Running int64
   666  	Dead    int64
   667  }
   668  
   669  func (jc *JobChildrenSummary) Sum() int {
   670  	if jc == nil {
   671  		return 0
   672  	}
   673  
   674  	return int(jc.Pending + jc.Running + jc.Dead)
   675  }
   676  
   677  // TaskGroup summarizes the state of all the allocations of a particular
   678  // TaskGroup
   679  type TaskGroupSummary struct {
   680  	Queued   int
   681  	Complete int
   682  	Failed   int
   683  	Running  int
   684  	Starting int
   685  	Lost     int
   686  }
   687  
   688  // JobListStub is used to return a subset of information about
   689  // jobs during list operations.
   690  type JobListStub struct {
   691  	ID                string
   692  	ParentID          string
   693  	Name              string
   694  	Type              string
   695  	Priority          int
   696  	Periodic          bool
   697  	ParameterizedJob  bool
   698  	Stop              bool
   699  	Status            string
   700  	StatusDescription string
   701  	JobSummary        *JobSummary
   702  	CreateIndex       uint64
   703  	ModifyIndex       uint64
   704  	JobModifyIndex    uint64
   705  	SubmitTime        int64
   706  }
   707  
   708  // JobIDSort is used to sort jobs by their job ID's.
   709  type JobIDSort []*JobListStub
   710  
   711  func (j JobIDSort) Len() int {
   712  	return len(j)
   713  }
   714  
   715  func (j JobIDSort) Less(a, b int) bool {
   716  	return j[a].ID < j[b].ID
   717  }
   718  
   719  func (j JobIDSort) Swap(a, b int) {
   720  	j[a], j[b] = j[b], j[a]
   721  }
   722  
   723  // NewServiceJob creates and returns a new service-style job
   724  // for long-lived processes using the provided name, ID, and
   725  // relative job priority.
   726  func NewServiceJob(id, name, region string, pri int) *Job {
   727  	return newJob(id, name, region, JobTypeService, pri)
   728  }
   729  
   730  // NewBatchJob creates and returns a new batch-style job for
   731  // short-lived processes using the provided name and ID along
   732  // with the relative job priority.
   733  func NewBatchJob(id, name, region string, pri int) *Job {
   734  	return newJob(id, name, region, JobTypeBatch, pri)
   735  }
   736  
   737  // newJob is used to create a new Job struct.
   738  func newJob(id, name, region, typ string, pri int) *Job {
   739  	return &Job{
   740  		Region:   &region,
   741  		ID:       &id,
   742  		Name:     &name,
   743  		Type:     &typ,
   744  		Priority: &pri,
   745  	}
   746  }
   747  
   748  // SetMeta is used to set arbitrary k/v pairs of metadata on a job.
   749  func (j *Job) SetMeta(key, val string) *Job {
   750  	if j.Meta == nil {
   751  		j.Meta = make(map[string]string)
   752  	}
   753  	j.Meta[key] = val
   754  	return j
   755  }
   756  
   757  // AddDatacenter is used to add a datacenter to a job.
   758  func (j *Job) AddDatacenter(dc string) *Job {
   759  	j.Datacenters = append(j.Datacenters, dc)
   760  	return j
   761  }
   762  
   763  // Constrain is used to add a constraint to a job.
   764  func (j *Job) Constrain(c *Constraint) *Job {
   765  	j.Constraints = append(j.Constraints, c)
   766  	return j
   767  }
   768  
   769  // AddTaskGroup adds a task group to an existing job.
   770  func (j *Job) AddTaskGroup(grp *TaskGroup) *Job {
   771  	j.TaskGroups = append(j.TaskGroups, grp)
   772  	return j
   773  }
   774  
   775  // AddPeriodicConfig adds a periodic config to an existing job.
   776  func (j *Job) AddPeriodicConfig(cfg *PeriodicConfig) *Job {
   777  	j.Periodic = cfg
   778  	return j
   779  }
   780  
   781  type WriteRequest struct {
   782  	// The target region for this write
   783  	Region string
   784  
   785  	// Namespace is the target namespace for this write
   786  	Namespace string
   787  
   788  	// SecretID is the secret ID of an ACL token
   789  	SecretID string
   790  }
   791  
   792  // JobValidateRequest is used to validate a job
   793  type JobValidateRequest struct {
   794  	Job *Job
   795  	WriteRequest
   796  }
   797  
   798  // JobValidateResponse is the response from validate request
   799  type JobValidateResponse struct {
   800  	// DriverConfigValidated indicates whether the agent validated the driver
   801  	// config
   802  	DriverConfigValidated bool
   803  
   804  	// ValidationErrors is a list of validation errors
   805  	ValidationErrors []string
   806  
   807  	// Error is a string version of any error that may have occurred
   808  	Error string
   809  
   810  	// Warnings contains any warnings about the given job. These may include
   811  	// deprecation warnings.
   812  	Warnings string
   813  }
   814  
   815  // JobRevertRequest is used to revert a job to a prior version.
   816  type JobRevertRequest struct {
   817  	// JobID is the ID of the job  being reverted
   818  	JobID string
   819  
   820  	// JobVersion the version to revert to.
   821  	JobVersion uint64
   822  
   823  	// EnforcePriorVersion if set will enforce that the job is at the given
   824  	// version before reverting.
   825  	EnforcePriorVersion *uint64
   826  
   827  	WriteRequest
   828  }
   829  
   830  // JobUpdateRequest is used to update a job
   831  type JobRegisterRequest struct {
   832  	Job *Job
   833  	// If EnforceIndex is set then the job will only be registered if the passed
   834  	// JobModifyIndex matches the current Jobs index. If the index is zero, the
   835  	// register only occurs if the job is new.
   836  	EnforceIndex   bool
   837  	JobModifyIndex uint64
   838  	PolicyOverride bool
   839  
   840  	WriteRequest
   841  }
   842  
   843  // RegisterJobRequest is used to serialize a job registration
   844  type RegisterJobRequest struct {
   845  	Job            *Job
   846  	EnforceIndex   bool   `json:",omitempty"`
   847  	JobModifyIndex uint64 `json:",omitempty"`
   848  	PolicyOverride bool   `json:",omitempty"`
   849  }
   850  
   851  // JobRegisterResponse is used to respond to a job registration
   852  type JobRegisterResponse struct {
   853  	EvalID          string
   854  	EvalCreateIndex uint64
   855  	JobModifyIndex  uint64
   856  
   857  	// Warnings contains any warnings about the given job. These may include
   858  	// deprecation warnings.
   859  	Warnings string
   860  
   861  	QueryMeta
   862  }
   863  
   864  // JobDeregisterResponse is used to respond to a job deregistration
   865  type JobDeregisterResponse struct {
   866  	EvalID          string
   867  	EvalCreateIndex uint64
   868  	JobModifyIndex  uint64
   869  	QueryMeta
   870  }
   871  
   872  type JobPlanRequest struct {
   873  	Job            *Job
   874  	Diff           bool
   875  	PolicyOverride bool
   876  	WriteRequest
   877  }
   878  
   879  type JobPlanResponse struct {
   880  	JobModifyIndex     uint64
   881  	CreatedEvals       []*Evaluation
   882  	Diff               *JobDiff
   883  	Annotations        *PlanAnnotations
   884  	FailedTGAllocs     map[string]*AllocationMetric
   885  	NextPeriodicLaunch time.Time
   886  
   887  	// Warnings contains any warnings about the given job. These may include
   888  	// deprecation warnings.
   889  	Warnings string
   890  }
   891  
   892  type JobDiff struct {
   893  	Type       string
   894  	ID         string
   895  	Fields     []*FieldDiff
   896  	Objects    []*ObjectDiff
   897  	TaskGroups []*TaskGroupDiff
   898  }
   899  
   900  type TaskGroupDiff struct {
   901  	Type    string
   902  	Name    string
   903  	Fields  []*FieldDiff
   904  	Objects []*ObjectDiff
   905  	Tasks   []*TaskDiff
   906  	Updates map[string]uint64
   907  }
   908  
   909  type TaskDiff struct {
   910  	Type        string
   911  	Name        string
   912  	Fields      []*FieldDiff
   913  	Objects     []*ObjectDiff
   914  	Annotations []string
   915  }
   916  
   917  type FieldDiff struct {
   918  	Type        string
   919  	Name        string
   920  	Old, New    string
   921  	Annotations []string
   922  }
   923  
   924  type ObjectDiff struct {
   925  	Type    string
   926  	Name    string
   927  	Fields  []*FieldDiff
   928  	Objects []*ObjectDiff
   929  }
   930  
   931  type PlanAnnotations struct {
   932  	DesiredTGUpdates map[string]*DesiredUpdates
   933  }
   934  
   935  type DesiredUpdates struct {
   936  	Ignore            uint64
   937  	Place             uint64
   938  	Migrate           uint64
   939  	Stop              uint64
   940  	InPlaceUpdate     uint64
   941  	DestructiveUpdate uint64
   942  	Canary            uint64
   943  }
   944  
   945  type JobDispatchRequest struct {
   946  	JobID   string
   947  	Payload []byte
   948  	Meta    map[string]string
   949  }
   950  
   951  type JobDispatchResponse struct {
   952  	DispatchedJobID string
   953  	EvalID          string
   954  	EvalCreateIndex uint64
   955  	JobCreateIndex  uint64
   956  	WriteMeta
   957  }
   958  
   959  // JobVersionsResponse is used for a job get versions request
   960  type JobVersionsResponse struct {
   961  	Versions []*Job
   962  	Diffs    []*JobDiff
   963  	QueryMeta
   964  }
   965  
   966  // JobStabilityRequest is used to marked a job as stable.
   967  type JobStabilityRequest struct {
   968  	// Job to set the stability on
   969  	JobID      string
   970  	JobVersion uint64
   971  
   972  	// Set the stability
   973  	Stable bool
   974  	WriteRequest
   975  }
   976  
   977  // JobStabilityResponse is the response when marking a job as stable.
   978  type JobStabilityResponse struct {
   979  	JobModifyIndex uint64
   980  	WriteMeta
   981  }