github.com/jrxfive/nomad@v0.6.1-0.20170802162750-1fef470e89bf/nomad/job_endpoint.go (about)

     1  package nomad
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"sort"
     7  	"strings"
     8  	"time"
     9  
    10  	"github.com/armon/go-metrics"
    11  	"github.com/golang/snappy"
    12  	"github.com/hashicorp/consul/lib"
    13  	"github.com/hashicorp/go-memdb"
    14  	"github.com/hashicorp/go-multierror"
    15  	"github.com/hashicorp/nomad/client/driver"
    16  	"github.com/hashicorp/nomad/helper"
    17  	"github.com/hashicorp/nomad/nomad/state"
    18  	"github.com/hashicorp/nomad/nomad/structs"
    19  	"github.com/hashicorp/nomad/scheduler"
    20  )
    21  
    22  const (
    23  	// RegisterEnforceIndexErrPrefix is the prefix to use in errors caused by
    24  	// enforcing the job modify index during registers.
    25  	RegisterEnforceIndexErrPrefix = "Enforcing job modify index"
    26  
    27  	// DispatchPayloadSizeLimit is the maximum size of the uncompressed input
    28  	// data payload.
    29  	DispatchPayloadSizeLimit = 16 * 1024
    30  )
    31  
    32  var (
    33  	// vaultConstraint is the implicit constraint added to jobs requesting a
    34  	// Vault token
    35  	vaultConstraint = &structs.Constraint{
    36  		LTarget: "${attr.vault.version}",
    37  		RTarget: ">= 0.6.1",
    38  		Operand: structs.ConstraintVersion,
    39  	}
    40  )
    41  
    42  // Job endpoint is used for job interactions
    43  type Job struct {
    44  	srv *Server
    45  }
    46  
    47  // Register is used to upsert a job for scheduling
    48  func (j *Job) Register(args *structs.JobRegisterRequest, reply *structs.JobRegisterResponse) error {
    49  	if done, err := j.srv.forward("Job.Register", args, args, reply); done {
    50  		return err
    51  	}
    52  	defer metrics.MeasureSince([]string{"nomad", "job", "register"}, time.Now())
    53  
    54  	// Validate the arguments
    55  	if args.Job == nil {
    56  		return fmt.Errorf("missing job for registration")
    57  	}
    58  
    59  	// Initialize the job fields (sets defaults and any necessary init work).
    60  	canonicalizeWarnings := args.Job.Canonicalize()
    61  
    62  	// Add implicit constraints
    63  	setImplicitConstraints(args.Job)
    64  
    65  	// Validate the job and capture any warnings
    66  	err, warnings := validateJob(args.Job)
    67  	if err != nil {
    68  		return err
    69  	}
    70  
    71  	// Set the warning message
    72  	reply.Warnings = structs.MergeMultierrorWarnings(warnings, canonicalizeWarnings)
    73  
    74  	// Lookup the job
    75  	snap, err := j.srv.fsm.State().Snapshot()
    76  	if err != nil {
    77  		return err
    78  	}
    79  	ws := memdb.NewWatchSet()
    80  	existingJob, err := snap.JobByID(ws, args.Job.ID)
    81  	if err != nil {
    82  		return err
    83  	}
    84  
    85  	// If EnforceIndex set, check it before trying to apply
    86  	if args.EnforceIndex {
    87  		jmi := args.JobModifyIndex
    88  		if existingJob != nil {
    89  			if jmi == 0 {
    90  				return fmt.Errorf("%s 0: job already exists", RegisterEnforceIndexErrPrefix)
    91  			} else if jmi != existingJob.JobModifyIndex {
    92  				return fmt.Errorf("%s %d: job exists with conflicting job modify index: %d",
    93  					RegisterEnforceIndexErrPrefix, jmi, existingJob.JobModifyIndex)
    94  			}
    95  		} else if jmi != 0 {
    96  			return fmt.Errorf("%s %d: job does not exist", RegisterEnforceIndexErrPrefix, jmi)
    97  		}
    98  	}
    99  
   100  	// Validate job transitions if its an update
   101  	if existingJob != nil {
   102  		if err := validateJobUpdate(existingJob, args.Job); err != nil {
   103  			return err
   104  		}
   105  	}
   106  
   107  	// Ensure that the job has permissions for the requested Vault tokens
   108  	policies := args.Job.VaultPolicies()
   109  	if len(policies) != 0 {
   110  		vconf := j.srv.config.VaultConfig
   111  		if !vconf.IsEnabled() {
   112  			return fmt.Errorf("Vault not enabled and Vault policies requested")
   113  		}
   114  
   115  		// Have to check if the user has permissions
   116  		if !vconf.AllowsUnauthenticated() {
   117  			if args.Job.VaultToken == "" {
   118  				return fmt.Errorf("Vault policies requested but missing Vault Token")
   119  			}
   120  
   121  			vault := j.srv.vault
   122  			s, err := vault.LookupToken(context.Background(), args.Job.VaultToken)
   123  			if err != nil {
   124  				return err
   125  			}
   126  
   127  			allowedPolicies, err := PoliciesFrom(s)
   128  			if err != nil {
   129  				return err
   130  			}
   131  
   132  			// If we are given a root token it can access all policies
   133  			if !lib.StrContains(allowedPolicies, "root") {
   134  				flatPolicies := structs.VaultPoliciesSet(policies)
   135  				subset, offending := helper.SliceStringIsSubset(allowedPolicies, flatPolicies)
   136  				if !subset {
   137  					return fmt.Errorf("Passed Vault Token doesn't allow access to the following policies: %s",
   138  						strings.Join(offending, ", "))
   139  				}
   140  			}
   141  		}
   142  	}
   143  
   144  	// Clear the Vault token
   145  	args.Job.VaultToken = ""
   146  
   147  	// Check if the job has changed at all
   148  	if existingJob == nil || existingJob.SpecChanged(args.Job) {
   149  		// Set the submit time
   150  		args.Job.SetSubmitTime()
   151  
   152  		// Commit this update via Raft
   153  		_, index, err := j.srv.raftApply(structs.JobRegisterRequestType, args)
   154  		if err != nil {
   155  			j.srv.logger.Printf("[ERR] nomad.job: Register failed: %v", err)
   156  			return err
   157  		}
   158  
   159  		// Populate the reply with job information
   160  		reply.JobModifyIndex = index
   161  	} else {
   162  		reply.JobModifyIndex = existingJob.JobModifyIndex
   163  	}
   164  
   165  	// If the job is periodic or parameterized, we don't create an eval.
   166  	if args.Job.IsPeriodic() || args.Job.IsParameterized() {
   167  		return nil
   168  	}
   169  
   170  	// Create a new evaluation
   171  	eval := &structs.Evaluation{
   172  		ID:             structs.GenerateUUID(),
   173  		Priority:       args.Job.Priority,
   174  		Type:           args.Job.Type,
   175  		TriggeredBy:    structs.EvalTriggerJobRegister,
   176  		JobID:          args.Job.ID,
   177  		JobModifyIndex: reply.JobModifyIndex,
   178  		Status:         structs.EvalStatusPending,
   179  	}
   180  	update := &structs.EvalUpdateRequest{
   181  		Evals:        []*structs.Evaluation{eval},
   182  		WriteRequest: structs.WriteRequest{Region: args.Region},
   183  	}
   184  
   185  	// Commit this evaluation via Raft
   186  	// XXX: There is a risk of partial failure where the JobRegister succeeds
   187  	// but that the EvalUpdate does not.
   188  	_, evalIndex, err := j.srv.raftApply(structs.EvalUpdateRequestType, update)
   189  	if err != nil {
   190  		j.srv.logger.Printf("[ERR] nomad.job: Eval create failed: %v", err)
   191  		return err
   192  	}
   193  
   194  	// Populate the reply with eval information
   195  	reply.EvalID = eval.ID
   196  	reply.EvalCreateIndex = evalIndex
   197  	reply.Index = evalIndex
   198  	return nil
   199  }
   200  
   201  // setImplicitConstraints adds implicit constraints to the job based on the
   202  // features it is requesting.
   203  func setImplicitConstraints(j *structs.Job) {
   204  	// Get the required Vault Policies
   205  	policies := j.VaultPolicies()
   206  
   207  	// Get the required signals
   208  	signals := j.RequiredSignals()
   209  
   210  	// Hot path
   211  	if len(signals) == 0 && len(policies) == 0 {
   212  		return
   213  	}
   214  
   215  	// Add Vault constraints
   216  	for _, tg := range j.TaskGroups {
   217  		_, ok := policies[tg.Name]
   218  		if !ok {
   219  			// Not requesting Vault
   220  			continue
   221  		}
   222  
   223  		found := false
   224  		for _, c := range tg.Constraints {
   225  			if c.Equal(vaultConstraint) {
   226  				found = true
   227  				break
   228  			}
   229  		}
   230  
   231  		if !found {
   232  			tg.Constraints = append(tg.Constraints, vaultConstraint)
   233  		}
   234  	}
   235  
   236  	// Add signal constraints
   237  	for _, tg := range j.TaskGroups {
   238  		tgSignals, ok := signals[tg.Name]
   239  		if !ok {
   240  			// Not requesting Vault
   241  			continue
   242  		}
   243  
   244  		// Flatten the signals
   245  		required := helper.MapStringStringSliceValueSet(tgSignals)
   246  		sigConstraint := getSignalConstraint(required)
   247  
   248  		found := false
   249  		for _, c := range tg.Constraints {
   250  			if c.Equal(sigConstraint) {
   251  				found = true
   252  				break
   253  			}
   254  		}
   255  
   256  		if !found {
   257  			tg.Constraints = append(tg.Constraints, sigConstraint)
   258  		}
   259  	}
   260  }
   261  
   262  // getSignalConstraint builds a suitable constraint based on the required
   263  // signals
   264  func getSignalConstraint(signals []string) *structs.Constraint {
   265  	return &structs.Constraint{
   266  		Operand: structs.ConstraintSetContains,
   267  		LTarget: "${attr.os.signals}",
   268  		RTarget: strings.Join(signals, ","),
   269  	}
   270  }
   271  
   272  // Summary retreives the summary of a job
   273  func (j *Job) Summary(args *structs.JobSummaryRequest,
   274  	reply *structs.JobSummaryResponse) error {
   275  	if done, err := j.srv.forward("Job.Summary", args, args, reply); done {
   276  		return err
   277  	}
   278  	defer metrics.MeasureSince([]string{"nomad", "job_summary", "get_job_summary"}, time.Now())
   279  	// Setup the blocking query
   280  	opts := blockingOptions{
   281  		queryOpts: &args.QueryOptions,
   282  		queryMeta: &reply.QueryMeta,
   283  		run: func(ws memdb.WatchSet, state *state.StateStore) error {
   284  			// Look for job summary
   285  			out, err := state.JobSummaryByID(ws, args.JobID)
   286  			if err != nil {
   287  				return err
   288  			}
   289  
   290  			// Setup the output
   291  			reply.JobSummary = out
   292  			if out != nil {
   293  				reply.Index = out.ModifyIndex
   294  			} else {
   295  				// Use the last index that affected the job_summary table
   296  				index, err := state.Index("job_summary")
   297  				if err != nil {
   298  					return err
   299  				}
   300  				reply.Index = index
   301  			}
   302  
   303  			// Set the query response
   304  			j.srv.setQueryMeta(&reply.QueryMeta)
   305  			return nil
   306  		}}
   307  	return j.srv.blockingRPC(&opts)
   308  }
   309  
   310  // Validate validates a job
   311  func (j *Job) Validate(args *structs.JobValidateRequest, reply *structs.JobValidateResponse) error {
   312  	defer metrics.MeasureSince([]string{"nomad", "job", "validate"}, time.Now())
   313  
   314  	// Initialize the job fields (sets defaults and any necessary init work).
   315  	canonicalizeWarnings := args.Job.Canonicalize()
   316  
   317  	// Add implicit constraints
   318  	setImplicitConstraints(args.Job)
   319  
   320  	// Validate the job and capture any warnings
   321  	err, warnings := validateJob(args.Job)
   322  	if err != nil {
   323  		if merr, ok := err.(*multierror.Error); ok {
   324  			for _, err := range merr.Errors {
   325  				reply.ValidationErrors = append(reply.ValidationErrors, err.Error())
   326  			}
   327  			reply.Error = merr.Error()
   328  		} else {
   329  			reply.ValidationErrors = append(reply.ValidationErrors, err.Error())
   330  			reply.Error = err.Error()
   331  		}
   332  	}
   333  
   334  	// Set the warning message
   335  	reply.Warnings = structs.MergeMultierrorWarnings(warnings, canonicalizeWarnings)
   336  	reply.DriverConfigValidated = true
   337  	return nil
   338  }
   339  
   340  // Revert is used to revert the job to a prior version
   341  func (j *Job) Revert(args *structs.JobRevertRequest, reply *structs.JobRegisterResponse) error {
   342  	if done, err := j.srv.forward("Job.Revert", args, args, reply); done {
   343  		return err
   344  	}
   345  	defer metrics.MeasureSince([]string{"nomad", "job", "revert"}, time.Now())
   346  
   347  	// Validate the arguments
   348  	if args.JobID == "" {
   349  		return fmt.Errorf("missing job ID for revert")
   350  	}
   351  
   352  	// Lookup the job by version
   353  	snap, err := j.srv.fsm.State().Snapshot()
   354  	if err != nil {
   355  		return err
   356  	}
   357  
   358  	ws := memdb.NewWatchSet()
   359  	cur, err := snap.JobByID(ws, args.JobID)
   360  	if err != nil {
   361  		return err
   362  	}
   363  	if cur == nil {
   364  		return fmt.Errorf("job %q not found", args.JobID)
   365  	}
   366  	if args.JobVersion == cur.Version {
   367  		return fmt.Errorf("can't revert to current version")
   368  	}
   369  
   370  	jobV, err := snap.JobByIDAndVersion(ws, args.JobID, args.JobVersion)
   371  	if err != nil {
   372  		return err
   373  	}
   374  	if jobV == nil {
   375  		return fmt.Errorf("job %q at version %d not found", args.JobID, args.JobVersion)
   376  	}
   377  
   378  	// Build the register request
   379  	reg := &structs.JobRegisterRequest{
   380  		Job:          jobV.Copy(),
   381  		WriteRequest: args.WriteRequest,
   382  	}
   383  
   384  	// If the request is enforcing the existing version do a check.
   385  	if args.EnforcePriorVersion != nil {
   386  		if cur.Version != *args.EnforcePriorVersion {
   387  			return fmt.Errorf("Current job has version %d; enforcing version %d", cur.Version, *args.EnforcePriorVersion)
   388  		}
   389  
   390  		reg.EnforceIndex = true
   391  		reg.JobModifyIndex = cur.JobModifyIndex
   392  	}
   393  
   394  	// Register the version.
   395  	return j.Register(reg, reply)
   396  }
   397  
   398  // Stable is used to mark the job version as stable
   399  func (j *Job) Stable(args *structs.JobStabilityRequest, reply *structs.JobStabilityResponse) error {
   400  	if done, err := j.srv.forward("Job.Stable", args, args, reply); done {
   401  		return err
   402  	}
   403  	defer metrics.MeasureSince([]string{"nomad", "job", "stable"}, time.Now())
   404  
   405  	// Validate the arguments
   406  	if args.JobID == "" {
   407  		return fmt.Errorf("missing job ID for marking job as stable")
   408  	}
   409  
   410  	// Lookup the job by version
   411  	snap, err := j.srv.fsm.State().Snapshot()
   412  	if err != nil {
   413  		return err
   414  	}
   415  
   416  	ws := memdb.NewWatchSet()
   417  	jobV, err := snap.JobByIDAndVersion(ws, args.JobID, args.JobVersion)
   418  	if err != nil {
   419  		return err
   420  	}
   421  	if jobV == nil {
   422  		return fmt.Errorf("job %q at version %d not found", args.JobID, args.JobVersion)
   423  	}
   424  
   425  	// Commit this stability request via Raft
   426  	_, modifyIndex, err := j.srv.raftApply(structs.JobStabilityRequestType, args)
   427  	if err != nil {
   428  		j.srv.logger.Printf("[ERR] nomad.job: Job stability request failed: %v", err)
   429  		return err
   430  	}
   431  
   432  	// Setup the reply
   433  	reply.Index = modifyIndex
   434  	return nil
   435  }
   436  
   437  // Evaluate is used to force a job for re-evaluation
   438  func (j *Job) Evaluate(args *structs.JobEvaluateRequest, reply *structs.JobRegisterResponse) error {
   439  	if done, err := j.srv.forward("Job.Evaluate", args, args, reply); done {
   440  		return err
   441  	}
   442  	defer metrics.MeasureSince([]string{"nomad", "job", "evaluate"}, time.Now())
   443  
   444  	// Validate the arguments
   445  	if args.JobID == "" {
   446  		return fmt.Errorf("missing job ID for evaluation")
   447  	}
   448  
   449  	// Lookup the job
   450  	snap, err := j.srv.fsm.State().Snapshot()
   451  	if err != nil {
   452  		return err
   453  	}
   454  	ws := memdb.NewWatchSet()
   455  	job, err := snap.JobByID(ws, args.JobID)
   456  	if err != nil {
   457  		return err
   458  	}
   459  	if job == nil {
   460  		return fmt.Errorf("job not found")
   461  	}
   462  
   463  	if job.IsPeriodic() {
   464  		return fmt.Errorf("can't evaluate periodic job")
   465  	} else if job.IsParameterized() {
   466  		return fmt.Errorf("can't evaluate parameterized job")
   467  	}
   468  
   469  	// Create a new evaluation
   470  	eval := &structs.Evaluation{
   471  		ID:             structs.GenerateUUID(),
   472  		Priority:       job.Priority,
   473  		Type:           job.Type,
   474  		TriggeredBy:    structs.EvalTriggerJobRegister,
   475  		JobID:          job.ID,
   476  		JobModifyIndex: job.ModifyIndex,
   477  		Status:         structs.EvalStatusPending,
   478  	}
   479  	update := &structs.EvalUpdateRequest{
   480  		Evals:        []*structs.Evaluation{eval},
   481  		WriteRequest: structs.WriteRequest{Region: args.Region},
   482  	}
   483  
   484  	// Commit this evaluation via Raft
   485  	_, evalIndex, err := j.srv.raftApply(structs.EvalUpdateRequestType, update)
   486  	if err != nil {
   487  		j.srv.logger.Printf("[ERR] nomad.job: Eval create failed: %v", err)
   488  		return err
   489  	}
   490  
   491  	// Setup the reply
   492  	reply.EvalID = eval.ID
   493  	reply.EvalCreateIndex = evalIndex
   494  	reply.JobModifyIndex = job.ModifyIndex
   495  	reply.Index = evalIndex
   496  	return nil
   497  }
   498  
   499  // Deregister is used to remove a job the cluster.
   500  func (j *Job) Deregister(args *structs.JobDeregisterRequest, reply *structs.JobDeregisterResponse) error {
   501  	if done, err := j.srv.forward("Job.Deregister", args, args, reply); done {
   502  		return err
   503  	}
   504  	defer metrics.MeasureSince([]string{"nomad", "job", "deregister"}, time.Now())
   505  
   506  	// Validate the arguments
   507  	if args.JobID == "" {
   508  		return fmt.Errorf("missing job ID for deregistering")
   509  	}
   510  
   511  	// Lookup the job
   512  	snap, err := j.srv.fsm.State().Snapshot()
   513  	if err != nil {
   514  		return err
   515  	}
   516  	ws := memdb.NewWatchSet()
   517  	job, err := snap.JobByID(ws, args.JobID)
   518  	if err != nil {
   519  		return err
   520  	}
   521  
   522  	// Commit this update via Raft
   523  	_, index, err := j.srv.raftApply(structs.JobDeregisterRequestType, args)
   524  	if err != nil {
   525  		j.srv.logger.Printf("[ERR] nomad.job: Deregister failed: %v", err)
   526  		return err
   527  	}
   528  
   529  	// Populate the reply with job information
   530  	reply.JobModifyIndex = index
   531  
   532  	// If the job is periodic or parameterized, we don't create an eval.
   533  	if job != nil && (job.IsPeriodic() || job.IsParameterized()) {
   534  		return nil
   535  	}
   536  
   537  	// Create a new evaluation
   538  	// XXX: The job priority / type is strange for this, since it's not a high
   539  	// priority even if the job was. The scheduler itself also doesn't matter,
   540  	// since all should be able to handle deregistration in the same way.
   541  	eval := &structs.Evaluation{
   542  		ID:             structs.GenerateUUID(),
   543  		Priority:       structs.JobDefaultPriority,
   544  		Type:           structs.JobTypeService,
   545  		TriggeredBy:    structs.EvalTriggerJobDeregister,
   546  		JobID:          args.JobID,
   547  		JobModifyIndex: index,
   548  		Status:         structs.EvalStatusPending,
   549  	}
   550  	update := &structs.EvalUpdateRequest{
   551  		Evals:        []*structs.Evaluation{eval},
   552  		WriteRequest: structs.WriteRequest{Region: args.Region},
   553  	}
   554  
   555  	// Commit this evaluation via Raft
   556  	_, evalIndex, err := j.srv.raftApply(structs.EvalUpdateRequestType, update)
   557  	if err != nil {
   558  		j.srv.logger.Printf("[ERR] nomad.job: Eval create failed: %v", err)
   559  		return err
   560  	}
   561  
   562  	// Populate the reply with eval information
   563  	reply.EvalID = eval.ID
   564  	reply.EvalCreateIndex = evalIndex
   565  	reply.Index = evalIndex
   566  	return nil
   567  }
   568  
   569  // GetJob is used to request information about a specific job
   570  func (j *Job) GetJob(args *structs.JobSpecificRequest,
   571  	reply *structs.SingleJobResponse) error {
   572  	if done, err := j.srv.forward("Job.GetJob", args, args, reply); done {
   573  		return err
   574  	}
   575  	defer metrics.MeasureSince([]string{"nomad", "job", "get_job"}, time.Now())
   576  
   577  	// Setup the blocking query
   578  	opts := blockingOptions{
   579  		queryOpts: &args.QueryOptions,
   580  		queryMeta: &reply.QueryMeta,
   581  		run: func(ws memdb.WatchSet, state *state.StateStore) error {
   582  			// Look for the job
   583  			out, err := state.JobByID(ws, args.JobID)
   584  			if err != nil {
   585  				return err
   586  			}
   587  
   588  			// Setup the output
   589  			reply.Job = out
   590  			if out != nil {
   591  				reply.Index = out.ModifyIndex
   592  			} else {
   593  				// Use the last index that affected the nodes table
   594  				index, err := state.Index("jobs")
   595  				if err != nil {
   596  					return err
   597  				}
   598  				reply.Index = index
   599  			}
   600  
   601  			// Set the query response
   602  			j.srv.setQueryMeta(&reply.QueryMeta)
   603  			return nil
   604  		}}
   605  	return j.srv.blockingRPC(&opts)
   606  }
   607  
   608  // GetJobVersions is used to retrieve all tracked versions of a job.
   609  func (j *Job) GetJobVersions(args *structs.JobVersionsRequest,
   610  	reply *structs.JobVersionsResponse) error {
   611  	if done, err := j.srv.forward("Job.GetJobVersions", args, args, reply); done {
   612  		return err
   613  	}
   614  	defer metrics.MeasureSince([]string{"nomad", "job", "get_job_versions"}, time.Now())
   615  
   616  	// Setup the blocking query
   617  	opts := blockingOptions{
   618  		queryOpts: &args.QueryOptions,
   619  		queryMeta: &reply.QueryMeta,
   620  		run: func(ws memdb.WatchSet, state *state.StateStore) error {
   621  			// Look for the job
   622  			out, err := state.JobVersionsByID(ws, args.JobID)
   623  			if err != nil {
   624  				return err
   625  			}
   626  
   627  			// Setup the output
   628  			reply.Versions = out
   629  			if len(out) != 0 {
   630  				reply.Index = out[0].ModifyIndex
   631  
   632  				// Compute the diffs
   633  				if args.Diffs {
   634  					for i := 0; i < len(out)-1; i++ {
   635  						old, new := out[i+1], out[i]
   636  						d, err := old.Diff(new, true)
   637  						if err != nil {
   638  							return fmt.Errorf("failed to create job diff: %v", err)
   639  						}
   640  						reply.Diffs = append(reply.Diffs, d)
   641  					}
   642  				}
   643  			} else {
   644  				// Use the last index that affected the nodes table
   645  				index, err := state.Index("job_version")
   646  				if err != nil {
   647  					return err
   648  				}
   649  				reply.Index = index
   650  			}
   651  
   652  			// Set the query response
   653  			j.srv.setQueryMeta(&reply.QueryMeta)
   654  			return nil
   655  		}}
   656  	return j.srv.blockingRPC(&opts)
   657  }
   658  
   659  // List is used to list the jobs registered in the system
   660  func (j *Job) List(args *structs.JobListRequest,
   661  	reply *structs.JobListResponse) error {
   662  	if done, err := j.srv.forward("Job.List", args, args, reply); done {
   663  		return err
   664  	}
   665  	defer metrics.MeasureSince([]string{"nomad", "job", "list"}, time.Now())
   666  
   667  	// Setup the blocking query
   668  	opts := blockingOptions{
   669  		queryOpts: &args.QueryOptions,
   670  		queryMeta: &reply.QueryMeta,
   671  		run: func(ws memdb.WatchSet, state *state.StateStore) error {
   672  			// Capture all the jobs
   673  			var err error
   674  			var iter memdb.ResultIterator
   675  			if prefix := args.QueryOptions.Prefix; prefix != "" {
   676  				iter, err = state.JobsByIDPrefix(ws, prefix)
   677  			} else {
   678  				iter, err = state.Jobs(ws)
   679  			}
   680  			if err != nil {
   681  				return err
   682  			}
   683  
   684  			var jobs []*structs.JobListStub
   685  			for {
   686  				raw := iter.Next()
   687  				if raw == nil {
   688  					break
   689  				}
   690  				job := raw.(*structs.Job)
   691  				summary, err := state.JobSummaryByID(ws, job.ID)
   692  				if err != nil {
   693  					return fmt.Errorf("unable to look up summary for job: %v", job.ID)
   694  				}
   695  				jobs = append(jobs, job.Stub(summary))
   696  			}
   697  			reply.Jobs = jobs
   698  
   699  			// Use the last index that affected the jobs table
   700  			index, err := state.Index("jobs")
   701  			if err != nil {
   702  				return err
   703  			}
   704  			reply.Index = index
   705  
   706  			// Set the query response
   707  			j.srv.setQueryMeta(&reply.QueryMeta)
   708  			return nil
   709  		}}
   710  	return j.srv.blockingRPC(&opts)
   711  }
   712  
   713  // Allocations is used to list the allocations for a job
   714  func (j *Job) Allocations(args *structs.JobSpecificRequest,
   715  	reply *structs.JobAllocationsResponse) error {
   716  	if done, err := j.srv.forward("Job.Allocations", args, args, reply); done {
   717  		return err
   718  	}
   719  	defer metrics.MeasureSince([]string{"nomad", "job", "allocations"}, time.Now())
   720  
   721  	// Setup the blocking query
   722  	opts := blockingOptions{
   723  		queryOpts: &args.QueryOptions,
   724  		queryMeta: &reply.QueryMeta,
   725  		run: func(ws memdb.WatchSet, state *state.StateStore) error {
   726  			// Capture the allocations
   727  			allocs, err := state.AllocsByJob(ws, args.JobID, args.AllAllocs)
   728  			if err != nil {
   729  				return err
   730  			}
   731  
   732  			// Convert to stubs
   733  			if len(allocs) > 0 {
   734  				reply.Allocations = make([]*structs.AllocListStub, 0, len(allocs))
   735  				for _, alloc := range allocs {
   736  					reply.Allocations = append(reply.Allocations, alloc.Stub())
   737  				}
   738  			}
   739  
   740  			// Use the last index that affected the allocs table
   741  			index, err := state.Index("allocs")
   742  			if err != nil {
   743  				return err
   744  			}
   745  			reply.Index = index
   746  
   747  			// Set the query response
   748  			j.srv.setQueryMeta(&reply.QueryMeta)
   749  			return nil
   750  
   751  		}}
   752  	return j.srv.blockingRPC(&opts)
   753  }
   754  
   755  // Evaluations is used to list the evaluations for a job
   756  func (j *Job) Evaluations(args *structs.JobSpecificRequest,
   757  	reply *structs.JobEvaluationsResponse) error {
   758  	if done, err := j.srv.forward("Job.Evaluations", args, args, reply); done {
   759  		return err
   760  	}
   761  	defer metrics.MeasureSince([]string{"nomad", "job", "evaluations"}, time.Now())
   762  
   763  	// Setup the blocking query
   764  	opts := blockingOptions{
   765  		queryOpts: &args.QueryOptions,
   766  		queryMeta: &reply.QueryMeta,
   767  		run: func(ws memdb.WatchSet, state *state.StateStore) error {
   768  			// Capture the evals
   769  			var err error
   770  			reply.Evaluations, err = state.EvalsByJob(ws, args.JobID)
   771  			if err != nil {
   772  				return err
   773  			}
   774  
   775  			// Use the last index that affected the evals table
   776  			index, err := state.Index("evals")
   777  			if err != nil {
   778  				return err
   779  			}
   780  			reply.Index = index
   781  
   782  			// Set the query response
   783  			j.srv.setQueryMeta(&reply.QueryMeta)
   784  			return nil
   785  		}}
   786  
   787  	return j.srv.blockingRPC(&opts)
   788  }
   789  
   790  // Deployments is used to list the deployments for a job
   791  func (j *Job) Deployments(args *structs.JobSpecificRequest,
   792  	reply *structs.DeploymentListResponse) error {
   793  	if done, err := j.srv.forward("Job.Deployments", args, args, reply); done {
   794  		return err
   795  	}
   796  	defer metrics.MeasureSince([]string{"nomad", "job", "deployments"}, time.Now())
   797  
   798  	// Setup the blocking query
   799  	opts := blockingOptions{
   800  		queryOpts: &args.QueryOptions,
   801  		queryMeta: &reply.QueryMeta,
   802  		run: func(ws memdb.WatchSet, state *state.StateStore) error {
   803  			// Capture the deployments
   804  			deploys, err := state.DeploymentsByJobID(ws, args.JobID)
   805  			if err != nil {
   806  				return err
   807  			}
   808  
   809  			// Use the last index that affected the deployment table
   810  			index, err := state.Index("deployment")
   811  			if err != nil {
   812  				return err
   813  			}
   814  			reply.Index = index
   815  			reply.Deployments = deploys
   816  
   817  			// Set the query response
   818  			j.srv.setQueryMeta(&reply.QueryMeta)
   819  			return nil
   820  
   821  		}}
   822  	return j.srv.blockingRPC(&opts)
   823  }
   824  
   825  // LatestDeployment is used to retrieve the latest deployment for a job
   826  func (j *Job) LatestDeployment(args *structs.JobSpecificRequest,
   827  	reply *structs.SingleDeploymentResponse) error {
   828  	if done, err := j.srv.forward("Job.LatestDeployment", args, args, reply); done {
   829  		return err
   830  	}
   831  	defer metrics.MeasureSince([]string{"nomad", "job", "latest_deployment"}, time.Now())
   832  
   833  	// Setup the blocking query
   834  	opts := blockingOptions{
   835  		queryOpts: &args.QueryOptions,
   836  		queryMeta: &reply.QueryMeta,
   837  		run: func(ws memdb.WatchSet, state *state.StateStore) error {
   838  			// Capture the deployments
   839  			deploys, err := state.DeploymentsByJobID(ws, args.JobID)
   840  			if err != nil {
   841  				return err
   842  			}
   843  
   844  			// Use the last index that affected the deployment table
   845  			index, err := state.Index("deployment")
   846  			if err != nil {
   847  				return err
   848  			}
   849  			reply.Index = index
   850  			if len(deploys) > 0 {
   851  				sort.Slice(deploys, func(i, j int) bool {
   852  					return deploys[i].CreateIndex > deploys[j].CreateIndex
   853  				})
   854  				reply.Deployment = deploys[0]
   855  			}
   856  
   857  			// Set the query response
   858  			j.srv.setQueryMeta(&reply.QueryMeta)
   859  			return nil
   860  
   861  		}}
   862  	return j.srv.blockingRPC(&opts)
   863  }
   864  
   865  // Plan is used to cause a dry-run evaluation of the Job and return the results
   866  // with a potential diff containing annotations.
   867  func (j *Job) Plan(args *structs.JobPlanRequest, reply *structs.JobPlanResponse) error {
   868  	if done, err := j.srv.forward("Job.Plan", args, args, reply); done {
   869  		return err
   870  	}
   871  	defer metrics.MeasureSince([]string{"nomad", "job", "plan"}, time.Now())
   872  
   873  	// Validate the arguments
   874  	if args.Job == nil {
   875  		return fmt.Errorf("Job required for plan")
   876  	}
   877  
   878  	// Initialize the job fields (sets defaults and any necessary init work).
   879  	canonicalizeWarnings := args.Job.Canonicalize()
   880  
   881  	// Add implicit constraints
   882  	setImplicitConstraints(args.Job)
   883  
   884  	// Validate the job and capture any warnings
   885  	err, warnings := validateJob(args.Job)
   886  	if err != nil {
   887  		return err
   888  	}
   889  
   890  	// Set the warning message
   891  	reply.Warnings = structs.MergeMultierrorWarnings(warnings, canonicalizeWarnings)
   892  
   893  	// Acquire a snapshot of the state
   894  	snap, err := j.srv.fsm.State().Snapshot()
   895  	if err != nil {
   896  		return err
   897  	}
   898  
   899  	// Get the original job
   900  	ws := memdb.NewWatchSet()
   901  	oldJob, err := snap.JobByID(ws, args.Job.ID)
   902  	if err != nil {
   903  		return err
   904  	}
   905  
   906  	var index uint64
   907  	var updatedIndex uint64
   908  
   909  	if oldJob != nil {
   910  		index = oldJob.JobModifyIndex
   911  
   912  		// We want to reuse deployments where possible, so only insert the job if
   913  		// it has changed or the job didn't exist
   914  		if oldJob.SpecChanged(args.Job) {
   915  			// Insert the updated Job into the snapshot
   916  			updatedIndex = oldJob.JobModifyIndex + 1
   917  			snap.UpsertJob(updatedIndex, args.Job)
   918  		}
   919  	} else if oldJob == nil {
   920  		// Insert the updated Job into the snapshot
   921  		snap.UpsertJob(100, args.Job)
   922  	}
   923  
   924  	// Create an eval and mark it as requiring annotations and insert that as well
   925  	eval := &structs.Evaluation{
   926  		ID:             structs.GenerateUUID(),
   927  		Priority:       args.Job.Priority,
   928  		Type:           args.Job.Type,
   929  		TriggeredBy:    structs.EvalTriggerJobRegister,
   930  		JobID:          args.Job.ID,
   931  		JobModifyIndex: updatedIndex,
   932  		Status:         structs.EvalStatusPending,
   933  		AnnotatePlan:   true,
   934  	}
   935  
   936  	// Create an in-memory Planner that returns no errors and stores the
   937  	// submitted plan and created evals.
   938  	planner := &scheduler.Harness{
   939  		State: &snap.StateStore,
   940  	}
   941  
   942  	// Create the scheduler and run it
   943  	sched, err := scheduler.NewScheduler(eval.Type, j.srv.logger, snap, planner)
   944  	if err != nil {
   945  		return err
   946  	}
   947  
   948  	if err := sched.Process(eval); err != nil {
   949  		return err
   950  	}
   951  
   952  	// Annotate and store the diff
   953  	if plans := len(planner.Plans); plans != 1 {
   954  		return fmt.Errorf("scheduler resulted in an unexpected number of plans: %v", plans)
   955  	}
   956  	annotations := planner.Plans[0].Annotations
   957  	if args.Diff {
   958  		jobDiff, err := oldJob.Diff(args.Job, true)
   959  		if err != nil {
   960  			return fmt.Errorf("failed to create job diff: %v", err)
   961  		}
   962  
   963  		if err := scheduler.Annotate(jobDiff, annotations); err != nil {
   964  			return fmt.Errorf("failed to annotate job diff: %v", err)
   965  		}
   966  		reply.Diff = jobDiff
   967  	}
   968  
   969  	// Grab the failures
   970  	if len(planner.Evals) != 1 {
   971  		return fmt.Errorf("scheduler resulted in an unexpected number of eval updates: %v", planner.Evals)
   972  	}
   973  	updatedEval := planner.Evals[0]
   974  
   975  	// If it is a periodic job calculate the next launch
   976  	if args.Job.IsPeriodic() && args.Job.Periodic.Enabled {
   977  		reply.NextPeriodicLaunch = args.Job.Periodic.Next(time.Now().In(args.Job.Periodic.GetLocation()))
   978  	}
   979  
   980  	reply.FailedTGAllocs = updatedEval.FailedTGAllocs
   981  	reply.JobModifyIndex = index
   982  	reply.Annotations = annotations
   983  	reply.CreatedEvals = planner.CreateEvals
   984  	reply.Index = index
   985  	return nil
   986  }
   987  
   988  // validateJob validates a Job and task drivers and returns an error if there is
   989  // a validation problem or if the Job is of a type a user is not allowed to
   990  // submit.
   991  func validateJob(job *structs.Job) (invalid, warnings error) {
   992  	validationErrors := new(multierror.Error)
   993  	if err := job.Validate(); err != nil {
   994  		multierror.Append(validationErrors, err)
   995  	}
   996  
   997  	// Get any warnings
   998  	warnings = job.Warnings()
   999  
  1000  	// Get the signals required
  1001  	signals := job.RequiredSignals()
  1002  
  1003  	// Validate the driver configurations.
  1004  	for _, tg := range job.TaskGroups {
  1005  		// Get the signals for the task group
  1006  		tgSignals, tgOk := signals[tg.Name]
  1007  
  1008  		for _, task := range tg.Tasks {
  1009  			d, err := driver.NewDriver(
  1010  				task.Driver,
  1011  				driver.NewEmptyDriverContext(),
  1012  			)
  1013  			if err != nil {
  1014  				msg := "failed to create driver for task %q in group %q for validation: %v"
  1015  				multierror.Append(validationErrors, fmt.Errorf(msg, tg.Name, task.Name, err))
  1016  				continue
  1017  			}
  1018  
  1019  			if err := d.Validate(task.Config); err != nil {
  1020  				formatted := fmt.Errorf("group %q -> task %q -> config: %v", tg.Name, task.Name, err)
  1021  				multierror.Append(validationErrors, formatted)
  1022  			}
  1023  
  1024  			// The task group didn't have any task that required signals
  1025  			if !tgOk {
  1026  				continue
  1027  			}
  1028  
  1029  			// This task requires signals. Ensure the driver is capable
  1030  			if required, ok := tgSignals[task.Name]; ok {
  1031  				abilities := d.Abilities()
  1032  				if !abilities.SendSignals {
  1033  					formatted := fmt.Errorf("group %q -> task %q: driver %q doesn't support sending signals. Requested signals are %v",
  1034  						tg.Name, task.Name, task.Driver, strings.Join(required, ", "))
  1035  					multierror.Append(validationErrors, formatted)
  1036  				}
  1037  			}
  1038  		}
  1039  	}
  1040  
  1041  	if job.Type == structs.JobTypeCore {
  1042  		multierror.Append(validationErrors, fmt.Errorf("job type cannot be core"))
  1043  	}
  1044  
  1045  	if len(job.Payload) != 0 {
  1046  		multierror.Append(validationErrors, fmt.Errorf("job can't be submitted with a payload, only dispatched"))
  1047  	}
  1048  
  1049  	return validationErrors.ErrorOrNil(), warnings
  1050  }
  1051  
  1052  // validateJobUpdate ensures updates to a job are valid.
  1053  func validateJobUpdate(old, new *structs.Job) error {
  1054  	// Type transitions are disallowed
  1055  	if old.Type != new.Type {
  1056  		return fmt.Errorf("cannot update job from type %q to %q", old.Type, new.Type)
  1057  	}
  1058  
  1059  	// Transitioning to/from periodic is disallowed
  1060  	if old.IsPeriodic() && !new.IsPeriodic() {
  1061  		return fmt.Errorf("cannot update non-periodic job to being periodic")
  1062  	}
  1063  	if new.IsPeriodic() && !old.IsPeriodic() {
  1064  		return fmt.Errorf("cannot update periodic job to being non-periodic")
  1065  	}
  1066  
  1067  	// Transitioning to/from parameterized is disallowed
  1068  	if old.IsParameterized() && !new.IsParameterized() {
  1069  		return fmt.Errorf("cannot update non-parameterized job to being parameterized")
  1070  	}
  1071  	if new.IsParameterized() && !old.IsParameterized() {
  1072  		return fmt.Errorf("cannot update parameterized job to being non-parameterized")
  1073  	}
  1074  
  1075  	return nil
  1076  }
  1077  
  1078  // Dispatch a parameterized job.
  1079  func (j *Job) Dispatch(args *structs.JobDispatchRequest, reply *structs.JobDispatchResponse) error {
  1080  	if done, err := j.srv.forward("Job.Dispatch", args, args, reply); done {
  1081  		return err
  1082  	}
  1083  	defer metrics.MeasureSince([]string{"nomad", "job", "dispatch"}, time.Now())
  1084  
  1085  	// Lookup the parameterized job
  1086  	if args.JobID == "" {
  1087  		return fmt.Errorf("missing parameterized job ID")
  1088  	}
  1089  
  1090  	snap, err := j.srv.fsm.State().Snapshot()
  1091  	if err != nil {
  1092  		return err
  1093  	}
  1094  	ws := memdb.NewWatchSet()
  1095  	parameterizedJob, err := snap.JobByID(ws, args.JobID)
  1096  	if err != nil {
  1097  		return err
  1098  	}
  1099  	if parameterizedJob == nil {
  1100  		return fmt.Errorf("parameterized job not found")
  1101  	}
  1102  
  1103  	if !parameterizedJob.IsParameterized() {
  1104  		return fmt.Errorf("Specified job %q is not a parameterized job", args.JobID)
  1105  	}
  1106  
  1107  	if parameterizedJob.Stop {
  1108  		return fmt.Errorf("Specified job %q is stopped", args.JobID)
  1109  	}
  1110  
  1111  	// Validate the arguments
  1112  	if err := validateDispatchRequest(args, parameterizedJob); err != nil {
  1113  		return err
  1114  	}
  1115  
  1116  	// Derive the child job and commit it via Raft
  1117  	dispatchJob := parameterizedJob.Copy()
  1118  	dispatchJob.ParameterizedJob = nil
  1119  	dispatchJob.ID = structs.DispatchedID(parameterizedJob.ID, time.Now())
  1120  	dispatchJob.ParentID = parameterizedJob.ID
  1121  	dispatchJob.Name = dispatchJob.ID
  1122  	dispatchJob.SetSubmitTime()
  1123  
  1124  	// Merge in the meta data
  1125  	for k, v := range args.Meta {
  1126  		if dispatchJob.Meta == nil {
  1127  			dispatchJob.Meta = make(map[string]string, len(args.Meta))
  1128  		}
  1129  		dispatchJob.Meta[k] = v
  1130  	}
  1131  
  1132  	// Compress the payload
  1133  	dispatchJob.Payload = snappy.Encode(nil, args.Payload)
  1134  
  1135  	regReq := &structs.JobRegisterRequest{
  1136  		Job:          dispatchJob,
  1137  		WriteRequest: args.WriteRequest,
  1138  	}
  1139  
  1140  	// Commit this update via Raft
  1141  	_, jobCreateIndex, err := j.srv.raftApply(structs.JobRegisterRequestType, regReq)
  1142  	if err != nil {
  1143  		j.srv.logger.Printf("[ERR] nomad.job: Dispatched job register failed: %v", err)
  1144  		return err
  1145  	}
  1146  
  1147  	reply.JobCreateIndex = jobCreateIndex
  1148  	reply.DispatchedJobID = dispatchJob.ID
  1149  	reply.Index = jobCreateIndex
  1150  
  1151  	// If the job is periodic, we don't create an eval.
  1152  	if !dispatchJob.IsPeriodic() {
  1153  		// Create a new evaluation
  1154  		eval := &structs.Evaluation{
  1155  			ID:             structs.GenerateUUID(),
  1156  			Priority:       dispatchJob.Priority,
  1157  			Type:           dispatchJob.Type,
  1158  			TriggeredBy:    structs.EvalTriggerJobRegister,
  1159  			JobID:          dispatchJob.ID,
  1160  			JobModifyIndex: jobCreateIndex,
  1161  			Status:         structs.EvalStatusPending,
  1162  		}
  1163  		update := &structs.EvalUpdateRequest{
  1164  			Evals:        []*structs.Evaluation{eval},
  1165  			WriteRequest: structs.WriteRequest{Region: args.Region},
  1166  		}
  1167  
  1168  		// Commit this evaluation via Raft
  1169  		_, evalIndex, err := j.srv.raftApply(structs.EvalUpdateRequestType, update)
  1170  		if err != nil {
  1171  			j.srv.logger.Printf("[ERR] nomad.job: Eval create failed: %v", err)
  1172  			return err
  1173  		}
  1174  
  1175  		// Setup the reply
  1176  		reply.EvalID = eval.ID
  1177  		reply.EvalCreateIndex = evalIndex
  1178  		reply.Index = evalIndex
  1179  	}
  1180  
  1181  	return nil
  1182  }
  1183  
  1184  // validateDispatchRequest returns whether the request is valid given the
  1185  // parameterized job.
  1186  func validateDispatchRequest(req *structs.JobDispatchRequest, job *structs.Job) error {
  1187  	// Check the payload constraint is met
  1188  	hasInputData := len(req.Payload) != 0
  1189  	if job.ParameterizedJob.Payload == structs.DispatchPayloadRequired && !hasInputData {
  1190  		return fmt.Errorf("Payload is not provided but required by parameterized job")
  1191  	} else if job.ParameterizedJob.Payload == structs.DispatchPayloadForbidden && hasInputData {
  1192  		return fmt.Errorf("Payload provided but forbidden by parameterized job")
  1193  	}
  1194  
  1195  	// Check the payload doesn't exceed the size limit
  1196  	if l := len(req.Payload); l > DispatchPayloadSizeLimit {
  1197  		return fmt.Errorf("Payload exceeds maximum size; %d > %d", l, DispatchPayloadSizeLimit)
  1198  	}
  1199  
  1200  	// Check if the metadata is a set
  1201  	keys := make(map[string]struct{}, len(req.Meta))
  1202  	for k := range keys {
  1203  		if _, ok := keys[k]; ok {
  1204  			return fmt.Errorf("Duplicate key %q in passed metadata", k)
  1205  		}
  1206  		keys[k] = struct{}{}
  1207  	}
  1208  
  1209  	required := helper.SliceStringToSet(job.ParameterizedJob.MetaRequired)
  1210  	optional := helper.SliceStringToSet(job.ParameterizedJob.MetaOptional)
  1211  
  1212  	// Check the metadata key constraints are met
  1213  	unpermitted := make(map[string]struct{})
  1214  	for k := range req.Meta {
  1215  		_, req := required[k]
  1216  		_, opt := optional[k]
  1217  		if !req && !opt {
  1218  			unpermitted[k] = struct{}{}
  1219  		}
  1220  	}
  1221  
  1222  	if len(unpermitted) != 0 {
  1223  		flat := make([]string, 0, len(unpermitted))
  1224  		for k := range unpermitted {
  1225  			flat = append(flat, k)
  1226  		}
  1227  
  1228  		return fmt.Errorf("Dispatch request included unpermitted metadata keys: %v", flat)
  1229  	}
  1230  
  1231  	missing := make(map[string]struct{})
  1232  	for _, k := range job.ParameterizedJob.MetaRequired {
  1233  		if _, ok := req.Meta[k]; !ok {
  1234  			missing[k] = struct{}{}
  1235  		}
  1236  	}
  1237  
  1238  	if len(missing) != 0 {
  1239  		flat := make([]string, 0, len(missing))
  1240  		for k := range missing {
  1241  			flat = append(flat, k)
  1242  		}
  1243  
  1244  		return fmt.Errorf("Dispatch did not provide required meta keys: %v", flat)
  1245  	}
  1246  
  1247  	return nil
  1248  }