Skip to content

Commit 0f15f04

Browse files
committed
summarize: consider bipolarity in status condition
This introduces the consideration of bipolarity conditions in the status condition summary for Ready condition. The summarize.HelperOptions can now be configured with a list of bipolarity conditions which are used in SummarizeAndPatch() to set the Ready condition to failing bipolarity condition with the highest priority. Bipolarity condition is not a typical status property. It is a mix of positive and negative polarities. It's "normal-true" and "abnormal-false". Failing bipolarity conditions are prioritized over other conditions to show the actual reason of failure on the Ready status. Signed-off-by: Sunny <[email protected]>
1 parent 54d706a commit 0f15f04

File tree

2 files changed

+111
-9
lines changed

2 files changed

+111
-9
lines changed

internal/reconcile/summarize/summary.go

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,9 @@ type HelperOptions struct {
9090
// PatchFieldOwner defines the field owner configuration for the Kubernetes
9191
// patch operation.
9292
PatchFieldOwner string
93+
// BiPolarityConditionTypes is a list of bipolar conditions in the order
94+
// of priority.
95+
BiPolarityConditionTypes []string
9396
}
9497

9598
// Option is configuration that modifies SummarizeAndPatch.
@@ -149,6 +152,14 @@ func WithPatchFieldOwner(fieldOwner string) Option {
149152
}
150153
}
151154

155+
// WithBiPolarityConditionTypes sets the BiPolarityConditionTypes used to
156+
// calculate the value of Ready condition in SummarizeAndPatch.
157+
func WithBiPolarityConditionTypes(types ...string) Option {
158+
return func(s *HelperOptions) {
159+
s.BiPolarityConditionTypes = types
160+
}
161+
}
162+
152163
// SummarizeAndPatch summarizes and patches the result to the target object.
153164
// When used at the very end of a reconciliation, the result builder must be
154165
// specified using the Option WithResultBuilder(). The returned result and error
@@ -206,6 +217,26 @@ func (h *Helper) SummarizeAndPatch(ctx context.Context, obj conditions.Setter, o
206217
)
207218
}
208219

220+
// Check any BiPolarity conditions in the status that are False. Failing
221+
// BiPolarity condition should be set as the Ready condition value to
222+
// reflect the actual cause of the reconciliation failure.
223+
// NOTE: This is applicable to Ready condition only because it is a special
224+
// condition in kstatus that reflects the overall state of an object.
225+
// IMPLEMENTATION NOTE: An implementation of this within the
226+
// conditions.merge() exists in fluxcd/pkg repo branch `bipolarity`
227+
// (https://github.com/fluxcd/pkg/commit/756b9e6d253a4fae93c05419b7019d0169454858).
228+
// If that gets added to conditions.merge, the following can be removed.
229+
var failedBiPolarity []string
230+
for _, c := range opts.BiPolarityConditionTypes {
231+
if conditions.IsFalse(obj, c) {
232+
failedBiPolarity = append(failedBiPolarity, c)
233+
}
234+
}
235+
if len(failedBiPolarity) > 0 {
236+
topFailedBiPolarity := conditions.Get(obj, failedBiPolarity[0])
237+
conditions.MarkFalse(obj, meta.ReadyCondition, topFailedBiPolarity.Reason, topFailedBiPolarity.Message)
238+
}
239+
209240
// If object is not stalled, result is success and runtime error is nil,
210241
// ensure that Ready=True. Else, use the Ready failure message as the
211242
// runtime error message. This ensures that the reconciliation would be

internal/reconcile/summarize/summary_test.go

