open-nomad/api/jobs.go

1193 lines
30 KiB
Go
Raw Normal View History

2015-09-08 23:24:26 +00:00
package api
2015-09-17 19:40:51 +00:00
import (
2016-05-12 01:51:48 +00:00
"fmt"
"net/url"
2015-09-17 19:40:51 +00:00
"sort"
"strconv"
2015-10-20 23:42:53 +00:00
"time"
2017-02-06 19:48:28 +00:00
"github.com/gorhill/cronexpr"
2015-09-17 19:40:51 +00:00
)
2015-09-10 00:29:43 +00:00
const (
// JobTypeService indicates a long-running processes
JobTypeService = "service"
// JobTypeBatch indicates a short-lived process
JobTypeBatch = "batch"
2017-02-06 19:48:28 +00:00
// JobTypeSystem indicates a system process that should run on all clients
JobTypeSystem = "system"
2017-02-06 19:48:28 +00:00
// PeriodicSpecCron is used for a cron spec.
PeriodicSpecCron = "cron"
2017-09-07 23:56:15 +00:00
// DefaultNamespace is the default namespace.
DefaultNamespace = "default"
// For Job configuration, GlobalRegion is a sentinel region value
// that users may specify to indicate the job should be run on
// the region of the node that the job was submitted to.
// For Client configuration, if no region information is given,
// the client node will default to be part of the GlobalRegion.
GlobalRegion = "global"
2015-09-10 00:29:43 +00:00
)
2016-06-08 23:48:02 +00:00
const (
// RegisterEnforceIndexErrPrefix is the prefix to use in errors caused by
// enforcing the job modify index during registers.
RegisterEnforceIndexErrPrefix = "Enforcing job modify index"
)
2015-09-08 23:24:26 +00:00
// Jobs is used to access the job-specific endpoints.
type Jobs struct {
client *Client
}
// JobsParseRequest is used for arguments of the /vi/jobs/parse endpoint
type JobsParseRequest struct {
2018-04-17 14:18:36 +00:00
// JobHCL is an hcl jobspec
JobHCL string
2018-04-17 14:18:36 +00:00
// Canonicalize is a flag as to if the server should return default values
// for unset fields
Canonicalize bool
}
2015-09-08 23:24:26 +00:00
// Jobs returns a handle on the jobs endpoints.
func (c *Client) Jobs() *Jobs {
return &Jobs{client: c}
}
// Parse is used to convert the HCL repesentation of a Job to JSON server side.
// To parse the HCL client side see package github.com/hashicorp/nomad/jobspec
2018-04-17 14:18:36 +00:00
func (j *Jobs) ParseHCL(jobHCL string, canonicalize bool) (*Job, error) {
var job Job
req := &JobsParseRequest{
JobHCL: jobHCL,
Canonicalize: canonicalize,
}
_, err := j.client.write("/v1/jobs/parse", req, &job, nil)
return &job, err
}
2017-02-06 19:48:28 +00:00
func (j *Jobs) Validate(job *Job, q *WriteOptions) (*JobValidateResponse, *WriteMeta, error) {
var resp JobValidateResponse
req := &JobValidateRequest{Job: job}
if q != nil {
req.WriteRequest = WriteRequest{Region: q.Region}
}
wm, err := j.client.write("/v1/validate/job", req, &resp, q)
return &resp, wm, err
}
2017-09-19 14:47:10 +00:00
// RegisterOptions is used to pass through job registration parameters
type RegisterOptions struct {
EnforceIndex bool
ModifyIndex uint64
PolicyOverride bool
}
2015-09-08 23:24:26 +00:00
// Register is used to register a new job. It returns the ID
// of the evaluation, along with any errors encountered.
func (j *Jobs) Register(job *Job, q *WriteOptions) (*JobRegisterResponse, *WriteMeta, error) {
2017-09-19 14:47:10 +00:00
return j.RegisterOpts(job, nil, q)
2016-06-08 23:48:02 +00:00
}
// EnforceRegister is used to register a job enforcing its job modify index.
func (j *Jobs) EnforceRegister(job *Job, modifyIndex uint64, q *WriteOptions) (*JobRegisterResponse, *WriteMeta, error) {
2017-09-19 14:47:10 +00:00
opts := RegisterOptions{EnforceIndex: true, ModifyIndex: modifyIndex}
return j.RegisterOpts(job, &opts, q)
}
2016-06-08 23:48:02 +00:00
2017-09-19 14:47:10 +00:00
// Register is used to register a new job. It returns the ID
// of the evaluation, along with any errors encountered.
func (j *Jobs) RegisterOpts(job *Job, opts *RegisterOptions, q *WriteOptions) (*JobRegisterResponse, *WriteMeta, error) {
// Format the request
2016-06-08 23:48:02 +00:00
req := &RegisterJobRequest{
2017-09-19 14:47:10 +00:00
Job: job,
2016-06-08 23:48:02 +00:00
}
2017-09-19 14:47:10 +00:00
if opts != nil {
if opts.EnforceIndex {
req.EnforceIndex = true
req.JobModifyIndex = opts.ModifyIndex
}
if opts.PolicyOverride {
req.PolicyOverride = true
}
}
var resp JobRegisterResponse
2015-09-08 23:24:26 +00:00
wm, err := j.client.write("/v1/jobs", req, &resp, q)
if err != nil {
return nil, nil, err
2015-09-08 23:24:26 +00:00
}
return &resp, wm, nil
2015-09-08 23:24:26 +00:00
}
// List is used to list all of the existing jobs.
2015-09-14 02:55:47 +00:00
func (j *Jobs) List(q *QueryOptions) ([]*JobListStub, *QueryMeta, error) {
var resp []*JobListStub
2015-09-09 20:18:50 +00:00
qm, err := j.client.query("/v1/jobs", &resp, q)
2015-09-08 23:24:26 +00:00
if err != nil {
2015-09-08 23:45:16 +00:00
return nil, qm, err
2015-09-08 23:24:26 +00:00
}
2015-09-17 19:40:51 +00:00
sort.Sort(JobIDSort(resp))
2015-09-08 23:45:16 +00:00
return resp, qm, nil
2015-09-08 23:24:26 +00:00
}
// PrefixList is used to list all existing jobs that match the prefix.
func (j *Jobs) PrefixList(prefix string) ([]*JobListStub, *QueryMeta, error) {
return j.List(&QueryOptions{Prefix: prefix})
}
2015-09-09 00:49:31 +00:00
// Info is used to retrieve information about a particular
2015-09-09 00:20:52 +00:00
// job given its unique ID.
2015-09-09 20:18:50 +00:00
func (j *Jobs) Info(jobID string, q *QueryOptions) (*Job, *QueryMeta, error) {
2015-09-09 00:20:52 +00:00
var resp Job
qm, err := j.client.query("/v1/job/"+url.PathEscape(jobID), &resp, q)
2015-09-09 00:20:52 +00:00
if err != nil {
return nil, nil, err
}
return &resp, qm, nil
}
2020-01-25 01:15:49 +00:00
// Scale is used to retrieve information about a particular
// job given its unique ID.
func (j *Jobs) Scale(jobID, group string, count *int, message string, error bool, meta map[string]interface{},
q *WriteOptions) (*JobRegisterResponse, *WriteMeta, error) {
var count64 *int64
if count != nil {
count64 = int64ToPtr(int64(*count))
}
2020-01-25 01:15:49 +00:00
req := &ScalingRequest{
Count: count64,
Target: map[string]string{
"Job": jobID,
"Group": group,
},
Error: error,
Message: message,
Meta: meta,
2020-01-25 01:15:49 +00:00
}
var resp JobRegisterResponse
qm, err := j.client.write(fmt.Sprintf("/v1/job/%s/scale", url.PathEscape(jobID)), req, &resp, q)
2020-01-25 01:15:49 +00:00
if err != nil {
return nil, nil, err
}
return &resp, qm, nil
}
// ScaleStatus is used to retrieve information about a particular
// job given its unique ID.
func (j *Jobs) ScaleStatus(jobID string, q *QueryOptions) (*JobScaleStatusResponse, *QueryMeta, error) {
var resp JobScaleStatusResponse
qm, err := j.client.query(fmt.Sprintf("/v1/job/%s/scale", url.PathEscape(jobID)), &resp, q)
if err != nil {
return nil, nil, err
}
return &resp, qm, nil
}
// Versions is used to retrieve all versions of a particular job given its
// unique ID.
func (j *Jobs) Versions(jobID string, diffs bool, q *QueryOptions) ([]*Job, []*JobDiff, *QueryMeta, error) {
var resp JobVersionsResponse
qm, err := j.client.query(fmt.Sprintf("/v1/job/%s/versions?diffs=%v", url.PathEscape(jobID), diffs), &resp, q)
2017-04-13 23:55:21 +00:00
if err != nil {
return nil, nil, nil, err
2017-04-13 23:55:21 +00:00
}
return resp.Versions, resp.Diffs, qm, nil
2017-04-13 23:55:21 +00:00
}
2015-09-09 00:49:31 +00:00
// Allocations is used to return the allocs for a given job ID.
func (j *Jobs) Allocations(jobID string, allAllocs bool, q *QueryOptions) ([]*AllocationListStub, *QueryMeta, error) {
2015-09-14 02:55:47 +00:00
var resp []*AllocationListStub
u, err := url.Parse("/v1/job/" + url.PathEscape(jobID) + "/allocations")
if err != nil {
return nil, nil, err
}
v := u.Query()
v.Add("all", strconv.FormatBool(allAllocs))
u.RawQuery = v.Encode()
qm, err := j.client.query(u.String(), &resp, q)
2015-09-09 00:49:31 +00:00
if err != nil {
return nil, nil, err
}
2015-09-17 19:40:51 +00:00
sort.Sort(AllocIndexSort(resp))
2015-09-09 00:49:31 +00:00
return resp, qm, nil
}
2017-07-01 00:23:34 +00:00
// Deployments is used to query the deployments associated with the given job
// ID.
func (j *Jobs) Deployments(jobID string, all bool, q *QueryOptions) ([]*Deployment, *QueryMeta, error) {
2017-07-01 00:23:34 +00:00
var resp []*Deployment
u, err := url.Parse("/v1/job/" + url.PathEscape(jobID) + "/deployments")
if err != nil {
return nil, nil, err
}
v := u.Query()
v.Add("all", strconv.FormatBool(all))
u.RawQuery = v.Encode()
qm, err := j.client.query(u.String(), &resp, q)
2017-07-01 00:23:34 +00:00
if err != nil {
return nil, nil, err
}
sort.Sort(DeploymentIndexSort(resp))
return resp, qm, nil
}
// LatestDeployment is used to query for the latest deployment associated with
2015-09-09 01:42:34 +00:00
// the given job ID.
2017-07-01 00:23:34 +00:00
func (j *Jobs) LatestDeployment(jobID string, q *QueryOptions) (*Deployment, *QueryMeta, error) {
var resp *Deployment
qm, err := j.client.query("/v1/job/"+url.PathEscape(jobID)+"/deployment", &resp, q)
2017-07-01 00:23:34 +00:00
if err != nil {
return nil, nil, err
}
return resp, qm, nil
}
// Evaluations is used to query the evaluations associated with the given job
// ID.
2015-09-09 20:18:50 +00:00
func (j *Jobs) Evaluations(jobID string, q *QueryOptions) ([]*Evaluation, *QueryMeta, error) {
2015-09-09 01:42:34 +00:00
var resp []*Evaluation
qm, err := j.client.query("/v1/job/"+url.PathEscape(jobID)+"/evaluations", &resp, q)
2015-09-09 01:42:34 +00:00
if err != nil {
return nil, nil, err
}
2015-09-17 19:40:51 +00:00
sort.Sort(EvalIndexSort(resp))
2015-09-09 01:42:34 +00:00
return resp, qm, nil
}
2017-04-15 03:54:30 +00:00
// Deregister is used to remove an existing job. If purge is set to true, the job
// is deregistered and purged from the system versus still being queryable and
// eventually GC'ed from the system. Most callers should not specify purge.
func (j *Jobs) Deregister(jobID string, purge bool, q *WriteOptions) (string, *WriteMeta, error) {
2017-04-19 21:57:28 +00:00
var resp JobDeregisterResponse
wm, err := j.client.delete(fmt.Sprintf("/v1/job/%v?purge=%t", url.PathEscape(jobID), purge), &resp, q)
2015-09-09 01:42:34 +00:00
if err != nil {
2015-09-17 00:12:48 +00:00
return "", nil, err
2015-09-09 01:42:34 +00:00
}
2015-09-17 00:12:48 +00:00
return resp.EvalID, wm, nil
2015-09-09 01:42:34 +00:00
}
2015-09-10 01:39:24 +00:00
// ForceEvaluate is used to force-evaluate an existing job.
func (j *Jobs) ForceEvaluate(jobID string, q *WriteOptions) (string, *WriteMeta, error) {
var resp JobRegisterResponse
wm, err := j.client.write("/v1/job/"+url.PathEscape(jobID)+"/evaluate", nil, &resp, q)
if err != nil {
return "", nil, err
}
return resp.EvalID, wm, nil
}
// EvaluateWithOpts is used to force-evaluate an existing job and takes additional options
// for whether to force reschedule failed allocations
func (j *Jobs) EvaluateWithOpts(jobID string, opts EvalOptions, q *WriteOptions) (string, *WriteMeta, error) {
req := &JobEvaluateRequest{
JobID: jobID,
EvalOptions: opts,
}
2017-04-19 21:57:28 +00:00
var resp JobRegisterResponse
wm, err := j.client.write("/v1/job/"+url.PathEscape(jobID)+"/evaluate", req, &resp, q)
2015-09-10 01:39:24 +00:00
if err != nil {
return "", nil, err
}
return resp.EvalID, wm, nil
}
2016-01-19 19:09:36 +00:00
// PeriodicForce spawns a new instance of the periodic job and returns the eval ID
func (j *Jobs) PeriodicForce(jobID string, q *WriteOptions) (string, *WriteMeta, error) {
var resp periodicForceResponse
wm, err := j.client.write("/v1/job/"+url.PathEscape(jobID)+"/periodic/force", nil, &resp, q)
2016-01-19 19:09:36 +00:00
if err != nil {
return "", nil, err
}
return resp.EvalID, wm, nil
}
2017-09-19 14:47:10 +00:00
// PlanOptions is used to pass through job planning parameters
type PlanOptions struct {
Diff bool
PolicyOverride bool
}
2016-05-12 01:51:48 +00:00
func (j *Jobs) Plan(job *Job, diff bool, q *WriteOptions) (*JobPlanResponse, *WriteMeta, error) {
2017-09-19 14:47:10 +00:00
opts := PlanOptions{Diff: diff}
return j.PlanOpts(job, &opts, q)
}
func (j *Jobs) PlanOpts(job *Job, opts *PlanOptions, q *WriteOptions) (*JobPlanResponse, *WriteMeta, error) {
2016-05-12 01:51:48 +00:00
if job == nil {
return nil, nil, fmt.Errorf("must pass non-nil job")
}
2017-09-19 14:47:10 +00:00
// Setup the request
2016-05-12 01:51:48 +00:00
req := &JobPlanRequest{
2017-09-19 14:47:10 +00:00
Job: job,
2016-05-12 01:51:48 +00:00
}
2017-09-19 14:47:10 +00:00
if opts != nil {
req.Diff = opts.Diff
req.PolicyOverride = opts.PolicyOverride
}
var resp JobPlanResponse
wm, err := j.client.write("/v1/job/"+url.PathEscape(*job.ID)+"/plan", req, &resp, q)
2016-05-12 01:51:48 +00:00
if err != nil {
return nil, nil, err
}
return &resp, wm, nil
}
func (j *Jobs) Summary(jobID string, q *QueryOptions) (*JobSummary, *QueryMeta, error) {
var resp JobSummary
qm, err := j.client.query("/v1/job/"+url.PathEscape(jobID)+"/summary", &resp, q)
if err != nil {
return nil, nil, err
}
return &resp, qm, nil
}
2016-12-05 05:22:13 +00:00
func (j *Jobs) Dispatch(jobID string, meta map[string]string,
2016-12-14 20:50:08 +00:00
payload []byte, q *WriteOptions) (*JobDispatchResponse, *WriteMeta, error) {
2016-12-05 05:22:13 +00:00
var resp JobDispatchResponse
req := &JobDispatchRequest{
2016-12-14 20:50:08 +00:00
JobID: jobID,
Meta: meta,
Payload: payload,
2016-12-05 05:22:13 +00:00
}
wm, err := j.client.write("/v1/job/"+url.PathEscape(jobID)+"/dispatch", req, &resp, q)
2016-12-05 05:22:13 +00:00
if err != nil {
return nil, nil, err
}
return &resp, wm, nil
}
2017-04-19 21:57:28 +00:00
// Revert is used to revert the given job to the passed version. If
// enforceVersion is set, the job is only reverted if the current version is at
// the passed version.
func (j *Jobs) Revert(jobID string, version uint64, enforcePriorVersion *uint64,
q *WriteOptions, consulToken, vaultToken string) (*JobRegisterResponse, *WriteMeta, error) {
2017-04-19 21:57:28 +00:00
var resp JobRegisterResponse
req := &JobRevertRequest{
JobID: jobID,
JobVersion: version,
EnforcePriorVersion: enforcePriorVersion,
// ConsulToken: consulToken, // TODO(shoenig) enable!
VaultToken: vaultToken,
2017-04-19 21:57:28 +00:00
}
wm, err := j.client.write("/v1/job/"+url.PathEscape(jobID)+"/revert", req, &resp, q)
2017-04-19 21:57:28 +00:00
if err != nil {
return nil, nil, err
}
return &resp, wm, nil
}
2017-07-06 19:49:13 +00:00
// Stable is used to mark a job version's stability.
func (j *Jobs) Stable(jobID string, version uint64, stable bool,
q *WriteOptions) (*JobStabilityResponse, *WriteMeta, error) {
var resp JobStabilityResponse
req := &JobStabilityRequest{
JobID: jobID,
JobVersion: version,
Stable: stable,
}
wm, err := j.client.write("/v1/job/"+url.PathEscape(jobID)+"/stable", req, &resp, q)
2017-07-06 19:49:13 +00:00
if err != nil {
return nil, nil, err
}
return &resp, wm, nil
}
2016-01-19 19:09:36 +00:00
// periodicForceResponse is used to deserialize a force response
type periodicForceResponse struct {
EvalID string
}
// UpdateStrategy defines a task groups update strategy.
type UpdateStrategy struct {
2018-03-23 17:56:00 +00:00
Stagger *time.Duration `mapstructure:"stagger"`
MaxParallel *int `mapstructure:"max_parallel"`
HealthCheck *string `mapstructure:"health_check"`
MinHealthyTime *time.Duration `mapstructure:"min_healthy_time"`
HealthyDeadline *time.Duration `mapstructure:"healthy_deadline"`
ProgressDeadline *time.Duration `mapstructure:"progress_deadline"`
Canary *int `mapstructure:"canary"`
2018-03-23 17:56:00 +00:00
AutoRevert *bool `mapstructure:"auto_revert"`
2019-05-09 13:42:18 +00:00
AutoPromote *bool `mapstructure:"auto_promote"`
}
// DefaultUpdateStrategy provides a baseline that can be used to upgrade
// jobs with the old policy or for populating field defaults.
func DefaultUpdateStrategy() *UpdateStrategy {
return &UpdateStrategy{
Stagger: timeToPtr(30 * time.Second),
MaxParallel: intToPtr(1),
HealthCheck: stringToPtr("checks"),
MinHealthyTime: timeToPtr(10 * time.Second),
HealthyDeadline: timeToPtr(5 * time.Minute),
ProgressDeadline: timeToPtr(10 * time.Minute),
AutoRevert: boolToPtr(false),
Canary: intToPtr(0),
AutoPromote: boolToPtr(false),
}
}
func (u *UpdateStrategy) Copy() *UpdateStrategy {
if u == nil {
return nil
}
copy := new(UpdateStrategy)
if u.Stagger != nil {
copy.Stagger = timeToPtr(*u.Stagger)
}
if u.MaxParallel != nil {
copy.MaxParallel = intToPtr(*u.MaxParallel)
}
if u.HealthCheck != nil {
copy.HealthCheck = stringToPtr(*u.HealthCheck)
}
if u.MinHealthyTime != nil {
copy.MinHealthyTime = timeToPtr(*u.MinHealthyTime)
}
if u.HealthyDeadline != nil {
copy.HealthyDeadline = timeToPtr(*u.HealthyDeadline)
}
2018-03-23 17:56:00 +00:00
if u.ProgressDeadline != nil {
copy.ProgressDeadline = timeToPtr(*u.ProgressDeadline)
2018-03-23 17:56:00 +00:00
}
if u.AutoRevert != nil {
copy.AutoRevert = boolToPtr(*u.AutoRevert)
}
if u.Canary != nil {
copy.Canary = intToPtr(*u.Canary)
}
2019-05-09 13:42:18 +00:00
if u.AutoPromote != nil {
copy.AutoPromote = boolToPtr(*u.AutoPromote)
}
return copy
}
func (u *UpdateStrategy) Merge(o *UpdateStrategy) {
if o == nil {
return
}
if o.Stagger != nil {
u.Stagger = timeToPtr(*o.Stagger)
}
if o.MaxParallel != nil {
u.MaxParallel = intToPtr(*o.MaxParallel)
}
if o.HealthCheck != nil {
u.HealthCheck = stringToPtr(*o.HealthCheck)
}
if o.MinHealthyTime != nil {
u.MinHealthyTime = timeToPtr(*o.MinHealthyTime)
}
if o.HealthyDeadline != nil {
u.HealthyDeadline = timeToPtr(*o.HealthyDeadline)
}
2018-03-23 17:56:00 +00:00
if o.ProgressDeadline != nil {
u.ProgressDeadline = timeToPtr(*o.ProgressDeadline)
2018-03-23 17:56:00 +00:00
}
if o.AutoRevert != nil {
u.AutoRevert = boolToPtr(*o.AutoRevert)
}
if o.Canary != nil {
u.Canary = intToPtr(*o.Canary)
}
2019-05-09 13:42:18 +00:00
if o.AutoPromote != nil {
u.AutoPromote = boolToPtr(*o.AutoPromote)
}
}
func (u *UpdateStrategy) Canonicalize() {
d := DefaultUpdateStrategy()
2017-08-23 13:30:28 +00:00
if u.MaxParallel == nil {
u.MaxParallel = d.MaxParallel
}
if u.Stagger == nil {
u.Stagger = d.Stagger
}
if u.HealthCheck == nil {
u.HealthCheck = d.HealthCheck
}
if u.HealthyDeadline == nil {
u.HealthyDeadline = d.HealthyDeadline
}
2018-03-23 17:56:00 +00:00
if u.ProgressDeadline == nil {
u.ProgressDeadline = d.ProgressDeadline
}
if u.MinHealthyTime == nil {
u.MinHealthyTime = d.MinHealthyTime
}
if u.AutoRevert == nil {
u.AutoRevert = d.AutoRevert
}
if u.Canary == nil {
u.Canary = d.Canary
}
if u.AutoPromote == nil {
u.AutoPromote = d.AutoPromote
}
}
// Empty returns whether the UpdateStrategy is empty or has user defined values.
func (u *UpdateStrategy) Empty() bool {
if u == nil {
return true
}
if u.Stagger != nil && *u.Stagger != 0 {
return false
}
if u.MaxParallel != nil && *u.MaxParallel != 0 {
return false
}
if u.HealthCheck != nil && *u.HealthCheck != "" {
return false
}
if u.MinHealthyTime != nil && *u.MinHealthyTime != 0 {
return false
}
if u.HealthyDeadline != nil && *u.HealthyDeadline != 0 {
return false
}
2018-03-23 17:56:00 +00:00
if u.ProgressDeadline != nil && *u.ProgressDeadline != 0 {
return false
}
if u.AutoRevert != nil && *u.AutoRevert {
return false
}
if u.AutoPromote != nil && *u.AutoPromote {
return false
}
if u.Canary != nil && *u.Canary != 0 {
return false
}
return true
}
// PeriodicConfig is for serializing periodic config for a job.
type PeriodicConfig struct {
2017-02-06 19:48:28 +00:00
Enabled *bool
Spec *string
SpecType *string
ProhibitOverlap *bool `mapstructure:"prohibit_overlap"`
TimeZone *string `mapstructure:"time_zone"`
2017-02-06 19:48:28 +00:00
}
func (p *PeriodicConfig) Canonicalize() {
if p.Enabled == nil {
p.Enabled = boolToPtr(true)
2017-02-06 19:48:28 +00:00
}
2017-02-24 22:18:09 +00:00
if p.Spec == nil {
p.Spec = stringToPtr("")
2017-02-24 22:18:09 +00:00
}
2017-02-06 19:48:28 +00:00
if p.SpecType == nil {
p.SpecType = stringToPtr(PeriodicSpecCron)
2017-02-06 19:48:28 +00:00
}
if p.ProhibitOverlap == nil {
p.ProhibitOverlap = boolToPtr(false)
2017-02-06 19:48:28 +00:00
}
if p.TimeZone == nil || *p.TimeZone == "" {
p.TimeZone = stringToPtr("UTC")
}
2017-02-06 19:48:28 +00:00
}
// Next returns the closest time instant matching the spec that is after the
// passed time. If no matching instance exists, the zero value of time.Time is
// returned. The `time.Location` of the returned value matches that of the
// passed time.
2018-04-26 22:46:22 +00:00
func (p *PeriodicConfig) Next(fromTime time.Time) (time.Time, error) {
2017-02-06 19:48:28 +00:00
if *p.SpecType == PeriodicSpecCron {
if e, err := cronexpr.Parse(*p.Spec); err == nil {
return cronParseNext(e, fromTime, *p.Spec)
2017-02-06 19:48:28 +00:00
}
}
2018-04-26 22:46:22 +00:00
return time.Time{}, nil
}
// cronParseNext is a helper that parses the next time for the given expression
// but captures any panic that may occur in the underlying library.
// --- THIS FUNCTION IS REPLICATED IN nomad/structs/structs.go
// and should be kept in sync.
func cronParseNext(e *cronexpr.Expression, fromTime time.Time, spec string) (t time.Time, err error) {
defer func() {
if recover() != nil {
t = time.Time{}
err = fmt.Errorf("failed parsing cron expression: %q", spec)
}
}()
return e.Next(fromTime), nil
}
2017-02-15 22:37:06 +00:00
func (p *PeriodicConfig) GetLocation() (*time.Location, error) {
if p.TimeZone == nil || *p.TimeZone == "" {
return time.UTC, nil
}
return time.LoadLocation(*p.TimeZone)
}
// ParameterizedJobConfig is used to configure the parameterized job.
type ParameterizedJobConfig struct {
2016-12-14 20:50:08 +00:00
Payload string
MetaRequired []string `mapstructure:"meta_required"`
MetaOptional []string `mapstructure:"meta_optional"`
2016-11-23 22:56:50 +00:00
}
2015-09-08 23:24:26 +00:00
// Job is used to serialize a job.
type Job struct {
2017-04-15 03:54:30 +00:00
Stop *bool
2017-02-06 19:48:28 +00:00
Region *string
2017-09-07 23:56:15 +00:00
Namespace *string
2017-02-06 19:48:28 +00:00
ID *string
ParentID *string
Name *string
Type *string
Priority *int
AllAtOnce *bool `mapstructure:"all_at_once"`
2015-09-08 23:24:26 +00:00
Datacenters []string
2015-09-09 02:27:04 +00:00
Constraints []*Constraint
2018-07-16 13:30:58 +00:00
Affinities []*Affinity
2015-09-10 00:29:43 +00:00
TaskGroups []*TaskGroup
Update *UpdateStrategy
Spreads []*Spread
Periodic *PeriodicConfig
ParameterizedJob *ParameterizedJobConfig
Dispatched bool
2016-12-14 20:50:08 +00:00
Payload []byte
Reschedule *ReschedulePolicy
2018-03-01 19:21:32 +00:00
Migrate *MigrateStrategy
2015-09-08 23:24:26 +00:00
Meta map[string]string
ConsulToken *string `mapstructure:"consul_token"`
VaultToken *string `mapstructure:"vault_token"`
2017-02-06 19:48:28 +00:00
Status *string
StatusDescription *string
2017-04-13 20:54:57 +00:00
Stable *bool
Version *uint64
2017-06-30 02:08:25 +00:00
SubmitTime *int64
2017-02-06 19:48:28 +00:00
CreateIndex *uint64
ModifyIndex *uint64
JobModifyIndex *uint64
}
// IsPeriodic returns whether a job is periodic.
func (j *Job) IsPeriodic() bool {
return j.Periodic != nil
}
// IsParameterized returns whether a job is parameterized job.
func (j *Job) IsParameterized() bool {
return j.ParameterizedJob != nil && !j.Dispatched
2017-02-06 19:48:28 +00:00
}
func (j *Job) Canonicalize() {
if j.ID == nil {
j.ID = stringToPtr("")
2017-02-06 19:48:28 +00:00
}
if j.Name == nil {
j.Name = stringToPtr(*j.ID)
2017-02-06 19:48:28 +00:00
}
2017-02-13 23:18:17 +00:00
if j.ParentID == nil {
j.ParentID = stringToPtr("")
2017-02-13 23:18:17 +00:00
}
2017-09-07 23:56:15 +00:00
if j.Namespace == nil {
j.Namespace = stringToPtr(DefaultNamespace)
2017-09-07 23:56:15 +00:00
}
2017-02-06 19:48:28 +00:00
if j.Priority == nil {
j.Priority = intToPtr(50)
2017-02-06 19:48:28 +00:00
}
2017-04-15 03:54:30 +00:00
if j.Stop == nil {
j.Stop = boolToPtr(false)
2017-04-15 03:54:30 +00:00
}
2017-02-06 19:48:28 +00:00
if j.Region == nil {
j.Region = stringToPtr(GlobalRegion)
2017-02-06 19:48:28 +00:00
}
2017-09-07 23:56:15 +00:00
if j.Namespace == nil {
j.Namespace = stringToPtr("default")
2017-09-07 23:56:15 +00:00
}
2017-02-06 19:48:28 +00:00
if j.Type == nil {
j.Type = stringToPtr("service")
2017-02-06 19:48:28 +00:00
}
if j.AllAtOnce == nil {
j.AllAtOnce = boolToPtr(false)
2017-02-06 19:48:28 +00:00
}
if j.ConsulToken == nil {
j.ConsulToken = stringToPtr("")
}
2017-02-06 19:48:28 +00:00
if j.VaultToken == nil {
j.VaultToken = stringToPtr("")
2017-02-06 19:48:28 +00:00
}
if j.Status == nil {
j.Status = stringToPtr("")
2017-02-06 19:48:28 +00:00
}
if j.StatusDescription == nil {
j.StatusDescription = stringToPtr("")
2017-02-06 19:48:28 +00:00
}
2017-04-13 20:54:57 +00:00
if j.Stable == nil {
j.Stable = boolToPtr(false)
2017-04-13 20:54:57 +00:00
}
if j.Version == nil {
j.Version = uint64ToPtr(0)
2017-04-13 20:54:57 +00:00
}
2017-02-06 19:48:28 +00:00
if j.CreateIndex == nil {
j.CreateIndex = uint64ToPtr(0)
2017-02-06 19:48:28 +00:00
}
if j.ModifyIndex == nil {
j.ModifyIndex = uint64ToPtr(0)
2017-02-06 19:48:28 +00:00
}
if j.JobModifyIndex == nil {
j.JobModifyIndex = uint64ToPtr(0)
2017-02-06 19:48:28 +00:00
}
if j.Periodic != nil {
j.Periodic.Canonicalize()
}
if j.Update != nil {
j.Update.Canonicalize()
} else if *j.Type == JobTypeService {
j.Update = DefaultUpdateStrategy()
}
2017-02-06 19:48:28 +00:00
for _, tg := range j.TaskGroups {
tg.Canonicalize(j)
2017-02-06 19:48:28 +00:00
}
for _, spread := range j.Spreads {
spread.Canonicalize()
}
for _, a := range j.Affinities {
a.Canonicalize()
}
2015-09-14 02:55:47 +00:00
}
// LookupTaskGroup finds a task group by name
func (j *Job) LookupTaskGroup(name string) *TaskGroup {
for _, tg := range j.TaskGroups {
if *tg.Name == name {
return tg
}
}
return nil
}
// JobSummary summarizes the state of the allocations of a job
type JobSummary struct {
2017-09-07 23:56:15 +00:00
JobID string
Namespace string
Summary map[string]TaskGroupSummary
Children *JobChildrenSummary
// Raft Indexes
CreateIndex uint64
ModifyIndex uint64
}
2016-12-06 01:24:37 +00:00
// JobChildrenSummary contains the summary of children job status
type JobChildrenSummary struct {
Pending int64
Running int64
Dead int64
}
func (jc *JobChildrenSummary) Sum() int {
if jc == nil {
return 0
}
return int(jc.Pending + jc.Running + jc.Dead)
2016-12-06 01:24:37 +00:00
}
// TaskGroup summarizes the state of all the allocations of a particular
// TaskGroup
type TaskGroupSummary struct {
Queued int
Complete int
Failed int
Running int
Starting int
Lost int
}
2015-09-14 02:55:47 +00:00
// JobListStub is used to return a subset of information about
// jobs during list operations.
type JobListStub struct {
ID string
ParentID string
2015-09-14 02:55:47 +00:00
Name string
Datacenters []string
2015-09-14 02:55:47 +00:00
Type string
Priority int
2017-04-16 00:05:52 +00:00
Periodic bool
ParameterizedJob bool
2017-04-15 03:54:30 +00:00
Stop bool
2015-09-14 02:55:47 +00:00
Status string
StatusDescription string
JobSummary *JobSummary
2015-09-14 02:55:47 +00:00
CreateIndex uint64
ModifyIndex uint64
2016-06-08 23:48:02 +00:00
JobModifyIndex uint64
2017-06-30 02:08:25 +00:00
SubmitTime int64
2015-09-08 23:24:26 +00:00
}
2015-09-17 19:40:51 +00:00
// JobIDSort is used to sort jobs by their job ID's.
type JobIDSort []*JobListStub
func (j JobIDSort) Len() int {
return len(j)
}
func (j JobIDSort) Less(a, b int) bool {
return j[a].ID < j[b].ID
}
func (j JobIDSort) Swap(a, b int) {
j[a], j[b] = j[b], j[a]
}
2015-09-10 00:29:43 +00:00
// NewServiceJob creates and returns a new service-style job
// for long-lived processes using the provided name, ID, and
// relative job priority.
2015-09-16 18:42:08 +00:00
func NewServiceJob(id, name, region string, pri int) *Job {
return newJob(id, name, region, JobTypeService, pri)
2015-09-10 00:29:43 +00:00
}
// NewBatchJob creates and returns a new batch-style job for
// short-lived processes using the provided name and ID along
// with the relative job priority.
2015-09-16 18:42:08 +00:00
func NewBatchJob(id, name, region string, pri int) *Job {
return newJob(id, name, region, JobTypeBatch, pri)
2015-09-10 00:29:43 +00:00
}
// newJob is used to create a new Job struct.
2015-09-16 18:42:08 +00:00
func newJob(id, name, region, typ string, pri int) *Job {
2015-09-10 00:29:43 +00:00
return &Job{
2017-02-06 19:48:28 +00:00
Region: &region,
ID: &id,
Name: &name,
Type: &typ,
Priority: &pri,
2015-09-10 00:29:43 +00:00
}
}
// SetMeta is used to set arbitrary k/v pairs of metadata on a job.
func (j *Job) SetMeta(key, val string) *Job {
if j.Meta == nil {
j.Meta = make(map[string]string)
}
j.Meta[key] = val
return j
}
// AddDatacenter is used to add a datacenter to a job.
func (j *Job) AddDatacenter(dc string) *Job {
j.Datacenters = append(j.Datacenters, dc)
return j
}
// Constrain is used to add a constraint to a job.
func (j *Job) Constrain(c *Constraint) *Job {
j.Constraints = append(j.Constraints, c)
return j
}
2018-07-16 13:30:58 +00:00
// AddAffinity is used to add an affinity to a job.
func (j *Job) AddAffinity(a *Affinity) *Job {
j.Affinities = append(j.Affinities, a)
return j
}
2015-09-10 00:29:43 +00:00
// AddTaskGroup adds a task group to an existing job.
func (j *Job) AddTaskGroup(grp *TaskGroup) *Job {
j.TaskGroups = append(j.TaskGroups, grp)
return j
}
2016-01-13 18:19:53 +00:00
// AddPeriodicConfig adds a periodic config to an existing job.
func (j *Job) AddPeriodicConfig(cfg *PeriodicConfig) *Job {
j.Periodic = cfg
return j
}
func (j *Job) AddSpread(s *Spread) *Job {
j.Spreads = append(j.Spreads, s)
return j
}
2017-02-06 19:48:28 +00:00
type WriteRequest struct {
// The target region for this write
Region string
2017-08-21 04:39:36 +00:00
2017-09-07 23:56:15 +00:00
// Namespace is the target namespace for this write
Namespace string
2017-08-21 04:39:36 +00:00
// SecretID is the secret ID of an ACL token
SecretID string
2017-02-06 19:48:28 +00:00
}
// JobValidateRequest is used to validate a job
type JobValidateRequest struct {
Job *Job
WriteRequest
}
// JobValidateResponse is the response from validate request
type JobValidateResponse struct {
// DriverConfigValidated indicates whether the agent validated the driver
// config
DriverConfigValidated bool
// ValidationErrors is a list of validation errors
ValidationErrors []string
// Error is a string version of any error that may have occurred
Error string
// Warnings contains any warnings about the given job. These may include
// deprecation warnings.
Warnings string
2017-02-06 19:48:28 +00:00
}
2017-04-18 20:09:24 +00:00
// JobRevertRequest is used to revert a job to a prior version.
type JobRevertRequest struct {
// JobID is the ID of the job being reverted
JobID string
// JobVersion the version to revert to.
JobVersion uint64
// EnforcePriorVersion if set will enforce that the job is at the given
// version before reverting.
EnforcePriorVersion *uint64
// ConsulToken is the Consul token that proves the submitter of the job revert
// has access to the Service Identity policies associated with the job's
// Consul Connect enabled services. This field is only used to transfer the
// token and is not stored after the Job revert.
ConsulToken string `json:",omitempty"`
// VaultToken is the Vault token that proves the submitter of the job revert
// has access to any Vault policies specified in the targeted job version. This
// field is only used to authorize the revert and is not stored after the Job
// revert.
VaultToken string `json:",omitempty"`
2017-04-18 20:09:24 +00:00
WriteRequest
}
2017-02-16 00:56:36 +00:00
// JobUpdateRequest is used to update a job
type JobRegisterRequest struct {
2017-02-16 00:56:36 +00:00
Job *Job
// If EnforceIndex is set then the job will only be registered if the passed
// JobModifyIndex matches the current Jobs index. If the index is zero, the
// register only occurs if the job is new.
EnforceIndex bool
JobModifyIndex uint64
2017-09-19 14:47:10 +00:00
PolicyOverride bool
2017-02-16 00:56:36 +00:00
WriteRequest
}
2016-03-29 22:02:14 +00:00
// RegisterJobRequest is used to serialize a job registration
type RegisterJobRequest struct {
2016-06-08 23:48:02 +00:00
Job *Job
2016-07-28 21:48:25 +00:00
EnforceIndex bool `json:",omitempty"`
JobModifyIndex uint64 `json:",omitempty"`
2017-09-19 14:47:10 +00:00
PolicyOverride bool `json:",omitempty"`
2015-09-08 23:24:26 +00:00
}
2017-04-19 21:57:28 +00:00
// JobRegisterResponse is used to respond to a job registration
type JobRegisterResponse struct {
EvalID string
EvalCreateIndex uint64
JobModifyIndex uint64
// Warnings contains any warnings about the given job. These may include
// deprecation warnings.
Warnings string
2017-04-19 21:57:28 +00:00
QueryMeta
2015-09-08 23:24:26 +00:00
}
2015-09-17 00:12:48 +00:00
2017-04-19 21:57:28 +00:00
// JobDeregisterResponse is used to respond to a job deregistration
type JobDeregisterResponse struct {
EvalID string
EvalCreateIndex uint64
JobModifyIndex uint64
QueryMeta
2015-09-17 00:12:48 +00:00
}
2016-05-12 01:51:48 +00:00
type JobPlanRequest struct {
2017-09-19 14:47:10 +00:00
Job *Job
Diff bool
PolicyOverride bool
WriteRequest
2016-05-12 01:51:48 +00:00
}
type JobPlanResponse struct {
JobModifyIndex uint64
CreatedEvals []*Evaluation
Diff *JobDiff
Annotations *PlanAnnotations
FailedTGAllocs map[string]*AllocationMetric
NextPeriodicLaunch time.Time
// Warnings contains any warnings about the given job. These may include
// deprecation warnings.
Warnings string
2016-05-12 01:51:48 +00:00
}
type JobDiff struct {
Type string
ID string
Fields []*FieldDiff
Objects []*ObjectDiff
TaskGroups []*TaskGroupDiff
}
type TaskGroupDiff struct {
Type string
Name string
Fields []*FieldDiff
Objects []*ObjectDiff
Tasks []*TaskDiff
Updates map[string]uint64
}
type TaskDiff struct {
Type string
Name string
Fields []*FieldDiff
Objects []*ObjectDiff
Annotations []string
}
type FieldDiff struct {
Type string
Name string
Old, New string
Annotations []string
}
type ObjectDiff struct {
Type string
Name string
Fields []*FieldDiff
Objects []*ObjectDiff
}
type PlanAnnotations struct {
DesiredTGUpdates map[string]*DesiredUpdates
PreemptedAllocs []*AllocationListStub
2016-05-12 01:51:48 +00:00
}
type DesiredUpdates struct {
Ignore uint64
Place uint64
Migrate uint64
Stop uint64
InPlaceUpdate uint64
DestructiveUpdate uint64
2017-05-23 20:02:47 +00:00
Canary uint64
2018-11-01 22:05:17 +00:00
Preemptions uint64
2016-05-12 01:51:48 +00:00
}
2016-12-05 05:22:13 +00:00
type JobDispatchRequest struct {
2016-12-14 20:50:08 +00:00
JobID string
Payload []byte
Meta map[string]string
2016-12-05 05:22:13 +00:00
}
type JobDispatchResponse struct {
DispatchedJobID string
EvalID string
EvalCreateIndex uint64
JobCreateIndex uint64
2017-02-16 23:47:36 +00:00
WriteMeta
2016-12-05 05:22:13 +00:00
}
// JobVersionsResponse is used for a job get versions request
type JobVersionsResponse struct {
Versions []*Job
Diffs []*JobDiff
QueryMeta
}
2017-07-06 19:49:13 +00:00
// JobStabilityRequest is used to marked a job as stable.
type JobStabilityRequest struct {
// Job to set the stability on
JobID string
JobVersion uint64
// Set the stability
Stable bool
WriteRequest
}
// JobStabilityResponse is the response when marking a job as stable.
type JobStabilityResponse struct {
JobModifyIndex uint64
WriteMeta
}
// JobEvaluateRequest is used when we just need to re-evaluate a target job
type JobEvaluateRequest struct {
JobID string
EvalOptions EvalOptions
WriteRequest
}
// EvalOptions is used to encapsulate options when forcing a job evaluation
type EvalOptions struct {
ForceReschedule bool
}