Skip to content

Commit 5d27194

Browse files
committed
controller code/test refactoring/additions
main bullet points are: - decrease nesting in main reconcile code by setting conditions and returning early when handling unexpected errors. - move reconciler unit tests to a separate file from the suite. if we add other controllers in the future, we'll likely want separate test files for each controller. - export the reconciler's resolver field to make it easy to swap out for tests (also add and use a test entity source) - remove the manager setup in the unit tests. this ensures we can test individual runs of the Reconcile function, which is good for two reasons: 1. no need to use `Eventually`, no polling, etc. so it speeds up the tests 2. it ensures that we fully understand the transitions that occur from a beginning state to and end state. - added more unit tests to increase code coverage. - moved conditions and reasons checks to invariants that run after every test of reconciling an existing Operator object. - make certain bundle entity properties optional. not all bundles will provide/require GVKs or require other packages. lookups for those entities should return nil slices rather than an error. - remove unnecessary RBAC rules for create/update/patch/delete Operator objects. this leaves only get, list, watch, update status, and update finalizers.
1 parent 13ea200 commit 5d27194

File tree

9 files changed

+351
-217
lines changed

9 files changed

+351
-217
lines changed

api/v1alpha1/operator_types.go

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,9 @@ limitations under the License.
1717
package v1alpha1
1818

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

2425
// OperatorSpec defines the desired state of Operator
@@ -32,9 +33,10 @@ const (
3233
// TODO(user): add more Types, here and into init()
3334
TypeReady = "Ready"
3435

35-
ReasonNotImplemented = "NotImplemented"
36-
ReasonResolutionFailed = "ResolutionFailed"
37-
ReasonResolutionSucceeded = "ResolutionSucceeded"
36+
ReasonResolutionSucceeded = "ResolutionSucceeded"
37+
ReasonResolutionFailed = "ResolutionFailed"
38+
ReasonBundleLookupFailed = "BundleLookupFailed"
39+
ReasonBundleDeploymentFailed = "BundleDeploymentFailed"
3840
)
3941

4042
func init() {
@@ -44,7 +46,10 @@ func init() {
4446
)
4547
// TODO(user): add Reasons from above
4648
operatorutil.ConditionReasons = append(operatorutil.ConditionReasons,
47-
ReasonNotImplemented, ReasonResolutionSucceeded, ReasonResolutionFailed,
49+
ReasonResolutionSucceeded,
50+
ReasonResolutionFailed,
51+
ReasonBundleLookupFailed,
52+
ReasonBundleDeploymentFailed,
4853
)
4954
}
5055

config/rbac/role.yaml

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,8 @@ rules:
2121
resources:
2222
- operators
2323
verbs:
24-
- create
25-
- delete
2624
- get
2725
- list
28-
- patch
29-
- update
3026
- watch
3127
- apiGroups:
3228
- operators.operatorframework.io

controllers/operator_controller.go

Lines changed: 82 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ package controllers
1818

