Skip to content

Commit b2086bd

Browse files
tylerslatonnjhale
andauthored
fix(openshift): use env var instead of clusterversion status (#2817)
Get the current OpenShift release version from the RELEASE_VERSION environment variable since the behavior of the original source -- the ClusterVersion desired release status field -- has changed. Signed-off-by: Tyler Slaton <[email protected]> Co-authored-by: Nick Hale <[email protected]>
1 parent c652154 commit b2086bd

File tree

4 files changed

+90
-124
lines changed

4 files changed

+90
-124
lines changed

pkg/controller/operators/openshift/clusteroperator_controller_test.go

+29-36
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package openshift
22

33
import (
44
"fmt"
5+
"os"
56

67
semver "github.com/blang/semver/v4"
78
. "github.com/onsi/ginkgo/v2"
@@ -19,7 +20,6 @@ import (
1920
var _ = Describe("ClusterOperator controller", func() {
2021
var (
2122
clusterOperatorName types.NamespacedName
22-
cv *configv1.ClusterVersion
2323
csv *operatorsv1alpha1.ClusterServiceVersion
2424
)
2525

@@ -55,33 +55,11 @@ var _ = Describe("ClusterOperator controller", func() {
5555
})
5656

5757
BeforeEach(func() {
58-
// "version" singleton is available in OpenShift by default
59-
cv = &configv1.ClusterVersion{}
60-
cv.SetName("version")
61-
62-
Eventually(func() error {
63-
return k8sClient.Create(ctx, cv)
64-
}).Should(Succeed())
65-
66-
cv.Status = configv1.ClusterVersionStatus{
67-
Desired: configv1.Release{
68-
Version: clusterVersion,
69-
},
70-
}
71-
72-
Eventually(func() error {
73-
return k8sClient.Status().Update(ctx, cv)
74-
}).Should(Succeed())
58+
os.Setenv(releaseEnvVar, clusterVersion)
7559
})
7660

7761
AfterEach(func() {
78-
Eventually(func() error {
79-
err := k8sClient.Delete(ctx, cv)
80-
if err != nil && apierrors.IsNotFound(err) {
81-
err = nil
82-
}
83-
return err
84-
}).Should(Succeed())
62+
resetCurrentReleaseTo(clusterVersion)
8563
})
8664

8765
It("should ensure the ClusterOperator always exists", func() {
@@ -143,10 +121,14 @@ var _ = Describe("ClusterOperator controller", func() {
143121
}))
144122

145123
By("setting upgradeable=false when there's an error determining compatibility")
146-
cv.Status = configv1.ClusterVersionStatus{}
147-
124+
// Reset the ClusterOperator with an invalid version set
125+
Expect(resetCurrentReleaseTo("")).To(Succeed())
148126
Eventually(func() error {
149-
return k8sClient.Status().Update(ctx, cv)
127+
err := k8sClient.Delete(ctx, co)
128+
if err != nil && !apierrors.IsNotFound(err) {
129+
return err
130+
}
131+
return nil
150132
}).Should(Succeed())
151133

152134
Eventually(func() ([]configv1.ClusterOperatorStatusCondition, error) {
@@ -156,18 +138,18 @@ var _ = Describe("ClusterOperator controller", func() {
156138
Type: configv1.OperatorUpgradeable,
157139
Status: configv1.ConditionFalse,
158140
Reason: ErrorCheckingOperatorCompatibility,
159-
Message: "Encountered errors while checking compatibility with the next minor version of OpenShift: desired release version missing from ClusterVersion",
141+
Message: fmt.Sprintf("Encountered errors while checking compatibility with the next minor version of OpenShift: desired release version missing from %v env variable", releaseEnvVar),
160142
LastTransitionTime: fixedNow(),
161143
}))
162144

163-
cv.Status = configv1.ClusterVersionStatus{
164-
Desired: configv1.Release{
165-
Version: clusterVersion,
166-
},
167-
}
168-
145+
// Reset the ClusterOperator with a valid version set
146+
Expect(resetCurrentReleaseTo(clusterVersion)).To(Succeed())
169147
Eventually(func() error {
170-
return k8sClient.Status().Update(ctx, cv)
148+
err := k8sClient.Delete(ctx, co)
149+
if err != nil && !apierrors.IsNotFound(err) {
150+
return err
151+
}
152+
return nil
171153
}).Should(Succeed())
172154

173155
By("setting upgradeable=false when incompatible operators exist")
@@ -327,3 +309,14 @@ var _ = Describe("ClusterOperator controller", func() {
327309
}))
328310
})
329311
})
312+
313+
// resetCurrentRelease thread safely updates the currentRelease.version and then sets the openshift release
314+
// env var to the desired version. WARNING: This function should only be used for testing purposes as it
315+
// goes around the desired logic of only setting the version of the cluster for this operator once.
316+
func resetCurrentReleaseTo(version string) error {
317+
currentRelease.mu.Lock()
318+
defer currentRelease.mu.Unlock()
319+
320+
currentRelease.version = nil
321+
return os.Setenv(releaseEnvVar, version)
322+
}

pkg/controller/operators/openshift/helpers.go

+49-13
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@ import (
44
"context"
55
"errors"
66
"fmt"
7+
"os"
78
"strings"
9+
"sync"
810

911
semver "github.com/blang/semver/v4"
1012
configv1 "github.com/openshift/api/config/v1"
@@ -119,17 +121,17 @@ func transientErrors(err error) error {
119121
}
120122

121123
func incompatibleOperators(ctx context.Context, cli client.Client) (skews, error) {
122-
desired, err := desiredRelease(ctx, cli)
124+
current, err := getCurrentRelease()
123125
if err != nil {
124126
return nil, err
125127
}
126128

127-
if desired == nil {
129+
if current == nil {
128130
// Note: This shouldn't happen
129131
return nil, fmt.Errorf("failed to determine current OpenShift Y-stream release")
130132
}
131133

132-
next, err := nextY(*desired)
134+
next, err := nextY(*current)
133135
if err != nil {
134136
return nil, err
135137
}
@@ -168,24 +170,58 @@ func incompatibleOperators(ctx context.Context, cli client.Client) (skews, error
168170
return incompatible, nil
169171
}
170172

171-
func desiredRelease(ctx context.Context, cli client.Client) (*semver.Version, error) {
172-
cv := configv1.ClusterVersion{}
173-
if err := cli.Get(ctx, client.ObjectKey{Name: "version"}, &cv); err != nil { // "version" is the name of OpenShift's ClusterVersion singleton
174-
return nil, &transientError{fmt.Errorf("failed to get ClusterVersion: %w", err)}
173+
type openshiftRelease struct {
174+
version *semver.Version
175+
mu sync.Mutex
176+
}
177+
178+
var (
179+
currentRelease = &openshiftRelease{}
180+
)
181+
182+
const (
183+
releaseEnvVar = "RELEASE_VERSION" // OpenShift's env variable for defining the current release
184+
)
185+
186+
// getCurrentRelease thread safely retrieves the current version of OCP at the time of this operator starting.
187+
// This is defined by an environment variable that our release manifests define (and get dynamically updated)
188+
// by OCP. For the purposes of this package, that environment variable is a constant under the name of releaseEnvVar.
189+
//
190+
// Note: currentRelease is designed to be a singleton that only gets updated the first time that this function
191+
// is called. As a result, all calls to this will return the same value even if the releaseEnvVar gets
192+
// changed during runtime somehow.
193+
func getCurrentRelease() (*semver.Version, error) {
194+
currentRelease.mu.Lock()
195+
defer currentRelease.mu.Unlock()
196+
197+
if currentRelease.version != nil {
198+
/*
199+
If the version is already set, we don't want to set it again as the currentRelease
200+
is designed to be a singleton. If a new version is set, we are making an assumption
201+
that this controller will be restarted and thus pull in the new version from the
202+
environment into memory.
203+
204+
Note: sync.Once is not used here as it was difficult to reliably test without hitting
205+
race conditions.
206+
*/
207+
return currentRelease.version, nil
175208
}
176209

177-
v := cv.Status.Desired.Version
178-
if v == "" {
179-
// The release version hasn't been set yet
180-
return nil, fmt.Errorf("desired release version missing from ClusterVersion")
210+
// Get the raw version from the releaseEnvVar environment variable
211+
raw, ok := os.LookupEnv(releaseEnvVar)
212+
if !ok || raw == "" {
213+
// No env var set, try again later
214+
return nil, fmt.Errorf("desired release version missing from %v env variable", releaseEnvVar)
181215
}
182216

183-
desired, err := semver.ParseTolerant(v)
217+
release, err := semver.ParseTolerant(raw)
184218
if err != nil {
185219
return nil, fmt.Errorf("cluster version has invalid desired release version: %w", err)
186220
}
187221

188-
return &desired, nil
222+
currentRelease.version = &release
223+
224+
return currentRelease.version, nil
189225
}
190226

191227
func nextY(v semver.Version) (semver.Version, error) {

pkg/controller/operators/openshift/helpers_test.go

+11-72
Original file line numberDiff line numberDiff line change
@@ -221,22 +221,13 @@ func TestIncompatibleOperators(t *testing.T) {
221221
}
222222
for _, tt := range []struct {
223223
description string
224-
cv configv1.ClusterVersion
224+
version string
225225
in skews
226226
expect expect
227227
}{
228228
{
229229
description: "Compatible",
230-
cv: configv1.ClusterVersion{
231-
ObjectMeta: metav1.ObjectMeta{
232-
Name: "version",
233-
},
234-
Status: configv1.ClusterVersionStatus{
235-
Desired: configv1.Release{
236-
Version: "1.0.0",
237-
},
238-
},
239-
},
230+
version: "1.0.0",
240231
in: skews{
241232
{
242233
name: "almond",
@@ -261,16 +252,7 @@ func TestIncompatibleOperators(t *testing.T) {
261252
},
262253
{
263254
description: "Incompatible",
264-
cv: configv1.ClusterVersion{
265-
ObjectMeta: metav1.ObjectMeta{
266-
Name: "version",
267-
},
268-
Status: configv1.ClusterVersionStatus{
269-
Desired: configv1.Release{
270-
Version: "1.0.0",
271-
},
272-
},
273-
},
255+
version: "1.0.0",
274256
in: skews{
275257
{
276258
name: "almond",
@@ -331,16 +313,7 @@ func TestIncompatibleOperators(t *testing.T) {
331313
},
332314
{
333315
description: "Mixed",
334-
cv: configv1.ClusterVersion{
335-
ObjectMeta: metav1.ObjectMeta{
336-
Name: "version",
337-
},
338-
Status: configv1.ClusterVersionStatus{
339-
Desired: configv1.Release{
340-
Version: "1.0.0",
341-
},
342-
},
343-
},
316+
version: "1.0.0",
344317
in: skews{
345318
{
346319
name: "almond",
@@ -376,16 +349,7 @@ func TestIncompatibleOperators(t *testing.T) {
376349
},
377350
{
378351
description: "Mixed/BadVersion",
379-
cv: configv1.ClusterVersion{
380-
ObjectMeta: metav1.ObjectMeta{
381-
Name: "version",
382-
},
383-
Status: configv1.ClusterVersionStatus{
384-
Desired: configv1.Release{
385-
Version: "1.0.0",
386-
},
387-
},
388-
},
352+
version: "1.0.0",
389353
in: skews{
390354
{
391355
name: "almond",
@@ -424,16 +388,7 @@ func TestIncompatibleOperators(t *testing.T) {
424388
},
425389
{
426390
description: "EmptyVersion",
427-
cv: configv1.ClusterVersion{
428-
ObjectMeta: metav1.ObjectMeta{
429-
Name: "version",
430-
},
431-
Status: configv1.ClusterVersionStatus{
432-
Desired: configv1.Release{
433-
Version: "", // This should result in an transient error
434-
},
435-
},
436-
},
391+
version: "", // This should result in an transient error
437392
in: skews{
438393
{
439394
name: "almond",
@@ -453,16 +408,7 @@ func TestIncompatibleOperators(t *testing.T) {
453408
},
454409
{
455410
description: "ClusterZ",
456-
cv: configv1.ClusterVersion{
457-
ObjectMeta: metav1.ObjectMeta{
458-
Name: "version",
459-
},
460-
Status: configv1.ClusterVersionStatus{
461-
Desired: configv1.Release{
462-
Version: "1.0.1", // Next Y-stream is 1.1.0, NOT 1.1.1
463-
},
464-
},
465-
},
411+
version: "1.0.1", // Next Y-stream is 1.1.0, NOT 1.1.1
466412
in: skews{
467413
{
468414
name: "beech",
@@ -477,16 +423,7 @@ func TestIncompatibleOperators(t *testing.T) {
477423
},
478424
{
479425
description: "ClusterPre",
480-
cv: configv1.ClusterVersion{
481-
ObjectMeta: metav1.ObjectMeta{
482-
Name: "version",
483-
},
484-
Status: configv1.ClusterVersionStatus{
485-
Desired: configv1.Release{
486-
Version: "1.1.0-pre", // Next Y-stream is 1.1.0, NOT 1.2.0
487-
},
488-
},
489-
},
426+
version: "1.1.0-pre", // Next Y-stream is 1.1.0, NOT 1.2.0
490427
in: skews{
491428
{
492429
name: "almond",
@@ -501,7 +438,9 @@ func TestIncompatibleOperators(t *testing.T) {
501438
},
502439
} {
503440
t.Run(tt.description, func(t *testing.T) {
504-
objs := []client.Object{tt.cv.DeepCopy()}
441+
objs := []client.Object{}
442+
443+
resetCurrentReleaseTo(tt.version)
505444

506445
for _, s := range tt.in {
507446
csv := &operatorsv1alpha1.ClusterServiceVersion{}

pkg/controller/operators/openshift/options.go

+1-3
Original file line numberDiff line numberDiff line change
@@ -163,7 +163,6 @@ func WithOLMOperator() ReconcilerOption {
163163

164164
enqueue := handler.EnqueueRequestsFromMapFunc(config.mapClusterOperator)
165165

166-
name := "version"
167166
originalCSV := predicate.NewPredicateFuncs(func(obj client.Object) bool {
168167
csv, ok := obj.(*operatorsv1alpha1.ClusterServiceVersion)
169168
if !ok {
@@ -174,8 +173,7 @@ func WithOLMOperator() ReconcilerOption {
174173
return !csv.IsCopied() // Keep original CSVs only
175174
})
176175
config.TweakBuilder = func(bldr *builder.Builder) *builder.Builder {
177-
return bldr.Watches(&source.Kind{Type: &operatorsv1alpha1.ClusterServiceVersion{}}, enqueue, builder.WithPredicates(originalCSV)).
178-
Watches(&source.Kind{Type: &configv1.ClusterVersion{}}, enqueue, builder.WithPredicates(watchName(&name)))
176+
return bldr.Watches(&source.Kind{Type: &operatorsv1alpha1.ClusterServiceVersion{}}, enqueue, builder.WithPredicates(originalCSV))
179177
}
180178
}
181179
}

0 commit comments

Comments
 (0)