Lines changed: 80 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -44,18 +44,26 @@ import (
4444
// This tests the scenario where SummarizeAndPatch is used at the very end of a
4545
// reconciliation.
4646
func TestSummarizeAndPatch(t *testing.T) {
47+
testBipolarCondition1 := "FooChecked1"
48+
testBipolarCondition2 := "FooChecked2"
4749
var testReadyConditions = Conditions{
4850
Target: meta.ReadyCondition,
4951
Owned: []string{
5052
sourcev1.FetchFailedCondition,
5153
sourcev1.ArtifactOutdatedCondition,
54+
sourcev1.SourceVerifiedCondition,
55+
testBipolarCondition1,
56+
testBipolarCondition2,
5257
meta.ReadyCondition,
5358
meta.ReconcilingCondition,
5459
meta.StalledCondition,
5560
},
5661
Summarize: []string{
5762
sourcev1.FetchFailedCondition,
5863
sourcev1.ArtifactOutdatedCondition,
64+
sourcev1.SourceVerifiedCondition,
65+
testBipolarCondition1,
66+
testBipolarCondition2,
5967
meta.StalledCondition,
6068
meta.ReconcilingCondition,
6169
},
@@ -66,6 +74,7 @@ func TestSummarizeAndPatch(t *testing.T) {
6674
meta.ReconcilingCondition,
6775
},
6876
}
77+
var testBipolarConditions = []string{sourcev1.SourceVerifiedCondition, testBipolarCondition1, testBipolarCondition2}
6978
var testFooConditions = Conditions{
7079
Target: "Foo",
7180
Owned: []string{
@@ -83,15 +92,16 @@ func TestSummarizeAndPatch(t *testing.T) {
8392
}
8493

8594
tests := []struct {
86-
name string
87-
generation int64
88-
beforeFunc func(obj conditions.Setter)
89-
result reconcile.Result
90-
reconcileErr error
91-
conditions []Conditions
92-
wantErr bool
93-
afterFunc func(t *WithT, obj client.Object)
94-
assertConditions []metav1.Condition
95+
name string
96+
generation int64
97+
beforeFunc func(obj conditions.Setter)
98+
result reconcile.Result
99+
reconcileErr error
100+
conditions []Conditions
101+
bipolarConditions []string
102+
wantErr bool
103+
afterFunc func(t *WithT, obj client.Object)
104+
assertConditions []metav1.Condition
95105
}{
96106
// Success/Fail indicates if a reconciliation succeeded or failed.
97107
// The object generation is expected to match the observed generation in
@@ -250,6 +260,64 @@ func TestSummarizeAndPatch(t *testing.T) {
250260
},
251261
wantErr: true,
252262
},
263+
{
264+
name: "Fail, reconciling with bipolar condition False, Ready gets bipolar failure value",
265+
generation: 2,
266+
beforeFunc: func(obj conditions.Setter) {
267+
conditions.MarkReconciling(obj, "NewRevision", "new index revision")
268+
conditions.MarkFalse(obj, sourcev1.SourceVerifiedCondition, "VerifyFailed", "verify failed")
269+
},
270+
result: reconcile.ResultEmpty,
271+
reconcileErr: errors.New("failed to verify source"),
272+
conditions: []Conditions{testReadyConditions},
273+
bipolarConditions: testBipolarConditions,
274+
wantErr: true,
275+
assertConditions: []metav1.Condition{
276+
*conditions.FalseCondition(meta.ReadyCondition, "VerifyFailed", "verify failed"),
277+
*conditions.FalseCondition(sourcev1.SourceVerifiedCondition, "VerifyFailed", "verify failed"),
278+
*conditions.TrueCondition(meta.ReconcilingCondition, "NewRevision", "new index revision"),
279+
},
280+
},
281+
{
282+
name: "Fail, bipolar condition True, negative polarity True, Ready gets negative polarity value",
283+
generation: 2,
284+
beforeFunc: func(obj conditions.Setter) {
285+
conditions.MarkReconciling(obj, "NewGeneration", "new obj gen")
286+
conditions.MarkTrue(obj, sourcev1.ArtifactOutdatedCondition, "NewRevision", "new digest")
287+
conditions.MarkTrue(obj, sourcev1.SourceVerifiedCondition, "Success", "verified")
288+
},
289+
result: reconcile.ResultEmpty,
290+
reconcileErr: errors.New("failed to create dir"),
291+
conditions: []Conditions{testReadyConditions},
292+
bipolarConditions: testBipolarConditions,
293+
wantErr: true,
294+
assertConditions: []metav1.Condition{
295+
*conditions.FalseCondition(meta.ReadyCondition, "NewRevision", "new digest"),
296+
*conditions.TrueCondition(sourcev1.ArtifactOutdatedCondition, "NewRevision", "new digest"),
297+
*conditions.TrueCondition(meta.ReconcilingCondition, "NewGeneration", "new obj gen"),
298+
*conditions.TrueCondition(sourcev1.SourceVerifiedCondition, "Success", "verified"),
299+
},
300+
},
301+
{
302+
name: "Fail, multiple bipolar conditions False, Ready gets the bipolar with high priority",
303+
generation: 2,
304+
beforeFunc: func(obj conditions.Setter) {
305+
conditions.MarkTrue(obj, sourcev1.SourceVerifiedCondition, "Success", "verified")
306+
conditions.MarkFalse(obj, testBipolarCondition1, "AAA", "aaa")
307+
conditions.MarkFalse(obj, testBipolarCondition2, "BBB", "bbb")
308+
},
309+
result: reconcile.ResultEmpty,
310+
reconcileErr: errors.New("some failure"),
311+
conditions: []Conditions{testReadyConditions},
312+
bipolarConditions: testBipolarConditions,
313+
wantErr: true,
314+
assertConditions: []metav1.Condition{
315+
*conditions.FalseCondition(meta.ReadyCondition, "AAA", "aaa"),
316+
*conditions.FalseCondition(testBipolarCondition1, "AAA", "aaa"),
317+
*conditions.FalseCondition(testBipolarCondition2, "BBB", "bbb"),
318+
*conditions.TrueCondition(sourcev1.SourceVerifiedCondition, "Success", "verified"),
319+
},
320+
},
253321
}
254322

255323
for _, tt := range tests {
@@ -289,6 +357,9 @@ func TestSummarizeAndPatch(t *testing.T) {
289357
WithProcessors(RecordContextualError, RecordReconcileReq),
290358
WithResultBuilder(reconcile.AlwaysRequeueResultBuilder{RequeueAfter: obj.Spec.Interval.Duration}),
291359
}
360+
if tt.bipolarConditions != nil {
361+
summaryOpts = append(summaryOpts, WithBiPolarityConditionTypes(tt.bipolarConditions...))
362+
}
292363
_, gotErr := summaryHelper.SummarizeAndPatch(ctx, obj, summaryOpts...)
293364
g.Expect(gotErr != nil).To(Equal(tt.wantErr))
294365

0 commit comments

Comments
 (0)