1919
import (
2020
"context"
21+
"fmt"
2122

2223
rukpakv1alpha1 "github.com/operator-framework/rukpak/api/v1alpha1"
2324
"k8s.io/apimachinery/pkg/api/equality"
@@ -26,6 +27,7 @@ import (
2627
"k8s.io/apimachinery/pkg/runtime"
2728
"k8s.io/apimachinery/pkg/types"
2829
utilerrors "k8s.io/apimachinery/pkg/util/errors"
30+
"k8s.io/utils/pointer"
2931
ctrl "sigs.k8s.io/controller-runtime"
3032
"sigs.k8s.io/controller-runtime/pkg/client"
3133
"sigs.k8s.io/controller-runtime/pkg/log"
@@ -38,23 +40,13 @@ import (
3840
// OperatorReconciler reconciles a Operator object
3941
type OperatorReconciler struct {
4042
client.Client
41-
Scheme *runtime.Scheme
42-
43-
resolver *resolution.OperatorResolver
44-
}
45-
46-
func NewOperatorReconciler(c client.Client, s *runtime.Scheme, r *resolution.OperatorResolver) *OperatorReconciler {
47-
return &OperatorReconciler{
48-
Client: c,
49-
Scheme: s,
50-
resolver: r,
51-
}
43+
Scheme *runtime.Scheme
44+
Resolver *resolution.OperatorResolver
5245
}
5346

54-
//+kubebuilder:rbac:groups=operators.operatorframework.io,resources=operators,verbs=get;list;watch;create;update;patch;delete
47+
//+kubebuilder:rbac:groups=operators.operatorframework.io,resources=operators,verbs=get;list;watch
5548
//+kubebuilder:rbac:groups=operators.operatorframework.io,resources=operators/status,verbs=get;update;patch
5649
//+kubebuilder:rbac:groups=operators.operatorframework.io,resources=operators/finalizers,verbs=update
57-
5850
//+kubebuilder:rbac:groups=core.rukpak.io,resources=bundledeployments,verbs=get;list;watch;create;update;patch
5951

6052
// Reconcile is part of the main kubernetes reconciliation loop which aims to
@@ -112,61 +104,102 @@ func checkForUnexpectedFieldChange(a, b operatorsv1alpha1.Operator) bool {
112104

113105
// Helper function to do the actual reconcile
114106
func (r *OperatorReconciler) reconcile(ctx context.Context, op *operatorsv1alpha1.Operator) (ctrl.Result, error) {
115-
// define condition parameters
116-
var status = metav1.ConditionTrue
117-
var reason = operatorsv1alpha1.ReasonResolutionSucceeded
118-
var message = "resolution was successful"
119107

120108
// run resolution
121-
solution, err := r.resolver.Resolve(ctx)
109+
solution, err := r.Resolver.Resolve(ctx)
122110
if err != nil {
123-
status = metav1.ConditionTrue
124-
reason = operatorsv1alpha1.ReasonResolutionFailed
125-
message = err.Error()
126-
} else {
127-
// extract package bundle path from resolved variable
128-
for _, variable := range solution.SelectedVariables() {
129-
switch v := variable.(type) {
130-
case *bundles_and_dependencies.BundleVariable:
131-
packageName, err := v.BundleEntity().PackageName()
111+
apimeta.SetStatusCondition(&op.Status.Conditions, metav1.Condition{
112+
Type: operatorsv1alpha1.TypeReady,
113+
Status: metav1.ConditionFalse,
114+
Reason: operatorsv1alpha1.ReasonResolutionFailed,
115+
Message: err.Error(),
116+
ObservedGeneration: op.GetGeneration(),
117+
})
118+
return ctrl.Result{}, nil
119+
}
120+
121+
// extract package bundle path from resolved variable
122+
packageFound := false
123+
for _, variable := range solution.SelectedVariables() {
124+
switch v := variable.(type) {
125+
case *bundles_and_dependencies.BundleVariable:
126+
packageName, err := v.BundleEntity().PackageName()
127+
if err != nil {
128+
apimeta.SetStatusCondition(&op.Status.Conditions, metav1.Condition{
129+
Type: operatorsv1alpha1.TypeReady,
130+
Status: metav1.ConditionFalse,
131+
Reason: operatorsv1alpha1.ReasonBundleLookupFailed,
132+
Message: err.Error(),
133+
ObservedGeneration: op.GetGeneration(),
134+
})
135+
return ctrl.Result{}, err
136+
}
137+
if packageName == op.Spec.PackageName {
138+
bundlePath, err := v.BundleEntity().BundlePath()
132139
if err != nil {
140+
apimeta.SetStatusCondition(&op.Status.Conditions, metav1.Condition{
141+
Type: operatorsv1alpha1.TypeReady,
142+
Status: metav1.ConditionFalse,
143+
Reason: operatorsv1alpha1.ReasonBundleLookupFailed,
144+
Message: err.Error(),
145+
ObservedGeneration: op.GetGeneration(),
146+
})
133147
return ctrl.Result{}, err
134148
}
135-
if packageName == op.Spec.PackageName {
136-
bundlePath, err := v.BundleEntity().BundlePath()
137-
if err != nil {
138-
return ctrl.Result{}, err
139-
}
140-
dep, err := r.generateExpectedBundleDeployment(*op, bundlePath)
141-
if err != nil {
142-
return ctrl.Result{}, err
143-
}
144-
// Create bundleDeployment if not found or Update if needed
145-
if err := r.ensureBundleDeployment(ctx, dep); err != nil {
146-
return ctrl.Result{}, err
147-
}
148-
break
149+
dep := r.generateExpectedBundleDeployment(*op, bundlePath)
150+
// Create bundleDeployment if not found or Update if needed
151+
if err := r.ensureBundleDeployment(ctx, dep); err != nil {
152+
apimeta.SetStatusCondition(&op.Status.Conditions, metav1.Condition{
153+
Type: operatorsv1alpha1.TypeReady,
154+
Status: metav1.ConditionFalse,
155+
Reason: operatorsv1alpha1.ReasonBundleDeploymentFailed,
156+
Message: err.Error(),
157+
ObservedGeneration: op.GetGeneration(),
158+
})
159+
return ctrl.Result{}, err
149160
}
161+
packageFound = true
162+
break
150163
}
151164
}
152165
}
166+
if !packageFound {
167+
apimeta.SetStatusCondition(&op.Status.Conditions, metav1.Condition{
168+
Type: operatorsv1alpha1.TypeReady,
169+
Status: metav1.ConditionFalse,
170+
Reason: operatorsv1alpha1.ReasonBundleLookupFailed,
171+
Message: fmt.Sprintf("resolved set does not contain expected package %q", op.Spec.PackageName),
172+
ObservedGeneration: op.GetGeneration(),
173+
})
174+
}
153175

154176
// update operator status
155177
apimeta.SetStatusCondition(&op.Status.Conditions, metav1.Condition{
156178
Type: operatorsv1alpha1.TypeReady,
157-
Status: status,
158-
Reason: reason,
159-
Message: message,
179+
Status: metav1.ConditionTrue,
180+
Reason: operatorsv1alpha1.ReasonResolutionSucceeded,
181+
Message: "resolution was successful",
160182
ObservedGeneration: op.GetGeneration(),
161183
})
162184

163185
return ctrl.Result{}, nil
164186
}
165187

166-
func (r *OperatorReconciler) generateExpectedBundleDeployment(o operatorsv1alpha1.Operator, bundlePath string) (*rukpakv1alpha1.BundleDeployment, error) {
167-
dep := &rukpakv1alpha1.BundleDeployment{
188+
func (r *OperatorReconciler) generateExpectedBundleDeployment(o operatorsv1alpha1.Operator, bundlePath string) *rukpakv1alpha1.BundleDeployment {
189+
// TODO: Use unstructured + server side apply?
190+
return &rukpakv1alpha1.BundleDeployment{
168191
ObjectMeta: metav1.ObjectMeta{
169192
Name: o.GetName(),
193+
OwnerReferences: []metav1.OwnerReference{
194+
{
195+
APIVersion: operatorsv1alpha1.GroupVersion.String(),
196+
Kind: "Operator",
197+
Name: o.Name,
198+
UID: o.UID,
199+
Controller: pointer.Bool(true),
200+
BlockOwnerDeletion: pointer.Bool(true),
201+
},
202+
},
170203
},
171204
Spec: rukpakv1alpha1.BundleDeploymentSpec{
172205
//TODO: Don't assume plain provisioner
@@ -193,11 +226,6 @@ func (r *OperatorReconciler) generateExpectedBundleDeployment(o operatorsv1alpha
193226
},
194227
},
195228
}
196-
197-
if err := ctrl.SetControllerReference(&o, dep, r.Scheme); err != nil {
198-
return nil, err
199-
}
200-
return dep, nil
201229
}
202230

203231
// SetupWithManager sets up the controller with the Manager.
@@ -224,6 +252,9 @@ func (r *OperatorReconciler) ensureBundleDeployment(ctx context.Context, desired
224252
}
225253

226254
// Check if the existing bundleDeployment's spec needs to be updated
255+
//
256+
// FIXME: checking only for spec differences means that we will not fixup
257+
// changes to and removals of desired metadata.
227258
if equality.Semantic.DeepEqual(existingBundleDeployment.Spec, desiredBundleDeployment.Spec) {
228259
return nil
229260
}

0 commit comments

Comments
 (0)