Merge pull request #114218 from apelisse/move-fieldmanager

Re-factor FieldManager tests for better re-use
This commit is contained in:
Kubernetes Prow Robot 2022-12-10 07:54:30 -08:00 committed by GitHub
commit a125912cd5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 389 additions and 291 deletions

View File

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
package fieldmanager package fieldmanager_test
import ( import (
"bytes" "bytes"
@ -29,27 +29,29 @@ import (
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager"
"k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/fieldmanagertest"
"sigs.k8s.io/structured-merge-diff/v4/fieldpath" "sigs.k8s.io/structured-merge-diff/v4/fieldpath"
) )
type fakeManager struct{} type fakeManager struct{}
var _ Manager = &fakeManager{} var _ fieldmanager.Manager = &fakeManager{}
func (*fakeManager) Update(_, newObj runtime.Object, managed Managed, _ string) (runtime.Object, Managed, error) { func (*fakeManager) Update(_, newObj runtime.Object, managed fieldmanager.Managed, _ string) (runtime.Object, fieldmanager.Managed, error) {
return newObj, managed, nil return newObj, managed, nil
} }
func (*fakeManager) Apply(_, _ runtime.Object, _ Managed, _ string, _ bool) (runtime.Object, Managed, error) { func (*fakeManager) Apply(_, _ runtime.Object, _ fieldmanager.Managed, _ string, _ bool) (runtime.Object, fieldmanager.Managed, error) {
panic("not implemented") panic("not implemented")
return nil, nil, nil return nil, nil, nil
} }
func TestCapManagersManagerMergesEntries(t *testing.T) { func TestCapManagersManagerMergesEntries(t *testing.T) {
f := NewTestFieldManager(schema.FromAPIVersionAndKind("v1", "Pod"), f := fieldmanagertest.NewTestFieldManager(schema.FromAPIVersionAndKind("v1", "Pod"),
"", "",
func(m Manager) Manager { func(m fieldmanager.Manager) fieldmanager.Manager {
return NewCapManagersManager(m, 3) return fieldmanager.NewCapManagersManager(m, 3)
}) })
podWithLabels := func(labels ...string) runtime.Object { podWithLabels := func(labels ...string) runtime.Object {
@ -112,10 +114,10 @@ func TestCapManagersManagerMergesEntries(t *testing.T) {
} }
func TestCapUpdateManagers(t *testing.T) { func TestCapUpdateManagers(t *testing.T) {
f := NewTestFieldManager(schema.FromAPIVersionAndKind("v1", "Pod"), f := fieldmanagertest.NewTestFieldManager(schema.FromAPIVersionAndKind("v1", "Pod"),
"", "",
func(m Manager) Manager { func(m fieldmanager.Manager) fieldmanager.Manager {
return NewCapManagersManager(m, 3) return fieldmanager.NewCapManagersManager(m, 3)
}) })
set := func(fields ...string) *metav1.FieldsV1 { set := func(fields ...string) *metav1.FieldsV1 {
@ -232,12 +234,13 @@ func TestCapUpdateManagers(t *testing.T) {
for _, tc := range testCases { for _, tc := range testCases {
f.Reset() f.Reset()
accessor, err := meta.Accessor(f.liveObj) live := f.Live()
accessor, err := meta.Accessor(live)
if err != nil { if err != nil {
t.Fatalf("%v: couldn't get accessor: %v", tc.name, err) t.Fatalf("%v: couldn't get accessor: %v", tc.name, err)
} }
accessor.SetManagedFields(tc.input) accessor.SetManagedFields(tc.input)
if err := f.Update(f.liveObj, "no-op-update"); err != nil { if err := f.Update(live, "no-op-update"); err != nil {
t.Fatalf("%v: failed to do no-op update to object: %v", tc.name, err) t.Fatalf("%v: failed to do no-op update to object: %v", tc.name, err)
} }
@ -249,13 +252,13 @@ func TestCapUpdateManagers(t *testing.T) {
} }
// expectIdempotence does a no-op update and ensures that managedFields doesn't change by calling capUpdateManagers. // expectIdempotence does a no-op update and ensures that managedFields doesn't change by calling capUpdateManagers.
func expectIdempotence(t *testing.T, f TestFieldManager) { func expectIdempotence(t *testing.T, f fieldmanagertest.TestFieldManager) {
before := []metav1.ManagedFieldsEntry{} before := []metav1.ManagedFieldsEntry{}
for _, m := range f.ManagedFields() { for _, m := range f.ManagedFields() {
before = append(before, *m.DeepCopy()) before = append(before, *m.DeepCopy())
} }
if err := f.Update(f.liveObj, "no-op-update"); err != nil { if err := f.Update(f.Live(), "no-op-update"); err != nil {
t.Fatalf("failed to do no-op update to object: %v", err) t.Fatalf("failed to do no-op update to object: %v", err)
} }
@ -265,7 +268,7 @@ func expectIdempotence(t *testing.T, f TestFieldManager) {
} }
// expectManagesField ensures that manager m currently manages field path p. // expectManagesField ensures that manager m currently manages field path p.
func expectManagesField(t *testing.T, f TestFieldManager, m string, p fieldpath.Path) { func expectManagesField(t *testing.T, f fieldmanagertest.TestFieldManager, m string, p fieldpath.Path) {
for _, e := range f.ManagedFields() { for _, e := range f.ManagedFields() {
if e.Manager == m { if e.Manager == m {
var s fieldpath.Set var s fieldpath.Set

View File

@ -14,14 +14,12 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
package fieldmanager package fieldmanager_test
import ( import (
"errors"
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"net/http" "net/http"
"path/filepath"
"reflect" "reflect"
"strings" "strings"
"testing" "testing"
@ -36,165 +34,15 @@ import (
"k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/runtime/serializer" "k8s.io/apimachinery/pkg/runtime/serializer"
yamlutil "k8s.io/apimachinery/pkg/util/yaml" yamlutil "k8s.io/apimachinery/pkg/util/yaml"
"k8s.io/kube-openapi/pkg/util/proto" "k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/fieldmanagertest"
prototesting "k8s.io/kube-openapi/pkg/util/proto/testing" "k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/internal"
"sigs.k8s.io/structured-merge-diff/v4/fieldpath"
"sigs.k8s.io/structured-merge-diff/v4/merge"
"sigs.k8s.io/structured-merge-diff/v4/typed"
"sigs.k8s.io/yaml" "sigs.k8s.io/yaml"
) )
var kubernetesSwaggerSchema = prototesting.Fake{
Path: filepath.Join(
strings.Repeat(".."+string(filepath.Separator), 8),
"api", "openapi-spec", "swagger.json"),
}
type fakeObjectConvertor struct {
converter merge.Converter
apiVersion fieldpath.APIVersion
}
//nolint:staticcheck,ineffassign // SA4009 backwards compatibility
func (c *fakeObjectConvertor) Convert(in, out, context interface{}) error {
if typedValue, ok := in.(*typed.TypedValue); ok {
var err error
out, err = c.converter.Convert(typedValue, c.apiVersion)
return err
}
return nil
}
func (c *fakeObjectConvertor) ConvertToVersion(in runtime.Object, _ runtime.GroupVersioner) (runtime.Object, error) {
return in, nil
}
func (c *fakeObjectConvertor) ConvertFieldLabel(_ schema.GroupVersionKind, _, _ string) (string, string, error) {
return "", "", errors.New("not implemented")
}
type fakeObjectDefaulter struct{}
func (d *fakeObjectDefaulter) Default(in runtime.Object) {}
type TestFieldManager struct {
fieldManager *FieldManager
apiVersion string
emptyObj runtime.Object
liveObj runtime.Object
}
func NewDefaultTestFieldManager(gvk schema.GroupVersionKind) TestFieldManager {
return NewTestFieldManager(gvk, "", nil)
}
func NewSubresourceTestFieldManager(gvk schema.GroupVersionKind) TestFieldManager {
return NewTestFieldManager(gvk, "scale", nil)
}
func NewTestFieldManager(gvk schema.GroupVersionKind, subresource string, chainFieldManager func(Manager) Manager) TestFieldManager {
m := NewFakeOpenAPIModels()
typeConverter := NewFakeTypeConverter(m)
converter := newVersionConverter(typeConverter, &fakeObjectConvertor{}, gvk.GroupVersion())
apiVersion := fieldpath.APIVersion(gvk.GroupVersion().String())
objectConverter := &fakeObjectConvertor{converter, apiVersion}
f, err := NewStructuredMergeManager(
typeConverter,
objectConverter,
&fakeObjectDefaulter{},
gvk.GroupVersion(),
gvk.GroupVersion(),
nil,
)
if err != nil {
panic(err)
}
live := &unstructured.Unstructured{}
live.SetKind(gvk.Kind)
live.SetAPIVersion(gvk.GroupVersion().String())
f = NewLastAppliedUpdater(
NewLastAppliedManager(
NewProbabilisticSkipNonAppliedManager(
NewBuildManagerInfoManager(
NewManagedFieldsUpdater(
NewStripMetaManager(f),
), gvk.GroupVersion(), subresource,
), &fakeObjectCreater{gvk: gvk}, gvk, DefaultTrackOnCreateProbability,
), typeConverter, objectConverter, gvk.GroupVersion(),
),
)
if chainFieldManager != nil {
f = chainFieldManager(f)
}
return TestFieldManager{
fieldManager: NewFieldManager(f, subresource),
apiVersion: gvk.GroupVersion().String(),
emptyObj: live,
liveObj: live.DeepCopyObject(),
}
}
func NewFakeTypeConverter(m proto.Models) TypeConverter {
tc, err := NewTypeConverter(m, false)
if err != nil {
panic(fmt.Sprintf("Failed to build TypeConverter: %v", err))
}
return tc
}
func NewFakeOpenAPIModels() proto.Models {
d, err := kubernetesSwaggerSchema.OpenAPISchema()
if err != nil {
panic(err)
}
m, err := proto.NewOpenAPIData(d)
if err != nil {
panic(err)
}
return m
}
func (f *TestFieldManager) APIVersion() string {
return f.apiVersion
}
func (f *TestFieldManager) Reset() {
f.liveObj = f.emptyObj.DeepCopyObject()
}
func (f *TestFieldManager) Get() runtime.Object {
return f.liveObj.DeepCopyObject()
}
func (f *TestFieldManager) Apply(obj runtime.Object, manager string, force bool) error {
out, err := f.fieldManager.Apply(f.liveObj, obj, manager, force)
if err == nil {
f.liveObj = out
}
return err
}
func (f *TestFieldManager) Update(obj runtime.Object, manager string) error {
out, err := f.fieldManager.Update(f.liveObj, obj, manager)
if err == nil {
f.liveObj = out
}
return err
}
func (f *TestFieldManager) ManagedFields() []metav1.ManagedFieldsEntry {
accessor, err := meta.Accessor(f.liveObj)
if err != nil {
panic(fmt.Errorf("couldn't get accessor: %v", err))
}
return accessor.GetManagedFields()
}
// TestUpdateApplyConflict tests that applying to an object, which // TestUpdateApplyConflict tests that applying to an object, which
// wasn't created by apply, will give conflicts // wasn't created by apply, will give conflicts
func TestUpdateApplyConflict(t *testing.T) { func TestUpdateApplyConflict(t *testing.T) {
f := NewDefaultTestFieldManager(schema.FromAPIVersionAndKind("apps/v1", "Deployment")) f := fieldmanagertest.NewDefaultTestFieldManager(schema.FromAPIVersionAndKind("apps/v1", "Deployment"))
patch := []byte(`{ patch := []byte(`{
"apiVersion": "apps/v1", "apiVersion": "apps/v1",
@ -255,7 +103,7 @@ func TestUpdateApplyConflict(t *testing.T) {
} }
func TestApplyStripsFields(t *testing.T) { func TestApplyStripsFields(t *testing.T) {
f := NewDefaultTestFieldManager(schema.FromAPIVersionAndKind("apps/v1", "Deployment")) f := fieldmanagertest.NewDefaultTestFieldManager(schema.FromAPIVersionAndKind("apps/v1", "Deployment"))
newObj := &unstructured.Unstructured{ newObj := &unstructured.Unstructured{
Object: map[string]interface{}{ Object: map[string]interface{}{
@ -287,7 +135,7 @@ func TestApplyStripsFields(t *testing.T) {
} }
func TestVersionCheck(t *testing.T) { func TestVersionCheck(t *testing.T) {
f := NewDefaultTestFieldManager(schema.FromAPIVersionAndKind("apps/v1", "Deployment")) f := fieldmanagertest.NewDefaultTestFieldManager(schema.FromAPIVersionAndKind("apps/v1", "Deployment"))
appliedObj := &unstructured.Unstructured{Object: map[string]interface{}{}} appliedObj := &unstructured.Unstructured{Object: map[string]interface{}{}}
if err := yaml.Unmarshal([]byte(`{ if err := yaml.Unmarshal([]byte(`{
@ -327,7 +175,7 @@ func TestVersionCheck(t *testing.T) {
} }
} }
func TestVersionCheckDoesNotPanic(t *testing.T) { func TestVersionCheckDoesNotPanic(t *testing.T) {
f := NewDefaultTestFieldManager(schema.FromAPIVersionAndKind("apps/v1", "Deployment")) f := fieldmanagertest.NewDefaultTestFieldManager(schema.FromAPIVersionAndKind("apps/v1", "Deployment"))
appliedObj := &unstructured.Unstructured{Object: map[string]interface{}{}} appliedObj := &unstructured.Unstructured{Object: map[string]interface{}{}}
if err := yaml.Unmarshal([]byte(`{ if err := yaml.Unmarshal([]byte(`{
@ -366,7 +214,7 @@ func TestVersionCheckDoesNotPanic(t *testing.T) {
} }
func TestApplyDoesNotStripLabels(t *testing.T) { func TestApplyDoesNotStripLabels(t *testing.T) {
f := NewDefaultTestFieldManager(schema.FromAPIVersionAndKind("v1", "Pod")) f := fieldmanagertest.NewDefaultTestFieldManager(schema.FromAPIVersionAndKind("v1", "Pod"))
appliedObj := &unstructured.Unstructured{Object: map[string]interface{}{}} appliedObj := &unstructured.Unstructured{Object: map[string]interface{}{}}
if err := yaml.Unmarshal([]byte(`{ if err := yaml.Unmarshal([]byte(`{
@ -420,7 +268,7 @@ func TestApplyNewObject(t *testing.T) {
for _, test := range tests { for _, test := range tests {
t.Run(test.gvk.String(), func(t *testing.T) { t.Run(test.gvk.String(), func(t *testing.T) {
f := NewDefaultTestFieldManager(test.gvk) f := fieldmanagertest.NewDefaultTestFieldManager(test.gvk)
appliedObj := &unstructured.Unstructured{Object: map[string]interface{}{}} appliedObj := &unstructured.Unstructured{Object: map[string]interface{}{}}
if err := yaml.Unmarshal(test.obj, &appliedObj.Object); err != nil { if err := yaml.Unmarshal(test.obj, &appliedObj.Object); err != nil {
@ -458,7 +306,7 @@ func BenchmarkNewObject(b *testing.B) {
} }
for _, test := range tests { for _, test := range tests {
b.Run(test.gvk.Kind, func(b *testing.B) { b.Run(test.gvk.Kind, func(b *testing.B) {
f := NewDefaultTestFieldManager(test.gvk) f := fieldmanagertest.NewDefaultTestFieldManager(test.gvk)
decoder := serializer.NewCodecFactory(scheme).UniversalDecoder(test.gvk.GroupVersion()) decoder := serializer.NewCodecFactory(scheme).UniversalDecoder(test.gvk.GroupVersion())
newObj, err := runtime.Decode(decoder, test.obj) newObj, err := runtime.Decode(decoder, test.obj)
@ -564,8 +412,7 @@ func BenchmarkConvertObjectToTyped(b *testing.B) {
for _, test := range tests { for _, test := range tests {
b.Run(test.gvk.Kind, func(b *testing.B) { b.Run(test.gvk.Kind, func(b *testing.B) {
decoder := serializer.NewCodecFactory(scheme).UniversalDecoder(test.gvk.GroupVersion()) decoder := serializer.NewCodecFactory(scheme).UniversalDecoder(test.gvk.GroupVersion())
m := NewFakeOpenAPIModels() typeConverter := fieldmanagertest.NewBuiltinTypeConverter()
typeConverter := NewFakeTypeConverter(m)
structured, err := runtime.Decode(decoder, test.obj) structured, err := runtime.Decode(decoder, test.obj)
if err != nil { if err != nil {
@ -626,8 +473,7 @@ func BenchmarkCompare(b *testing.B) {
for _, test := range tests { for _, test := range tests {
b.Run(test.gvk.Kind, func(b *testing.B) { b.Run(test.gvk.Kind, func(b *testing.B) {
decoder := serializer.NewCodecFactory(scheme).UniversalDecoder(test.gvk.GroupVersion()) decoder := serializer.NewCodecFactory(scheme).UniversalDecoder(test.gvk.GroupVersion())
m := NewFakeOpenAPIModels() typeConverter := fieldmanagertest.NewBuiltinTypeConverter()
typeConverter := NewFakeTypeConverter(m)
structured, err := runtime.Decode(decoder, test.obj) structured, err := runtime.Decode(decoder, test.obj)
if err != nil { if err != nil {
@ -677,7 +523,7 @@ func BenchmarkCompare(b *testing.B) {
} }
func BenchmarkRepeatedUpdate(b *testing.B) { func BenchmarkRepeatedUpdate(b *testing.B) {
f := NewDefaultTestFieldManager(schema.FromAPIVersionAndKind("v1", "Pod")) f := fieldmanagertest.NewDefaultTestFieldManager(schema.FromAPIVersionAndKind("v1", "Pod"))
podBytes := getObjectBytes("pod.yaml") podBytes := getObjectBytes("pod.yaml")
var obj *corev1.Pod var obj *corev1.Pod
@ -716,7 +562,7 @@ func BenchmarkRepeatedUpdate(b *testing.B) {
} }
func TestApplyFailsWithManagedFields(t *testing.T) { func TestApplyFailsWithManagedFields(t *testing.T) {
f := NewDefaultTestFieldManager(schema.FromAPIVersionAndKind("v1", "Pod")) f := fieldmanagertest.NewDefaultTestFieldManager(schema.FromAPIVersionAndKind("v1", "Pod"))
appliedObj := &unstructured.Unstructured{Object: map[string]interface{}{}} appliedObj := &unstructured.Unstructured{Object: map[string]interface{}{}}
if err := yaml.Unmarshal([]byte(`{ if err := yaml.Unmarshal([]byte(`{
@ -741,7 +587,7 @@ func TestApplyFailsWithManagedFields(t *testing.T) {
} }
func TestApplySuccessWithNoManagedFields(t *testing.T) { func TestApplySuccessWithNoManagedFields(t *testing.T) {
f := NewDefaultTestFieldManager(schema.FromAPIVersionAndKind("v1", "Pod")) f := fieldmanagertest.NewDefaultTestFieldManager(schema.FromAPIVersionAndKind("v1", "Pod"))
appliedObj := &unstructured.Unstructured{Object: map[string]interface{}{}} appliedObj := &unstructured.Unstructured{Object: map[string]interface{}{}}
if err := yaml.Unmarshal([]byte(`{ if err := yaml.Unmarshal([]byte(`{
@ -764,7 +610,7 @@ func TestApplySuccessWithNoManagedFields(t *testing.T) {
// Run an update and apply, and make sure that nothing has changed. // Run an update and apply, and make sure that nothing has changed.
func TestNoOpChanges(t *testing.T) { func TestNoOpChanges(t *testing.T) {
f := NewDefaultTestFieldManager(schema.FromAPIVersionAndKind("v1", "Pod")) f := fieldmanagertest.NewDefaultTestFieldManager(schema.FromAPIVersionAndKind("v1", "Pod"))
obj := &unstructured.Unstructured{Object: map[string]interface{}{}} obj := &unstructured.Unstructured{Object: map[string]interface{}{}}
if err := yaml.Unmarshal([]byte(`{ if err := yaml.Unmarshal([]byte(`{
@ -782,40 +628,40 @@ func TestNoOpChanges(t *testing.T) {
if err := f.Apply(obj.DeepCopyObject(), "fieldmanager_test_apply", false); err != nil { if err := f.Apply(obj.DeepCopyObject(), "fieldmanager_test_apply", false); err != nil {
t.Fatalf("failed to apply object: %v", err) t.Fatalf("failed to apply object: %v", err)
} }
before := f.liveObj.DeepCopyObject() before := f.Live()
// Wait to make sure the timestamp is different // Wait to make sure the timestamp is different
time.Sleep(time.Second) time.Sleep(time.Second)
// Applying with a different fieldmanager will create an entry.. // Applying with a different fieldmanager will create an entry..
if err := f.Apply(obj.DeepCopyObject(), "fieldmanager_test_apply_other", false); err != nil { if err := f.Apply(obj.DeepCopyObject(), "fieldmanager_test_apply_other", false); err != nil {
t.Fatalf("failed to update object: %v", err) t.Fatalf("failed to update object: %v", err)
} }
if reflect.DeepEqual(before, f.liveObj) { if reflect.DeepEqual(before, f.Live()) {
t.Fatalf("Applying no-op apply with new manager didn't change object: \n%v", f.liveObj) t.Fatalf("Applying no-op apply with new manager didn't change object: \n%v", f.Live())
} }
before = f.liveObj.DeepCopyObject() before = f.Live()
// Wait to make sure the timestamp is different // Wait to make sure the timestamp is different
time.Sleep(time.Second) time.Sleep(time.Second)
if err := f.Update(obj.DeepCopyObject(), "fieldmanager_test_update"); err != nil { if err := f.Update(obj.DeepCopyObject(), "fieldmanager_test_update"); err != nil {
t.Fatalf("failed to update object: %v", err) t.Fatalf("failed to update object: %v", err)
} }
if !reflect.DeepEqual(before, f.liveObj) { if !reflect.DeepEqual(before, f.Live()) {
t.Fatalf("No-op update has changed the object:\n%v\n---\n%v", before, f.liveObj) t.Fatalf("No-op update has changed the object:\n%v\n---\n%v", before, f.Live())
} }
before = f.liveObj.DeepCopyObject() before = f.Live()
// Wait to make sure the timestamp is different // Wait to make sure the timestamp is different
time.Sleep(time.Second) time.Sleep(time.Second)
if err := f.Apply(obj.DeepCopyObject(), "fieldmanager_test_apply", true); err != nil { if err := f.Apply(obj.DeepCopyObject(), "fieldmanager_test_apply", true); err != nil {
t.Fatalf("failed to re-apply object: %v", err) t.Fatalf("failed to re-apply object: %v", err)
} }
if !reflect.DeepEqual(before, f.liveObj) { if !reflect.DeepEqual(before, f.Live()) {
t.Fatalf("No-op apply has changed the object:\n%v\n---\n%v", before, f.liveObj) t.Fatalf("No-op apply has changed the object:\n%v\n---\n%v", before, f.Live())
} }
} }
// Tests that one can reset the managedFields by sending either an empty // Tests that one can reset the managedFields by sending either an empty
// list // list
func TestResetManagedFieldsEmptyList(t *testing.T) { func TestResetManagedFieldsEmptyList(t *testing.T) {
f := NewDefaultTestFieldManager(schema.FromAPIVersionAndKind("v1", "Pod")) f := fieldmanagertest.NewDefaultTestFieldManager(schema.FromAPIVersionAndKind("v1", "Pod"))
obj := &unstructured.Unstructured{Object: map[string]interface{}{}} obj := &unstructured.Unstructured{Object: map[string]interface{}{}}
if err := yaml.Unmarshal([]byte(`{ if err := yaml.Unmarshal([]byte(`{
@ -856,7 +702,7 @@ func TestResetManagedFieldsEmptyList(t *testing.T) {
// Tests that one can reset the managedFields by sending either a list with one empty item. // Tests that one can reset the managedFields by sending either a list with one empty item.
func TestResetManagedFieldsEmptyItem(t *testing.T) { func TestResetManagedFieldsEmptyItem(t *testing.T) {
f := NewDefaultTestFieldManager(schema.FromAPIVersionAndKind("v1", "Pod")) f := fieldmanagertest.NewDefaultTestFieldManager(schema.FromAPIVersionAndKind("v1", "Pod"))
obj := &unstructured.Unstructured{Object: map[string]interface{}{}} obj := &unstructured.Unstructured{Object: map[string]interface{}{}}
if err := yaml.Unmarshal([]byte(`{ if err := yaml.Unmarshal([]byte(`{
@ -896,7 +742,7 @@ func TestResetManagedFieldsEmptyItem(t *testing.T) {
} }
func TestServerSideApplyWithInvalidLastApplied(t *testing.T) { func TestServerSideApplyWithInvalidLastApplied(t *testing.T) {
f := NewDefaultTestFieldManager(schema.FromAPIVersionAndKind("apps/v1", "Deployment")) f := fieldmanagertest.NewDefaultTestFieldManager(schema.FromAPIVersionAndKind("apps/v1", "Deployment"))
// create object with client-side apply // create object with client-side apply
newObj := &unstructured.Unstructured{Object: map[string]interface{}{}} newObj := &unstructured.Unstructured{Object: map[string]interface{}{}}
@ -915,7 +761,7 @@ spec:
} }
invalidLastApplied := "invalid-object" invalidLastApplied := "invalid-object"
if err := setLastApplied(newObj, invalidLastApplied); err != nil { if err := internal.SetLastApplied(newObj, invalidLastApplied); err != nil {
t.Errorf("failed to set last applied: %v", err) t.Errorf("failed to set last applied: %v", err)
} }
@ -923,7 +769,7 @@ spec:
t.Errorf("failed to update object: %v", err) t.Errorf("failed to update object: %v", err)
} }
lastApplied, err := getLastApplied(f.liveObj) lastApplied, err := getLastApplied(f.Live())
if err != nil { if err != nil {
t.Errorf("failed to get last applied: %v", err) t.Errorf("failed to get last applied: %v", err)
} }
@ -951,7 +797,7 @@ spec:
t.Errorf("expected conflict when applying with invalid last-applied annotation, but got no error for object: \n%+v", appliedObj) t.Errorf("expected conflict when applying with invalid last-applied annotation, but got no error for object: \n%+v", appliedObj)
} }
lastApplied, err = getLastApplied(f.liveObj) lastApplied, err = getLastApplied(f.Live())
if err != nil { if err != nil {
t.Errorf("failed to get last applied: %v", err) t.Errorf("failed to get last applied: %v", err)
} }
@ -964,7 +810,7 @@ spec:
t.Errorf("failed to force server-side apply with: %v", err) t.Errorf("failed to force server-side apply with: %v", err)
} }
lastApplied, err = getLastApplied(f.liveObj) lastApplied, err = getLastApplied(f.Live())
if err != nil { if err != nil {
t.Errorf("failed to get last applied: %v", err) t.Errorf("failed to get last applied: %v", err)
} }
@ -975,7 +821,7 @@ spec:
} }
func TestInteropForClientSideApplyAndServerSideApply(t *testing.T) { func TestInteropForClientSideApplyAndServerSideApply(t *testing.T) {
f := NewDefaultTestFieldManager(schema.FromAPIVersionAndKind("apps/v1", "Deployment")) f := fieldmanagertest.NewDefaultTestFieldManager(schema.FromAPIVersionAndKind("apps/v1", "Deployment"))
// create object with client-side apply // create object with client-side apply
newObj := &unstructured.Unstructured{Object: map[string]interface{}{}} newObj := &unstructured.Unstructured{Object: map[string]interface{}{}}
@ -1010,7 +856,7 @@ spec:
if err := f.Update(newObj, "kubectl-client-side-apply-test"); err != nil { if err := f.Update(newObj, "kubectl-client-side-apply-test"); err != nil {
t.Errorf("failed to update object: %v", err) t.Errorf("failed to update object: %v", err)
} }
lastApplied, err := getLastApplied(f.liveObj) lastApplied, err := getLastApplied(f.Live())
if err != nil { if err != nil {
t.Errorf("failed to get last applied: %v", err) t.Errorf("failed to get last applied: %v", err)
} }
@ -1049,7 +895,7 @@ spec:
t.Errorf("error applying object: %v", err) t.Errorf("error applying object: %v", err)
} }
lastApplied, err = getLastApplied(f.liveObj) lastApplied, err = getLastApplied(f.Live())
if err != nil { if err != nil {
t.Errorf("failed to get last applied: %v", err) t.Errorf("failed to get last applied: %v", err)
} }
@ -1059,7 +905,7 @@ spec:
} }
func TestNoTrackManagedFieldsForClientSideApply(t *testing.T) { func TestNoTrackManagedFieldsForClientSideApply(t *testing.T) {
f := NewDefaultTestFieldManager(schema.FromAPIVersionAndKind("apps/v1", "Deployment")) f := fieldmanagertest.NewDefaultTestFieldManager(schema.FromAPIVersionAndKind("apps/v1", "Deployment"))
// create object // create object
newObj := &unstructured.Unstructured{Object: map[string]interface{}{}} newObj := &unstructured.Unstructured{Object: map[string]interface{}{}}
@ -1131,7 +977,7 @@ spec:
if m := f.ManagedFields(); len(m) != 0 { if m := f.ManagedFields(); len(m) != 0 {
t.Errorf("expected to continue to not track managed fields, but got: %v", m) t.Errorf("expected to continue to not track managed fields, but got: %v", m)
} }
lastApplied, err := getLastApplied(f.liveObj) lastApplied, err := getLastApplied(f.Live())
if err != nil { if err != nil {
t.Errorf("failed to get last applied: %v", err) t.Errorf("failed to get last applied: %v", err)
} }
@ -1188,7 +1034,7 @@ spec:
if m := f.ManagedFields(); len(m) == 0 { if m := f.ManagedFields(); len(m) == 0 {
t.Errorf("expected to track managed fields, but got: %v", m) t.Errorf("expected to track managed fields, but got: %v", m)
} }
lastApplied, err = getLastApplied(f.liveObj) lastApplied, err = getLastApplied(f.Live())
if err != nil { if err != nil {
t.Errorf("failed to get last applied: %v", err) t.Errorf("failed to get last applied: %v", err)
} }
@ -1218,7 +1064,7 @@ func setLastAppliedFromEncoded(obj runtime.Object, lastApplied []byte) error {
if err != nil { if err != nil {
return err return err
} }
return setLastApplied(obj, lastAppliedJSON) return internal.SetLastApplied(obj, lastAppliedJSON)
} }
func getLastApplied(obj runtime.Object) (string, error) { func getLastApplied(obj runtime.Object) (string, error) {
@ -1239,7 +1085,7 @@ func getLastApplied(obj runtime.Object) (string, error) {
} }
func TestUpdateViaSubresources(t *testing.T) { func TestUpdateViaSubresources(t *testing.T) {
f := NewSubresourceTestFieldManager(schema.FromAPIVersionAndKind("v1", "Pod")) f := fieldmanagertest.NewTestFieldManager(schema.FromAPIVersionAndKind("v1", "Pod"), "scale", nil)
obj := &unstructured.Unstructured{Object: map[string]interface{}{}} obj := &unstructured.Unstructured{Object: map[string]interface{}{}}
if err := yaml.Unmarshal([]byte(`{ if err := yaml.Unmarshal([]byte(`{
@ -1294,7 +1140,7 @@ func TestUpdateViaSubresources(t *testing.T) {
// Ensures that a no-op Apply does not mutate managed fields // Ensures that a no-op Apply does not mutate managed fields
func TestApplyDoesNotChangeManagedFields(t *testing.T) { func TestApplyDoesNotChangeManagedFields(t *testing.T) {
originalManagedFields := []metav1.ManagedFieldsEntry{} originalManagedFields := []metav1.ManagedFieldsEntry{}
f := NewDefaultTestFieldManager( f := fieldmanagertest.NewDefaultTestFieldManager(
schema.FromAPIVersionAndKind("apps/v1", "Deployment")) schema.FromAPIVersionAndKind("apps/v1", "Deployment"))
newObj := &unstructured.Unstructured{ newObj := &unstructured.Unstructured{
Object: map[string]interface{}{}, Object: map[string]interface{}{},
@ -1388,7 +1234,7 @@ func TestApplyDoesNotChangeManagedFields(t *testing.T) {
// Ensures that a no-op Update does not mutate managed fields // Ensures that a no-op Update does not mutate managed fields
func TestUpdateDoesNotChangeManagedFields(t *testing.T) { func TestUpdateDoesNotChangeManagedFields(t *testing.T) {
originalManagedFields := []metav1.ManagedFieldsEntry{} originalManagedFields := []metav1.ManagedFieldsEntry{}
f := NewDefaultTestFieldManager( f := fieldmanagertest.NewDefaultTestFieldManager(
schema.FromAPIVersionAndKind("apps/v1", "Deployment")) schema.FromAPIVersionAndKind("apps/v1", "Deployment"))
newObj := &unstructured.Unstructured{ newObj := &unstructured.Unstructured{
Object: map[string]interface{}{}, Object: map[string]interface{}{},
@ -1460,7 +1306,7 @@ func TestUpdateDoesNotChangeManagedFields(t *testing.T) {
// This test makes sure that the liveObject during a patch does not mutate // This test makes sure that the liveObject during a patch does not mutate
// its managed fields. // its managed fields.
func TestLiveObjectManagedFieldsNotRemoved(t *testing.T) { func TestLiveObjectManagedFieldsNotRemoved(t *testing.T) {
f := NewDefaultTestFieldManager( f := fieldmanagertest.NewDefaultTestFieldManager(
schema.FromAPIVersionAndKind("apps/v1", "Deployment")) schema.FromAPIVersionAndKind("apps/v1", "Deployment"))
newObj := &unstructured.Unstructured{ newObj := &unstructured.Unstructured{
Object: map[string]interface{}{}, Object: map[string]interface{}{},
@ -1518,7 +1364,7 @@ func TestLiveObjectManagedFieldsNotRemoved(t *testing.T) {
t.Fatalf("failed to apply object: %v", err) t.Fatalf("failed to apply object: %v", err)
} }
originalLiveObj := f.liveObj originalLiveObj := f.Live()
accessor, err := meta.Accessor(originalLiveObj) accessor, err := meta.Accessor(originalLiveObj)
if err != nil { if err != nil {

View File

@ -0,0 +1,227 @@
/*
Copyright 2022 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 fieldmanagertest
import (
"errors"
"fmt"
"path/filepath"
"strings"
"k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager"
"k8s.io/kube-openapi/pkg/util/proto"
prototesting "k8s.io/kube-openapi/pkg/util/proto/testing"
"sigs.k8s.io/structured-merge-diff/v4/fieldpath"
"sigs.k8s.io/structured-merge-diff/v4/merge"
"sigs.k8s.io/structured-merge-diff/v4/typed"
)
var kubernetesSwaggerSchema = prototesting.Fake{
Path: filepath.Join(
strings.Repeat(".."+string(filepath.Separator), 8),
"api", "openapi-spec", "swagger.json"),
}
// NewBuiltinTypeConverter creates a TypeConverter with all the built-in
// types defined, given the committed kubernetes swagger.json.
func NewBuiltinTypeConverter() fieldmanager.TypeConverter {
tc, err := fieldmanager.NewTypeConverter(newFakeOpenAPIModels(), false)
if err != nil {
panic(fmt.Errorf("Failed to build TypeConverter: %v", err))
}
return tc
}
func newFakeOpenAPIModels() proto.Models {
d, err := kubernetesSwaggerSchema.OpenAPISchema()
if err != nil {
panic(err)
}
m, err := proto.NewOpenAPIData(d)
if err != nil {
panic(err)
}
return m
}
type fakeObjectConvertor struct {
converter merge.Converter
apiVersion fieldpath.APIVersion
}
//nolint:staticcheck,ineffassign // SA4009 backwards compatibility
func (c *fakeObjectConvertor) Convert(in, out, context interface{}) error {
if typedValue, ok := in.(*typed.TypedValue); ok {
var err error
out, err = c.converter.Convert(typedValue, c.apiVersion)
return err
}
return nil
}
func (c *fakeObjectConvertor) ConvertToVersion(in runtime.Object, _ runtime.GroupVersioner) (runtime.Object, error) {
return in, nil
}
func (c *fakeObjectConvertor) ConvertFieldLabel(_ schema.GroupVersionKind, _, _ string) (string, string, error) {
return "", "", errors.New("not implemented")
}
type fakeObjectDefaulter struct{}
func (d *fakeObjectDefaulter) Default(in runtime.Object) {}
type sameVersionConverter struct{}
func (sameVersionConverter) Convert(object *typed.TypedValue, version fieldpath.APIVersion) (*typed.TypedValue, error) {
return object, nil
}
func (sameVersionConverter) IsMissingVersionError(error) bool {
return false
}
// NewFakeObjectCreater implements ObjectCreater, it can create empty
// objects (unstructured) of the given GVK.
func NewFakeObjectCreater() runtime.ObjectCreater {
return &fakeObjectCreater{}
}
type fakeObjectCreater struct{}
func (f *fakeObjectCreater) New(gvk schema.GroupVersionKind) (runtime.Object, error) {
u := unstructured.Unstructured{Object: map[string]interface{}{}}
u.SetAPIVersion(gvk.GroupVersion().String())
u.SetKind(gvk.Kind)
return &u, nil
}
// TestFieldManager is a FieldManager that can be used in test to
// simulate the behavior of Server-Side Apply and field tracking. This
// also has a few methods to get a sense of the state of the object.
//
// This TestFieldManager uses a series of "fake" objects to simulate
// some behavior which come with the limitation that you can only use
// one version since there is no version conversion logic.
//
// You can use this rather than NewDefaultTestFieldManager if you want
// to specify either a sub-resource, or a set of modified Manager to
// test them specifically.
type TestFieldManager struct {
fieldManager *fieldmanager.FieldManager
apiVersion string
emptyObj runtime.Object
liveObj runtime.Object
}
// NewDefaultTestFieldManager returns a new TestFieldManager built for
// the given gvk, on the main resource.
func NewDefaultTestFieldManager(gvk schema.GroupVersionKind) TestFieldManager {
return NewTestFieldManager(gvk, "", nil)
}
// NewTestFieldManager creates a new manager for the given GVK.
func NewTestFieldManager(gvk schema.GroupVersionKind, subresource string, chainFieldManager func(fieldmanager.Manager) fieldmanager.Manager) TestFieldManager {
typeConverter := NewBuiltinTypeConverter()
apiVersion := fieldpath.APIVersion(gvk.GroupVersion().String())
objectConverter := &fakeObjectConvertor{sameVersionConverter{}, apiVersion}
f, err := fieldmanager.NewStructuredMergeManager(
typeConverter,
objectConverter,
&fakeObjectDefaulter{},
gvk.GroupVersion(),
gvk.GroupVersion(),
nil,
)
if err != nil {
panic(err)
}
live := &unstructured.Unstructured{}
live.SetKind(gvk.Kind)
live.SetAPIVersion(gvk.GroupVersion().String())
f = fieldmanager.NewLastAppliedUpdater(
fieldmanager.NewLastAppliedManager(
fieldmanager.NewProbabilisticSkipNonAppliedManager(
fieldmanager.NewBuildManagerInfoManager(
fieldmanager.NewManagedFieldsUpdater(
fieldmanager.NewStripMetaManager(f),
), gvk.GroupVersion(), subresource,
), NewFakeObjectCreater(), gvk, fieldmanager.DefaultTrackOnCreateProbability,
), typeConverter, objectConverter, gvk.GroupVersion(),
),
)
if chainFieldManager != nil {
f = chainFieldManager(f)
}
return TestFieldManager{
fieldManager: fieldmanager.NewFieldManager(f, subresource),
apiVersion: gvk.GroupVersion().String(),
emptyObj: live,
liveObj: live.DeepCopyObject(),
}
}
// APIVersion of the object that we're tracking.
func (f *TestFieldManager) APIVersion() string {
return f.apiVersion
}
// Reset resets the state of the liveObject by resetting it to an empty object.
func (f *TestFieldManager) Reset() {
f.liveObj = f.emptyObj.DeepCopyObject()
}
// Live returns a copy of the current liveObject.
func (f *TestFieldManager) Live() runtime.Object {
return f.liveObj.DeepCopyObject()
}
// Apply applies the given object on top of the current liveObj, for the
// given manager and force flag.
func (f *TestFieldManager) Apply(obj runtime.Object, manager string, force bool) error {
out, err := f.fieldManager.Apply(f.liveObj, obj, manager, force)
if err == nil {
f.liveObj = out
}
return err
}
// Update will updates the managed fields in the liveObj based on the
// changes performed by the update.
func (f *TestFieldManager) Update(obj runtime.Object, manager string) error {
out, err := f.fieldManager.Update(f.liveObj, obj, manager)
if err == nil {
f.liveObj = out
}
return err
}
// ManagedFields returns the list of existing managed fields for the
// liveObj.
func (f *TestFieldManager) ManagedFields() []metav1.ManagedFieldsEntry {
accessor, err := meta.Accessor(f.liveObj)
if err != nil {
panic(fmt.Errorf("couldn't get accessor: %v", err))
}
return accessor.GetManagedFields()
}

View File

@ -0,0 +1,45 @@
/*
Copyright 2022 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 internal
import (
"fmt"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/meta"
apimachineryvalidation "k8s.io/apimachinery/pkg/api/validation"
"k8s.io/apimachinery/pkg/runtime"
)
// SetLastApplied sets the last-applied annotation the given value in
// the object.
func SetLastApplied(obj runtime.Object, value string) error {
accessor, err := meta.Accessor(obj)
if err != nil {
panic(fmt.Sprintf("couldn't get accessor: %v", err))
}
var annotations = accessor.GetAnnotations()
if annotations == nil {
annotations = map[string]string{}
}
annotations[corev1.LastAppliedConfigAnnotation] = value
if err := apimachineryvalidation.ValidateAnnotationsSize(annotations); err != nil {
delete(annotations, corev1.LastAppliedConfigAnnotation)
}
accessor.SetAnnotations(annotations)
return nil
}

View File

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
package fieldmanager package fieldmanager_test
import ( import (
"fmt" "fmt"
@ -25,6 +25,7 @@ import (
apierrors "k8s.io/apimachinery/pkg/api/errors" apierrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/fieldmanagertest"
"k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/internal" "k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/internal"
"sigs.k8s.io/structured-merge-diff/v4/fieldpath" "sigs.k8s.io/structured-merge-diff/v4/fieldpath"
"sigs.k8s.io/structured-merge-diff/v4/merge" "sigs.k8s.io/structured-merge-diff/v4/merge"
@ -43,7 +44,7 @@ type testArgs struct {
// created with the client-side apply last-applied annotation // created with the client-side apply last-applied annotation
// will not give conflicts // will not give conflicts
func TestApplyUsingLastAppliedAnnotation(t *testing.T) { func TestApplyUsingLastAppliedAnnotation(t *testing.T) {
f := NewDefaultTestFieldManager(schema.FromAPIVersionAndKind("apps/v1", "Deployment")) f := fieldmanagertest.NewDefaultTestFieldManager(schema.FromAPIVersionAndKind("apps/v1", "Deployment"))
tests := []testArgs{ tests := []testArgs{
{ {
@ -563,7 +564,7 @@ spec:
} }
func TestServiceApply(t *testing.T) { func TestServiceApply(t *testing.T) {
f := NewDefaultTestFieldManager(schema.FromAPIVersionAndKind("v1", "Service")) f := fieldmanagertest.NewDefaultTestFieldManager(schema.FromAPIVersionAndKind("v1", "Service"))
tests := []testArgs{ tests := []testArgs{
{ {
@ -674,7 +675,7 @@ spec:
} }
func TestReplicationControllerApply(t *testing.T) { func TestReplicationControllerApply(t *testing.T) {
f := NewDefaultTestFieldManager(schema.FromAPIVersionAndKind("v1", "ReplicationController")) f := fieldmanagertest.NewDefaultTestFieldManager(schema.FromAPIVersionAndKind("v1", "ReplicationController"))
tests := []testArgs{ tests := []testArgs{
{ {
@ -737,7 +738,7 @@ spec:
} }
func TestPodApply(t *testing.T) { func TestPodApply(t *testing.T) {
f := NewDefaultTestFieldManager(schema.FromAPIVersionAndKind("v1", "Pod")) f := fieldmanagertest.NewDefaultTestFieldManager(schema.FromAPIVersionAndKind("v1", "Pod"))
tests := []testArgs{ tests := []testArgs{
{ {
@ -914,7 +915,7 @@ spec:
testConflicts(t, f, tests) testConflicts(t, f, tests)
} }
func testConflicts(t *testing.T, f TestFieldManager, tests []testArgs) { func testConflicts(t *testing.T, f fieldmanagertest.TestFieldManager, tests []testArgs) {
for i, test := range tests { for i, test := range tests {
t.Run(fmt.Sprintf("test %d", i), func(t *testing.T) { t.Run(fmt.Sprintf("test %d", i), func(t *testing.T) {
f.Reset() f.Reset()
@ -972,8 +973,8 @@ func testConflicts(t *testing.T, f TestFieldManager, tests []testArgs) {
} }
// Eventually resource should contain applied changes // Eventually resource should contain applied changes
if !apiequality.Semantic.DeepDerivative(appliedObj, f.Get()) { if !apiequality.Semantic.DeepDerivative(appliedObj, f.Live()) {
t.Errorf("expected equal resource: \n%#v, got: \n%#v", appliedObj, f.Get()) t.Errorf("expected equal resource: \n%#v, got: \n%#v", appliedObj, f.Live())
} }
}) })
} }

View File

@ -21,9 +21,9 @@ import (
corev1 "k8s.io/api/core/v1" corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/meta" "k8s.io/apimachinery/pkg/api/meta"
apimachineryvalidation "k8s.io/apimachinery/pkg/api/validation"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime"
"k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/internal"
) )
type lastAppliedUpdater struct { type lastAppliedUpdater struct {
@ -62,7 +62,7 @@ func (f *lastAppliedUpdater) Apply(liveObj, newObj runtime.Object, managed Manag
if err != nil { if err != nil {
return nil, nil, fmt.Errorf("failed to build last-applied annotation: %v", err) return nil, nil, fmt.Errorf("failed to build last-applied annotation: %v", err)
} }
err = setLastApplied(liveObj, lastAppliedValue) err = internal.SetLastApplied(liveObj, lastAppliedValue)
if err != nil { if err != nil {
return nil, nil, fmt.Errorf("failed to set last-applied annotation: %v", err) return nil, nil, fmt.Errorf("failed to set last-applied annotation: %v", err)
} }
@ -83,23 +83,6 @@ func hasLastApplied(obj runtime.Object) bool {
return ok && len(lastApplied) > 0 return ok && len(lastApplied) > 0
} }
func setLastApplied(obj runtime.Object, value string) error {
accessor, err := meta.Accessor(obj)
if err != nil {
panic(fmt.Sprintf("couldn't get accessor: %v", err))
}
var annotations = accessor.GetAnnotations()
if annotations == nil {
annotations = map[string]string{}
}
annotations[corev1.LastAppliedConfigAnnotation] = value
if err := apimachineryvalidation.ValidateAnnotationsSize(annotations); err != nil {
delete(annotations, corev1.LastAppliedConfigAnnotation)
}
accessor.SetAnnotations(annotations)
return nil
}
func buildLastApplied(obj runtime.Object) (string, error) { func buildLastApplied(obj runtime.Object) (string, error) {
obj = obj.DeepCopyObject() obj = obj.DeepCopyObject()

View File

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
package fieldmanager package fieldmanager_test
import ( import (
"fmt" "fmt"
@ -26,14 +26,16 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager"
"k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/fieldmanagertest"
"sigs.k8s.io/yaml" "sigs.k8s.io/yaml"
) )
func TestLastAppliedUpdater(t *testing.T) { func TestLastAppliedUpdater(t *testing.T) {
f := NewTestFieldManager(schema.FromAPIVersionAndKind("apps/v1", "Deployment"), f := fieldmanagertest.NewTestFieldManager(schema.FromAPIVersionAndKind("apps/v1", "Deployment"),
"", "",
func(m Manager) Manager { func(m fieldmanager.Manager) fieldmanager.Manager {
return NewLastAppliedUpdater(m) return fieldmanager.NewLastAppliedUpdater(m)
}) })
originalLastApplied := `nonempty` originalLastApplied := `nonempty`
@ -69,7 +71,7 @@ spec:
t.Errorf("error applying object: %v", err) t.Errorf("error applying object: %v", err)
} }
lastApplied, err := getLastApplied(f.liveObj) lastApplied, err := getLastApplied(f.Live())
if err != nil { if err != nil {
t.Errorf("failed to get last applied: %v", err) t.Errorf("failed to get last applied: %v", err)
} }
@ -82,7 +84,7 @@ spec:
t.Errorf("error applying object: %v", err) t.Errorf("error applying object: %v", err)
} }
lastApplied, err = getLastApplied(f.liveObj) lastApplied, err = getLastApplied(f.Live())
if err != nil { if err != nil {
t.Errorf("failed to get last applied: %v", err) t.Errorf("failed to get last applied: %v", err)
} }
@ -187,17 +189,17 @@ func TestLargeLastApplied(t *testing.T) {
} }
for _, test := range tests { for _, test := range tests {
t.Run(test.name, func(t *testing.T) { t.Run(test.name, func(t *testing.T) {
f := NewTestFieldManager(schema.FromAPIVersionAndKind("v1", "ConfigMap"), f := fieldmanagertest.NewTestFieldManager(schema.FromAPIVersionAndKind("v1", "ConfigMap"),
"", "",
func(m Manager) Manager { func(m fieldmanager.Manager) fieldmanager.Manager {
return NewLastAppliedUpdater(m) return fieldmanager.NewLastAppliedUpdater(m)
}) })
if err := f.Apply(test.oldObject, "kubectl", false); err != nil { if err := f.Apply(test.oldObject, "kubectl", false); err != nil {
t.Errorf("Error applying object: %v", err) t.Errorf("Error applying object: %v", err)
} }
lastApplied, err := getLastApplied(f.liveObj) lastApplied, err := getLastApplied(f.Live())
if err != nil { if err != nil {
t.Errorf("Failed to access last applied annotation: %v", err) t.Errorf("Failed to access last applied annotation: %v", err)
} }
@ -210,12 +212,12 @@ func TestLargeLastApplied(t *testing.T) {
} }
accessor := meta.NewAccessor() accessor := meta.NewAccessor()
annotations, err := accessor.Annotations(f.liveObj) annotations, err := accessor.Annotations(f.Live())
if err != nil { if err != nil {
t.Errorf("Failed to access annotations: %v", err) t.Errorf("Failed to access annotations: %v", err)
} }
if annotations == nil { if annotations == nil {
t.Errorf("No annotations on obj: %v", f.liveObj) t.Errorf("No annotations on obj: %v", f.Live())
} }
lastApplied, ok := annotations[corev1.LastAppliedConfigAnnotation] lastApplied, ok := annotations[corev1.LastAppliedConfigAnnotation]
if ok || len(lastApplied) > 0 { if ok || len(lastApplied) > 0 {

View File

@ -14,26 +14,29 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
package fieldmanager package fieldmanager_test
import ( import (
"fmt" "fmt"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"reflect" "reflect"
"testing" "testing"
"time" "time"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/api/meta" "k8s.io/apimachinery/pkg/api/meta"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager"
"k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/fieldmanagertest"
"k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/internal" "k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/internal"
"sigs.k8s.io/yaml" "sigs.k8s.io/yaml"
) )
func TestManagedFieldsUpdateDoesModifyTime(t *testing.T) { func TestManagedFieldsUpdateDoesModifyTime(t *testing.T) {
var err error var err error
f := NewTestFieldManager(schema.FromAPIVersionAndKind("v1", "ConfigMap"), "", nil) f := fieldmanagertest.NewDefaultTestFieldManager(schema.FromAPIVersionAndKind("v1", "ConfigMap"))
err = updateObject(&f, "fieldmanager_test", []byte(`{ err = updateObject(&f, "fieldmanager_test", []byte(`{
"apiVersion": "v1", "apiVersion": "v1",
@ -74,7 +77,7 @@ func TestManagedFieldsUpdateDoesModifyTime(t *testing.T) {
func TestManagedFieldsApplyDoesModifyTime(t *testing.T) { func TestManagedFieldsApplyDoesModifyTime(t *testing.T) {
var err error var err error
f := NewTestFieldManager(schema.FromAPIVersionAndKind("v1", "ConfigMap"), "", nil) f := fieldmanagertest.NewDefaultTestFieldManager(schema.FromAPIVersionAndKind("v1", "ConfigMap"))
err = applyObject(&f, "fieldmanager_test", []byte(`{ err = applyObject(&f, "fieldmanager_test", []byte(`{
"apiVersion": "v1", "apiVersion": "v1",
@ -115,7 +118,7 @@ func TestManagedFieldsApplyDoesModifyTime(t *testing.T) {
func TestManagedFieldsUpdateWithoutChangesDoesNotModifyTime(t *testing.T) { func TestManagedFieldsUpdateWithoutChangesDoesNotModifyTime(t *testing.T) {
var err error var err error
f := NewTestFieldManager(schema.FromAPIVersionAndKind("v1", "ConfigMap"), "", nil) f := fieldmanagertest.NewDefaultTestFieldManager(schema.FromAPIVersionAndKind("v1", "ConfigMap"))
err = updateObject(&f, "fieldmanager_test", []byte(`{ err = updateObject(&f, "fieldmanager_test", []byte(`{
"apiVersion": "v1", "apiVersion": "v1",
@ -156,7 +159,7 @@ func TestManagedFieldsUpdateWithoutChangesDoesNotModifyTime(t *testing.T) {
func TestManagedFieldsApplyWithoutChangesDoesNotModifyTime(t *testing.T) { func TestManagedFieldsApplyWithoutChangesDoesNotModifyTime(t *testing.T) {
var err error var err error
f := NewTestFieldManager(schema.FromAPIVersionAndKind("v1", "ConfigMap"), "", nil) f := fieldmanagertest.NewDefaultTestFieldManager(schema.FromAPIVersionAndKind("v1", "ConfigMap"))
err = applyObject(&f, "fieldmanager_test", []byte(`{ err = applyObject(&f, "fieldmanager_test", []byte(`{
"apiVersion": "v1", "apiVersion": "v1",
@ -197,7 +200,7 @@ func TestManagedFieldsApplyWithoutChangesDoesNotModifyTime(t *testing.T) {
func TestNonManagedFieldsUpdateDoesNotModifyTime(t *testing.T) { func TestNonManagedFieldsUpdateDoesNotModifyTime(t *testing.T) {
var err error var err error
f := NewTestFieldManager(schema.FromAPIVersionAndKind("v1", "ConfigMap"), "", nil) f := fieldmanagertest.NewDefaultTestFieldManager(schema.FromAPIVersionAndKind("v1", "ConfigMap"))
err = updateObject(&f, "fieldmanager_a_test", []byte(`{ err = updateObject(&f, "fieldmanager_a_test", []byte(`{
"apiVersion": "v1", "apiVersion": "v1",
@ -260,7 +263,7 @@ func TestNonManagedFieldsUpdateDoesNotModifyTime(t *testing.T) {
func TestNonManagedFieldsApplyDoesNotModifyTime(t *testing.T) { func TestNonManagedFieldsApplyDoesNotModifyTime(t *testing.T) {
var err error var err error
f := NewTestFieldManager(schema.FromAPIVersionAndKind("v1", "ConfigMap"), "", nil) f := fieldmanagertest.NewDefaultTestFieldManager(schema.FromAPIVersionAndKind("v1", "ConfigMap"))
err = applyObject(&f, "fieldmanager_a_test", []byte(`{ err = applyObject(&f, "fieldmanager_a_test", []byte(`{
"apiVersion": "v1", "apiVersion": "v1",
@ -323,7 +326,7 @@ func TestNonManagedFieldsApplyDoesNotModifyTime(t *testing.T) {
func TestTakingOverManagedFieldsDuringUpdateDoesNotModifyPreviousManagerTime(t *testing.T) { func TestTakingOverManagedFieldsDuringUpdateDoesNotModifyPreviousManagerTime(t *testing.T) {
var err error var err error
f := NewTestFieldManager(schema.FromAPIVersionAndKind("v1", "ConfigMap"), "", nil) f := fieldmanagertest.NewDefaultTestFieldManager(schema.FromAPIVersionAndKind("v1", "ConfigMap"))
err = updateObject(&f, "fieldmanager_a_test", []byte(`{ err = updateObject(&f, "fieldmanager_a_test", []byte(`{
"apiVersion": "v1", "apiVersion": "v1",
@ -374,7 +377,7 @@ func TestTakingOverManagedFieldsDuringUpdateDoesNotModifyPreviousManagerTime(t *
func TestTakingOverManagedFieldsDuringApplyDoesNotModifyPreviousManagerTime(t *testing.T) { func TestTakingOverManagedFieldsDuringApplyDoesNotModifyPreviousManagerTime(t *testing.T) {
var err error var err error
f := NewTestFieldManager(schema.FromAPIVersionAndKind("v1", "ConfigMap"), "", nil) f := fieldmanagertest.NewDefaultTestFieldManager(schema.FromAPIVersionAndKind("v1", "ConfigMap"))
err = applyObject(&f, "fieldmanager_a_test", []byte(`{ err = applyObject(&f, "fieldmanager_a_test", []byte(`{
"apiVersion": "v1", "apiVersion": "v1",
@ -425,15 +428,15 @@ func TestTakingOverManagedFieldsDuringApplyDoesNotModifyPreviousManagerTime(t *t
type NoopManager struct{} type NoopManager struct{}
func (NoopManager) Apply(liveObj, appliedObj runtime.Object, managed Managed, fieldManager string, force bool) (runtime.Object, Managed, error) { func (NoopManager) Apply(liveObj, appliedObj runtime.Object, managed fieldmanager.Managed, fieldManager string, force bool) (runtime.Object, fieldmanager.Managed, error) {
return nil, managed, nil return nil, managed, nil
} }
func (NoopManager) Update(liveObj, newObj runtime.Object, managed Managed, manager string) (runtime.Object, Managed, error) { func (NoopManager) Update(liveObj, newObj runtime.Object, managed fieldmanager.Managed, manager string) (runtime.Object, fieldmanager.Managed, error) {
return nil, nil, nil return nil, nil, nil
} }
func updateObject(f *TestFieldManager, fieldManagerName string, object []byte) error { func updateObject(f *fieldmanagertest.TestFieldManager, fieldManagerName string, object []byte) error {
obj := &unstructured.Unstructured{Object: map[string]interface{}{}} obj := &unstructured.Unstructured{Object: map[string]interface{}{}}
if err := yaml.Unmarshal(object, &obj.Object); err != nil { if err := yaml.Unmarshal(object, &obj.Object); err != nil {
return fmt.Errorf("error decoding YAML: %v", err) return fmt.Errorf("error decoding YAML: %v", err)
@ -444,7 +447,7 @@ func updateObject(f *TestFieldManager, fieldManagerName string, object []byte) e
return nil return nil
} }
func applyObject(f *TestFieldManager, fieldManagerName string, object []byte) error { func applyObject(f *fieldmanagertest.TestFieldManager, fieldManagerName string, object []byte) error {
obj := &unstructured.Unstructured{Object: map[string]interface{}{}} obj := &unstructured.Unstructured{Object: map[string]interface{}{}}
if err := yaml.Unmarshal(object, &obj.Object); err != nil { if err := yaml.Unmarshal(object, &obj.Object); err != nil {
return fmt.Errorf("error decoding YAML: %v", err) return fmt.Errorf("error decoding YAML: %v", err)
@ -495,12 +498,12 @@ func TestNilNewObjectReplacedWithDeepCopyExcludingManagedFields(t *testing.T) {
} }
// Decode the managed fields in the live object, since it isn't allowed in the patch. // Decode the managed fields in the live object, since it isn't allowed in the patch.
managed, err := DecodeManagedFields(accessor.GetManagedFields()) managed, err := fieldmanager.DecodeManagedFields(accessor.GetManagedFields())
if err != nil { if err != nil {
t.Fatalf("failed to decode managed fields: %v", err) t.Fatalf("failed to decode managed fields: %v", err)
} }
updater := NewManagedFieldsUpdater(NoopManager{}) updater := fieldmanager.NewManagedFieldsUpdater(NoopManager{})
newObject, _, err := updater.Apply(obj, obj.DeepCopyObject(), managed, "some_manager", false) newObject, _, err := updater.Apply(obj, obj.DeepCopyObject(), managed, "some_manager", false)
if err != nil { if err != nil {

View File

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
package fieldmanager package fieldmanager_test
import ( import (
"strings" "strings"
@ -23,30 +23,18 @@ import (
corev1 "k8s.io/api/core/v1" corev1 "k8s.io/api/core/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors" apierrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager"
"k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/fieldmanagertest"
"sigs.k8s.io/yaml" "sigs.k8s.io/yaml"
) )
type fakeObjectCreater struct {
gvk schema.GroupVersionKind
}
var _ runtime.ObjectCreater = &fakeObjectCreater{}
func (f *fakeObjectCreater) New(_ schema.GroupVersionKind) (runtime.Object, error) {
u := unstructured.Unstructured{Object: map[string]interface{}{}}
u.SetAPIVersion(f.gvk.GroupVersion().String())
u.SetKind(f.gvk.Kind)
return &u, nil
}
func TestNoUpdateBeforeFirstApply(t *testing.T) { func TestNoUpdateBeforeFirstApply(t *testing.T) {
f := NewTestFieldManager(schema.FromAPIVersionAndKind("v1", "Pod"), "", func(m Manager) Manager { f := fieldmanagertest.NewTestFieldManager(schema.FromAPIVersionAndKind("v1", "Pod"), "", func(m fieldmanager.Manager) fieldmanager.Manager {
return NewSkipNonAppliedManager( return fieldmanager.NewSkipNonAppliedManager(
m, m,
&fakeObjectCreater{gvk: schema.GroupVersionKind{Version: "v1", Kind: "Pod"}}, fieldmanagertest.NewFakeObjectCreater(),
schema.GroupVersionKind{}, schema.FromAPIVersionAndKind("v1", "Pod"),
) )
}) })
@ -82,11 +70,11 @@ func TestNoUpdateBeforeFirstApply(t *testing.T) {
} }
func TestUpdateBeforeFirstApply(t *testing.T) { func TestUpdateBeforeFirstApply(t *testing.T) {
f := NewTestFieldManager(schema.FromAPIVersionAndKind("v1", "Pod"), "", func(m Manager) Manager { f := fieldmanagertest.NewTestFieldManager(schema.FromAPIVersionAndKind("v1", "Pod"), "", func(m fieldmanager.Manager) fieldmanager.Manager {
return NewSkipNonAppliedManager( return fieldmanager.NewSkipNonAppliedManager(
m, m,
&fakeObjectCreater{gvk: schema.GroupVersionKind{Version: "v1", Kind: "Pod"}}, fieldmanagertest.NewFakeObjectCreater(),
schema.GroupVersionKind{}, schema.FromAPIVersionAndKind("v1", "Pod"),
) )
}) })