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