Skip to content

Commit 0e81d05

Browse files
Merge pull request #1977 from liouk/AUTH-543
AUTH-543: Add workload deletion condition method and clean-up
2 parents 7f9bc3e + adfa251 commit 0e81d05

File tree

5 files changed

+107
-19
lines changed

5 files changed

+107
-19
lines changed

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

Lines changed: 31 additions & 6 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,7 +16,6 @@ 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"
@@ -41,6 +42,12 @@ type Delegate interface {
4142
// operator will be degraded, not available and not progressing
4243
// returned errors (if any) will be added to the Message field
4344
PreconditionFulfilled(ctx context.Context) (bool, error)
45+
46+
// WorkloadDeleted indicates whether the delegate workload has been deleted or not. It returns a bool
47+
// flag to indicate this, a string representing the workload's name and an error. When true, this
48+
// controller will remove any fields from the operator status that the controller is managing, and
49+
// will also remove the version field.
50+
WorkloadDeleted(ctx context.Context) (bool, string, error)
4451
}
4552

4653
// Controller is a generic workload controller that deals with Deployment resource.
@@ -133,6 +140,20 @@ func (c *Controller) sync(ctx context.Context, controllerContext factory.SyncCon
133140
return c.updateOperatorStatus(ctx, operatorStatus, nil, false, false, nil)
134141
}
135142

143+
if deleted, operandName, err := c.delegate.WorkloadDeleted(ctx); err != nil {
144+
return c.updateOperatorStatus(ctx, operatorStatus, nil, false, false, []error{err})
145+
} else if deleted {
146+
// Server-Side-Apply with an empty operator status for the specific field manager will effectively
147+
// remove any conditions and generations owned by it, because the respective API fields have 'map'
148+
// as the list type where field managers can be list element-specific
149+
if err := c.operatorClient.ApplyOperatorStatus(ctx, c.controllerInstanceName, applyoperatorv1.OperatorStatus()); err != nil {
150+
return err
151+
}
152+
153+
c.versionRecorder.UnsetVersion(c.constructOperandNameFor(operandName))
154+
return nil
155+
}
156+
136157
workload, operatorConfigAtHighestGeneration, errs := c.delegate.Sync(ctx, controllerContext)
137158

138159
return c.updateOperatorStatus(ctx, operatorStatus, workload, operatorConfigAtHighestGeneration, true, errs)
@@ -341,11 +362,7 @@ func (c *Controller) updateOperatorStatus(ctx context.Context, previousStatus *o
341362
// which should immediately result in a deployment generation diff, which should cause this block to be skipped until it is ready.
342363
workloadHasAllPodsUpdated := workload.Status.UpdatedReplicas == desiredReplicas
343364
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)
365+
c.versionRecorder.SetVersion(c.constructOperandNameFor(workload.Name), c.targetOperandVersion)
349366
}
350367

