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 }