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