Skip to content

Commit 5afdd00

Browse files
authored
clear conditions on loop start and ensure synced (#61)
This is the first patch that looks at cleaning up our handling of Condition objects for resources. We make a change to the common runtime to clear all Condition objects from the desired resource's Status.Conditions collection at the beginning of the reconciliation loop and add a deferred call to a function that ensures that an ACK.ResourceSynced condition has been placed on the resource if the resource manager has failed to add one. Future patches will add functionality to the code generator to allow programmatic control over when generated resource managers set ACK.ResourceSynced conditions on their resources. Issue: aws-controllers-k8s/community#1030 Signed-off-by: Jay Pipes <[email protected]> By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.
1 parent 05271e4 commit 5afdd00

File tree

3 files changed

+99
-0
lines changed

3 files changed

+99
-0
lines changed

pkg/condition/condition.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,3 +157,10 @@ func LateInitializationInProgress(subject acktypes.ConditionManager) bool {
157157
c := LateInitialized(subject)
158158
return c != nil && c.Status == corev1.ConditionFalse
159159
}
160+
161+
// Clear resets the resource's collection of Conditions to an empty list.
162+
func Clear(
163+
subject acktypes.ConditionManager,
164+
) {
165+
subject.ReplaceConditions([]*ackv1alpha1.Condition{})
166+
}

pkg/runtime/reconciler.go

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import (
2929
ackv1alpha1 "github.com/aws-controllers-k8s/runtime/apis/core/v1alpha1"
3030
ackcompare "github.com/aws-controllers-k8s/runtime/pkg/compare"
3131
"github.com/aws-controllers-k8s/runtime/pkg/condition"
32+
ackcondition "github.com/aws-controllers-k8s/runtime/pkg/condition"
3233
ackcfg "github.com/aws-controllers-k8s/runtime/pkg/config"
3334
ackerr "github.com/aws-controllers-k8s/runtime/pkg/errors"
3435
ackmetrics "github.com/aws-controllers-k8s/runtime/pkg/metrics"
@@ -204,6 +205,9 @@ func (r *resourceReconciler) Sync(
204205

205206
var latest acktypes.AWSResource // the newly created or mutated resource
206207

208+
r.resetConditions(ctx, desired)
209+
defer r.ensureConditions(ctx, latest, err)
210+
207211
isAdopted := IsAdopted(desired)
208212
rlog.WithValues("is_adopted", isAdopted)
209213

@@ -237,6 +241,56 @@ func (r *resourceReconciler) Sync(
237241
return r.handleRequeues(ctx, latest)
238242
}
239243

244+
// resetConditions strips the supplied resource of all objects in its
245+
// Status.Conditions collection. We do this at the start of each reconciliation
246+
// loop in order to ensure that the objects in the Status.Conditions collection
247+
// represent the state transitions that occurred in the last reconciliation
248+
// loop. In other words, Status.Conditions should refer to the latest observed
249+
// state read.
250+
func (r *resourceReconciler) resetConditions(
251+
ctx context.Context,
252+
res acktypes.AWSResource,
253+
) {
254+
var err error
255+
rlog := ackrtlog.FromContext(ctx)
256+
exit := rlog.Trace("r.resetConditions")
257+
defer exit(err)
258+
259+
ackcondition.Clear(res)
260+
}
261+
262+
// ensureConditions examines the supplied resource's collection of Condition
263+
// objects and ensures that an ACK.ResourceSynced condition is present.
264+
func (r *resourceReconciler) ensureConditions(
265+
ctx context.Context,
266+
res acktypes.AWSResource,
267+
reconcileErr error,
268+
) {
269+
if ackcompare.IsNil(res) {
270+
return
271+
}
272+
273+
var err error
274+
rlog := ackrtlog.FromContext(ctx)
275+
exit := rlog.Trace("r.ensureConditions")
276+
defer exit(err)
277+
278+
if syncedCond := ackcondition.Synced(res); syncedCond == nil {
279+
rlog.Debug("resource missing ACK.ResourceSynced condition")
280+
// only the resource manager will know whether the resource is in a
281+
// stable sync state. Even if we got no error back from the
282+
// create/update operations, we can only set this to Unknown.
283+
condStatus := corev1.ConditionUnknown
284+
if reconcileErr == ackerr.Terminal {
285+
// A terminal condition by its very nature indicates a stable state
286+
// for a resource being synced. The resource is considered synced
287+
// because its state will not change.
288+
condStatus = corev1.ConditionTrue
289+
}
290+
ackcondition.SetSynced(res, condStatus, nil, nil)
291+
}
292+
}
293+
240294
// createResource marks the CR as managed by ACK, calls one or more AWS APIs to
241295
// create the backend AWS resource and patches the CR's Metadata, Spec and
242296
// Status back to the Kubernetes API.

pkg/runtime/reconciler_test.go

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,13 +151,29 @@ func TestReconcilerUpdate(t *testing.T) {
151151
delta.Add("Spec.A", "val1", "val2")
152152

153153
desired, desiredRTObj, _ := resourceMocks()
154+
desired.On("ReplaceConditions", []*ackv1alpha1.Condition{}).Return()
154155

155156
ids := &ackmocks.AWSResourceIdentifiers{}
156157
ids.On("ARN").Return(&arn)
157158

158159
latest, latestRTObj, _ := resourceMocks()
159160
latest.On("Identifiers").Return(ids)
161+
162+
// resourceReconciler.ensureConditions will ensure that if the resource
163+
// manager has not set any Conditions on the resource, that at least an
164+
// ACK.ResourceSynced condition with status Unknown will be set on the
165+
// resource.
160166
latest.On("Conditions").Return([]*ackv1alpha1.Condition{})
167+
latest.On(
168+
"ReplaceConditions",
169+
mock.AnythingOfType("[]*v1alpha1.Condition"),
170+
).Return().Run(func(args mock.Arguments) {
171+
conditions := args.Get(0).([]*ackv1alpha1.Condition)
172+
assert.Equal(t, 1, len(conditions))
173+
cond := conditions[0]
174+
assert.Equal(t, cond.Type, ackv1alpha1.ConditionTypeResourceSynced)
175+
assert.Equal(t, cond.Status, corev1.ConditionUnknown)
176+
})
161177

162178
rm := &ackmocks.AWSResourceManager{}
163179
rm.On("ReadOne", ctx, desired).Return(
@@ -207,13 +223,18 @@ func TestReconcilerUpdate_PatchMetadataAndSpec_DiffInMetadata(t *testing.T) {
207223
delta.Add("Spec.A", "val1", "val2")
208224

209225
desired, desiredRTObj, _ := resourceMocks()
226+
desired.On("ReplaceConditions", []*ackv1alpha1.Condition{}).Return()
210227

211228
ids := &ackmocks.AWSResourceIdentifiers{}
212229
ids.On("ARN").Return(&arn)
213230

214231
latest, latestRTObj, latestMetaObj := resourceMocks()
215232
latest.On("Identifiers").Return(ids)
216233
latest.On("Conditions").Return([]*ackv1alpha1.Condition{})
234+
latest.On(
235+
"ReplaceConditions",
236+
mock.AnythingOfType("[]*v1alpha1.Condition"),
237+
).Return()
217238

218239
// Note the change in annotations
219240
latestMetaObj.SetAnnotations(map[string]string{"a": "b"})
@@ -261,13 +282,18 @@ func TestReconcilerUpdate_PatchMetadataAndSpec_DiffInSpec(t *testing.T) {
261282
delta.Add("Spec.A", "val1", "val2")
262283

263284
desired, desiredRTObj, _ := resourceMocks()
285+
desired.On("ReplaceConditions", []*ackv1alpha1.Condition{}).Return()
264286

265287
ids := &ackmocks.AWSResourceIdentifiers{}
266288
ids.On("ARN").Return(&arn)
267289

268290
latest, latestRTObj, _ := resourceMocks()
269291
latest.On("Identifiers").Return(ids)
270292
latest.On("Conditions").Return([]*ackv1alpha1.Condition{})
293+
latest.On(
294+
"ReplaceConditions",
295+
mock.AnythingOfType("[]*v1alpha1.Condition"),
296+
).Return()
271297
// Note no change to metadata...
272298

273299
rmf, rd := managedResourceManagerFactoryMocks(desired, latest)
@@ -310,13 +336,18 @@ func TestReconcilerHandleReconcilerError_PatchStatus_Latest(t *testing.T) {
310336
delta.Add("Spec.A", "val1", "val2")
311337

312338
desired, desiredRTObj, _ := resourceMocks()
339+
desired.On("ReplaceConditions", []*ackv1alpha1.Condition{}).Return()
313340

314341
ids := &ackmocks.AWSResourceIdentifiers{}
315342
ids.On("ARN").Return(&arn)
316343

317344
latest, latestRTObj, latestMetaObj := resourceMocks()
318345
latest.On("Identifiers").Return(ids)
319346
latest.On("Conditions").Return([]*ackv1alpha1.Condition{})
347+
latest.On(
348+
"ReplaceConditions",
349+
mock.AnythingOfType("[]*v1alpha1.Condition"),
350+
).Return()
320351

321352
latestMetaObj.SetAnnotations(map[string]string{"a": "b"})
322353

@@ -343,6 +374,7 @@ func TestReconcilerHandleReconcilerError_NoPatchStatus_NoLatest(t *testing.T) {
343374
ctx := context.TODO()
344375

345376
desired, _, _ := resourceMocks()
377+
desired.On("ReplaceConditions", []*ackv1alpha1.Condition{}).Return()
346378

347379
rmf, _ := managedResourceManagerFactoryMocks(desired, nil)
348380
r, kc := reconcilerMocks(rmf)
@@ -371,13 +403,18 @@ func TestReconcilerUpdate_ErrorInLateInitialization(t *testing.T) {
371403
delta.Add("Spec.A", "val1", "val2")
372404

373405
desired, desiredRTObj, _ := resourceMocks()
406+
desired.On("ReplaceConditions", []*ackv1alpha1.Condition{}).Return()
374407

375408
ids := &ackmocks.AWSResourceIdentifiers{}
376409
ids.On("ARN").Return(&arn)
377410

378411
latest, latestRTObj, _ := resourceMocks()
379412
latest.On("Identifiers").Return(ids)
380413
latest.On("Conditions").Return([]*ackv1alpha1.Condition{})
414+
latest.On(
415+
"ReplaceConditions",
416+
mock.AnythingOfType("[]*v1alpha1.Condition"),
417+
).Return()
381418

382419
rm := &ackmocks.AWSResourceManager{}
383420
rm.On("ReadOne", ctx, desired).Return(
@@ -423,6 +460,7 @@ func TestReconcilerUpdate_ResourceNotManaged(t *testing.T) {
423460
delta := ackcompare.NewDelta()
424461

425462
desired, _, _ := resourceMocks()
463+
desired.On("ReplaceConditions", []*ackv1alpha1.Condition{}).Return()
426464

427465
ids := &ackmocks.AWSResourceIdentifiers{}
428466
ids.On("ARN").Return(&arn)

0 commit comments

Comments
 (0)