github.com/taylorchu/nomad@v0.5.3-rc1.0.20170407200202-db11e7dd7b55/nomad/job_endpoint.go (about)

     1  package nomad
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"strings"
     7  	"time"
     8  
     9  	"github.com/armon/go-metrics"
    10  	"github.com/golang/snappy"
    11  	"github.com/hashicorp/consul/lib"
    12  	"github.com/hashicorp/go-memdb"
    13  	"github.com/hashicorp/go-multierror"
    14  	"github.com/hashicorp/nomad/client/driver"
    15  	"github.com/hashicorp/nomad/helper"
    16  	"github.com/hashicorp/nomad/nomad/state"
    17  	"github.com/hashicorp/nomad/nomad/structs"
    18  	"github.com/hashicorp/nomad/scheduler"
    19  )
    20  
    21  const (
    22  	// RegisterEnforceIndexErrPrefix is the prefix to use in errors caused by
    23  	// enforcing the job modify index during registers.
    24  	RegisterEnforceIndexErrPrefix = "Enforcing job modify index"
    25  
    26  	// DispatchPayloadSizeLimit is the maximum size of the uncompressed input
    27  	// data payload.
    28  	DispatchPayloadSizeLimit = 16 * 1024
    29  )
    30  
    31  var (
    32  	// vaultConstraint is the implicit constraint added to jobs requesting a
    33  	// Vault token
    34  	vaultConstraint = &structs.Constraint{
    35  		LTarget: "${attr.vault.version}",
    36  		RTarget: ">= 0.6.1",
    37  		Operand: structs.ConstraintVersion,
    38  	}
    39  )
    40  
    41  // Job endpoint is used for job interactions
    42  type Job struct {
    43  	srv *Server
    44  }
    45  
    46  // Register is used to upsert a job for scheduling
    47  func (j *Job) Register(args *structs.JobRegisterRequest, reply *structs.JobRegisterResponse) error {
    48  	if done, err := j.srv.forward("Job.Register", args, args, reply); done {
    49  		return err
    50  	}
    51  	defer metrics.MeasureSince([]string{"nomad", "job", "register"}, time.Now())
    52  
    53  	// Validate the arguments
    54  	if args.Job == nil {
    55  		return fmt.Errorf("missing job for registration")
    56  	}
    57  
    58  	// Initialize the job fields (sets defaults and any necessary init work).
    59  	args.Job.Canonicalize()
    60  
    61  	// Add implicit constraints
    62  	setImplicitConstraints(args.Job)
    63  
    64  	// Validate the job.
    65  	if err := validateJob(args.Job); err != nil {
    66  		return err
    67  	}
    68  
    69  	if args.EnforceIndex {
    70  		// Lookup the job
    71  		snap, err := j.srv.fsm.State().Snapshot()
    72  		if err != nil {
    73  			return err
    74  		}
    75  		ws := memdb.NewWatchSet()
    76  		job, err := snap.JobByID(ws, args.Job.ID)
    77  		if err != nil {
    78  			return err
    79  		}
    80  		jmi := args.JobModifyIndex
    81  		if job != nil {
    82  			if jmi == 0 {
    83  				return fmt.Errorf("%s 0: job already exists", RegisterEnforceIndexErrPrefix)
    84  			} else if jmi != job.JobModifyIndex {
    85  				return fmt.Errorf("%s %d: job exists with conflicting job modify index: %d",
    86  					RegisterEnforceIndexErrPrefix, jmi, job.JobModifyIndex)
    87  			}
    88  		} else if jmi != 0 {
    89  			return fmt.Errorf("%s %d: job does not exist", RegisterEnforceIndexErrPrefix, jmi)
    90  		}
    91  	}
    92  
    93  	// Ensure that the job has permissions for the requested Vault tokens
    94  	policies := args.Job.VaultPolicies()
    95  	if len(policies) != 0 {
    96  		vconf := j.srv.config.VaultConfig
    97  		if !vconf.IsEnabled() {
    98  			return fmt.Errorf("Vault not enabled and Vault policies requested")
    99  		}
   100  
   101  		// Have to check if the user has permissions
   102  		if !vconf.AllowsUnauthenticated() {
   103  			if args.Job.VaultToken == "" {
   104  				return fmt.Errorf("Vault policies requested but missing Vault Token")
   105  			}
   106  
   107  			vault := j.srv.vault
   108  			s, err := vault.LookupToken(context.Background(), args.Job.VaultToken)
   109  			if err != nil {
   110  				return err
   111  			}
   112  
   113  			allowedPolicies, err := PoliciesFrom(s)
   114  			if err != nil {
   115  				return err
   116  			}
   117  
   118  			// If we are given a root token it can access all policies
   119  			if !lib.StrContains(allowedPolicies, "root") {
   120  				flatPolicies := structs.VaultPoliciesSet(policies)
   121  				subset, offending := helper.SliceStringIsSubset(allowedPolicies, flatPolicies)
   122  				if !subset {
   123  					return fmt.Errorf("Passed Vault Token doesn't allow access to the following policies: %s",
   124  						strings.Join(offending, ", "))
   125  				}
   126  			}
   127  		}
   128  	}
   129  
   130  	// Clear the Vault token
   131  	args.Job.VaultToken = ""
   132  
   133  	// Commit this update via Raft
   134  	_, index, err := j.srv.raftApply(structs.JobRegisterRequestType, args)
   135  	if err != nil {
   136  		j.srv.logger.Printf("[ERR] nomad.job: Register failed: %v", err)
   137  		return err
   138  	}
   139  
   140  	// Populate the reply with job information
   141  	reply.JobModifyIndex = index
   142  
   143  	// If the job is periodic or parameterized, we don't create an eval.
   144  	if args.Job.IsPeriodic() || args.Job.IsParameterized() {
   145  		return nil
   146  	}
   147  
   148  	// Create a new evaluation
   149  	eval := &structs.Evaluation{
   150  		ID:             structs.GenerateUUID(),
   151  		Priority:       args.Job.Priority,
   152  		Type:           args.Job.Type,
   153  		TriggeredBy:    structs.EvalTriggerJobRegister,
   154  		JobID:          args.Job.ID,
   155  		JobModifyIndex: index,
   156  		Status:         structs.EvalStatusPending,
   157  	}
   158  	update := &structs.EvalUpdateRequest{
   159  		Evals:        []*structs.Evaluation{eval},
   160  		WriteRequest: structs.WriteRequest{Region: args.Region},
   161  	}
   162  
   163  	// Commit this evaluation via Raft
   164  	// XXX: There is a risk of partial failure where the JobRegister succeeds
   165  	// but that the EvalUpdate does not.
   166  	_, evalIndex, err := j.srv.raftApply(structs.EvalUpdateRequestType, update)
   167  	if err != nil {
   168  		j.srv.logger.Printf("[ERR] nomad.job: Eval create failed: %v", err)
   169  		return err
   170  	}
   171  
   172  	// Populate the reply with eval information
   173  	reply.EvalID = eval.ID
   174  	reply.EvalCreateIndex = evalIndex
   175  	reply.Index = evalIndex
   176  	return nil
   177  }
   178  
   179  // setImplicitConstraints adds implicit constraints to the job based on the
   180  // features it is requesting.
   181  func setImplicitConstraints(j *structs.Job) {
   182  	// Get the required Vault Policies
   183  	policies := j.VaultPolicies()
   184  
   185  	// Get the required signals
   186  	signals := j.RequiredSignals()
   187  
   188  	// Hot path
   189  	if len(signals) == 0 && len(policies) == 0 {
   190  		return
   191  	}
   192  
   193  	// Add Vault constraints
   194  	for _, tg := range j.TaskGroups {
   195  		_, ok := policies[tg.Name]
   196  		if !ok {
   197  			// Not requesting Vault
   198  			continue
   199  		}
   200  
   201  		found := false
   202  		for _, c := range tg.Constraints {
   203  			if c.Equal(vaultConstraint) {
   204  				found = true
   205  				break
   206  			}
   207  		}
   208  
   209  		if !found {
   210  			tg.Constraints = append(tg.Constraints, vaultConstraint)
   211  		}
   212  	}
   213  
   214  	// Add signal constraints
   215  	for _, tg := range j.TaskGroups {
   216  		tgSignals, ok := signals[tg.Name]
   217  		if !ok {
   218  			// Not requesting Vault
   219  			continue
   220  		}
   221  
   222  		// Flatten the signals
   223  		required := helper.MapStringStringSliceValueSet(tgSignals)
   224  		sigConstraint := getSignalConstraint(required)
   225  
   226  		found := false
   227  		for _, c := range tg.Constraints {
   228  			if c.Equal(sigConstraint) {
   229  				found = true
   230  				break
   231  			}
   232  		}
   233  
   234  		if !found {
   235  			tg.Constraints = append(tg.Constraints, sigConstraint)
   236  		}
   237  	}
   238  }
   239  
   240  // getSignalConstraint builds a suitable constraint based on the required
   241  // signals
   242  func getSignalConstraint(signals []string) *structs.Constraint {
   243  	return &structs.Constraint{
   244  		Operand: structs.ConstraintSetContains,
   245  		LTarget: "${attr.os.signals}",
   246  		RTarget: strings.Join(signals, ","),
   247  	}
   248  }
   249  
   250  // Summary retreives the summary of a job
   251  func (j *Job) Summary(args *structs.JobSummaryRequest,
   252  	reply *structs.JobSummaryResponse) error {
   253  	if done, err := j.srv.forward("Job.Summary", args, args, reply); done {
   254  		return err
   255  	}
   256  	defer metrics.MeasureSince([]string{"nomad", "job_summary", "get_job_summary"}, time.Now())
   257  	// Setup the blocking query
   258  	opts := blockingOptions{
   259  		queryOpts: &args.QueryOptions,
   260  		queryMeta: &reply.QueryMeta,
   261  		run: func(ws memdb.WatchSet, state *state.StateStore) error {
   262  			// Look for job summary
   263  			out, err := state.JobSummaryByID(ws, args.JobID)
   264  			if err != nil {
   265  				return err
   266  			}
   267  
   268  			// Setup the output
   269  			reply.JobSummary = out
   270  			if out != nil {
   271  				reply.Index = out.ModifyIndex
   272  			} else {
   273  				// Use the last index that affected the job_summary table
   274  				index, err := state.Index("job_summary")
   275  				if err != nil {
   276  					return err
   277  				}
   278  				reply.Index = index
   279  			}
   280  
   281  			// Set the query response
   282  			j.srv.setQueryMeta(&reply.QueryMeta)
   283  			return nil
   284  		}}
   285  	return j.srv.blockingRPC(&opts)
   286  }
   287  
   288  // Validate validates a job
   289  func (j *Job) Validate(args *structs.JobValidateRequest,
   290  	reply *structs.JobValidateResponse) error {
   291  
   292  	if err := validateJob(args.Job); err != nil {
   293  		if merr, ok := err.(*multierror.Error); ok {
   294  			for _, err := range merr.Errors {
   295  				reply.ValidationErrors = append(reply.ValidationErrors, err.Error())
   296  			}
   297  			reply.Error = merr.Error()
   298  		} else {
   299  			reply.ValidationErrors = append(reply.ValidationErrors, err.Error())
   300  			reply.Error = err.Error()
   301  		}
   302  	}
   303  	reply.DriverConfigValidated = true
   304  	return nil
   305  }
   306  
   307  // Evaluate is used to force a job for re-evaluation
   308  func (j *Job) Evaluate(args *structs.JobEvaluateRequest, reply *structs.JobRegisterResponse) error {
   309  	if done, err := j.srv.forward("Job.Evaluate", args, args, reply); done {
   310  		return err
   311  	}
   312  	defer metrics.MeasureSince([]string{"nomad", "job", "evaluate"}, time.Now())
   313  
   314  	// Validate the arguments
   315  	if args.JobID == "" {
   316  		return fmt.Errorf("missing job ID for evaluation")
   317  	}
   318  
   319  	// Lookup the job
   320  	snap, err := j.srv.fsm.State().Snapshot()
   321  	if err != nil {
   322  		return err
   323  	}
   324  	ws := memdb.NewWatchSet()
   325  	job, err := snap.JobByID(ws, args.JobID)
   326  	if err != nil {
   327  		return err
   328  	}
   329  	if job == nil {
   330  		return fmt.Errorf("job not found")
   331  	}
   332  
   333  	if job.IsPeriodic() {
   334  		return fmt.Errorf("can't evaluate periodic job")
   335  	} else if job.IsParameterized() {
   336  		return fmt.Errorf("can't evaluate parameterized job")
   337  	}
   338  
   339  	// Create a new evaluation
   340  	eval := &structs.Evaluation{
   341  		ID:             structs.GenerateUUID(),
   342  		Priority:       job.Priority,
   343  		Type:           job.Type,
   344  		TriggeredBy:    structs.EvalTriggerJobRegister,
   345  		JobID:          job.ID,
   346  		JobModifyIndex: job.ModifyIndex,
   347  		Status:         structs.EvalStatusPending,
   348  	}
   349  	update := &structs.EvalUpdateRequest{
   350  		Evals:        []*structs.Evaluation{eval},
   351  		WriteRequest: structs.WriteRequest{Region: args.Region},
   352  	}
   353  
   354  	// Commit this evaluation via Raft
   355  	_, evalIndex, err := j.srv.raftApply(structs.EvalUpdateRequestType, update)
   356  	if err != nil {
   357  		j.srv.logger.Printf("[ERR] nomad.job: Eval create failed: %v", err)
   358  		return err
   359  	}
   360  
   361  	// Setup the reply
   362  	reply.EvalID = eval.ID
   363  	reply.EvalCreateIndex = evalIndex
   364  	reply.JobModifyIndex = job.ModifyIndex
   365  	reply.Index = evalIndex
   366  	return nil
   367  }
   368  
   369  // Deregister is used to remove a job the cluster.
   370  func (j *Job) Deregister(args *structs.JobDeregisterRequest, reply *structs.JobDeregisterResponse) error {
   371  	if done, err := j.srv.forward("Job.Deregister", args, args, reply); done {
   372  		return err
   373  	}
   374  	defer metrics.MeasureSince([]string{"nomad", "job", "deregister"}, time.Now())
   375  
   376  	// Validate the arguments
   377  	if args.JobID == "" {
   378  		return fmt.Errorf("missing job ID for evaluation")
   379  	}
   380  
   381  	// Lookup the job
   382  	snap, err := j.srv.fsm.State().Snapshot()
   383  	if err != nil {
   384  		return err
   385  	}
   386  	ws := memdb.NewWatchSet()
   387  	job, err := snap.JobByID(ws, args.JobID)
   388  	if err != nil {
   389  		return err
   390  	}
   391  
   392  	// Commit this update via Raft
   393  	_, index, err := j.srv.raftApply(structs.JobDeregisterRequestType, args)
   394  	if err != nil {
   395  		j.srv.logger.Printf("[ERR] nomad.job: Deregister failed: %v", err)
   396  		return err
   397  	}
   398  
   399  	// Populate the reply with job information
   400  	reply.JobModifyIndex = index
   401  
   402  	// If the job is periodic or parameterized, we don't create an eval.
   403  	if job != nil && (job.IsPeriodic() || job.IsParameterized()) {
   404  		return nil
   405  	}
   406  
   407  	// Create a new evaluation
   408  	// XXX: The job priority / type is strange for this, since it's not a high
   409  	// priority even if the job was. The scheduler itself also doesn't matter,
   410  	// since all should be able to handle deregistration in the same way.
   411  	eval := &structs.Evaluation{
   412  		ID:             structs.GenerateUUID(),
   413  		Priority:       structs.JobDefaultPriority,
   414  		Type:           structs.JobTypeService,
   415  		TriggeredBy:    structs.EvalTriggerJobDeregister,
   416  		JobID:          args.JobID,
   417  		JobModifyIndex: index,
   418  		Status:         structs.EvalStatusPending,
   419  	}
   420  	update := &structs.EvalUpdateRequest{
   421  		Evals:        []*structs.Evaluation{eval},
   422  		WriteRequest: structs.WriteRequest{Region: args.Region},
   423  	}
   424  
   425  	// Commit this evaluation via Raft
   426  	_, evalIndex, err := j.srv.raftApply(structs.EvalUpdateRequestType, update)
   427  	if err != nil {
   428  		j.srv.logger.Printf("[ERR] nomad.job: Eval create failed: %v", err)
   429  		return err
   430  	}
   431  
   432  	// Populate the reply with eval information
   433  	reply.EvalID = eval.ID
   434  	reply.EvalCreateIndex = evalIndex
   435  	reply.Index = evalIndex
   436  	return nil
   437  }
   438  
   439  // GetJob is used to request information about a specific job
   440  func (j *Job) GetJob(args *structs.JobSpecificRequest,
   441  	reply *structs.SingleJobResponse) error {
   442  	if done, err := j.srv.forward("Job.GetJob", args, args, reply); done {
   443  		return err
   444  	}
   445  	defer metrics.MeasureSince([]string{"nomad", "job", "get_job"}, time.Now())
   446  
   447  	// Setup the blocking query
   448  	opts := blockingOptions{
   449  		queryOpts: &args.QueryOptions,
   450  		queryMeta: &reply.QueryMeta,
   451  		run: func(ws memdb.WatchSet, state *state.StateStore) error {
   452  			// Look for the job
   453  			out, err := state.JobByID(ws, args.JobID)
   454  			if err != nil {
   455  				return err
   456  			}
   457  
   458  			// Setup the output
   459  			reply.Job = out
   460  			if out != nil {
   461  				reply.Index = out.ModifyIndex
   462  			} else {
   463  				// Use the last index that affected the nodes table
   464  				index, err := state.Index("jobs")
   465  				if err != nil {
   466  					return err
   467  				}
   468  				reply.Index = index
   469  			}
   470  
   471  			// Set the query response
   472  			j.srv.setQueryMeta(&reply.QueryMeta)
   473  			return nil
   474  		}}
   475  	return j.srv.blockingRPC(&opts)
   476  }
   477  
   478  // List is used to list the jobs registered in the system
   479  func (j *Job) List(args *structs.JobListRequest,
   480  	reply *structs.JobListResponse) error {
   481  	if done, err := j.srv.forward("Job.List", args, args, reply); done {
   482  		return err
   483  	}
   484  	defer metrics.MeasureSince([]string{"nomad", "job", "list"}, time.Now())
   485  
   486  	// Setup the blocking query
   487  	opts := blockingOptions{
   488  		queryOpts: &args.QueryOptions,
   489  		queryMeta: &reply.QueryMeta,
   490  		run: func(ws memdb.WatchSet, state *state.StateStore) error {
   491  			// Capture all the jobs
   492  			var err error
   493  			var iter memdb.ResultIterator
   494  			if prefix := args.QueryOptions.Prefix; prefix != "" {
   495  				iter, err = state.JobsByIDPrefix(ws, prefix)
   496  			} else {
   497  				iter, err = state.Jobs(ws)
   498  			}
   499  			if err != nil {
   500  				return err
   501  			}
   502  
   503  			var jobs []*structs.JobListStub
   504  			for {
   505  				raw := iter.Next()
   506  				if raw == nil {
   507  					break
   508  				}
   509  				job := raw.(*structs.Job)
   510  				summary, err := state.JobSummaryByID(ws, job.ID)
   511  				if err != nil {
   512  					return fmt.Errorf("unable to look up summary for job: %v", job.ID)
   513  				}
   514  				jobs = append(jobs, job.Stub(summary))
   515  			}
   516  			reply.Jobs = jobs
   517  
   518  			// Use the last index that affected the jobs table
   519  			index, err := state.Index("jobs")
   520  			if err != nil {
   521  				return err
   522  			}
   523  			reply.Index = index
   524  
   525  			// Set the query response
   526  			j.srv.setQueryMeta(&reply.QueryMeta)
   527  			return nil
   528  		}}
   529  	return j.srv.blockingRPC(&opts)
   530  }
   531  
   532  // Allocations is used to list the allocations for a job
   533  func (j *Job) Allocations(args *structs.JobSpecificRequest,
   534  	reply *structs.JobAllocationsResponse) error {
   535  	if done, err := j.srv.forward("Job.Allocations", args, args, reply); done {
   536  		return err
   537  	}
   538  	defer metrics.MeasureSince([]string{"nomad", "job", "allocations"}, time.Now())
   539  
   540  	// Setup the blocking query
   541  	opts := blockingOptions{
   542  		queryOpts: &args.QueryOptions,
   543  		queryMeta: &reply.QueryMeta,
   544  		run: func(ws memdb.WatchSet, state *state.StateStore) error {
   545  			// Capture the allocations
   546  			allocs, err := state.AllocsByJob(ws, args.JobID, args.AllAllocs)
   547  			if err != nil {
   548  				return err
   549  			}
   550  
   551  			// Convert to stubs
   552  			if len(allocs) > 0 {
   553  				reply.Allocations = make([]*structs.AllocListStub, 0, len(allocs))
   554  				for _, alloc := range allocs {
   555  					reply.Allocations = append(reply.Allocations, alloc.Stub())
   556  				}
   557  			}
   558  
   559  			// Use the last index that affected the allocs table
   560  			index, err := state.Index("allocs")
   561  			if err != nil {
   562  				return err
   563  			}
   564  			reply.Index = index
   565  
   566  			// Set the query response
   567  			j.srv.setQueryMeta(&reply.QueryMeta)
   568  			return nil
   569  
   570  		}}
   571  	return j.srv.blockingRPC(&opts)
   572  }
   573  
   574  // Evaluations is used to list the evaluations for a job
   575  func (j *Job) Evaluations(args *structs.JobSpecificRequest,
   576  	reply *structs.JobEvaluationsResponse) error {
   577  	if done, err := j.srv.forward("Job.Evaluations", args, args, reply); done {
   578  		return err
   579  	}
   580  	defer metrics.MeasureSince([]string{"nomad", "job", "evaluations"}, time.Now())
   581  
   582  	// Setup the blocking query
   583  	opts := blockingOptions{
   584  		queryOpts: &args.QueryOptions,
   585  		queryMeta: &reply.QueryMeta,
   586  		run: func(ws memdb.WatchSet, state *state.StateStore) error {
   587  			// Capture the evals
   588  			var err error
   589  			reply.Evaluations, err = state.EvalsByJob(ws, args.JobID)
   590  			if err != nil {
   591  				return err
   592  			}
   593  
   594  			// Use the last index that affected the evals table
   595  			index, err := state.Index("evals")
   596  			if err != nil {
   597  				return err
   598  			}
   599  			reply.Index = index
   600  
   601  			// Set the query response
   602  			j.srv.setQueryMeta(&reply.QueryMeta)
   603  			return nil
   604  		}}
   605  
   606  	return j.srv.blockingRPC(&opts)
   607  }
   608  
   609  // Plan is used to cause a dry-run evaluation of the Job and return the results
   610  // with a potential diff containing annotations.
   611  func (j *Job) Plan(args *structs.JobPlanRequest, reply *structs.JobPlanResponse) error {
   612  	if done, err := j.srv.forward("Job.Plan", args, args, reply); done {
   613  		return err
   614  	}
   615  	defer metrics.MeasureSince([]string{"nomad", "job", "plan"}, time.Now())
   616  
   617  	// Validate the arguments
   618  	if args.Job == nil {
   619  		return fmt.Errorf("Job required for plan")
   620  	}
   621  
   622  	// Initialize the job fields (sets defaults and any necessary init work).
   623  	args.Job.Canonicalize()
   624  
   625  	// Add implicit constraints
   626  	setImplicitConstraints(args.Job)
   627  
   628  	// Validate the job.
   629  	if err := validateJob(args.Job); err != nil {
   630  		return err
   631  	}
   632  
   633  	// Acquire a snapshot of the state
   634  	snap, err := j.srv.fsm.State().Snapshot()
   635  	if err != nil {
   636  		return err
   637  	}
   638  
   639  	// Get the original job
   640  	ws := memdb.NewWatchSet()
   641  	oldJob, err := snap.JobByID(ws, args.Job.ID)
   642  	if err != nil {
   643  		return err
   644  	}
   645  
   646  	var index uint64
   647  	var updatedIndex uint64
   648  	if oldJob != nil {
   649  		index = oldJob.JobModifyIndex
   650  		updatedIndex = oldJob.JobModifyIndex + 1
   651  	}
   652  
   653  	// Insert the updated Job into the snapshot
   654  	snap.UpsertJob(updatedIndex, args.Job)
   655  
   656  	// Create an eval and mark it as requiring annotations and insert that as well
   657  	eval := &structs.Evaluation{
   658  		ID:             structs.GenerateUUID(),
   659  		Priority:       args.Job.Priority,
   660  		Type:           args.Job.Type,
   661  		TriggeredBy:    structs.EvalTriggerJobRegister,
   662  		JobID:          args.Job.ID,
   663  		JobModifyIndex: updatedIndex,
   664  		Status:         structs.EvalStatusPending,
   665  		AnnotatePlan:   true,
   666  	}
   667  
   668  	// Create an in-memory Planner that returns no errors and stores the
   669  	// submitted plan and created evals.
   670  	planner := &scheduler.Harness{
   671  		State: &snap.StateStore,
   672  	}
   673  
   674  	// Create the scheduler and run it
   675  	sched, err := scheduler.NewScheduler(eval.Type, j.srv.logger, snap, planner)
   676  	if err != nil {
   677  		return err
   678  	}
   679  
   680  	if err := sched.Process(eval); err != nil {
   681  		return err
   682  	}
   683  
   684  	// Annotate and store the diff
   685  	if plans := len(planner.Plans); plans != 1 {
   686  		return fmt.Errorf("scheduler resulted in an unexpected number of plans: %v", plans)
   687  	}
   688  	annotations := planner.Plans[0].Annotations
   689  	if args.Diff {
   690  		jobDiff, err := oldJob.Diff(args.Job, true)
   691  		if err != nil {
   692  			return fmt.Errorf("failed to create job diff: %v", err)
   693  		}
   694  
   695  		if err := scheduler.Annotate(jobDiff, annotations); err != nil {
   696  			return fmt.Errorf("failed to annotate job diff: %v", err)
   697  		}
   698  		reply.Diff = jobDiff
   699  	}
   700  
   701  	// Grab the failures
   702  	if len(planner.Evals) != 1 {
   703  		return fmt.Errorf("scheduler resulted in an unexpected number of eval updates: %v", planner.Evals)
   704  	}
   705  	updatedEval := planner.Evals[0]
   706  
   707  	// If it is a periodic job calculate the next launch
   708  	if args.Job.IsPeriodic() && args.Job.Periodic.Enabled {
   709  		reply.NextPeriodicLaunch = args.Job.Periodic.Next(time.Now().In(args.Job.Periodic.GetLocation()))
   710  	}
   711  
   712  	reply.FailedTGAllocs = updatedEval.FailedTGAllocs
   713  	reply.JobModifyIndex = index
   714  	reply.Annotations = annotations
   715  	reply.CreatedEvals = planner.CreateEvals
   716  	reply.Index = index
   717  	return nil
   718  }
   719  
   720  // validateJob validates a Job and task drivers and returns an error if there is
   721  // a validation problem or if the Job is of a type a user is not allowed to
   722  // submit.
   723  func validateJob(job *structs.Job) error {
   724  	validationErrors := new(multierror.Error)
   725  	if err := job.Validate(); err != nil {
   726  		multierror.Append(validationErrors, err)
   727  	}
   728  
   729  	// Get the signals required
   730  	signals := job.RequiredSignals()
   731  
   732  	// Validate the driver configurations.
   733  	for _, tg := range job.TaskGroups {
   734  		// Get the signals for the task group
   735  		tgSignals, tgOk := signals[tg.Name]
   736  
   737  		for _, task := range tg.Tasks {
   738  			d, err := driver.NewDriver(
   739  				task.Driver,
   740  				driver.NewEmptyDriverContext(),
   741  			)
   742  			if err != nil {
   743  				msg := "failed to create driver for task %q in group %q for validation: %v"
   744  				multierror.Append(validationErrors, fmt.Errorf(msg, tg.Name, task.Name, err))
   745  				continue
   746  			}
   747  
   748  			if err := d.Validate(task.Config); err != nil {
   749  				formatted := fmt.Errorf("group %q -> task %q -> config: %v", tg.Name, task.Name, err)
   750  				multierror.Append(validationErrors, formatted)
   751  			}
   752  
   753  			// The task group didn't have any task that required signals
   754  			if !tgOk {
   755  				continue
   756  			}
   757  
   758  			// This task requires signals. Ensure the driver is capable
   759  			if required, ok := tgSignals[task.Name]; ok {
   760  				abilities := d.Abilities()
   761  				if !abilities.SendSignals {
   762  					formatted := fmt.Errorf("group %q -> task %q: driver %q doesn't support sending signals. Requested signals are %v",
   763  						tg.Name, task.Name, task.Driver, strings.Join(required, ", "))
   764  					multierror.Append(validationErrors, formatted)
   765  				}
   766  			}
   767  		}
   768  	}
   769  
   770  	if job.Type == structs.JobTypeCore {
   771  		multierror.Append(validationErrors, fmt.Errorf("job type cannot be core"))
   772  	}
   773  
   774  	if len(job.Payload) != 0 {
   775  		multierror.Append(validationErrors, fmt.Errorf("job can't be submitted with a payload, only dispatched"))
   776  	}
   777  
   778  	return validationErrors.ErrorOrNil()
   779  }
   780  
   781  // Dispatch a parameterized job.
   782  func (j *Job) Dispatch(args *structs.JobDispatchRequest, reply *structs.JobDispatchResponse) error {
   783  	if done, err := j.srv.forward("Job.Dispatch", args, args, reply); done {
   784  		return err
   785  	}
   786  	defer metrics.MeasureSince([]string{"nomad", "job", "dispatch"}, time.Now())
   787  
   788  	// Lookup the parameterized job
   789  	if args.JobID == "" {
   790  		return fmt.Errorf("missing parameterized job ID")
   791  	}
   792  
   793  	snap, err := j.srv.fsm.State().Snapshot()
   794  	if err != nil {
   795  		return err
   796  	}
   797  	ws := memdb.NewWatchSet()
   798  	parameterizedJob, err := snap.JobByID(ws, args.JobID)
   799  	if err != nil {
   800  		return err
   801  	}
   802  	if parameterizedJob == nil {
   803  		return fmt.Errorf("parameterized job not found")
   804  	}
   805  
   806  	if !parameterizedJob.IsParameterized() {
   807  		return fmt.Errorf("Specified job %q is not a parameterized job", args.JobID)
   808  	}
   809  
   810  	// Validate the arguments
   811  	if err := validateDispatchRequest(args, parameterizedJob); err != nil {
   812  		return err
   813  	}
   814  
   815  	// Derive the child job and commit it via Raft
   816  	dispatchJob := parameterizedJob.Copy()
   817  	dispatchJob.ParameterizedJob = nil
   818  	dispatchJob.ID = structs.DispatchedID(parameterizedJob.ID, time.Now())
   819  	dispatchJob.ParentID = parameterizedJob.ID
   820  	dispatchJob.Name = dispatchJob.ID
   821  
   822  	// Merge in the meta data
   823  	for k, v := range args.Meta {
   824  		if dispatchJob.Meta == nil {
   825  			dispatchJob.Meta = make(map[string]string, len(args.Meta))
   826  		}
   827  		dispatchJob.Meta[k] = v
   828  	}
   829  
   830  	// Compress the payload
   831  	dispatchJob.Payload = snappy.Encode(nil, args.Payload)
   832  
   833  	regReq := &structs.JobRegisterRequest{
   834  		Job:          dispatchJob,
   835  		WriteRequest: args.WriteRequest,
   836  	}
   837  
   838  	// Commit this update via Raft
   839  	_, jobCreateIndex, err := j.srv.raftApply(structs.JobRegisterRequestType, regReq)
   840  	if err != nil {
   841  		j.srv.logger.Printf("[ERR] nomad.job: Dispatched job register failed: %v", err)
   842  		return err
   843  	}
   844  
   845  	reply.JobCreateIndex = jobCreateIndex
   846  	reply.DispatchedJobID = dispatchJob.ID
   847  	reply.Index = jobCreateIndex
   848  
   849  	// If the job is periodic, we don't create an eval.
   850  	if !dispatchJob.IsPeriodic() {
   851  		// Create a new evaluation
   852  		eval := &structs.Evaluation{
   853  			ID:             structs.GenerateUUID(),
   854  			Priority:       dispatchJob.Priority,
   855  			Type:           dispatchJob.Type,
   856  			TriggeredBy:    structs.EvalTriggerJobRegister,
   857  			JobID:          dispatchJob.ID,
   858  			JobModifyIndex: jobCreateIndex,
   859  			Status:         structs.EvalStatusPending,
   860  		}
   861  		update := &structs.EvalUpdateRequest{
   862  			Evals:        []*structs.Evaluation{eval},
   863  			WriteRequest: structs.WriteRequest{Region: args.Region},
   864  		}
   865  
   866  		// Commit this evaluation via Raft
   867  		_, evalIndex, err := j.srv.raftApply(structs.EvalUpdateRequestType, update)
   868  		if err != nil {
   869  			j.srv.logger.Printf("[ERR] nomad.job: Eval create failed: %v", err)
   870  			return err
   871  		}
   872  
   873  		// Setup the reply
   874  		reply.EvalID = eval.ID
   875  		reply.EvalCreateIndex = evalIndex
   876  		reply.Index = evalIndex
   877  	}
   878  
   879  	return nil
   880  }
   881  
   882  // validateDispatchRequest returns whether the request is valid given the
   883  // parameterized job.
   884  func validateDispatchRequest(req *structs.JobDispatchRequest, job *structs.Job) error {
   885  	// Check the payload constraint is met
   886  	hasInputData := len(req.Payload) != 0
   887  	if job.ParameterizedJob.Payload == structs.DispatchPayloadRequired && !hasInputData {
   888  		return fmt.Errorf("Payload is not provided but required by parameterized job")
   889  	} else if job.ParameterizedJob.Payload == structs.DispatchPayloadForbidden && hasInputData {
   890  		return fmt.Errorf("Payload provided but forbidden by parameterized job")
   891  	}
   892  
   893  	// Check the payload doesn't exceed the size limit
   894  	if l := len(req.Payload); l > DispatchPayloadSizeLimit {
   895  		return fmt.Errorf("Payload exceeds maximum size; %d > %d", l, DispatchPayloadSizeLimit)
   896  	}
   897  
   898  	// Check if the metadata is a set
   899  	keys := make(map[string]struct{}, len(req.Meta))
   900  	for k := range keys {
   901  		if _, ok := keys[k]; ok {
   902  			return fmt.Errorf("Duplicate key %q in passed metadata", k)
   903  		}
   904  		keys[k] = struct{}{}
   905  	}
   906  
   907  	required := helper.SliceStringToSet(job.ParameterizedJob.MetaRequired)
   908  	optional := helper.SliceStringToSet(job.ParameterizedJob.MetaOptional)
   909  
   910  	// Check the metadata key constraints are met
   911  	unpermitted := make(map[string]struct{})
   912  	for k := range req.Meta {
   913  		_, req := required[k]
   914  		_, opt := optional[k]
   915  		if !req && !opt {
   916  			unpermitted[k] = struct{}{}
   917  		}
   918  	}
   919  
   920  	if len(unpermitted) != 0 {
   921  		flat := make([]string, 0, len(unpermitted))
   922  		for k := range unpermitted {
   923  			flat = append(flat, k)
   924  		}
   925  
   926  		return fmt.Errorf("Dispatch request included unpermitted metadata keys: %v", flat)
   927  	}
   928  
   929  	missing := make(map[string]struct{})
   930  	for _, k := range job.ParameterizedJob.MetaRequired {
   931  		if _, ok := req.Meta[k]; !ok {
   932  			missing[k] = struct{}{}
   933  		}
   934  	}
   935  
   936  	if len(missing) != 0 {
   937  		flat := make([]string, 0, len(missing))
   938  		for k := range missing {
   939  			flat = append(flat, k)
   940  		}
   941  
   942  		return fmt.Errorf("Dispatch did not provide required meta keys: %v", flat)
   943  	}
   944  
   945  	return nil
   946  }