Skip to content

Commit 76a4bdb

Browse files
authored
Allow Innovative upgrades to be skipped during upgrade (#1091)
* Allow Innovative upgrades to be skipped during upgrade * Fix the conditions and function names
1 parent 55b44c2 commit 76a4bdb

File tree

5 files changed

+387
-9
lines changed

5 files changed

+387
-9
lines changed

e2e/upgrades/upgrades_test.go

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,53 @@ func TestUpgradesMajorVersion20_1To20_2(t *testing.T) {
198198
steps.Run(t)
199199
}
200200

201+
func TestUpgradesMajorVersionSkippingInnovativeRelease24_3To25_1(t *testing.T) {
202+
203+
if testing.Short() {
204+
t.Skip("skipping test in short mode.")
205+
}
206+
207+
testLog := zapr.NewLogger(zaptest.NewLogger(t))
208+
209+
e := testenv.CreateActiveEnvForTest()
210+
env := e.Start()
211+
defer e.Stop()
212+
213+
sb := testenv.NewDiffingSandbox(t, env)
214+
sb.StartManager(t, controller.InitClusterReconcilerWithLogger(testLog))
215+
216+
builder := testutil.NewBuilder("crdb").WithNodeCount(3).WithTLS().
217+
WithImage("cockroachdb/cockroach:v24.3.4").
218+
WithPVDataStore("1Gi").WithResources(resRequirements)
219+
220+
steps := testutil.Steps{
221+
{
222+
Name: "creates a 3-node secure cluster",
223+
Test: func(t *testing.T) {
224+
require.NoError(t, sb.Create(builder.Cr()))
225+
testutil.RequireClusterToBeReadyEventuallyTimeout(t, sb, builder, e2e.CreateClusterTimeout)
226+
},
227+
},
228+
{
229+
Name: "upgrades the cluster to the next major version skipping innovative release",
230+
Test: func(t *testing.T) {
231+
current := builder.Cr()
232+
require.NoError(t, sb.Get(current))
233+
234+
updated := current.DeepCopy()
235+
updated.Spec.Image.Name = "cockroachdb/cockroach:v25.1.0"
236+
require.NoError(t, sb.Patch(updated, client.MergeFrom(current)))
237+
// we wait 10 min because we will be waiting 3 min for each pod.
238+
testutil.RequireClusterToBeReadyEventuallyTimeout(t, sb, builder, e2e.CreateClusterTimeout)
239+
testutil.RequireDbContainersToUseImage(t, sb, updated)
240+
t.Log("Done with major upgrade")
241+
},
242+
},
243+
}
244+
245+
steps.Run(t)
246+
}
247+
201248
// TestUpgradesMinorVersionThenRollback tests a minor version bump
202249
// then rollsback that upgrade
203250
func TestUpgradesMinorVersionThenRollback(t *testing.T) {

pkg/update/internal.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,16 @@ const (
2525
AdminRole = "admin"
2626
)
2727

28+
// ReleaseType represents the type of a release
29+
type ReleaseType int
30+
31+
const (
32+
// Regular releases are mandatory upgrades that cannot be skipped.
33+
Regular ReleaseType = iota
34+
// Innovative releases are optional upgrades that can be skipped.
35+
Innovative
36+
)
37+
2838
// internalUsers is a set of SQL users created as part of the managed service, not to be used
2939
// by customers. This struct is used to hide specific users in the console.
3040
var internalUsers = map[string]struct{}{

pkg/update/update_cockroach_version.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ func UpdateClusterCockroachVersion(
9494

9595
l.V(int(zapcore.InfoLevel)).Info("starting upgrade")
9696

97-
if isForwardOneMajorVersion(update.WantVersion, update.CurrentVersion) {
97+
if isMajorUpgradeAllowed(update.WantVersion, update.CurrentVersion) {
9898
if err := setDowngradeOption(ctx, update.WantVersion, update.CurrentVersion, update.Db, l); err != nil {
9999
return errors.Wrapf(err, "setting downgrade option for major roll forward failed")
100100
}
@@ -206,7 +206,7 @@ func kindAndCheckPreserveDowngradeSetting(
206206
if isPatch(wantVersion, currentVersion) {
207207
l.V(int(zapcore.DebugLevel)).Info("patch upgrade")
208208
return "PATCH", nil
209-
} else if isForwardOneMajorVersion(wantVersion, currentVersion) {
209+
} else if isMajorUpgradeAllowed(wantVersion, currentVersion) {
210210
l.V(int(zapcore.DebugLevel)).Info("major upgrade")
211211
s := "MAJOR_UPGRADE"
212212
preserve, err := preserveDowngradeSetting(ctx, db)
@@ -226,7 +226,7 @@ func kindAndCheckPreserveDowngradeSetting(
226226
}
227227
}
228228
return s, nil
229-
} else if isBackOneMajorVersion(wantVersion, currentVersion) {
229+
} else if isMajorRollbackAllowed(wantVersion, currentVersion) {
230230
l.V(int(zapcore.DebugLevel)).Info("major rollback")
231231
return CheckDowngradeSetting(ctx, wantVersion, currentVersion, db)
232232
}

pkg/update/update_cockroach_version_common.go

Lines changed: 186 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ package update
1919
import (
2020
"errors"
2121
"fmt"
22+
"strconv"
23+
"strings"
2224
"time"
2325

2426
semver "github.com/Masterminds/semver/v3"
@@ -134,18 +136,200 @@ func isPatch(wantVersion *semver.Version, currentVersion *semver.Version) bool {
134136
return currentVersion.Major() == wantVersion.Major() && currentVersion.Minor() == wantVersion.Minor()
135137
}
136138

137-
func isForwardOneMajorVersion(wantVersion *semver.Version, currentVersion *semver.Version) bool {
139+
// getReleaseType returns if a release is Innovative or Regular.
140+
func getReleaseType(major, minor int) ReleaseType {
141+
// Before 25.1, we define them explicitly
142+
switch fmt.Sprintf("%d.%d", major, minor) {
143+
case "24.1", "24.3":
144+
return Regular
145+
case "24.2":
146+
return Innovative
147+
}
148+
149+
// Post 25.1: Odd releases (1,3) are Innovative, Even (2,4) are Regular
150+
if minor%2 == 1 {
151+
return Innovative
152+
}
153+
return Regular
154+
}
155+
156+
// generateReleases generates a list of release versions from a 24.1 to the specified year.
157+
// The releases follow a "YY.Q" format, where YY represents the last two digits of the year, and Q represents the quarter.
158+
//
159+
// Parameters:
160+
// - upToYear: The last two digits of the year up to which releases should be generated.
161+
//
162+
// Returns:
163+
// - A slice of strings containing release versions from "24.1" onward, up to the specified year, with four
164+
// releases per year (except 2024).
165+
func generateReleases(upToYear int) []string {
166+
var releases = []string{"24.1", "24.2", "24.3"}
167+
168+
for year := 25; year <= upToYear; year++ {
169+
for quarter := 1; quarter <= 4; quarter++ {
170+
releases = append(releases, fmt.Sprintf("%d.%d", year, quarter))
171+
}
172+
}
173+
174+
return releases
175+
}
176+
177+
// getNextReleases returns a list of possible upgradable release versions following the given currentVersion.
178+
// It iterates through the generated release versions for the current year and collects the next releases
179+
// after finding the currentVersion. The function stops collecting releases once it encounters the next regular release.
180+
//
181+
// Parameters:
182+
// - currentVersion: The current release version in the format "YY.Q" (e.g., "24.1").
183+
//
184+
// Returns:
185+
// - A slice of strings containing the possible upgradable release versions, stopping at the next regular release.
186+
func getNextReleases(currentVersion string) []string {
187+
var nextReleases []string
188+
var found bool
189+
190+
releases := generateReleases(time.Now().Year() % 100)
191+
for _, release := range releases {
192+
year, _ := strconv.Atoi(strings.Split(release, ".")[0])
193+
quarter, _ := strconv.Atoi(strings.Split(release, ".")[1])
194+
195+
if found {
196+
nextReleases = append(nextReleases, release)
197+
if getReleaseType(year, quarter) == Regular {
198+
break // Stop at the next regular release
199+
}
200+
}
201+
if release == currentVersion {
202+
found = true
203+
}
204+
}
205+
206+
return nextReleases
207+
}
208+
209+
// getPreviousReleases returns a list of possible rollback release versions preceding the given currentVersion.
210+
// It iterates through the generated release versions in reverse order and collects previous releases
211+
// until it encounters the last regular release before the currentVersion.
212+
//
213+
// Parameters:
214+
// - currentVersion: The current release version in the format "YY.Q" (e.g., "24.1").
215+
//
216+
// Returns:
217+
// - A slice of strings containing possible rollback release versions, stopping at the last regular release before the currentVersion.
218+
func getPreviousReleases(currentVersion string) []string {
219+
var prevReleases []string
220+
var found bool
221+
222+
releases := generateReleases(time.Now().Year() % 100)
223+
for i := len(releases) - 1; i >= 0; i-- {
224+
release := releases[i]
225+
year, _ := strconv.Atoi(strings.Split(release, ".")[0])
226+
quarter, _ := strconv.Atoi(strings.Split(release, ".")[1])
227+
if found {
228+
prevReleases = append(prevReleases, release)
229+
if getReleaseType(year, quarter) == Regular {
230+
break
231+
}
232+
}
233+
if release == currentVersion {
234+
found = true
235+
}
236+
}
237+
238+
return prevReleases
239+
}
240+
241+
// isMajorUpgradeAllowed determines whether an upgrade from the current version to the desired version is allowed
242+
// based on the release cycle and upgrade policies.
243+
//
244+
// Upgrade Rules:
245+
// 1. For versions prior to 2024:
246+
// - A major upgrade is allowed if
247+
// - The major version remains the same and the minor version increments by 1 (e.g., 19.1 -> 19.2).
248+
// - The major version increments by 1 and the minor version resets to 1. (e.g., 19.2 -> 20.1).
249+
//
250+
// 2. For version of 2024:
251+
// - There are 3 releases (24.1, 24.2, 24.3) where 24.2 being innovative release
252+
//
253+
// 3. For versions from 2025 onwards:
254+
// - Releases follow a quarterly cycle, with some releases designated as innovative.
255+
// - Users are allowed to skip upgrading to an innovative release.
256+
// - The upgrade is permitted if the desired version matches one of the next valid releases.
257+
//
258+
// Parameters:
259+
// - wantVersion: The target version to which an upgrade is requested.
260+
// - currentVersion: The currently installed version.
261+
//
262+
// Returns:
263+
// - true if the upgrade is allowed based on the release rules; false otherwise.
264+
func isMajorUpgradeAllowed(wantVersion *semver.Version, currentVersion *semver.Version) bool {
138265
// Two cases:
139266
// 19.1 to 19.2 -> same year
140267
// 19.2 to 20.1 -> next year
268+
269+
// Since 2025, we have adopted a quarterly release cycle, with two of the four annual releases designated
270+
// as innovative releases. Users have the option to skip upgrading to an innovative release.
271+
// For 2024, we just had 3 releases overall, with 24.2 as the innovation release.
272+
if currentVersion.Major() >= 24 {
273+
// Four Cases:
274+
// 24.1 to 24.2 -> Same year without skipping innovative release
275+
// 24.1 to 24.3 -> Same year with skipping innovative release
276+
// 24.3 to 25.1 -> Next year without skipping innovative release
277+
// 24.3 to 25.2 -> Next year with skipping innovative release
278+
nextPossibleRelease := getNextReleases(fmt.Sprintf("%d.%d", currentVersion.Major(), currentVersion.Minor()))
279+
for _, version := range nextPossibleRelease {
280+
if version == fmt.Sprintf("%d.%d", wantVersion.Major(), wantVersion.Minor()) {
281+
return true
282+
}
283+
}
284+
285+
return false
286+
}
287+
141288
return (currentVersion.Major() == wantVersion.Major() && currentVersion.Minor()+1 == wantVersion.Minor()) ||
142289
(currentVersion.Major()+1 == wantVersion.Major() && currentVersion.Minor()-1 == wantVersion.Minor())
143290
}
144291

145-
func isBackOneMajorVersion(wantVersion *semver.Version, currentVersion *semver.Version) bool {
292+
// isMajorRollbackAllowed determines whether rolling back from the current version to the desired version is allowed
293+
// based on the release cycle and rollback policies.
294+
//
295+
// Rollback Rules:
296+
// 1. For versions prior to 2024:
297+
// - A rollback is allowed if:
298+
// - The major version remains the same and the minor version decrements by 1 (e.g., 19.2 -> 19.1).
299+
// - The major version decrements by 1 and the minor version is the last release. (e.g., 20.1 -> 19.2).
300+
//
301+
// 2. For versions from 2024 onwards:
302+
// - Users can skip rolling back to an innovative release.
303+
// - The rollback is permitted if the desired version matches one of the valid previous releases.
304+
//
305+
// Parameters:
306+
// - wantVersion: The target version to which a rollback is requested.
307+
// - currentVersion: The currently installed version.
308+
//
309+
// Returns:
310+
// - true if the rollback is allowed based on the rollback policies; false otherwise.
311+
func isMajorRollbackAllowed(wantVersion *semver.Version, currentVersion *semver.Version) bool {
146312
// Two cases:
147313
// 19.2 to 19.1 -> same year
148314
// 20.1 to 19.2 -> previous year
315+
316+
// Since 2024, users have the option to skip rollback to an innovative release.
317+
if wantVersion.Major() >= 24 {
318+
// Four cases:
319+
// 24.2 -> 24.1 -> Same year without skipping innovative release
320+
// 24.3 -> 24.1 -> Same year with skipping innovative release
321+
// 25.1 -> 24.3 -> Previous year without skipping innovative release
322+
// 25.2 -> 24.3 -> Previous year with skipping innovative release
323+
rollbackReleases := getPreviousReleases(fmt.Sprintf("%d.%d", currentVersion.Major(), currentVersion.Minor()))
324+
for _, version := range rollbackReleases {
325+
if version == fmt.Sprintf("%d.%d", wantVersion.Major(), wantVersion.Minor()) {
326+
return true
327+
}
328+
}
329+
330+
return false
331+
}
332+
149333
return (currentVersion.Major() == wantVersion.Major() && currentVersion.Minor() == wantVersion.Minor()+1) ||
150334
(currentVersion.Major() == wantVersion.Major()+1 && currentVersion.Minor() == wantVersion.Minor()-1)
151335
}

0 commit comments

Comments
 (0)