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