Skip to content

Commit 19d9f59

Browse files
authored
Use APIReader for reading adopted resource. Stop using deepcopy for kc.Patch calls (#51)
Issue #, if available: aws-controllers-k8s/community#935 Description of changes: * Mostly adding the latest improvements from reconciler into adoption_reconciler * Use APIReader to read k8s resource instead of kc.Client for avoiding caching problems * Do not use DeepCopy for 'kc.Patch' calls because the object metadata gets lost. * Preserve resource status after kc.Patch call. * Only create resource if it does not already exists. Making adoption reconciler idempotent. TODOs (aws-controllers-k8s/community#939) * Handle 'resource modified underneath' error when resource gets modified by resource reconciler while adopted reconciler is still patching status. * Add unit tests for the adoption_reconciler.go file * Refactor 'PatchMetadataAndSpec' , 'PatchStatus' method to be reusable between 'reconciler.go' & 'adoption_reconciler.go' Testing: * Sagemaker adoption_reconciler e2e tests passed By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.
1 parent f4fed76 commit 19d9f59

File tree

1 file changed

+92
-48
lines changed

1 file changed

+92
-48
lines changed

pkg/runtime/adoption_reconciler.go

Lines changed: 92 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import (
2222
apierrors "k8s.io/apimachinery/pkg/api/errors"
2323
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2424
"k8s.io/apimachinery/pkg/runtime/schema"
25+
"k8s.io/apimachinery/pkg/types"
2526
ctrlrt "sigs.k8s.io/controller-runtime"
2627
"sigs.k8s.io/controller-runtime/pkg/client"
2728
k8sctrlutil "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
@@ -53,6 +54,7 @@ type adoptionReconciler struct {
5354
// of an upstream controller-runtime.Manager
5455
func (r *adoptionReconciler) BindControllerManager(mgr ctrlrt.Manager) error {
5556
r.kc = mgr.GetClient()
57+
r.apiReader = mgr.GetAPIReader()
5658
return ctrlrt.NewControllerManagedBy(
5759
mgr,
5860
).For(
@@ -130,7 +132,7 @@ func (r *adoptionReconciler) reconcile(req ctrlrt.Request) error {
130132
}
131133

132134
if res.DeletionTimestamp != nil {
133-
return r.cleanup(ctx, *res)
135+
return r.cleanup(ctx, res)
134136
}
135137

136138
// Determine whether the reason is in a terminal state
@@ -212,31 +214,41 @@ func (r *adoptionReconciler) sync(
212214
targetDescriptor.MarkManaged(described)
213215
targetDescriptor.MarkAdopted(described)
214216

215-
if err := r.kc.Create(ctx, described.RuntimeObject()); err != nil {
216-
return r.onError(ctx, desired, err)
217-
}
218-
219-
if err := r.kc.Status().Update(ctx, described.RuntimeObject()); err != nil {
220-
return r.onError(ctx, desired, err)
217+
// Only create the described resource if it does not already exist
218+
// in k8s cluster.
219+
if err := r.apiReader.Get(ctx, types.NamespacedName{
220+
Namespace: described.MetaObject().GetNamespace(),
221+
Name: described.MetaObject().GetName(),
222+
}, described.RuntimeObject()); err != nil {
223+
if apierrors.IsNotFound(err) {
224+
// If Adopted AWS resource was not found in k8s, create it.
225+
if err := r.kc.Create(ctx, described.RuntimeObject()); err != nil {
226+
return r.onError(ctx, desired, err)
227+
}
228+
229+
if err := r.kc.Status().Update(ctx, described.RuntimeObject()); err != nil {
230+
return r.onError(ctx, desired, err)
231+
}
232+
} else {
233+
// for any other error except NotFound, return error
234+
return r.onError(ctx, desired, err)
235+
}
221236
}
222237

223-
if err := r.markManaged(ctx, *desired); err != nil {
238+
if err := r.markManaged(ctx, desired); err != nil {
224239
return r.onError(ctx, desired, err)
225240
}
226241

227-
if err := r.onSuccess(ctx, desired); err != nil {
228-
// Don't attempt to patch conditions again, directly return err
229-
return err
230-
}
231-
232-
return nil
242+
// Don't attempt to patch conditions again, directly return result of
243+
// 'r.onSuccess'
244+
return r.onSuccess(ctx, desired)
233245
}
234246

235-
// cleanup ensures that the supplied AWSResource's backing API resource is
236-
// destroyed along with all child dependent resources
247+
// cleanup removes the finalizer from AdoptedResource so that k8s object can
248+
// be deleted.
237249
func (r *adoptionReconciler) cleanup(
238250
ctx context.Context,
239-
current ackv1alpha1.AdoptedResource,
251+
current *ackv1alpha1.AdoptedResource,
240252
) error {
241253
if err := r.markUnmanaged(ctx, current); err != nil {
242254
return err
@@ -252,7 +264,15 @@ func (r *adoptionReconciler) getAdoptedResource(
252264
req ctrlrt.Request,
253265
) (*ackv1alpha1.AdoptedResource, error) {
254266
ro := &ackv1alpha1.AdoptedResource{}
255-
if err := r.kc.Get(ctx, req.NamespacedName, ro); err != nil {
267+
// Here we use k8s APIReader to read the k8s object by making the
268+
// direct call to k8s apiserver instead of using k8sClient.
269+
// The reason is that k8sClient uses a cache and sometimes k8sClient can
270+
// return stale copy of object.
271+
// It is okay to make direct call to k8s apiserver because we are only
272+
// making single read call for complete reconciler loop.
273+
// See following issue for more details:
274+
// https://github.com/aws-controllers-k8s/community/issues/894
275+
if err := r.apiReader.Get(ctx, req.NamespacedName, ro); err != nil {
256276
return nil, err
257277
}
258278
return ro, nil
@@ -279,16 +299,17 @@ func (r *adoptionReconciler) onSuccess(
279299
}
280300

281301
// patchAdoptedCondition updates the adopted condition status of the adopted resource
302+
// The resource passed in the parameter gets updated with the conditions
282303
func (r *adoptionReconciler) patchAdoptedCondition(
283304
ctx context.Context,
284305
res *ackv1alpha1.AdoptedResource,
285306
err error,
286307
) error {
287-
ko := res.DeepCopy()
308+
base := res.DeepCopy()
288309

289310
// Adopted condition
290311
var adoptedCondition *ackv1alpha1.Condition = nil
291-
for _, condition := range ko.Status.Conditions {
312+
for _, condition := range res.Status.Conditions {
292313
if condition.Type == ackv1alpha1.ConditionTypeAdopted {
293314
adoptedCondition = condition
294315
break
@@ -299,7 +320,7 @@ func (r *adoptionReconciler) patchAdoptedCondition(
299320
adoptedCondition = &ackv1alpha1.Condition{
300321
Type: ackv1alpha1.ConditionTypeAdopted,
301322
}
302-
ko.Status.Conditions = append(ko.Status.Conditions, adoptedCondition)
323+
res.Status.Conditions = append(res.Status.Conditions, adoptedCondition)
303324
}
304325

305326
var errMessage string
@@ -312,11 +333,7 @@ func (r *adoptionReconciler) patchAdoptedCondition(
312333
adoptedCondition.Status = corev1.ConditionTrue
313334
}
314335

315-
return r.kc.Status().Patch(
316-
ctx,
317-
ko.DeepCopyObject(),
318-
client.MergeFrom(res),
319-
)
336+
return r.patchStatus(ctx, res, base)
320337
}
321338

322339
// isAdopted returns true if the AdoptedResource is in a terminal adoption state
@@ -344,39 +361,27 @@ func (r *adoptionReconciler) getTargetResourceGroupKind(
344361
}
345362

346363
// markManaged places the supplied resource under the management of ACK.
364+
// It adds the finalizer string, patches the object in etcd and updates
365+
// the object 'res' in parameter with latest metadata.
347366
func (r *adoptionReconciler) markManaged(
348367
ctx context.Context,
349-
res ackv1alpha1.AdoptedResource,
368+
res *ackv1alpha1.AdoptedResource,
350369
) error {
351-
orig := res.DeepCopyObject()
370+
base := res.DeepCopy()
352371
k8sctrlutil.AddFinalizer(&res.ObjectMeta, finalizerString)
353-
err := r.kc.Patch(
354-
ctx,
355-
res.DeepCopyObject(),
356-
client.MergeFrom(orig),
357-
)
358-
if err != nil {
359-
return err
360-
}
361-
return nil
372+
return r.patchMetadataAndSpec(ctx, res, base)
362373
}
363374

364375
// markUnmanaged removes the supplied resource from management by ACK.
376+
// It removes the finalizer string, patches the object in etcd and updates
377+
// the object 'res' in parameter with latest metadata.
365378
func (r *adoptionReconciler) markUnmanaged(
366379
ctx context.Context,
367-
res ackv1alpha1.AdoptedResource,
380+
res *ackv1alpha1.AdoptedResource,
368381
) error {
369-
orig := res.DeepCopyObject()
382+
base := res.DeepCopy()
370383
k8sctrlutil.RemoveFinalizer(&res.ObjectMeta, finalizerString)
371-
err := r.kc.Patch(
372-
ctx,
373-
res.DeepCopyObject(),
374-
client.MergeFrom(orig),
375-
)
376-
if err != nil {
377-
return err
378-
}
379-
return nil
384+
return r.patchMetadataAndSpec(ctx, res, base)
380385
}
381386

382387
// handleReconcileError will handle errors from reconcile handlers, which
@@ -481,6 +486,45 @@ func (r *adoptionReconciler) getRegion(
481486
return ackv1alpha1.AWSRegion(r.cfg.Region)
482487
}
483488

489+
// patchMetadataAndSpec patches the Metadata and Spec for AdoptedResource into
490+
// k8s. The adopted resource 'res' also gets updated with content returned from
491+
// apiserver.
492+
// TODO(vijat@): Refactor this and use single 'patchMetadataAndSpec' method
493+
// for reconciler and adoptionReconciler
494+
func (r *adoptionReconciler) patchMetadataAndSpec(
495+
ctx context.Context,
496+
res *ackv1alpha1.AdoptedResource,
497+
base *ackv1alpha1.AdoptedResource,
498+
) error {
499+
// k8s Client Patch call updates the status of original object with the
500+
// content returned from apiserver.
501+
// Keep a copy of status field to reset the status of 'res' after patch call
502+
resStatusCopy := res.DeepCopy().Status
503+
err := r.kc.Patch(
504+
ctx,
505+
res,
506+
client.MergeFrom(base),
507+
)
508+
res.Status = resStatusCopy
509+
return err
510+
}
511+
512+
// patchStatus patches the Status for AdoptedResource into k8s. The adopted
513+
// resource 'res' also gets updated with the content returned from apiserver.
514+
// TODO(vijat@): Refactor this and use single 'patchStatus' method
515+
// for reconciler and adoptionReconciler
516+
func (r *adoptionReconciler) patchStatus(
517+
ctx context.Context,
518+
res *ackv1alpha1.AdoptedResource,
519+
base *ackv1alpha1.AdoptedResource,
520+
) error {
521+
return r.kc.Status().Patch(
522+
ctx,
523+
res,
524+
client.MergeFrom(base),
525+
)
526+
}
527+
484528
// NewAdoptionReconciler returns a new adoptionReconciler object
485529
func NewAdoptionReconciler(
486530
sc acktypes.ServiceController,

0 commit comments

Comments
 (0)