Skip to content

Commit b699f1b

Browse files
DarrylWongsrosenberg
authored andcommitted
mixedversion: add quick test for generating valid plans
This change adds a quick test to generate random plans with various options and asserts that certain variants hold.
1 parent 5cdeb75 commit b699f1b

File tree

3 files changed

+165
-3
lines changed

3 files changed

+165
-3
lines changed

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1667,8 +1667,8 @@ func assertValidTest(test *Test, fatalFunc func(...interface{})) {
16671667
}
16681668
if maxUpgradesFromBootstrapVersion < minUpgrades {
16691669
fail(errors.Newf(
1670-
"invalid test options: minimum bootstrap version (%s) does not allow for min %d upgrades",
1671-
minBootstrapVersion, minUpgrades,
1670+
"invalid test options: minimum bootstrap version (%s) does not allow for min %d upgrades to %s, max is %d",
1671+
minBootstrapVersion, minUpgrades, currentVersion, maxUpgradesFromBootstrapVersion,
16721672
))
16731673
}
16741674
// Override the max upgrades if the minimum bootstrap version does not allow for that

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -222,7 +222,7 @@ func Test_assertValidTest(t *testing.T) {
222222
assertValidTest(mvt, fatalFunc())
223223
require.Error(t, fatalErr)
224224
require.Equal(t,
225-
`mixedversion.NewTest: invalid test options: minimum bootstrap version (v21.2.0) does not allow for min 10 upgrades`,
225+
`mixedversion.NewTest: invalid test options: minimum bootstrap version (v21.2.0) does not allow for min 10 upgrades to v23.1.2, max is 3`,
226226
fatalErr.Error(),
227227
)
228228

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

Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,10 @@ import (
1010
"fmt"
1111
"io"
1212
"math/rand"
13+
"reflect"
1314
"strconv"
1415
"testing"
16+
"testing/quick"
1517
"time"
1618

1719
"github.com/cockroachdb/cockroach/pkg/cmd/roachtest/option"
@@ -21,6 +23,7 @@ import (
2123
"github.com/cockroachdb/cockroach/pkg/roachprod/logger"
2224
"github.com/cockroachdb/cockroach/pkg/roachprod/vm"
2325
"github.com/cockroachdb/cockroach/pkg/testutils/datapathutils"
26+
"github.com/cockroachdb/cockroach/pkg/testutils/release"
2427
"github.com/cockroachdb/cockroach/pkg/util/randutil"
2528
"github.com/cockroachdb/cockroach/pkg/util/retry"
2629
"github.com/cockroachdb/datadriven"
@@ -1041,3 +1044,162 @@ func Test_SeparateProcessUsesLatestPred(t *testing.T) {
10411044
require.Equal(t, latestVersion, version)
10421045
}
10431046
}
1047+
1048+
func Test_ChooseUpgradePathInvariants(t *testing.T) {
1049+
rng, _ := randutil.NewTestRand()
1050+
defer withTestBuildVersion("v25.1.0")()
1051+
1052+
newestPossibleVersion := clusterupgrade.MustParseVersion("v25.1.0")
1053+
oldestVersionWithReleaseData := clusterupgrade.MustParseVersion("v22.1.0")
1054+
maxPossibleUpgrades, err := release.MajorReleasesBetween(&newestPossibleVersion.Version, &oldestVersionWithReleaseData.Version)
1055+
require.NoError(t, err)
1056+
1057+
randomNthPredecessor := func(rng *rand.Rand, v *clusterupgrade.Version, n int) *clusterupgrade.Version {
1058+
curVersion := v.Version
1059+
for range n {
1060+
versionStr, err := release.RandomPredecessor(rng, &curVersion)
1061+
require.NoError(t, err)
1062+
clusterupgradeVersion, err := clusterupgrade.ParseVersion(versionStr)
1063+
require.NoError(t, err)
1064+
curVersion = clusterupgradeVersion.Version
1065+
}
1066+
return &clusterupgrade.Version{Version: curVersion}
1067+
}
1068+
1069+
generator := func(values []reflect.Value, rng *rand.Rand) {
1070+
// We don't always want to upgrade into the latest release (v25.1) so pick a release
1071+
// n predecessors older.
1072+
finalVersionOffset := rng.Intn(maxPossibleUpgrades)
1073+
finalVersion := randomNthPredecessor(rng, newestPossibleVersion, finalVersionOffset)
1074+
values[0] = reflect.ValueOf(finalVersion.Version.String())
1075+
1076+
maxUpgrades := rng.Intn(maxPossibleUpgrades-finalVersionOffset) + 1
1077+
upgradesRemaining := maxUpgrades - finalVersionOffset
1078+
1079+
// We force the minSupportedVersion to be at least one release series older, otherwise we
1080+
// aren't testing anything interesting (e.g. no upgrades will be possible).
1081+
minSupportedVersionOffset := 1
1082+
if upgradesRemaining > 0 {
1083+
minSupportedVersionOffset = rng.Intn(upgradesRemaining) + 1
1084+
}
1085+
upgradesRemaining -= minSupportedVersionOffset
1086+
minSupportedVersion := randomNthPredecessor(rng, finalVersion, minSupportedVersionOffset)
1087+
values[1] = reflect.ValueOf(minSupportedVersion.Version.String())
1088+
1089+
// The minBootstrapVersion can be any version less than or equal to minSupportedVersion.
1090+
minBootstrapVersionOffset := 0
1091+
if upgradesRemaining > 0 {
1092+
minBootstrapVersionOffset = rng.Intn(upgradesRemaining)
1093+
}
1094+
upgradesRemaining -= minBootstrapVersionOffset
1095+
minBootstrapVersion := randomNthPredecessor(rng, minSupportedVersion, minBootstrapVersionOffset)
1096+
values[2] = reflect.ValueOf(minBootstrapVersion.Version.String())
1097+
1098+
// Find the maximum amount of possible upgrades from the minBootstrapVersion
1099+
// and pick a random value [1, maxUpgradesFromMBV] to set numUpgrades to.
1100+
maxUpgradesFromMBV, err := release.MajorReleasesBetween(&finalVersion.Version, &minBootstrapVersion.Version)
1101+
require.NoError(t, err)
1102+
1103+
numUpgrades := rng.Intn(maxUpgradesFromMBV) + 1
1104+
values[3] = reflect.ValueOf(numUpgrades)
1105+
1106+
values[4] = reflect.ValueOf(rng.Float64() > 0.5)
1107+
}
1108+
1109+
cfg := quick.Config{
1110+
MaxCount: 100,
1111+
Rand: rng,
1112+
Values: generator,
1113+
}
1114+
1115+
var fatalErr error
1116+
fatalFunc := func() func(...interface{}) {
1117+
fatalErr = nil
1118+
return func(args ...interface{}) {
1119+
require.Len(t, args, 1)
1120+
err, isErr := args[0].(error)
1121+
require.True(t, isErr)
1122+
1123+
fatalErr = err
1124+
}
1125+
}
1126+
1127+
verifyPlan := func(
1128+
finalVersion string, minSupportedVersion string, minBootstrapVersion string, numUpgrades int, skipVersions bool,
1129+
) bool {
1130+
// The top level withTestBuildVersion will take care of resetting this for us.
1131+
_ = withTestBuildVersion(finalVersion)
1132+
1133+
// Set up our test plan using the generated values.
1134+
mvt := newTest(
1135+
NumUpgrades(numUpgrades),
1136+
MinimumSupportedVersion(minSupportedVersion),
1137+
MinimumBootstrapVersion(minBootstrapVersion),
1138+
)
1139+
if skipVersions {
1140+
mvt.options.skipVersionProbability = 1
1141+
}
1142+
mvt.options.predecessorFunc = randomPredecessor
1143+
1144+
assertValidTest(mvt, fatalFunc())
1145+
if fatalErr != nil {
1146+
t.Log(fatalErr)
1147+
return false
1148+
}
1149+
1150+
plan, err := mvt.plan()
1151+
logAndFail := func(format string, args ...any) bool {
1152+
t.Logf(format, args)
1153+
t.Log(plan.PrettyPrint())
1154+
return false
1155+
}
1156+
if err != nil {
1157+
return logAndFail(err.Error())
1158+
}
1159+
1160+
// It is possible for the number of total upgrades generated to be less than the
1161+
// number requested if:
1162+
//
1163+
// 1. The requested number of upgrades is greater than the number of possible upgrades
1164+
// from the minBootstrapVersion to the current version. mvt.numUpgrades should reflect
1165+
// this upper bound.
1166+
// 2. Skip upgrades are enabled. The framework will prioritize testing at least one
1167+
// skip upgrade over matching the exact number of upgrades requested.
1168+
assertNumUpgrades := func() bool {
1169+
numUpgradesGenerated := len(plan.allUpgrades())
1170+
expectedUpgrades := mvt.numUpgrades()
1171+
if numUpgradesGenerated != expectedUpgrades {
1172+
if skipVersions && numUpgradesGenerated == expectedUpgrades-1 {
1173+
return true
1174+
}
1175+
return logAndFail("expected %d upgrades, got %d", expectedUpgrades, numUpgradesGenerated)
1176+
}
1177+
return true
1178+
}
1179+
1180+
// randomPredecessor should take special care to pick a predecessor that is newer
1181+
// than the minimum supported version and minimum bootstrap version if they are in
1182+
// the same series as the predecessor.
1183+
assertRandomPredecessorRespectsMinVersion := func() bool {
1184+
msvSeries := mvt.options.minimumSupportedVersion.Series()
1185+
mbvSeries := mvt.options.minimumBootstrapVersion.Series()
1186+
for _, v := range plan.Versions() {
1187+
if v.Series() == msvSeries {
1188+
if v.LessThan(mvt.options.minimumSupportedVersion) {
1189+
return logAndFail("randomPredecessor should have picked predecessor newer than minimum supported version")
1190+
}
1191+
}
1192+
if v.Series() == mbvSeries {
1193+
if v.LessThan(mvt.options.minimumBootstrapVersion) {
1194+
return logAndFail("randomPredecessor should have picked predecessor newer than minimum bootstrap version")
1195+
}
1196+
}
1197+
}
1198+
return true
1199+
}
1200+
1201+
return assertNumUpgrades() && assertRandomPredecessorRespectsMinVersion()
1202+
}
1203+
1204+
require.NoError(t, quick.Check(verifyPlan, &cfg))
1205+
}

0 commit comments

Comments
 (0)