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

Let crd support strategic merge patch on metadata #56606

Closed

Conversation

caesarxuchao
Copy link
Member

@caesarxuchao caesarxuchao commented Nov 30, 2017

Fix #56348

cc @liggitt @nikhita @enisoc @mengqiy @ironcladlou

The size of this fix is +13/1- (e6d7af1). Comparing it with converting the garbage collector to user JSON merge patch(#56595 +125/−20), I prefer this fix. I'll add unit tests if we can reach consensus.

The major criticism on this approach was that we shouldn't support strategic merge patch for CR partially. I'd like to defend this approach once more:

  • From the perspective of the evolution of CRD, supporting strategic merge patch is a goal. And no matter how we achieve that goal, the schema of ObjectMeta should be defined by the APIServer, like what's done in this PR. So I regard this PR as a small step towards a larger goal, rather than a one-time hack.
  • From the perspective of API consistency , ObjectMeta should be homogeneous across all resources stored on apiservers, including the supported operations on ObjectMeta.
  • From users' perspective, they will receive a clear error message if they send a strategic merge patch for fields other than the ObjectMeta.

Another point raised by @ironcladlou was that we should "promote the use of one of the IETF PATCH standards" (#56348 (comment)). My perspective is that the strategic merge patch is the only patch type that can safely patch list in a singe roundtrip, it should be supported by all resources. Currently only CRD doesn't support it.

Custom resources now accept strategic merge patch against its `metadata` field. The patch will be rejected if it contains fields other than `metadata`.

@caesarxuchao caesarxuchao self-assigned this Nov 30, 2017
@k8s-ci-robot k8s-ci-robot added size/L Denotes a PR that changes 100-499 lines, ignoring generated files. do-not-merge/release-note-label-needed Indicates that a PR should not merge because it's missing one of the release note labels. cncf-cla: yes Indicates the PR's author has signed the CNCF CLA. labels Nov 30, 2017
@caesarxuchao caesarxuchao changed the title Let crd support strategic merge patch on metadata [WIP] Let crd support strategic merge patch on metadata Nov 30, 2017
@k8s-ci-robot k8s-ci-robot added the do-not-merge/work-in-progress Indicates that a PR should not merge because it is a work in progress. label Nov 30, 2017
@@ -439,6 +439,9 @@ func (UnstructuredObjectConverter) Convert(in, out, context interface{}) error {
func (UnstructuredObjectConverter) ConvertToVersion(in runtime.Object, target runtime.GroupVersioner) (runtime.Object, error) {
if kind := in.GetObjectKind().GroupVersionKind(); !kind.Empty() {
gvk, ok := target.KindForGroupVersionKinds([]schema.GroupVersionKind{kind})
if gvk.GroupVersion().Version == runtime.APIVersionInternal {
Copy link
Member

Choose a reason for hiding this comment

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

@liggitt
Copy link
Member

liggitt commented Nov 30, 2017

cc @kubernetes/sig-api-machinery-pr-reviews

@k8s-ci-robot k8s-ci-robot added the sig/api-machinery Categorizes an issue or PR as relevant to SIG API Machinery. label Nov 30, 2017
@sttts sttts assigned nikhita and unassigned nikhita Nov 30, 2017
@@ -220,7 +220,7 @@ KUBELET_HOST=${KUBELET_HOST:-"127.0.0.1"}
# By default only allow CORS for requests on localhost
API_CORS_ALLOWED_ORIGINS=${API_CORS_ALLOWED_ORIGINS:-/127.0.0.1(:[0-9]+)?$,/localhost(:[0-9]+)?$}
KUBELET_PORT=${KUBELET_PORT:-10250}
LOG_LEVEL=${LOG_LEVEL:-3}
LOG_LEVEL=${LOG_LEVEL:-5}
Copy link
Contributor

Choose a reason for hiding this comment

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

debug change

@@ -866,7 +871,11 @@ func StrategicMergeMapPatch(original, patch JSONMap, dataStruct interface{}) (JS
// each field in a strategic merge patch, we can't find the go struct tags. Hence, we can't easily do a strategic merge
// for custom resources. So we should fail fast and return an error.
if _, ok := dataStruct.(*unstructured.Unstructured); ok {
return nil, mergepatch.ErrUnsupportedStrategicMergePatchFormat
schema, err = NewPatchMetaFromStruct(MetaOnly{})
Copy link
Contributor

Choose a reason for hiding this comment

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

What does this mean for patches again fields outside of meta? Will they apply like to a native type without any tags?

Copy link
Contributor

Choose a reason for hiding this comment

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

If yes, I would like to see a test case. If not, it must be rejected.

Copy link
Member

Choose a reason for hiding this comment

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

only supporting a patch type on part of an object is bizarre to me

@liggitt
Copy link
Member

liggitt commented Nov 30, 2017

My perspective is that the strategic merge patch is the only patch type that can safely patch list in a singe roundtrip, it should be supported by all resources. Currently only CRD doesn't support it.

Is our position that all aggregated API servers must support strategic merge patch? I don't see a compelling reason to require that to participate in GC. What was the measured impact of simply doing a JSON merge patch on the expected field with the resourceVersion?

From the perspective of the evolution of CRD, supporting strategic merge patch is a goal.

I'm not sure I agree. Experience with strategic merge patch from apply has not been good. Why do we want to expand it?

@@ -866,7 +871,14 @@ func StrategicMergeMapPatch(original, patch JSONMap, dataStruct interface{}) (JS
// each field in a strategic merge patch, we can't find the go struct tags. Hence, we can't easily do a strategic merge
// for custom resources. So we should fail fast and return an error.
if _, ok := dataStruct.(*unstructured.Unstructured); ok {
return nil, mergepatch.ErrUnsupportedStrategicMergePatchFormat
if _, ok := patch["metadata"]; !ok || len(patch) != 1 {
Copy link
Member Author

Choose a reason for hiding this comment

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

@sttts I forgot to push this check. This will prevent strategic merge patch outside of "metadata".

Choose a reason for hiding this comment

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

is an empty patch ever allowed?

Copy link
Member

Choose a reason for hiding this comment

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

@jennybuckley An empty strategic merge patch on custom resources is not allowed (It wasn't allowed before as well).

@lavalamp
Copy link
Member

cc @lavalamp

@lavalamp
Copy link
Member

cc @jennybuckley

@lavalamp
Copy link
Member

Is our position that all aggregated API servers must support strategic merge patch?

Not thus far.

I don't see a compelling reason to require that to participate in GC.

I tend to agree, it seems good to keep the bar low for participating.

@enisoc
Copy link
Member

enisoc commented Nov 30, 2017

@sttts wrote:

only supporting a patch type on part of an object is bizarre to me

In my opinion it's weird that we don't already support strategic merge patch on metadata inside CRs. We have all the tags we need via ObjectMeta, so why forbid it? As evidenced by this use case for GC, it's a useful thing to be able to do; I would say we just didn't get around to implementing it yet. Apparently the authors and reviewers of GC support for CRs also made the (IMO) reasonable assumption that patching metadata should work the same for any object that has ObjectMeta (which we enforce for everything made by CRD).

@liggitt wrote:

Is our position that all aggregated API servers must support strategic merge patch?

We aren't necessarily taking that position by doing this. We're already in the state where GC doesn't work for anything that doesn't support SMP. Our goal right now is fixing GC specifically for CRs hosted through CRD. Broadening GC support to other things that don't support SMP is beyond the scope of this bugfix.

On a side note, I would expect SMP to come "free" for any aggregated API server that uses our libraries and uses compiled-in Go types. Is that not the case?

@liggitt wrote:

What was the measured impact of simply doing a JSON merge patch on the expected field with the resourceVersion?

We haven't measured it yet because Chao's PR (#56595) shows there is already significant impact on the core architecture of the GC. Currently everything runs off of a dependency graph that only changes when object relationships change (not when arbitrary fields in those objects change). In order to use anything other than SMP, GC would need to know the latest state of the object fields so it can avoid clobbering other changes, which is a significant change in the algorithm that we would prefer not to cherry-pick to 1.8.x and 1.9.1.

@liggitt wrote:

Experience with strategic merge patch from apply has not been good. Why do we want to expand it?

Strategic merge patch/diff is currently our best known solution for the problem of reconciling mutations made by automation (e.g. controllers, admission) with changes to the original source of truth (e.g. YAML in source control). Kubectl apply was an early proving ground for this and we expect to use SMP more in things like Declarative App Management. So for example, some parts of DAM might require strategic merge diff, which will require non-compiled-in CRs (such as those from CRD) to provide the SMP tags in some form (e.g. OpenAPI).

@@ -439,6 +439,9 @@ func (UnstructuredObjectConverter) Convert(in, out, context interface{}) error {
func (UnstructuredObjectConverter) ConvertToVersion(in runtime.Object, target runtime.GroupVersioner) (runtime.Object, error) {
if kind := in.GetObjectKind().GroupVersionKind(); !kind.Empty() {
gvk, ok := target.KindForGroupVersionKinds([]schema.GroupVersionKind{kind})
if gvk.GroupVersion().Version == runtime.APIVersionInternal {
return in, nil
Copy link
Member

Choose a reason for hiding this comment

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

What is the purpose of this?

Copy link
Contributor

Choose a reason for hiding this comment

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

This should be impossible - an internal unstructed is a coding error, and should never be allowed

@lavalamp
Copy link
Member

lavalamp commented Dec 1, 2017

Just to clarify, Chao's claim is that CRD is the only apiserver that would ever need this fix, assuming others use the generic apiserver library. But this is equivalent to mandating all apiservers support SMP on metadata. (Otherwise we have to fix GC too, which would render this change redundant.)

I agree it's weird to generally allow patching arbitrary subsets of objects, but allowing patching of metadata even if you don't understand the rest of the object seems very powerful. So I don't object to that.

So I really think it comes down to whether we want to mandate support for (at least metadata) SMP.

Currently I am inclined to say:

  1. Let's take this patch.
  2. Let's not set a precedent, and be 100% willing to reevaluate this if someone comes up with a valid use case.

@smarterclayton
Copy link
Contributor

Given that strategic merge patch is unversioned this means we can never change the merge patch strategy on metadata ever again. I’m not confident enough in SMR to say that is accurate.

I don’t think we should require all api servers support strategic merge patch without a lot more discussion - that statement above chills me to the bone...

@smarterclayton
Copy link
Contributor

Also, what happens to an existing valid api server extension today that doesn’t support this?

@caesarxuchao
Copy link
Member Author

Also, what happens to an existing valid api server extension today that doesn’t support this?

Do we have apiservers that are not based on the generic apiserver library? If they don't support SMP, they can still use background GC. Foreground and orphan will stuck.

@caesarxuchao
Copy link
Member Author

caesarxuchao commented Dec 1, 2017

Can we agree on accepting this patch for 1.9 (obviously I'll need to add more tests)? It's making CRD more powerful, and fixes 99% GC use cases.

I can continue working on my other fix (#56595) that converts GC to use JSON merge patch, but that one requires more soak time.

@lavalamp
Copy link
Member

lavalamp commented Dec 4, 2017

Given that strategic merge patch is unversioned this means we can never change the merge patch

Ugh, this is seriously still true? We need to replace it with something more principled ASAP.

@@ -115,16 +110,6 @@ if [ "${CLOUD_PROVIDER}" == "openstack" ]; then
fi
fi

#set feature gates if using ipvs mode
Copy link
Member

Choose a reason for hiding this comment

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

Why are there so many changes to this file? It seems unrelated?

Copy link
Member Author

Choose a reason for hiding this comment

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

Must be a rebase error. I'll finalize this PR.

@caesarxuchao caesarxuchao force-pushed the crd-strategic-metadata branch 2 times, most recently from b921a13 to d836a10 Compare December 6, 2017 00:01
@k8s-github-robot k8s-github-robot removed the needs-rebase Indicates a PR cannot be merged because it has merge conflicts with HEAD. label Dec 6, 2017
@caesarxuchao caesarxuchao changed the title [WIP] Let crd support strategic merge patch on metadata Let crd support strategic merge patch on metadata Dec 6, 2017
@k8s-ci-robot k8s-ci-robot removed the do-not-merge/work-in-progress Indicates that a PR should not merge because it is a work in progress. label Dec 6, 2017
@@ -439,6 +439,9 @@ func (UnstructuredObjectConverter) Convert(in, out, context interface{}) error {
func (UnstructuredObjectConverter) ConvertToVersion(in runtime.Object, target runtime.GroupVersioner) (runtime.Object, error) {
if kind := in.GetObjectKind().GroupVersionKind(); !kind.Empty() {
gvk, ok := target.KindForGroupVersionKinds([]schema.GroupVersionKind{kind})
// if gvk.GroupVersion().Version == runtime.APIVersionInternal {
// return in, nil
Copy link
Member Author

Choose a reason for hiding this comment

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

@smarterclayton I wanted to treat the original version as the internal version of a custom resource. Otherwise we need special handling for Unstructured at https://github.com/kubernetes/kubernetes/pull/56606/files#diff-15bdfc59ec78ff09b509d4a347560a1bR244 and https://github.com/kubernetes/kubernetes/pull/56606/files#diff-15bdfc59ec78ff09b509d4a347560a1bR346.

Copy link
Member

Choose a reason for hiding this comment

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

Looks much better without these lines. But we should delete instead of comment out :)

@caesarxuchao
Copy link
Member Author

@lavalamp @liggitt it's ready for review. PTAL. Thank you.

@caesarxuchao caesarxuchao assigned lavalamp and liggitt and unassigned caesarxuchao Dec 6, 2017
@caesarxuchao
Copy link
Member Author

/retest

@k8s-ci-robot
Copy link
Contributor

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by: caesarxuchao
We suggest the following additional approver: lavalamp

Assign the PR to them by writing /assign @lavalamp in a comment when ready.

Associated issue: #56348

The full list of commands accepted by this bot can be found here.

Needs approval from an approver in each of these OWNERS Files:

You can indicate your approval by writing /approve in a comment
You can cancel your approval by writing /approve cancel in a comment

@@ -6746,3 +6747,100 @@ func TestUnknownField(t *testing.T) {
}
}
}

func TestStrategicMergePatchUnstructured(t *testing.T) {
Copy link
Member Author

Choose a reason for hiding this comment

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

When writing this test, I didn't notice we already had a unit test at staging/src/k8s.io/apiserver/pkg/endpoints/handlers/rest_test.go. Anyway, it doesn't hurt to have extra tests at different layers.

@k8s-ci-robot k8s-ci-robot added release-note Denotes a PR that will be considered when it comes time to generate release notes. and removed do-not-merge/release-note-label-needed Indicates that a PR should not merge because it's missing one of the release note labels. labels Dec 6, 2017
@lavalamp
Copy link
Member

@liggitt So did I convince you we should take this at kubecon? :)

@liggitt
Copy link
Member

liggitt commented Dec 12, 2017

@liggitt: Is our position that all aggregated API servers must support strategic merge patch?

@enisoc: We aren't necessarily taking that position by doing this.

We are by refusing to make GC work with a resource unless it supports SMP

@lavalamp: So did I convince you we should take this at kubecon? :)

I still think the semantics of only supporting a patch type on part of an object are bizarre. Would openapi claim to accept strategic merge patch content-type for CRDs? That would lead clients like kubectl apply to compute SMP patches, rather than jsonmerge patches, and fail.

Also, the typecasts in patch handling smell particularly bad to me.

I'd still be more inclined to switch GC to a patch type that is more reasonable to support, rather than do this.

@sttts
Copy link
Contributor

sttts commented Dec 13, 2017

I'd still be more inclined to switch GC to a patch type that is more reasonable to support, rather than do this.

I agree with @liggitt. I would rather see a fixed GC in 1.9.x with non-strategic patches than taking this shortcut.

@lavalamp
Copy link
Member

@liggitt: Is our position that all aggregated API servers must support strategic merge patch?

@enisoc: We aren't necessarily taking that position by doing this.

@liggitt: We are by refusing to make GC work with a resource unless it supports SMP

I disagree. I don't think we are "refusing" permanently. This is just an expedient way to solve the only existing problem case.

@k8s-github-robot
Copy link

@caesarxuchao PR needs rebase

@k8s-github-robot k8s-github-robot added the needs-rebase Indicates a PR cannot be merged because it has merge conflicts with HEAD. label Feb 6, 2018
@liggitt
Copy link
Member

liggitt commented May 3, 2018

closing based on discussion in #58414 (comment) and removal of the need for SMP support for patch retry in #63146

/close

k8s-github-robot pushed a commit that referenced this pull request Jun 7, 2018
Automatic merge from submit-queue (batch tested with PRs 63386, 64624, 62297, 64847). If you want to cherry-pick this change to another branch, please follow the instructions <a href="https://github.com/kubernetes/community/blob/master/contributors/devel/cherry-picks.md">here</a>.

Let the garbage collector use json merge patch when SMP is not supported

**What this PR does / why we need it**:
Let garbage collector fallback to use json merge patch when strategic merge patch returns 415. This enables orphan delete on custom resources. 

**Which issue(s) this PR fixes** *(optional, in `fixes #<issue number>(, fixes #<issue_number>, ...)` format, will close the issue(s) when PR gets merged)*:
Fixes #56348

**Special notes for your reviewer**:
This PR is developed based on #56595. Ref #56606 for more information. 

**Release note**:

```release-note
Orphan delete is now supported for custom resources
```

/sig api-machinery
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
cncf-cla: yes Indicates the PR's author has signed the CNCF CLA. needs-rebase Indicates a PR cannot be merged because it has merge conflicts with HEAD. release-note Denotes a PR that will be considered when it comes time to generate release notes. sig/api-machinery Categorizes an issue or PR as relevant to SIG API Machinery. size/L Denotes a PR that changes 100-499 lines, ignoring generated files.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

CR is stuck with "orphan" finalizer
10 participants