github.com/taylorchu/nomad@v0.5.3-rc1.0.20170407200202-db11e7dd7b55/api/jobs.go (about) 1 package api 2 3 import ( 4 "fmt" 5 "net/url" 6 "sort" 7 "strconv" 8 "time" 9 10 "github.com/gorhill/cronexpr" 11 "github.com/hashicorp/nomad/helper" 12 ) 13 14 const ( 15 // JobTypeService indicates a long-running processes 16 JobTypeService = "service" 17 18 // JobTypeBatch indicates a short-lived process 19 JobTypeBatch = "batch" 20 21 // PeriodicSpecCron is used for a cron spec. 22 PeriodicSpecCron = "cron" 23 ) 24 25 const ( 26 // RegisterEnforceIndexErrPrefix is the prefix to use in errors caused by 27 // enforcing the job modify index during registers. 28 RegisterEnforceIndexErrPrefix = "Enforcing job modify index" 29 ) 30 31 // Jobs is used to access the job-specific endpoints. 32 type Jobs struct { 33 client *Client 34 } 35 36 // Jobs returns a handle on the jobs endpoints. 37 func (c *Client) Jobs() *Jobs { 38 return &Jobs{client: c} 39 } 40 41 func (j *Jobs) Validate(job *Job, q *WriteOptions) (*JobValidateResponse, *WriteMeta, error) { 42 var resp JobValidateResponse 43 req := &JobValidateRequest{Job: job} 44 if q != nil { 45 req.WriteRequest = WriteRequest{Region: q.Region} 46 } 47 wm, err := j.client.write("/v1/validate/job", req, &resp, q) 48 return &resp, wm, err 49 } 50 51 // Register is used to register a new job. It returns the ID 52 // of the evaluation, along with any errors encountered. 53 func (j *Jobs) Register(job *Job, q *WriteOptions) (string, *WriteMeta, error) { 54 55 var resp registerJobResponse 56 57 req := &RegisterJobRequest{Job: job} 58 wm, err := j.client.write("/v1/jobs", req, &resp, q) 59 if err != nil { 60 return "", nil, err 61 } 62 return resp.EvalID, wm, nil 63 } 64 65 // EnforceRegister is used to register a job enforcing its job modify index. 66 func (j *Jobs) EnforceRegister(job *Job, modifyIndex uint64, q *WriteOptions) (string, *WriteMeta, error) { 67 68 var resp registerJobResponse 69 70 req := &RegisterJobRequest{ 71 Job: job, 72 EnforceIndex: true, 73 JobModifyIndex: modifyIndex, 74 } 75 wm, err := j.client.write("/v1/jobs", req, &resp, q) 76 if err != nil { 77 return "", nil, err 78 } 79 return resp.EvalID, wm, nil 80 } 81 82 // List is used to list all of the existing jobs. 83 func (j *Jobs) List(q *QueryOptions) ([]*JobListStub, *QueryMeta, error) { 84 var resp []*JobListStub 85 qm, err := j.client.query("/v1/jobs", &resp, q) 86 if err != nil { 87 return nil, qm, err 88 } 89 sort.Sort(JobIDSort(resp)) 90 return resp, qm, nil 91 } 92 93 // PrefixList is used to list all existing jobs that match the prefix. 94 func (j *Jobs) PrefixList(prefix string) ([]*JobListStub, *QueryMeta, error) { 95 return j.List(&QueryOptions{Prefix: prefix}) 96 } 97 98 // Info is used to retrieve information about a particular 99 // job given its unique ID. 100 func (j *Jobs) Info(jobID string, q *QueryOptions) (*Job, *QueryMeta, error) { 101 var resp Job 102 qm, err := j.client.query("/v1/job/"+jobID, &resp, q) 103 if err != nil { 104 return nil, nil, err 105 } 106 return &resp, qm, nil 107 } 108 109 // Allocations is used to return the allocs for a given job ID. 110 func (j *Jobs) Allocations(jobID string, allAllocs bool, q *QueryOptions) ([]*AllocationListStub, *QueryMeta, error) { 111 var resp []*AllocationListStub 112 u, err := url.Parse("/v1/job/" + jobID + "/allocations") 113 if err != nil { 114 return nil, nil, err 115 } 116 117 v := u.Query() 118 v.Add("all", strconv.FormatBool(allAllocs)) 119 u.RawQuery = v.Encode() 120 121 qm, err := j.client.query(u.String(), &resp, q) 122 if err != nil { 123 return nil, nil, err 124 } 125 sort.Sort(AllocIndexSort(resp)) 126 return resp, qm, nil 127 } 128 129 // Evaluations is used to query the evaluations associated with 130 // the given job ID. 131 func (j *Jobs) Evaluations(jobID string, q *QueryOptions) ([]*Evaluation, *QueryMeta, error) { 132 var resp []*Evaluation 133 qm, err := j.client.query("/v1/job/"+jobID+"/evaluations", &resp, q) 134 if err != nil { 135 return nil, nil, err 136 } 137 sort.Sort(EvalIndexSort(resp)) 138 return resp, qm, nil 139 } 140 141 // Deregister is used to remove an existing job. 142 func (j *Jobs) Deregister(jobID string, q *WriteOptions) (string, *WriteMeta, error) { 143 var resp deregisterJobResponse 144 wm, err := j.client.delete("/v1/job/"+jobID, &resp, q) 145 if err != nil { 146 return "", nil, err 147 } 148 return resp.EvalID, wm, nil 149 } 150 151 // ForceEvaluate is used to force-evaluate an existing job. 152 func (j *Jobs) ForceEvaluate(jobID string, q *WriteOptions) (string, *WriteMeta, error) { 153 var resp registerJobResponse 154 wm, err := j.client.write("/v1/job/"+jobID+"/evaluate", nil, &resp, q) 155 if err != nil { 156 return "", nil, err 157 } 158 return resp.EvalID, wm, nil 159 } 160 161 // PeriodicForce spawns a new instance of the periodic job and returns the eval ID 162 func (j *Jobs) PeriodicForce(jobID string, q *WriteOptions) (string, *WriteMeta, error) { 163 var resp periodicForceResponse 164 wm, err := j.client.write("/v1/job/"+jobID+"/periodic/force", nil, &resp, q) 165 if err != nil { 166 return "", nil, err 167 } 168 return resp.EvalID, wm, nil 169 } 170 171 func (j *Jobs) Plan(job *Job, diff bool, q *WriteOptions) (*JobPlanResponse, *WriteMeta, error) { 172 if job == nil { 173 return nil, nil, fmt.Errorf("must pass non-nil job") 174 } 175 176 var resp JobPlanResponse 177 req := &JobPlanRequest{ 178 Job: job, 179 Diff: diff, 180 } 181 wm, err := j.client.write("/v1/job/"+*job.ID+"/plan", req, &resp, q) 182 if err != nil { 183 return nil, nil, err 184 } 185 186 return &resp, wm, nil 187 } 188 189 func (j *Jobs) Summary(jobID string, q *QueryOptions) (*JobSummary, *QueryMeta, error) { 190 var resp JobSummary 191 qm, err := j.client.query("/v1/job/"+jobID+"/summary", &resp, q) 192 if err != nil { 193 return nil, nil, err 194 } 195 return &resp, qm, nil 196 } 197 198 func (j *Jobs) Dispatch(jobID string, meta map[string]string, 199 payload []byte, q *WriteOptions) (*JobDispatchResponse, *WriteMeta, error) { 200 var resp JobDispatchResponse 201 req := &JobDispatchRequest{ 202 JobID: jobID, 203 Meta: meta, 204 Payload: payload, 205 } 206 wm, err := j.client.write("/v1/job/"+jobID+"/dispatch", req, &resp, q) 207 if err != nil { 208 return nil, nil, err 209 } 210 return &resp, wm, nil 211 } 212 213 // periodicForceResponse is used to deserialize a force response 214 type periodicForceResponse struct { 215 EvalID string 216 } 217 218 // UpdateStrategy is for serializing update strategy for a job. 219 type UpdateStrategy struct { 220 Stagger time.Duration 221 MaxParallel int `mapstructure:"max_parallel"` 222 } 223 224 // PeriodicConfig is for serializing periodic config for a job. 225 type PeriodicConfig struct { 226 Enabled *bool 227 Spec *string 228 SpecType *string 229 ProhibitOverlap *bool `mapstructure:"prohibit_overlap"` 230 TimeZone *string `mapstructure:"time_zone"` 231 } 232 233 func (p *PeriodicConfig) Canonicalize() { 234 if p.Enabled == nil { 235 p.Enabled = helper.BoolToPtr(true) 236 } 237 if p.Spec == nil { 238 p.Spec = helper.StringToPtr("") 239 } 240 if p.SpecType == nil { 241 p.SpecType = helper.StringToPtr(PeriodicSpecCron) 242 } 243 if p.ProhibitOverlap == nil { 244 p.ProhibitOverlap = helper.BoolToPtr(false) 245 } 246 if p.TimeZone == nil || *p.TimeZone == "" { 247 p.TimeZone = helper.StringToPtr("UTC") 248 } 249 } 250 251 // Next returns the closest time instant matching the spec that is after the 252 // passed time. If no matching instance exists, the zero value of time.Time is 253 // returned. The `time.Location` of the returned value matches that of the 254 // passed time. 255 func (p *PeriodicConfig) Next(fromTime time.Time) time.Time { 256 if *p.SpecType == PeriodicSpecCron { 257 if e, err := cronexpr.Parse(*p.Spec); err == nil { 258 return e.Next(fromTime) 259 } 260 } 261 262 return time.Time{} 263 } 264 265 func (p *PeriodicConfig) GetLocation() (*time.Location, error) { 266 if p.TimeZone == nil || *p.TimeZone == "" { 267 return time.UTC, nil 268 } 269 270 return time.LoadLocation(*p.TimeZone) 271 } 272 273 // ParameterizedJobConfig is used to configure the parameterized job. 274 type ParameterizedJobConfig struct { 275 Payload string 276 MetaRequired []string `mapstructure:"meta_required"` 277 MetaOptional []string `mapstructure:"meta_optional"` 278 } 279 280 // Job is used to serialize a job. 281 type Job struct { 282 Region *string 283 ID *string 284 ParentID *string 285 Name *string 286 Type *string 287 Priority *int 288 AllAtOnce *bool `mapstructure:"all_at_once"` 289 Datacenters []string 290 Constraints []*Constraint 291 TaskGroups []*TaskGroup 292 Update *UpdateStrategy 293 Periodic *PeriodicConfig 294 ParameterizedJob *ParameterizedJobConfig 295 Payload []byte 296 Meta map[string]string 297 VaultToken *string `mapstructure:"vault_token"` 298 Status *string 299 StatusDescription *string 300 CreateIndex *uint64 301 ModifyIndex *uint64 302 JobModifyIndex *uint64 303 } 304 305 // IsPeriodic returns whether a job is periodic. 306 func (j *Job) IsPeriodic() bool { 307 return j.Periodic != nil 308 } 309 310 // IsParameterized returns whether a job is parameterized job. 311 func (j *Job) IsParameterized() bool { 312 return j.ParameterizedJob != nil 313 } 314 315 func (j *Job) Canonicalize() { 316 if j.ID == nil { 317 j.ID = helper.StringToPtr("") 318 } 319 if j.Name == nil { 320 j.Name = helper.StringToPtr(*j.ID) 321 } 322 if j.ParentID == nil { 323 j.ParentID = helper.StringToPtr("") 324 } 325 if j.Priority == nil { 326 j.Priority = helper.IntToPtr(50) 327 } 328 if j.Region == nil { 329 j.Region = helper.StringToPtr("global") 330 } 331 if j.Type == nil { 332 j.Type = helper.StringToPtr("service") 333 } 334 if j.AllAtOnce == nil { 335 j.AllAtOnce = helper.BoolToPtr(false) 336 } 337 if j.VaultToken == nil { 338 j.VaultToken = helper.StringToPtr("") 339 } 340 if j.Status == nil { 341 j.Status = helper.StringToPtr("") 342 } 343 if j.StatusDescription == nil { 344 j.StatusDescription = helper.StringToPtr("") 345 } 346 if j.CreateIndex == nil { 347 j.CreateIndex = helper.Uint64ToPtr(0) 348 } 349 if j.ModifyIndex == nil { 350 j.ModifyIndex = helper.Uint64ToPtr(0) 351 } 352 if j.JobModifyIndex == nil { 353 j.JobModifyIndex = helper.Uint64ToPtr(0) 354 } 355 if j.Periodic != nil { 356 j.Periodic.Canonicalize() 357 } 358 359 for _, tg := range j.TaskGroups { 360 tg.Canonicalize(j) 361 } 362 } 363 364 // JobSummary summarizes the state of the allocations of a job 365 type JobSummary struct { 366 JobID string 367 Summary map[string]TaskGroupSummary 368 Children *JobChildrenSummary 369 370 // Raft Indexes 371 CreateIndex uint64 372 ModifyIndex uint64 373 } 374 375 // JobChildrenSummary contains the summary of children job status 376 type JobChildrenSummary struct { 377 Pending int64 378 Running int64 379 Dead int64 380 } 381 382 func (jc *JobChildrenSummary) Sum() int { 383 if jc == nil { 384 return 0 385 } 386 387 return int(jc.Pending + jc.Running + jc.Dead) 388 } 389 390 // TaskGroup summarizes the state of all the allocations of a particular 391 // TaskGroup 392 type TaskGroupSummary struct { 393 Queued int 394 Complete int 395 Failed int 396 Running int 397 Starting int 398 Lost int 399 } 400 401 // JobListStub is used to return a subset of information about 402 // jobs during list operations. 403 type JobListStub struct { 404 ID string 405 ParentID string 406 Name string 407 Type string 408 Priority int 409 Status string 410 StatusDescription string 411 JobSummary *JobSummary 412 CreateIndex uint64 413 ModifyIndex uint64 414 JobModifyIndex uint64 415 } 416 417 // JobIDSort is used to sort jobs by their job ID's. 418 type JobIDSort []*JobListStub 419 420 func (j JobIDSort) Len() int { 421 return len(j) 422 } 423 424 func (j JobIDSort) Less(a, b int) bool { 425 return j[a].ID < j[b].ID 426 } 427 428 func (j JobIDSort) Swap(a, b int) { 429 j[a], j[b] = j[b], j[a] 430 } 431 432 // NewServiceJob creates and returns a new service-style job 433 // for long-lived processes using the provided name, ID, and 434 // relative job priority. 435 func NewServiceJob(id, name, region string, pri int) *Job { 436 return newJob(id, name, region, JobTypeService, pri) 437 } 438 439 // NewBatchJob creates and returns a new batch-style job for 440 // short-lived processes using the provided name and ID along 441 // with the relative job priority. 442 func NewBatchJob(id, name, region string, pri int) *Job { 443 return newJob(id, name, region, JobTypeBatch, pri) 444 } 445 446 // newJob is used to create a new Job struct. 447 func newJob(id, name, region, typ string, pri int) *Job { 448 return &Job{ 449 Region: ®ion, 450 ID: &id, 451 Name: &name, 452 Type: &typ, 453 Priority: &pri, 454 } 455 } 456 457 // SetMeta is used to set arbitrary k/v pairs of metadata on a job. 458 func (j *Job) SetMeta(key, val string) *Job { 459 if j.Meta == nil { 460 j.Meta = make(map[string]string) 461 } 462 j.Meta[key] = val 463 return j 464 } 465 466 // AddDatacenter is used to add a datacenter to a job. 467 func (j *Job) AddDatacenter(dc string) *Job { 468 j.Datacenters = append(j.Datacenters, dc) 469 return j 470 } 471 472 // Constrain is used to add a constraint to a job. 473 func (j *Job) Constrain(c *Constraint) *Job { 474 j.Constraints = append(j.Constraints, c) 475 return j 476 } 477 478 // AddTaskGroup adds a task group to an existing job. 479 func (j *Job) AddTaskGroup(grp *TaskGroup) *Job { 480 j.TaskGroups = append(j.TaskGroups, grp) 481 return j 482 } 483 484 // AddPeriodicConfig adds a periodic config to an existing job. 485 func (j *Job) AddPeriodicConfig(cfg *PeriodicConfig) *Job { 486 j.Periodic = cfg 487 return j 488 } 489 490 type WriteRequest struct { 491 // The target region for this write 492 Region string 493 } 494 495 // JobValidateRequest is used to validate a job 496 type JobValidateRequest struct { 497 Job *Job 498 WriteRequest 499 } 500 501 // JobValidateResponse is the response from validate request 502 type JobValidateResponse struct { 503 // DriverConfigValidated indicates whether the agent validated the driver 504 // config 505 DriverConfigValidated bool 506 507 // ValidationErrors is a list of validation errors 508 ValidationErrors []string 509 510 // Error is a string version of any error that may have occured 511 Error string 512 } 513 514 // JobUpdateRequest is used to update a job 515 type JobRegisterRequest struct { 516 Job *Job 517 // If EnforceIndex is set then the job will only be registered if the passed 518 // JobModifyIndex matches the current Jobs index. If the index is zero, the 519 // register only occurs if the job is new. 520 EnforceIndex bool 521 JobModifyIndex uint64 522 523 WriteRequest 524 } 525 526 // RegisterJobRequest is used to serialize a job registration 527 type RegisterJobRequest struct { 528 Job *Job 529 EnforceIndex bool `json:",omitempty"` 530 JobModifyIndex uint64 `json:",omitempty"` 531 } 532 533 // registerJobResponse is used to deserialize a job response 534 type registerJobResponse struct { 535 EvalID string 536 } 537 538 // deregisterJobResponse is used to decode a deregister response 539 type deregisterJobResponse struct { 540 EvalID string 541 } 542 543 type JobPlanRequest struct { 544 Job *Job 545 Diff bool 546 WriteRequest 547 } 548 549 type JobPlanResponse struct { 550 JobModifyIndex uint64 551 CreatedEvals []*Evaluation 552 Diff *JobDiff 553 Annotations *PlanAnnotations 554 FailedTGAllocs map[string]*AllocationMetric 555 NextPeriodicLaunch time.Time 556 } 557 558 type JobDiff struct { 559 Type string 560 ID string 561 Fields []*FieldDiff 562 Objects []*ObjectDiff 563 TaskGroups []*TaskGroupDiff 564 } 565 566 type TaskGroupDiff struct { 567 Type string 568 Name string 569 Fields []*FieldDiff 570 Objects []*ObjectDiff 571 Tasks []*TaskDiff 572 Updates map[string]uint64 573 } 574 575 type TaskDiff struct { 576 Type string 577 Name string 578 Fields []*FieldDiff 579 Objects []*ObjectDiff 580 Annotations []string 581 } 582 583 type FieldDiff struct { 584 Type string 585 Name string 586 Old, New string 587 Annotations []string 588 } 589 590 type ObjectDiff struct { 591 Type string 592 Name string 593 Fields []*FieldDiff 594 Objects []*ObjectDiff 595 } 596 597 type PlanAnnotations struct { 598 DesiredTGUpdates map[string]*DesiredUpdates 599 } 600 601 type DesiredUpdates struct { 602 Ignore uint64 603 Place uint64 604 Migrate uint64 605 Stop uint64 606 InPlaceUpdate uint64 607 DestructiveUpdate uint64 608 } 609 610 type JobDispatchRequest struct { 611 JobID string 612 Payload []byte 613 Meta map[string]string 614 } 615 616 type JobDispatchResponse struct { 617 DispatchedJobID string 618 EvalID string 619 EvalCreateIndex uint64 620 JobCreateIndex uint64 621 WriteMeta 622 }