Skip to content
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

Merged
merged 13 commits into from
Jul 8, 2021

Conversation

alexmunda
Copy link
Contributor

@alexmunda alexmunda commented Jun 23, 2021

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.

@alexmunda alexmunda force-pushed the munda/idempotent-job-dispatch branch from 46d7bf2 to b5d21a9 Compare June 29, 2021 15:30
@vercel vercel bot temporarily deployed to Preview – nomad June 29, 2021 15:30 Inactive
@alexmunda alexmunda marked this pull request as ready for review June 29, 2021 15:35
@vercel vercel bot temporarily deployed to Preview – nomad June 29, 2021 20:52 Inactive
@alexmunda alexmunda changed the title Enforce idempotency of dispatched jobs using special meta key Enforce idempotency of dispatched jobs using supplied token Jun 29, 2021
@alexmunda alexmunda force-pushed the munda/idempotent-job-dispatch branch from 2a58824 to f607c35 Compare June 29, 2021 20:59
@vercel vercel bot temporarily deployed to Preview – nomad June 29, 2021 20:59 Inactive
@alexmunda alexmunda requested a review from tgross June 29, 2021 21:47
@alexmunda alexmunda changed the title Enforce idempotency of dispatched jobs using supplied token Enforce idempotency of dispatched jobs using token on dispatch request Jun 30, 2021
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,
Copy link
Member

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

Copy link
Contributor

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.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added to WriteOptions!

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @alexmunda !

Copy link
Contributor

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.

Copy link
Contributor Author

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?

@@ -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
Copy link
Member

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.

nomad/job_endpoint.go Outdated Show resolved Hide resolved
@vercel vercel bot temporarily deployed to Preview – nomad July 2, 2021 15:58 Inactive
// Return the existing job.
reply.JobCreateIndex = existingJob.CreateIndex
reply.DispatchedJobID = existingJob.ID
reply.Index = existingJob.ModifyIndex
Copy link
Contributor Author

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.

Copy link
Contributor

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!

@alexmunda alexmunda requested a review from notnoop July 7, 2021 20:41
Copy link
Contributor

@notnoop notnoop left a 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:

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.

nomad/job_endpoint.go Outdated Show resolved Hide resolved
Copy link
Contributor

@notnoop notnoop left a 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!

@notnoop notnoop merged commit 0f0bcdb into main Jul 8, 2021
@notnoop notnoop deleted the munda/idempotent-job-dispatch branch July 8, 2021 14:23
@notnoop notnoop mentioned this pull request Jul 28, 2021
@github-actions
Copy link

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.
If you have found a problem that seems related to this change, please open a new issue and complete the issue template so we can capture all the details necessary to investigate further.

@github-actions github-actions bot locked as resolved and limited conversation to collaborators Nov 18, 2022
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
theme/batch Issues related to batch jobs and scheduling type/enhancement
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants