4
4
"context"
5
5
"errors"
6
6
"fmt"
7
+ "strings"
8
+
7
9
appsv1 "k8s.io/api/apps/v1"
8
10
corev1 "k8s.io/api/core/v1"
9
11
apierrors "k8s.io/apimachinery/pkg/api/errors"
@@ -14,11 +16,11 @@ import (
14
16
corev1listers "k8s.io/client-go/listers/core/v1"
15
17
"k8s.io/client-go/tools/cache"
16
18
"k8s.io/client-go/util/workqueue"
17
- "strings"
18
19
19
20
operatorv1 "github.com/openshift/api/operator/v1"
20
21
openshiftconfigclientv1 "github.com/openshift/client-go/config/clientset/versioned/typed/config/v1"
21
22
applyoperatorv1 "github.com/openshift/client-go/operator/applyconfigurations/operator/v1"
23
+ "github.com/openshift/library-go/pkg/apiserver/jsonpatch"
22
24
"github.com/openshift/library-go/pkg/apps/deployment"
23
25
"github.com/openshift/library-go/pkg/controller/factory"
24
26
"github.com/openshift/library-go/pkg/operator/events"
@@ -33,7 +35,25 @@ const (
33
35
// Delegate captures a set of methods that hold a custom logic
34
36
type Delegate interface {
35
37
// Sync a method that will be used for delegation. It should bring the desired workload into operation.
36
- Sync (ctx context.Context , controllerContext factory.SyncContext ) (* appsv1.Deployment , bool , []error )
38
+ //
39
+ // It returns a reference to the workload, a bool indicating whether the operator config is at the highest
40
+ // generation, a bool indicating whether the workload references must be removed from the operator status
41
+ // (e.g. in case the workload has been gracefully deleted), two strings indicating the name & namespace
42
+ // of the workload that must be cleaned up from the operator status (respective to the conditions to be
43
+ // deleted) and a list of errors.
44
+ //
45
+ // When a workload gets deleted (as indicated by the removeWorkload boolean), not only its respective
46
+ // status conditions will removed, but also its generations, and its version string will be
47
+ // set to "". To make sure that the StatusSyncer removes any versions that are equal to the empty
48
+ // string completely, use StatusSyncer.WithEmptyVersionRemoval().
49
+ Sync (ctx context.Context , controllerContext factory.SyncContext ) (
50
+ delegateWorkload * appsv1.Deployment ,
51
+ operatorConfigAtHighestGeneration bool ,
52
+ removeWorkload bool ,
53
+ deletedWorkloadName string ,
54
+ deletedWorkloadNamespace string ,
55
+ errs []error ,
56
+ )
37
57
38
58
// PreconditionFulfilled a method that indicates whether all prerequisites are met and we can Sync.
39
59
//
@@ -81,7 +101,7 @@ func NewController(instanceName, operatorNamespace, targetNamespace, targetOpera
81
101
kubeClient kubernetes.Interface ,
82
102
podLister corev1listers.PodLister ,
83
103
informers []factory.Informer ,
84
- tagetNamespaceInformers []factory.Informer ,
104
+ targetNamespaceInformers []factory.Informer ,
85
105
delegate Delegate ,
86
106
openshiftClusterConfigClient openshiftconfigclientv1.ClusterOperatorInterface ,
87
107
eventRecorder events.Recorder ,
@@ -100,11 +120,11 @@ func NewController(instanceName, operatorNamespace, targetNamespace, targetOpera
100
120
delegate : delegate ,
101
121
openshiftClusterConfigClient : openshiftClusterConfigClient ,
102
122
versionRecorder : versionRecorder ,
103
- queue : workqueue .NewNamedRateLimitingQueue (workqueue .DefaultControllerRateLimiter (), instanceName ),
123
+ queue : workqueue .NewNamedRateLimitingQueue (workqueue .DefaultTypedControllerRateLimiter [ any ] (), instanceName ),
104
124
}
105
125
106
126
c := factory .New ()
107
- for _ , nsi := range tagetNamespaceInformers {
127
+ for _ , nsi := range targetNamespaceInformers {
108
128
c .WithNamespaceInformer (nsi , targetNamespace )
109
129
}
110
130
@@ -128,14 +148,14 @@ func (c *Controller) sync(ctx context.Context, controllerContext factory.SyncCon
128
148
}
129
149
130
150
if fulfilled , err := c .delegate .PreconditionFulfilled (ctx ); err != nil {
131
- return c .updateOperatorStatus (ctx , operatorStatus , nil , false , false , []error {err })
151
+ return c .updateOperatorStatus (ctx , operatorStatus , nil , false , false , false , "" , "" , []error {err })
132
152
} else if ! fulfilled {
133
- return c .updateOperatorStatus (ctx , operatorStatus , nil , false , false , nil )
153
+ return c .updateOperatorStatus (ctx , operatorStatus , nil , false , false , false , "" , "" , nil )
134
154
}
135
155
136
- workload , operatorConfigAtHighestGeneration , errs := c .delegate .Sync (ctx , controllerContext )
156
+ workload , operatorConfigAtHighestGeneration , removeWorkload , deletedWorkloadName , deletedWorkloadNamespace , errs := c .delegate .Sync (ctx , controllerContext )
137
157
138
- return c .updateOperatorStatus (ctx , operatorStatus , workload , operatorConfigAtHighestGeneration , true , errs )
158
+ return c .updateOperatorStatus (ctx , operatorStatus , workload , operatorConfigAtHighestGeneration , true , removeWorkload , deletedWorkloadName , deletedWorkloadNamespace , errs )
139
159
}
140
160
141
161
// shouldSync checks ManagementState to determine if we can run this operator, probably set by a cluster administrator.
@@ -156,48 +176,74 @@ func (c *Controller) shouldSync(ctx context.Context, operatorSpec *operatorv1.Op
156
176
}
157
177
}
158
178
159
- // updateOperatorStatus updates the status based on the actual workload and errors that might have occurred during synchronization.
160
- func (c * Controller ) updateOperatorStatus (ctx context.Context , previousStatus * operatorv1.OperatorStatus , workload * appsv1.Deployment , operatorConfigAtHighestGeneration bool , preconditionsReady bool , errs []error ) (err error ) {
179
+ func (c * Controller ) updateOperatorStatus (ctx context.Context ,
180
+ previousStatus * operatorv1.OperatorStatus ,
181
+ workload * appsv1.Deployment ,
182
+ operatorConfigAtHighestGeneration , preconditionsReady , removeWorkload bool ,
183
+ deletedWorkloadName , deletedWorkloadNamespace string ,
184
+ errs []error ,
185
+ ) (err error ) {
186
+
161
187
if errs == nil {
162
188
errs = []error {}
163
189
}
164
190
165
- deploymentAvailableCondition := applyoperatorv1 .OperatorCondition ().
166
- WithType (fmt .Sprintf ("%sDeployment%s" , c .conditionsPrefix , operatorv1 .OperatorStatusTypeAvailable ))
191
+ typeAvailable := fmt .Sprintf ("%sDeployment%s" , c .conditionsPrefix , operatorv1 .OperatorStatusTypeAvailable )
192
+ typeDegraded := fmt .Sprintf ("%sDeployment%s" , c .conditionsPrefix , operatorv1 .OperatorStatusTypeDegraded )
193
+ typeProgressing := fmt .Sprintf ("%sDeployment%s" , c .conditionsPrefix , operatorv1 .OperatorStatusTypeProgressing )
194
+ typeWorkloadDegraded := fmt .Sprintf ("%sWorkload%s" , c .conditionsPrefix , operatorv1 .OperatorStatusTypeDegraded )
167
195
168
- workloadDegradedCondition := applyoperatorv1 .OperatorCondition ().
169
- WithType (fmt .Sprintf ("%sWorkloadDegraded" , c .conditionsPrefix ))
196
+ if removeWorkload && len (deletedWorkloadName ) > 0 && len (deletedWorkloadNamespace ) > 0 {
197
+ // the workload has been deleted; remove conditions, generations and version
198
+ patch := jsonpatch .Merge (
199
+ v1helpers .RemoveConditionsJSONPatch (previousStatus , []string {typeAvailable , typeDegraded , typeProgressing , typeWorkloadDegraded }),
200
+ v1helpers .RemoveWorkloadGenerationsJSONPatch (previousStatus , deletedWorkloadName , deletedWorkloadNamespace ),
201
+ )
170
202
171
- deploymentDegradedCondition := applyoperatorv1 .OperatorCondition ().
172
- WithType (fmt .Sprintf ("%sDeploymentDegraded" , c .conditionsPrefix ))
203
+ if ! patch .IsEmpty () {
204
+ c .setVersion (deletedWorkloadName , "" )
205
+ patchErr := c .operatorClient .PatchOperatorStatus (ctx , patch )
206
+ if patchErr != nil {
207
+ errs = append (errs , patchErr )
208
+ }
209
+ }
173
210
174
- deploymentProgressingCondition := applyoperatorv1 . OperatorCondition ().
175
- WithType ( fmt . Sprintf ( "%sDeployment%s" , c . conditionsPrefix , operatorv1 . OperatorStatusTypeProgressing ))
211
+ return kerrors . NewAggregate ( errs )
212
+ }
176
213
177
- status := applyoperatorv1 .OperatorStatus ()
178
- if workload != nil {
179
- // The Hash field is not required since the LastGeneration field is enough to uniquely identify a Deployment's desired state
180
- status = status .WithGenerations (applyoperatorv1 .GenerationStatus ().
181
- WithGroup ("apps" ).
182
- WithResource ("deployments" ).
183
- WithNamespace (workload .Namespace ).
184
- WithName (workload .Name ).
185
- WithLastGeneration (workload .Generation ),
186
- )
214
+ deploymentAvailableCondition := applyoperatorv1 .OperatorCondition ().WithType (typeAvailable )
215
+ deploymentDegradedCondition := applyoperatorv1 .OperatorCondition ().WithType (typeDegraded )
216
+ deploymentProgressingCondition := applyoperatorv1 .OperatorCondition ().WithType (typeProgressing )
217
+ workloadDegradedCondition := applyoperatorv1 .OperatorCondition ().WithType (typeWorkloadDegraded )
218
+
219
+ // update workload conditions, generations and version
220
+ statusApplyConfig := applyoperatorv1 .OperatorStatus ()
221
+ statusApplyConfig = c .updateOperatorStatusConditions (previousStatus , statusApplyConfig , workload , preconditionsReady , deploymentAvailableCondition , deploymentDegradedCondition , deploymentProgressingCondition , workloadDegradedCondition , errs )
222
+ statusApplyConfig = c .updateOperatorStatusGenerationsVersion (statusApplyConfig , workload , operatorConfigAtHighestGeneration , preconditionsReady )
223
+
224
+ applyErr := c .operatorClient .ApplyOperatorStatus (ctx , c .controllerInstanceName , statusApplyConfig )
225
+ if applyErr != nil {
226
+ errs = append (errs , applyErr )
187
227
}
188
228
189
- defer func () {
190
- status = status .WithConditions (
191
- deploymentAvailableCondition ,
192
- deploymentDegradedCondition ,
193
- deploymentProgressingCondition ,
194
- workloadDegradedCondition ,
195
- )
229
+ return kerrors .NewAggregate (errs )
230
+ }
196
231
197
- if applyError := c .operatorClient .ApplyOperatorStatus (ctx , c .controllerInstanceName , status ); applyError != nil {
198
- err = applyError
199
- }
200
- }()
232
+ func (c * Controller ) updateOperatorStatusConditions (
233
+ previousStatus * operatorv1.OperatorStatus ,
234
+ statusApplyConfig * applyoperatorv1.OperatorStatusApplyConfiguration ,
235
+ workload * appsv1.Deployment ,
236
+ preconditionsReady bool ,
237
+ deploymentAvailableCondition , deploymentDegradedCondition , deploymentProgressingCondition , workloadDegradedCondition * applyoperatorv1.OperatorConditionApplyConfiguration ,
238
+ errs []error ,
239
+ ) * applyoperatorv1.OperatorStatusApplyConfiguration {
240
+
241
+ defer statusApplyConfig .WithConditions (
242
+ deploymentAvailableCondition ,
243
+ deploymentDegradedCondition ,
244
+ deploymentProgressingCondition ,
245
+ workloadDegradedCondition ,
246
+ )
201
247
202
248
if ! preconditionsReady {
203
249
var message string
@@ -228,7 +274,7 @@ func (c *Controller) updateOperatorStatus(ctx context.Context, previousStatus *o
228
274
WithReason ("PreconditionNotFulfilled" ).
229
275
WithMessage (message )
230
276
231
- return kerrors . NewAggregate ( errs )
277
+ return statusApplyConfig
232
278
}
233
279
234
280
if len (errs ) > 0 {
@@ -267,7 +313,7 @@ func (c *Controller) updateOperatorStatus(ctx context.Context, previousStatus *o
267
313
WithReason ("NoDeployment" ).
268
314
WithMessage (message )
269
315
270
- return kerrors . NewAggregate ( errs )
316
+ return statusApplyConfig
271
317
}
272
318
273
319
if workload .Status .AvailableReplicas == 0 {
@@ -336,22 +382,55 @@ func (c *Controller) updateOperatorStatus(ctx context.Context, previousStatus *o
336
382
WithReason ("AsExpected" )
337
383
}
338
384
385
+ return statusApplyConfig
386
+ }
387
+
388
+ func (c * Controller ) updateOperatorStatusGenerationsVersion (
389
+ statusApplyConfig * applyoperatorv1.OperatorStatusApplyConfiguration ,
390
+ workload * appsv1.Deployment ,
391
+ operatorConfigAtHighestGeneration , preconditionsReady bool ,
392
+ ) * applyoperatorv1.OperatorStatusApplyConfiguration {
393
+
394
+ if workload == nil {
395
+ return statusApplyConfig
396
+ }
397
+
398
+ statusApplyConfig = statusApplyConfig .WithGenerations (applyoperatorv1 .GenerationStatus ().
399
+ WithGroup ("apps" ).
400
+ WithResource ("deployments" ).
401
+ WithNamespace (workload .Namespace ).
402
+ WithName (workload .Name ).
403
+ WithLastGeneration (workload .Generation ),
404
+ )
405
+
406
+ if ! preconditionsReady {
407
+ return statusApplyConfig
408
+ }
409
+
410
+ desiredReplicas := int32 (1 )
411
+ if workload .Spec .Replicas != nil {
412
+ desiredReplicas = * (workload .Spec .Replicas )
413
+ }
414
+
415
+ workloadAtHighestGeneration := workload .ObjectMeta .Generation == workload .Status .ObservedGeneration
416
+ workloadHasAllPodsAvailable := workload .Status .AvailableReplicas >= desiredReplicas
417
+
339
418
// if the deployment is all available and at the expected generation, then update the version to the latest
340
419
// when we update, the image pull spec should immediately be different, which should immediately cause a deployment rollout
341
420
// which should immediately result in a deployment generation diff, which should cause this block to be skipped until it is ready.
342
421
workloadHasAllPodsUpdated := workload .Status .UpdatedReplicas == desiredReplicas
343
422
if workloadAtHighestGeneration && workloadHasAllPodsAvailable && workloadHasAllPodsUpdated && operatorConfigAtHighestGeneration {
344
- operandName := workload .Name
345
- if len (c .operandNamePrefix ) > 0 {
346
- operandName = fmt .Sprintf ("%s-%s" , c .operandNamePrefix , workload .Name )
347
- }
348
- c .versionRecorder .SetVersion (operandName , c .targetOperandVersion )
423
+ c .setVersion (workload .Name , c .targetOperandVersion )
349
424
}
350
425
351
- if len (errs ) > 0 {
352
- return kerrors .NewAggregate (errs )
426
+ return statusApplyConfig
427
+ }
428
+
429
+ func (c * Controller ) setVersion (operandName , version string ) {
430
+ if len (c .operandNamePrefix ) > 0 {
431
+ operandName = fmt .Sprintf ("%s-%s" , c .operandNamePrefix , operandName )
353
432
}
354
- return nil
433
+ c . versionRecorder . SetVersion ( operandName , version )
355
434
}
356
435
357
436
// hasDeploymentProgressed returns true if the deployment reports NewReplicaSetAvailable
0 commit comments