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 }