github.com/ryanslade/nomad@v0.2.4-0.20160128061903-fc95782f2089/nomad/job_endpoint.go (about)

     1  package nomad
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"time"
     7  
     8  	"github.com/armon/go-metrics"
     9  	"github.com/hashicorp/go-memdb"
    10  	"github.com/hashicorp/nomad/nomad/structs"
    11  	"github.com/hashicorp/nomad/nomad/watch"
    12  )
    13  
    14  // Job endpoint is used for job interactions
    15  type Job struct {
    16  	srv *Server
    17  }
    18  
    19  // Register is used to upsert a job for scheduling
    20  func (j *Job) Register(args *structs.JobRegisterRequest, reply *structs.JobRegisterResponse) error {
    21  	if done, err := j.srv.forward("Job.Register", args, args, reply); done {
    22  		return err
    23  	}
    24  	defer metrics.MeasureSince([]string{"nomad", "job", "register"}, time.Now())
    25  
    26  	// Validate the arguments
    27  	if args.Job == nil {
    28  		return fmt.Errorf("missing job for registration")
    29  	}
    30  
    31  	if err := j.checkBlacklist(args.Job); err != nil {
    32  		return err
    33  	}
    34  
    35  	// Initialize the job fields (sets defaults and any necessary init work).
    36  	args.Job.InitFields()
    37  
    38  	if err := args.Job.Validate(); err != nil {
    39  		return err
    40  	}
    41  
    42  	if args.Job.Type == structs.JobTypeCore {
    43  		return fmt.Errorf("job type cannot be core")
    44  	}
    45  
    46  	// Commit this update via Raft
    47  	_, index, err := j.srv.raftApply(structs.JobRegisterRequestType, args)
    48  	if err != nil {
    49  		j.srv.logger.Printf("[ERR] nomad.job: Register failed: %v", err)
    50  		return err
    51  	}
    52  
    53  	// Populate the reply with job information
    54  	reply.JobModifyIndex = index
    55  
    56  	// If the job is periodic, we don't create an eval.
    57  	if args.Job.IsPeriodic() {
    58  		return nil
    59  	}
    60  
    61  	// Create a new evaluation
    62  	eval := &structs.Evaluation{
    63  		ID:             structs.GenerateUUID(),
    64  		Priority:       args.Job.Priority,
    65  		Type:           args.Job.Type,
    66  		TriggeredBy:    structs.EvalTriggerJobRegister,
    67  		JobID:          args.Job.ID,
    68  		JobModifyIndex: index,
    69  		Status:         structs.EvalStatusPending,
    70  	}
    71  	update := &structs.EvalUpdateRequest{
    72  		Evals:        []*structs.Evaluation{eval},
    73  		WriteRequest: structs.WriteRequest{Region: args.Region},
    74  	}
    75  
    76  	// Commit this evaluation via Raft
    77  	// XXX: There is a risk of partial failure where the JobRegister succeeds
    78  	// but that the EvalUpdate does not.
    79  	_, evalIndex, err := j.srv.raftApply(structs.EvalUpdateRequestType, update)
    80  	if err != nil {
    81  		j.srv.logger.Printf("[ERR] nomad.job: Eval create failed: %v", err)
    82  		return err
    83  	}
    84  
    85  	// Populate the reply with eval information
    86  	reply.EvalID = eval.ID
    87  	reply.EvalCreateIndex = evalIndex
    88  	reply.Index = evalIndex
    89  	return nil
    90  }
    91  
    92  // checkBlacklist returns an error if the user has set any blacklisted field in
    93  // the job.
    94  func (j *Job) checkBlacklist(job *structs.Job) error {
    95  	if job.GC {
    96  		return errors.New("GC field of a job is used only internally and should not be set by user")
    97  	}
    98  
    99  	return nil
   100  }
   101  
   102  // Evaluate is used to force a job for re-evaluation
   103  func (j *Job) Evaluate(args *structs.JobEvaluateRequest, reply *structs.JobRegisterResponse) error {
   104  	if done, err := j.srv.forward("Job.Evaluate", args, args, reply); done {
   105  		return err
   106  	}
   107  	defer metrics.MeasureSince([]string{"nomad", "job", "evaluate"}, time.Now())
   108  
   109  	// Validate the arguments
   110  	if args.JobID == "" {
   111  		return fmt.Errorf("missing job ID for evaluation")
   112  	}
   113  
   114  	// Lookup the job
   115  	snap, err := j.srv.fsm.State().Snapshot()
   116  	if err != nil {
   117  		return err
   118  	}
   119  	job, err := snap.JobByID(args.JobID)
   120  	if err != nil {
   121  		return err
   122  	}
   123  	if job == nil {
   124  		return fmt.Errorf("job not found")
   125  	}
   126  
   127  	if job.IsPeriodic() {
   128  		return fmt.Errorf("can't evaluate periodic job")
   129  	}
   130  
   131  	// Create a new evaluation
   132  	eval := &structs.Evaluation{
   133  		ID:             structs.GenerateUUID(),
   134  		Priority:       job.Priority,
   135  		Type:           job.Type,
   136  		TriggeredBy:    structs.EvalTriggerJobRegister,
   137  		JobID:          job.ID,
   138  		JobModifyIndex: job.ModifyIndex,
   139  		Status:         structs.EvalStatusPending,
   140  	}
   141  	update := &structs.EvalUpdateRequest{
   142  		Evals:        []*structs.Evaluation{eval},
   143  		WriteRequest: structs.WriteRequest{Region: args.Region},
   144  	}
   145  
   146  	// Commit this evaluation via Raft
   147  	_, evalIndex, err := j.srv.raftApply(structs.EvalUpdateRequestType, update)
   148  	if err != nil {
   149  		j.srv.logger.Printf("[ERR] nomad.job: Eval create failed: %v", err)
   150  		return err
   151  	}
   152  
   153  	// Setup the reply
   154  	reply.EvalID = eval.ID
   155  	reply.EvalCreateIndex = evalIndex
   156  	reply.JobModifyIndex = job.ModifyIndex
   157  	reply.Index = evalIndex
   158  	return nil
   159  }
   160  
   161  // Deregister is used to remove a job the cluster.
   162  func (j *Job) Deregister(args *structs.JobDeregisterRequest, reply *structs.JobDeregisterResponse) error {
   163  	if done, err := j.srv.forward("Job.Deregister", args, args, reply); done {
   164  		return err
   165  	}
   166  	defer metrics.MeasureSince([]string{"nomad", "job", "deregister"}, time.Now())
   167  
   168  	// Validate the arguments
   169  	if args.JobID == "" {
   170  		return fmt.Errorf("missing job ID for evaluation")
   171  	}
   172  
   173  	// Lookup the job
   174  	snap, err := j.srv.fsm.State().Snapshot()
   175  	if err != nil {
   176  		return err
   177  	}
   178  	job, err := snap.JobByID(args.JobID)
   179  	if err != nil {
   180  		return err
   181  	}
   182  	if job == nil {
   183  		return fmt.Errorf("job not found")
   184  	}
   185  
   186  	// Commit this update via Raft
   187  	_, index, err := j.srv.raftApply(structs.JobDeregisterRequestType, args)
   188  	if err != nil {
   189  		j.srv.logger.Printf("[ERR] nomad.job: Deregister failed: %v", err)
   190  		return err
   191  	}
   192  
   193  	// Populate the reply with job information
   194  	reply.JobModifyIndex = index
   195  
   196  	// If the job is periodic, we don't create an eval.
   197  	if job.IsPeriodic() {
   198  		return nil
   199  	}
   200  
   201  	// Create a new evaluation
   202  	// XXX: The job priority / type is strange for this, since it's not a high
   203  	// priority even if the job was. The scheduler itself also doesn't matter,
   204  	// since all should be able to handle deregistration in the same way.
   205  	eval := &structs.Evaluation{
   206  		ID:             structs.GenerateUUID(),
   207  		Priority:       structs.JobDefaultPriority,
   208  		Type:           structs.JobTypeService,
   209  		TriggeredBy:    structs.EvalTriggerJobDeregister,
   210  		JobID:          args.JobID,
   211  		JobModifyIndex: index,
   212  		Status:         structs.EvalStatusPending,
   213  	}
   214  	update := &structs.EvalUpdateRequest{
   215  		Evals:        []*structs.Evaluation{eval},
   216  		WriteRequest: structs.WriteRequest{Region: args.Region},
   217  	}
   218  
   219  	// Commit this evaluation via Raft
   220  	_, evalIndex, err := j.srv.raftApply(structs.EvalUpdateRequestType, update)
   221  	if err != nil {
   222  		j.srv.logger.Printf("[ERR] nomad.job: Eval create failed: %v", err)
   223  		return err
   224  	}
   225  
   226  	// Populate the reply with eval information
   227  	reply.EvalID = eval.ID
   228  	reply.EvalCreateIndex = evalIndex
   229  	reply.Index = evalIndex
   230  	return nil
   231  }
   232  
   233  // GetJob is used to request information about a specific job
   234  func (j *Job) GetJob(args *structs.JobSpecificRequest,
   235  	reply *structs.SingleJobResponse) error {
   236  	if done, err := j.srv.forward("Job.GetJob", args, args, reply); done {
   237  		return err
   238  	}
   239  	defer metrics.MeasureSince([]string{"nomad", "job", "get_job"}, time.Now())
   240  
   241  	// Setup the blocking query
   242  	opts := blockingOptions{
   243  		queryOpts: &args.QueryOptions,
   244  		queryMeta: &reply.QueryMeta,
   245  		watch:     watch.NewItems(watch.Item{Job: args.JobID}),
   246  		run: func() error {
   247  
   248  			// Look for the job
   249  			snap, err := j.srv.fsm.State().Snapshot()
   250  			if err != nil {
   251  				return err
   252  			}
   253  			out, err := snap.JobByID(args.JobID)
   254  			if err != nil {
   255  				return err
   256  			}
   257  
   258  			// Setup the output
   259  			reply.Job = out
   260  			if out != nil {
   261  				reply.Index = out.ModifyIndex
   262  			} else {
   263  				// Use the last index that affected the nodes table
   264  				index, err := snap.Index("jobs")
   265  				if err != nil {
   266  					return err
   267  				}
   268  				reply.Index = index
   269  			}
   270  
   271  			// Set the query response
   272  			j.srv.setQueryMeta(&reply.QueryMeta)
   273  			return nil
   274  		}}
   275  	return j.srv.blockingRPC(&opts)
   276  }
   277  
   278  // List is used to list the jobs registered in the system
   279  func (j *Job) List(args *structs.JobListRequest,
   280  	reply *structs.JobListResponse) error {
   281  	if done, err := j.srv.forward("Job.List", args, args, reply); done {
   282  		return err
   283  	}
   284  	defer metrics.MeasureSince([]string{"nomad", "job", "list"}, time.Now())
   285  
   286  	// Setup the blocking query
   287  	opts := blockingOptions{
   288  		queryOpts: &args.QueryOptions,
   289  		queryMeta: &reply.QueryMeta,
   290  		watch:     watch.NewItems(watch.Item{Table: "jobs"}),
   291  		run: func() error {
   292  			// Capture all the jobs
   293  			snap, err := j.srv.fsm.State().Snapshot()
   294  			if err != nil {
   295  				return err
   296  			}
   297  			var iter memdb.ResultIterator
   298  			if prefix := args.QueryOptions.Prefix; prefix != "" {
   299  				iter, err = snap.JobsByIDPrefix(prefix)
   300  			} else {
   301  				iter, err = snap.Jobs()
   302  			}
   303  			if err != nil {
   304  				return err
   305  			}
   306  
   307  			var jobs []*structs.JobListStub
   308  			for {
   309  				raw := iter.Next()
   310  				if raw == nil {
   311  					break
   312  				}
   313  				job := raw.(*structs.Job)
   314  				jobs = append(jobs, job.Stub())
   315  			}
   316  			reply.Jobs = jobs
   317  
   318  			// Use the last index that affected the jobs table
   319  			index, err := snap.Index("jobs")
   320  			if err != nil {
   321  				return err
   322  			}
   323  			reply.Index = index
   324  
   325  			// Set the query response
   326  			j.srv.setQueryMeta(&reply.QueryMeta)
   327  			return nil
   328  		}}
   329  	return j.srv.blockingRPC(&opts)
   330  }
   331  
   332  // Allocations is used to list the allocations for a job
   333  func (j *Job) Allocations(args *structs.JobSpecificRequest,
   334  	reply *structs.JobAllocationsResponse) error {
   335  	if done, err := j.srv.forward("Job.Allocations", args, args, reply); done {
   336  		return err
   337  	}
   338  	defer metrics.MeasureSince([]string{"nomad", "job", "allocations"}, time.Now())
   339  
   340  	// Setup the blocking query
   341  	opts := blockingOptions{
   342  		queryOpts: &args.QueryOptions,
   343  		queryMeta: &reply.QueryMeta,
   344  		watch:     watch.NewItems(watch.Item{AllocJob: args.JobID}),
   345  		run: func() error {
   346  			// Capture the allocations
   347  			snap, err := j.srv.fsm.State().Snapshot()
   348  			if err != nil {
   349  				return err
   350  			}
   351  			allocs, err := snap.AllocsByJob(args.JobID)
   352  			if err != nil {
   353  				return err
   354  			}
   355  
   356  			// Convert to stubs
   357  			if len(allocs) > 0 {
   358  				reply.Allocations = make([]*structs.AllocListStub, 0, len(allocs))
   359  				for _, alloc := range allocs {
   360  					reply.Allocations = append(reply.Allocations, alloc.Stub())
   361  				}
   362  			}
   363  
   364  			// Use the last index that affected the allocs table
   365  			index, err := snap.Index("allocs")
   366  			if err != nil {
   367  				return err
   368  			}
   369  			reply.Index = index
   370  
   371  			// Set the query response
   372  			j.srv.setQueryMeta(&reply.QueryMeta)
   373  			return nil
   374  
   375  		}}
   376  	return j.srv.blockingRPC(&opts)
   377  }
   378  
   379  // Evaluations is used to list the evaluations for a job
   380  func (j *Job) Evaluations(args *structs.JobSpecificRequest,
   381  	reply *structs.JobEvaluationsResponse) error {
   382  	if done, err := j.srv.forward("Job.Evaluations", args, args, reply); done {
   383  		return err
   384  	}
   385  	defer metrics.MeasureSince([]string{"nomad", "job", "evaluations"}, time.Now())
   386  
   387  	// Capture the evaluations
   388  	snap, err := j.srv.fsm.State().Snapshot()
   389  	if err != nil {
   390  		return err
   391  	}
   392  	reply.Evaluations, err = snap.EvalsByJob(args.JobID)
   393  	if err != nil {
   394  		return err
   395  	}
   396  
   397  	// Use the last index that affected the evals table
   398  	index, err := snap.Index("evals")
   399  	if err != nil {
   400  		return err
   401  	}
   402  	reply.Index = index
   403  
   404  	// Set the query response
   405  	j.srv.setQueryMeta(&reply.QueryMeta)
   406  	return nil
   407  }