github.com/maier/nomad@v0.4.1-0.20161110003312-a9e3d0b8549d/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 "github.com/hashicorp/nomad/nomad/structs" 10 "github.com/hashicorp/nomad/nomad/watch" 11 "github.com/hashicorp/nomad/scheduler" 12 ) 13 14 const ( 15 // DefaultDequeueTimeout is used if no dequeue timeout is provided 16 DefaultDequeueTimeout = time.Second 17 ) 18 19 // Eval endpoint is used for eval interactions 20 type Eval struct { 21 srv *Server 22 } 23 24 // GetEval is used to request information about a specific evaluation 25 func (e *Eval) GetEval(args *structs.EvalSpecificRequest, 26 reply *structs.SingleEvalResponse) error { 27 if done, err := e.srv.forward("Eval.GetEval", args, args, reply); done { 28 return err 29 } 30 defer metrics.MeasureSince([]string{"nomad", "eval", "get_eval"}, time.Now()) 31 32 // Setup the blocking query 33 opts := blockingOptions{ 34 queryOpts: &args.QueryOptions, 35 queryMeta: &reply.QueryMeta, 36 watch: watch.NewItems(watch.Item{Eval: args.EvalID}), 37 run: func() error { 38 // Look for the job 39 snap, err := e.srv.fsm.State().Snapshot() 40 if err != nil { 41 return err 42 } 43 out, err := snap.EvalByID(args.EvalID) 44 if err != nil { 45 return err 46 } 47 48 // Setup the output 49 reply.Eval = out 50 if out != nil { 51 reply.Index = out.ModifyIndex 52 } else { 53 // Use the last index that affected the nodes table 54 index, err := snap.Index("evals") 55 if err != nil { 56 return err 57 } 58 reply.Index = index 59 } 60 61 // Set the query response 62 e.srv.setQueryMeta(&reply.QueryMeta) 63 return nil 64 }} 65 return e.srv.blockingRPC(&opts) 66 } 67 68 // Dequeue is used to dequeue a pending evaluation 69 func (e *Eval) Dequeue(args *structs.EvalDequeueRequest, 70 reply *structs.EvalDequeueResponse) error { 71 if done, err := e.srv.forward("Eval.Dequeue", args, args, reply); done { 72 return err 73 } 74 defer metrics.MeasureSince([]string{"nomad", "eval", "dequeue"}, time.Now()) 75 76 // Ensure there is at least one scheduler 77 if len(args.Schedulers) == 0 { 78 return fmt.Errorf("dequeue requires at least one scheduler type") 79 } 80 81 // Check that there isn't a scheduler version mismatch 82 if args.SchedulerVersion != scheduler.SchedulerVersion { 83 return fmt.Errorf("dequeue disallowed: calling scheduler version is %d; leader version is %d", 84 args.SchedulerVersion, scheduler.SchedulerVersion) 85 } 86 87 // Ensure there is a default timeout 88 if args.Timeout <= 0 { 89 args.Timeout = DefaultDequeueTimeout 90 } 91 92 // Attempt the dequeue 93 eval, token, err := e.srv.evalBroker.Dequeue(args.Schedulers, args.Timeout) 94 if err != nil { 95 return err 96 } 97 98 // Provide the output if any 99 if eval != nil { 100 reply.Eval = eval 101 reply.Token = token 102 } 103 104 // Set the query response 105 e.srv.setQueryMeta(&reply.QueryMeta) 106 return nil 107 } 108 109 // Ack is used to acknowledge completion of a dequeued evaluation 110 func (e *Eval) Ack(args *structs.EvalAckRequest, 111 reply *structs.GenericResponse) error { 112 if done, err := e.srv.forward("Eval.Ack", args, args, reply); done { 113 return err 114 } 115 defer metrics.MeasureSince([]string{"nomad", "eval", "ack"}, time.Now()) 116 117 // Ack the EvalID 118 if err := e.srv.evalBroker.Ack(args.EvalID, args.Token); err != nil { 119 return err 120 } 121 return nil 122 } 123 124 // NAck is used to negative acknowledge completion of a dequeued evaluation 125 func (e *Eval) Nack(args *structs.EvalAckRequest, 126 reply *structs.GenericResponse) error { 127 if done, err := e.srv.forward("Eval.Nack", args, args, reply); done { 128 return err 129 } 130 defer metrics.MeasureSince([]string{"nomad", "eval", "nack"}, time.Now()) 131 132 // Nack the EvalID 133 if err := e.srv.evalBroker.Nack(args.EvalID, args.Token); err != nil { 134 return err 135 } 136 return nil 137 } 138 139 // Update is used to perform an update of an Eval if it is outstanding. 140 func (e *Eval) Update(args *structs.EvalUpdateRequest, 141 reply *structs.GenericResponse) error { 142 if done, err := e.srv.forward("Eval.Update", args, args, reply); done { 143 return err 144 } 145 defer metrics.MeasureSince([]string{"nomad", "eval", "update"}, time.Now()) 146 147 // Ensure there is only a single update with token 148 if len(args.Evals) != 1 { 149 return fmt.Errorf("only a single eval can be updated") 150 } 151 eval := args.Evals[0] 152 153 // Verify the evaluation is outstanding, and that the tokens match. 154 if err := e.srv.evalBroker.OutstandingReset(eval.ID, args.EvalToken); err != nil { 155 return err 156 } 157 158 // Update via Raft 159 _, index, err := e.srv.raftApply(structs.EvalUpdateRequestType, args) 160 if err != nil { 161 return err 162 } 163 164 // Update the index 165 reply.Index = index 166 return nil 167 } 168 169 // Create is used to make a new evaluation 170 func (e *Eval) Create(args *structs.EvalUpdateRequest, 171 reply *structs.GenericResponse) error { 172 if done, err := e.srv.forward("Eval.Create", args, args, reply); done { 173 return err 174 } 175 defer metrics.MeasureSince([]string{"nomad", "eval", "create"}, time.Now()) 176 177 // Ensure there is only a single update with token 178 if len(args.Evals) != 1 { 179 return fmt.Errorf("only a single eval can be created") 180 } 181 eval := args.Evals[0] 182 183 // Verify the parent evaluation is outstanding, and that the tokens match. 184 if err := e.srv.evalBroker.OutstandingReset(eval.PreviousEval, args.EvalToken); err != nil { 185 return err 186 } 187 188 // Look for the eval 189 snap, err := e.srv.fsm.State().Snapshot() 190 if err != nil { 191 return err 192 } 193 out, err := snap.EvalByID(eval.ID) 194 if err != nil { 195 return err 196 } 197 if out != nil { 198 return fmt.Errorf("evaluation already exists") 199 } 200 201 // Update via Raft 202 _, index, err := e.srv.raftApply(structs.EvalUpdateRequestType, args) 203 if err != nil { 204 return err 205 } 206 207 // Update the index 208 reply.Index = index 209 return nil 210 } 211 212 // Reblock is used to reinsert an existing blocked evaluation into the blocked 213 // evaluation tracker. 214 func (e *Eval) Reblock(args *structs.EvalUpdateRequest, reply *structs.GenericResponse) error { 215 if done, err := e.srv.forward("Eval.Reblock", args, args, reply); done { 216 return err 217 } 218 defer metrics.MeasureSince([]string{"nomad", "eval", "reblock"}, time.Now()) 219 220 // Ensure there is only a single update with token 221 if len(args.Evals) != 1 { 222 return fmt.Errorf("only a single eval can be reblocked") 223 } 224 eval := args.Evals[0] 225 226 // Verify the evaluation is outstanding, and that the tokens match. 227 if err := e.srv.evalBroker.OutstandingReset(eval.ID, args.EvalToken); err != nil { 228 return err 229 } 230 231 // Look for the eval 232 snap, err := e.srv.fsm.State().Snapshot() 233 if err != nil { 234 return err 235 } 236 out, err := snap.EvalByID(eval.ID) 237 if err != nil { 238 return err 239 } 240 if out == nil { 241 return fmt.Errorf("evaluation does not exist") 242 } 243 if out.Status != structs.EvalStatusBlocked { 244 return fmt.Errorf("evaluation not blocked") 245 } 246 247 // Reblock the eval 248 e.srv.blockedEvals.Reblock(eval, args.EvalToken) 249 return nil 250 } 251 252 // Reap is used to cleanup dead evaluations and allocations 253 func (e *Eval) Reap(args *structs.EvalDeleteRequest, 254 reply *structs.GenericResponse) error { 255 if done, err := e.srv.forward("Eval.Reap", args, args, reply); done { 256 return err 257 } 258 defer metrics.MeasureSince([]string{"nomad", "eval", "reap"}, time.Now()) 259 260 // Update via Raft 261 _, index, err := e.srv.raftApply(structs.EvalDeleteRequestType, args) 262 if err != nil { 263 return err 264 } 265 266 // Update the index 267 reply.Index = index 268 return nil 269 } 270 271 // List is used to get a list of the evaluations in the system 272 func (e *Eval) List(args *structs.EvalListRequest, 273 reply *structs.EvalListResponse) error { 274 if done, err := e.srv.forward("Eval.List", args, args, reply); done { 275 return err 276 } 277 defer metrics.MeasureSince([]string{"nomad", "eval", "list"}, time.Now()) 278 279 // Setup the blocking query 280 opts := blockingOptions{ 281 queryOpts: &args.QueryOptions, 282 queryMeta: &reply.QueryMeta, 283 watch: watch.NewItems(watch.Item{Table: "evals"}), 284 run: func() error { 285 // Scan all the evaluations 286 snap, err := e.srv.fsm.State().Snapshot() 287 if err != nil { 288 return err 289 } 290 var iter memdb.ResultIterator 291 if prefix := args.QueryOptions.Prefix; prefix != "" { 292 iter, err = snap.EvalsByIDPrefix(prefix) 293 } else { 294 iter, err = snap.Evals() 295 } 296 if err != nil { 297 return err 298 } 299 300 var evals []*structs.Evaluation 301 for { 302 raw := iter.Next() 303 if raw == nil { 304 break 305 } 306 eval := raw.(*structs.Evaluation) 307 evals = append(evals, eval) 308 } 309 reply.Evaluations = evals 310 311 // Use the last index that affected the jobs table 312 index, err := snap.Index("evals") 313 if err != nil { 314 return err 315 } 316 reply.Index = index 317 318 // Set the query response 319 e.srv.setQueryMeta(&reply.QueryMeta) 320 return nil 321 }} 322 return e.srv.blockingRPC(&opts) 323 } 324 325 // Allocations is used to list the allocations for an evaluation 326 func (e *Eval) Allocations(args *structs.EvalSpecificRequest, 327 reply *structs.EvalAllocationsResponse) error { 328 if done, err := e.srv.forward("Eval.Allocations", args, args, reply); done { 329 return err 330 } 331 defer metrics.MeasureSince([]string{"nomad", "eval", "allocations"}, time.Now()) 332 333 // Setup the blocking query 334 opts := blockingOptions{ 335 queryOpts: &args.QueryOptions, 336 queryMeta: &reply.QueryMeta, 337 watch: watch.NewItems(watch.Item{AllocEval: args.EvalID}), 338 run: func() error { 339 // Capture the allocations 340 snap, err := e.srv.fsm.State().Snapshot() 341 if err != nil { 342 return err 343 } 344 allocs, err := snap.AllocsByEval(args.EvalID) 345 if err != nil { 346 return err 347 } 348 349 // Convert to a stub 350 if len(allocs) > 0 { 351 reply.Allocations = make([]*structs.AllocListStub, 0, len(allocs)) 352 for _, alloc := range allocs { 353 reply.Allocations = append(reply.Allocations, alloc.Stub()) 354 } 355 } 356 357 // Use the last index that affected the allocs table 358 index, err := snap.Index("allocs") 359 if err != nil { 360 return err 361 } 362 reply.Index = index 363 364 // Set the query response 365 e.srv.setQueryMeta(&reply.QueryMeta) 366 return nil 367 }} 368 return e.srv.blockingRPC(&opts) 369 }