Skip to content

Commit 4e9fee1

Browse files
committed
workload: indicate whether a workload has been deleted during Sync
Also, refactor update status func to separate between updating the status and also the version and generations.
1 parent c414a91 commit 4e9fee1

File tree

2 files changed

+241
-65
lines changed

2 files changed

+241
-65
lines changed

pkg/operator/apiserver/controller/workload/workload.go

Lines changed: 129 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ import (
44
"context"
55
"errors"
66
"fmt"
7+
"strings"
8+
79
appsv1 "k8s.io/api/apps/v1"
810
corev1 "k8s.io/api/core/v1"
911
apierrors "k8s.io/apimachinery/pkg/api/errors"
@@ -14,11 +16,11 @@ import (
1416
corev1listers "k8s.io/client-go/listers/core/v1"
1517
"k8s.io/client-go/tools/cache"
1618
"k8s.io/client-go/util/workqueue"
17-
"strings"
1819

1920
operatorv1 "github.com/openshift/api/operator/v1"
2021
openshiftconfigclientv1 "github.com/openshift/client-go/config/clientset/versioned/typed/config/v1"
2122
applyoperatorv1 "github.com/openshift/client-go/operator/applyconfigurations/operator/v1"
23+
"github.com/openshift/library-go/pkg/apiserver/jsonpatch"
2224
"github.com/openshift/library-go/pkg/apps/deployment"
2325
"github.com/openshift/library-go/pkg/controller/factory"
2426
"github.com/openshift/library-go/pkg/operator/events"
@@ -33,7 +35,25 @@ const (
3335
// Delegate captures a set of methods that hold a custom logic
3436
type Delegate interface {
3537
// 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+
)
3757

3858
// PreconditionFulfilled a method that indicates whether all prerequisites are met and we can Sync.
3959
//
@@ -81,7 +101,7 @@ func NewController(instanceName, operatorNamespace, targetNamespace, targetOpera
81101
kubeClient kubernetes.Interface,
82102
podLister corev1listers.PodLister,
83103
informers []factory.Informer,
84-
tagetNamespaceInformers []factory.Informer,
104+
targetNamespaceInformers []factory.Informer,
85105
delegate Delegate,
86106
openshiftClusterConfigClient openshiftconfigclientv1.ClusterOperatorInterface,
87107
eventRecorder events.Recorder,
@@ -100,11 +120,11 @@ func NewController(instanceName, operatorNamespace, targetNamespace, targetOpera
100120
delegate: delegate,
101121
openshiftClusterConfigClient: openshiftClusterConfigClient,
102122
versionRecorder: versionRecorder,
103-
queue: workqueue.NewNamedRateLimitingQueue(workqueue.DefaultControllerRateLimiter(), instanceName),
123+
queue: workqueue.NewNamedRateLimitingQueue(workqueue.DefaultTypedControllerRateLimiter[any](), instanceName),
104124
}
105125

106126
c := factory.New()
107-
for _, nsi := range tagetNamespaceInformers {
127+
for _, nsi := range targetNamespaceInformers {
108128
c.WithNamespaceInformer(nsi, targetNamespace)
109129
}
110130

@@ -128,14 +148,14 @@ func (c *Controller) sync(ctx context.Context, controllerContext factory.SyncCon
128148
}
129149

130150
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})
132152
} else if !fulfilled {
133-
return c.updateOperatorStatus(ctx, operatorStatus, nil, false, false, nil)
153+
return c.updateOperatorStatus(ctx, operatorStatus, nil, false, false, false, "", "", nil)
134154
}
135155

136-
workload, operatorConfigAtHighestGeneration, errs := c.delegate.Sync(ctx, controllerContext)
156+
workload, operatorConfigAtHighestGeneration, removeWorkload, deletedWorkloadName, deletedWorkloadNamespace, errs := c.delegate.Sync(ctx, controllerContext)
137157

138-
return c.updateOperatorStatus(ctx, operatorStatus, workload, operatorConfigAtHighestGeneration, true, errs)
158+
return c.updateOperatorStatus(ctx, operatorStatus, workload, operatorConfigAtHighestGeneration, true, removeWorkload, deletedWorkloadName, deletedWorkloadNamespace, errs)
139159
}
140160

141161
// 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
156176
}
157177
}
158178

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+
161187
if errs == nil {
162188
errs = []error{}
163189
}
164190

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)
167195

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+
)
170202

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+
}
173210

174-
deploymentProgressingCondition := applyoperatorv1.OperatorCondition().
175-
WithType(fmt.Sprintf("%sDeployment%s", c.conditionsPrefix, operatorv1.OperatorStatusTypeProgressing))
211+
return kerrors.NewAggregate(errs)
212+
}
176213

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)
187227
}
188228

189-
defer func() {
190-
status = status.WithConditions(
191-
deploymentAvailableCondition,
192-
deploymentDegradedCondition,
193-
deploymentProgressingCondition,
194-
workloadDegradedCondition,
195-
)
229+
return kerrors.NewAggregate(errs)
230+
}
196231

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+
)
201247

202248
if !preconditionsReady {
203249
var message string
@@ -228,7 +274,7 @@ func (c *Controller) updateOperatorStatus(ctx context.Context, previousStatus *o
228274
WithReason("PreconditionNotFulfilled").
229275
WithMessage(message)
230276

231-
return kerrors.NewAggregate(errs)
277+
return statusApplyConfig
232278
}
233279

234280
if len(errs) > 0 {
@@ -267,7 +313,7 @@ func (c *Controller) updateOperatorStatus(ctx context.Context, previousStatus *o
267313
WithReason("NoDeployment").
268314
WithMessage(message)
269315

270-
return kerrors.NewAggregate(errs)
316+
return statusApplyConfig
271317
}
272318

273319
if workload.Status.AvailableReplicas == 0 {
@@ -336,22 +382,55 @@ func (c *Controller) updateOperatorStatus(ctx context.Context, previousStatus *o
336382
WithReason("AsExpected")
337383
}
338384

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+
339418
// if the deployment is all available and at the expected generation, then update the version to the latest
340419
// when we update, the image pull spec should immediately be different, which should immediately cause a deployment rollout
341420
// which should immediately result in a deployment generation diff, which should cause this block to be skipped until it is ready.
342421
workloadHasAllPodsUpdated := workload.Status.UpdatedReplicas == desiredReplicas
343422
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)
349424
}
350425

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)
353432
}
354-
return nil
433+
c.versionRecorder.SetVersion(operandName, version)
355434
}
356435

357436
// hasDeploymentProgressed returns true if the deployment reports NewReplicaSetAvailable

0 commit comments

Comments
 (0)