Skip to content

Commit b34d7c1

Browse files
authored
Merge pull request #526 from sttts/sttts-managed-reconcile-avoid-temporary-data-loss
reconciler/managed: avoid temporary data loss to managed on annotation update
2 parents d4164c1 + 5b4ebc1 commit b34d7c1

File tree

2 files changed

+49
-19
lines changed

2 files changed

+49
-19
lines changed

pkg/reconciler/managed/api.go

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import (
2222
"github.com/google/go-cmp/cmp"
2323
"github.com/google/go-cmp/cmp/cmpopts"
2424
corev1 "k8s.io/api/core/v1"
25+
kerrors "k8s.io/apimachinery/pkg/api/errors"
2526
"k8s.io/apimachinery/pkg/runtime"
2627
"k8s.io/apimachinery/pkg/types"
2728
"k8s.io/client-go/util/retry"
@@ -167,18 +168,19 @@ func NewRetryingCriticalAnnotationUpdater(c client.Client) *RetryingCriticalAnno
167168

168169
// UpdateCriticalAnnotations updates (i.e. persists) the annotations of the
169170
// supplied Object. It retries in the face of any API server error several times
170-
// in order to ensure annotations that contain critical state are persisted. Any
171-
// pending changes to the supplied Object's spec, status, or other metadata are
172-
// reset to their current state according to the API server.
171+
// in order to ensure annotations that contain critical state are persisted.
172+
// Pending changes to the supplied Object's spec, status, or other metadata
173+
// might get reset to their current state according to the API server, e.g. in
174+
// case of a conflict error.
173175
func (u *RetryingCriticalAnnotationUpdater) UpdateCriticalAnnotations(ctx context.Context, o client.Object) error {
174176
a := o.GetAnnotations()
175177
err := retry.OnError(retry.DefaultRetry, resource.IsAPIError, func() error {
176-
nn := types.NamespacedName{Name: o.GetName()}
177-
if err := u.client.Get(ctx, nn, o); err != nil {
178-
return err
178+
err := u.client.Update(ctx, o)
179+
if kerrors.IsConflict(err) {
180+
err = u.client.Get(ctx, types.NamespacedName{Name: o.GetName()}, o)
181+
meta.AddAnnotations(o, a)
179182
}
180-
meta.AddAnnotations(o, a)
181-
return u.client.Update(ctx, o)
183+
return err
182184
})
183185
return errors.Wrap(err, errUpdateCriticalAnnotations)
184186
}

pkg/reconciler/managed/api_test.go

Lines changed: 39 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,10 @@ import (
2121
"testing"
2222

2323
"github.com/google/go-cmp/cmp"
24+
kerrors "k8s.io/apimachinery/pkg/api/errors"
2425
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2526
"k8s.io/apimachinery/pkg/runtime"
27+
"k8s.io/apimachinery/pkg/runtime/schema"
2628
"sigs.k8s.io/controller-runtime/pkg/client"
2729

2830
xpv1 "github.com/crossplane/crossplane-runtime/apis/common/v1"
@@ -356,59 +358,85 @@ func TestResolveReferences(t *testing.T) {
356358
}
357359

358360
func TestRetryingCriticalAnnotationUpdater(t *testing.T) {
359-
360361
errBoom := errors.New("boom")
361362

362363
type args struct {
363364
ctx context.Context
364365
o client.Object
365366
}
367+
type want struct {
368+
err error
369+
o client.Object
370+
}
371+
372+
setLabels := func(obj client.Object) error {
373+
obj.SetLabels(map[string]string{"getcalled": "true"})
374+
return nil
375+
}
376+
objectReturnedByGet := &fake.Managed{}
377+
setLabels(objectReturnedByGet)
366378

367379
cases := map[string]struct {
368380
reason string
369-
c client.Client
381+
c *test.MockClient
370382
args args
371-
want error
383+
want want
372384
}{
373-
"GetError": {
385+
"UpdateConflictGetError": {
374386
reason: "We should return any error we encounter getting the supplied object",
375387
c: &test.MockClient{
376-
MockGet: test.NewMockGetFn(errBoom),
388+
MockGet: test.NewMockGetFn(errBoom, setLabels),
389+
MockUpdate: test.NewMockUpdateFn(kerrors.NewConflict(schema.GroupResource{
390+
Group: "foo.com",
391+
Resource: "bars",
392+
}, "abc", errBoom)),
377393
},
378394
args: args{
379395
o: &fake.Managed{},
380396
},
381-
want: errors.Wrap(errBoom, errUpdateCriticalAnnotations),
397+
want: want{
398+
err: errors.Wrap(errBoom, errUpdateCriticalAnnotations),
399+
o: objectReturnedByGet,
400+
},
382401
},
383402
"UpdateError": {
384403
reason: "We should return any error we encounter updating the supplied object",
385404
c: &test.MockClient{
386-
MockGet: test.NewMockGetFn(nil),
405+
MockGet: test.NewMockGetFn(nil, setLabels),
387406
MockUpdate: test.NewMockUpdateFn(errBoom),
388407
},
389408
args: args{
390409
o: &fake.Managed{},
391410
},
392-
want: errors.Wrap(errBoom, errUpdateCriticalAnnotations),
411+
want: want{
412+
err: errors.Wrap(errBoom, errUpdateCriticalAnnotations),
413+
o: &fake.Managed{},
414+
},
393415
},
394416
"Success": {
395417
reason: "We should return without error if we successfully update our annotations",
396418
c: &test.MockClient{
397-
MockGet: test.NewMockGetFn(nil),
419+
MockGet: test.NewMockGetFn(nil, setLabels),
398420
MockUpdate: test.NewMockUpdateFn(errBoom),
399421
},
400422
args: args{
401423
o: &fake.Managed{},
402424
},
403-
want: errors.Wrap(errBoom, errUpdateCriticalAnnotations),
425+
want: want{
426+
err: errors.Wrap(errBoom, errUpdateCriticalAnnotations),
427+
o: &fake.Managed{},
428+
},
404429
},
405430
}
406431

407432
for name, tc := range cases {
408433
t.Run(name, func(t *testing.T) {
409434
u := NewRetryingCriticalAnnotationUpdater(tc.c)
410435
got := u.UpdateCriticalAnnotations(tc.args.ctx, tc.args.o)
411-
if diff := cmp.Diff(tc.want, got, test.EquateErrors()); diff != "" {
436+
if diff := cmp.Diff(tc.want.err, got, test.EquateErrors()); diff != "" {
437+
t.Errorf("\n%s\nu.UpdateCriticalAnnotations(...): -want, +got:\n%s", tc.reason, diff)
438+
}
439+
if diff := cmp.Diff(tc.want.o, tc.args.o); diff != "" {
412440
t.Errorf("\n%s\nu.UpdateCriticalAnnotations(...): -want, +got:\n%s", tc.reason, diff)
413441
}
414442
})

0 commit comments

Comments
 (0)