github.com/diptanu/nomad@v0.5.7-0.20170516172507-d72e86cbe3d9/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 JobRegisterResponse 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 JobRegisterResponse 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 // Versions is used to retrieve all versions of a particular 110 // job given its unique ID. 111 func (j *Jobs) Versions(jobID string, q *QueryOptions) ([]*Job, *QueryMeta, error) { 112 var resp []*Job 113 qm, err := j.client.query("/v1/job/"+jobID+"/versions", &resp, q) 114 if err != nil { 115 return nil, nil, err 116 } 117 return resp, qm, nil 118 } 119 120 // Allocations is used to return the allocs for a given job ID. 121 func (j *Jobs) Allocations(jobID string, allAllocs bool, q *QueryOptions) ([]*AllocationListStub, *QueryMeta, error) { 122 var resp []*AllocationListStub 123 u, err := url.Parse("/v1/job/" + jobID + "/allocations") 124 if err != nil { 125 return nil, nil, err 126 } 127 128 v := u.Query() 129 v.Add("all", strconv.FormatBool(allAllocs)) 130 u.RawQuery = v.Encode() 131 132 qm, err := j.client.query(u.String(), &resp, q) 133 if err != nil { 134 return nil, nil, err 135 } 136 sort.Sort(AllocIndexSort(resp)) 137 return resp, qm, nil 138 } 139 140 // Evaluations is used to query the evaluations associated with 141 // the given job ID. 142 func (j *Jobs) Evaluations(jobID string, q *QueryOptions) ([]*Evaluation, *QueryMeta, error) { 143 var resp []*Evaluation 144 qm, err := j.client.query("/v1/job/"+jobID+"/evaluations", &resp, q) 145 if err != nil { 146 return nil, nil, err 147 } 148 sort.Sort(EvalIndexSort(resp)) 149 return resp, qm, nil 150 } 151 152 // Deregister is used to remove an existing job. If purge is set to true, the job 153 // is deregistered and purged from the system versus still being queryable and 154 // eventually GC'ed from the system. Most callers should not specify purge. 155 func (j *Jobs) Deregister(jobID string, purge bool, q *WriteOptions) (string, *WriteMeta, error) { 156 var resp JobDeregisterResponse 157 wm, err := j.client.delete(fmt.Sprintf("/v1/job/%v?purge=%t", jobID, purge), &resp, q) 158 if err != nil { 159 return "", nil, err 160 } 161 return resp.EvalID, wm, nil 162 } 163 164 // ForceEvaluate is used to force-evaluate an existing job. 165 func (j *Jobs) ForceEvaluate(jobID string, q *WriteOptions) (string, *WriteMeta, error) { 166 var resp JobRegisterResponse 167 wm, err := j.client.write("/v1/job/"+jobID+"/evaluate", nil, &resp, q) 168 if err != nil { 169 return "", nil, err 170 } 171 return resp.EvalID, wm, nil 172 } 173 174 // PeriodicForce spawns a new instance of the periodic job and returns the eval ID 175 func (j *Jobs) PeriodicForce(jobID string, q *WriteOptions) (string, *WriteMeta, error) { 176 var resp periodicForceResponse 177 wm, err := j.client.write("/v1/job/"+jobID+"/periodic/force", nil, &resp, q) 178 if err != nil { 179 return "", nil, err 180 } 181 return resp.EvalID, wm, nil 182 } 183 184 func (j *Jobs) Plan(job *Job, diff bool, q *WriteOptions) (*JobPlanResponse, *WriteMeta, error) { 185 if job == nil { 186 return nil, nil, fmt.Errorf("must pass non-nil job") 187 } 188 189 var resp JobPlanResponse 190 req := &JobPlanRequest{ 191 Job: job, 192 Diff: diff, 193 } 194 wm, err := j.client.write("/v1/job/"+*job.ID+"/plan", req, &resp, q) 195 if err != nil { 196 return nil, nil, err 197 } 198 199 return &resp, wm, nil 200 } 201 202 func (j *Jobs) Summary(jobID string, q *QueryOptions) (*JobSummary, *QueryMeta, error) { 203 var resp JobSummary 204 qm, err := j.client.query("/v1/job/"+jobID+"/summary", &resp, q) 205 if err != nil { 206 return nil, nil, err 207 } 208 return &resp, qm, nil 209 } 210 211 func (j *Jobs) Dispatch(jobID string, meta map[string]string, 212 payload []byte, q *WriteOptions) (*JobDispatchResponse, *WriteMeta, error) { 213 var resp JobDispatchResponse 214 req := &JobDispatchRequest{ 215 JobID: jobID, 216 Meta: meta, 217 Payload: payload, 218 } 219 wm, err := j.client.write("/v1/job/"+jobID+"/dispatch", req, &resp, q) 220 if err != nil { 221 return nil, nil, err 222 } 223 return &resp, wm, nil 224 } 225 226 // Revert is used to revert the given job to the passed version. If 227 // enforceVersion is set, the job is only reverted if the current version is at 228 // the passed version. 229 func (j *Jobs) Revert(jobID string, version uint64, enforcePriorVersion *uint64, 230 q *WriteOptions) (*JobRegisterResponse, *WriteMeta, error) { 231 232 var resp JobRegisterResponse 233 req := &JobRevertRequest{ 234 JobID: jobID, 235 JobVersion: version, 236 EnforcePriorVersion: enforcePriorVersion, 237 } 238 wm, err := j.client.write("/v1/job/"+jobID+"/revert", req, &resp, q) 239 if err != nil { 240 return nil, nil, err 241 } 242 return &resp, wm, nil 243 } 244 245 // periodicForceResponse is used to deserialize a force response 246 type periodicForceResponse struct { 247 EvalID string 248 } 249 250 // UpdateStrategy is for serializing update strategy for a job. 251 type UpdateStrategy struct { 252 Stagger time.Duration 253 MaxParallel int `mapstructure:"max_parallel"` 254 } 255 256 // PeriodicConfig is for serializing periodic config for a job. 257 type PeriodicConfig struct { 258 Enabled *bool 259 Spec *string 260 SpecType *string 261 ProhibitOverlap *bool `mapstructure:"prohibit_overlap"` 262 TimeZone *string `mapstructure:"time_zone"` 263 } 264 265 func (p *PeriodicConfig) Canonicalize() { 266 if p.Enabled == nil { 267 p.Enabled = helper.BoolToPtr(true) 268 } 269 if p.Spec == nil { 270 p.Spec = helper.StringToPtr("") 271 } 272 if p.SpecType == nil { 273 p.SpecType = helper.StringToPtr(PeriodicSpecCron) 274 } 275 if p.ProhibitOverlap == nil { 276 p.ProhibitOverlap = helper.BoolToPtr(false) 277 } 278 if p.TimeZone == nil || *p.TimeZone == "" { 279 p.TimeZone = helper.StringToPtr("UTC") 280 } 281 } 282 283 // Next returns the closest time instant matching the spec that is after the 284 // passed time. If no matching instance exists, the zero value of time.Time is 285 // returned. The `time.Location` of the returned value matches that of the 286 // passed time. 287 func (p *PeriodicConfig) Next(fromTime time.Time) time.Time { 288 if *p.SpecType == PeriodicSpecCron { 289 if e, err := cronexpr.Parse(*p.Spec); err == nil { 290 return e.Next(fromTime) 291 } 292 } 293 294 return time.Time{} 295 } 296 297 func (p *PeriodicConfig) GetLocation() (*time.Location, error) { 298 if p.TimeZone == nil || *p.TimeZone == "" { 299 return time.UTC, nil 300 } 301 302 return time.LoadLocation(*p.TimeZone) 303 } 304 305 // ParameterizedJobConfig is used to configure the parameterized job. 306 type ParameterizedJobConfig struct { 307 Payload string 308 MetaRequired []string `mapstructure:"meta_required"` 309 MetaOptional []string `mapstructure:"meta_optional"` 310 } 311 312 // Job is used to serialize a job. 313 type Job struct { 314 Stop *bool 315 Region *string 316 ID *string 317 ParentID *string 318 Name *string 319 Type *string 320 Priority *int 321 AllAtOnce *bool `mapstructure:"all_at_once"` 322 Datacenters []string 323 Constraints []*Constraint 324 TaskGroups []*TaskGroup 325 Update *UpdateStrategy 326 Periodic *PeriodicConfig 327 ParameterizedJob *ParameterizedJobConfig 328 Payload []byte 329 Meta map[string]string 330 VaultToken *string `mapstructure:"vault_token"` 331 Status *string 332 StatusDescription *string 333 Stable *bool 334 Version *uint64 335 CreateIndex *uint64 336 ModifyIndex *uint64 337 JobModifyIndex *uint64 338 } 339 340 // IsPeriodic returns whether a job is periodic. 341 func (j *Job) IsPeriodic() bool { 342 return j.Periodic != nil 343 } 344 345 // IsParameterized returns whether a job is parameterized job. 346 func (j *Job) IsParameterized() bool { 347 return j.ParameterizedJob != nil 348 } 349 350 func (j *Job) Canonicalize() { 351 if j.ID == nil { 352 j.ID = helper.StringToPtr("") 353 } 354 if j.Name == nil { 355 j.Name = helper.StringToPtr(*j.ID) 356 } 357 if j.ParentID == nil { 358 j.ParentID = helper.StringToPtr("") 359 } 360 if j.Priority == nil { 361 j.Priority = helper.IntToPtr(50) 362 } 363 if j.Stop == nil { 364 j.Stop = helper.BoolToPtr(false) 365 } 366 if j.Region == nil { 367 j.Region = helper.StringToPtr("global") 368 } 369 if j.Type == nil { 370 j.Type = helper.StringToPtr("service") 371 } 372 if j.AllAtOnce == nil { 373 j.AllAtOnce = helper.BoolToPtr(false) 374 } 375 if j.VaultToken == nil { 376 j.VaultToken = helper.StringToPtr("") 377 } 378 if j.Status == nil { 379 j.Status = helper.StringToPtr("") 380 } 381 if j.StatusDescription == nil { 382 j.StatusDescription = helper.StringToPtr("") 383 } 384 if j.Stable == nil { 385 j.Stable = helper.BoolToPtr(false) 386 } 387 if j.Version == nil { 388 j.Version = helper.Uint64ToPtr(0) 389 } 390 if j.CreateIndex == nil { 391 j.CreateIndex = helper.Uint64ToPtr(0) 392 } 393 if j.ModifyIndex == nil { 394 j.ModifyIndex = helper.Uint64ToPtr(0) 395 } 396 if j.JobModifyIndex == nil { 397 j.JobModifyIndex = helper.Uint64ToPtr(0) 398 } 399 if j.Periodic != nil { 400 j.Periodic.Canonicalize() 401 } 402 403 for _, tg := range j.TaskGroups { 404 tg.Canonicalize(j) 405 } 406 } 407 408 // JobSummary summarizes the state of the allocations of a job 409 type JobSummary struct { 410 JobID string 411 Summary map[string]TaskGroupSummary 412 Children *JobChildrenSummary 413 414 // Raft Indexes 415 CreateIndex uint64 416 ModifyIndex uint64 417 } 418 419 // JobChildrenSummary contains the summary of children job status 420 type JobChildrenSummary struct { 421 Pending int64 422 Running int64 423 Dead int64 424 } 425 426 func (jc *JobChildrenSummary) Sum() int { 427 if jc == nil { 428 return 0 429 } 430 431 return int(jc.Pending + jc.Running + jc.Dead) 432 } 433 434 // TaskGroup summarizes the state of all the allocations of a particular 435 // TaskGroup 436 type TaskGroupSummary struct { 437 Queued int 438 Complete int 439 Failed int 440 Running int 441 Starting int 442 Lost int 443 } 444 445 // JobListStub is used to return a subset of information about 446 // jobs during list operations. 447 type JobListStub struct { 448 ID string 449 ParentID string 450 Name string 451 Type string 452 Priority int 453 Periodic bool 454 ParameterizedJob bool 455 Stop bool 456 Status string 457 StatusDescription string 458 JobSummary *JobSummary 459 CreateIndex uint64 460 ModifyIndex uint64 461 JobModifyIndex uint64 462 } 463 464 // JobIDSort is used to sort jobs by their job ID's. 465 type JobIDSort []*JobListStub 466 467 func (j JobIDSort) Len() int { 468 return len(j) 469 } 470 471 func (j JobIDSort) Less(a, b int) bool { 472 return j[a].ID < j[b].ID 473 } 474 475 func (j JobIDSort) Swap(a, b int) { 476 j[a], j[b] = j[b], j[a] 477 } 478 479 // NewServiceJob creates and returns a new service-style job 480 // for long-lived processes using the provided name, ID, and 481 // relative job priority. 482 func NewServiceJob(id, name, region string, pri int) *Job { 483 return newJob(id, name, region, JobTypeService, pri) 484 } 485 486 // NewBatchJob creates and returns a new batch-style job for 487 // short-lived processes using the provided name and ID along 488 // with the relative job priority. 489 func NewBatchJob(id, name, region string, pri int) *Job { 490 return newJob(id, name, region, JobTypeBatch, pri) 491 } 492 493 // newJob is used to create a new Job struct. 494 func newJob(id, name, region, typ string, pri int) *Job { 495 return &Job{ 496 Region: ®ion, 497 ID: &id, 498 Name: &name, 499 Type: &typ, 500 Priority: &pri, 501 } 502 } 503 504 // SetMeta is used to set arbitrary k/v pairs of metadata on a job. 505 func (j *Job) SetMeta(key, val string) *Job { 506 if j.Meta == nil { 507 j.Meta = make(map[string]string) 508 } 509 j.Meta[key] = val 510 return j 511 } 512 513 // AddDatacenter is used to add a datacenter to a job. 514 func (j *Job) AddDatacenter(dc string) *Job { 515 j.Datacenters = append(j.Datacenters, dc) 516 return j 517 } 518 519 // Constrain is used to add a constraint to a job. 520 func (j *Job) Constrain(c *Constraint) *Job { 521 j.Constraints = append(j.Constraints, c) 522 return j 523 } 524 525 // AddTaskGroup adds a task group to an existing job. 526 func (j *Job) AddTaskGroup(grp *TaskGroup) *Job { 527 j.TaskGroups = append(j.TaskGroups, grp) 528 return j 529 } 530 531 // AddPeriodicConfig adds a periodic config to an existing job. 532 func (j *Job) AddPeriodicConfig(cfg *PeriodicConfig) *Job { 533 j.Periodic = cfg 534 return j 535 } 536 537 type WriteRequest struct { 538 // The target region for this write 539 Region string 540 } 541 542 // JobValidateRequest is used to validate a job 543 type JobValidateRequest struct { 544 Job *Job 545 WriteRequest 546 } 547 548 // JobValidateResponse is the response from validate request 549 type JobValidateResponse struct { 550 // DriverConfigValidated indicates whether the agent validated the driver 551 // config 552 DriverConfigValidated bool 553 554 // ValidationErrors is a list of validation errors 555 ValidationErrors []string 556 557 // Error is a string version of any error that may have occured 558 Error string 559 } 560 561 // JobRevertRequest is used to revert a job to a prior version. 562 type JobRevertRequest struct { 563 // JobID is the ID of the job being reverted 564 JobID string 565 566 // JobVersion the version to revert to. 567 JobVersion uint64 568 569 // EnforcePriorVersion if set will enforce that the job is at the given 570 // version before reverting. 571 EnforcePriorVersion *uint64 572 573 WriteRequest 574 } 575 576 // JobUpdateRequest is used to update a job 577 type JobRegisterRequest struct { 578 Job *Job 579 // If EnforceIndex is set then the job will only be registered if the passed 580 // JobModifyIndex matches the current Jobs index. If the index is zero, the 581 // register only occurs if the job is new. 582 EnforceIndex bool 583 JobModifyIndex uint64 584 585 WriteRequest 586 } 587 588 // RegisterJobRequest is used to serialize a job registration 589 type RegisterJobRequest struct { 590 Job *Job 591 EnforceIndex bool `json:",omitempty"` 592 JobModifyIndex uint64 `json:",omitempty"` 593 } 594 595 // JobRegisterResponse is used to respond to a job registration 596 type JobRegisterResponse struct { 597 EvalID string 598 EvalCreateIndex uint64 599 JobModifyIndex uint64 600 QueryMeta 601 } 602 603 // JobDeregisterResponse is used to respond to a job deregistration 604 type JobDeregisterResponse struct { 605 EvalID string 606 EvalCreateIndex uint64 607 JobModifyIndex uint64 608 QueryMeta 609 } 610 611 type JobPlanRequest struct { 612 Job *Job 613 Diff bool 614 WriteRequest 615 } 616 617 type JobPlanResponse struct { 618 JobModifyIndex uint64 619 CreatedEvals []*Evaluation 620 Diff *JobDiff 621 Annotations *PlanAnnotations 622 FailedTGAllocs map[string]*AllocationMetric 623 NextPeriodicLaunch time.Time 624 } 625 626 type JobDiff struct { 627 Type string 628 ID string 629 Fields []*FieldDiff 630 Objects []*ObjectDiff 631 TaskGroups []*TaskGroupDiff 632 } 633 634 type TaskGroupDiff struct { 635 Type string 636 Name string 637 Fields []*FieldDiff 638 Objects []*ObjectDiff 639 Tasks []*TaskDiff 640 Updates map[string]uint64 641 } 642 643 type TaskDiff struct { 644 Type string 645 Name string 646 Fields []*FieldDiff 647 Objects []*ObjectDiff 648 Annotations []string 649 } 650 651 type FieldDiff struct { 652 Type string 653 Name string 654 Old, New string 655 Annotations []string 656 } 657 658 type ObjectDiff struct { 659 Type string 660 Name string 661 Fields []*FieldDiff 662 Objects []*ObjectDiff 663 } 664 665 type PlanAnnotations struct { 666 DesiredTGUpdates map[string]*DesiredUpdates 667 } 668 669 type DesiredUpdates struct { 670 Ignore uint64 671 Place uint64 672 Migrate uint64 673 Stop uint64 674 InPlaceUpdate uint64 675 DestructiveUpdate uint64 676 } 677 678 type JobDispatchRequest struct { 679 JobID string 680 Payload []byte 681 Meta map[string]string 682 } 683 684 type JobDispatchResponse struct { 685 DispatchedJobID string 686 EvalID string 687 EvalCreateIndex uint64 688 JobCreateIndex uint64 689 WriteMeta 690 }