Skip to content

Commit b658b42

Browse files
Merge pull request #364 from jottofar/bug-1822844
Bug 1822844: Block z level upgrades when ClusterVersionOverridesSet is set
2 parents 7dee92e + b72e843 commit b658b42

File tree

7 files changed

+61
-28
lines changed

7 files changed

+61
-28
lines changed

pkg/cvo/cvo.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -326,7 +326,7 @@ func (optr *Operator) Run(ctx context.Context, workers int) {
326326

327327
// start the config sync loop, and have it notify the queue when new status is detected
328328
go runThrottledStatusNotifier(stopCh, optr.statusInterval, 2, optr.configSync.StatusCh(), func() { optr.queue.Add(optr.queueKey()) })
329-
go optr.configSync.Start(ctx, 16)
329+
go optr.configSync.Start(ctx, 16, optr.name, optr.cvLister)
330330
go wait.Until(func() { optr.worker(optr.availableUpdatesQueue, optr.availableUpdatesSync) }, time.Second, stopCh)
331331
go wait.Until(func() { optr.worker(optr.upgradeableQueue, optr.upgradeableSync) }, time.Second, stopCh)
332332
go wait.Until(func() {

pkg/cvo/cvo_scenarios_test.go

Lines changed: 11 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ func TestCVO_StartupAndSync(t *testing.T) {
106106

107107
defer shutdownFn()
108108
worker := o.configSync.(*SyncWorker)
109-
go worker.Start(ctx, 1)
109+
go worker.Start(ctx, 1, o.name, o.cvLister)
110110

111111
// Step 1: Verify the CVO creates the initial Cluster Version object
112112
//
@@ -377,7 +377,7 @@ func TestCVO_StartupAndSyncUnverifiedPayload(t *testing.T) {
377377
VerificationError: payloadErr,
378378
}
379379

380-
go worker.Start(ctx, 1)
380+
go worker.Start(ctx, 1, o.name, o.cvLister)
381381

382382
// Step 1: Verify the CVO creates the initial Cluster Version object
383383
//
@@ -638,7 +638,7 @@ func TestCVO_StartupAndSyncPreconditionFailing(t *testing.T) {
638638
Directory: "testdata/payloadtest",
639639
Local: true,
640640
}
641-
go worker.Start(ctx, 1)
641+
go worker.Start(ctx, 1, o.name, o.cvLister)
642642

643643
// Step 1: Verify the CVO creates the initial Cluster Version object
644644
//
@@ -938,7 +938,7 @@ func TestCVO_UpgradeUnverifiedPayload(t *testing.T) {
938938
retriever := worker.retriever.(*fakeDirectoryRetriever)
939939
retriever.Set(PayloadInfo{}, payloadErr)
940940

941-
go worker.Start(ctx, 1)
941+
go worker.Start(ctx, 1, o.name, o.cvLister)
942942

943943
// Step 1: The operator should report that it is blocked on unverified content
944944
//
@@ -1163,7 +1163,7 @@ func TestCVO_UpgradeUnverifiedPayloadRetriveOnce(t *testing.T) {
11631163
retriever := worker.retriever.(*fakeDirectoryRetriever)
11641164
retriever.Set(PayloadInfo{}, payloadErr)
11651165

1166-
go worker.Start(ctx, 1)
1166+
go worker.Start(ctx, 1, o.name, o.cvLister)
11671167

11681168
// Step 1: The operator should report that it is blocked on unverified content
11691169
//
@@ -1415,7 +1415,7 @@ func TestCVO_UpgradePreconditionFailing(t *testing.T) {
14151415
worker := o.configSync.(*SyncWorker)
14161416
worker.preconditions = []precondition.Precondition{&testPrecondition{SuccessAfter: 3}}
14171417

1418-
go worker.Start(ctx, 1)
1418+
go worker.Start(ctx, 1, o.name, o.cvLister)
14191419

14201420
// Step 1: The operator should report that it is blocked on precondition checks failing
14211421
//
@@ -1650,7 +1650,7 @@ func TestCVO_UpgradeVerifiedPayload(t *testing.T) {
16501650
retriever := worker.retriever.(*fakeDirectoryRetriever)
16511651
retriever.Set(PayloadInfo{}, payloadErr)
16521652

1653-
go worker.Start(ctx, 1)
1653+
go worker.Start(ctx, 1, o.name, o.cvLister)
16541654

16551655
// Step 1: The operator should report that it is blocked on unverified content
16561656
//
@@ -1886,7 +1886,7 @@ func TestCVO_RestartAndReconcile(t *testing.T) {
18861886
// Step 2: Start the sync worker and verify the sequence of events, and then verify
18871887
// the status does not change
18881888
//
1889-
go worker.Start(ctx, 1)
1889+
go worker.Start(ctx, 1, o.name, o.cvLister)
18901890
//
18911891
verifyAllStatus(t, worker.StatusCh(),
18921892
SyncWorkerStatus{
@@ -2045,10 +2045,8 @@ func TestCVO_ErrorDuringReconcile(t *testing.T) {
20452045
if worker.work.State != payload.ReconcilingPayload {
20462046
t.Fatalf("The worker should be reconciling: %v", worker.work)
20472047
}
2048-
2049-
// Step 2: Start the sync worker and verify the sequence of events
20502048
//
2051-
go worker.Start(ctx, 1)
2049+
go worker.Start(ctx, 1, o.name, o.cvLister)
20522050
//
20532051
verifyAllStatus(t, worker.StatusCh(),
20542052
SyncWorkerStatus{
@@ -2252,7 +2250,7 @@ func TestCVO_ParallelError(t *testing.T) {
22522250
//
22532251
cancellable, cancel := context.WithCancel(ctx)
22542252
defer cancel()
2255-
go worker.Start(cancellable, 1)
2253+
go worker.Start(cancellable, 1, o.name, o.cvLister)
22562254
//
22572255
verifyAllStatus(t, worker.StatusCh(),
22582256
SyncWorkerStatus{
@@ -2470,7 +2468,7 @@ func verifyAllStatus(t *testing.T, ch <-chan SyncWorkerStatus, items ...SyncWork
24702468
t.Helper()
24712469
if len(items) == 0 {
24722470
if len(ch) > 0 {
2473-
t.Fatalf("expected status to empty, got %#v", <-ch)
2471+
t.Fatalf("expected status to be empty, got %#v", <-ch)
24742472
}
24752473
return
24762474
}

pkg/cvo/sync_test.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import (
2222
clientgotesting "k8s.io/client-go/testing"
2323

2424
configv1 "github.com/openshift/api/config/v1"
25+
configlistersv1 "github.com/openshift/client-go/config/listers/config/v1"
2526

2627
"github.com/openshift/cluster-version-operator/lib"
2728
"github.com/openshift/cluster-version-operator/lib/resourcebuilder"
@@ -387,7 +388,8 @@ func (r *fakeSyncRecorder) StatusCh() <-chan SyncWorkerStatus {
387388
return ch
388389
}
389390

390-
func (r *fakeSyncRecorder) Start(ctx context.Context, maxWorkers int) {}
391+
func (r *fakeSyncRecorder) Start(ctx context.Context, maxWorkers int, cvoOptrName string, lister configlistersv1.ClusterVersionLister) {
392+
}
391393

392394
func (r *fakeSyncRecorder) Update(generation int64, desired configv1.Update, overrides []configv1.ComponentOverride, state payload.State) *SyncWorkerStatus {
393395
r.Updates = append(r.Updates, desired)
@@ -453,7 +455,7 @@ func (pf *testPrecondition) Name() string {
453455
return fmt.Sprintf("TestPrecondition SuccessAfter: %d", pf.SuccessAfter)
454456
}
455457

456-
func (pf *testPrecondition) Run(_ context.Context, _ precondition.ReleaseContext) error {
458+
func (pf *testPrecondition) Run(_ context.Context, _ precondition.ReleaseContext, cv *configv1.ClusterVersion) error {
457459
if pf.SuccessAfter == 0 {
458460
return nil
459461
}

pkg/cvo/sync_worker.go

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
"sync"
1111
"time"
1212

13+
configlistersv1 "github.com/openshift/client-go/config/listers/config/v1"
1314
"github.com/prometheus/client_golang/prometheus"
1415
"golang.org/x/time/rate"
1516
"k8s.io/klog"
@@ -29,7 +30,7 @@ import (
2930

3031
// ConfigSyncWorker abstracts how the image is synchronized to the server. Introduced for testing.
3132
type ConfigSyncWorker interface {
32-
Start(ctx context.Context, maxWorkers int)
33+
Start(ctx context.Context, maxWorkers int, cvoOptrName string, lister configlistersv1.ClusterVersionLister)
3334
Update(generation int64, desired configv1.Update, overrides []configv1.ComponentOverride, state payload.State) *SyncWorkerStatus
3435
StatusCh() <-chan SyncWorkerStatus
3536
}
@@ -241,7 +242,7 @@ func (w *SyncWorker) Update(generation int64, desired configv1.Update, overrides
241242
// Start periodically invokes run, detecting whether content has changed.
242243
// It is edge-triggered when Update() is invoked and level-driven after the
243244
// syncOnce() has succeeded for a given input (we are said to be "reconciling").
244-
func (w *SyncWorker) Start(ctx context.Context, maxWorkers int) {
245+
func (w *SyncWorker) Start(ctx context.Context, maxWorkers int, cvoOptrName string, lister configlistersv1.ClusterVersionLister) {
245246
klog.V(5).Infof("Starting sync worker")
246247

247248
work := &SyncWork{}
@@ -306,11 +307,15 @@ func (w *SyncWorker) Start(ctx context.Context, maxWorkers int) {
306307
w.lock.Unlock()
307308
defer cancelFn()
308309

310+
config, err := lister.Get(cvoOptrName)
311+
if err != nil {
312+
return err
313+
}
309314
// reporter hides status updates that occur earlier than the previous failure,
310315
// so that we don't fail, then immediately start reporting an earlier status
311316
reporter := &statusWrapper{w: w, previousStatus: w.Status()}
312317
klog.V(5).Infof("Previous sync status: %#v", reporter.previousStatus)
313-
return w.syncOnce(ctx, work, maxWorkers, reporter)
318+
return w.syncOnce(ctx, work, maxWorkers, reporter, config)
314319
}()
315320
if err != nil {
316321
// backoff wait
@@ -466,7 +471,7 @@ func (w *SyncWorker) Status() *SyncWorkerStatus {
466471
// sync retrieves the image and applies it to the server, returning an error if
467472
// the update could not be completely applied. The status is updated as we progress.
468473
// Cancelling the context will abort the execution of the sync.
469-
func (w *SyncWorker) syncOnce(ctx context.Context, work *SyncWork, maxWorkers int, reporter StatusReporter) error {
474+
func (w *SyncWorker) syncOnce(ctx context.Context, work *SyncWork, maxWorkers int, reporter StatusReporter, clusterVersion *configv1.ClusterVersion) error {
470475
klog.V(4).Infof("Running sync %s (force=%t) on generation %d in state %s at attempt %d", versionString(work.Desired), work.Desired.Force, work.Generation, work.State, work.Attempt)
471476
update := work.Desired
472477

@@ -524,7 +529,7 @@ func (w *SyncWorker) syncOnce(ctx context.Context, work *SyncWork, maxWorkers in
524529
Actual: update,
525530
Verified: info.Verified,
526531
})
527-
if err := precondition.Summarize(w.preconditions.RunAll(ctx, precondition.ReleaseContext{DesiredVersion: payloadUpdate.ReleaseVersion})); err != nil {
532+
if err := precondition.Summarize(w.preconditions.RunAll(ctx, precondition.ReleaseContext{DesiredVersion: payloadUpdate.ReleaseVersion}, clusterVersion)); err != nil {
528533
if update.Force {
529534
klog.V(4).Infof("Forcing past precondition failures: %s", err)
530535
} else {

pkg/payload/precondition/clusterversion/upgradable_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,7 @@ func TestUpgradeableRun(t *testing.T) {
128128
cvLister := fakeClusterVersionLister(clusterVersion)
129129
instance := NewUpgradeable(cvLister)
130130

131-
err := instance.Run(context.TODO(), precondition.ReleaseContext{DesiredVersion: tc.desiredVersion})
131+
err := instance.Run(context.TODO(), precondition.ReleaseContext{DesiredVersion: tc.desiredVersion}, clusterVersion)
132132
switch {
133133
case err != nil && len(tc.expected) == 0:
134134
t.Error(err)

pkg/payload/precondition/clusterversion/upgradeable.go

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,10 +28,26 @@ func NewUpgradeable(lister configv1listers.ClusterVersionLister) *Upgradeable {
2828
}
2929
}
3030

31+
// ClusterVersionOverridesCondition returns an UpgradeableClusterVersionOverrides condition when overrides are set, and nil when no overrides are set.
32+
func ClusterVersionOverridesCondition(cv *configv1.ClusterVersion) *configv1.ClusterOperatorStatusCondition {
33+
for _, override := range cv.Spec.Overrides {
34+
if override.Unmanaged {
35+
condition := configv1.ClusterOperatorStatusCondition{
36+
Type: configv1.ClusterStatusConditionType("UpgradeableClusterVersionOverrides"),
37+
Status: configv1.ConditionFalse,
38+
Reason: "ClusterVersionOverridesSet",
39+
Message: "Disabling ownership via cluster version overrides prevents upgrades. Please remove overrides before continuing.",
40+
}
41+
return &condition
42+
}
43+
}
44+
return nil
45+
}
46+
3147
// Run runs the Upgradeable precondition.
3248
// If the feature gate `key` is not found, or the api for clusterversion doesn't exist, this check is inert and always returns nil error.
3349
// Otherwise, if Upgradeable condition is set to false in the object, it returns an PreconditionError when possible.
34-
func (pf *Upgradeable) Run(ctx context.Context, releaseContext precondition.ReleaseContext) error {
50+
func (pf *Upgradeable) Run(ctx context.Context, releaseContext precondition.ReleaseContext, clusterVersion *configv1.ClusterVersion) error {
3551
cv, err := pf.lister.Get(pf.key)
3652
if apierrors.IsNotFound(err) || meta.IsNoMatchError(err) {
3753
return nil
@@ -68,9 +84,20 @@ func (pf *Upgradeable) Run(ctx context.Context, releaseContext precondition.Rele
6884
klog.V(5).Infof("currentMinor %s releaseContext.DesiredVersion %s desiredMinor %s", currentMinor, releaseContext.DesiredVersion, desiredMinor)
6985

7086
// if there is no difference in the minor version (4.y.z where 4.y is the same for current and desired), then we can still upgrade
87+
// if no cluster overrides have been set
7188
if currentMinor == desiredMinor {
72-
klog.V(4).Infof("Precondition %q passed: minor from the current %s matches minor from the target %s (both %s).", pf.Name(), cv.Status.History[0].Version, releaseContext.DesiredVersion, currentMinor)
73-
return nil
89+
klog.V(4).Infof("Precondition %q passed: minor from the current %s matches minor from the target %s (both %s).", pf.Name(), currentVersion, releaseContext.DesiredVersion, currentMinor)
90+
if condition := ClusterVersionOverridesCondition(clusterVersion); condition != nil {
91+
klog.V(4).Infof("Update from %s to %s blocked by %s: %s", currentVersion, releaseContext.DesiredVersion, condition.Reason, condition.Message)
92+
93+
return &precondition.Error{
94+
Reason: condition.Reason,
95+
Message: condition.Message,
96+
Name: pf.Name(),
97+
}
98+
} else {
99+
return nil
100+
}
74101
}
75102

76103
return &precondition.Error{

pkg/payload/precondition/precondition.go

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"fmt"
66
"strings"
77

8+
configv1 "github.com/openshift/api/config/v1"
89
"k8s.io/klog"
910

1011
"github.com/openshift/cluster-version-operator/pkg/payload"
@@ -41,7 +42,7 @@ type ReleaseContext struct {
4142
// Precondition defines the precondition check for a payload.
4243
type Precondition interface {
4344
// Run executes the precondition checks ands returns an error when the precondition fails.
44-
Run(ctx context.Context, releaseContext ReleaseContext) error
45+
Run(ctx context.Context, releaseContext ReleaseContext, cv *configv1.ClusterVersion) error
4546

4647
// Name returns a human friendly name for the precondition.
4748
Name() string
@@ -52,10 +53,10 @@ type List []Precondition
5253

5354
// RunAll runs all the reflight checks in order, returning a list of errors if any.
5455
// All checks are run, regardless if any one precondition fails.
55-
func (pfList List) RunAll(ctx context.Context, releaseContext ReleaseContext) []error {
56+
func (pfList List) RunAll(ctx context.Context, releaseContext ReleaseContext, cv *configv1.ClusterVersion) []error {
5657
var errs []error
5758
for _, pf := range pfList {
58-
if err := pf.Run(ctx, releaseContext); err != nil {
59+
if err := pf.Run(ctx, releaseContext, cv); err != nil {
5960
klog.Errorf("Precondition %q failed: %v", pf.Name(), err)
6061
errs = append(errs, err)
6162
}

0 commit comments

Comments
 (0)