Skip to content

Commit adf0cf5

Browse files
OCPBUGS-42880: Upgradeable=False should not block a 4.(y+1).z to 4.(y+1).z' retarget (#1094)
* OCPBUGS-42880: Upgradeable=False should not block a 4.(y+1).z to 4.(y+1).z' retarget * Update pkg/payload/precondition/clusterversion/upgradeable.go Co-authored-by: Petr Muller <[email protected]> --------- Co-authored-by: Petr Muller <[email protected]>
1 parent e546515 commit adf0cf5

File tree

5 files changed

+166
-127
lines changed

5 files changed

+166
-127
lines changed

pkg/cvo/status_history.go

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -215,3 +215,21 @@ func getEffectiveMicro(version string) string {
215215
}
216216
return splits[2]
217217
}
218+
219+
// getCurrentVersion determines and returns the cluster's current version by iterating through the
220+
// provided update history until it finds the first version with update State of Completed. If a
221+
// Completed version is not found the version of the oldest history entry, which is the originally
222+
// installed version, is returned. If history is empty the empty string is returned.
223+
func getCurrentVersion(history []configv1.UpdateHistory) string {
224+
for _, h := range history {
225+
if h.State == configv1.CompletedUpdate {
226+
klog.V(2).Infof("Cluster current version=%s", h.Version)
227+
return h.Version
228+
}
229+
}
230+
// Empty history should only occur if method is called early in startup before history is populated.
231+
if len(history) != 0 {
232+
return history[len(history)-1].Version
233+
}
234+
return ""
235+
}

pkg/cvo/status_history_test.go

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -775,3 +775,41 @@ func Test_sameZStreamVersion(t *testing.T) {
775775
})
776776
}
777777
}
778+
779+
func TestGetEffectiveMinor(t *testing.T) {
780+
tests := []struct {
781+
name string
782+
input string
783+
expected string
784+
}{
785+
{
786+
name: "empty",
787+
input: "",
788+
expected: "",
789+
},
790+
{
791+
name: "invalid",
792+
input: "something@very-differe",
793+
expected: "",
794+
},
795+
{
796+
name: "multidot",
797+
input: "v4.7.12.3+foo",
798+
expected: "7",
799+
},
800+
{
801+
name: "single",
802+
input: "v4.7",
803+
expected: "7",
804+
},
805+
}
806+
807+
for _, tc := range tests {
808+
t.Run(tc.name, func(t *testing.T) {
809+
actual := getEffectiveMinor(tc.input)
810+
if tc.expected != actual {
811+
t.Error(actual)
812+
}
813+
})
814+
}
815+
}

pkg/cvo/upgradeable.go

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@ import (
2323
"github.com/openshift/cluster-version-operator/lib/resourcedelete"
2424
"github.com/openshift/cluster-version-operator/lib/resourcemerge"
2525
"github.com/openshift/cluster-version-operator/pkg/internal"
26-
"github.com/openshift/cluster-version-operator/pkg/payload/precondition/clusterversion"
2726
)
2827

2928
const (
@@ -288,8 +287,8 @@ func gateApplicableToCurrentVersion(gateName string, currentVersion string) (boo
288287
internal.AdminGatesConfigMap, gateName, adminAckGateFmt)
289288
} else {
290289
parts := strings.Split(ackVersion, "-")
291-
ackMinor := clusterversion.GetEffectiveMinor(parts[1])
292-
cvMinor := clusterversion.GetEffectiveMinor(currentVersion)
290+
ackMinor := getEffectiveMinor(parts[1])
291+
cvMinor := getEffectiveMinor(currentVersion)
293292
if ackMinor == cvMinor {
294293
applicable = true
295294
}
@@ -338,7 +337,7 @@ func (check *clusterAdminAcksCompletedUpgradeable) Check() *configv1.ClusterOper
338337
Message: message,
339338
}
340339
}
341-
currentVersion := clusterversion.GetCurrentVersion(cv.Status.History)
340+
currentVersion := getCurrentVersion(cv.Status.History)
342341

343342
// This can occur in early start up when the configmap is first added and version history
344343
// has not yet been populated.

pkg/payload/precondition/clusterversion/upgradeable.go

Lines changed: 26 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,17 @@ package clusterversion
22

33
import (
44
"context"
5-
"strconv"
6-
"strings"
75
"time"
86

7+
"github.com/blang/semver/v4"
98
configv1 "github.com/openshift/api/config/v1"
109
configv1listers "github.com/openshift/client-go/config/listers/config/v1"
1110
apierrors "k8s.io/apimachinery/pkg/api/errors"
1211
"k8s.io/apimachinery/pkg/api/meta"
1312
"k8s.io/klog/v2"
1413

1514
"github.com/openshift/cluster-version-operator/lib/resourcemerge"
16-
precondition "github.com/openshift/cluster-version-operator/pkg/payload/precondition"
15+
"github.com/openshift/cluster-version-operator/pkg/payload/precondition"
1716
)
1817

1918
// Upgradeable checks if clusterversion is upgradeable currently.
@@ -74,30 +73,40 @@ func (pf *Upgradeable) Run(ctx context.Context, releaseContext precondition.Rele
7473
return nil
7574
}
7675

