Skip to content

Commit e113b11

Browse files
authored
Merge pull request #143006 from cockroachdb/blathers/backport-release-25.1-142211
release-25.1: mixedversion: add MinBootstrapVersion option
2 parents dfe0390 + a99fff6 commit e113b11

19 files changed

+867
-681
lines changed

pkg/cmd/roachtest/roachtestutil/clusterupgrade/clusterupgrade.go

+6
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,12 @@ func (v *Version) AtLeast(other *Version) bool {
8383
return v.Version.AtLeast(&other.Version)
8484
}
8585

86+
// LessThan returns true if the version is strictly
87+
// older than the other version. `v < other`
88+
func (v *Version) LessThan(other *Version) bool {
89+
return !v.Version.AtLeast(&other.Version)
90+
}
91+
8692
// Series returns the release series this version is a part of.
8793
func (v *Version) Series() string {
8894
return release.VersionSeries(&v.Version)

pkg/cmd/roachtest/roachtestutil/mixedversion/BUILD.bazel

+1
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ go_test(
7171
"//pkg/testutils/release",
7272
"//pkg/util/intsets",
7373
"//pkg/util/randutil",
74+
"//pkg/util/retry",
7475
"//pkg/util/version",
7576
"@com_github_cockroachdb_datadriven//:datadriven",
7677
"@com_github_cockroachdb_errors//:errors",

pkg/cmd/roachtest/roachtestutil/mixedversion/README.md

+5
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,11 @@ This is the first _user hook_ we encounter: `OnStartup`. [Hooks](#terminology) a
164164

165165
One important point to note is that mixed-version tests start the cluster at a [_predecessor version_ (not necessarily immediate predecessor)](#high-level-overview). As such, if a feature was just introduced in the current release, it won't be available at this point.
166166

167+
**Note**: _Minimum Supported Version_ vs. _Minimum Bootstrap Version_:
168+
One common mistake is to think that the _minimum supported version_ will configure the test to never start on any version older. However, this setting only configures _user hooks_ to not be scheduled at any versions older than the _minimum supported version_. It is still possible for the test to start the cluster at a _predecessor version_ of the _minimum supported version_ and run upgrades normally without any user hooks. Even if the feature we are primarily testing was not introduced until _minimum supported version_, it can be useful to run through the exercise of loading in old data.
169+
170+
However, you may run into a scenario that requires the cluster to _start_ at a certain version. This is where _minimum bootstrap version_ comes in and enforces that the framework will not generate any plans that start the cluster on a version older. For best practices, if either option works, prefer _Minimum Supported Version_ due to the extra coverage it provides.
171+
167172
#### Testing code in mixed-version
168173

169174
``` go

pkg/cmd/roachtest/roachtestutil/mixedversion/mixedversion.go

+126-31
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,6 @@ import (
7373
"math/rand"
7474
"os"
7575
"path/filepath"
76-
"slices"
7776
"strings"
7877
"sync"
7978
"time"
@@ -327,6 +326,10 @@ type (
327326
minUpgrades int
328327
maxUpgrades int
329328
minimumSupportedVersion *clusterupgrade.Version
329+
// N.B. If unset, then there is no minimum bootstrap version enforced.
330+
// We do this over, e.g. setting it to the oldest version we have release data
331+
// for, because unit tests can use fake release data much older than that.
332+
minimumBootstrapVersion *clusterupgrade.Version
330333
// predecessorFunc computes the predecessor of a particular
331334
// release. By default, random predecessors are used, but tests
332335
// may choose to always use the latest predecessor as well.
@@ -456,6 +459,16 @@ func MinimumSupportedVersion(v string) CustomOption {
456459
}
457460
}
458461

462+
// MinimumBootstrapVersion allows tests to specify that the
463+
// cluster created should only be bootstrapped on a version
464+
// `v` or above. May override MaxUpgrades if the minimum bootstrap
465+
// version does not support that many upgrades.
466+
func MinimumBootstrapVersion(v string) CustomOption {
467+
return func(opts *testOptions) {
468+
opts.minimumBootstrapVersion = clusterupgrade.MustParseVersion(v)
469+
}
470+
}
471+
459472
// ClusterSettingOption adds a cluster setting option, in addition to the
460473
// default set of options.
461474
func ClusterSettingOption(opt ...install.ClusterSettingOption) CustomOption {
@@ -817,20 +830,19 @@ func (t *Test) plan() (plan *TestPlan, retErr error) {
817830
}
818831
}()
819832
var retries int
820-
// In case the length of the test plan exceeds `opts.maxNumPlanSteps`, retry up to 200 times.
833+
// In case the length of the test plan exceeds `opts.maxNumPlanSteps`, retry up to 100 times.
821834
// N.B. Statistically, the expected number of retries is miniscule; see #138014 for more info.
822-
for ; retries < 200; retries++ {
835+
for ; retries < 100; retries++ {
823836

824837
// Pick a random deployment mode to use in this test run among the
825838
// list of enabled deployment modes enabled for this test.
826839
deploymentMode := t.deploymentMode()
827840
t.updateOptionsForDeploymentMode(deploymentMode)
828841

829-
upgradePath, err := t.choosePreviousReleases()
842+
upgradePath, err := t.chooseUpgradePath()
830843
if err != nil {
831844
return nil, err
832845
}
833-
upgradePath = append(upgradePath, clusterupgrade.CurrentVersion())
834846

835847
if override := os.Getenv(upgradePathOverrideEnv); override != "" {
836848
upgradePath, err = parseUpgradePathOverride(override)
@@ -893,14 +905,15 @@ func (t *Test) runCommandFunc(nodes option.NodeListOption, cmd string) stepFunc
893905
}
894906
}
895907

896-
// choosePreviousReleases returns a list of predecessor releases
897-
// relative to the current build version. It uses the `predecessorFunc`
898-
// field to compute the actual list of predecessors. This function
899-
// may also choose to skip releases when supported. Special care is
900-
// taken to avoid using releases that are not available under a
901-
// certain cluster architecture. Specifically, ARM64 builds are
902-
// only available on v22.2.0+.
903-
func (t *Test) choosePreviousReleases() ([]*clusterupgrade.Version, error) {
908+
// chooseUpgradePath returns a valid upgrade path for the test to
909+
// take. An upgrade path is a list of predecessor versions that can
910+
// be upgraded into eachother, ending at the current build version.
911+
// It uses the `predecessorFunc` field to compute the actual list of
912+
// predecessors. This function may also choose to skip releases when
913+
// supported. Special care is taken to avoid using releases that are
914+
// not available under a certain cluster architecture. Specifically,
915+
// ARM64 builds are only available on v22.2.0+.
916+
func (t *Test) chooseUpgradePath() ([]*clusterupgrade.Version, error) {
904917
skipVersions := t.prng.Float64() < t.options.skipVersionProbability
905918
isAvailable := func(v *clusterupgrade.Version) bool {
906919
if t.clusterArch() != vm.ArchARM64 {
@@ -952,35 +965,79 @@ func (t *Test) choosePreviousReleases() ([]*clusterupgrade.Version, error) {
952965
return []*clusterupgrade.Version{pred, predPred}, nil
953966
}
954967

968+
// possibleUpgradePathsMap maps a version to the possible upgrade paths that
969+
// can be taken with exactly n upgrades.
970+
possibleUpgradePathsMap := make(map[*clusterupgrade.Version]map[int][][]*clusterupgrade.Version)
971+
// findPossibleUpgradePaths finds all legal upgrade paths ending at version `v` with
972+
// exactly `numUpgrades` upgrades.
973+
var findPossibleUpgradePaths func(v *clusterupgrade.Version, numUpgrades int) ([][]*clusterupgrade.Version, error)
974+
findPossibleUpgradePaths = func(v *clusterupgrade.Version, numUpgrades int) ([][]*clusterupgrade.Version, error) {
975+
// If there are no upgrades, then the only possible path is the current version.
976+
if numUpgrades == 0 {
977+
return [][]*clusterupgrade.Version{{v}}, nil
978+
}
979+
// Check if we have already computed the possible upgrade paths for this version.
980+
if _, ok := possibleUpgradePathsMap[v]; !ok {
981+
possibleUpgradePathsMap[v] = make(map[int][][]*clusterupgrade.Version)
982+
} else if paths, ok := possibleUpgradePathsMap[v][numUpgrades]; ok {
983+
return paths, nil
984+
}
985+
986+
// The possible upgrade paths for this version with exactly `numUpgrades` upgrades
987+
// is the possible upgrade paths of `v's` predecessors with `numUpgrades-1`
988+
// upgrades plus version `v`.
989+
var paths [][]*clusterupgrade.Version
990+
predecessors, err := possiblePredecessorsFor(v)
991+
if err != nil {
992+
return nil, err
993+
}
994+
for _, pred := range predecessors {
995+
// If the predecessor is older than the minimum bootstrapped version, then
996+
// it's not a legal upgrade path.
997+
if t.options.minimumBootstrapVersion != nil && pred.LessThan(t.options.minimumBootstrapVersion) {
998+
continue
999+
}
1000+
predPaths, err := findPossibleUpgradePaths(pred, numUpgrades-1)
1001+
if err != nil {
1002+
return nil, err
1003+
}
1004+
for _, p := range predPaths {
1005+
paths = append(paths, append(p, v))
1006+
}
1007+
}
1008+
1009+
possibleUpgradePathsMap[v][numUpgrades] = paths
1010+
return paths, nil
1011+
}
1012+
9551013
currentVersion := clusterupgrade.CurrentVersion()
9561014
var upgradePath []*clusterupgrade.Version
9571015
numUpgrades := t.numUpgrades()
9581016

959-
for j := 0; j < numUpgrades; j++ {
960-
predecessors, err := possiblePredecessorsFor(currentVersion)
1017+
// Best effort to find a valid upgrade path with exactly numUpgrades. If one is
1018+
// not found because some release is not available for the cluster architecture,
1019+
// retry with fewer upgrades. We log a warning below in case we have a shorter
1020+
// upgrade path than requested because of this.
1021+
for j := numUpgrades; j > 0; j-- {
1022+
// N.B. We need to enumerate all possible upgrade paths in order to respect the
1023+
// minimum bootstrap version of the test. Due to skip upgrades, we can't just
1024+
// greedily pick the first path we find with `numUpgrades` upgrades.
1025+
possibleUpgradePaths, err := findPossibleUpgradePaths(currentVersion, j)
9611026
if err != nil {
9621027
return nil, err
9631028
}
964-
965-
// If there are no valid predecessors, it means some release is
966-
// not available for the cluster architecture. We log a warning
967-
// below in case we have a shorter upgrade path than requested
968-
// because of this.
969-
if len(predecessors) == 0 {
1029+
if possibleUpgradePaths != nil {
1030+
upgradePath = possibleUpgradePaths[t.prng.Intn(len(possibleUpgradePaths))]
9701031
break
9711032
}
972-
973-
chosenPredecessor := predecessors[t.prng.Intn(len(predecessors))]
974-
upgradePath = append(upgradePath, chosenPredecessor)
975-
currentVersion = chosenPredecessor
9761033
}
9771034

9781035
if len(upgradePath) < numUpgrades {
1036+
if t.clusterArch() != vm.ArchARM64 {
1037+
return nil, errors.Newf("unable to find a valid upgrade path with %d upgrades", numUpgrades)
1038+
}
9791039
t.logger.Printf("WARNING: skipping upgrades as ARM64 is only supported on %s+", minSupportedARM64Version)
9801040
}
981-
982-
// The upgrade path to be returned is from oldest to newest release.
983-
slices.Reverse(upgradePath)
9841041
return upgradePath, nil
9851042
}
9861043

@@ -1045,7 +1102,7 @@ func randomPredecessor(
10451102
)
10461103
}
10471104

1048-
// If the patch version of `minSupporrted` is 0, it means that we
1105+
// If the patch version of `minSupported` is 0, it means that we
10491106
// can choose any patch release in the predecessor series. It is
10501107
// also safe to return `predV` here if the `minSupported` version is
10511108
// a pre-release: we already validated that `predV`is at least
@@ -1323,6 +1380,8 @@ func assertValidTest(test *Test, fatalFunc func(...interface{})) {
13231380
fail := func(err error) {
13241381
fatalFunc(errors.Wrap(err, "mixedversion.NewTest"))
13251382
}
1383+
minUpgrades := test.options.minUpgrades
1384+
maxUpgrades := test.options.maxUpgrades
13261385

13271386
if test.options.useFixturesProbability > 0 && len(test.crdbNodes) != numNodesInFixtures {
13281387
fail(
@@ -1333,11 +1392,11 @@ func assertValidTest(test *Test, fatalFunc func(...interface{})) {
13331392
)
13341393
}
13351394

1336-
if test.options.minUpgrades > test.options.maxUpgrades {
1395+
if minUpgrades > maxUpgrades {
13371396
fail(
13381397
fmt.Errorf(
13391398
"invalid test options: maxUpgrades (%d) must be greater than minUpgrades (%d)",
1340-
test.options.maxUpgrades, test.options.minUpgrades,
1399+
maxUpgrades, minUpgrades,
13411400
),
13421401
)
13431402
}
@@ -1399,6 +1458,42 @@ func assertValidTest(test *Test, fatalFunc func(...interface{})) {
13991458
SeparateProcessDeployment, minSeparateProcessNodes,
14001459
))
14011460
}
1461+
1462+
// Validate that the minimum bootstrap version if set.
1463+
if minBootstrapVersion := test.options.minimumBootstrapVersion; minBootstrapVersion != nil {
1464+
// The minimum bootstrap version should be from an older major
1465+
// version or, if from the same major version, from an older minor
1466+
// version.
1467+
validVersion = minBootstrapVersion.Major() < currentVersion.Major() ||
1468+
(minBootstrapVersion.Major() == currentVersion.Major() && minBootstrapVersion.Minor() < currentVersion.Minor())
1469+
1470+
if !validVersion {
1471+
fail(
1472+
fmt.Errorf(
1473+
"invalid test options: minimum bootstrap version (%s) should be from an older release series than current version (%s)",
1474+
minBootstrapVersion.Version.String(), currentVersion.Version.String(),
1475+
),
1476+
)
1477+
}
1478+
1479+
// The minimum bootstrap version should be compatible with the min and max upgrades.
1480+
maxUpgradesFromBootstrapVersion, err := release.MajorReleasesBetween(&minBootstrapVersion.Version, &currentVersion.Version)
1481+
if err != nil {
1482+
fail(err)
1483+
}
1484+
if maxUpgradesFromBootstrapVersion < minUpgrades {
1485+
fail(errors.Newf(
1486+
"invalid test options: minimum bootstrap version (%s) does not allow for min %d upgrades",
1487+
minBootstrapVersion, minUpgrades,
1488+
))
1489+
}
1490+
// Override the max upgrades if the minimum bootstrap version does not allow for that
1491+
// many upgrades.
1492+
if maxUpgrades > maxUpgradesFromBootstrapVersion {
1493+
test.logger.Printf("WARN: overriding maxUpgrades, minimum bootstrap version (%s) allows for at most %d upgrades", minBootstrapVersion, maxUpgradesFromBootstrapVersion)
1494+
test.options.maxUpgrades = maxUpgradesFromBootstrapVersion
1495+
}
1496+
}
14021497
}
14031498

14041499
// parseUpgradePathOverride parses the upgrade path override and returns it as a list

pkg/cmd/roachtest/roachtestutil/mixedversion/mixedversion_test.go

+30-2
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,33 @@ func Test_assertValidTest(t *testing.T) {
202202
mvt = newTest(MinimumSupportedVersion("v22.2.0"))
203203
assertValidTest(mvt, fatalFunc())
204204
require.NoError(t, fatalErr)
205+
206+
// Test that if there are fewer upgrades possible between the minimum
207+
// bootstrap version and the current version than MaxUpgrades, maxUpgrades
208+
// is overridden to the former.
209+
mvt = newTest(MinimumBootstrapVersion("v21.2.0"), MaxUpgrades(10))
210+
assertValidTest(mvt, fatalFunc())
211+
require.NoError(t, fatalErr)
212+
require.Equal(t, 3, mvt.options.maxUpgrades)
213+
214+
// Test that if there are fewer upgrades possible between the minimum
215+
// bootstrap version and the current version than MinUpgrades, the test
216+
// is invalid.
217+
mvt = newTest(MinimumBootstrapVersion("v21.2.0"), MinUpgrades(10))
218+
assertValidTest(mvt, fatalFunc())
219+
require.Error(t, fatalErr)
220+
require.Equal(t,
221+
`mixedversion.NewTest: invalid test options: minimum bootstrap version (v21.2.0) does not allow for min 10 upgrades`,
222+
fatalErr.Error(),
223+
)
224+
225+
mvt = newTest(MinimumBootstrapVersion("v24.2.0"))
226+
assertValidTest(mvt, fatalFunc())
227+
require.Error(t, fatalErr)
228+
require.Equal(t,
229+
`mixedversion.NewTest: invalid test options: minimum bootstrap version (v24.2.0) should be from an older release series than current version (v23.1.2)`,
230+
fatalErr.Error(),
231+
)
205232
}
206233

207234
func Test_choosePreviousReleases(t *testing.T) {
@@ -264,13 +291,14 @@ func Test_choosePreviousReleases(t *testing.T) {
264291
}
265292
mvt._arch = &tc.arch
266293

267-
releases, err := mvt.choosePreviousReleases()
294+
releases, err := mvt.chooseUpgradePath()
268295
if tc.expectedErr == "" {
269296
require.NoError(t, err)
270297
var expectedVersions []*clusterupgrade.Version
271298
for _, er := range tc.expectedReleases {
272299
expectedVersions = append(expectedVersions, clusterupgrade.MustParseVersion(er))
273300
}
301+
expectedVersions = append(expectedVersions, clusterupgrade.CurrentVersion())
274302
require.Equal(t, expectedVersions, releases)
275303
} else {
276304
require.Error(t, err)
@@ -368,7 +396,7 @@ func Test_randomPredecessor(t *testing.T) {
368396
func withTestBuildVersion(v string) func() {
369397
testBuildVersion := version.MustParse(v)
370398
clusterupgrade.TestBuildVersion = testBuildVersion
371-
return func() { clusterupgrade.TestBuildVersion = nil }
399+
return func() { clusterupgrade.TestBuildVersion = buildVersion }
372400
}
373401

374402
func TestSupportsSkipUpgradeTo(t *testing.T) {

0 commit comments

Comments
 (0)