mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-27 05:27:21 +00:00
Merge pull request #91690 from apelisse/ignore-failures
fieldManager: Ignore and log all errors when updating managedFields
This commit is contained in:
commit
d585527c70
@ -156,10 +156,7 @@ func createHandler(r rest.NamedCreater, scope *RequestScope, admit admission.Int
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to create new object (Create for %v): %v", scope.Kind, err)
|
return nil, fmt.Errorf("failed to create new object (Create for %v): %v", scope.Kind, err)
|
||||||
}
|
}
|
||||||
obj, err = scope.FieldManager.Update(liveObj, obj, managerOrUserAgent(options.FieldManager, req.UserAgent()))
|
obj = scope.FieldManager.UpdateNoErrors(liveObj, obj, managerOrUserAgent(options.FieldManager, req.UserAgent()))
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to update object (Create for %v) managed fields: %v", scope.Kind, err)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if mutatingAdmission, ok := admit.(admission.MutationInterface); ok && mutatingAdmission.Handles(admission.Create) {
|
if mutatingAdmission, ok := admit.(admission.MutationInterface); ok && mutatingAdmission.Handles(admission.Create) {
|
||||||
if err := mutatingAdmission.Admit(ctx, admissionAttributes, scope); err != nil {
|
if err := mutatingAdmission.Admit(ctx, admissionAttributes, scope); err != nil {
|
||||||
|
@ -18,12 +18,14 @@ package fieldmanager
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
"k8s.io/apimachinery/pkg/api/meta"
|
"k8s.io/apimachinery/pkg/api/meta"
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
"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/internal"
|
"k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/internal"
|
||||||
|
"k8s.io/klog/v2"
|
||||||
openapiproto "k8s.io/kube-openapi/pkg/util/proto"
|
openapiproto "k8s.io/kube-openapi/pkg/util/proto"
|
||||||
"sigs.k8s.io/structured-merge-diff/v3/fieldpath"
|
"sigs.k8s.io/structured-merge-diff/v3/fieldpath"
|
||||||
)
|
)
|
||||||
@ -37,6 +39,8 @@ const DefaultMaxUpdateManagers int = 10
|
|||||||
// starts being tracked from the object's creation, instead of from the first time the object is applied to.
|
// starts being tracked from the object's creation, instead of from the first time the object is applied to.
|
||||||
const DefaultTrackOnCreateProbability float32 = 1
|
const DefaultTrackOnCreateProbability float32 = 1
|
||||||
|
|
||||||
|
var atMostEverySecond = internal.NewAtMostEvery(time.Second)
|
||||||
|
|
||||||
// Managed groups a fieldpath.ManagedFields together with the timestamps associated with each operation.
|
// Managed groups a fieldpath.ManagedFields together with the timestamps associated with each operation.
|
||||||
type Managed interface {
|
type Managed interface {
|
||||||
// Fields gets the fieldpath.ManagedFields.
|
// Fields gets the fieldpath.ManagedFields.
|
||||||
@ -120,7 +124,9 @@ func (f *FieldManager) Update(liveObj, newObj runtime.Object, manager string) (o
|
|||||||
// don't understand managedFields from deleting it accidentally.
|
// don't understand managedFields from deleting it accidentally.
|
||||||
managed, err = internal.DecodeObjectManagedFields(liveObj)
|
managed, err = internal.DecodeObjectManagedFields(liveObj)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to decode managed fields: %v", err)
|
// If we also can't decode the liveObject, then
|
||||||
|
// just restart managedFields from scratch.
|
||||||
|
managed = internal.NewEmptyManaged()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -138,6 +144,25 @@ func (f *FieldManager) Update(liveObj, newObj runtime.Object, manager string) (o
|
|||||||
return object, nil
|
return object, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UpdateNoErrors is the same as Update, but it will not return
|
||||||
|
// errors. If an error happens, the object is returned with
|
||||||
|
// managedFields cleared.
|
||||||
|
func (f *FieldManager) UpdateNoErrors(liveObj, newObj runtime.Object, manager string) runtime.Object {
|
||||||
|
obj, err := f.Update(liveObj, newObj, manager)
|
||||||
|
if err != nil {
|
||||||
|
atMostEverySecond.Do(func() {
|
||||||
|
klog.Errorf("[SHOULD NOT HAPPEN] failed to update managedFields for %v: %v",
|
||||||
|
newObj.GetObjectKind().GroupVersionKind(),
|
||||||
|
err)
|
||||||
|
})
|
||||||
|
// Explicitly remove managedFields on failure, so that
|
||||||
|
// we can't have garbage in it.
|
||||||
|
internal.RemoveObjectManagedFields(newObj)
|
||||||
|
return newObj
|
||||||
|
}
|
||||||
|
return obj
|
||||||
|
}
|
||||||
|
|
||||||
// Apply is used when server-side apply is called, as it merges the
|
// Apply is used when server-side apply is called, as it merges the
|
||||||
// object and updates the managed fields.
|
// object and updates the managed fields.
|
||||||
func (f *FieldManager) Apply(liveObj, appliedObj runtime.Object, manager string, force bool) (object runtime.Object, err error) {
|
func (f *FieldManager) Apply(liveObj, appliedObj runtime.Object, manager string, force bool) (object runtime.Object, err error) {
|
||||||
|
@ -53,6 +53,11 @@ func (m *managedStruct) Times() map[string]*metav1.Time {
|
|||||||
return m.times
|
return m.times
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NewEmptyManaged creates an empty ManagedInterface.
|
||||||
|
func NewEmptyManaged() ManagedInterface {
|
||||||
|
return NewManaged(fieldpath.ManagedFields{}, map[string]*metav1.Time{})
|
||||||
|
}
|
||||||
|
|
||||||
// NewManaged creates a ManagedInterface from a fieldpath.ManagedFields and the timestamps associated with each operation.
|
// NewManaged creates a ManagedInterface from a fieldpath.ManagedFields and the timestamps associated with each operation.
|
||||||
func NewManaged(f fieldpath.ManagedFields, t map[string]*metav1.Time) ManagedInterface {
|
func NewManaged(f fieldpath.ManagedFields, t map[string]*metav1.Time) ManagedInterface {
|
||||||
return &managedStruct{
|
return &managedStruct{
|
||||||
|
@ -18,14 +18,12 @@ package fieldmanager
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"time"
|
|
||||||
|
|
||||||
"k8s.io/apimachinery/pkg/api/errors"
|
"k8s.io/apimachinery/pkg/api/errors"
|
||||||
"k8s.io/apimachinery/pkg/api/meta"
|
"k8s.io/apimachinery/pkg/api/meta"
|
||||||
"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/internal"
|
"k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/internal"
|
||||||
"k8s.io/klog/v2"
|
|
||||||
openapiproto "k8s.io/kube-openapi/pkg/util/proto"
|
openapiproto "k8s.io/kube-openapi/pkg/util/proto"
|
||||||
"sigs.k8s.io/structured-merge-diff/v3/fieldpath"
|
"sigs.k8s.io/structured-merge-diff/v3/fieldpath"
|
||||||
"sigs.k8s.io/structured-merge-diff/v3/merge"
|
"sigs.k8s.io/structured-merge-diff/v3/merge"
|
||||||
@ -41,7 +39,6 @@ type structuredMergeManager struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var _ Manager = &structuredMergeManager{}
|
var _ Manager = &structuredMergeManager{}
|
||||||
var atMostEverySecond = internal.NewAtMostEvery(time.Second)
|
|
||||||
|
|
||||||
// NewStructuredMergeManager creates a new Manager that merges apply requests
|
// NewStructuredMergeManager creates a new Manager that merges apply requests
|
||||||
// and update managed fields for other types of requests.
|
// and update managed fields for other types of requests.
|
||||||
@ -98,19 +95,11 @@ func (f *structuredMergeManager) Update(liveObj, newObj runtime.Object, managed
|
|||||||
}
|
}
|
||||||
newObjTyped, err := f.typeConverter.ObjectToTyped(newObjVersioned)
|
newObjTyped, err := f.typeConverter.ObjectToTyped(newObjVersioned)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// Return newObj and just by-pass fields update. This really shouldn't happen.
|
return nil, nil, fmt.Errorf("failed to convert new object (%v) to smd typed: %v", newObjVersioned.GetObjectKind().GroupVersionKind(), err)
|
||||||
atMostEverySecond.Do(func() {
|
|
||||||
klog.Errorf("[SHOULD NOT HAPPEN] failed to create typed new object of type %v: %v", newObjVersioned.GetObjectKind().GroupVersionKind(), err)
|
|
||||||
})
|
|
||||||
return newObj, managed, nil
|
|
||||||
}
|
}
|
||||||
liveObjTyped, err := f.typeConverter.ObjectToTyped(liveObjVersioned)
|
liveObjTyped, err := f.typeConverter.ObjectToTyped(liveObjVersioned)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// Return newObj and just by-pass fields update. This really shouldn't happen.
|
return nil, nil, fmt.Errorf("failed to convert live object (%v) to smd typed: %v", liveObjVersioned.GetObjectKind().GroupVersionKind(), err)
|
||||||
atMostEverySecond.Do(func() {
|
|
||||||
klog.Errorf("[SHOULD NOT HAPPEN] failed to create typed live object of type %v: %v", liveObjVersioned.GetObjectKind().GroupVersionKind(), err)
|
|
||||||
})
|
|
||||||
return newObj, managed, nil
|
|
||||||
}
|
}
|
||||||
apiVersion := fieldpath.APIVersion(f.groupVersion.String())
|
apiVersion := fieldpath.APIVersion(f.groupVersion.String())
|
||||||
|
|
||||||
|
@ -323,9 +323,7 @@ func (p *jsonPatcher) applyPatchToCurrentObject(currentObject runtime.Object) (r
|
|||||||
}
|
}
|
||||||
|
|
||||||
if p.fieldManager != nil {
|
if p.fieldManager != nil {
|
||||||
if objToUpdate, err = p.fieldManager.Update(currentObject, objToUpdate, managerOrUserAgent(p.options.FieldManager, p.userAgent)); err != nil {
|
objToUpdate = p.fieldManager.UpdateNoErrors(currentObject, objToUpdate, managerOrUserAgent(p.options.FieldManager, p.userAgent))
|
||||||
return nil, fmt.Errorf("failed to update object (json PATCH for %v) managed fields: %v", p.kind, err)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return objToUpdate, nil
|
return objToUpdate, nil
|
||||||
}
|
}
|
||||||
@ -408,9 +406,7 @@ func (p *smpPatcher) applyPatchToCurrentObject(currentObject runtime.Object) (ru
|
|||||||
}
|
}
|
||||||
|
|
||||||
if p.fieldManager != nil {
|
if p.fieldManager != nil {
|
||||||
if newObj, err = p.fieldManager.Update(currentObject, newObj, managerOrUserAgent(p.options.FieldManager, p.userAgent)); err != nil {
|
newObj = p.fieldManager.UpdateNoErrors(currentObject, newObj, managerOrUserAgent(p.options.FieldManager, p.userAgent))
|
||||||
return nil, fmt.Errorf("failed to update object (smp PATCH for %v) managed fields: %v", p.kind, err)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return newObj, nil
|
return newObj, nil
|
||||||
}
|
}
|
||||||
|
@ -131,11 +131,7 @@ func UpdateResource(r rest.Updater, scope *RequestScope, admit admission.Interfa
|
|||||||
if scope.FieldManager != nil {
|
if scope.FieldManager != nil {
|
||||||
transformers = append(transformers, func(_ context.Context, newObj, liveObj runtime.Object) (runtime.Object, error) {
|
transformers = append(transformers, func(_ context.Context, newObj, liveObj runtime.Object) (runtime.Object, error) {
|
||||||
if shouldUpdateManagedFields {
|
if shouldUpdateManagedFields {
|
||||||
obj, err := scope.FieldManager.Update(liveObj, newObj, managerOrUserAgent(options.FieldManager, req.UserAgent()))
|
return scope.FieldManager.UpdateNoErrors(liveObj, newObj, managerOrUserAgent(options.FieldManager, req.UserAgent())), nil
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to update object (Update for %v) managed fields: %v", scope.Kind, err)
|
|
||||||
}
|
|
||||||
return obj, nil
|
|
||||||
}
|
}
|
||||||
return newObj, nil
|
return newObj, nil
|
||||||
})
|
})
|
||||||
|
@ -1422,6 +1422,162 @@ func TestClearManagedFieldsWithUpdate(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestErrorsDontFail
|
||||||
|
func TestErrorsDontFail(t *testing.T) {
|
||||||
|
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, genericfeatures.ServerSideApply, true)()
|
||||||
|
|
||||||
|
_, client, closeFn := setup(t)
|
||||||
|
defer closeFn()
|
||||||
|
|
||||||
|
// Tries to create with a managed fields that has an empty `fieldsType`.
|
||||||
|
_, err := client.CoreV1().RESTClient().Post().
|
||||||
|
Namespace("default").
|
||||||
|
Resource("configmaps").
|
||||||
|
Param("fieldManager", "apply_test").
|
||||||
|
Body([]byte(`{
|
||||||
|
"apiVersion": "v1",
|
||||||
|
"kind": "ConfigMap",
|
||||||
|
"metadata": {
|
||||||
|
"name": "test-cm",
|
||||||
|
"namespace": "default",
|
||||||
|
"managedFields": [{
|
||||||
|
"manager": "apply_test",
|
||||||
|
"operation": "Apply",
|
||||||
|
"apiVersion": "v1",
|
||||||
|
"time": "2019-07-08T09:31:18Z",
|
||||||
|
"fieldsType": "",
|
||||||
|
"fieldsV1": {}
|
||||||
|
}],
|
||||||
|
"labels": {
|
||||||
|
"test-label": "test"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"key": "value"
|
||||||
|
}
|
||||||
|
}`)).
|
||||||
|
Do(context.TODO()).
|
||||||
|
Get()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to create object with empty fieldsType: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestErrorsDontFailUpdate(t *testing.T) {
|
||||||
|
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, genericfeatures.ServerSideApply, true)()
|
||||||
|
|
||||||
|
_, client, closeFn := setup(t)
|
||||||
|
defer closeFn()
|
||||||
|
|
||||||
|
_, err := client.CoreV1().RESTClient().Post().
|
||||||
|
Namespace("default").
|
||||||
|
Resource("configmaps").
|
||||||
|
Param("fieldManager", "apply_test").
|
||||||
|
Body([]byte(`{
|
||||||
|
"apiVersion": "v1",
|
||||||
|
"kind": "ConfigMap",
|
||||||
|
"metadata": {
|
||||||
|
"name": "test-cm",
|
||||||
|
"namespace": "default",
|
||||||
|
"labels": {
|
||||||
|
"test-label": "test"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"key": "value"
|
||||||
|
}
|
||||||
|
}`)).
|
||||||
|
Do(context.TODO()).
|
||||||
|
Get()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to create object: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = client.CoreV1().RESTClient().Put().
|
||||||
|
Namespace("default").
|
||||||
|
Resource("configmaps").
|
||||||
|
Name("test-cm").
|
||||||
|
Param("fieldManager", "apply_test").
|
||||||
|
Body([]byte(`{
|
||||||
|
"apiVersion": "v1",
|
||||||
|
"kind": "ConfigMap",
|
||||||
|
"metadata": {
|
||||||
|
"name": "test-cm",
|
||||||
|
"namespace": "default",
|
||||||
|
"managedFields": [{
|
||||||
|
"manager": "apply_test",
|
||||||
|
"operation": "Apply",
|
||||||
|
"apiVersion": "v1",
|
||||||
|
"time": "2019-07-08T09:31:18Z",
|
||||||
|
"fieldsType": "",
|
||||||
|
"fieldsV1": {}
|
||||||
|
}],
|
||||||
|
"labels": {
|
||||||
|
"test-label": "test"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"key": "value"
|
||||||
|
}
|
||||||
|
}`)).
|
||||||
|
Do(context.TODO()).
|
||||||
|
Get()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to update object with empty fieldsType: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestErrorsDontFailPatch(t *testing.T) {
|
||||||
|
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, genericfeatures.ServerSideApply, true)()
|
||||||
|
|
||||||
|
_, client, closeFn := setup(t)
|
||||||
|
defer closeFn()
|
||||||
|
|
||||||
|
_, err := client.CoreV1().RESTClient().Post().
|
||||||
|
Namespace("default").
|
||||||
|
Resource("configmaps").
|
||||||
|
Param("fieldManager", "apply_test").
|
||||||
|
Body([]byte(`{
|
||||||
|
"apiVersion": "v1",
|
||||||
|
"kind": "ConfigMap",
|
||||||
|
"metadata": {
|
||||||
|
"name": "test-cm",
|
||||||
|
"namespace": "default",
|
||||||
|
"labels": {
|
||||||
|
"test-label": "test"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"key": "value"
|
||||||
|
}
|
||||||
|
}`)).
|
||||||
|
Do(context.TODO()).
|
||||||
|
Get()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to create object: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = client.CoreV1().RESTClient().Patch(types.JSONPatchType).
|
||||||
|
Namespace("default").
|
||||||
|
Resource("configmaps").
|
||||||
|
Name("test-cm").
|
||||||
|
Param("fieldManager", "apply_test").
|
||||||
|
Body([]byte(`[{"op": "replace", "path": "/metadata/managedFields", "value": [{
|
||||||
|
"manager": "apply_test",
|
||||||
|
"operation": "Apply",
|
||||||
|
"apiVersion": "v1",
|
||||||
|
"time": "2019-07-08T09:31:18Z",
|
||||||
|
"fieldsType": "",
|
||||||
|
"fieldsV1": {}
|
||||||
|
}]}]`)).
|
||||||
|
Do(context.TODO()).
|
||||||
|
Get()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to patch object with empty FieldsType: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
var podBytes = []byte(`
|
var podBytes = []byte(`
|
||||||
apiVersion: v1
|
apiVersion: v1
|
||||||
kind: Pod
|
kind: Pod
|
||||||
|
Loading…
Reference in New Issue
Block a user