github.com/zoomfoo/nomad@v0.8.5-0.20180907175415-f28fd3a1a056/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 "github.com/hashicorp/nomad/nomad/structs" 13 ) 14 15 const ( 16 // JobTypeService indicates a long-running processes 17 JobTypeService = "service" 18 19 // JobTypeBatch indicates a short-lived process 20 JobTypeBatch = "batch" 21 22 // PeriodicSpecCron is used for a cron spec. 23 PeriodicSpecCron = "cron" 24 25 // DefaultNamespace is the default namespace. 26 DefaultNamespace = "default" 27 ) 28 29 const ( 30 // RegisterEnforceIndexErrPrefix is the prefix to use in errors caused by 31 // enforcing the job modify index during registers. 32 RegisterEnforceIndexErrPrefix = "Enforcing job modify index" 33 ) 34 35 // Jobs is used to access the job-specific endpoints. 36 type Jobs struct { 37 client *Client 38 } 39 40 // JobsParseRequest is used for arguments of the /vi/jobs/parse endpoint 41 type JobsParseRequest struct { 42 // JobHCL is an hcl jobspec 43 JobHCL string 44 45 // Canonicalize is a flag as to if the server should return default values 46 // for unset fields 47 Canonicalize bool 48 } 49 50 // Jobs returns a handle on the jobs endpoints. 51 func (c *Client) Jobs() *Jobs { 52 return &Jobs{client: c} 53 } 54 55 // Parse is used to convert the HCL repesentation of a Job to JSON server side. 56 // To parse the HCL client side see package github.com/hashicorp/nomad/jobspec 57 func (j *Jobs) ParseHCL(jobHCL string, canonicalize bool) (*Job, error) { 58 var job Job 59 req := &JobsParseRequest{ 60 JobHCL: jobHCL, 61 Canonicalize: canonicalize, 62 } 63 _, err := j.client.write("/v1/jobs/parse", req, &job, nil) 64 return &job, err 65 } 66 67 func (j *Jobs) Validate(job *Job, q *WriteOptions) (*JobValidateResponse, *WriteMeta, error) { 68 var resp JobValidateResponse 69 req := &JobValidateRequest{Job: job} 70 if q != nil { 71 req.WriteRequest = WriteRequest{Region: q.Region} 72 } 73 wm, err := j.client.write("/v1/validate/job", req, &resp, q) 74 return &resp, wm, err 75 } 76 77 // RegisterOptions is used to pass through job registration parameters 78 type RegisterOptions struct { 79 EnforceIndex bool 80 ModifyIndex uint64 81 PolicyOverride bool 82 } 83 84 // Register is used to register a new job. It returns the ID 85 // of the evaluation, along with any errors encountered. 86 func (j *Jobs) Register(job *Job, q *WriteOptions) (*JobRegisterResponse, *WriteMeta, error) { 87 return j.RegisterOpts(job, nil, q) 88 } 89 90 // EnforceRegister is used to register a job enforcing its job modify index. 91 func (j *Jobs) EnforceRegister(job *Job, modifyIndex uint64, q *WriteOptions) (*JobRegisterResponse, *WriteMeta, error) { 92 opts := RegisterOptions{EnforceIndex: true, ModifyIndex: modifyIndex} 93 return j.RegisterOpts(job, &opts, q) 94 } 95 96 // Register is used to register a new job. It returns the ID 97 // of the evaluation, along with any errors encountered. 98 func (j *Jobs) RegisterOpts(job *Job, opts *RegisterOptions, q *WriteOptions) (*JobRegisterResponse, *WriteMeta, error) { 99 // Format the request 100 req := &RegisterJobRequest{ 101 Job: job, 102 } 103 if opts != nil { 104 if opts.EnforceIndex { 105 req.EnforceIndex = true 106 req.JobModifyIndex = opts.ModifyIndex 107 } 108 if opts.PolicyOverride { 109 req.PolicyOverride = true 110 } 111 } 112 113 var resp JobRegisterResponse 114 wm, err := j.client.write("/v1/jobs", req, &resp, q) 115 if err != nil { 116 return nil, nil, err 117 } 118 return &resp, wm, nil 119 } 120 121 // List is used to list all of the existing jobs. 122 func (j *Jobs) List(q *QueryOptions) ([]*JobListStub, *QueryMeta, error) { 123 var resp []*JobListStub 124 qm, err := j.client.query("/v1/jobs", &resp, q) 125 if err != nil { 126 return nil, qm, err 127 } 128 sort.Sort(JobIDSort(resp)) 129 return resp, qm, nil 130 } 131 132 // PrefixList is used to list all existing jobs that match the prefix. 133 func (j *Jobs) PrefixList(prefix string) ([]*JobListStub, *QueryMeta, error) { 134 return j.List(&QueryOptions{Prefix: prefix}) 135 } 136 137 // Info is used to retrieve information about a particular 138 // job given its unique ID. 139 func (j *Jobs) Info(jobID string, q *QueryOptions) (*Job, *QueryMeta, error) { 140 var resp Job 141 qm, err := j.client.query("/v1/job/"+jobID, &resp, q) 142 if err != nil { 143 return nil, nil, err 144 } 145 return &resp, qm, nil 146 } 147 148 // Versions is used to retrieve all versions of a particular job given its 149 // unique ID. 150 func (j *Jobs) Versions(jobID string, diffs bool, q *QueryOptions) ([]*Job, []*JobDiff, *QueryMeta, error) { 151 var resp JobVersionsResponse 152 qm, err := j.client.query(fmt.Sprintf("/v1/job/%s/versions?diffs=%v", jobID, diffs), &resp, q) 153 if err != nil { 154 return nil, nil, nil, err 155 } 156 return resp.Versions, resp.Diffs, qm, nil 157 } 158 159 // Allocations is used to return the allocs for a given job ID. 160 func (j *Jobs) Allocations(jobID string, allAllocs bool, q *QueryOptions) ([]*AllocationListStub, *QueryMeta, error) { 161 var resp []*AllocationListStub 162 u, err := url.Parse("/v1/job/" + jobID + "/allocations") 163 if err != nil { 164 return nil, nil, err 165 } 166 167 v := u.Query() 168 v.Add("all", strconv.FormatBool(allAllocs)) 169 u.RawQuery = v.Encode() 170 171 qm, err := j.client.query(u.String(), &resp, q) 172 if err != nil { 173 return nil, nil, err 174 } 175 sort.Sort(AllocIndexSort(resp)) 176 return resp, qm, nil 177 } 178 179 // Deployments is used to query the deployments associated with the given job 180 // ID. 181 func (j *Jobs) Deployments(jobID string, q *QueryOptions) ([]*Deployment, *QueryMeta, error) { 182 var resp []*Deployment 183 qm, err := j.client.query("/v1/job/"+jobID+"/deployments", &resp, q) 184 if err != nil { 185 return nil, nil, err 186 } 187 sort.Sort(DeploymentIndexSort(resp)) 188 return resp, qm, nil 189 } 190 191 // LatestDeployment is used to query for the latest deployment associated with 192 // the given job ID. 193 func (j *Jobs) LatestDeployment(jobID string, q *QueryOptions) (*Deployment, *QueryMeta, error) { 194 var resp *Deployment 195 qm, err := j.client.query("/v1/job/"+jobID+"/deployment", &resp, q) 196 if err != nil { 197 return nil, nil, err 198 } 199 return resp, qm, nil 200 } 201 202 // Evaluations is used to query the evaluations associated with the given job 203 // ID. 204 func (j *Jobs) Evaluations(jobID string, q *QueryOptions) ([]*Evaluation, *QueryMeta, error) { 205 var resp []*Evaluation 206 qm, err := j.client.query("/v1/job/"+jobID+"/evaluations", &resp, q) 207 if err != nil { 208 return nil, nil, err 209 } 210 sort.Sort(EvalIndexSort(resp)) 211 return resp, qm, nil 212 } 213 214 // Deregister is used to remove an existing job. If purge is set to true, the job 215 // is deregistered and purged from the system versus still being queryable and 216 // eventually GC'ed from the system. Most callers should not specify purge. 217 func (j *Jobs) Deregister(jobID string, purge bool, q *WriteOptions) (string, *WriteMeta, error) { 218 var resp JobDeregisterResponse 219 wm, err := j.client.delete(fmt.Sprintf("/v1/job/%v?purge=%t", jobID, purge), &resp, q) 220 if err != nil { 221 return "", nil, err 222 } 223 return resp.EvalID, wm, nil 224 } 225 226 // ForceEvaluate is used to force-evaluate an existing job. 227 func (j *Jobs) ForceEvaluate(jobID string, q *WriteOptions) (string, *WriteMeta, error) { 228 var resp JobRegisterResponse 229 wm, err := j.client.write("/v1/job/"+jobID+"/evaluate", nil, &resp, q) 230 if err != nil { 231 return "", nil, err 232 } 233 return resp.EvalID, wm, nil 234 } 235 236 // EvaluateWithOpts is used to force-evaluate an existing job and takes additional options 237 // for whether to force reschedule failed allocations 238 func (j *Jobs) EvaluateWithOpts(jobID string, opts EvalOptions, q *WriteOptions) (string, *WriteMeta, error) { 239 req := &JobEvaluateRequest{ 240 JobID: jobID, 241 EvalOptions: opts, 242 } 243 244 var resp JobRegisterResponse 245 wm, err := j.client.write("/v1/job/"+jobID+"/evaluate", req, &resp, q) 246 if err != nil { 247 return "", nil, err 248 } 249 return resp.EvalID, wm, nil 250 } 251 252 // PeriodicForce spawns a new instance of the periodic job and returns the eval ID 253 func (j *Jobs) PeriodicForce(jobID string, q *WriteOptions) (string, *WriteMeta, error) { 254 var resp periodicForceResponse 255 wm, err := j.client.write("/v1/job/"+jobID+"/periodic/force", nil, &resp, q) 256 if err != nil { 257 return "", nil, err 258 } 259 return resp.EvalID, wm, nil 260 } 261 262 // PlanOptions is used to pass through job planning parameters 263 type PlanOptions struct { 264 Diff bool 265 PolicyOverride bool 266 } 267 268 func (j *Jobs) Plan(job *Job, diff bool, q *WriteOptions) (*JobPlanResponse, *WriteMeta, error) { 269 opts := PlanOptions{Diff: diff} 270 return j.PlanOpts(job, &opts, q) 271 } 272 273 func (j *Jobs) PlanOpts(job *Job, opts *PlanOptions, q *WriteOptions) (*JobPlanResponse, *WriteMeta, error) { 274 if job == nil { 275 return nil, nil, fmt.Errorf("must pass non-nil job") 276 } 277 278 // Setup the request 279 req := &JobPlanRequest{ 280 Job: job, 281 } 282 if opts != nil { 283 req.Diff = opts.Diff 284 req.PolicyOverride = opts.PolicyOverride 285 } 286 287 var resp JobPlanResponse 288 wm, err := j.client.write("/v1/job/"+*job.ID+"/plan", req, &resp, q) 289 if err != nil { 290 return nil, nil, err 291 } 292 return &resp, wm, nil 293 } 294 295 func (j *Jobs) Summary(jobID string, q *QueryOptions) (*JobSummary, *QueryMeta, error) { 296 var resp JobSummary 297 qm, err := j.client.query("/v1/job/"+jobID+"/summary", &resp, q) 298 if err != nil { 299 return nil, nil, err 300 } 301 return &resp, qm, nil 302 } 303 304 func (j *Jobs) Dispatch(jobID string, meta map[string]string, 305 payload []byte, q *WriteOptions) (*JobDispatchResponse, *WriteMeta, error) { 306 var resp JobDispatchResponse 307 req := &JobDispatchRequest{ 308 JobID: jobID, 309 Meta: meta, 310 Payload: payload, 311 } 312 wm, err := j.client.write("/v1/job/"+jobID+"/dispatch", req, &resp, q) 313 if err != nil { 314 return nil, nil, err 315 } 316 return &resp, wm, nil 317 } 318 319 // Revert is used to revert the given job to the passed version. If 320 // enforceVersion is set, the job is only reverted if the current version is at 321 // the passed version. 322 func (j *Jobs) Revert(jobID string, version uint64, enforcePriorVersion *uint64, 323 q *WriteOptions) (*JobRegisterResponse, *WriteMeta, error) { 324 325 var resp JobRegisterResponse 326 req := &JobRevertRequest{ 327 JobID: jobID, 328 JobVersion: version, 329 EnforcePriorVersion: enforcePriorVersion, 330 } 331 wm, err := j.client.write("/v1/job/"+jobID+"/revert", req, &resp, q) 332 if err != nil { 333 return nil, nil, err 334 } 335 return &resp, wm, nil 336 } 337 338 // Stable is used to mark a job version's stability. 339 func (j *Jobs) Stable(jobID string, version uint64, stable bool, 340 q *WriteOptions) (*JobStabilityResponse, *WriteMeta, error) { 341 342 var resp JobStabilityResponse 343 req := &JobStabilityRequest{ 344 JobID: jobID, 345 JobVersion: version, 346 Stable: stable, 347 } 348 wm, err := j.client.write("/v1/job/"+jobID+"/stable", req, &resp, q) 349 if err != nil { 350 return nil, nil, err 351 } 352 return &resp, wm, nil 353 } 354 355 // periodicForceResponse is used to deserialize a force response 356 type periodicForceResponse struct { 357 EvalID string 358 } 359 360 // UpdateStrategy defines a task groups update strategy. 361 type UpdateStrategy struct { 362 Stagger *time.Duration `mapstructure:"stagger"` 363 MaxParallel *int `mapstructure:"max_parallel"` 364 HealthCheck *string `mapstructure:"health_check"` 365 MinHealthyTime *time.Duration `mapstructure:"min_healthy_time"` 366 HealthyDeadline *time.Duration `mapstructure:"healthy_deadline"` 367 ProgressDeadline *time.Duration `mapstructure:"progress_deadline"` 368 AutoRevert *bool `mapstructure:"auto_revert"` 369 Canary *int `mapstructure:"canary"` 370 } 371 372 // DefaultUpdateStrategy provides a baseline that can be used to upgrade 373 // jobs with the old policy or for populating field defaults. 374 func DefaultUpdateStrategy() *UpdateStrategy { 375 return &UpdateStrategy{ 376 Stagger: helper.TimeToPtr(30 * time.Second), 377 MaxParallel: helper.IntToPtr(1), 378 HealthCheck: helper.StringToPtr("checks"), 379 MinHealthyTime: helper.TimeToPtr(10 * time.Second), 380 HealthyDeadline: helper.TimeToPtr(5 * time.Minute), 381 ProgressDeadline: helper.TimeToPtr(10 * time.Minute), 382 AutoRevert: helper.BoolToPtr(false), 383 Canary: helper.IntToPtr(0), 384 } 385 } 386 387 func (u *UpdateStrategy) Copy() *UpdateStrategy { 388 if u == nil { 389 return nil 390 } 391 392 copy := new(UpdateStrategy) 393 394 if u.Stagger != nil { 395 copy.Stagger = helper.TimeToPtr(*u.Stagger) 396 } 397 398 if u.MaxParallel != nil { 399 copy.MaxParallel = helper.IntToPtr(*u.MaxParallel) 400 } 401 402 if u.HealthCheck != nil { 403 copy.HealthCheck = helper.StringToPtr(*u.HealthCheck) 404 } 405 406 if u.MinHealthyTime != nil { 407 copy.MinHealthyTime = helper.TimeToPtr(*u.MinHealthyTime) 408 } 409 410 if u.HealthyDeadline != nil { 411 copy.HealthyDeadline = helper.TimeToPtr(*u.HealthyDeadline) 412 } 413 414 if u.ProgressDeadline != nil { 415 copy.ProgressDeadline = helper.TimeToPtr(*u.ProgressDeadline) 416 } 417 418 if u.AutoRevert != nil { 419 copy.AutoRevert = helper.BoolToPtr(*u.AutoRevert) 420 } 421 422 if u.Canary != nil { 423 copy.Canary = helper.IntToPtr(*u.Canary) 424 } 425 426 return copy 427 } 428 429 func (u *UpdateStrategy) Merge(o *UpdateStrategy) { 430 if o == nil { 431 return 432 } 433 434 if o.Stagger != nil { 435 u.Stagger = helper.TimeToPtr(*o.Stagger) 436 } 437 438 if o.MaxParallel != nil { 439 u.MaxParallel = helper.IntToPtr(*o.MaxParallel) 440 } 441 442 if o.HealthCheck != nil { 443 u.HealthCheck = helper.StringToPtr(*o.HealthCheck) 444 } 445 446 if o.MinHealthyTime != nil { 447 u.MinHealthyTime = helper.TimeToPtr(*o.MinHealthyTime) 448 } 449 450 if o.HealthyDeadline != nil { 451 u.HealthyDeadline = helper.TimeToPtr(*o.HealthyDeadline) 452 } 453 454 if o.ProgressDeadline != nil { 455 u.ProgressDeadline = helper.TimeToPtr(*o.ProgressDeadline) 456 } 457 458 if o.AutoRevert != nil { 459 u.AutoRevert = helper.BoolToPtr(*o.AutoRevert) 460 } 461 462 if o.Canary != nil { 463 u.Canary = helper.IntToPtr(*o.Canary) 464 } 465 } 466 467 func (u *UpdateStrategy) Canonicalize() { 468 d := DefaultUpdateStrategy() 469 470 if u.MaxParallel == nil { 471 u.MaxParallel = d.MaxParallel 472 } 473 474 if u.Stagger == nil { 475 u.Stagger = d.Stagger 476 } 477 478 if u.HealthCheck == nil { 479 u.HealthCheck = d.HealthCheck 480 } 481 482 if u.HealthyDeadline == nil { 483 u.HealthyDeadline = d.HealthyDeadline 484 } 485 486 if u.ProgressDeadline == nil { 487 u.ProgressDeadline = d.ProgressDeadline 488 } 489 490 if u.MinHealthyTime == nil { 491 u.MinHealthyTime = d.MinHealthyTime 492 } 493 494 if u.AutoRevert == nil { 495 u.AutoRevert = d.AutoRevert 496 } 497 498 if u.Canary == nil { 499 u.Canary = d.Canary 500 } 501 } 502 503 // Empty returns whether the UpdateStrategy is empty or has user defined values. 504 func (u *UpdateStrategy) Empty() bool { 505 if u == nil { 506 return true 507 } 508 509 if u.Stagger != nil && *u.Stagger != 0 { 510 return false 511 } 512 513 if u.MaxParallel != nil && *u.MaxParallel != 0 { 514 return false 515 } 516 517 if u.HealthCheck != nil && *u.HealthCheck != "" { 518 return false 519 } 520 521 if u.MinHealthyTime != nil && *u.MinHealthyTime != 0 { 522 return false 523 } 524 525 if u.HealthyDeadline != nil && *u.HealthyDeadline != 0 { 526 return false 527 } 528 529 if u.ProgressDeadline != nil && *u.ProgressDeadline != 0 { 530 return false 531 } 532 533 if u.AutoRevert != nil && *u.AutoRevert { 534 return false 535 } 536 537 if u.Canary != nil && *u.Canary != 0 { 538 return false 539 } 540 541 return true 542 } 543 544 // PeriodicConfig is for serializing periodic config for a job. 545 type PeriodicConfig struct { 546 Enabled *bool 547 Spec *string 548 SpecType *string 549 ProhibitOverlap *bool `mapstructure:"prohibit_overlap"` 550 TimeZone *string `mapstructure:"time_zone"` 551 } 552 553 func (p *PeriodicConfig) Canonicalize() { 554 if p.Enabled == nil { 555 p.Enabled = helper.BoolToPtr(true) 556 } 557 if p.Spec == nil { 558 p.Spec = helper.StringToPtr("") 559 } 560 if p.SpecType == nil { 561 p.SpecType = helper.StringToPtr(PeriodicSpecCron) 562 } 563 if p.ProhibitOverlap == nil { 564 p.ProhibitOverlap = helper.BoolToPtr(false) 565 } 566 if p.TimeZone == nil || *p.TimeZone == "" { 567 p.TimeZone = helper.StringToPtr("UTC") 568 } 569 } 570 571 // Next returns the closest time instant matching the spec that is after the 572 // passed time. If no matching instance exists, the zero value of time.Time is 573 // returned. The `time.Location` of the returned value matches that of the 574 // passed time. 575 func (p *PeriodicConfig) Next(fromTime time.Time) (time.Time, error) { 576 if *p.SpecType == PeriodicSpecCron { 577 if e, err := cronexpr.Parse(*p.Spec); err == nil { 578 return structs.CronParseNext(e, fromTime, *p.Spec) 579 } 580 } 581 582 return time.Time{}, nil 583 } 584 585 func (p *PeriodicConfig) GetLocation() (*time.Location, error) { 586 if p.TimeZone == nil || *p.TimeZone == "" { 587 return time.UTC, nil 588 } 589 590 return time.LoadLocation(*p.TimeZone) 591 } 592 593 // ParameterizedJobConfig is used to configure the parameterized job. 594 type ParameterizedJobConfig struct { 595 Payload string 596 MetaRequired []string `mapstructure:"meta_required"` 597 MetaOptional []string `mapstructure:"meta_optional"` 598 } 599 600 // Job is used to serialize a job. 601 type Job struct { 602 Stop *bool 603 Region *string 604 Namespace *string 605 ID *string 606 ParentID *string 607 Name *string 608 Type *string 609 Priority *int 610 AllAtOnce *bool `mapstructure:"all_at_once"` 611 Datacenters []string 612 Constraints []*Constraint 613 Affinities []*Affinity 614 TaskGroups []*TaskGroup 615 Update *UpdateStrategy 616 Spreads []*Spread 617 Periodic *PeriodicConfig 618 ParameterizedJob *ParameterizedJobConfig 619 Dispatched bool 620 Payload []byte 621 Reschedule *ReschedulePolicy 622 Migrate *MigrateStrategy 623 Meta map[string]string 624 VaultToken *string `mapstructure:"vault_token"` 625 Status *string 626 StatusDescription *string 627 Stable *bool 628 Version *uint64 629 SubmitTime *int64 630 CreateIndex *uint64 631 ModifyIndex *uint64 632 JobModifyIndex *uint64 633 } 634 635 // IsPeriodic returns whether a job is periodic. 636 func (j *Job) IsPeriodic() bool { 637 return j.Periodic != nil 638 } 639 640 // IsParameterized returns whether a job is parameterized job. 641 func (j *Job) IsParameterized() bool { 642 return j.ParameterizedJob != nil && !j.Dispatched 643 } 644 645 func (j *Job) Canonicalize() { 646 if j.ID == nil { 647 j.ID = helper.StringToPtr("") 648 } 649 if j.Name == nil { 650 j.Name = helper.StringToPtr(*j.ID) 651 } 652 if j.ParentID == nil { 653 j.ParentID = helper.StringToPtr("") 654 } 655 if j.Namespace == nil { 656 j.Namespace = helper.StringToPtr(DefaultNamespace) 657 } 658 if j.Priority == nil { 659 j.Priority = helper.IntToPtr(50) 660 } 661 if j.Stop == nil { 662 j.Stop = helper.BoolToPtr(false) 663 } 664 if j.Region == nil { 665 j.Region = helper.StringToPtr("global") 666 } 667 if j.Namespace == nil { 668 j.Namespace = helper.StringToPtr("default") 669 } 670 if j.Type == nil { 671 j.Type = helper.StringToPtr("service") 672 } 673 if j.AllAtOnce == nil { 674 j.AllAtOnce = helper.BoolToPtr(false) 675 } 676 if j.VaultToken == nil { 677 j.VaultToken = helper.StringToPtr("") 678 } 679 if j.Status == nil { 680 j.Status = helper.StringToPtr("") 681 } 682 if j.StatusDescription == nil { 683 j.StatusDescription = helper.StringToPtr("") 684 } 685 if j.Stable == nil { 686 j.Stable = helper.BoolToPtr(false) 687 } 688 if j.Version == nil { 689 j.Version = helper.Uint64ToPtr(0) 690 } 691 if j.CreateIndex == nil { 692 j.CreateIndex = helper.Uint64ToPtr(0) 693 } 694 if j.ModifyIndex == nil { 695 j.ModifyIndex = helper.Uint64ToPtr(0) 696 } 697 if j.JobModifyIndex == nil { 698 j.JobModifyIndex = helper.Uint64ToPtr(0) 699 } 700 if j.Periodic != nil { 701 j.Periodic.Canonicalize() 702 } 703 if j.Update != nil { 704 j.Update.Canonicalize() 705 } 706 707 for _, tg := range j.TaskGroups { 708 tg.Canonicalize(j) 709 } 710 } 711 712 // LookupTaskGroup finds a task group by name 713 func (j *Job) LookupTaskGroup(name string) *TaskGroup { 714 for _, tg := range j.TaskGroups { 715 if *tg.Name == name { 716 return tg 717 } 718 } 719 return nil 720 } 721 722 // JobSummary summarizes the state of the allocations of a job 723 type JobSummary struct { 724 JobID string 725 Namespace string 726 Summary map[string]TaskGroupSummary 727 Children *JobChildrenSummary 728 729 // Raft Indexes 730 CreateIndex uint64 731 ModifyIndex uint64 732 } 733 734 // JobChildrenSummary contains the summary of children job status 735 type JobChildrenSummary struct { 736 Pending int64 737 Running int64 738 Dead int64 739 } 740 741 func (jc *JobChildrenSummary) Sum() int { 742 if jc == nil { 743 return 0 744 } 745 746 return int(jc.Pending + jc.Running + jc.Dead) 747 } 748 749 // TaskGroup summarizes the state of all the allocations of a particular 750 // TaskGroup 751 type TaskGroupSummary struct { 752 Queued int 753 Complete int 754 Failed int 755 Running int 756 Starting int 757 Lost int 758 } 759 760 // JobListStub is used to return a subset of information about 761 // jobs during list operations. 762 type JobListStub struct { 763 ID string 764 ParentID string 765 Name string 766 Type string 767 Priority int 768 Periodic bool 769 ParameterizedJob bool 770 Stop bool 771 Status string 772 StatusDescription string 773 JobSummary *JobSummary 774 CreateIndex uint64 775 ModifyIndex uint64 776 JobModifyIndex uint64 777 SubmitTime int64 778 } 779 780 // JobIDSort is used to sort jobs by their job ID's. 781 type JobIDSort []*JobListStub 782 783 func (j JobIDSort) Len() int { 784 return len(j) 785 } 786 787 func (j JobIDSort) Less(a, b int) bool { 788 return j[a].ID < j[b].ID 789 } 790 791 func (j JobIDSort) Swap(a, b int) { 792 j[a], j[b] = j[b], j[a] 793 } 794 795 // NewServiceJob creates and returns a new service-style job 796 // for long-lived processes using the provided name, ID, and 797 // relative job priority. 798 func NewServiceJob(id, name, region string, pri int) *Job { 799 return newJob(id, name, region, JobTypeService, pri) 800 } 801 802 // NewBatchJob creates and returns a new batch-style job for 803 // short-lived processes using the provided name and ID along 804 // with the relative job priority. 805 func NewBatchJob(id, name, region string, pri int) *Job { 806 return newJob(id, name, region, JobTypeBatch, pri) 807 } 808 809 // newJob is used to create a new Job struct. 810 func newJob(id, name, region, typ string, pri int) *Job { 811 return &Job{ 812 Region: ®ion, 813 ID: &id, 814 Name: &name, 815 Type: &typ, 816 Priority: &pri, 817 } 818 } 819 820 // SetMeta is used to set arbitrary k/v pairs of metadata on a job. 821 func (j *Job) SetMeta(key, val string) *Job { 822 if j.Meta == nil { 823 j.Meta = make(map[string]string) 824 } 825 j.Meta[key] = val 826 return j 827 } 828 829 // AddDatacenter is used to add a datacenter to a job. 830 func (j *Job) AddDatacenter(dc string) *Job { 831 j.Datacenters = append(j.Datacenters, dc) 832 return j 833 } 834 835 // Constrain is used to add a constraint to a job. 836 func (j *Job) Constrain(c *Constraint) *Job { 837 j.Constraints = append(j.Constraints, c) 838 return j 839 } 840 841 // AddAffinity is used to add an affinity to a job. 842 func (j *Job) AddAffinity(a *Affinity) *Job { 843 j.Affinities = append(j.Affinities, a) 844 return j 845 } 846 847 // AddTaskGroup adds a task group to an existing job. 848 func (j *Job) AddTaskGroup(grp *TaskGroup) *Job { 849 j.TaskGroups = append(j.TaskGroups, grp) 850 return j 851 } 852 853 // AddPeriodicConfig adds a periodic config to an existing job. 854 func (j *Job) AddPeriodicConfig(cfg *PeriodicConfig) *Job { 855 j.Periodic = cfg 856 return j 857 } 858 859 func (j *Job) AddSpread(s *Spread) *Job { 860 j.Spreads = append(j.Spreads, s) 861 return j 862 } 863 864 type WriteRequest struct { 865 // The target region for this write 866 Region string 867 868 // Namespace is the target namespace for this write 869 Namespace string 870 871 // SecretID is the secret ID of an ACL token 872 SecretID string 873 } 874 875 // JobValidateRequest is used to validate a job 876 type JobValidateRequest struct { 877 Job *Job 878 WriteRequest 879 } 880 881 // JobValidateResponse is the response from validate request 882 type JobValidateResponse struct { 883 // DriverConfigValidated indicates whether the agent validated the driver 884 // config 885 DriverConfigValidated bool 886 887 // ValidationErrors is a list of validation errors 888 ValidationErrors []string 889 890 // Error is a string version of any error that may have occurred 891 Error string 892 893 // Warnings contains any warnings about the given job. These may include 894 // deprecation warnings. 895 Warnings string 896 } 897 898 // JobRevertRequest is used to revert a job to a prior version. 899 type JobRevertRequest struct { 900 // JobID is the ID of the job being reverted 901 JobID string 902 903 // JobVersion the version to revert to. 904 JobVersion uint64 905 906 // EnforcePriorVersion if set will enforce that the job is at the given 907 // version before reverting. 908 EnforcePriorVersion *uint64 909 910 WriteRequest 911 } 912 913 // JobUpdateRequest is used to update a job 914 type JobRegisterRequest struct { 915 Job *Job 916 // If EnforceIndex is set then the job will only be registered if the passed 917 // JobModifyIndex matches the current Jobs index. If the index is zero, the 918 // register only occurs if the job is new. 919 EnforceIndex bool 920 JobModifyIndex uint64 921 PolicyOverride bool 922 923 WriteRequest 924 } 925 926 // RegisterJobRequest is used to serialize a job registration 927 type RegisterJobRequest struct { 928 Job *Job 929 EnforceIndex bool `json:",omitempty"` 930 JobModifyIndex uint64 `json:",omitempty"` 931 PolicyOverride bool `json:",omitempty"` 932 } 933 934 // JobRegisterResponse is used to respond to a job registration 935 type JobRegisterResponse struct { 936 EvalID string 937 EvalCreateIndex uint64 938 JobModifyIndex uint64 939 940 // Warnings contains any warnings about the given job. These may include 941 // deprecation warnings. 942 Warnings string 943 944 QueryMeta 945 } 946 947 // JobDeregisterResponse is used to respond to a job deregistration 948 type JobDeregisterResponse struct { 949 EvalID string 950 EvalCreateIndex uint64 951 JobModifyIndex uint64 952 QueryMeta 953 } 954 955 type JobPlanRequest struct { 956 Job *Job 957 Diff bool 958 PolicyOverride bool 959 WriteRequest 960 } 961 962 type JobPlanResponse struct { 963 JobModifyIndex uint64 964 CreatedEvals []*Evaluation 965 Diff *JobDiff 966 Annotations *PlanAnnotations 967 FailedTGAllocs map[string]*AllocationMetric 968 NextPeriodicLaunch time.Time 969 970 // Warnings contains any warnings about the given job. These may include 971 // deprecation warnings. 972 Warnings string 973 } 974 975 type JobDiff struct { 976 Type string 977 ID string 978 Fields []*FieldDiff 979 Objects []*ObjectDiff 980 TaskGroups []*TaskGroupDiff 981 } 982 983 type TaskGroupDiff struct { 984 Type string 985 Name string 986 Fields []*FieldDiff 987 Objects []*ObjectDiff 988 Tasks []*TaskDiff 989 Updates map[string]uint64 990 } 991 992 type TaskDiff struct { 993 Type string 994 Name string 995 Fields []*FieldDiff 996 Objects []*ObjectDiff 997 Annotations []string 998 } 999 1000 type FieldDiff struct { 1001 Type string 1002 Name string 1003 Old, New string 1004 Annotations []string 1005 } 1006 1007 type ObjectDiff struct { 1008 Type string 1009 Name string 1010 Fields []*FieldDiff 1011 Objects []*ObjectDiff 1012 } 1013 1014 type PlanAnnotations struct { 1015 DesiredTGUpdates map[string]*DesiredUpdates 1016 } 1017 1018 type DesiredUpdates struct { 1019 Ignore uint64 1020 Place uint64 1021 Migrate uint64 1022 Stop uint64 1023 InPlaceUpdate uint64 1024 DestructiveUpdate uint64 1025 Canary uint64 1026 } 1027 1028 type JobDispatchRequest struct { 1029 JobID string 1030 Payload []byte 1031 Meta map[string]string 1032 } 1033 1034 type JobDispatchResponse struct { 1035 DispatchedJobID string 1036 EvalID string 1037 EvalCreateIndex uint64 1038 JobCreateIndex uint64 1039 WriteMeta 1040 } 1041 1042 // JobVersionsResponse is used for a job get versions request 1043 type JobVersionsResponse struct { 1044 Versions []*Job 1045 Diffs []*JobDiff 1046 QueryMeta 1047 } 1048 1049 // JobStabilityRequest is used to marked a job as stable. 1050 type JobStabilityRequest struct { 1051 // Job to set the stability on 1052 JobID string 1053 JobVersion uint64 1054 1055 // Set the stability 1056 Stable bool 1057 WriteRequest 1058 } 1059 1060 // JobStabilityResponse is the response when marking a job as stable. 1061 type JobStabilityResponse struct { 1062 JobModifyIndex uint64 1063 WriteMeta 1064 } 1065 1066 // JobEvaluateRequest is used when we just need to re-evaluate a target job 1067 type JobEvaluateRequest struct { 1068 JobID string 1069 EvalOptions EvalOptions 1070 WriteRequest 1071 } 1072 1073 // EvalOptions is used to encapsulate options when forcing a job evaluation 1074 type EvalOptions struct { 1075 ForceReschedule bool 1076 }