Skip to content

Commit bcd58d8

Browse files
committed
loosen upgradeable condition to allow z-level upgrades
If current and desired have the same 4.y, then the upgrade is allowed even if upgradeable==false. If the 4.y is different, then upgrades are not allowed. This permits CVE updates and fixes the current service-catalog upgrade problem.
1 parent a45fa12 commit bcd58d8

File tree

5 files changed

+207
-14
lines changed

5 files changed

+207
-14
lines changed

pkg/cvo/sync_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -453,7 +453,7 @@ func (pf *testPrecondition) Name() string {
453453
return fmt.Sprintf("TestPrecondition SuccessAfter: %d", pf.SuccessAfter)
454454
}
455455

456-
func (pf *testPrecondition) Run(_ context.Context) error {
456+
func (pf *testPrecondition) Run(_ context.Context, _ precondition.ReleaseContext) error {
457457
if pf.SuccessAfter == 0 {
458458
return nil
459459
}

pkg/cvo/sync_worker.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -521,7 +521,7 @@ func (w *SyncWorker) syncOnce(ctx context.Context, work *SyncWork, maxWorkers in
521521
Actual: update,
522522
Verified: info.Verified,
523523
})
524-
if err := precondition.Summarize(w.preconditions.RunAll(ctx)); err != nil && !update.Force {
524+
if err := precondition.Summarize(w.preconditions.RunAll(ctx, precondition.ReleaseContext{DesiredVersion: payloadUpdate.ReleaseVersion})); err != nil && !update.Force {
525525
reporter.Report(SyncWorkerStatus{
526526
Generation: work.Generation,
527527
Failure: err,
Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
package clusterversion
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"testing"
7+
8+
"github.com/openshift/cluster-version-operator/pkg/payload/precondition"
9+
10+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
11+
12+
configv1 "github.com/openshift/api/config/v1"
13+
configv1listers "github.com/openshift/client-go/config/listers/config/v1"
14+
15+
"k8s.io/client-go/tools/cache"
16+
)
17+
18+
func TestGetEffectiveMinor(t *testing.T) {
19+
tests := []struct {
20+
name string
21+
input string
22+
expected string
23+
}{
24+
{
25+
name: "empty",
26+
input: "",
27+
expected: "",
28+
},
29+
{
30+
name: "invalid",
31+
input: "something@very-differe",
32+
expected: "",
33+
},
34+
{
35+
name: "multidot",
36+
input: "v4.7.12.3+foo",
37+
expected: "7",
38+
},
39+
{
40+
name: "single",
41+
input: "v4.7",
42+
expected: "7",
43+
},
44+
}
45+
46+
for _, tc := range tests {
47+
t.Run(tc.name, func(t *testing.T) {
48+
actual := getEffectiveMinor(tc.input)
49+
if tc.expected != actual {
50+
t.Error(actual)
51+
}
52+
})
53+
}
54+
}
55+
56+
func TestUpgradeableRun(t *testing.T) {
57+
ptr := func(status configv1.ConditionStatus) *configv1.ConditionStatus {
58+
return &status
59+
}
60+
61+
tests := []struct {
62+
name string
63+
upgradeable *configv1.ConditionStatus
64+
currVersion string
65+
desiredVersion string
66+
expected string
67+
}{
68+
{
69+
name: "first",
70+
desiredVersion: "4.2",
71+
expected: "",
72+
},
73+
{
74+
name: "move-y, no condition",
75+
currVersion: "4.1",
76+
desiredVersion: "4.2",
77+
expected: "",
78+
},
79+
{
80+
name: "move-y, with true condition",
81+
upgradeable: ptr(configv1.ConditionTrue),
82+
currVersion: "4.1",
83+
desiredVersion: "4.2",
84+
expected: "",
85+
},
86+
{
87+
name: "move-y, with unknown condition",
88+
upgradeable: ptr(configv1.ConditionUnknown),
89+
currVersion: "4.1",
90+
desiredVersion: "4.2",
91+
expected: "",
92+
},
93+
{
94+
name: "move-y, with false condition",
95+
upgradeable: ptr(configv1.ConditionFalse),
96+
currVersion: "4.1",
97+
desiredVersion: "4.2",
98+
expected: "set to False",
99+
},
100+
{
101+
name: "move-z, with false condition",
102+
upgradeable: ptr(configv1.ConditionFalse),
103+
currVersion: "4.1.3",
104+
desiredVersion: "4.1.4",
105+
expected: "",
106+
},
107+
}
108+
109+
for _, tc := range tests {
110+
t.Run(tc.name, func(t *testing.T) {
111+
clusterVersion := &configv1.ClusterVersion{
112+
ObjectMeta: metav1.ObjectMeta{Name: "version"},
113+
Spec: configv1.ClusterVersionSpec{},
114+
Status: configv1.ClusterVersionStatus{
115+
History: []configv1.UpdateHistory{},
116+
},
117+
}
118+
if len(tc.currVersion) > 0 {
119+
clusterVersion.Status.History = append(clusterVersion.Status.History, configv1.UpdateHistory{Version: tc.currVersion})
120+
}
121+
if tc.upgradeable != nil {
122+
clusterVersion.Status.Conditions = append(clusterVersion.Status.Conditions, configv1.ClusterOperatorStatusCondition{
123+
Type: configv1.OperatorUpgradeable,
124+
Status: *tc.upgradeable,
125+
Message: fmt.Sprintf("set to %v", *tc.upgradeable),
126+
})
127+
}
128+
cvLister := fakeClusterVersionLister(clusterVersion)
129+
instance := NewUpgradeable(cvLister)
130+
131+
err := instance.Run(context.TODO(), precondition.ReleaseContext{DesiredVersion: tc.desiredVersion})
132+
switch {
133+
case err != nil && len(tc.expected) == 0:
134+
t.Error(err)
135+
case err != nil && err.Error() == tc.expected:
136+
case err != nil && err.Error() != tc.expected:
137+
t.Error(err)
138+
case err == nil && len(tc.expected) == 0:
139+
case err == nil && len(tc.expected) != 0:
140+
t.Error(err)
141+
}
142+
143+
})
144+
}
145+
}
146+
147+
func fakeClusterVersionLister(clusterVersion *configv1.ClusterVersion) configv1listers.ClusterVersionLister {
148+
indexer := cache.NewIndexer(cache.MetaNamespaceKeyFunc, cache.Indexers{})
149+
if clusterVersion == nil {
150+
return configv1listers.NewClusterVersionLister(indexer)
151+
}
152+
153+
indexer.Add(clusterVersion)
154+
return configv1listers.NewClusterVersionLister(indexer)
155+
}

pkg/payload/precondition/clusterversion/upgradeable.go

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

33
import (
44
"context"
5+
"strings"
56

67
configv1 "github.com/openshift/api/config/v1"
78
configv1listers "github.com/openshift/client-go/config/listers/config/v1"
@@ -29,7 +30,7 @@ func NewUpgradeable(lister configv1listers.ClusterVersionLister) *Upgradeable {
2930
// Run runs the Upgradeable precondition.
3031
// 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.
3132
// Otherwise, if Upgradeable condition is set to false in the object, it returns an PreconditionError when possible.
32-
func (pf *Upgradeable) Run(ctx context.Context) error {
33+
func (pf *Upgradeable) Run(ctx context.Context, releaseContext precondition.ReleaseContext) error {
3334
cv, err := pf.lister.Get(pf.key)
3435
if apierrors.IsNotFound(err) || meta.IsNoMatchError(err) {
3536
return nil
@@ -42,16 +43,43 @@ func (pf *Upgradeable) Run(ctx context.Context) error {
4243
Name: pf.Name(),
4344
}
4445
}
45-
if up := resourcemerge.FindOperatorStatusCondition(cv.Status.Conditions, configv1.OperatorUpgradeable); up != nil && up.Status == configv1.ConditionFalse {
46-
return &precondition.Error{
47-
Nested: err,
48-
Reason: up.Reason,
49-
Message: up.Message,
50-
Name: pf.Name(),
51-
}
46+
47+
// if we are upgradeable==true we can always upgrade
48+
up := resourcemerge.FindOperatorStatusCondition(cv.Status.Conditions, configv1.OperatorUpgradeable)
49+
if up == nil || up.Status != configv1.ConditionFalse {
50+
return nil
51+
}
52+
53+
// we can always allow the upgrade if there isn't a version already installed
54+
if len(cv.Status.History) == 0 {
55+
return nil
56+
}
57+
58+
currentMinor := getEffectiveMinor(cv.Status.History[0].Version)
59+
desiredMinor := getEffectiveMinor(releaseContext.DesiredVersion)
60+
61+
// 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
62+
if currentMinor == desiredMinor {
63+
return nil
64+
}
65+
66+
return &precondition.Error{
67+
Nested: err,
68+
Reason: up.Reason,
69+
Message: up.Message,
70+
Name: pf.Name(),
5271
}
53-
return nil
5472
}
5573

5674
// Name returns Name for the precondition.
5775
func (pf *Upgradeable) Name() string { return "ClusterVersionUpgradeable" }
76+
77+
// getEffectiveMinor attempts to do a simple parse of the version provided. If it does not parse, the value is considered
78+
// empty string, which works for the comparison done here for equivalence.
79+
func getEffectiveMinor(version string) string {
80+
splits := strings.Split(version, ".")
81+
if len(splits) < 2 {
82+
return ""
83+
}
84+
return splits[1]
85+
}

pkg/payload/precondition/precondition.go

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,10 +28,20 @@ func (e *Error) Cause() error {
2828
return e.Nested
2929
}
3030

31+
// ReleaseContext holds information about the update being considered
32+
type ReleaseContext struct {
33+
// DesiredVersion is the version of the payload being considered.
34+
// While this might be a semantic version, consumers should not
35+
// require SemVer validity so they can handle custom releases
36+
// where the author decided to use a different naming scheme, or
37+
// to leave the version completely unset.
38+
DesiredVersion string
39+
}
40+
3141
// Precondition defines the precondition check for a payload.
3242
type Precondition interface {
3343
// Run executes the precondition checks ands returns an error when the precondition fails.
34-
Run(context.Context) error
44+
Run(ctx context.Context, releaseContext ReleaseContext) error
3545

3646
// Name returns a human friendly name for the precondition.
3747
Name() string
@@ -42,10 +52,10 @@ type List []Precondition
4252

4353
// RunAll runs all the reflight checks in order, returning a list of errors if any.
4454
// All checks are run, regardless if any one precondition fails.
45-
func (pfList List) RunAll(ctx context.Context) []error {
55+
func (pfList List) RunAll(ctx context.Context, releaseContext ReleaseContext) []error {
4656
var errs []error
4757
for _, pf := range pfList {
48-
if err := pf.Run(ctx); err != nil {
58+
if err := pf.Run(ctx, releaseContext); err != nil {
4959
klog.Errorf("Precondition %q failed: %v", pf.Name(), err)
5060
errs = append(errs, err)
5161
}

0 commit comments

Comments
 (0)