Skip to content

Commit b011e1b

Browse files
Merge pull request openshift#1093 from hongkailiu/OTA-861-better-guard
OTA-861: Generate an accepted risk for Y-then-Z upgrade
2 parents cfd8f88 + cb7a835 commit b011e1b

File tree

4 files changed

+86
-26
lines changed

4 files changed

+86
-26
lines changed

pkg/cvo/status_history.go

Lines changed: 0 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -215,21 +215,3 @@ 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/upgradeable.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ 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"
2627
)
2728

2829
const (
@@ -365,7 +366,7 @@ func (check *clusterAdminAcksCompletedUpgradeable) Check() *configv1.ClusterOper
365366
Message: message,
366367
}
367368
}
368-
currentVersion := getCurrentVersion(cv.Status.History)
369+
currentVersion := clusterversion.GetCurrentVersion(cv.Status.History)
369370

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

pkg/payload/precondition/clusterversion/upgradeable.go

Lines changed: 50 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package clusterversion
22

33
import (
44
"context"
5+
"fmt"
56
"time"
67

78
"github.com/blang/semver/v4"
@@ -93,7 +94,8 @@ func (pf *Upgradeable) Run(ctx context.Context, releaseContext precondition.Rele
9394
}
9495

9596
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)
96-
if targetVersion.LTE(currentVersion) || (targetVersion.Major == currentVersion.Major && targetVersion.Minor == currentVersion.Minor) {
97+
patchOnly := targetVersion.Major == currentVersion.Major && targetVersion.Minor == currentVersion.Minor
98+
if targetVersion.LTE(currentVersion) || patchOnly {
9799
// When Upgradeable==False, a patch level update with the same minor level is allowed unless overrides are set
98100
// 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
99101
if condition := ClusterVersionOverridesCondition(cv); condition != nil {
@@ -104,6 +106,15 @@ func (pf *Upgradeable) Run(ctx context.Context, releaseContext precondition.Rele
104106
Name: pf.Name(),
105107
}
106108
} else {
109+
if completedVersion := minorUpdateFrom(cv.Status, currentVersion); completedVersion != "" && patchOnly {
110+
// This is to generate an accepted risk for the accepting case 4.y.z -> 4.y+1.z' -> 4.y+1.z''
111+
return &precondition.Error{
112+
Reason: "MinorVersionClusterUpdateInProgress",
113+
Message: fmt.Sprintf("Retarget to %s while a minor level update from %s to %s is in progress", targetVersion, completedVersion, currentVersion),
114+
Name: pf.Name(),
115+
NonBlockingWarning: true,
116+
}
117+
}
107118
klog.V(2).Infof("Precondition %q passed on update to %s", pf.Name(), targetVersion.String())
108119
return nil
109120
}
@@ -117,5 +128,43 @@ func (pf *Upgradeable) Run(ctx context.Context, releaseContext precondition.Rele
117128
}
118129
}
119130

131+
// minorUpdateFrom returns the version that was installed completed if a minor level upgrade is in progress
132+
// and the empty string otherwise
133+
func minorUpdateFrom(status configv1.ClusterVersionStatus, currentVersion semver.Version) string {
134+
completedVersion := GetCurrentVersion(status.History)
135+
if completedVersion == "" {
136+
return ""
137+
}
138+
v, err := semver.Parse(completedVersion)
139+
if err != nil {
140+
return ""
141+
}
142+
if cond := resourcemerge.FindOperatorStatusCondition(status.Conditions, configv1.OperatorProgressing); cond != nil &&
143+
cond.Status == configv1.ConditionTrue &&
144+
v.Major == currentVersion.Major &&
145+
v.Minor < currentVersion.Minor {
146+
return completedVersion
147+
}
148+
return ""
149+
}
150+
120151
// Name returns the name of the precondition.
121152
func (pf *Upgradeable) Name() string { return "ClusterVersionUpgradeable" }
153+
154+
// GetCurrentVersion determines and returns the cluster's current version by iterating through the
155+
// provided update history until it finds the first version with update State of Completed. If a
156+
// Completed version is not found the version of the oldest history entry, which is the originally
157+
// installed version, is returned. If history is empty the empty string is returned.
158+
func GetCurrentVersion(history []configv1.UpdateHistory) string {
159+
for _, h := range history {
160+
if h.State == configv1.CompletedUpdate {
161+
klog.V(2).Infof("Cluster current version=%s", h.Version)
162+
return h.Version
163+
}
164+
}
165+
// Empty history should only occur if method is called early in startup before history is populated.
166+
if len(history) != 0 {
167+
return history[len(history)-1].Version
168+
}
169+
return ""
170+
}

pkg/payload/precondition/clusterversion/upgradeable_test.go

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

33
import (
44
"context"
5+
"errors"
56
"fmt"
67
"testing"
78

@@ -23,11 +24,12 @@ func TestUpgradeableRun(t *testing.T) {
2324
}
2425

2526
tests := []struct {
26-
name string
27-
upgradeable *configv1.ConditionStatus
28-
currVersion string
29-
desiredVersion string
30-
expected *precondition.Error
27+
name string
28+
upgradeable *configv1.ConditionStatus
29+
completedVersion string
30+
currVersion string
31+
desiredVersion string
32+
expected *precondition.Error
3133
}{
3234
{
3335
name: "first",
@@ -125,6 +127,26 @@ func TestUpgradeableRun(t *testing.T) {
125127
currVersion: "4.17.0-0.test-2024-10-07-173400-ci-ln-pwn9ngk-latest",
126128
desiredVersion: "4.17.0-rc.6",
127129
},
130+
{
131+
name: "move-y-then-z, with false condition",
132+
upgradeable: ptr(configv1.ConditionFalse),
133+
completedVersion: "4.1.1",
134+
currVersion: "4.2.1",
135+
desiredVersion: "4.2.3",
136+
expected: &precondition.Error{
137+
NonBlockingWarning: true,
138+
Name: "ClusterVersionUpgradeable",
139+
Message: "Retarget to 4.2.3 while a minor level update from 4.1.1 to 4.2.1 is in progress",
140+
Reason: "MinorVersionClusterUpdateInProgress",
141+
},
142+
},
143+
{
144+
name: "move-z-then-z, with false condition",
145+
upgradeable: ptr(configv1.ConditionFalse),
146+
completedVersion: "4.2.1",
147+
currVersion: "4.2.2",
148+
desiredVersion: "4.2.3",
149+
},
128150
}
129151

130152
for _, tc := range tests {
@@ -134,8 +156,13 @@ func TestUpgradeableRun(t *testing.T) {
134156
Spec: configv1.ClusterVersionSpec{},
135157
Status: configv1.ClusterVersionStatus{
136158
Desired: configv1.Release{Version: tc.currVersion},
159+
History: []configv1.UpdateHistory{{State: configv1.CompletedUpdate, Version: tc.completedVersion}},
137160
},
138161
}
162+
if tc.completedVersion != "" && tc.completedVersion != tc.currVersion {
163+
clusterVersion.Status.Conditions = append(clusterVersion.Status.Conditions,
164+
configv1.ClusterOperatorStatusCondition{Type: configv1.OperatorProgressing, Status: configv1.ConditionTrue})
165+
}
139166
if tc.upgradeable != nil {
140167
clusterVersion.Status.Conditions = append(clusterVersion.Status.Conditions, configv1.ClusterOperatorStatusCondition{
141168
Type: configv1.OperatorUpgradeable,
@@ -154,7 +181,8 @@ func TestUpgradeableRun(t *testing.T) {
154181
if (tc.expected == nil && err != nil) || (tc.expected != nil && err == nil) {
155182
t.Errorf("%s: expected %v but got %v", tc.name, tc.expected, err)
156183
} else if tc.expected != nil && err != nil {
157-
if pError, ok := err.(*precondition.Error); !ok {
184+
var pError *precondition.Error
185+
if !errors.As(err, &pError) {
158186
t.Errorf("Failed to convert to err: %v", err)
159187
} else if diff := cmp.Diff(tc.expected, pError, cmpopts.IgnoreFields(precondition.Error{}, "Nested")); diff != "" {
160188
t.Errorf("%s differs from expected:\n%s", tc.name, diff)

0 commit comments

Comments
 (0)