github.com/anth0d/nomad@v0.0.0-20221214183521-ae3a0a2cad06/nomad/alloc_endpoint.go (about)

     1  package nomad
     2  
     3  import (
     4  	"fmt"
     5  	"net/http"
     6  	"time"
     7  
     8  	"github.com/armon/go-metrics"
     9  	"github.com/hashicorp/go-hclog"
    10  	"github.com/hashicorp/go-memdb"
    11  	"github.com/hashicorp/go-multierror"
    12  
    13  	"github.com/hashicorp/nomad/acl"
    14  	"github.com/hashicorp/nomad/helper/pointer"
    15  	"github.com/hashicorp/nomad/helper/uuid"
    16  	"github.com/hashicorp/nomad/nomad/state"
    17  	"github.com/hashicorp/nomad/nomad/state/paginator"
    18  	"github.com/hashicorp/nomad/nomad/structs"
    19  )
    20  
    21  // Alloc endpoint is used for manipulating allocations
    22  type Alloc struct {
    23  	srv    *Server
    24  	ctx    *RPCContext
    25  	logger hclog.Logger
    26  }
    27  
    28  func NewAllocEndpoint(srv *Server, ctx *RPCContext) *Alloc {
    29  	return &Alloc{srv: srv, ctx: ctx, logger: srv.logger.Named("alloc")}
    30  }
    31  
    32  // List is used to list the allocations in the system
    33  func (a *Alloc) List(args *structs.AllocListRequest, reply *structs.AllocListResponse) error {
    34  	if done, err := a.srv.forward("Alloc.List", args, args, reply); done {
    35  		return err
    36  	}
    37  	defer metrics.MeasureSince([]string{"nomad", "alloc", "list"}, time.Now())
    38  
    39  	namespace := args.RequestNamespace()
    40  
    41  	// Check namespace read-job permissions
    42  	aclObj, err := a.srv.ResolveToken(args.AuthToken)
    43  	if err != nil {
    44  		return err
    45  	}
    46  	if !aclObj.AllowNsOp(namespace, acl.NamespaceCapabilityReadJob) {
    47  		return structs.ErrPermissionDenied
    48  	}
    49  	allow := aclObj.AllowNsOpFunc(acl.NamespaceCapabilityReadJob)
    50  
    51  	// Setup the blocking query
    52  	sort := state.SortOption(args.Reverse)
    53  	opts := blockingOptions{
    54  		queryOpts: &args.QueryOptions,
    55  		queryMeta: &reply.QueryMeta,
    56  		run: func(ws memdb.WatchSet, state *state.StateStore) error {
    57  			// Scan all the allocations
    58  			var err error
    59  			var iter memdb.ResultIterator
    60  			var opts paginator.StructsTokenizerOptions
    61  
    62  			// get list of accessible namespaces
    63  			allowableNamespaces, err := allowedNSes(aclObj, state, allow)
    64  			if err == structs.ErrPermissionDenied {
    65  				// return empty allocation if token is not authorized for any
    66  				// namespace, matching other endpoints
    67  				reply.Allocations = make([]*structs.AllocListStub, 0)
    68  			} else if err != nil {
    69  				return err
    70  			} else {
    71  				if prefix := args.QueryOptions.Prefix; prefix != "" {
    72  					iter, err = state.AllocsByIDPrefix(ws, namespace, prefix, sort)
    73  					opts = paginator.StructsTokenizerOptions{
    74  						WithID: true,
    75  					}
    76  				} else if namespace != structs.AllNamespacesSentinel {
    77  					iter, err = state.AllocsByNamespaceOrdered(ws, namespace, sort)
    78  					opts = paginator.StructsTokenizerOptions{
    79  						WithCreateIndex: true,
    80  						WithID:          true,
    81  					}
    82  				} else {
    83  					iter, err = state.Allocs(ws, sort)
    84  					opts = paginator.StructsTokenizerOptions{
    85  						WithCreateIndex: true,
    86  						WithID:          true,
    87  					}
    88  				}
    89  				if err != nil {
    90  					return err
    91  				}
    92  
    93  				tokenizer := paginator.NewStructsTokenizer(iter, opts)
    94  				filters := []paginator.Filter{
    95  					paginator.NamespaceFilter{
    96  						AllowableNamespaces: allowableNamespaces,
    97  					},
    98  				}
    99  
   100  				var stubs []*structs.AllocListStub
   101  				paginator, err := paginator.NewPaginator(iter, tokenizer, filters, args.QueryOptions,
   102  					func(raw interface{}) error {
   103  						allocation := raw.(*structs.Allocation)
   104  						stubs = append(stubs, allocation.Stub(args.Fields))
   105  						return nil
   106  					})
   107  				if err != nil {
   108  					return structs.NewErrRPCCodedf(
   109  						http.StatusBadRequest, "failed to create result paginator: %v", err)
   110  				}
   111  
   112  				nextToken, err := paginator.Page()
   113  				if err != nil {
   114  					return structs.NewErrRPCCodedf(
   115  						http.StatusBadRequest, "failed to read result page: %v", err)
   116  				}
   117  
   118  				reply.QueryMeta.NextToken = nextToken
   119  				reply.Allocations = stubs
   120  			}
   121  
   122  			// Use the last index that affected the allocs table
   123  			index, err := state.Index("allocs")
   124  			if err != nil {
   125  				return err
   126  			}
   127  			reply.Index = index
   128  
   129  			// Set the query response
   130  			a.srv.setQueryMeta(&reply.QueryMeta)
   131  			return nil
   132  		}}
   133  	return a.srv.blockingRPC(&opts)
   134  }
   135  
   136  // GetAlloc is used to lookup a particular allocation
   137  func (a *Alloc) GetAlloc(args *structs.AllocSpecificRequest,
   138  	reply *structs.SingleAllocResponse) error {
   139  	if done, err := a.srv.forward("Alloc.GetAlloc", args, args, reply); done {
   140  		return err
   141  	}
   142  	defer metrics.MeasureSince([]string{"nomad", "alloc", "get_alloc"}, time.Now())
   143  
   144  	// Check namespace read-job permissions before performing blocking query.
   145  	allowNsOp := acl.NamespaceValidator(acl.NamespaceCapabilityReadJob)
   146  	aclObj, err := a.srv.ResolveToken(args.AuthToken)
   147  	if err != nil {
   148  		// If ResolveToken had an unexpected error return that
   149  		if err != structs.ErrTokenNotFound {
   150  			return err
   151  		}
   152  
   153  		// Attempt to lookup AuthToken as a Node.SecretID since nodes
   154  		// call this endpoint and don't have an ACL token.
   155  		node, stateErr := a.srv.fsm.State().NodeBySecretID(nil, args.AuthToken)
   156  		if stateErr != nil {
   157  			// Return the original ResolveToken error with this err
   158  			var merr multierror.Error
   159  			merr.Errors = append(merr.Errors, err, stateErr)
   160  			return merr.ErrorOrNil()
   161  		}
   162  
   163  		// Not a node or a valid ACL token
   164  		if node == nil {
   165  			return structs.ErrTokenNotFound
   166  		}
   167  	}
   168  
   169  	// Setup the blocking query
   170  	opts := blockingOptions{
   171  		queryOpts: &args.QueryOptions,
   172  		queryMeta: &reply.QueryMeta,
   173  		run: func(ws memdb.WatchSet, state *state.StateStore) error {
   174  			// Lookup the allocation
   175  			out, err := state.AllocByID(ws, args.AllocID)
   176  			if err != nil {
   177  				return err
   178  			}
   179  
   180  			// Setup the output
   181  			reply.Alloc = out
   182  			if out != nil {
   183  				// Re-check namespace in case it differs from request.
   184  				if !allowNsOp(aclObj, out.Namespace) {
   185  					return structs.NewErrUnknownAllocation(args.AllocID)
   186  				}
   187  
   188  				reply.Index = out.ModifyIndex
   189  			} else {
   190  				// Use the last index that affected the allocs table
   191  				index, err := state.Index("allocs")
   192  				if err != nil {
   193  					return err
   194  				}
   195  				reply.Index = index
   196  			}
   197  
   198  			// Set the query response
   199  			a.srv.setQueryMeta(&reply.QueryMeta)
   200  			return nil
   201  		}}
   202  	return a.srv.blockingRPC(&opts)
   203  }
   204  
   205  // GetAllocs is used to lookup a set of allocations
   206  func (a *Alloc) GetAllocs(args *structs.AllocsGetRequest,
   207  	reply *structs.AllocsGetResponse) error {
   208  
   209  	// Ensure the connection was initiated by a client if TLS is used.
   210  	err := validateTLSCertificateLevel(a.srv, a.ctx, tlsCertificateLevelClient)
   211  	if err != nil {
   212  		return err
   213  	}
   214  
   215  	if done, err := a.srv.forward("Alloc.GetAllocs", args, args, reply); done {
   216  		return err
   217  	}
   218  	defer metrics.MeasureSince([]string{"nomad", "alloc", "get_allocs"}, time.Now())
   219  
   220  	allocs := make([]*structs.Allocation, len(args.AllocIDs))
   221  
   222  	// Setup the blocking query. We wait for at least one of the requested
   223  	// allocations to be above the min query index. This guarantees that the
   224  	// server has received that index.
   225  	opts := blockingOptions{
   226  		queryOpts: &args.QueryOptions,
   227  		queryMeta: &reply.QueryMeta,
   228  		run: func(ws memdb.WatchSet, state *state.StateStore) error {
   229  			// Lookup the allocation
   230  			thresholdMet := false
   231  			maxIndex := uint64(0)
   232  			for i, alloc := range args.AllocIDs {
   233  				out, err := state.AllocByID(ws, alloc)
   234  				if err != nil {
   235  					return err
   236  				}
   237  				if out == nil {
   238  					// We don't have the alloc yet
   239  					thresholdMet = false
   240  					break
   241  				}
   242  
   243  				// Store the pointer
   244  				allocs[i] = out
   245  
   246  				// Check if we have passed the minimum index
   247  				if out.ModifyIndex > args.QueryOptions.MinQueryIndex {
   248  					thresholdMet = true
   249  				}
   250  
   251  				if maxIndex < out.ModifyIndex {
   252  					maxIndex = out.ModifyIndex
   253  				}
   254  			}
   255  
   256  			// Setup the output
   257  			if thresholdMet {
   258  				reply.Allocs = allocs
   259  				reply.Index = maxIndex
   260  			} else {
   261  				// Use the last index that affected the nodes table
   262  				index, err := state.Index("allocs")
   263  				if err != nil {
   264  					return err
   265  				}
   266  				reply.Index = index
   267  			}
   268  
   269  			// Set the query response
   270  			a.srv.setQueryMeta(&reply.QueryMeta)
   271  			return nil
   272  		},
   273  	}
   274  	return a.srv.blockingRPC(&opts)
   275  }
   276  
   277  // Stop is used to stop an allocation and migrate it to another node.
   278  func (a *Alloc) Stop(args *structs.AllocStopRequest, reply *structs.AllocStopResponse) error {
   279  	if done, err := a.srv.forward("Alloc.Stop", args, args, reply); done {
   280  		return err
   281  	}
   282  	defer metrics.MeasureSince([]string{"nomad", "alloc", "stop"}, time.Now())
   283  
   284  	alloc, err := getAlloc(a.srv.State(), args.AllocID)
   285  	if err != nil {
   286  		return err
   287  	}
   288  
   289  	// Check for namespace alloc-lifecycle permissions.
   290  	allowNsOp := acl.NamespaceValidator(acl.NamespaceCapabilityAllocLifecycle)
   291  	aclObj, err := a.srv.ResolveToken(args.AuthToken)
   292  	if err != nil {
   293  		return err
   294  	} else if !allowNsOp(aclObj, alloc.Namespace) {
   295  		return structs.ErrPermissionDenied
   296  	}
   297  
   298  	now := time.Now().UTC().UnixNano()
   299  	eval := &structs.Evaluation{
   300  		ID:             uuid.Generate(),
   301  		Namespace:      alloc.Namespace,
   302  		Priority:       alloc.Job.Priority,
   303  		Type:           alloc.Job.Type,
   304  		TriggeredBy:    structs.EvalTriggerAllocStop,
   305  		JobID:          alloc.Job.ID,
   306  		JobModifyIndex: alloc.Job.ModifyIndex,
   307  		Status:         structs.EvalStatusPending,
   308  		CreateTime:     now,
   309  		ModifyTime:     now,
   310  	}
   311  
   312  	transitionReq := &structs.AllocUpdateDesiredTransitionRequest{
   313  		Evals: []*structs.Evaluation{eval},
   314  		Allocs: map[string]*structs.DesiredTransition{
   315  			args.AllocID: {
   316  				Migrate:         pointer.Of(true),
   317  				NoShutdownDelay: pointer.Of(args.NoShutdownDelay),
   318  			},
   319  		},
   320  	}
   321  
   322  	// Commit this update via Raft
   323  	_, index, err := a.srv.raftApply(structs.AllocUpdateDesiredTransitionRequestType, transitionReq)
   324  	if err != nil {
   325  		a.logger.Error("AllocUpdateDesiredTransitionRequest failed", "error", err)
   326  		return err
   327  	}
   328  
   329  	// Setup the response
   330  	reply.Index = index
   331  	reply.EvalID = eval.ID
   332  	return nil
   333  }
   334  
   335  // UpdateDesiredTransition is used to update the desired transitions of an
   336  // allocation.
   337  func (a *Alloc) UpdateDesiredTransition(args *structs.AllocUpdateDesiredTransitionRequest, reply *structs.GenericResponse) error {
   338  	if done, err := a.srv.forward("Alloc.UpdateDesiredTransition", args, args, reply); done {
   339  		return err
   340  	}
   341  	defer metrics.MeasureSince([]string{"nomad", "alloc", "update_desired_transition"}, time.Now())
   342  
   343  	// Check that it is a management token.
   344  	if aclObj, err := a.srv.ResolveToken(args.AuthToken); err != nil {
   345  		return err
   346  	} else if aclObj != nil && !aclObj.IsManagement() {
   347  		return structs.ErrPermissionDenied
   348  	}
   349  
   350  	// Ensure at least a single alloc
   351  	if len(args.Allocs) == 0 {
   352  		return fmt.Errorf("must update at least one allocation")
   353  	}
   354  
   355  	// Commit this update via Raft
   356  	_, index, err := a.srv.raftApply(structs.AllocUpdateDesiredTransitionRequestType, args)
   357  	if err != nil {
   358  		a.logger.Error("AllocUpdateDesiredTransitionRequest failed", "error", err)
   359  		return err
   360  	}
   361  
   362  	// Setup the response
   363  	reply.Index = index
   364  	return nil
   365  }
   366  
   367  // GetServiceRegistrations returns a list of service registrations which belong
   368  // to the passed allocation ID.
   369  func (a *Alloc) GetServiceRegistrations(
   370  	args *structs.AllocServiceRegistrationsRequest,
   371  	reply *structs.AllocServiceRegistrationsResponse) error {
   372  
   373  	if done, err := a.srv.forward(structs.AllocServiceRegistrationsRPCMethod, args, args, reply); done {
   374  		return err
   375  	}
   376  	defer metrics.MeasureSince([]string{"nomad", "alloc", "get_service_registrations"}, time.Now())
   377  
   378  	// If ACLs are enabled, ensure the caller has the read-job namespace
   379  	// capability.
   380  	aclObj, err := a.srv.ResolveToken(args.AuthToken)
   381  	if err != nil {
   382  		return err
   383  	} else if aclObj != nil {
   384  		if !aclObj.AllowNsOp(args.RequestNamespace(), acl.NamespaceCapabilityReadJob) {
   385  			return structs.ErrPermissionDenied
   386  		}
   387  	}
   388  
   389  	// Set up the blocking query.
   390  	return a.srv.blockingRPC(&blockingOptions{
   391  		queryOpts: &args.QueryOptions,
   392  		queryMeta: &reply.QueryMeta,
   393  		run: func(ws memdb.WatchSet, stateStore *state.StateStore) error {
   394  
   395  			// Read the allocation to ensure its namespace matches the request
   396  			// args.
   397  			alloc, err := stateStore.AllocByID(ws, args.AllocID)
   398  			if err != nil {
   399  				return err
   400  			}
   401  
   402  			// Guard against the alloc not-existing or that the namespace does
   403  			// not match the request arguments.
   404  			if alloc == nil || alloc.Namespace != args.RequestNamespace() {
   405  				return nil
   406  			}
   407  
   408  			// Perform the state query to get an iterator.
   409  			iter, err := stateStore.GetServiceRegistrationsByAllocID(ws, args.AllocID)
   410  			if err != nil {
   411  				return err
   412  			}
   413  
   414  			// Set up our output after we have checked the error.
   415  			services := make([]*structs.ServiceRegistration, 0)
   416  
   417  			// Iterate the iterator, appending all service registrations
   418  			// returned to the reply.
   419  			for raw := iter.Next(); raw != nil; raw = iter.Next() {
   420  				services = append(services, raw.(*structs.ServiceRegistration))
   421  			}
   422  			reply.Services = services
   423  
   424  			// Use the index table to populate the query meta as we have no way
   425  			// of tracking the max index on deletes.
   426  			return a.srv.setReplyQueryMeta(stateStore, state.TableServiceRegistrations, &reply.QueryMeta)
   427  		},
   428  	})
   429  }