77-
// we can always allow the upgrade if there isn't a version already installed
78-
if len(cv.Status.History) == 0 {
79-
klog.V(2).Infof("Precondition %s passed: no release history.", pf.Name())
80-
return nil
76+
currentVersion, err := semver.Parse(cv.Status.Desired.Version)
77+
if err != nil {
78+
return &precondition.Error{
79+
Nested: err,
80+
Reason: "InvalidCurrentVersion",
81+
Message: err.Error(),
82+
Name: pf.Name(),
83+
NonBlockingWarning: true, // do not block on issues that require an update to fix
84+
}
8185
}
8286

83-
currentVersion := GetCurrentVersion(cv.Status.History)
84-
currentMinor := GetEffectiveMinor(currentVersion)
85-
desiredMinor := GetEffectiveMinor(releaseContext.DesiredVersion)
86-
klog.V(2).Infof("currentMinor %s releaseContext.DesiredVersion %s desiredMinor %s", currentMinor, releaseContext.DesiredVersion, desiredMinor)
87+
targetVersion, err := semver.Parse(releaseContext.DesiredVersion)
88+
if err != nil {
89+
return &precondition.Error{
90+
Nested: err,
91+
Reason: "InvalidDesiredVersion",
92+
Message: err.Error(),
93+
Name: pf.Name(),
94+
}
95+
}
8796

88-
// 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
89-
// if no cluster overrides have been set
90-
if !minorVersionUpgrade(currentMinor, desiredMinor) {
91-
klog.V(2).Infof("Precondition %q passed: minor from the target %s is not a minor version update from the current %s.%s.", pf.Name(), releaseContext.DesiredVersion, currentVersion, currentMinor)
97+
klog.V(4).Infof("The current version is %s parsed from %s and the target version is %s parsed from %s", currentVersion.String(), cv.Status.Desired.Version, targetVersion.String(), releaseContext.DesiredVersion)
98+
if targetVersion.LTE(currentVersion) || (targetVersion.Major == currentVersion.Major && targetVersion.Minor == currentVersion.Minor) {
99+
// When Upgradeable==False, a patch level update with the same minor level is allowed unless overrides are set
100+
// This Upgradeable precondition is only concerned about moving forward, i.e., do not care about downgrade which is taken care of by the Rollback precondition
92101
if condition := ClusterVersionOverridesCondition(cv); condition != nil {
93-
klog.V(2).Infof("Update from %s to %s blocked by %s: %s", currentVersion, releaseContext.DesiredVersion, condition.Reason, condition.Message)
94-
102+
klog.V(2).Infof("Retarget from %s to %s is blocked by %s: %s", currentVersion.String(), targetVersion.String(), condition.Reason, condition.Message)
95103
return &precondition.Error{
96104
Reason: condition.Reason,
97105
Message: condition.Message,
98106
Name: pf.Name(),
99107
}
100108
} else {
109+
klog.V(2).Infof("Precondition %q passed on update to %s", pf.Name(), targetVersion.String())
101110
return nil
102111
}
103112
}
@@ -112,45 +121,3 @@ func (pf *Upgradeable) Run(ctx context.Context, releaseContext precondition.Rele
112121

113122
// Name returns Name for the precondition.
114123
func (pf *Upgradeable) Name() string { return "ClusterVersionUpgradeable" }
115-
116-
// GetCurrentVersion determines and returns the cluster's current version by iterating through the
117-
// provided update history until it finds the first version with update State of Completed. If a
118-
// Completed version is not found the version of the oldest history entry, which is the originally
119-
// installed version, is returned. If history is empty the empty string is returned.
120-
func GetCurrentVersion(history []configv1.UpdateHistory) string {
121-
for _, h := range history {
122-
if h.State == configv1.CompletedUpdate {
123-
klog.V(2).Infof("Cluster current version=%s", h.Version)
124-
return h.Version
125-
}
126-
}
127-
// Empty history should only occur if method is called early in startup before history is populated.
128-
if len(history) != 0 {
129-
return history[len(history)-1].Version
130-
}
131-
return ""
132-
}
133-
134-
// GetEffectiveMinor attempts to do a simple parse of the version provided. If it does not parse, the value is considered
135-
// empty string, which works for the comparison done here for equivalence.
136-
func GetEffectiveMinor(version string) string {
137-
splits := strings.Split(version, ".")
138-
if len(splits) < 2 {
139-
return ""
140-
}
141-
return splits[1]
142-
}
143-
144-
// minorVersionUpgrade returns true if the the desired update minor version number is greater
145-
// than the current version minor version number. Errors resulting from either version
146-
// number being unset or NaN are ignored simply resulting in false returned.
147-
func minorVersionUpgrade(currentMinor string, desiredMinor string) bool {
148-
if currentMinorNum, err := strconv.Atoi(currentMinor); err == nil {
149-
if desiredMinorNum, err := strconv.Atoi(desiredMinor); err == nil {
150-
if desiredMinorNum > currentMinorNum {
151-
return true
152-
}
153-
}
154-
}
155-
return false
156-
}

0 commit comments

Comments
 (0)