github.com/ncodes/nomad@v0.5.7-0.20170403112158-97adf4a74fb3/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/ncodes/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  
    25  const (
    26  	// RegisterEnforceIndexErrPrefix is the prefix to use in errors caused by
    27  	// enforcing the job modify index during registers.
    28  	RegisterEnforceIndexErrPrefix = "Enforcing job modify index"
    29  )
    30  
    31  // Jobs is used to access the job-specific endpoints.
    32  type Jobs struct {
    33  	client *Client
    34  }
    35  
    36  // Jobs returns a handle on the jobs endpoints.
    37  func (c *Client) Jobs() *Jobs {
    38  	return &Jobs{client: c}
    39  }
    40  
    41  func (j *Jobs) Validate(job *Job, q *WriteOptions) (*JobValidateResponse, *WriteMeta, error) {
    42  	var resp JobValidateResponse
    43  	req := &JobValidateRequest{Job: job}
    44  	if q != nil {
    45  		req.WriteRequest = WriteRequest{Region: q.Region}
    46  	}
    47  	wm, err := j.client.write("/v1/validate/job", req, &resp, q)
    48  	return &resp, wm, err
    49  }
    50  
    51  // Register is used to register a new job. It returns the ID
    52  // of the evaluation, along with any errors encountered.
    53  func (j *Jobs) Register(job *Job, q *WriteOptions) (string, *WriteMeta, error) {
    54  
    55  	var resp registerJobResponse
    56  
    57  	req := &RegisterJobRequest{Job: job}
    58  	wm, err := j.client.write("/v1/jobs", req, &resp, q)
    59  	if err != nil {
    60  		return "", nil, err
    61  	}
    62  	return resp.EvalID, wm, nil
    63  }
    64  
    65  // EnforceRegister is used to register a job enforcing its job modify index.
    66  func (j *Jobs) EnforceRegister(job *Job, modifyIndex uint64, q *WriteOptions) (string, *WriteMeta, error) {
    67  
    68  	var resp registerJobResponse
    69  
    70  	req := &RegisterJobRequest{
    71  		Job:            job,
    72  		EnforceIndex:   true,
    73  		JobModifyIndex: modifyIndex,
    74  	}
    75  	wm, err := j.client.write("/v1/jobs", req, &resp, q)
    76  	if err != nil {
    77  		return "", nil, err
    78  	}
    79  	return resp.EvalID, wm, nil
    80  }
    81  
    82  // List is used to list all of the existing jobs.
    83  func (j *Jobs) List(q *QueryOptions) ([]*JobListStub, *QueryMeta, error) {
    84  	var resp []*JobListStub
    85  	qm, err := j.client.query("/v1/jobs", &resp, q)
    86  	if err != nil {
    87  		return nil, qm, err
    88  	}
    89  	sort.Sort(JobIDSort(resp))
    90  	return resp, qm, nil
    91  }
    92  
    93  // PrefixList is used to list all existing jobs that match the prefix.
    94  func (j *Jobs) PrefixList(prefix string) ([]*JobListStub, *QueryMeta, error) {
    95  	return j.List(&QueryOptions{Prefix: prefix})
    96  }
    97  
    98  // Info is used to retrieve information about a particular
    99  // job given its unique ID.
   100  func (j *Jobs) Info(jobID string, q *QueryOptions) (*Job, *QueryMeta, error) {
   101  	var resp Job
   102  	qm, err := j.client.query("/v1/job/"+jobID, &resp, q)
   103  	if err != nil {
   104  		return nil, nil, err
   105  	}
   106  	return &resp, qm, nil
   107  }
   108  
   109  // Allocations is used to return the allocs for a given job ID.
   110  func (j *Jobs) Allocations(jobID string, allAllocs bool, q *QueryOptions) ([]*AllocationListStub, *QueryMeta, error) {
   111  	var resp []*AllocationListStub
   112  	u, err := url.Parse("/v1/job/" + jobID + "/allocations")
   113  	if err != nil {
   114  		return nil, nil, err
   115  	}
   116  
   117  	v := u.Query()
   118  	v.Add("all", strconv.FormatBool(allAllocs))
   119  	u.RawQuery = v.Encode()
   120  
   121  	qm, err := j.client.query(u.String(), &resp, q)
   122  	if err != nil {
   123  		return nil, nil, err
   124  	}
   125  	sort.Sort(AllocIndexSort(resp))
   126  	return resp, qm, nil
   127  }
   128  
   129  // Evaluations is used to query the evaluations associated with
   130  // the given job ID.
   131  func (j *Jobs) Evaluations(jobID string, q *QueryOptions) ([]*Evaluation, *QueryMeta, error) {
   132  	var resp []*Evaluation
   133  	qm, err := j.client.query("/v1/job/"+jobID+"/evaluations", &resp, q)
   134  	if err != nil {
   135  		return nil, nil, err
   136  	}
   137  	sort.Sort(EvalIndexSort(resp))
   138  	return resp, qm, nil
   139  }
   140  
   141  // Deregister is used to remove an existing job.
   142  func (j *Jobs) Deregister(jobID string, q *WriteOptions) (string, *WriteMeta, error) {
   143  	var resp deregisterJobResponse
   144  	wm, err := j.client.delete("/v1/job/"+jobID, &resp, q)
   145  	if err != nil {
   146  		return "", nil, err
   147  	}
   148  	return resp.EvalID, wm, nil
   149  }
   150  
   151  // ForceEvaluate is used to force-evaluate an existing job.
   152  func (j *Jobs) ForceEvaluate(jobID string, q *WriteOptions) (string, *WriteMeta, error) {
   153  	var resp registerJobResponse
   154  	wm, err := j.client.write("/v1/job/"+jobID+"/evaluate", nil, &resp, q)
   155  	if err != nil {
   156  		return "", nil, err
   157  	}
   158  	return resp.EvalID, wm, nil
   159  }
   160  
   161  // PeriodicForce spawns a new instance of the periodic job and returns the eval ID
   162  func (j *Jobs) PeriodicForce(jobID string, q *WriteOptions) (string, *WriteMeta, error) {
   163  	var resp periodicForceResponse
   164  	wm, err := j.client.write("/v1/job/"+jobID+"/periodic/force", nil, &resp, q)
   165  	if err != nil {
   166  		return "", nil, err
   167  	}
   168  	return resp.EvalID, wm, nil
   169  }
   170  
   171  func (j *Jobs) Plan(job *Job, diff bool, q *WriteOptions) (*JobPlanResponse, *WriteMeta, error) {
   172  	if job == nil {
   173  		return nil, nil, fmt.Errorf("must pass non-nil job")
   174  	}
   175  
   176  	var resp JobPlanResponse
   177  	req := &JobPlanRequest{
   178  		Job:  job,
   179  		Diff: diff,
   180  	}
   181  	wm, err := j.client.write("/v1/job/"+*job.ID+"/plan", req, &resp, q)
   182  	if err != nil {
   183  		return nil, nil, err
   184  	}
   185  
   186  	return &resp, wm, nil
   187  }
   188  
   189  func (j *Jobs) Summary(jobID string, q *QueryOptions) (*JobSummary, *QueryMeta, error) {
   190  	var resp JobSummary
   191  	qm, err := j.client.query("/v1/job/"+jobID+"/summary", &resp, q)
   192  	if err != nil {
   193  		return nil, nil, err
   194  	}
   195  	return &resp, qm, nil
   196  }
   197  
   198  func (j *Jobs) Dispatch(jobID string, meta map[string]string,
   199  	payload []byte, q *WriteOptions) (*JobDispatchResponse, *WriteMeta, error) {
   200  	var resp JobDispatchResponse
   201  	req := &JobDispatchRequest{
   202  		JobID:   jobID,
   203  		Meta:    meta,
   204  		Payload: payload,
   205  	}
   206  	wm, err := j.client.write("/v1/job/"+jobID+"/dispatch", req, &resp, q)
   207  	if err != nil {
   208  		return nil, nil, err
   209  	}
   210  	return &resp, wm, nil
   211  }
   212  
   213  // periodicForceResponse is used to deserialize a force response
   214  type periodicForceResponse struct {
   215  	EvalID string
   216  }
   217  
   218  // UpdateStrategy is for serializing update strategy for a job.
   219  type UpdateStrategy struct {
   220  	Stagger     time.Duration
   221  	MaxParallel int `mapstructure:"max_parallel"`
   222  }
   223  
   224  // PeriodicConfig is for serializing periodic config for a job.
   225  type PeriodicConfig struct {
   226  	Enabled         *bool
   227  	Spec            *string
   228  	SpecType        *string
   229  	ProhibitOverlap *bool   `mapstructure:"prohibit_overlap"`
   230  	TimeZone        *string `mapstructure:"time_zone"`
   231  }
   232  
   233  func (p *PeriodicConfig) Canonicalize() {
   234  	if p.Enabled == nil {
   235  		p.Enabled = helper.BoolToPtr(true)
   236  	}
   237  	if p.Spec == nil {
   238  		p.Spec = helper.StringToPtr("")
   239  	}
   240  	if p.SpecType == nil {
   241  		p.SpecType = helper.StringToPtr(PeriodicSpecCron)
   242  	}
   243  	if p.ProhibitOverlap == nil {
   244  		p.ProhibitOverlap = helper.BoolToPtr(false)
   245  	}
   246  	if p.TimeZone == nil || *p.TimeZone == "" {
   247  		p.TimeZone = helper.StringToPtr("UTC")
   248  	}
   249  }
   250  
   251  // Next returns the closest time instant matching the spec that is after the
   252  // passed time. If no matching instance exists, the zero value of time.Time is
   253  // returned. The `time.Location` of the returned value matches that of the
   254  // passed time.
   255  func (p *PeriodicConfig) Next(fromTime time.Time) time.Time {
   256  	if *p.SpecType == PeriodicSpecCron {
   257  		if e, err := cronexpr.Parse(*p.Spec); err == nil {
   258  			return e.Next(fromTime)
   259  		}
   260  	}
   261  
   262  	return time.Time{}
   263  }
   264  
   265  func (p *PeriodicConfig) GetLocation() (*time.Location, error) {
   266  	if p.TimeZone == nil || *p.TimeZone == "" {
   267  		return time.UTC, nil
   268  	}
   269  
   270  	return time.LoadLocation(*p.TimeZone)
   271  }
   272  
   273  // ParameterizedJobConfig is used to configure the parameterized job.
   274  type ParameterizedJobConfig struct {
   275  	Payload      string
   276  	MetaRequired []string `mapstructure:"meta_required"`
   277  	MetaOptional []string `mapstructure:"meta_optional"`
   278  }
   279  
   280  // Job is used to serialize a job.
   281  type Job struct {
   282  	Region            *string
   283  	ID                *string
   284  	ParentID          *string
   285  	Name              *string
   286  	Type              *string
   287  	Priority          *int
   288  	AllAtOnce         *bool `mapstructure:"all_at_once"`
   289  	Datacenters       []string
   290  	Constraints       []*Constraint
   291  	TaskGroups        []*TaskGroup
   292  	Update            *UpdateStrategy
   293  	Periodic          *PeriodicConfig
   294  	ParameterizedJob  *ParameterizedJobConfig
   295  	Payload           []byte
   296  	Meta              map[string]string
   297  	VaultToken        *string `mapstructure:"vault_token"`
   298  	Status            *string
   299  	StatusDescription *string
   300  	CreateIndex       *uint64
   301  	ModifyIndex       *uint64
   302  	JobModifyIndex    *uint64
   303  }
   304  
   305  // IsPeriodic returns whether a job is periodic.
   306  func (j *Job) IsPeriodic() bool {
   307  	return j.Periodic != nil
   308  }
   309  
   310  // IsParameterized returns whether a job is parameterized job.
   311  func (j *Job) IsParameterized() bool {
   312  	return j.ParameterizedJob != nil
   313  }
   314  
   315  func (j *Job) Canonicalize() {
   316  	if j.ID == nil {
   317  		j.ID = helper.StringToPtr("")
   318  	}
   319  	if j.Name == nil {
   320  		j.Name = helper.StringToPtr(*j.ID)
   321  	}
   322  	if j.ParentID == nil {
   323  		j.ParentID = helper.StringToPtr("")
   324  	}
   325  	if j.Priority == nil {
   326  		j.Priority = helper.IntToPtr(50)
   327  	}
   328  	if j.Region == nil {
   329  		j.Region = helper.StringToPtr("global")
   330  	}
   331  	if j.Type == nil {
   332  		j.Type = helper.StringToPtr("service")
   333  	}
   334  	if j.AllAtOnce == nil {
   335  		j.AllAtOnce = helper.BoolToPtr(false)
   336  	}
   337  	if j.VaultToken == nil {
   338  		j.VaultToken = helper.StringToPtr("")
   339  	}
   340  	if j.Status == nil {
   341  		j.Status = helper.StringToPtr("")
   342  	}
   343  	if j.StatusDescription == nil {
   344  		j.StatusDescription = helper.StringToPtr("")
   345  	}
   346  	if j.CreateIndex == nil {
   347  		j.CreateIndex = helper.Uint64ToPtr(0)
   348  	}
   349  	if j.ModifyIndex == nil {
   350  		j.ModifyIndex = helper.Uint64ToPtr(0)
   351  	}
   352  	if j.JobModifyIndex == nil {
   353  		j.JobModifyIndex = helper.Uint64ToPtr(0)
   354  	}
   355  	if j.Periodic != nil {
   356  		j.Periodic.Canonicalize()
   357  	}
   358  
   359  	for _, tg := range j.TaskGroups {
   360  		tg.Canonicalize(j)
   361  	}
   362  }
   363  
   364  // JobSummary summarizes the state of the allocations of a job
   365  type JobSummary struct {
   366  	JobID    string
   367  	Summary  map[string]TaskGroupSummary
   368  	Children *JobChildrenSummary
   369  
   370  	// Raft Indexes
   371  	CreateIndex uint64
   372  	ModifyIndex uint64
   373  }
   374  
   375  // JobChildrenSummary contains the summary of children job status
   376  type JobChildrenSummary struct {
   377  	Pending int64
   378  	Running int64
   379  	Dead    int64
   380  }
   381  
   382  func (jc *JobChildrenSummary) Sum() int {
   383  	if jc == nil {
   384  		return 0
   385  	}
   386  
   387  	return int(jc.Pending + jc.Running + jc.Dead)
   388  }
   389  
   390  // TaskGroup summarizes the state of all the allocations of a particular
   391  // TaskGroup
   392  type TaskGroupSummary struct {
   393  	Queued   int
   394  	Complete int
   395  	Failed   int
   396  	Running  int
   397  	Starting int
   398  	Lost     int
   399  }
   400  
   401  // JobListStub is used to return a subset of information about
   402  // jobs during list operations.
   403  type JobListStub struct {
   404  	ID                string
   405  	ParentID          string
   406  	Name              string
   407  	Type              string
   408  	Priority          int
   409  	Status            string
   410  	StatusDescription string
   411  	JobSummary        *JobSummary
   412  	CreateIndex       uint64
   413  	ModifyIndex       uint64
   414  	JobModifyIndex    uint64
   415  }
   416  
   417  // JobIDSort is used to sort jobs by their job ID's.
   418  type JobIDSort []*JobListStub
   419  
   420  func (j JobIDSort) Len() int {
   421  	return len(j)
   422  }
   423  
   424  func (j JobIDSort) Less(a, b int) bool {
   425  	return j[a].ID < j[b].ID
   426  }
   427  
   428  func (j JobIDSort) Swap(a, b int) {
   429  	j[a], j[b] = j[b], j[a]
   430  }
   431  
   432  // NewServiceJob creates and returns a new service-style job
   433  // for long-lived processes using the provided name, ID, and
   434  // relative job priority.
   435  func NewServiceJob(id, name, region string, pri int) *Job {
   436  	return newJob(id, name, region, JobTypeService, pri)
   437  }
   438  
   439  // NewBatchJob creates and returns a new batch-style job for
   440  // short-lived processes using the provided name and ID along
   441  // with the relative job priority.
   442  func NewBatchJob(id, name, region string, pri int) *Job {
   443  	return newJob(id, name, region, JobTypeBatch, pri)
   444  }
   445  
   446  // newJob is used to create a new Job struct.
   447  func newJob(id, name, region, typ string, pri int) *Job {
   448  	return &Job{
   449  		Region:   &region,
   450  		ID:       &id,
   451  		Name:     &name,
   452  		Type:     &typ,
   453  		Priority: &pri,
   454  	}
   455  }
   456  
   457  // SetMeta is used to set arbitrary k/v pairs of metadata on a job.
   458  func (j *Job) SetMeta(key, val string) *Job {
   459  	if j.Meta == nil {
   460  		j.Meta = make(map[string]string)
   461  	}
   462  	j.Meta[key] = val
   463  	return j
   464  }
   465  
   466  // AddDatacenter is used to add a datacenter to a job.
   467  func (j *Job) AddDatacenter(dc string) *Job {
   468  	j.Datacenters = append(j.Datacenters, dc)
   469  	return j
   470  }
   471  
   472  // Constrain is used to add a constraint to a job.
   473  func (j *Job) Constrain(c *Constraint) *Job {
   474  	j.Constraints = append(j.Constraints, c)
   475  	return j
   476  }
   477  
   478  // AddTaskGroup adds a task group to an existing job.
   479  func (j *Job) AddTaskGroup(grp *TaskGroup) *Job {
   480  	j.TaskGroups = append(j.TaskGroups, grp)
   481  	return j
   482  }
   483  
   484  // AddPeriodicConfig adds a periodic config to an existing job.
   485  func (j *Job) AddPeriodicConfig(cfg *PeriodicConfig) *Job {
   486  	j.Periodic = cfg
   487  	return j
   488  }
   489  
   490  type WriteRequest struct {
   491  	// The target region for this write
   492  	Region string
   493  }
   494  
   495  // JobValidateRequest is used to validate a job
   496  type JobValidateRequest struct {
   497  	Job *Job
   498  	WriteRequest
   499  }
   500  
   501  // JobValidateResponse is the response from validate request
   502  type JobValidateResponse struct {
   503  	// DriverConfigValidated indicates whether the agent validated the driver
   504  	// config
   505  	DriverConfigValidated bool
   506  
   507  	// ValidationErrors is a list of validation errors
   508  	ValidationErrors []string
   509  
   510  	// Error is a string version of any error that may have occured
   511  	Error string
   512  }
   513  
   514  // JobUpdateRequest is used to update a job
   515  type JobRegisterRequest struct {
   516  	Job *Job
   517  	// If EnforceIndex is set then the job will only be registered if the passed
   518  	// JobModifyIndex matches the current Jobs index. If the index is zero, the
   519  	// register only occurs if the job is new.
   520  	EnforceIndex   bool
   521  	JobModifyIndex uint64
   522  
   523  	WriteRequest
   524  }
   525  
   526  // RegisterJobRequest is used to serialize a job registration
   527  type RegisterJobRequest struct {
   528  	Job            *Job
   529  	EnforceIndex   bool   `json:",omitempty"`
   530  	JobModifyIndex uint64 `json:",omitempty"`
   531  }
   532  
   533  // registerJobResponse is used to deserialize a job response
   534  type registerJobResponse struct {
   535  	EvalID string
   536  }
   537  
   538  // deregisterJobResponse is used to decode a deregister response
   539  type deregisterJobResponse struct {
   540  	EvalID string
   541  }
   542  
   543  type JobPlanRequest struct {
   544  	Job  *Job
   545  	Diff bool
   546  	WriteRequest
   547  }
   548  
   549  type JobPlanResponse struct {
   550  	JobModifyIndex     uint64
   551  	CreatedEvals       []*Evaluation
   552  	Diff               *JobDiff
   553  	Annotations        *PlanAnnotations
   554  	FailedTGAllocs     map[string]*AllocationMetric
   555  	NextPeriodicLaunch time.Time
   556  }
   557  
   558  type JobDiff struct {
   559  	Type       string
   560  	ID         string
   561  	Fields     []*FieldDiff
   562  	Objects    []*ObjectDiff
   563  	TaskGroups []*TaskGroupDiff
   564  }
   565  
   566  type TaskGroupDiff struct {
   567  	Type    string
   568  	Name    string
   569  	Fields  []*FieldDiff
   570  	Objects []*ObjectDiff
   571  	Tasks   []*TaskDiff
   572  	Updates map[string]uint64
   573  }
   574  
   575  type TaskDiff struct {
   576  	Type        string
   577  	Name        string
   578  	Fields      []*FieldDiff
   579  	Objects     []*ObjectDiff
   580  	Annotations []string
   581  }
   582  
   583  type FieldDiff struct {
   584  	Type        string
   585  	Name        string
   586  	Old, New    string
   587  	Annotations []string
   588  }
   589  
   590  type ObjectDiff struct {
   591  	Type    string
   592  	Name    string
   593  	Fields  []*FieldDiff
   594  	Objects []*ObjectDiff
   595  }
   596  
   597  type PlanAnnotations struct {
   598  	DesiredTGUpdates map[string]*DesiredUpdates
   599  }
   600  
   601  type DesiredUpdates struct {
   602  	Ignore            uint64
   603  	Place             uint64
   604  	Migrate           uint64
   605  	Stop              uint64
   606  	InPlaceUpdate     uint64
   607  	DestructiveUpdate uint64
   608  }
   609  
   610  type JobDispatchRequest struct {
   611  	JobID   string
   612  	Payload []byte
   613  	Meta    map[string]string
   614  }
   615  
   616  type JobDispatchResponse struct {
   617  	DispatchedJobID string
   618  	EvalID          string
   619  	EvalCreateIndex uint64
   620  	JobCreateIndex  uint64
   621  	WriteMeta
   622  }