Skip to content

Commit 89fb5ef

Browse files
authored
Resolve references again only when resource is patched with finalizer (#80)
Issue #, if available: aws-controllers-k8s/community#1234 Description of changes: * Keeps existing functionality but adds a condition to check for finalizer before adding the finalizer and resolving the references again. * If the resource is not getting patched again, there is no need for resolving the references again * Adds Unit test cases for Create behavior of both managed and unmanaged resources ---- * Original issue [#1187](aws-controllers-k8s/community#1187) explaining why references are resolved again during resource creation. By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.
1 parent a0caf01 commit 89fb5ef

File tree

2 files changed

+93
-10
lines changed

2 files changed

+93
-10
lines changed

pkg/runtime/reconciler.go

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -351,17 +351,23 @@ func (r *resourceReconciler) createResource(
351351
// finalizer to the CR; a finalizer that is removed once ACK no longer
352352
// manages the resource OR if the backend AWS service resource is
353353
// properly deleted.
354-
if err = r.setResourceManaged(ctx, desired); err != nil {
355-
return nil, err
356-
}
354+
if !r.rd.IsManaged(desired) {
355+
if err = r.setResourceManaged(ctx, desired); err != nil {
356+
return nil, err
357+
}
357358

358-
rlog.Enter("rm.ResolveReferences")
359-
resolvedRefDesired, err := rm.ResolveReferences(ctx, r.apiReader, desired)
360-
rlog.Exit("rm.ResolveReferences", err)
361-
if err != nil {
362-
return resolvedRefDesired, err
359+
// Resolve the references again after adding the finalizer and
360+
// patching the resource. Patching resource omits the resolved references
361+
// because they are not persisted in etcd. So we resolve the references
362+
// again before performing the create operation.
363+
rlog.Enter("rm.ResolveReferences")
364+
resolvedRefDesired, err := rm.ResolveReferences(ctx, r.apiReader, desired)
365+
rlog.Exit("rm.ResolveReferences", err)
366+
if err != nil {
367+
return resolvedRefDesired, err
368+
}
369+
desired = resolvedRefDesired
363370
}
364-
desired = resolvedRefDesired
365371

366372
rlog.Enter("rm.Create")
367373
latest, err = rm.Create(ctx, desired)

pkg/runtime/reconciler_test.go

Lines changed: 78 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,7 @@ func managerFactoryMocks(
139139
return rmf, rd
140140
}
141141

142-
func TestReconcilerCreate_CheckReferencesResolveTwice(t *testing.T) {
142+
func TestReconcilerCreate_UnManagedResource_CheckReferencesResolveTwice(t *testing.T) {
143143
require := require.New(t)
144144

145145
ctx := context.TODO()
@@ -183,6 +183,8 @@ func TestReconcilerCreate_CheckReferencesResolveTwice(t *testing.T) {
183183
rmf, rd := managedResourceManagerFactoryMocks(desired, latest)
184184

185185
rm.On("LateInitialize", ctx, latest).Return(latest, nil)
186+
// Mark the resource as NotManaged before the Create call
187+
rd.On("IsManaged", desired).Return(false).Once()
186188
rd.On("IsManaged", desired).Return(true)
187189
rd.On("Delta", desired, latest).Return(ackcompare.NewDelta())
188190
rd.On("Delta", latest, latest).Return(ackcompare.NewDelta())
@@ -214,6 +216,81 @@ func TestReconcilerCreate_CheckReferencesResolveTwice(t *testing.T) {
214216
rm.AssertCalled(t, "IsSynced", ctx, latest)
215217
}
216218

219+
func TestReconcilerCreate_ManagedResource_CheckReferencesResolveOnce(t *testing.T) {
220+
require := require.New(t)
221+
222+
ctx := context.TODO()
223+
arn := ackv1alpha1.AWSResourceName("mybook-arn")
224+
225+
desired, _, _ := resourceMocks()
226+
desired.On("ReplaceConditions", []*ackv1alpha1.Condition{}).Return()
227+
228+
ids := &ackmocks.AWSResourceIdentifiers{}
229+
ids.On("ARN").Return(&arn)
230+
231+
latest, latestRTObj, _ := resourceMocks()
232+
latest.On("Identifiers").Return(ids)
233+
234+
latest.On("Conditions").Return([]*ackv1alpha1.Condition{})
235+
latest.On(
236+
"ReplaceConditions",
237+
mock.AnythingOfType("[]*v1alpha1.Condition"),
238+
).Return().Run(func(args mock.Arguments) {
239+
conditions := args.Get(0).([]*ackv1alpha1.Condition)
240+
assert.Equal(t, 1, len(conditions))
241+
cond := conditions[0]
242+
assert.Equal(t, ackv1alpha1.ConditionTypeResourceSynced, cond.Type)
243+
assert.Equal(t, corev1.ConditionTrue, cond.Status)
244+
})
245+
246+
rm := &ackmocks.AWSResourceManager{}
247+
rm.On("ResolveReferences", ctx, nil, desired).Return(
248+
desired, nil,
249+
).Once()
250+
rm.On("ReadOne", ctx, desired).Return(
251+
latest, ackerr.NotFound,
252+
).Once()
253+
rm.On("ReadOne", ctx, latest).Return(
254+
latest, nil,
255+
)
256+
rm.On("Create", ctx, desired).Return(
257+
latest, nil,
258+
)
259+
rm.On("IsSynced", ctx, latest).Return(true, nil)
260+
rmf, rd := managedResourceManagerFactoryMocks(desired, latest)
261+
262+
rm.On("LateInitialize", ctx, latest).Return(latest, nil)
263+
rd.On("IsManaged", desired).Return(true)
264+
rd.On("Delta", desired, latest).Return(ackcompare.NewDelta())
265+
rd.On("Delta", latest, latest).Return(ackcompare.NewDelta())
266+
267+
r, kc := reconcilerMocks(rmf)
268+
269+
// pointers returned from "client.MergeFrom" fails the equality check during
270+
// assertion even when parameters inside two objects are same.
271+
// hence we use mock.AnythingOfType parameter to assert patch call
272+
kc.On("Patch", ctx, latestRTObj, mock.AnythingOfType("*client.mergeFromPatch")).Return(nil)
273+
274+
// With the above mocks and below assertions, we check that if we got a
275+
// NotFound error return from `AWSResourceManager.ReadOne()` that we end
276+
// up calling the AWSResourceManager.Create() call in the Reconciler.Sync()
277+
// method,
278+
_, err := r.Sync(ctx, rm, desired)
279+
require.Nil(err)
280+
// Make sure references are resolved once for the resource creation when
281+
// the resource is already managed
282+
rm.AssertNumberOfCalls(t, "ResolveReferences", 1)
283+
rm.AssertCalled(t, "ResolveReferences", ctx, nil, desired)
284+
rm.AssertCalled(t, "ReadOne", ctx, desired)
285+
rm.AssertCalled(t, "Create", ctx, desired)
286+
// No changes to metadata or spec so Patch on the object shouldn't be done
287+
kc.AssertNotCalled(t, "Patch", ctx, latestRTObj, mock.AnythingOfType("*client.mergeFromPatch"))
288+
// Only the HandleReconcilerError wrapper function ever calls patchResourceStatus
289+
kc.AssertNotCalled(t, "Status")
290+
rm.AssertCalled(t, "LateInitialize", ctx, latest)
291+
rm.AssertCalled(t, "IsSynced", ctx, latest)
292+
}
293+
217294
func TestReconcilerUpdate(t *testing.T) {
218295
require := require.New(t)
219296

0 commit comments

Comments
 (0)