-
Notifications
You must be signed in to change notification settings - Fork 1.2k
✨ Add Patch MergeFrom optimistic locking option #969
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
✨ Add Patch MergeFrom optimistic locking option #969
Conversation
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.
@vincepri thanks for this change, I'm looking forward to getting this merged!
only a minor nit from my side, but not blocking
5994e61
to
f9ce06d
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.
Just some lil nits
@@ -1209,6 +1209,42 @@ var _ = Describe("Client", func() { | |||
close(done) | |||
}) | |||
|
|||
It("should patch an existing object from a go struct, using optimistic locking", func(done Done) { |
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.
Maybe out of the scope of this PR but why are all these tests async? I thought this was only useful if you were testing asynchronous code
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 not sure, what do you mean?
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.
That they accept the done Done
parameter, it's an optional feature of Ginkgo It
: https://onsi.github.io/ginkgo/#asynchronous-tests
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.
Not 100% sure, I guess they can all run in parallel
@vincepri Can you change the PR to target |
@alvaroaleman Yeah I can rework the branch, unfortunately when I worked on the patch I was already on release-0.5 and forgot to change the base branch :/ |
if !ok { | ||
return nil, fmt.Errorf("cannot use OptimisticLock, from object %q is not a valid metav1.Object", s.from) | ||
} | ||
resourceVersion := fromMeta.GetResourceVersion() |
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.
How does patch with a RV precondition differ from just doing Update
?
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.
That's a good question, I think when it comes down to patch is easier to merge only the fields a client has updated. It does keep the scope down to only things that have changes, so it's still possible to retry to patch only things necessary.
This particular use case came up from Cluster API conditions work, where we need to make sure that when patching an object we aren't overwriting other controllers. Adding resource version on patch keeps the scope down to the patch and we can still try to do a best-effort merge.
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.
But with this precondition, if anything on the object was changed its RV gets increased and this patch fails. Hence, it has the exact same semantics as UPDATE
unless I am missing something.
It seems to me a bit that what you actually want is a precondition on the .status.conditions
and not the RV?
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.
Yeah, it's the same update semantic, although in our case it'll allow us to only focus on a single field and try merging that field without bothering about the rest
@@ -59,8 +63,29 @@ func ConstantPatch(patchType types.PatchType, data []byte) Patch { | |||
return RawPatch(patchType, data) | |||
} | |||
|
|||
type MergeFromWithOptimisticLock struct{} |
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 had to read the code to understand what you meant by OptimisticLock
. This definitely needs godocs.
I also don't understand how that differs from Update
except that you put less data on the wire and trade it for a local computation of the patch which seems like an implementation detail to me that should rarely if ever be relevant.
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.
+1 I'll add a godoc
Yeah but how is this different from just doing `Update(newObject)`?
I guess I don't properly understand the use case. Do you have an example
somewhere where this does what you want and Update does not?
…On Thu, May 28, 2020 at 12:06 PM Vince Prignano ***@***.***> wrote:
***@***.**** commented on this pull request.
------------------------------
In pkg/client/patch.go
<#969 (comment)>
:
> - return jsonpatch.CreateMergePatch(originalJSON, modifiedJSON)
+ data, err := jsonpatch.CreateMergePatch(originalJSON, modifiedJSON)
+ if err != nil {
+ return nil, err
+ }
+
+ if s.opts.OptimisticLock {
+ dataMap := map[string]interface{}{}
+ if err := json.Unmarshal(data, &dataMap); err != nil {
+ return nil, err
+ }
+ fromMeta, ok := s.from.(metav1.Object)
+ if !ok {
+ return nil, fmt.Errorf("cannot use OptimisticLock, from object %q is not a valid metav1.Object", s.from)
+ }
+ resourceVersion := fromMeta.GetResourceVersion()
Yeah, it's the same update semantic, although in our case it'll allow us
to only focus on a single field and try merging that field without
bothering about the rest
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
<#969 (comment)>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/ABRR6ZHBFZKKETJ66P2E2E3RT2DYXANCNFSM4NMJF4CA>
.
|
It's not different in terms of functionality, apart from the need to set the whole spec/status vs only a single field, although we're trying to integrate this logic in https://github.com/kubernetes-sigs/cluster-api/blob/master/util/patch/patch.go#L84 and it'd be a little strange to issue Update within a Patch helper |
@alvaroaleman Do you see this as something that you wouldn't want exposed in controller runtime? I guess we can bring this in Cluster API itself if it wouldn't be useful for others as well. Although, given that the functionality of using resource version with Patch is there, and kubectl also allows it, I thought it'd be good to expose it for users that want to go down this path. |
Well to my understanding the semantic difference between My objection here is that its not clear to me how this differs from an |
There is a difference between update and patch that I believe justifies this PR. Let's say you add a new field to an object without changing the API version. And then you populate that field. If you have a client that is using an older copy of the code that doesn't have this new field, when it sends an update (e.g. maybe you were setting an annotation), it won't have the new field included. Because update is a wholesale replace, the contents of that new field are now lost. If you use patch, you can target just the changes you want to make, and it won't result in fields that aren't present in the older copy of the go structs being dropped. Adding support for optional optimistic currency control in this patch seems reasonable to me because of this short-coming that update has. |
okay, that makes sense. Can we add this information to the godoc ("like update but avoids dropping fields the apiserver has but that aren't present in your go types")? |
4b04fc3
to
6ec57bf
Compare
@alvaroaleman @ncdc added godoc |
/retest |
6ec57bf
to
62b2f0c
Compare
Signed-off-by: Vince Prignano <[email protected]>
62b2f0c
to
8c6919c
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.
Thanks for your work and the explanations!
@alvaroaleman good to merge? |
Oh yeah sorry, I am very used to "github appove adds lgtm and approve" /lgtm |
[APPROVALNOTIFIER] This PR is APPROVED This pull-request has been approved by: alvaroaleman, vincepri The full list of commands accepted by this bot can be found here. The pull request process is described here
Needs approval from an approver in each of these files:
Approvers can indicate their approval by writing |
Signed-off-by: Vince Prignano [email protected]