Merge pull request #88875 from apelisse/apply-no-op

SSA: Applying same object twice should be a no-op the second time

Kubernetes-commit: 71ad0a90205c12e8ebbe6872db30097026916659
This commit is contained in:
Kubernetes Publisher 2020-03-07 01:13:34 -08:00
commit ea2fe0c9c4
7 changed files with 158 additions and 38 deletions

10
Godeps/Godeps.json generated
View File

@ -588,19 +588,19 @@
},
{
"ImportPath": "k8s.io/api",
"Rev": "0a52b7486422"
"Rev": "510bcd53e1cf"
},
{
"ImportPath": "k8s.io/apimachinery",
"Rev": "ac276fc34867"
"Rev": "2b7fa1cb5395"
},
{
"ImportPath": "k8s.io/client-go",
"Rev": "425ea3e5d030"
"Rev": "5194bac86967"
},
{
"ImportPath": "k8s.io/component-base",
"Rev": "2cff9454e9d1"
"Rev": "dae26a37dccb"
},
{
"ImportPath": "k8s.io/gengo",
@ -624,7 +624,7 @@
},
{
"ImportPath": "sigs.k8s.io/structured-merge-diff/v3",
"Rev": "5e70324e7c1c"
"Rev": "v3.0.0"
},
{
"ImportPath": "sigs.k8s.io/yaml",

18
go.mod
View File

@ -43,23 +43,23 @@ require (
gopkg.in/square/go-jose.v2 v2.2.2
gopkg.in/yaml.v2 v2.2.8
gotest.tools v2.2.0+incompatible // indirect
k8s.io/api v0.0.0-20200306151724-0a52b7486422
k8s.io/apimachinery v0.0.0-20200306202157-ac276fc34867
k8s.io/client-go v0.0.0-20200307002527-425ea3e5d030
k8s.io/component-base v0.0.0-20200305122702-2cff9454e9d1
k8s.io/api v0.0.0-20200307122242-510bcd53e1cf
k8s.io/apimachinery v0.0.0-20200307122051-2b7fa1cb5395
k8s.io/client-go v0.0.0-20200307122516-5194bac86967
k8s.io/component-base v0.0.0-20200307123030-dae26a37dccb
k8s.io/klog v1.0.0
k8s.io/kube-openapi v0.0.0-20200121204235-bf4fb3bd569c
k8s.io/utils v0.0.0-20200229041039-0a110f9eb7ab
sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.7
sigs.k8s.io/structured-merge-diff/v3 v3.0.0-20200207200219-5e70324e7c1c
sigs.k8s.io/structured-merge-diff/v3 v3.0.0
sigs.k8s.io/yaml v1.2.0
)
replace (
golang.org/x/sys => golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a // pinned to release-branch.go1.13
golang.org/x/tools => golang.org/x/tools v0.0.0-20190821162956-65e3620a7ae7 // pinned to release-branch.go1.13
k8s.io/api => k8s.io/api v0.0.0-20200306151724-0a52b7486422
k8s.io/apimachinery => k8s.io/apimachinery v0.0.0-20200306202157-ac276fc34867
k8s.io/client-go => k8s.io/client-go v0.0.0-20200307002527-425ea3e5d030
k8s.io/component-base => k8s.io/component-base v0.0.0-20200305122702-2cff9454e9d1
k8s.io/api => k8s.io/api v0.0.0-20200307122242-510bcd53e1cf
k8s.io/apimachinery => k8s.io/apimachinery v0.0.0-20200307122051-2b7fa1cb5395
k8s.io/client-go => k8s.io/client-go v0.0.0-20200307122516-5194bac86967
k8s.io/component-base => k8s.io/component-base v0.0.0-20200307123030-dae26a37dccb
)

12
go.sum
View File

@ -337,10 +337,10 @@ gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
k8s.io/api v0.0.0-20200306151724-0a52b7486422/go.mod h1:4bHpvwcObcdIFmIlnwDdJznMrytQbQCBpJvQgh+FnrA=
k8s.io/apimachinery v0.0.0-20200306202157-ac276fc34867/go.mod h1:5X8oEhnd931nEg6/Nkumo00nT6ZsCLp2h7Xwd7Ym6P4=
k8s.io/client-go v0.0.0-20200307002527-425ea3e5d030/go.mod h1:9W6NBDAP22mJEhrKYXmPsL6dTQqczIp6+4GZcpWDlKs=
k8s.io/component-base v0.0.0-20200305122702-2cff9454e9d1/go.mod h1:3RwNWXDYjUOPPeNNQE+7cPPoWAnnL/938q4q+jnBTNk=
k8s.io/api v0.0.0-20200307122242-510bcd53e1cf/go.mod h1:4LcBtPb1OoMEmTvQLypv2jGrtMEK8ZRCPQyc+uOSi4o=
k8s.io/apimachinery v0.0.0-20200307122051-2b7fa1cb5395/go.mod h1:9SnR/e11v5IbyPCGbvJViimtJ0SwHG4nfZFjU77ftcA=
k8s.io/client-go v0.0.0-20200307122516-5194bac86967/go.mod h1:Ys+cjVY1SiB17qdMz+qc3ItvCfnYLsgVt49tSUmvwTo=
k8s.io/component-base v0.0.0-20200307123030-dae26a37dccb/go.mod h1:P8U3GgtuxIyAFY/al0ojd2AP/ifHmYMZBx3HFO3N0es=
k8s.io/gengo v0.0.0-20190128074634-0689ccc1d7d6/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0=
k8s.io/klog v0.0.0-20181102134211-b9b56d5dfc92/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk=
k8s.io/klog v0.3.0/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk=
@ -353,8 +353,8 @@ k8s.io/utils v0.0.0-20200229041039-0a110f9eb7ab/go.mod h1:sZAwmy6armz5eXlNoLmJcl
sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.7 h1:uuHDyjllyzRyCIvvn0OBjiRB0SgBZGqHNYAmjR7fO50=
sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.7/go.mod h1:PHgbrJT7lCHcxMU+mDHEm+nx46H4zuuHZkDP6icnhu0=
sigs.k8s.io/structured-merge-diff/v3 v3.0.0-20200116222232-67a7b8c61874/go.mod h1:PlARxl6Hbt/+BC80dRLi1qAmnMqwqDg62YvvVkZjemw=
sigs.k8s.io/structured-merge-diff/v3 v3.0.0-20200207200219-5e70324e7c1c h1:xQP7F7Lntt2dtYmg12WPQHObOrAyPHlMWP1JVSa79GM=
sigs.k8s.io/structured-merge-diff/v3 v3.0.0-20200207200219-5e70324e7c1c/go.mod h1:PlARxl6Hbt/+BC80dRLi1qAmnMqwqDg62YvvVkZjemw=
sigs.k8s.io/structured-merge-diff/v3 v3.0.0 h1:dOmIZBMfhcHS09XZkMyUgkq5trg3/jRyJYFZUiaOp8E=
sigs.k8s.io/structured-merge-diff/v3 v3.0.0/go.mod h1:PlARxl6Hbt/+BC80dRLi1qAmnMqwqDg62YvvVkZjemw=
sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o=
sigs.k8s.io/yaml v1.2.0 h1:kr/MCeFWJWTwyaHoR9c8EjH9OumOmoF9YGiZd7lFm/Q=
sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc=

View File

@ -94,6 +94,7 @@ func NewDefaultCRDFieldManager(models openapiproto.Models, objectConverter runti
// newDefaultFieldManager is a helper function which wraps a Manager with certain default logic.
func newDefaultFieldManager(f Manager, objectCreater runtime.ObjectCreater, kind schema.GroupVersionKind) *FieldManager {
f = NewStripMetaManager(f)
f = NewManagedFieldsUpdater(f)
f = NewBuildManagerInfoManager(f, kind.GroupVersion())
f = NewCapManagersManager(f, DefaultMaxUpdateManagers)
f = NewProbabilisticSkipNonAppliedManager(f, objectCreater, kind, DefaultTrackOnCreateProbability)

View File

@ -22,6 +22,7 @@ import (
"io/ioutil"
"net/http"
"path/filepath"
"reflect"
"strings"
"testing"
"time"
@ -104,6 +105,7 @@ func NewTestFieldManager(gvk schema.GroupVersionKind) TestFieldManager {
live.SetKind(gvk.Kind)
live.SetAPIVersion(gvk.GroupVersion().String())
f = fieldmanager.NewStripMetaManager(f)
f = fieldmanager.NewManagedFieldsUpdater(f)
f = fieldmanager.NewBuildManagerInfoManager(f, gvk.GroupVersion())
return TestFieldManager{
fieldManager: f,
@ -732,3 +734,52 @@ func TestApplySuccessWithNoManagedFields(t *testing.T) {
t.Fatalf("failed to apply object: %v", err)
}
}
// Run an update and apply, and make sure that nothing has changed.
func TestNoOpChanges(t *testing.T) {
f := NewTestFieldManager(schema.FromAPIVersionAndKind("v1", "Pod"))
obj := &unstructured.Unstructured{Object: map[string]interface{}{}}
if err := yaml.Unmarshal([]byte(`{
"apiVersion": "v1",
"kind": "Pod",
"metadata": {
"labels": {
"a": "b"
},
}
}`), &obj.Object); err != nil {
t.Fatalf("error decoding YAML: %v", err)
}
if err := f.Apply(obj, "fieldmanager_test_apply", false); err != nil {
t.Fatalf("failed to apply object: %v", err)
}
before := f.liveObj.DeepCopyObject()
// Wait to make sure the timestamp is different
time.Sleep(time.Second)
// Applying with a different fieldmanager will create an entry..
if err := f.Apply(obj, "fieldmanager_test_apply_other", false); err != nil {
t.Fatalf("failed to update object: %v", err)
}
if reflect.DeepEqual(before, f.liveObj) {
t.Fatalf("Applying no-op apply with new manager didn't change object: \n%v", f.liveObj)
}
before = f.liveObj.DeepCopyObject()
// Wait to make sure the timestamp is different
time.Sleep(time.Second)
if err := f.Update(obj, "fieldmanager_test_update"); err != nil {
t.Fatalf("failed to update object: %v", err)
}
if !reflect.DeepEqual(before, f.liveObj) {
t.Fatalf("No-op update has changed the object:\n%v\n---\n%v", before, f.liveObj)
}
before = f.liveObj.DeepCopyObject()
// Wait to make sure the timestamp is different
time.Sleep(time.Second)
if err := f.Apply(obj, "fieldmanager_test_apply", true); err != nil {
t.Fatalf("failed to re-apply object: %v", err)
}
if !reflect.DeepEqual(before, f.liveObj) {
t.Fatalf("No-op apply has changed the object:\n%v\n---\n%v", before, f.liveObj)
}
}

View File

@ -0,0 +1,82 @@
/*
Copyright 2020 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package fieldmanager
import (
"time"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"sigs.k8s.io/structured-merge-diff/v3/fieldpath"
)
type managedFieldsUpdater struct {
fieldManager Manager
}
var _ Manager = &managedFieldsUpdater{}
// NewManagedFieldsUpdater is responsible for updating the managedfields
// in the object, updating the time of the operation as necessary. For
// updates, it uses a hard-coded manager to detect if things have
// changed, and swaps back the correct manager after the operation is
// done.
func NewManagedFieldsUpdater(fieldManager Manager) Manager {
return &managedFieldsUpdater{
fieldManager: fieldManager,
}
}
// Update implements Manager.
func (f *managedFieldsUpdater) Update(liveObj, newObj runtime.Object, managed Managed, manager string) (runtime.Object, Managed, error) {
self := "current-operation"
object, managed, err := f.fieldManager.Update(liveObj, newObj, managed, self)
if err != nil {
return object, managed, err
}
// If the current operation took any fields from anything, it means the object changed,
// so update the timestamp of the managedFieldsEntry and merge with any previous updates from the same manager
if vs, ok := managed.Fields()[self]; ok {
delete(managed.Fields(), self)
managed.Times()[manager] = &metav1.Time{Time: time.Now().UTC()}
if previous, ok := managed.Fields()[manager]; ok {
managed.Fields()[manager] = fieldpath.NewVersionedSet(vs.Set().Union(previous.Set()), vs.APIVersion(), vs.Applied())
} else {
managed.Fields()[manager] = vs
}
}
return object, managed, nil
}
// Apply implements Manager.
func (f *managedFieldsUpdater) Apply(liveObj, appliedObj runtime.Object, managed Managed, fieldManager string, force bool) (runtime.Object, Managed, error) {
formerManaged := managed.Fields().Copy()
object, managed, err := f.fieldManager.Apply(liveObj, appliedObj, managed, fieldManager, force)
if err != nil {
return object, managed, err
}
if object != nil || !managed.Fields().Equals(formerManaged) {
managed.Times()[fieldManager] = &metav1.Time{Time: time.Now().UTC()}
}
if object == nil {
object = liveObj
}
return object, managed, nil
}

View File

@ -22,7 +22,6 @@ import (
"k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/internal"
@ -116,26 +115,12 @@ func (f *structuredMergeManager) Update(liveObj, newObj runtime.Object, managed
apiVersion := fieldpath.APIVersion(f.groupVersion.String())
// TODO(apelisse) use the first return value when unions are implemented
self := "current-operation"
_, managedFields, err := f.updater.Update(liveObjTyped, newObjTyped, apiVersion, managed.Fields(), self)
_, managedFields, err := f.updater.Update(liveObjTyped, newObjTyped, apiVersion, managed.Fields(), manager)
if err != nil {
return nil, nil, fmt.Errorf("failed to update ManagedFields: %v", err)
}
managed = internal.NewManaged(managedFields, managed.Times())
// If the current operation took any fields from anything, it means the object changed,
// so update the timestamp of the managedFieldsEntry and merge with any previous updates from the same manager
if vs, ok := managed.Fields()[self]; ok {
delete(managed.Fields(), self)
managed.Times()[manager] = &metav1.Time{Time: time.Now().UTC()}
if previous, ok := managed.Fields()[manager]; ok {
managed.Fields()[manager] = fieldpath.NewVersionedSet(vs.Set().Union(previous.Set()), vs.APIVersion(), vs.Applied())
} else {
managed.Fields()[manager] = vs
}
}
return newObj, managed, nil
}
@ -182,8 +167,9 @@ func (f *structuredMergeManager) Apply(liveObj, patchObj runtime.Object, managed
}
managed = internal.NewManaged(managedFields, managed.Times())
// Update the time in the managedFieldsEntry for this operation
managed.Times()[manager] = &metav1.Time{Time: time.Now().UTC()}
if newObjTyped == nil {
return nil, managed, nil
}
newObj, err := f.typeConverter.TypedToObject(newObjTyped)
if err != nil {