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 }