github.com/kardianos/nomad@v0.1.3-0.20151022182107-b13df73ee850/nomad/job_endpoint.go (about)

     1  package nomad
     2  
     3  import (
     4  	"fmt"
     5  	"time"
     6  
     7  	"github.com/armon/go-metrics"
     8  	"github.com/hashicorp/nomad/nomad/structs"
     9  )
    10  
    11  // Job endpoint is used for job interactions
    12  type Job struct {
    13  	srv *Server
    14  }
    15  
    16  // Register is used to upsert a job for scheduling
    17  func (j *Job) Register(args *structs.JobRegisterRequest, reply *structs.JobRegisterResponse) error {
    18  	if done, err := j.srv.forward("Job.Register", args, args, reply); done {
    19  		return err
    20  	}
    21  	defer metrics.MeasureSince([]string{"nomad", "job", "register"}, time.Now())
    22  
    23  	// Validate the arguments
    24  	if args.Job == nil {
    25  		return fmt.Errorf("missing job for registration")
    26  	}
    27  	if err := args.Job.Validate(); err != nil {
    28  		return err
    29  	}
    30  	if args.Job.Type == structs.JobTypeCore {
    31  		return fmt.Errorf("job type cannot be core")
    32  	}
    33  
    34  	// Commit this update via Raft
    35  	_, index, err := j.srv.raftApply(structs.JobRegisterRequestType, args)
    36  	if err != nil {
    37  		j.srv.logger.Printf("[ERR] nomad.job: Register failed: %v", err)
    38  		return err
    39  	}
    40  
    41  	// Create a new evaluation
    42  	eval := &structs.Evaluation{
    43  		ID:             structs.GenerateUUID(),
    44  		Priority:       args.Job.Priority,
    45  		Type:           args.Job.Type,
    46  		TriggeredBy:    structs.EvalTriggerJobRegister,
    47  		JobID:          args.Job.ID,
    48  		JobModifyIndex: index,
    49  		Status:         structs.EvalStatusPending,
    50  	}
    51  	update := &structs.EvalUpdateRequest{
    52  		Evals:        []*structs.Evaluation{eval},
    53  		WriteRequest: structs.WriteRequest{Region: args.Region},
    54  	}
    55  
    56  	// Commit this evaluation via Raft
    57  	// XXX: There is a risk of partial failure where the JobRegister succeeds
    58  	// but that the EvalUpdate does not.
    59  	_, evalIndex, err := j.srv.raftApply(structs.EvalUpdateRequestType, update)
    60  	if err != nil {
    61  		j.srv.logger.Printf("[ERR] nomad.job: Eval create failed: %v", err)
    62  		return err
    63  	}
    64  
    65  	// Setup the reply
    66  	reply.EvalID = eval.ID
    67  	reply.EvalCreateIndex = evalIndex
    68  	reply.JobModifyIndex = index
    69  	reply.Index = evalIndex
    70  	return nil
    71  }
    72  
    73  // Evaluate is used to force a job for re-evaluation
    74  func (j *Job) Evaluate(args *structs.JobEvaluateRequest, reply *structs.JobRegisterResponse) error {
    75  	if done, err := j.srv.forward("Job.Evaluate", args, args, reply); done {
    76  		return err
    77  	}
    78  	defer metrics.MeasureSince([]string{"nomad", "job", "evaluate"}, time.Now())
    79  
    80  	// Validate the arguments
    81  	if args.JobID == "" {
    82  		return fmt.Errorf("missing job ID for evaluation")
    83  	}
    84  
    85  	// Lookup the job
    86  	snap, err := j.srv.fsm.State().Snapshot()
    87  	if err != nil {
    88  		return err
    89  	}
    90  	job, err := snap.JobByID(args.JobID)
    91  	if err != nil {
    92  		return err
    93  	}
    94  	if job == nil {
    95  		return fmt.Errorf("job not found")
    96  	}
    97  
    98  	// Create a new evaluation
    99  	eval := &structs.Evaluation{
   100  		ID:             structs.GenerateUUID(),
   101  		Priority:       job.Priority,
   102  		Type:           job.Type,
   103  		TriggeredBy:    structs.EvalTriggerJobRegister,
   104  		JobID:          job.ID,
   105  		JobModifyIndex: job.ModifyIndex,
   106  		Status:         structs.EvalStatusPending,
   107  	}
   108  	update := &structs.EvalUpdateRequest{
   109  		Evals:        []*structs.Evaluation{eval},
   110  		WriteRequest: structs.WriteRequest{Region: args.Region},
   111  	}
   112  
   113  	// Commit this evaluation via Raft
   114  	_, evalIndex, err := j.srv.raftApply(structs.EvalUpdateRequestType, update)
   115  	if err != nil {
   116  		j.srv.logger.Printf("[ERR] nomad.job: Eval create failed: %v", err)
   117  		return err
   118  	}
   119  
   120  	// Setup the reply
   121  	reply.EvalID = eval.ID
   122  	reply.EvalCreateIndex = evalIndex
   123  	reply.JobModifyIndex = job.ModifyIndex
   124  	reply.Index = evalIndex
   125  	return nil
   126  }
   127  
   128  // Deregister is used to remove a job the cluster.
   129  func (j *Job) Deregister(args *structs.JobDeregisterRequest, reply *structs.JobDeregisterResponse) error {
   130  	if done, err := j.srv.forward("Job.Deregister", args, args, reply); done {
   131  		return err
   132  	}
   133  	defer metrics.MeasureSince([]string{"nomad", "job", "deregister"}, time.Now())
   134  
   135  	// Commit this update via Raft
   136  	_, index, err := j.srv.raftApply(structs.JobDeregisterRequestType, args)
   137  	if err != nil {
   138  		j.srv.logger.Printf("[ERR] nomad.job: Deregister failed: %v", err)
   139  		return err
   140  	}
   141  
   142  	// Create a new evaluation
   143  	// XXX: The job priority / type is strange for this, since it's not a high
   144  	// priority even if the job was. The scheduler itself also doesn't matter,
   145  	// since all should be able to handle deregistration in the same way.
   146  	eval := &structs.Evaluation{
   147  		ID:             structs.GenerateUUID(),
   148  		Priority:       structs.JobDefaultPriority,
   149  		Type:           structs.JobTypeService,
   150  		TriggeredBy:    structs.EvalTriggerJobDeregister,
   151  		JobID:          args.JobID,
   152  		JobModifyIndex: index,
   153  		Status:         structs.EvalStatusPending,
   154  	}
   155  	update := &structs.EvalUpdateRequest{
   156  		Evals:        []*structs.Evaluation{eval},
   157  		WriteRequest: structs.WriteRequest{Region: args.Region},
   158  	}
   159  
   160  	// Commit this evaluation via Raft
   161  	_, evalIndex, err := j.srv.raftApply(structs.EvalUpdateRequestType, update)
   162  	if err != nil {
   163  		j.srv.logger.Printf("[ERR] nomad.job: Eval create failed: %v", err)
   164  		return err
   165  	}
   166  
   167  	// Setup the reply
   168  	reply.EvalID = eval.ID
   169  	reply.EvalCreateIndex = evalIndex
   170  	reply.JobModifyIndex = index
   171  	reply.Index = evalIndex
   172  	return nil
   173  }
   174  
   175  // GetJob is used to request information about a specific job
   176  func (j *Job) GetJob(args *structs.JobSpecificRequest,
   177  	reply *structs.SingleJobResponse) error {
   178  	if done, err := j.srv.forward("Job.GetJob", args, args, reply); done {
   179  		return err
   180  	}
   181  	defer metrics.MeasureSince([]string{"nomad", "job", "get_job"}, time.Now())
   182  
   183  	// Look for the job
   184  	snap, err := j.srv.fsm.State().Snapshot()
   185  	if err != nil {
   186  		return err
   187  	}
   188  	out, err := snap.JobByID(args.JobID)
   189  	if err != nil {
   190  		return err
   191  	}
   192  
   193  	// Setup the output
   194  	if out != nil {
   195  		reply.Job = out
   196  		reply.Index = out.ModifyIndex
   197  	} else {
   198  		// Use the last index that affected the nodes table
   199  		index, err := snap.Index("jobs")
   200  		if err != nil {
   201  			return err
   202  		}
   203  		reply.Index = index
   204  	}
   205  
   206  	// Set the query response
   207  	j.srv.setQueryMeta(&reply.QueryMeta)
   208  	return nil
   209  }
   210  
   211  // List is used to list the jobs registered in the system
   212  func (j *Job) List(args *structs.JobListRequest,
   213  	reply *structs.JobListResponse) error {
   214  	if done, err := j.srv.forward("Job.List", args, args, reply); done {
   215  		return err
   216  	}
   217  	defer metrics.MeasureSince([]string{"nomad", "job", "list"}, time.Now())
   218  
   219  	// Capture all the jobs
   220  	snap, err := j.srv.fsm.State().Snapshot()
   221  	if err != nil {
   222  		return err
   223  	}
   224  	iter, err := snap.Jobs()
   225  	if err != nil {
   226  		return err
   227  	}
   228  
   229  	for {
   230  		raw := iter.Next()
   231  		if raw == nil {
   232  			break
   233  		}
   234  		job := raw.(*structs.Job)
   235  		reply.Jobs = append(reply.Jobs, job.Stub())
   236  	}
   237  
   238  	// Use the last index that affected the jobs table
   239  	index, err := snap.Index("jobs")
   240  	if err != nil {
   241  		return err
   242  	}
   243  	reply.Index = index
   244  
   245  	// Set the query response
   246  	j.srv.setQueryMeta(&reply.QueryMeta)
   247  	return nil
   248  }
   249  
   250  // Allocations is used to list the allocations for a job
   251  func (j *Job) Allocations(args *structs.JobSpecificRequest,
   252  	reply *structs.JobAllocationsResponse) error {
   253  	if done, err := j.srv.forward("Job.Allocations", args, args, reply); done {
   254  		return err
   255  	}
   256  	defer metrics.MeasureSince([]string{"nomad", "job", "allocations"}, time.Now())
   257  
   258  	// Capture the allocations
   259  	snap, err := j.srv.fsm.State().Snapshot()
   260  	if err != nil {
   261  		return err
   262  	}
   263  	allocs, err := snap.AllocsByJob(args.JobID)
   264  	if err != nil {
   265  		return err
   266  	}
   267  
   268  	// Convert to stubs
   269  	if len(allocs) > 0 {
   270  		reply.Allocations = make([]*structs.AllocListStub, 0, len(allocs))
   271  		for _, alloc := range allocs {
   272  			reply.Allocations = append(reply.Allocations, alloc.Stub())
   273  		}
   274  	}
   275  
   276  	// Use the last index that affected the allocs table
   277  	index, err := snap.Index("allocs")
   278  	if err != nil {
   279  		return err
   280  	}
   281  	reply.Index = index
   282  
   283  	// Set the query response
   284  	j.srv.setQueryMeta(&reply.QueryMeta)
   285  	return nil
   286  }
   287  
   288  // Evaluations is used to list the evaluations for a job
   289  func (j *Job) Evaluations(args *structs.JobSpecificRequest,
   290  	reply *structs.JobEvaluationsResponse) error {
   291  	if done, err := j.srv.forward("Job.Evaluations", args, args, reply); done {
   292  		return err
   293  	}
   294  	defer metrics.MeasureSince([]string{"nomad", "job", "evaluations"}, time.Now())
   295  
   296  	// Capture the evaluations
   297  	snap, err := j.srv.fsm.State().Snapshot()
   298  	if err != nil {
   299  		return err
   300  	}
   301  	reply.Evaluations, err = snap.EvalsByJob(args.JobID)
   302  	if err != nil {
   303  		return err
   304  	}
   305  
   306  	// Use the last index that affected the evals table
   307  	index, err := snap.Index("evals")
   308  	if err != nil {
   309  		return err
   310  	}
   311  	reply.Index = index
   312  
   313  	// Set the query response
   314  	j.srv.setQueryMeta(&reply.QueryMeta)
   315  	return nil
   316  }