Skip to content

Commit aba96a9

Browse files
Merge pull request #1852 from njhale/fix-hotloop
[release-4.6] Bug 1889745: fix(operators): prevent hotloop
2 parents d7b2aff + 356ef4e commit aba96a9

File tree

1 file changed

+60
-40
lines changed

1 file changed

+60
-40
lines changed

pkg/controller/operators/operator_controller.go

Lines changed: 60 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -45,11 +45,11 @@ type OperatorReconciler struct {
4545
client.Client
4646

4747
log logr.Logger
48-
mu sync.RWMutex
4948
factory decorators.OperatorFactory
5049

51-
// operators contains the names of Operators the OperatorReconciler has observed exist.
52-
operators map[types.NamespacedName]struct{}
50+
// last observed resourceVersion for known Operators
51+
lastResourceVersion map[types.NamespacedName]string
52+
mu sync.RWMutex
5353
}
5454

5555
// +kubebuilder:rbac:groups=operators.coreos.com,resources=operators,verbs=create;update;patch;delete
@@ -101,9 +101,9 @@ func NewOperatorReconciler(cli client.Client, log logr.Logger, scheme *runtime.S
101101
return &OperatorReconciler{
102102
Client: cli,
103103

104-
log: log,
105-
factory: factory,
106-
operators: map[types.NamespacedName]struct{}{},
104+
log: log,
105+
factory: factory,
106+
lastResourceVersion: map[types.NamespacedName]string{},
107107
}, nil
108108
}
109109

@@ -115,40 +115,61 @@ func (r *OperatorReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) {
115115
log := r.log.WithValues("request", req)
116116
log.V(1).Info("reconciling operator")
117117

118-
// Fetch the Operator from the cache
118+
// Get the Operator
119119
ctx := context.TODO()
120+
create := false
121+
name := req.NamespacedName.Name
120122
in := &operatorsv1.Operator{}
121123
if err := r.Get(ctx, req.NamespacedName, in); err != nil {
122124
if apierrors.IsNotFound(err) {
123-
log.Info("Could not find Operator")
124-
r.unobserve(req.NamespacedName)
125-
// TODO(njhale): Recreate operator if we can find any components.
125+
create = true
126+
in.SetName(name)
126127
} else {
127-
log.Error(err, "Error finding Operator")
128+
log.Error(err, "Error requesting Operator")
129+
return reconcile.Result{Requeue: true}, nil
128130
}
131+
}
129132

133+
rv, ok := r.getLastResourceVersion(req.NamespacedName)
134+
if !create && ok && rv == in.ResourceVersion {
135+
log.V(1).Info("Operator is already up-to-date")
130136
return reconcile.Result{}, nil
131137
}
132-
r.observe(req.NamespacedName)
138+
139+
// Set the cached resource version to 0 so we can handle
140+
// the race with requests enqueuing via mapComponentRequests
141+
r.setLastResourceVersion(req.NamespacedName, "0")
133142

134143
// Wrap with convenience decorator
135144
operator, err := r.factory.NewOperator(in)
136145
if err != nil {
137146
log.Error(err, "Could not wrap Operator with convenience decorator")
138-
return reconcile.Result{}, nil
147+
return reconcile.Result{Requeue: true}, nil
139148
}
140149

141150
if err = r.updateComponents(ctx, operator); err != nil {
142151
log.Error(err, "Could not update components")
143-
return reconcile.Result{}, nil
152+
return reconcile.Result{Requeue: true}, nil
144153

145154
}
146155

147-
if err := r.Status().Update(ctx, operator.Operator); err != nil {
148-
log.Error(err, "Could not update Operator status")
149-
return ctrl.Result{}, err
156+
if create {
157+
if err := r.Create(context.Background(), operator.Operator); err != nil && !apierrors.IsAlreadyExists(err) {
158+
r.log.Error(err, "Could not create Operator", "operator", name)
159+
return ctrl.Result{Requeue: true}, nil
160+
}
161+
} else {
162+
if err := r.Status().Update(ctx, operator.Operator); err != nil {
163+
log.Error(err, "Could not update Operator status")
164+
return ctrl.Result{Requeue: true}, nil
165+
}
150166
}
151167

168+
// Only set the resource version if it already exists.
169+
// If it does not exist, it means mapComponentRequests was called
170+
// while we were reconciling and we need to reconcile again
171+
r.setLastResourceVersionIfExists(req.NamespacedName, operator.GetResourceVersion())
172+
152173
return ctrl.Result{}, nil
153174
}
154175

@@ -196,45 +217,44 @@ func (r *OperatorReconciler) listComponents(ctx context.Context, selector labels
196217
return componentLists, nil
197218
}
198219

199-
func (r *OperatorReconciler) observed(name types.NamespacedName) bool {
220+
func (r *OperatorReconciler) getLastResourceVersion(name types.NamespacedName) (string, bool) {
200221
r.mu.RLock()
201222
defer r.mu.RUnlock()
202-
_, ok := r.operators[name]
203-
return ok
223+
rv, ok := r.lastResourceVersion[name]
224+
return rv, ok
204225
}
205226

206-
func (r *OperatorReconciler) observe(name types.NamespacedName) {
227+
func (r *OperatorReconciler) setLastResourceVersion(name types.NamespacedName, rv string) {
207228
r.mu.Lock()
208229
defer r.mu.Unlock()
209-
r.operators[name] = struct{}{}
230+
r.lastResourceVersion[name] = rv
210231
}
211232

212-
func (r *OperatorReconciler) unobserve(name types.NamespacedName) {
233+
func (r *OperatorReconciler) setLastResourceVersionIfExists(name types.NamespacedName, rv string) {
213234
r.mu.Lock()
214235
defer r.mu.Unlock()
215-
delete(r.operators, name)
236+
if _, ok := r.lastResourceVersion[name]; ok {
237+
r.lastResourceVersion[name] = rv
238+
}
216239
}
217240

218-
func (r *OperatorReconciler) mapComponentRequests(obj handler.MapObject) (requests []reconcile.Request) {
241+
func (r *OperatorReconciler) unsetLastResourceVersion(name types.NamespacedName) {
242+
r.mu.Lock()
243+
defer r.mu.Unlock()
244+
delete(r.lastResourceVersion, name)
245+
}
246+
247+
func (r *OperatorReconciler) mapComponentRequests(obj handler.MapObject) []reconcile.Request {
248+
var requests []reconcile.Request
219249
if obj.Meta == nil {
220-
return
250+
return requests
221251
}
222252

223253
for _, name := range decorators.OperatorNames(obj.Meta.GetLabels()) {
224-
// Only enqueue if we can find the operator in our cache
225-
if r.observed(name) {
226-
requests = append(requests, reconcile.Request{NamespacedName: name})
227-
continue
228-
}
229-
230-
// Otherwise, best-effort generate a new operator
231-
// TODO(njhale): Implement verification that the operator-discovery admission webhook accepted this label (JWT or maybe sign a set of fields?)
232-
operator := &operatorsv1.Operator{}
233-
operator.SetName(name.Name)
234-
if err := r.Create(context.Background(), operator); err != nil && !apierrors.IsAlreadyExists(err) {
235-
r.log.Error(err, "couldn't generate operator", "operator", name, "component", obj.Meta.GetSelfLink())
236-
}
254+
// unset the last recorded resource version so the Operator will reconcile
255+
r.unsetLastResourceVersion(name)
256+
requests = append(requests, reconcile.Request{NamespacedName: name})
237257
}
238258

239-
return
259+
return requests
240260
}

0 commit comments

Comments
 (0)