github.com/adityamillind98/nomad@v0.11.8/nomad/alloc_endpoint.go (about)

     1  package nomad
     2  
     3  import (
     4  	"fmt"
     5  	"time"
     6  
     7  	metrics "github.com/armon/go-metrics"
     8  	log "github.com/hashicorp/go-hclog"
     9  	memdb "github.com/hashicorp/go-memdb"
    10  	multierror "github.com/hashicorp/go-multierror"
    11  
    12  	"github.com/hashicorp/nomad/acl"
    13  	"github.com/hashicorp/nomad/helper"
    14  	"github.com/hashicorp/nomad/helper/uuid"
    15  	"github.com/hashicorp/nomad/nomad/state"
    16  	"github.com/hashicorp/nomad/nomad/structs"
    17  )
    18  
    19  // Alloc endpoint is used for manipulating allocations
    20  type Alloc struct {
    21  	srv    *Server
    22  	logger log.Logger
    23  }
    24  
    25  // List is used to list the allocations in the system
    26  func (a *Alloc) List(args *structs.AllocListRequest, reply *structs.AllocListResponse) error {
    27  	if done, err := a.srv.forward("Alloc.List", args, args, reply); done {
    28  		return err
    29  	}
    30  	defer metrics.MeasureSince([]string{"nomad", "alloc", "list"}, time.Now())
    31  
    32  	// Check namespace read-job permissions
    33  	if aclObj, err := a.srv.ResolveToken(args.AuthToken); err != nil {
    34  		return err
    35  	} else if aclObj != nil && !aclObj.AllowNsOp(args.RequestNamespace(), acl.NamespaceCapabilityReadJob) {
    36  		return structs.ErrPermissionDenied
    37  	}
    38  
    39  	// Setup the blocking query
    40  	opts := blockingOptions{
    41  		queryOpts: &args.QueryOptions,
    42  		queryMeta: &reply.QueryMeta,
    43  		run: func(ws memdb.WatchSet, state *state.StateStore) error {
    44  			// Capture all the allocations
    45  			var err error
    46  			var iter memdb.ResultIterator
    47  			if prefix := args.QueryOptions.Prefix; prefix != "" {
    48  				iter, err = state.AllocsByIDPrefix(ws, args.RequestNamespace(), prefix)
    49  			} else {
    50  				iter, err = state.AllocsByNamespace(ws, args.RequestNamespace())
    51  			}
    52  			if err != nil {
    53  				return err
    54  			}
    55  
    56  			var allocs []*structs.AllocListStub
    57  			for {
    58  				raw := iter.Next()
    59  				if raw == nil {
    60  					break
    61  				}
    62  				alloc := raw.(*structs.Allocation)
    63  				allocs = append(allocs, alloc.Stub())
    64  			}
    65  			reply.Allocations = allocs
    66  
    67  			// Use the last index that affected the jobs table
    68  			index, err := state.Index("allocs")
    69  			if err != nil {
    70  				return err
    71  			}
    72  			reply.Index = index
    73  
    74  			// Set the query response
    75  			a.srv.setQueryMeta(&reply.QueryMeta)
    76  			return nil
    77  		}}
    78  	return a.srv.blockingRPC(&opts)
    79  }
    80  
    81  // GetAlloc is used to lookup a particular allocation
    82  func (a *Alloc) GetAlloc(args *structs.AllocSpecificRequest,
    83  	reply *structs.SingleAllocResponse) error {
    84  	if done, err := a.srv.forward("Alloc.GetAlloc", args, args, reply); done {
    85  		return err
    86  	}
    87  	defer metrics.MeasureSince([]string{"nomad", "alloc", "get_alloc"}, time.Now())
    88  
    89  	// Check namespace read-job permissions before performing blocking query.
    90  	allowNsOp := acl.NamespaceValidator(acl.NamespaceCapabilityReadJob)
    91  	aclObj, err := a.srv.ResolveToken(args.AuthToken)
    92  	if err != nil {
    93  		// If ResolveToken had an unexpected error return that
    94  		if err != structs.ErrTokenNotFound {
    95  			return err
    96  		}
    97  
    98  		// Attempt to lookup AuthToken as a Node.SecretID since nodes
    99  		// call this endpoint and don't have an ACL token.
   100  		node, stateErr := a.srv.fsm.State().NodeBySecretID(nil, args.AuthToken)
   101  		if stateErr != nil {
   102  			// Return the original ResolveToken error with this err
   103  			var merr multierror.Error
   104  			merr.Errors = append(merr.Errors, err, stateErr)
   105  			return merr.ErrorOrNil()
   106  		}
   107  
   108  		// Not a node or a valid ACL token
   109  		if node == nil {
   110  			return structs.ErrTokenNotFound
   111  		}
   112  	}
   113  
   114  	// Setup the blocking query
   115  	opts := blockingOptions{
   116  		queryOpts: &args.QueryOptions,
   117  		queryMeta: &reply.QueryMeta,
   118  		run: func(ws memdb.WatchSet, state *state.StateStore) error {
   119  			// Lookup the allocation
   120  			out, err := state.AllocByID(ws, args.AllocID)
   121  			if err != nil {
   122  				return err
   123  			}
   124  
   125  			// Setup the output
   126  			reply.Alloc = out
   127  			if out != nil {
   128  				// Re-check namespace in case it differs from request.
   129  				if !allowNsOp(aclObj, out.Namespace) {
   130  					return structs.NewErrUnknownAllocation(args.AllocID)
   131  				}
   132  
   133  				reply.Index = out.ModifyIndex
   134  			} else {
   135  				// Use the last index that affected the allocs table
   136  				index, err := state.Index("allocs")
   137  				if err != nil {
   138  					return err
   139  				}
   140  				reply.Index = index
   141  			}
   142  
   143  			// Set the query response
   144  			a.srv.setQueryMeta(&reply.QueryMeta)
   145  			return nil
   146  		}}
   147  	return a.srv.blockingRPC(&opts)
   148  }
   149  
   150  // GetAllocs is used to lookup a set of allocations
   151  func (a *Alloc) GetAllocs(args *structs.AllocsGetRequest,
   152  	reply *structs.AllocsGetResponse) error {
   153  	if done, err := a.srv.forward("Alloc.GetAllocs", args, args, reply); done {
   154  		return err
   155  	}
   156  	defer metrics.MeasureSince([]string{"nomad", "alloc", "get_allocs"}, time.Now())
   157  
   158  	allocs := make([]*structs.Allocation, len(args.AllocIDs))
   159  
   160  	// Setup the blocking query. We wait for at least one of the requested
   161  	// allocations to be above the min query index. This guarantees that the
   162  	// server has received that index.
   163  	opts := blockingOptions{
   164  		queryOpts: &args.QueryOptions,
   165  		queryMeta: &reply.QueryMeta,
   166  		run: func(ws memdb.WatchSet, state *state.StateStore) error {
   167  			// Lookup the allocation
   168  			thresholdMet := false
   169  			maxIndex := uint64(0)
   170  			for i, alloc := range args.AllocIDs {
   171  				out, err := state.AllocByID(ws, alloc)
   172  				if err != nil {
   173  					return err
   174  				}
   175  				if out == nil {
   176  					// We don't have the alloc yet
   177  					thresholdMet = false
   178  					break
   179  				}
   180  
   181  				// Store the pointer
   182  				allocs[i] = out
   183  
   184  				// Check if we have passed the minimum index
   185  				if out.ModifyIndex > args.QueryOptions.MinQueryIndex {
   186  					thresholdMet = true
   187  				}
   188  
   189  				if maxIndex < out.ModifyIndex {
   190  					maxIndex = out.ModifyIndex
   191  				}
   192  			}
   193  
   194  			// Setup the output
   195  			if thresholdMet {
   196  				reply.Allocs = allocs
   197  				reply.Index = maxIndex
   198  			} else {
   199  				// Use the last index that affected the nodes table
   200  				index, err := state.Index("allocs")
   201  				if err != nil {
   202  					return err
   203  				}
   204  				reply.Index = index
   205  			}
   206  
   207  			// Set the query response
   208  			a.srv.setQueryMeta(&reply.QueryMeta)
   209  			return nil
   210  		},
   211  	}
   212  	return a.srv.blockingRPC(&opts)
   213  }
   214  
   215  // Stop is used to stop an allocation and migrate it to another node.
   216  func (a *Alloc) Stop(args *structs.AllocStopRequest, reply *structs.AllocStopResponse) error {
   217  	if done, err := a.srv.forward("Alloc.Stop", args, args, reply); done {
   218  		return err
   219  	}
   220  	defer metrics.MeasureSince([]string{"nomad", "alloc", "stop"}, time.Now())
   221  
   222  	alloc, err := getAlloc(a.srv.State(), args.AllocID)
   223  	if err != nil {
   224  		return err
   225  	}
   226  
   227  	// Check for namespace alloc-lifecycle permissions.
   228  	allowNsOp := acl.NamespaceValidator(acl.NamespaceCapabilityAllocLifecycle)
   229  	aclObj, err := a.srv.ResolveToken(args.AuthToken)
   230  	if err != nil {
   231  		return err
   232  	} else if !allowNsOp(aclObj, alloc.Namespace) {
   233  		return structs.ErrPermissionDenied
   234  	}
   235  
   236  	now := time.Now().UTC().UnixNano()
   237  	eval := &structs.Evaluation{
   238  		ID:             uuid.Generate(),
   239  		Namespace:      alloc.Namespace,
   240  		Priority:       alloc.Job.Priority,
   241  		Type:           alloc.Job.Type,
   242  		TriggeredBy:    structs.EvalTriggerAllocStop,
   243  		JobID:          alloc.Job.ID,
   244  		JobModifyIndex: alloc.Job.ModifyIndex,
   245  		Status:         structs.EvalStatusPending,
   246  		CreateTime:     now,
   247  		ModifyTime:     now,
   248  	}
   249  
   250  	transitionReq := &structs.AllocUpdateDesiredTransitionRequest{
   251  		Evals: []*structs.Evaluation{eval},
   252  		Allocs: map[string]*structs.DesiredTransition{
   253  			args.AllocID: {
   254  				Migrate: helper.BoolToPtr(true),
   255  			},
   256  		},
   257  	}
   258  
   259  	// Commit this update via Raft
   260  	_, index, err := a.srv.raftApply(structs.AllocUpdateDesiredTransitionRequestType, transitionReq)
   261  	if err != nil {
   262  		a.logger.Error("AllocUpdateDesiredTransitionRequest failed", "error", err)
   263  		return err
   264  	}
   265  
   266  	// Setup the response
   267  	reply.Index = index
   268  	reply.EvalID = eval.ID
   269  	return nil
   270  }
   271  
   272  // UpdateDesiredTransition is used to update the desired transitions of an
   273  // allocation.
   274  func (a *Alloc) UpdateDesiredTransition(args *structs.AllocUpdateDesiredTransitionRequest, reply *structs.GenericResponse) error {
   275  	if done, err := a.srv.forward("Alloc.UpdateDesiredTransition", args, args, reply); done {
   276  		return err
   277  	}
   278  	defer metrics.MeasureSince([]string{"nomad", "alloc", "update_desired_transition"}, time.Now())
   279  
   280  	// Check that it is a management token.
   281  	if aclObj, err := a.srv.ResolveToken(args.AuthToken); err != nil {
   282  		return err
   283  	} else if aclObj != nil && !aclObj.IsManagement() {
   284  		return structs.ErrPermissionDenied
   285  	}
   286  
   287  	// Ensure at least a single alloc
   288  	if len(args.Allocs) == 0 {
   289  		return fmt.Errorf("must update at least one allocation")
   290  	}
   291  
   292  	// Commit this update via Raft
   293  	_, index, err := a.srv.raftApply(structs.AllocUpdateDesiredTransitionRequestType, args)
   294  	if err != nil {
   295  		a.logger.Error("AllocUpdateDesiredTransitionRequest failed", "error", err)
   296  		return err
   297  	}
   298  
   299  	// Setup the response
   300  	reply.Index = index
   301  	return nil
   302  }