Skip to content

Commit

Permalink
add dispatch idempotency token support in the CLI
Browse files Browse the repository at this point in the history
  • Loading branch information
lgfa29 committed Jul 22, 2021
1 parent 36d2f9f commit a2ccc88
Show file tree
Hide file tree
Showing 6 changed files with 138 additions and 75 deletions.
66 changes: 34 additions & 32 deletions api/jobs.go
Original file line number Diff line number Diff line change
Expand Up @@ -813,21 +813,22 @@ type Job struct {

/* Fields set by server, not sourced from job config file */

Stop *bool
ParentID *string
Dispatched bool
Payload []byte
ConsulNamespace *string `mapstructure:"consul_namespace"`
VaultNamespace *string `mapstructure:"vault_namespace"`
NomadTokenID *string `mapstructure:"nomad_token_id"`
Status *string
StatusDescription *string
Stable *bool
Version *uint64
SubmitTime *int64
CreateIndex *uint64
ModifyIndex *uint64
JobModifyIndex *uint64
Stop *bool
ParentID *string
Dispatched bool
DispatchIdempotencyToken *string
Payload []byte
ConsulNamespace *string `mapstructure:"consul_namespace"`
VaultNamespace *string `mapstructure:"vault_namespace"`
NomadTokenID *string `mapstructure:"nomad_token_id"`
Status *string
StatusDescription *string
Stable *bool
Version *uint64
SubmitTime *int64
CreateIndex *uint64
ModifyIndex *uint64
JobModifyIndex *uint64
}

