github.com/ryanslade/nomad@v0.2.4-0.20160128061903-fc95782f2089/nomad/job_endpoint.go (about) 1 package nomad 2 3 import ( 4 "errors" 5 "fmt" 6 "time" 7 8 "github.com/armon/go-metrics" 9 "github.com/hashicorp/go-memdb" 10 "github.com/hashicorp/nomad/nomad/structs" 11 "github.com/hashicorp/nomad/nomad/watch" 12 ) 13 14 // Job endpoint is used for job interactions 15 type Job struct { 16 srv *Server 17 } 18 19 // Register is used to upsert a job for scheduling 20 func (j *Job) Register(args *structs.JobRegisterRequest, reply *structs.JobRegisterResponse) error { 21 if done, err := j.srv.forward("Job.Register", args, args, reply); done { 22 return err 23 } 24 defer metrics.MeasureSince([]string{"nomad", "job", "register"}, time.Now()) 25 26 // Validate the arguments 27 if args.Job == nil { 28 return fmt.Errorf("missing job for registration") 29 } 30 31 if err := j.checkBlacklist(args.Job); err != nil { 32 return err 33 } 34 35 // Initialize the job fields (sets defaults and any necessary init work). 36 args.Job.InitFields() 37 38 if err := args.Job.Validate(); err != nil { 39 return err 40 } 41 42 if args.Job.Type == structs.JobTypeCore { 43 return fmt.Errorf("job type cannot be core") 44 } 45 46 // Commit this update via Raft 47 _, index, err := j.srv.raftApply(structs.JobRegisterRequestType, args) 48 if err != nil { 49 j.srv.logger.Printf("[ERR] nomad.job: Register failed: %v", err) 50 return err 51 } 52 53 // Populate the reply with job information 54 reply.JobModifyIndex = index 55 56 // If the job is periodic, we don't create an eval. 57 if args.Job.IsPeriodic() { 58 return nil 59 } 60 61 // Create a new evaluation 62 eval := &structs.Evaluation{ 63 ID: structs.GenerateUUID(), 64 Priority: args.Job.Priority, 65 Type: args.Job.Type, 66 TriggeredBy: structs.EvalTriggerJobRegister, 67 JobID: args.Job.ID, 68 JobModifyIndex: index, 69 Status: structs.EvalStatusPending, 70 } 71 update := &structs.EvalUpdateRequest{ 72 Evals: []*structs.Evaluation{eval}, 73 WriteRequest: structs.WriteRequest{Region: args.Region}, 74 } 75 76 // Commit this evaluation via Raft 77 // XXX: There is a risk of partial failure where the JobRegister succeeds 78 // but that the EvalUpdate does not. 79 _, evalIndex, err := j.srv.raftApply(structs.EvalUpdateRequestType, update) 80 if err != nil { 81 j.srv.logger.Printf("[ERR] nomad.job: Eval create failed: %v", err) 82 return err 83 } 84 85 // Populate the reply with eval information 86 reply.EvalID = eval.ID 87 reply.EvalCreateIndex = evalIndex 88 reply.Index = evalIndex 89 return nil 90 } 91 92 // checkBlacklist returns an error if the user has set any blacklisted field in 93 // the job. 94 func (j *Job) checkBlacklist(job *structs.Job) error { 95 if job.GC { 96 return errors.New("GC field of a job is used only internally and should not be set by user") 97 } 98 99 return nil 100 } 101 102 // Evaluate is used to force a job for re-evaluation 103 func (j *Job) Evaluate(args *structs.JobEvaluateRequest, reply *structs.JobRegisterResponse) error { 104 if done, err := j.srv.forward("Job.Evaluate", args, args, reply); done { 105 return err 106 } 107 defer metrics.MeasureSince([]string{"nomad", "job", "evaluate"}, time.Now()) 108 109 // Validate the arguments 110 if args.JobID == "" { 111 return fmt.Errorf("missing job ID for evaluation") 112 } 113 114 // Lookup the job 115 snap, err := j.srv.fsm.State().Snapshot() 116 if err != nil { 117 return err 118 } 119 job, err := snap.JobByID(args.JobID) 120 if err != nil { 121 return err 122 } 123 if job == nil { 124 return fmt.Errorf("job not found") 125 } 126 127 if job.IsPeriodic() { 128 return fmt.Errorf("can't evaluate periodic job") 129 } 130 131 // Create a new evaluation 132 eval := &structs.Evaluation{ 133 ID: structs.GenerateUUID(), 134 Priority: job.Priority, 135 Type: job.Type, 136 TriggeredBy: structs.EvalTriggerJobRegister, 137 JobID: job.ID, 138 JobModifyIndex: job.ModifyIndex, 139 Status: structs.EvalStatusPending, 140 } 141 update := &structs.EvalUpdateRequest{ 142 Evals: []*structs.Evaluation{eval}, 143 WriteRequest: structs.WriteRequest{Region: args.Region}, 144 } 145 146 // Commit this evaluation via Raft 147 _, evalIndex, err := j.srv.raftApply(structs.EvalUpdateRequestType, update) 148 if err != nil { 149 j.srv.logger.Printf("[ERR] nomad.job: Eval create failed: %v", err) 150 return err 151 } 152 153 // Setup the reply 154 reply.EvalID = eval.ID 155 reply.EvalCreateIndex = evalIndex 156 reply.JobModifyIndex = job.ModifyIndex 157 reply.Index = evalIndex 158 return nil 159 } 160 161 // Deregister is used to remove a job the cluster. 162 func (j *Job) Deregister(args *structs.JobDeregisterRequest, reply *structs.JobDeregisterResponse) error { 163 if done, err := j.srv.forward("Job.Deregister", args, args, reply); done { 164 return err 165 } 166 defer metrics.MeasureSince([]string{"nomad", "job", "deregister"}, time.Now()) 167 168 // Validate the arguments 169 if args.JobID == "" { 170 return fmt.Errorf("missing job ID for evaluation") 171 } 172 173 // Lookup the job 174 snap, err := j.srv.fsm.State().Snapshot() 175 if err != nil { 176 return err 177 } 178 job, err := snap.JobByID(args.JobID) 179 if err != nil { 180 return err 181 } 182 if job == nil { 183 return fmt.Errorf("job not found") 184 } 185 186 // Commit this update via Raft 187 _, index, err := j.srv.raftApply(structs.JobDeregisterRequestType, args) 188 if err != nil { 189 j.srv.logger.Printf("[ERR] nomad.job: Deregister failed: %v", err) 190 return err 191 } 192 193 // Populate the reply with job information 194 reply.JobModifyIndex = index 195 196 // If the job is periodic, we don't create an eval. 197 if job.IsPeriodic() { 198 return nil 199 } 200 201 // Create a new evaluation 202 // XXX: The job priority / type is strange for this, since it's not a high 203 // priority even if the job was. The scheduler itself also doesn't matter, 204 // since all should be able to handle deregistration in the same way. 205 eval := &structs.Evaluation{ 206 ID: structs.GenerateUUID(), 207 Priority: structs.JobDefaultPriority, 208 Type: structs.JobTypeService, 209 TriggeredBy: structs.EvalTriggerJobDeregister, 210 JobID: args.JobID, 211 JobModifyIndex: index, 212 Status: structs.EvalStatusPending, 213 } 214 update := &structs.EvalUpdateRequest{ 215 Evals: []*structs.Evaluation{eval}, 216 WriteRequest: structs.WriteRequest{Region: args.Region}, 217 } 218 219 // Commit this evaluation via Raft 220 _, evalIndex, err := j.srv.raftApply(structs.EvalUpdateRequestType, update) 221 if err != nil { 222 j.srv.logger.Printf("[ERR] nomad.job: Eval create failed: %v", err) 223 return err 224 } 225 226 // Populate the reply with eval information 227 reply.EvalID = eval.ID 228 reply.EvalCreateIndex = evalIndex 229 reply.Index = evalIndex 230 return nil 231 } 232 233 // GetJob is used to request information about a specific job 234 func (j *Job) GetJob(args *structs.JobSpecificRequest, 235 reply *structs.SingleJobResponse) error { 236 if done, err := j.srv.forward("Job.GetJob", args, args, reply); done { 237 return err 238 } 239 defer metrics.MeasureSince([]string{"nomad", "job", "get_job"}, time.Now()) 240 241 // Setup the blocking query 242 opts := blockingOptions{ 243 queryOpts: &args.QueryOptions, 244 queryMeta: &reply.QueryMeta, 245 watch: watch.NewItems(watch.Item{Job: args.JobID}), 246 run: func() error { 247 248 // Look for the job 249 snap, err := j.srv.fsm.State().Snapshot() 250 if err != nil { 251 return err 252 } 253 out, err := snap.JobByID(args.JobID) 254 if err != nil { 255 return err 256 } 257 258 // Setup the output 259 reply.Job = out 260 if out != nil { 261 reply.Index = out.ModifyIndex 262 } else { 263 // Use the last index that affected the nodes table 264 index, err := snap.Index("jobs") 265 if err != nil { 266 return err 267 } 268 reply.Index = index 269 } 270 271 // Set the query response 272 j.srv.setQueryMeta(&reply.QueryMeta) 273 return nil 274 }} 275 return j.srv.blockingRPC(&opts) 276 } 277 278 // List is used to list the jobs registered in the system 279 func (j *Job) List(args *structs.JobListRequest, 280 reply *structs.JobListResponse) error { 281 if done, err := j.srv.forward("Job.List", args, args, reply); done { 282 return err 283 } 284 defer metrics.MeasureSince([]string{"nomad", "job", "list"}, 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{Table: "jobs"}), 291 run: func() error { 292 // Capture all the jobs 293 snap, err := j.srv.fsm.State().Snapshot() 294 if err != nil { 295 return err 296 } 297 var iter memdb.ResultIterator 298 if prefix := args.QueryOptions.Prefix; prefix != "" { 299 iter, err = snap.JobsByIDPrefix(prefix) 300 } else { 301 iter, err = snap.Jobs() 302 } 303 if err != nil { 304 return err 305 } 306 307 var jobs []*structs.JobListStub 308 for { 309 raw := iter.Next() 310 if raw == nil { 311 break 312 } 313 job := raw.(*structs.Job) 314 jobs = append(jobs, job.Stub()) 315 } 316 reply.Jobs = jobs 317 318 // Use the last index that affected the jobs table 319 index, err := snap.Index("jobs") 320 if err != nil { 321 return err 322 } 323 reply.Index = index 324 325 // Set the query response 326 j.srv.setQueryMeta(&reply.QueryMeta) 327 return nil 328 }} 329 return j.srv.blockingRPC(&opts) 330 } 331 332 // Allocations is used to list the allocations for a job 333 func (j *Job) Allocations(args *structs.JobSpecificRequest, 334 reply *structs.JobAllocationsResponse) error { 335 if done, err := j.srv.forward("Job.Allocations", args, args, reply); done { 336 return err 337 } 338 defer metrics.MeasureSince([]string{"nomad", "job", "allocations"}, time.Now()) 339 340 // Setup the blocking query 341 opts := blockingOptions{ 342 queryOpts: &args.QueryOptions, 343 queryMeta: &reply.QueryMeta, 344 watch: watch.NewItems(watch.Item{AllocJob: args.JobID}), 345 run: func() error { 346 // Capture the allocations 347 snap, err := j.srv.fsm.State().Snapshot() 348 if err != nil { 349 return err 350 } 351 allocs, err := snap.AllocsByJob(args.JobID) 352 if err != nil { 353 return err 354 } 355 356 // Convert to stubs 357 if len(allocs) > 0 { 358 reply.Allocations = make([]*structs.AllocListStub, 0, len(allocs)) 359 for _, alloc := range allocs { 360 reply.Allocations = append(reply.Allocations, alloc.Stub()) 361 } 362 } 363 364 // Use the last index that affected the allocs table 365 index, err := snap.Index("allocs") 366 if err != nil { 367 return err 368 } 369 reply.Index = index 370 371 // Set the query response 372 j.srv.setQueryMeta(&reply.QueryMeta) 373 return nil 374 375 }} 376 return j.srv.blockingRPC(&opts) 377 } 378 379 // Evaluations is used to list the evaluations for a job 380 func (j *Job) Evaluations(args *structs.JobSpecificRequest, 381 reply *structs.JobEvaluationsResponse) error { 382 if done, err := j.srv.forward("Job.Evaluations", args, args, reply); done { 383 return err 384 } 385 defer metrics.MeasureSince([]string{"nomad", "job", "evaluations"}, time.Now()) 386 387 // Capture the evaluations 388 snap, err := j.srv.fsm.State().Snapshot() 389 if err != nil { 390 return err 391 } 392 reply.Evaluations, err = snap.EvalsByJob(args.JobID) 393 if err != nil { 394 return err 395 } 396 397 // Use the last index that affected the evals table 398 index, err := snap.Index("evals") 399 if err != nil { 400 return err 401 } 402 reply.Index = index 403 404 // Set the query response 405 j.srv.setQueryMeta(&reply.QueryMeta) 406 return nil 407 }