github.com/diptanu/nomad@v0.5.7-0.20170516172507-d72e86cbe3d9/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, reply *structs.JobValidateResponse) error {
   290  	defer metrics.MeasureSince([]string{"nomad", "job", "validate"}, time.Now())
   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  
   304  	reply.DriverConfigValidated = true
   305  	return nil
   306  }
   307  
   308  // Revert is used to revert the job to a prior version
   309  func (j *Job) Revert(args *structs.JobRevertRequest, reply *structs.JobRegisterResponse) error {
   310  	if done, err := j.srv.forward("Job.Revert", args, args, reply); done {
   311  		return err
   312  	}
   313  	defer metrics.MeasureSince([]string{"nomad", "job", "revert"}, time.Now())
   314  
   315  	// Validate the arguments
   316  	if args.JobID == "" {
   317  		return fmt.Errorf("missing job ID for evaluation")
   318  	}
   319  
   320  	// Lookup the job by version
   321  	snap, err := j.srv.fsm.State().Snapshot()
   322  	if err != nil {
   323  		return err
   324  	}
   325  
   326  	ws := memdb.NewWatchSet()
   327  	cur, err := snap.JobByID(ws, args.JobID)
   328  	if err != nil {
   329  		return err
   330  	}
   331  	if cur == nil {
   332  		return fmt.Errorf("job %q not found", args.JobID)
   333  	}
   334  	if args.JobVersion == cur.Version {
   335  		return fmt.Errorf("can't revert to current version")
   336  	}
   337  
   338  	jobV, err := snap.JobByIDAndVersion(ws, args.JobID, args.JobVersion)
   339  	if err != nil {
   340  		return err
   341  	}
   342  	if jobV == nil {
   343  		return fmt.Errorf("job %q at version %d not found", args.JobID, args.JobVersion)
   344  	}
   345  
   346  	// Build the register request
   347  	reg := &structs.JobRegisterRequest{
   348  		Job:          jobV.Copy(),
   349  		WriteRequest: args.WriteRequest,
   350  	}
   351  
   352  	// If the request is enforcing the existing version do a check.
   353  	if args.EnforcePriorVersion != nil {
   354  		if cur.Version != *args.EnforcePriorVersion {
   355  			return fmt.Errorf("Current job has version %d; enforcing version %d", cur.Version, *args.EnforcePriorVersion)
   356  		}
   357  
   358  		reg.EnforceIndex = true
   359  		reg.JobModifyIndex = cur.JobModifyIndex
   360  	}
   361  
   362  	// Register the version.
   363  	return j.Register(reg, reply)
   364  }
   365  
   366  // Evaluate is used to force a job for re-evaluation
   367  func (j *Job) Evaluate(args *structs.JobEvaluateRequest, reply *structs.JobRegisterResponse) error {
   368  	if done, err := j.srv.forward("Job.Evaluate", args, args, reply); done {
   369  		return err
   370  	}
   371  	defer metrics.MeasureSince([]string{"nomad", "job", "evaluate"}, time.Now())
   372  
   373  	// Validate the arguments
   374  	if args.JobID == "" {
   375  		return fmt.Errorf("missing job ID for evaluation")
   376  	}
   377  
   378  	// Lookup the job
   379  	snap, err := j.srv.fsm.State().Snapshot()
   380  	if err != nil {
   381  		return err
   382  	}
   383  	ws := memdb.NewWatchSet()
   384  	job, err := snap.JobByID(ws, args.JobID)
   385  	if err != nil {
   386  		return err
   387  	}
   388  	if job == nil {
   389  		return fmt.Errorf("job not found")
   390  	}
   391  
   392  	if job.IsPeriodic() {
   393  		return fmt.Errorf("can't evaluate periodic job")
   394  	} else if job.IsParameterized() {
   395  		return fmt.Errorf("can't evaluate parameterized job")
   396  	}
   397  
   398  	// Create a new evaluation
   399  	eval := &structs.Evaluation{
   400  		ID:             structs.GenerateUUID(),
   401  		Priority:       job.Priority,
   402  		Type:           job.Type,
   403  		TriggeredBy:    structs.EvalTriggerJobRegister,
   404  		JobID:          job.ID,
   405  		JobModifyIndex: job.ModifyIndex,
   406  		Status:         structs.EvalStatusPending,
   407  	}
   408  	update := &structs.EvalUpdateRequest{
   409  		Evals:        []*structs.Evaluation{eval},
   410  		WriteRequest: structs.WriteRequest{Region: args.Region},
   411  	}
   412  
   413  	// Commit this evaluation via Raft
   414  	_, evalIndex, err := j.srv.raftApply(structs.EvalUpdateRequestType, update)
   415  	if err != nil {
   416  		j.srv.logger.Printf("[ERR] nomad.job: Eval create failed: %v", err)
   417  		return err
   418  	}
   419  
   420  	// Setup the reply
   421  	reply.EvalID = eval.ID
   422  	reply.EvalCreateIndex = evalIndex
   423  	reply.JobModifyIndex = job.ModifyIndex
   424  	reply.Index = evalIndex
   425  	return nil
   426  }
   427  
   428  // Deregister is used to remove a job the cluster.
   429  func (j *Job) Deregister(args *structs.JobDeregisterRequest, reply *structs.JobDeregisterResponse) error {
   430  	if done, err := j.srv.forward("Job.Deregister", args, args, reply); done {
   431  		return err
   432  	}
   433  	defer metrics.MeasureSince([]string{"nomad", "job", "deregister"}, time.Now())
   434  
   435  	// Validate the arguments
   436  	if args.JobID == "" {
   437  		return fmt.Errorf("missing job ID for evaluation")
   438  	}
   439  
   440  	// Lookup the job
   441  	snap, err := j.srv.fsm.State().Snapshot()
   442  	if err != nil {
   443  		return err
   444  	}
   445  	ws := memdb.NewWatchSet()
   446  	job, err := snap.JobByID(ws, args.JobID)
   447  	if err != nil {
   448  		return err
   449  	}
   450  
   451  	// Commit this update via Raft
   452  	_, index, err := j.srv.raftApply(structs.JobDeregisterRequestType, args)
   453  	if err != nil {
   454  		j.srv.logger.Printf("[ERR] nomad.job: Deregister failed: %v", err)
   455  		return err
   456  	}
   457  
   458  	// Populate the reply with job information
   459  	reply.JobModifyIndex = index
   460  
   461  	// If the job is periodic or parameterized, we don't create an eval.
   462  	if job != nil && (job.IsPeriodic() || job.IsParameterized()) {
   463  		return nil
   464  	}
   465  
   466  	// Create a new evaluation
   467  	// XXX: The job priority / type is strange for this, since it's not a high
   468  	// priority even if the job was. The scheduler itself also doesn't matter,
   469  	// since all should be able to handle deregistration in the same way.
   470  	eval := &structs.Evaluation{
   471  		ID:             structs.GenerateUUID(),
   472  		Priority:       structs.JobDefaultPriority,
   473  		Type:           structs.JobTypeService,
   474  		TriggeredBy:    structs.EvalTriggerJobDeregister,
   475  		JobID:          args.JobID,
   476  		JobModifyIndex: index,
   477  		Status:         structs.EvalStatusPending,
   478  	}
   479  	update := &structs.EvalUpdateRequest{
   480  		Evals:        []*structs.Evaluation{eval},
   481  		WriteRequest: structs.WriteRequest{Region: args.Region},
   482  	}
   483  
   484  	// Commit this evaluation via Raft
   485  	_, evalIndex, err := j.srv.raftApply(structs.EvalUpdateRequestType, update)
   486  	if err != nil {
   487  		j.srv.logger.Printf("[ERR] nomad.job: Eval create failed: %v", err)
   488  		return err
   489  	}
   490  
   491  	// Populate the reply with eval information
   492  	reply.EvalID = eval.ID
   493  	reply.EvalCreateIndex = evalIndex
   494  	reply.Index = evalIndex
   495  	return nil
   496  }
   497  
   498  // GetJob is used to request information about a specific job
   499  func (j *Job) GetJob(args *structs.JobSpecificRequest,
   500  	reply *structs.SingleJobResponse) error {
   501  	if done, err := j.srv.forward("Job.GetJob", args, args, reply); done {
   502  		return err
   503  	}
   504  	defer metrics.MeasureSince([]string{"nomad", "job", "get_job"}, time.Now())
   505  
   506  	// Setup the blocking query
   507  	opts := blockingOptions{
   508  		queryOpts: &args.QueryOptions,
   509  		queryMeta: &reply.QueryMeta,
   510  		run: func(ws memdb.WatchSet, state *state.StateStore) error {
   511  			// Look for the job
   512  			out, err := state.JobByID(ws, args.JobID)
   513  			if err != nil {
   514  				return err
   515  			}
   516  
   517  			// Setup the output
   518  			reply.Job = out
   519  			if out != nil {
   520  				reply.Index = out.ModifyIndex
   521  			} else {
   522  				// Use the last index that affected the nodes table
   523  				index, err := state.Index("jobs")
   524  				if err != nil {
   525  					return err
   526  				}
   527  				reply.Index = index
   528  			}
   529  
   530  			// Set the query response
   531  			j.srv.setQueryMeta(&reply.QueryMeta)
   532  			return nil
   533  		}}
   534  	return j.srv.blockingRPC(&opts)
   535  }
   536  
   537  // GetJobVersions is used to retrieve all tracked versions of a job.
   538  func (j *Job) GetJobVersions(args *structs.JobSpecificRequest,
   539  	reply *structs.JobVersionsResponse) error {
   540  	if done, err := j.srv.forward("Job.GetJobVersions", args, args, reply); done {
   541  		return err
   542  	}
   543  	defer metrics.MeasureSince([]string{"nomad", "job", "get_job_versions"}, time.Now())
   544  
   545  	// Setup the blocking query
   546  	opts := blockingOptions{
   547  		queryOpts: &args.QueryOptions,
   548  		queryMeta: &reply.QueryMeta,
   549  		run: func(ws memdb.WatchSet, state *state.StateStore) error {
   550  			// Look for the job
   551  			out, err := state.JobVersionsByID(ws, args.JobID)
   552  			if err != nil {
   553  				return err
   554  			}
   555  
   556  			// Setup the output
   557  			reply.Versions = out
   558  			if len(out) != 0 {
   559  				reply.Index = out[0].ModifyIndex
   560  			} else {
   561  				// Use the last index that affected the nodes table
   562  				index, err := state.Index("job_version")
   563  				if err != nil {
   564  					return err
   565  				}
   566  				reply.Index = index
   567  			}
   568  
   569  			// Set the query response
   570  			j.srv.setQueryMeta(&reply.QueryMeta)
   571  			return nil
   572  		}}
   573  	return j.srv.blockingRPC(&opts)
   574  }
   575  
   576  // List is used to list the jobs registered in the system
   577  func (j *Job) List(args *structs.JobListRequest,
   578  	reply *structs.JobListResponse) error {
   579  	if done, err := j.srv.forward("Job.List", args, args, reply); done {
   580  		return err
   581  	}
   582  	defer metrics.MeasureSince([]string{"nomad", "job", "list"}, time.Now())
   583  
   584  	// Setup the blocking query
   585  	opts := blockingOptions{
   586  		queryOpts: &args.QueryOptions,
   587  		queryMeta: &reply.QueryMeta,
   588  		run: func(ws memdb.WatchSet, state *state.StateStore) error {
   589  			// Capture all the jobs
   590  			var err error
   591  			var iter memdb.ResultIterator
   592  			if prefix := args.QueryOptions.Prefix; prefix != "" {
   593  				iter, err = state.JobsByIDPrefix(ws, prefix)
   594  			} else {
   595  				iter, err = state.Jobs(ws)
   596  			}
   597  			if err != nil {
   598  				return err
   599  			}
   600  
   601  			var jobs []*structs.JobListStub
   602  			for {
   603  				raw := iter.Next()
   604  				if raw == nil {
   605  					break
   606  				}
   607  				job := raw.(*structs.Job)
   608  				summary, err := state.JobSummaryByID(ws, job.ID)
   609  				if err != nil {
   610  					return fmt.Errorf("unable to look up summary for job: %v", job.ID)
   611  				}
   612  				jobs = append(jobs, job.Stub(summary))
   613  			}
   614  			reply.Jobs = jobs
   615  
   616  			// Use the last index that affected the jobs table
   617  			index, err := state.Index("jobs")
   618  			if err != nil {
   619  				return err
   620  			}
   621  			reply.Index = index
   622  
   623  			// Set the query response
   624  			j.srv.setQueryMeta(&reply.QueryMeta)
   625  			return nil
   626  		}}
   627  	return j.srv.blockingRPC(&opts)
   628  }
   629  
   630  // Allocations is used to list the allocations for a job
   631  func (j *Job) Allocations(args *structs.JobSpecificRequest,
   632  	reply *structs.JobAllocationsResponse) error {
   633  	if done, err := j.srv.forward("Job.Allocations", args, args, reply); done {
   634  		return err
   635  	}
   636  	defer metrics.MeasureSince([]string{"nomad", "job", "allocations"}, time.Now())
   637  
   638  	// Setup the blocking query
   639  	opts := blockingOptions{
   640  		queryOpts: &args.QueryOptions,
   641  		queryMeta: &reply.QueryMeta,
   642  		run: func(ws memdb.WatchSet, state *state.StateStore) error {
   643  			// Capture the allocations
   644  			allocs, err := state.AllocsByJob(ws, args.JobID, args.AllAllocs)
   645  			if err != nil {
   646  				return err
   647  			}
   648  
   649  			// Convert to stubs
   650  			if len(allocs) > 0 {
   651  				reply.Allocations = make([]*structs.AllocListStub, 0, len(allocs))
   652  				for _, alloc := range allocs {
   653  					reply.Allocations = append(reply.Allocations, alloc.Stub())
   654  				}
   655  			}
   656  
   657  			// Use the last index that affected the allocs table
   658  			index, err := state.Index("allocs")
   659  			if err != nil {
   660  				return err
   661  			}
   662  			reply.Index = index
   663  
   664  			// Set the query response
   665  			j.srv.setQueryMeta(&reply.QueryMeta)
   666  			return nil
   667  
   668  		}}
   669  	return j.srv.blockingRPC(&opts)
   670  }
   671  
   672  // Evaluations is used to list the evaluations for a job
   673  func (j *Job) Evaluations(args *structs.JobSpecificRequest,
   674  	reply *structs.JobEvaluationsResponse) error {
   675  	if done, err := j.srv.forward("Job.Evaluations", args, args, reply); done {
   676  		return err
   677  	}
   678  	defer metrics.MeasureSince([]string{"nomad", "job", "evaluations"}, time.Now())
   679  
   680  	// Setup the blocking query
   681  	opts := blockingOptions{
   682  		queryOpts: &args.QueryOptions,
   683  		queryMeta: &reply.QueryMeta,
   684  		run: func(ws memdb.WatchSet, state *state.StateStore) error {
   685  			// Capture the evals
   686  			var err error
   687  			reply.Evaluations, err = state.EvalsByJob(ws, args.JobID)
   688  			if err != nil {
   689  				return err
   690  			}
   691  
   692  			// Use the last index that affected the evals table
   693  			index, err := state.Index("evals")
   694  			if err != nil {
   695  				return err
   696  			}
   697  			reply.Index = index
   698  
   699  			// Set the query response
   700  			j.srv.setQueryMeta(&reply.QueryMeta)
   701  			return nil
   702  		}}
   703  
   704  	return j.srv.blockingRPC(&opts)
   705  }
   706  
   707  // Plan is used to cause a dry-run evaluation of the Job and return the results
   708  // with a potential diff containing annotations.
   709  func (j *Job) Plan(args *structs.JobPlanRequest, reply *structs.JobPlanResponse) error {
   710  	if done, err := j.srv.forward("Job.Plan", args, args, reply); done {
   711  		return err
   712  	}
   713  	defer metrics.MeasureSince([]string{"nomad", "job", "plan"}, time.Now())
   714  
   715  	// Validate the arguments
   716  	if args.Job == nil {
   717  		return fmt.Errorf("Job required for plan")
   718  	}
   719  
   720  	// Initialize the job fields (sets defaults and any necessary init work).
   721  	args.Job.Canonicalize()
   722  
   723  	// Add implicit constraints
   724  	setImplicitConstraints(args.Job)
   725  
   726  	// Validate the job.
   727  	if err := validateJob(args.Job); err != nil {
   728  		return err
   729  	}
   730  
   731  	// Acquire a snapshot of the state
   732  	snap, err := j.srv.fsm.State().Snapshot()
   733  	if err != nil {
   734  		return err
   735  	}
   736  
   737  	// Get the original job
   738  	ws := memdb.NewWatchSet()
   739  	oldJob, err := snap.JobByID(ws, args.Job.ID)
   740  	if err != nil {
   741  		return err
   742  	}
   743  
   744  	var index uint64
   745  	var updatedIndex uint64
   746  	if oldJob != nil {
   747  		index = oldJob.JobModifyIndex
   748  		updatedIndex = oldJob.JobModifyIndex + 1
   749  	}
   750  
   751  	// Insert the updated Job into the snapshot
   752  	snap.UpsertJob(updatedIndex, args.Job)
   753  
   754  	// Create an eval and mark it as requiring annotations and insert that as well
   755  	eval := &structs.Evaluation{
   756  		ID:             structs.GenerateUUID(),
   757  		Priority:       args.Job.Priority,
   758  		Type:           args.Job.Type,
   759  		TriggeredBy:    structs.EvalTriggerJobRegister,
   760  		JobID:          args.Job.ID,
   761  		JobModifyIndex: updatedIndex,
   762  		Status:         structs.EvalStatusPending,
   763  		AnnotatePlan:   true,
   764  	}
   765  
   766  	// Create an in-memory Planner that returns no errors and stores the
   767  	// submitted plan and created evals.
   768  	planner := &scheduler.Harness{
   769  		State: &snap.StateStore,
   770  	}
   771  
   772  	// Create the scheduler and run it
   773  	sched, err := scheduler.NewScheduler(eval.Type, j.srv.logger, snap, planner)
   774  	if err != nil {
   775  		return err
   776  	}
   777  
   778  	if err := sched.Process(eval); err != nil {
   779  		return err
   780  	}
   781  
   782  	// Annotate and store the diff
   783  	if plans := len(planner.Plans); plans != 1 {
   784  		return fmt.Errorf("scheduler resulted in an unexpected number of plans: %v", plans)
   785  	}
   786  	annotations := planner.Plans[0].Annotations
   787  	if args.Diff {
   788  		jobDiff, err := oldJob.Diff(args.Job, true)
   789  		if err != nil {
   790  			return fmt.Errorf("failed to create job diff: %v", err)
   791  		}
   792  
   793  		if err := scheduler.Annotate(jobDiff, annotations); err != nil {
   794  			return fmt.Errorf("failed to annotate job diff: %v", err)
   795  		}
   796  		reply.Diff = jobDiff
   797  	}
   798  
   799  	// Grab the failures
   800  	if len(planner.Evals) != 1 {
   801  		return fmt.Errorf("scheduler resulted in an unexpected number of eval updates: %v", planner.Evals)
   802  	}
   803  	updatedEval := planner.Evals[0]
   804  
   805  	// If it is a periodic job calculate the next launch
   806  	if args.Job.IsPeriodic() && args.Job.Periodic.Enabled {
   807  		reply.NextPeriodicLaunch = args.Job.Periodic.Next(time.Now().In(args.Job.Periodic.GetLocation()))
   808  	}
   809  
   810  	reply.FailedTGAllocs = updatedEval.FailedTGAllocs
   811  	reply.JobModifyIndex = index
   812  	reply.Annotations = annotations
   813  	reply.CreatedEvals = planner.CreateEvals
   814  	reply.Index = index
   815  	return nil
   816  }
   817  
   818  // validateJob validates a Job and task drivers and returns an error if there is
   819  // a validation problem or if the Job is of a type a user is not allowed to
   820  // submit.
   821  func validateJob(job *structs.Job) error {
   822  	validationErrors := new(multierror.Error)
   823  	if err := job.Validate(); err != nil {
   824  		multierror.Append(validationErrors, err)
   825  	}
   826  
   827  	// Get the signals required
   828  	signals := job.RequiredSignals()
   829  
   830  	// Validate the driver configurations.
   831  	for _, tg := range job.TaskGroups {
   832  		// Get the signals for the task group
   833  		tgSignals, tgOk := signals[tg.Name]
   834  
   835  		for _, task := range tg.Tasks {
   836  			d, err := driver.NewDriver(
   837  				task.Driver,
   838  				driver.NewEmptyDriverContext(),
   839  			)
   840  			if err != nil {
   841  				msg := "failed to create driver for task %q in group %q for validation: %v"
   842  				multierror.Append(validationErrors, fmt.Errorf(msg, tg.Name, task.Name, err))
   843  				continue
   844  			}
   845  
   846  			if err := d.Validate(task.Config); err != nil {
   847  				formatted := fmt.Errorf("group %q -> task %q -> config: %v", tg.Name, task.Name, err)
   848  				multierror.Append(validationErrors, formatted)
   849  			}
   850  
   851  			// The task group didn't have any task that required signals
   852  			if !tgOk {
   853  				continue
   854  			}
   855  
   856  			// This task requires signals. Ensure the driver is capable
   857  			if required, ok := tgSignals[task.Name]; ok {
   858  				abilities := d.Abilities()
   859  				if !abilities.SendSignals {
   860  					formatted := fmt.Errorf("group %q -> task %q: driver %q doesn't support sending signals. Requested signals are %v",
   861  						tg.Name, task.Name, task.Driver, strings.Join(required, ", "))
   862  					multierror.Append(validationErrors, formatted)
   863  				}
   864  			}
   865  		}
   866  	}
   867  
   868  	if job.Type == structs.JobTypeCore {
   869  		multierror.Append(validationErrors, fmt.Errorf("job type cannot be core"))
   870  	}
   871  
   872  	if len(job.Payload) != 0 {
   873  		multierror.Append(validationErrors, fmt.Errorf("job can't be submitted with a payload, only dispatched"))
   874  	}
   875  
   876  	return validationErrors.ErrorOrNil()
   877  }
   878  
   879  // Dispatch a parameterized job.
   880  func (j *Job) Dispatch(args *structs.JobDispatchRequest, reply *structs.JobDispatchResponse) error {
   881  	if done, err := j.srv.forward("Job.Dispatch", args, args, reply); done {
   882  		return err
   883  	}
   884  	defer metrics.MeasureSince([]string{"nomad", "job", "dispatch"}, time.Now())
   885  
   886  	// Lookup the parameterized job
   887  	if args.JobID == "" {
   888  		return fmt.Errorf("missing parameterized job ID")
   889  	}
   890  
   891  	snap, err := j.srv.fsm.State().Snapshot()
   892  	if err != nil {
   893  		return err
   894  	}
   895  	ws := memdb.NewWatchSet()
   896  	parameterizedJob, err := snap.JobByID(ws, args.JobID)
   897  	if err != nil {
   898  		return err
   899  	}
   900  	if parameterizedJob == nil {
   901  		return fmt.Errorf("parameterized job not found")
   902  	}
   903  
   904  	if !parameterizedJob.IsParameterized() {
   905  		return fmt.Errorf("Specified job %q is not a parameterized job", args.JobID)
   906  	}
   907  
   908  	if parameterizedJob.Stop {
   909  		return fmt.Errorf("Specified job %q is stopped", args.JobID)
   910  	}
   911  
   912  	// Validate the arguments
   913  	if err := validateDispatchRequest(args, parameterizedJob); err != nil {
   914  		return err
   915  	}
   916  
   917  	// Derive the child job and commit it via Raft
   918  	dispatchJob := parameterizedJob.Copy()
   919  	dispatchJob.ParameterizedJob = nil
   920  	dispatchJob.ID = structs.DispatchedID(parameterizedJob.ID, time.Now())
   921  	dispatchJob.ParentID = parameterizedJob.ID
   922  	dispatchJob.Name = dispatchJob.ID
   923  
   924  	// Merge in the meta data
   925  	for k, v := range args.Meta {
   926  		if dispatchJob.Meta == nil {
   927  			dispatchJob.Meta = make(map[string]string, len(args.Meta))
   928  		}
   929  		dispatchJob.Meta[k] = v
   930  	}
   931  
   932  	// Compress the payload
   933  	dispatchJob.Payload = snappy.Encode(nil, args.Payload)
   934  
   935  	regReq := &structs.JobRegisterRequest{
   936  		Job:          dispatchJob,
   937  		WriteRequest: args.WriteRequest,
   938  	}
   939  
   940  	// Commit this update via Raft
   941  	_, jobCreateIndex, err := j.srv.raftApply(structs.JobRegisterRequestType, regReq)
   942  	if err != nil {
   943  		j.srv.logger.Printf("[ERR] nomad.job: Dispatched job register failed: %v", err)
   944  		return err
   945  	}
   946  
   947  	reply.JobCreateIndex = jobCreateIndex
   948  	reply.DispatchedJobID = dispatchJob.ID
   949  	reply.Index = jobCreateIndex
   950  
   951  	// If the job is periodic, we don't create an eval.
   952  	if !dispatchJob.IsPeriodic() {
   953  		// Create a new evaluation
   954  		eval := &structs.Evaluation{
   955  			ID:             structs.GenerateUUID(),
   956  			Priority:       dispatchJob.Priority,
   957  			Type:           dispatchJob.Type,
   958  			TriggeredBy:    structs.EvalTriggerJobRegister,
   959  			JobID:          dispatchJob.ID,
   960  			JobModifyIndex: jobCreateIndex,
   961  			Status:         structs.EvalStatusPending,
   962  		}
   963  		update := &structs.EvalUpdateRequest{
   964  			Evals:        []*structs.Evaluation{eval},
   965  			WriteRequest: structs.WriteRequest{Region: args.Region},
   966  		}
   967  
   968  		// Commit this evaluation via Raft
   969  		_, evalIndex, err := j.srv.raftApply(structs.EvalUpdateRequestType, update)
   970  		if err != nil {
   971  			j.srv.logger.Printf("[ERR] nomad.job: Eval create failed: %v", err)
   972  			return err
   973  		}
   974  
   975  		// Setup the reply
   976  		reply.EvalID = eval.ID
   977  		reply.EvalCreateIndex = evalIndex
   978  		reply.Index = evalIndex
   979  	}
   980  
   981  	return nil
   982  }
   983  
   984  // validateDispatchRequest returns whether the request is valid given the
   985  // parameterized job.
   986  func validateDispatchRequest(req *structs.JobDispatchRequest, job *structs.Job) error {
   987  	// Check the payload constraint is met
   988  	hasInputData := len(req.Payload) != 0
   989  	if job.ParameterizedJob.Payload == structs.DispatchPayloadRequired && !hasInputData {
   990  		return fmt.Errorf("Payload is not provided but required by parameterized job")
   991  	} else if job.ParameterizedJob.Payload == structs.DispatchPayloadForbidden && hasInputData {
   992  		return fmt.Errorf("Payload provided but forbidden by parameterized job")
   993  	}
   994  
   995  	// Check the payload doesn't exceed the size limit
   996  	if l := len(req.Payload); l > DispatchPayloadSizeLimit {
   997  		return fmt.Errorf("Payload exceeds maximum size; %d > %d", l, DispatchPayloadSizeLimit)
   998  	}
   999  
  1000  	// Check if the metadata is a set
  1001  	keys := make(map[string]struct{}, len(req.Meta))
  1002  	for k := range keys {
  1003  		if _, ok := keys[k]; ok {
  1004  			return fmt.Errorf("Duplicate key %q in passed metadata", k)
  1005  		}
  1006  		keys[k] = struct{}{}
  1007  	}
  1008  
  1009  	required := helper.SliceStringToSet(job.ParameterizedJob.MetaRequired)
  1010  	optional := helper.SliceStringToSet(job.ParameterizedJob.MetaOptional)
  1011  
  1012  	// Check the metadata key constraints are met
  1013  	unpermitted := make(map[string]struct{})
  1014  	for k := range req.Meta {
  1015  		_, req := required[k]
  1016  		_, opt := optional[k]
  1017  		if !req && !opt {
  1018  			unpermitted[k] = struct{}{}
  1019  		}
  1020  	}
  1021  
  1022  	if len(unpermitted) != 0 {
  1023  		flat := make([]string, 0, len(unpermitted))
  1024  		for k := range unpermitted {
  1025  			flat = append(flat, k)
  1026  		}
  1027  
  1028  		return fmt.Errorf("Dispatch request included unpermitted metadata keys: %v", flat)
  1029  	}
  1030  
  1031  	missing := make(map[string]struct{})
  1032  	for _, k := range job.ParameterizedJob.MetaRequired {
  1033  		if _, ok := req.Meta[k]; !ok {
  1034  			missing[k] = struct{}{}
  1035  		}
  1036  	}
  1037  
  1038  	if len(missing) != 0 {
  1039  		flat := make([]string, 0, len(missing))
  1040  		for k := range missing {
  1041  			flat = append(flat, k)
  1042  		}
  1043  
  1044  		return fmt.Errorf("Dispatch did not provide required meta keys: %v", flat)
  1045  	}
  1046  
  1047  	return nil
  1048  }