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