github.com/maier/nomad@v0.4.1-0.20161110003312-a9e3d0b8549d/nomad/eval_endpoint.go (about)

     1  package nomad
     2  
     3  import (
     4  	"fmt"
     5  	"time"
     6  
     7  	"github.com/armon/go-metrics"
     8  	"github.com/hashicorp/go-memdb"
     9  	"github.com/hashicorp/nomad/nomad/structs"
    10  	"github.com/hashicorp/nomad/nomad/watch"
    11  	"github.com/hashicorp/nomad/scheduler"
    12  )
    13  
    14  const (
    15  	// DefaultDequeueTimeout is used if no dequeue timeout is provided
    16  	DefaultDequeueTimeout = time.Second
    17  )
    18  
    19  // Eval endpoint is used for eval interactions
    20  type Eval struct {
    21  	srv *Server
    22  }
    23  
    24  // GetEval is used to request information about a specific evaluation
    25  func (e *Eval) GetEval(args *structs.EvalSpecificRequest,
    26  	reply *structs.SingleEvalResponse) error {
    27  	if done, err := e.srv.forward("Eval.GetEval", args, args, reply); done {
    28  		return err
    29  	}
    30  	defer metrics.MeasureSince([]string{"nomad", "eval", "get_eval"}, time.Now())
    31  
    32  	// Setup the blocking query
    33  	opts := blockingOptions{
    34  		queryOpts: &args.QueryOptions,
    35  		queryMeta: &reply.QueryMeta,
    36  		watch:     watch.NewItems(watch.Item{Eval: args.EvalID}),
    37  		run: func() error {
    38  			// Look for the job
    39  			snap, err := e.srv.fsm.State().Snapshot()
    40  			if err != nil {
    41  				return err
    42  			}
    43  			out, err := snap.EvalByID(args.EvalID)
    44  			if err != nil {
    45  				return err
    46  			}
    47  
    48  			// Setup the output
    49  			reply.Eval = out
    50  			if out != nil {
    51  				reply.Index = out.ModifyIndex
    52  			} else {
    53  				// Use the last index that affected the nodes table
    54  				index, err := snap.Index("evals")
    55  				if err != nil {
    56  					return err
    57  				}
    58  				reply.Index = index
    59  			}
    60  
    61  			// Set the query response
    62  			e.srv.setQueryMeta(&reply.QueryMeta)
    63  			return nil
    64  		}}
    65  	return e.srv.blockingRPC(&opts)
    66  }
    67  
    68  // Dequeue is used to dequeue a pending evaluation
    69  func (e *Eval) Dequeue(args *structs.EvalDequeueRequest,
    70  	reply *structs.EvalDequeueResponse) error {
    71  	if done, err := e.srv.forward("Eval.Dequeue", args, args, reply); done {
    72  		return err
    73  	}
    74  	defer metrics.MeasureSince([]string{"nomad", "eval", "dequeue"}, time.Now())
    75  
    76  	// Ensure there is at least one scheduler
    77  	if len(args.Schedulers) == 0 {
    78  		return fmt.Errorf("dequeue requires at least one scheduler type")
    79  	}
    80  
    81  	// Check that there isn't a scheduler version mismatch
    82  	if args.SchedulerVersion != scheduler.SchedulerVersion {
    83  		return fmt.Errorf("dequeue disallowed: calling scheduler version is %d; leader version is %d",
    84  			args.SchedulerVersion, scheduler.SchedulerVersion)
    85  	}
    86  
    87  	// Ensure there is a default timeout
    88  	if args.Timeout <= 0 {
    89  		args.Timeout = DefaultDequeueTimeout
    90  	}
    91  
    92  	// Attempt the dequeue
    93  	eval, token, err := e.srv.evalBroker.Dequeue(args.Schedulers, args.Timeout)
    94  	if err != nil {
    95  		return err
    96  	}
    97  
    98  	// Provide the output if any
    99  	if eval != nil {
   100  		reply.Eval = eval
   101  		reply.Token = token
   102  	}
   103  
   104  	// Set the query response
   105  	e.srv.setQueryMeta(&reply.QueryMeta)
   106  	return nil
   107  }
   108  
   109  // Ack is used to acknowledge completion of a dequeued evaluation
   110  func (e *Eval) Ack(args *structs.EvalAckRequest,
   111  	reply *structs.GenericResponse) error {
   112  	if done, err := e.srv.forward("Eval.Ack", args, args, reply); done {
   113  		return err
   114  	}
   115  	defer metrics.MeasureSince([]string{"nomad", "eval", "ack"}, time.Now())
   116  
   117  	// Ack the EvalID
   118  	if err := e.srv.evalBroker.Ack(args.EvalID, args.Token); err != nil {
   119  		return err
   120  	}
   121  	return nil
   122  }
   123  
   124  // NAck is used to negative acknowledge completion of a dequeued evaluation
   125  func (e *Eval) Nack(args *structs.EvalAckRequest,
   126  	reply *structs.GenericResponse) error {
   127  	if done, err := e.srv.forward("Eval.Nack", args, args, reply); done {
   128  		return err
   129  	}
   130  	defer metrics.MeasureSince([]string{"nomad", "eval", "nack"}, time.Now())
   131  
   132  	// Nack the EvalID
   133  	if err := e.srv.evalBroker.Nack(args.EvalID, args.Token); err != nil {
   134  		return err
   135  	}
   136  	return nil
   137  }
   138  
   139  // Update is used to perform an update of an Eval if it is outstanding.
   140  func (e *Eval) Update(args *structs.EvalUpdateRequest,
   141  	reply *structs.GenericResponse) error {
   142  	if done, err := e.srv.forward("Eval.Update", args, args, reply); done {
   143  		return err
   144  	}
   145  	defer metrics.MeasureSince([]string{"nomad", "eval", "update"}, time.Now())
   146  
   147  	// Ensure there is only a single update with token
   148  	if len(args.Evals) != 1 {
   149  		return fmt.Errorf("only a single eval can be updated")
   150  	}
   151  	eval := args.Evals[0]
   152  
   153  	// Verify the evaluation is outstanding, and that the tokens match.
   154  	if err := e.srv.evalBroker.OutstandingReset(eval.ID, args.EvalToken); err != nil {
   155  		return err
   156  	}
   157  
   158  	// Update via Raft
   159  	_, index, err := e.srv.raftApply(structs.EvalUpdateRequestType, args)
   160  	if err != nil {
   161  		return err
   162  	}
   163  
   164  	// Update the index
   165  	reply.Index = index
   166  	return nil
   167  }
   168  
   169  // Create is used to make a new evaluation
   170  func (e *Eval) Create(args *structs.EvalUpdateRequest,
   171  	reply *structs.GenericResponse) error {
   172  	if done, err := e.srv.forward("Eval.Create", args, args, reply); done {
   173  		return err
   174  	}
   175  	defer metrics.MeasureSince([]string{"nomad", "eval", "create"}, time.Now())
   176  
   177  	// Ensure there is only a single update with token
   178  	if len(args.Evals) != 1 {
   179  		return fmt.Errorf("only a single eval can be created")
   180  	}
   181  	eval := args.Evals[0]
   182  
   183  	// Verify the parent evaluation is outstanding, and that the tokens match.
   184  	if err := e.srv.evalBroker.OutstandingReset(eval.PreviousEval, args.EvalToken); err != nil {
   185  		return err
   186  	}
   187  
   188  	// Look for the eval
   189  	snap, err := e.srv.fsm.State().Snapshot()
   190  	if err != nil {
   191  		return err
   192  	}
   193  	out, err := snap.EvalByID(eval.ID)
   194  	if err != nil {
   195  		return err
   196  	}
   197  	if out != nil {
   198  		return fmt.Errorf("evaluation already exists")
   199  	}
   200  
   201  	// Update via Raft
   202  	_, index, err := e.srv.raftApply(structs.EvalUpdateRequestType, args)
   203  	if err != nil {
   204  		return err
   205  	}
   206  
   207  	// Update the index
   208  	reply.Index = index
   209  	return nil
   210  }
   211  
   212  // Reblock is used to reinsert an existing blocked evaluation into the blocked
   213  // evaluation tracker.
   214  func (e *Eval) Reblock(args *structs.EvalUpdateRequest, reply *structs.GenericResponse) error {
   215  	if done, err := e.srv.forward("Eval.Reblock", args, args, reply); done {
   216  		return err
   217  	}
   218  	defer metrics.MeasureSince([]string{"nomad", "eval", "reblock"}, time.Now())
   219  
   220  	// Ensure there is only a single update with token
   221  	if len(args.Evals) != 1 {
   222  		return fmt.Errorf("only a single eval can be reblocked")
   223  	}
   224  	eval := args.Evals[0]
   225  
   226  	// Verify the evaluation is outstanding, and that the tokens match.
   227  	if err := e.srv.evalBroker.OutstandingReset(eval.ID, args.EvalToken); err != nil {
   228  		return err
   229  	}
   230  
   231  	// Look for the eval
   232  	snap, err := e.srv.fsm.State().Snapshot()
   233  	if err != nil {
   234  		return err
   235  	}
   236  	out, err := snap.EvalByID(eval.ID)
   237  	if err != nil {
   238  		return err
   239  	}
   240  	if out == nil {
   241  		return fmt.Errorf("evaluation does not exist")
   242  	}
   243  	if out.Status != structs.EvalStatusBlocked {
   244  		return fmt.Errorf("evaluation not blocked")
   245  	}
   246  
   247  	// Reblock the eval
   248  	e.srv.blockedEvals.Reblock(eval, args.EvalToken)
   249  	return nil
   250  }
   251  
   252  // Reap is used to cleanup dead evaluations and allocations
   253  func (e *Eval) Reap(args *structs.EvalDeleteRequest,
   254  	reply *structs.GenericResponse) error {
   255  	if done, err := e.srv.forward("Eval.Reap", args, args, reply); done {
   256  		return err
   257  	}
   258  	defer metrics.MeasureSince([]string{"nomad", "eval", "reap"}, time.Now())
   259  
   260  	// Update via Raft
   261  	_, index, err := e.srv.raftApply(structs.EvalDeleteRequestType, args)
   262  	if err != nil {
   263  		return err
   264  	}
   265  
   266  	// Update the index
   267  	reply.Index = index
   268  	return nil
   269  }
   270  
   271  // List is used to get a list of the evaluations in the system
   272  func (e *Eval) List(args *structs.EvalListRequest,
   273  	reply *structs.EvalListResponse) error {
   274  	if done, err := e.srv.forward("Eval.List", args, args, reply); done {
   275  		return err
   276  	}
   277  	defer metrics.MeasureSince([]string{"nomad", "eval", "list"}, time.Now())
   278  
   279  	// Setup the blocking query
   280  	opts := blockingOptions{
   281  		queryOpts: &args.QueryOptions,
   282  		queryMeta: &reply.QueryMeta,
   283  		watch:     watch.NewItems(watch.Item{Table: "evals"}),
   284  		run: func() error {
   285  			// Scan all the evaluations
   286  			snap, err := e.srv.fsm.State().Snapshot()
   287  			if err != nil {
   288  				return err
   289  			}
   290  			var iter memdb.ResultIterator
   291  			if prefix := args.QueryOptions.Prefix; prefix != "" {
   292  				iter, err = snap.EvalsByIDPrefix(prefix)
   293  			} else {
   294  				iter, err = snap.Evals()
   295  			}
   296  			if err != nil {
   297  				return err
   298  			}
   299  
   300  			var evals []*structs.Evaluation
   301  			for {
   302  				raw := iter.Next()
   303  				if raw == nil {
   304  					break
   305  				}
   306  				eval := raw.(*structs.Evaluation)
   307  				evals = append(evals, eval)
   308  			}
   309  			reply.Evaluations = evals
   310  
   311  			// Use the last index that affected the jobs table
   312  			index, err := snap.Index("evals")
   313  			if err != nil {
   314  				return err
   315  			}
   316  			reply.Index = index
   317  
   318  			// Set the query response
   319  			e.srv.setQueryMeta(&reply.QueryMeta)
   320  			return nil
   321  		}}
   322  	return e.srv.blockingRPC(&opts)
   323  }
   324  
   325  // Allocations is used to list the allocations for an evaluation
   326  func (e *Eval) Allocations(args *structs.EvalSpecificRequest,
   327  	reply *structs.EvalAllocationsResponse) error {
   328  	if done, err := e.srv.forward("Eval.Allocations", args, args, reply); done {
   329  		return err
   330  	}
   331  	defer metrics.MeasureSince([]string{"nomad", "eval", "allocations"}, time.Now())
   332  
   333  	// Setup the blocking query
   334  	opts := blockingOptions{
   335  		queryOpts: &args.QueryOptions,
   336  		queryMeta: &reply.QueryMeta,
   337  		watch:     watch.NewItems(watch.Item{AllocEval: args.EvalID}),
   338  		run: func() error {
   339  			// Capture the allocations
   340  			snap, err := e.srv.fsm.State().Snapshot()
   341  			if err != nil {
   342  				return err
   343  			}
   344  			allocs, err := snap.AllocsByEval(args.EvalID)
   345  			if err != nil {
   346  				return err
   347  			}
   348  
   349  			// Convert to a stub
   350  			if len(allocs) > 0 {
   351  				reply.Allocations = make([]*structs.AllocListStub, 0, len(allocs))
   352  				for _, alloc := range allocs {
   353  					reply.Allocations = append(reply.Allocations, alloc.Stub())
   354  				}
   355  			}
   356  
   357  			// Use the last index that affected the allocs table
   358  			index, err := snap.Index("allocs")
   359  			if err != nil {
   360  				return err
   361  			}
   362  			reply.Index = index
   363  
   364  			// Set the query response
   365  			e.srv.setQueryMeta(&reply.QueryMeta)
   366  			return nil
   367  		}}
   368  	return e.srv.blockingRPC(&opts)
   369  }