Skip to content

Commit d1b2189

Browse files
authored
Add additional unit-tests for Conditions (#92)
* Add additional unit-tests for Conditions Make sure each condition is defined (as best we can) Enhancements to #91 Signed-off-by: Todd Short <[email protected]>
1 parent bc3af05 commit d1b2189

File tree

5 files changed

+87
-17
lines changed

5 files changed

+87
-17
lines changed

Dockerfile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ RUN go mod download
1515
COPY main.go main.go
1616
COPY api/ api/
1717
COPY controllers/ controllers/
18+
COPY internal/ internal/
1819

1920
# Build
2021
# the GOARCH has not a default value to allow the binary be built according to the host where the command

api/v1alpha1/operator_types.go

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ limitations under the License.
1717
package v1alpha1
1818

1919
import (
20+
operatorutil "github.com/operator-framework/operator-controller/internal/util"
2021
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2122
)
2223

@@ -28,13 +29,24 @@ type OperatorSpec struct {
2829
}
2930

3031
const (
31-
// TODO(user): add more Types
32+
// TODO(user): add more Types, here and into init()
3233
TypeReady = "Ready"
3334

34-
// TODO(user): add more Reasons
35+
// TODO(user): add more Reasons, here and into init()
3536
ReasonNotImplemented = "NotImplemented"
3637
)
3738

39+
func init() {
40+
// TODO(user): add Types from above
41+
operatorutil.ConditionTypes = append(operatorutil.ConditionTypes,
42+
TypeReady,
43+
)
44+
// TODO(user): add Reasons from above
45+
operatorutil.ConditionReasons = append(operatorutil.ConditionReasons,
46+
ReasonNotImplemented,
47+
)
48+
}
49+
3850
// OperatorStatus defines the observed state of Operator
3951
type OperatorStatus struct {
4052
// +patchMergeKey=type

controllers/operator_controller.go

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
Copyright 2022.
2+
Copyright 2023.
33
44
Licensed under the Apache License, Version 2.0 (the "License");
55
you may not use this file except in compliance with the License.
@@ -66,20 +66,15 @@ func (r *OperatorReconciler) Reconcile(ctx context.Context, req ctrl.Request) (c
6666
// Do checks before any Update()s, as Update() may modify the resource structure!
6767
updateStatus := !equality.Semantic.DeepEqual(existingOp.Status, reconciledOp.Status)
6868
updateFinalizers := !equality.Semantic.DeepEqual(existingOp.Finalizers, reconciledOp.Finalizers)
69-
70-
// Compare resources - ignoring status & metadata.finalizers
71-
compareOp := reconciledOp.DeepCopy()
72-
existingOp.Status, compareOp.Status = operatorsv1alpha1.OperatorStatus{}, operatorsv1alpha1.OperatorStatus{}
73-
existingOp.Finalizers, compareOp.Finalizers = []string{}, []string{}
74-
specDiffers := !equality.Semantic.DeepEqual(existingOp, compareOp)
69+
unexpectedFieldsChanged := checkForUnexpectedFieldChange(*existingOp, *reconciledOp)
7570

7671
if updateStatus {
7772
if updateErr := r.Status().Update(ctx, reconciledOp); updateErr != nil {
7873
return res, utilerrors.NewAggregate([]error{reconcileErr, updateErr})
7974
}
8075
}
8176

82-
if specDiffers {
77+
if unexpectedFieldsChanged {
8378
panic("spec or metadata changed by reconciler")
8479
}
8580

@@ -92,6 +87,13 @@ func (r *OperatorReconciler) Reconcile(ctx context.Context, req ctrl.Request) (c
9287
return res, reconcileErr
9388
}
9489

90+
// Compare resources - ignoring status & metadata.finalizers
91+
func checkForUnexpectedFieldChange(a, b operatorsv1alpha1.Operator) bool {
92+
a.Status, b.Status = operatorsv1alpha1.OperatorStatus{}, operatorsv1alpha1.OperatorStatus{}
93+
a.Finalizers, b.Finalizers = []string{}, []string{}
94+
return !equality.Semantic.DeepEqual(a, b)
95+
}
96+
9597
// Helper function to do the actual reconcile
9698
func (r *OperatorReconciler) reconcile(ctx context.Context, op *operatorsv1alpha1.Operator) (ctrl.Result, error) {
9799

controllers/suite_test.go

Lines changed: 38 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
Copyright 2022.
2+
Copyright 2023.
33
44
Licensed under the Apache License, Version 2.0 (the "License");
55
you may not use this file except in compliance with the License.
@@ -25,6 +25,7 @@ import (
2525
. "github.com/onsi/ginkgo/v2"
2626
. "github.com/onsi/gomega"
2727

28+
apimeta "k8s.io/apimachinery/pkg/api/meta"
2829
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2930
"k8s.io/apimachinery/pkg/types"
3031
"k8s.io/apimachinery/pkg/util/rand"
@@ -38,6 +39,7 @@ import (
3839

3940
operatorsv1alpha1 "github.com/operator-framework/operator-controller/api/v1alpha1"
4041
"github.com/operator-framework/operator-controller/controllers"
42+
operatorutil "github.com/operator-framework/operator-controller/internal/util"
4143
//+kubebuilder:scaffold:imports
4244
)
4345

@@ -127,18 +129,47 @@ var _ = Describe("Reconcile Test", func() {
127129
err = k8sClient.Delete(ctx, operator)
128130
Expect(err).To(Not(HaveOccurred()))
129131
})
130-
It("has a Condition created", func() {
131-
getOperator := &operatorsv1alpha1.Operator{}
132+
It("has all Conditions created", func() {
133+
op := &operatorsv1alpha1.Operator{}
132134

133135
err = k8sClient.Get(ctx, client.ObjectKey{
134136
Name: opName,
135-
}, getOperator)
137+
}, op)
136138
Expect(err).To(Not(HaveOccurred()))
137139

138-
// There should always be a "Ready" condition, regardless of Status.
139-
conds := getOperator.Status.Conditions
140+
// All defined condition Types MUST exist after reconciliation
141+
conds := op.Status.Conditions
140142
Expect(conds).To(Not(BeEmpty()))
141-
Expect(conds).To(ContainElement(HaveField("Type", operatorsv1alpha1.TypeReady)))
143+
Expect(conds).To(HaveLen(len(operatorutil.ConditionTypes)))
144+
for _, t := range operatorutil.ConditionTypes {
145+
Expect(apimeta.FindStatusCondition(conds, t)).To(Not(BeNil()))
146+
}
147+
})
148+
It("has matching generations in Conditions", func() {
149+
op := &operatorsv1alpha1.Operator{}
150+
151+
err = k8sClient.Get(ctx, client.ObjectKey{
152+
Name: opName,
153+
}, op)
154+
Expect(err).To(Not(HaveOccurred()))
155+
156+
// The ObservedGeneration MUST match the resource generation after reconciliation
157+
for _, c := range op.Status.Conditions {
158+
Expect(c.ObservedGeneration).To(Equal(op.GetGeneration()))
159+
}
160+
})
161+
It("has only pre-defined Reasons", func() {
162+
op := &operatorsv1alpha1.Operator{}
163+
164+
err = k8sClient.Get(ctx, client.ObjectKey{
165+
Name: opName,
166+
}, op)
167+
Expect(err).To(Not(HaveOccurred()))
168+
169+
// A given Reason MUST be in the list of ConditionReasons
170+
for _, c := range op.Status.Conditions {
171+
Expect(c.Reason).To(BeElementOf(operatorutil.ConditionReasons))
172+
}
142173
})
143174
})
144175
})

internal/util/util.go

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
/*
2+
Copyright 2023.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package util
18+
19+
// ConditionTypes is the full set of Operator condition Types.
20+
// ConditionReasons is the full set of Operator condition Reasons.
21+
//
22+
// NOTE: These are populated by init() in api/v1alpha1/operator_types.go
23+
var ConditionTypes []string
24+
var ConditionReasons []string

0 commit comments

Comments
 (0)