351368
if len(errs) > 0 {
@@ -354,6 +371,14 @@ func (c *Controller) updateOperatorStatus(ctx context.Context, previousStatus *o
354371
return nil
355372
}
356373

374+
func (c *Controller) constructOperandNameFor(name string) string {
375+
if len(c.operandNamePrefix) > 0 {
376+
return fmt.Sprintf("%s-%s", c.operandNamePrefix, name)
377+
}
378+
379+
return name
380+
}
381+
357382
// hasDeploymentProgressed returns true if the deployment reports NewReplicaSetAvailable
358383
// via the DeploymentProgressing condition
359384
func hasDeploymentProgressed(status appsv1.DeploymentStatus) bool {

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

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,13 @@ package workload
33
import (
44
"context"
55
"fmt"
6-
clocktesting "k8s.io/utils/clock/testing"
76
"testing"
87
"time"
98

109
corev1 "k8s.io/api/core/v1"
1110
"k8s.io/apimachinery/pkg/labels"
1211
corev1listers "k8s.io/client-go/listers/core/v1"
12+
clocktesting "k8s.io/utils/clock/testing"
1313
"k8s.io/utils/ptr"
1414

1515
operatorv1 "github.com/openshift/api/operator/v1"
@@ -39,6 +39,10 @@ type testDelegate struct {
3939
syncErrrors []error
4040
}
4141

42+
func (d *testDelegate) WorkloadDeleted(_ context.Context) (bool, string, error) {
43+
return false, "", nil
44+
}
45+
4246
func (d *testDelegate) PreconditionFulfilled(_ context.Context) (bool, error) {
4347
return d.preconditionReady, d.preconditionErr
4448
}

pkg/operator/status/status_controller.go

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,22 +2,21 @@ package status
22

33
import (
44
"context"
5-
"k8s.io/utils/clock"
65
"strings"
76
"time"
87

8+
"k8s.io/apimachinery/pkg/api/equality"
9+
apierrors "k8s.io/apimachinery/pkg/api/errors"
10+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
11+
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
912
"k8s.io/klog/v2"
13+
"k8s.io/utils/clock"
1014

1115
configv1 "github.com/openshift/api/config/v1"
1216
operatorv1 "github.com/openshift/api/operator/v1"
1317
configv1client "github.com/openshift/client-go/config/clientset/versioned/typed/config/v1"
1418
configv1informers "github.com/openshift/client-go/config/informers/externalversions/config/v1"
1519
configv1listers "github.com/openshift/client-go/config/listers/config/v1"
16-
"k8s.io/apimachinery/pkg/api/equality"
17-
apierrors "k8s.io/apimachinery/pkg/api/errors"
18-
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
19-
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
20-
2120
configv1helpers "github.com/openshift/library-go/pkg/config/clusteroperator/v1helpers"
2221
"github.com/openshift/library-go/pkg/controller/factory"
2322
"github.com/openshift/library-go/pkg/operator/events"
@@ -29,6 +28,8 @@ import (
2928
type VersionGetter interface {
3029
// SetVersion is a way to set the version for an operand. It must be thread-safe
3130
SetVersion(operandName, version string)
31+
// UnsetVersion removes a version for an operand if it exists; it is a no-op otherwise. It must be thread-safe
32+
UnsetVersion(operandName string)
3233
// GetVersion is way to get the versions for all operands. It must be thread-safe and return an object that doesn't mutate
3334
GetVersions() map[string]string
3435
// VersionChangedChannel is a channel that will get an item whenever SetVersion has been called

pkg/operator/status/version.go

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -35,14 +35,19 @@ func (v *versionGetter) SetVersion(operandName, version string) {
3535
defer v.lock.Unlock()
3636

3737
v.versions[operandName] = version
38+
v.notifyChannelsLocked()
39+
}
3840

39-
for i := range v.notificationChannels {
40-
ch := v.notificationChannels[i]
41-
// don't let a slow consumer block the rest
42-
go func() {
43-
ch <- struct{}{}
44-
}()
41+
func (v *versionGetter) UnsetVersion(operandName string) {
42+
v.lock.Lock()
43+
defer v.lock.Unlock()
44+
45+
if _, exists := v.versions[operandName]; !exists {
46+
return
4547
}
48+
49+
delete(v.versions, operandName)
50+
v.notifyChannelsLocked()
4651
}
4752

4853
func (v *versionGetter) GetVersions() map[string]string {
@@ -65,6 +70,17 @@ func (v *versionGetter) VersionChangedChannel() <-chan struct{} {
6570
return channel
6671
}
6772

73+
// notifyChannelsLocked must be called under a locked v.lock
74+
func (v *versionGetter) notifyChannelsLocked() {
75+
for i := range v.notificationChannels {
76+
ch := v.notificationChannels[i]
77+
// don't let a slow consumer block the rest
78+
go func() {
79+
ch <- struct{}{}
80+
}()
81+
}
82+
}
83+
6884
func ImageForOperandFromEnv() string {
6985
return os.Getenv(operandImageEnvVarName)
7086
}

pkg/operator/status/version_test.go

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,3 +33,45 @@ func TestVersionGetterBasic(t *testing.T) {
3333
}
3434

3535
}
36+
37+
func TestVersionGetterUnset(t *testing.T) {
38+
versionGetter := NewVersionGetter()
39+
40+
versionGetter.UnsetVersion("nonexistent")
41+
expected := map[string]string{}
42+
versions := versionGetter.GetVersions()
43+
if !reflect.DeepEqual(expected, versions) {
44+
t.Fatalf("Expected %v, got %v", expected, versions)
45+
}
46+
47+
versionGetter.SetVersion("foo", "1.0.0")
48+
versionGetter.SetVersion("bar", "2.0.0")
49+
50+
versions = versionGetter.GetVersions()
51+
expected = map[string]string{"foo": "1.0.0", "bar": "2.0.0"}
52+
if !reflect.DeepEqual(expected, versions) {
53+
t.Fatalf("wanted: %v; got: %v", expected, versions)
54+
}
55+
56+
ch := versionGetter.VersionChangedChannel()
57+
versionGetter.UnsetVersion("foo")
58+
59+
select {
60+
case <-ch:
61+
// we got notified
62+
case <-time.After(5 * time.Second):
63+
t.Fatal("expected change notification after UnsetVersion but didn't get any")
64+
}
65+
66+
versions = versionGetter.GetVersions()
67+
expected = map[string]string{"bar": "2.0.0"}
68+
if !reflect.DeepEqual(expected, versions) {
69+
t.Fatalf("Expected %v, got %v", expected, versions)
70+
}
71+
72+
versionGetter.UnsetVersion("nonexistent")
73+
versions = versionGetter.GetVersions()
74+
if !reflect.DeepEqual(expected, versions) {
75+
t.Fatalf("Expected %v, got %v", expected, versions)
76+
}
77+
}

0 commit comments

Comments
 (0)