// IsPeriodic returns whether a job is periodic.
Expand Down Expand Up @@ -987,23 +988,24 @@ type TaskGroupSummary struct {
// JobListStub is used to return a subset of information about
// jobs during list operations.
type JobListStub struct {
ID string
ParentID string
Name string
Namespace string `json:",omitempty"`
Datacenters []string
Type string
Priority int
Periodic bool
ParameterizedJob bool
Stop bool
Status string
StatusDescription string
JobSummary *JobSummary
CreateIndex uint64
ModifyIndex uint64
JobModifyIndex uint64
SubmitTime int64
ID string
ParentID string
Name string
Namespace string `json:",omitempty"`
Datacenters []string
Type string
Priority int
Periodic bool
ParameterizedJob bool
DispatchIdempotencyToken string
Stop bool
Status string
StatusDescription string
JobSummary *JobSummary
CreateIndex uint64
ModifyIndex uint64
JobModifyIndex uint64
SubmitTime int64
}

// JobIDSort is used to sort jobs by their job ID's.
Expand Down
29 changes: 25 additions & 4 deletions command/job_dispatch.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"os"
"strings"

"github.com/hashicorp/nomad/api"
"github.com/hashicorp/nomad/api/contexts"
flaghelper "github.com/hashicorp/nomad/helper/flags"
"github.com/posener/complete"
Expand All @@ -24,6 +25,10 @@ Usage: nomad job dispatch [options] <parameterized job> [input source]
path to a file. Metadata can be supplied by using the meta flag one or more
times.
An optional idempotency token can be used to prevent more than one instance
of the job to be dispatched. If an instance with the same token already
exists, the command returns without any action.
Upon successful creation, the dispatched job ID will be printed and the
triggered evaluation will be monitored. This can be disabled by supplying the
detach flag.
Expand All @@ -49,6 +54,10 @@ Dispatch Options:
the evaluation ID will be printed to the screen, which can be used to
examine the evaluation using the eval-status command.
-idempotency-token
Optional identifier used to prevent more than one instance of the job from
being dispatched.
-verbose
Display full information.
`
Expand All @@ -62,9 +71,10 @@ func (c *JobDispatchCommand) Synopsis() string {
func (c *JobDispatchCommand) AutocompleteFlags() complete.Flags {
return mergeAutocompleteFlags(c.Meta.AutocompleteFlags(FlagSetClient),
complete.Flags{
"-meta": complete.PredictAnything,
"-detach": complete.PredictNothing,
"-verbose": complete.PredictNothing,
"-meta": complete.PredictAnything,
"-detach": complete.PredictNothing,
"-idempotency-token": complete.PredictAnything,
"-verbose": complete.PredictNothing,
})
}

Expand All @@ -87,12 +97,14 @@ func (c *JobDispatchCommand) Name() string { return "job dispatch" }

func (c *JobDispatchCommand) Run(args []string) int {
var detach, verbose bool
var idempotencyToken string
var meta []string

flags := c.Meta.FlagSet(c.Name(), FlagSetClient)
flags.Usage = func() { c.Ui.Output(c.Help()) }
flags.BoolVar(&detach, "detach", false, "")
flags.BoolVar(&verbose, "verbose", false, "")
flags.StringVar(&idempotencyToken, "idempotency-token", "", "")
flags.Var((*flaghelper.StringFlag)(&meta), "meta", "")

if err := flags.Parse(args); err != nil {
Expand Down Expand Up @@ -151,7 +163,10 @@ func (c *JobDispatchCommand) Run(args []string) int {
}

// Dispatch the job
resp, _, err := client.Jobs().Dispatch(job, metaMap, payload, nil)
w := &api.WriteOptions{
IdempotencyToken: idempotencyToken,
}
resp, _, err := client.Jobs().Dispatch(job, metaMap, payload, w)
if err != nil {
c.Ui.Error(fmt.Sprintf("Failed to dispatch job: %s", err))
return 1
Expand All @@ -161,6 +176,12 @@ func (c *JobDispatchCommand) Run(args []string) int {
// eval.
evalCreated := resp.EvalID != ""

// See if dispatched job was skipped due to idempotency.
if !evalCreated && idempotencyToken != "" {
c.Ui.Output(fmt.Sprintf("Job %q already dispatched with idempotency token %q.", resp.DispatchedJobID, idempotencyToken))
return 0
}

basic := []string{
fmt.Sprintf("Dispatched Job ID|%s", resp.DispatchedJobID),
}
Expand Down
11 changes: 8 additions & 3 deletions command/job_status.go
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,10 @@ func (c *JobStatusCommand) Run(args []string) int {
fmt.Sprintf("Parameterized|%v", parameterized),
}

if job.DispatchIdempotencyToken != nil && *job.DispatchIdempotencyToken != "" {
basic = append(basic, fmt.Sprintf("Idempotency Token|%v", *job.DispatchIdempotencyToken))
}

if periodic && !parameterized {
if *job.Stop {
basic = append(basic, "Next Periodic Launch|none (job stopped)")
Expand Down Expand Up @@ -306,17 +310,18 @@ func (c *JobStatusCommand) outputParameterizedInfo(client *api.Client, job *api.
}

out := make([]string, 1)
out[0] = "ID|Status"
out[0] = "ID|Status|Idempotency Token"
for _, child := range children {
// Ensure that we are only showing jobs whose parent is the requested
// job.
if child.ParentID != *job.ID {
continue
}

out = append(out, fmt.Sprintf("%s|%s",
out = append(out, fmt.Sprintf("%s|%s|%s",
child.ID,
child.Status))
child.Status,
child.DispatchIdempotencyToken))
}

c.Ui.Output(c.Colorize().Color("\n[bold]Dispatched Jobs[reset]"))
Expand Down
74 changes: 38 additions & 36 deletions nomad/structs/structs.go
Original file line number Diff line number Diff line change
Expand Up @@ -4388,24 +4388,25 @@ func (j *Job) HasUpdateStrategy() bool {
// Stub is used to return a summary of the job
func (j *Job) Stub(summary *JobSummary) *JobListStub {
return &JobListStub{
ID: j.ID,
Namespace: j.Namespace,
ParentID: j.ParentID,
Name: j.Name,
Datacenters: j.Datacenters,
Multiregion: j.Multiregion,
Type: j.Type,
Priority: j.Priority,
Periodic: j.IsPeriodic(),
ParameterizedJob: j.IsParameterized(),
Stop: j.Stop,
Status: j.Status,
StatusDescription: j.StatusDescription,
CreateIndex: j.CreateIndex,
ModifyIndex: j.ModifyIndex,
JobModifyIndex: j.JobModifyIndex,
SubmitTime: j.SubmitTime,
JobSummary: summary,
ID: j.ID,
Namespace: j.Namespace,
ParentID: j.ParentID,
Name: j.Name,
Datacenters: j.Datacenters,
Multiregion: j.Multiregion,
Type: j.Type,
Priority: j.Priority,
Periodic: j.IsPeriodic(),
ParameterizedJob: j.IsParameterized(),
DispatchIdempotencyToken: j.DispatchIdempotencyToken,
Stop: j.Stop,
Status: j.Status,
StatusDescription: j.StatusDescription,
CreateIndex: j.CreateIndex,
ModifyIndex: j.ModifyIndex,
JobModifyIndex: j.JobModifyIndex,
SubmitTime: j.SubmitTime,
JobSummary: summary,
}
}

Expand Down Expand Up @@ -4560,24 +4561,25 @@ func (j *Job) SetSubmitTime() {
// JobListStub is used to return a subset of job information
// for the job list
type JobListStub struct {
ID string
ParentID string
Name string
Namespace string `json:",omitempty"`
Datacenters []string
Multiregion *Multiregion
Type string
Priority int
Periodic bool
ParameterizedJob bool
Stop bool
Status string
StatusDescription string
JobSummary *JobSummary
CreateIndex uint64
ModifyIndex uint64
JobModifyIndex uint64
SubmitTime int64
ID string
ParentID string
Name string
Namespace string `json:",omitempty"`
Datacenters []string
Multiregion *Multiregion
Type string
Priority int
Periodic bool
ParameterizedJob bool
DispatchIdempotencyToken string
Stop bool
Status string
StatusDescription string
JobSummary *JobSummary
CreateIndex uint64
ModifyIndex uint64
JobModifyIndex uint64
SubmitTime int64
}

// JobSummary summarizes the state of the allocations of a job
Expand Down
4 changes: 4 additions & 0 deletions website/content/api-docs/jobs.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -1617,6 +1617,10 @@ The table below shows this endpoint's support for
- `:job_id` `(string: <required>)` - Specifies the ID of the job (as specified
in the job file during submission). This is specified as part of the path.

- `idempotency_token` `(string: "")` - Optional identifier used to prevent more
than one instance of the job from being dispatched. This is specified as a
URL query parameter.

- `Payload` `(string: "")` - Specifies a base64 encoded string containing the
payload. This is limited to 16384 bytes (16KiB).

Expand Down
29 changes: 29 additions & 0 deletions website/content/docs/commands/job/dispatch.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,11 @@ flag one or more times.

The payload has a **size limit of 16384 bytes (16KiB)**.

An optional idempotency token can be specified to prevent dispatching more than
one instance of the same job. The token can have any value and will be matched
with existing jobs. If an instance with the same token already exists, the job
will not be dispatched.

Upon successful creation, the dispatched job ID will be printed and the
triggered evaluation will be monitored. This can be disabled by supplying the
detach flag.
Expand Down Expand Up @@ -58,6 +63,9 @@ capability for the job's namespace.
will be output, which can be used to examine the evaluation using the
[eval status] command

- `-idempotency-token`: Optional identifier used to prevent more than one
instance of the job from being dispatched.

- `-verbose`: Show full information.

## Examples
Expand Down Expand Up @@ -109,5 +117,26 @@ Dispatched Job ID = example/dispatch-1485380684-c37b3dba
Evaluation ID = d9034c4e
```

Dispatch with an idempotency token for the first time:

```shell-session
$ nomad job dispatch -idempotency-token=prod video-encode video-config.json
Dispatched Job ID = video-encode/dispatch-1485379325-cb38d00d
Evaluation ID = 31199841
==> Monitoring evaluation "31199841"
Evaluation triggered by job "example/dispatch-1485379325-cb38d00d"
Allocation "8254b85f" created: node "82ff9c50", group "cache"
Evaluation status changed: "pending" -> "complete"
==> Evaluation "31199841" finished with status "complete"
```

Dispatch with the same idempotency token:

```shell-session
$ nomad job dispatch -idempotency-token=prod video-encode video-config.json
Job "video-encode/dispatch-1485379325-cb38d00d" already dispatched with idempotency token "prod".
```

[eval status]: /docs/commands/eval-status
[parameterized job]: /docs/job-specification/parameterized 'Nomad parameterized Job Specification'

0 comments on commit a2ccc88

Please sign in to comment.