-
Notifications
You must be signed in to change notification settings - Fork 40.9k
Added missing 'time' for a field manager that server-side-applied same configuration #127939
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
base: master
Are you sure you want to change the base?
Conversation
Welcome @waltforme! |
Hi @waltforme. Thanks for your PR. I'm waiting for a kubernetes member to verify that this patch is reasonable to test. If it is, they should reply with Once the patch is verified, the new status will be reflected by the I understand the commands that are listed here. Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes-sigs/prow repository. |
if object != nil { | ||
managed.Times()[fieldManager] = &metav1.Time{Time: time.Now().UTC()} | ||
} else { | ||
if managerInFields && !managerInTimes { |
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 very familiar with this code, but it looks strange to put this logic in the branch that is calling RemoveObjectManagedFields
... are we sure this is the branch where a new manager is adding co-ownership of some fields?
this definitely needs really good unit tests around the scenario being exercised
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 the prompt review!
I have the same feeling that the two branches are a little confusing to follow. Let me try my best to clarify.
- Since
managedFieldsUpdater
implements theManager
interface, its method(f *managedFieldsUpdater) Apply
should return ‘the new object with managedFields removed, and the object's new proposed managedFields separately’:
kubernetes/staging/src/k8s.io/apimachinery/pkg/util/managedfields/internal/manager.go
Lines 45 to 51 in 7ee17ce
// Apply is used when server-side apply is called, as it merges the // object and updates the managed fields. // * `liveObj` is not mutated by this function // * `newObj` may be mutated by this function // Returns the new object with managedFields removed, and the object's new // proposed managedFields separately. Apply(liveObj, appliedObj runtime.Object, managed Managed, fieldManager string, force bool) (runtime.Object, Managed, error) - The
else
branch says: Since the live object is not touched (signaled byobject == nil
, detailed below), the merged object should be the same as the live object. So first make a deep copy ofliveObj
, then remove its managedFields to fit theManager
interface.
Details on object == nil
:
These lines say that nil
signals the situation that a merged object and the current live object being the same.
kubernetes/vendor/sigs.k8s.io/structured-merge-diff/v4/merge/update.go
Lines 209 to 211 in 7ee17ce
if !s.returnInputOnNoop && value.EqualsUsing(value.NewFreelistAllocator(), liveObject.AsValue(), newObject.AsValue()) { | |
newObject = nil | |
} |
In our use case, the 2nd field manager applies the same configuration as the 1st, so the merged object is indeed the same as the current live object, so a
nil
is indeed returned (and assigned to object
) to signal that.
BTW I believe there was a typo in the go doc comments of the Manager
interface’s Apply
method. Latest push fixed the typo.
Fully agree that we need unit tests here. Will work 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.
ok... I see object comes back as nil for no-ops
kubernetes/staging/src/k8s.io/apimachinery/pkg/util/managedfields/internal/structuredmerge.go
Lines 55 to 58 in 8770bd5
updater: merge.Updater{ | |
Converter: newVersionConverter(typeConverter, objectConverter, hub), // This is the converter provided to SMD from k8s | |
IgnoreFilter: resetFields, | |
}, |
constructs this:
// Updater is the object used to compute updated FieldSets and also | |
// merge the object on Apply. | |
type Updater struct { | |
// Deprecated: This will eventually become private. | |
Converter Converter | |
// Deprecated: This will eventually become private. | |
IgnoreFilter map[fieldpath.APIVersion]fieldpath.Filter | |
returnInputOnNoop bool | |
} |
leaving returnInputOnNoop
set to false
, so an apply that doesn't change anything returns nil for the object:
kubernetes/vendor/sigs.k8s.io/structured-merge-diff/v4/merge/update.go
Lines 205 to 208 in 8770bd5
if !s.returnInputOnNoop && value.EqualsUsing(value.NewFreelistAllocator(), liveObject.AsValue(), newObject.AsValue()) { | |
newObject = nil | |
} | |
return newObject, managers, nil |
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 the logic in this PR is correct, but needs commenting to explain.
I'd suggest:
if object != nil {
// non-nil object means the apply operation modified the object, so update the manager's timestamp
managed.Times()[fieldManager] = &metav1.Time{Time: time.Now().UTC()}
} else {
// nil object means the apply operation did not modify the input object
// clone the input object and return it without managed fields
object = liveObj.DeepCopyObject()
RemoveObjectManagedFields(object)
if _, managerInFields := managed.Fields()[fieldManager]; managerInFields {
if _, managerInTimes := managed.Times()[fieldManager]; !managerInTimes {
// if the manager owns fields, ensure it has an associated time.
// a no-op apply can add co-ownership of existing field values, so record the time that occurred.
managed.Times()[fieldManager] = &metav1.Time{Time: time.Now().UTC()}
}
}
}
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 the suggestions! With the comments, we should save some time for the future visitors. I included them in latest push.
Would this PR need a changelog entry? |
@sftim: how much would be appropriate to put in the change log entry? Just "Fixed Issue 127938", or a description of the behavior change? |
Try something like: Fixed an issue in the frobnicator. Before this fix, the `time` field was not set correctly if your
time machine was travelling below 141 kilometers per hour. |
@sftim Added. Thanks for the nice example! |
As suggested by @liggitt, the latest push added unit test. The added test
Latest push also made a few small corrections to the previously existing unit tests. |
@@ -335,7 +335,7 @@ func TestTakingOverManagedFieldsDuringUpdateDoesNotModifyPreviousManagerTime(t * | |||
}, | |||
"data": { | |||
"key_a": "value", | |||
"key_b": value" | |||
"key_b": "value" |
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.
huh... these pre-existing test typos are distressing... did you just notice these and fix them while you were here, or did fixing these reveal that these tests were broken?
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.
non-blocking for this PR, but cc @jpbetz for visibility to how accepting yaml parsing is of malformed json input sent as yaml. This was parsing as:
{
"apiVersion": "v1",
"data": {
"key_a": "value",
"key_b": "value\""
},
"kind": "ConfigMap",
"metadata": {
"name": "configmap"
}
}
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 former, I just noticed these so fixed them. I was wondering why these typos didn't break the testing, and now I get the answer from your explanations above. Thanks!
edecfd7
to
6d5c802
Compare
Signed-off-by: Jun Duan <[email protected]>
Signed-off-by: Jun Duan <[email protected]>
Signed-off-by: Jun Duan <[email protected]>
6d5c802
to
ce32cdc
Compare
[APPROVALNOTIFIER] This PR is NOT APPROVED This pull-request has been approved by: waltforme The full list of commands accepted by this bot can be found here.
Needs approval from an approver in each of these files:
Approvers can indicate their approval by writing |
The force-push to ce32cdc included the suggested changes from @liggitt, also squashed those changes to appropriate commit. I messed up one unrelated commit in the previous force-push to 6d5c802, and the bot added |
/remove-area kubelet |
/remove-sig node |
/ok-to-test |
Thanks @MikeSpreitzer ! |
object = liveObj.DeepCopyObject() | ||
RemoveObjectManagedFields(object) | ||
|
||
if _, managerInFields := managed.Fields()[fieldManager]; managerInFields { |
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.
@jpbetz how does partitioning of field manager among subresources work? does managed.Fields() only return entries relevant to the subresource for this 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 field manager relies on the GetResetFields()
(example) of the subresource to indicate which fields the subresource applies to.
'GetResetFieldsFilter()' was also added a in 1.32 to make it easier to declare the fields (example) for cases where the exclude set of GetResetFields()
is not expressive enough.
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 specific test I'm thinking of is:
- manager
foo
writes to pod, gets a managedFields entry for itself - manager
bar
writes to pods/status, gets a managedFields entry for itself under the status subresource - manager
bar
does no-op write to pod (e.g. empty patch{}
) so that it doesn't actually own any fields
Will the change in this PR make processing of step 3 see and get confused by the managed fields entry from 2?
if _, managerInTimes := managed.Times()[fieldManager]; !managerInTimes { | ||
// if the manager owns fields, ensure it has an associated time. | ||
// a no-op apply can add co-ownership of existing field values, so record the time that occurred. | ||
managed.Times()[fieldManager] = &metav1.Time{Time: time.Now().UTC()} |
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.
@jpbetz for existing managed entries that got created without timestamps, once this bug fix releases, the next no-op update will set the time to now
on a pre-existing managed field entry. That's a little weird, and probably ok, but I wanted to call it out explicitly as a side-effect of the fix.
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.
We fixed a few spurious resourceVersion bumps issues this year:
- Fix non-semantic apply requests to ignore empty maps #125317
- Fix check to ignore non-semantic changes to objects to handle unstructured #125263
So I'm keeping an eye out of anything that changes an apply request before the object is compared with the stored resource to decide if a write is needed. But this happens after fieldmanager.Apply()
so appears safe, at least from that perspective.
I believe there is a case that is not covered in this PR. If a manager performs a server-side apply (SSA) with a configuration that does not change the object, and then later performs another SSA with a different configuration that also does not change the object (but changes the list of field ownership for this manager), the Here's an example:
|
The Kubernetes project currently lacks enough contributors to adequately respond to all PRs. This bot triages PRs according to the following rules:
You can:
Please send feedback to sig-contributor-experience at kubernetes/community. /lifecycle stale |
What type of PR is this?
/kind bug
What this PR does / why we need it:
The 'time' stanza in
metadata.managedFields
is missing under some circumstances after a server-side apply. This PR tries to get the missing 'time' stanza back. More details are documented in #127938.Which issue(s) this PR fixes:
Fixes #127938
Special notes for your reviewer:
Does this PR introduce a user-facing change?
Additional documentation e.g., KEPs (Kubernetes Enhancement Proposals), usage docs, etc.: