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