-
Notifications
You must be signed in to change notification settings - Fork 2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Enforce idempotency of dispatched jobs using token on dispatch request #10806
Conversation
2b8ecda
to
c4be87a
Compare
46d7bf2
to
b5d21a9
Compare
2a58824
to
f607c35
Compare
api/jobs.go
Outdated
@@ -397,6 +397,22 @@ func (j *Jobs) Dispatch(jobID string, meta map[string]string, | |||
return &resp, wm, nil | |||
} | |||
|
|||
func (j *Jobs) DispatchIdempotent(jobID string, meta map[string]string, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Adding a new method here is good for backwards compatibility, but I'm wondering if in the long term it would be better to add a token to the WriteOptions
struct instead. That would make it possible for us to add idempotency to any kind of write API in the future without adding a bunch of new methods.
Would love to hear thoughts from @DerekStrickland @schmichael @notnoop on that
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like the idea of putting the token on the WriteOptions. If feels like it enables an almost aspect oriented approach for handling this generically at more of a framework level.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added to WriteOptions
!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks @alexmunda !
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm conflicted about having the field in WriteOptions and slightly lean towards how the PR had it before under JobDispatchRequest. Idempotency is such a common concern, so belonging to WriteOptions make sense. Though, it currently only applies to dispatch jobs; other APIs address a similar but not an exact problem using CAS and ModifyIndex (e.g. Update Scheduler Configuration endpoint).
I'm fine with having it in WriteOptions
, as it's probably the pattern we want to use moving forward - but would recommend calling out that only the dispatch endpoint supports it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@notnoop I am open to either approach 😄. With the current WriteOptions
approach, where would the best place be to call that out? Comment on the struct field?
nomad/job_endpoint.go
Outdated
@@ -1908,6 +1909,39 @@ func (j *Job) Dispatch(args *structs.JobDispatchRequest, reply *structs.JobDispa | |||
dispatchJob.Meta[k] = v | |||
} | |||
|
|||
// Check to see if an idempotency token was specified on the request |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The comments on this section are kind of the i++ // increment i by 1
sort. I'd consider adding a comment to the top of this block that says describes the goal instead:
// Ensure that we have only one dispatched version of this job running concurrently
// by comparing the idempotency token against any non-terminal versions.
Co-authored-by: Tim Gross <[email protected]>
…idempotency keys match.
// Return the existing job. | ||
reply.JobCreateIndex = existingJob.CreateIndex | ||
reply.DispatchedJobID = existingJob.ID | ||
reply.Index = existingJob.ModifyIndex |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@notnoop Should reply.Index
be ModifyIndex
or CreateIndex
? I went with ModifyIndex
so the caller can diff the create and modify indexes and tell that an existing job was returned.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think ModifyIndex
is good as well. Thanks!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks great. Just realized that args.IdempotencyToken
needs to be parsed and set in the http handler as well:
nomad/command/agent/job_endpoint.go
Lines 636 to 659 in 60a92ff
func (s *HTTPServer) jobDispatchRequest(resp http.ResponseWriter, req *http.Request, name string) (interface{}, error) { | |
if req.Method != "PUT" && req.Method != "POST" { | |
return nil, CodedError(405, ErrInvalidMethod) | |
} | |
args := structs.JobDispatchRequest{} | |
if err := decodeBody(req, &args); err != nil { | |
return nil, CodedError(400, err.Error()) | |
} | |
if args.JobID != "" && args.JobID != name { | |
return nil, CodedError(400, "Job ID does not match") | |
} | |
if args.JobID == "" { | |
args.JobID = name | |
} | |
s.parseWriteRequest(req, &args.WriteRequest) | |
var out structs.JobDispatchResponse | |
if err := s.agent.RPC("Job.Dispatch", &args, &out); err != nil { | |
return nil, err | |
} | |
setIndex(resp, out.Index) | |
return out, nil | |
} |
It will be nice to update the CLI as well to accept -idempotency-token
argument but we, the nomad team, can follow up with that.
Co-authored-by: Mahmood Ali <[email protected]>
c9de4d5
to
2bd2f58
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM. Thank you so much again!
I'm going to lock this pull request because it has been closed for 120 days ⏳. This helps our maintainers find and focus on the active contributions. |
This PR adds the ability for users to supply an idempotency token when dispatching a parameterized job. When a token is supplied, Nomad will ensure that no existing child jobs contain a matching token. If a child job does have a matching idempotency token, the dispatch will fail.
I believe this will require documentation updates to the job
Dispatch
endpoint.Addresses #10641.