@@ -10,8 +10,10 @@ import (
10
10
"fmt"
11
11
"io"
12
12
"math/rand"
13
+ "reflect"
13
14
"strconv"
14
15
"testing"
16
+ "testing/quick"
15
17
"time"
16
18
17
19
"github.com/cockroachdb/cockroach/pkg/cmd/roachtest/option"
@@ -21,6 +23,7 @@ import (
21
23
"github.com/cockroachdb/cockroach/pkg/roachprod/logger"
22
24
"github.com/cockroachdb/cockroach/pkg/roachprod/vm"
23
25
"github.com/cockroachdb/cockroach/pkg/testutils/datapathutils"
26
+ "github.com/cockroachdb/cockroach/pkg/testutils/release"
24
27
"github.com/cockroachdb/cockroach/pkg/util/randutil"
25
28
"github.com/cockroachdb/cockroach/pkg/util/retry"
26
29
"github.com/cockroachdb/datadriven"
@@ -1041,3 +1044,162 @@ func Test_SeparateProcessUsesLatestPred(t *testing.T) {
1041
1044
require .Equal (t , latestVersion , version )
1042
1045
}
1043
1046
}
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