@@ -73,7 +73,6 @@ import (
73
73
"math/rand"
74
74
"os"
75
75
"path/filepath"
76
- "slices"
77
76
"strings"
78
77
"sync"
79
78
"time"
@@ -327,6 +326,10 @@ type (
327
326
minUpgrades int
328
327
maxUpgrades int
329
328
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
330
333
// predecessorFunc computes the predecessor of a particular
331
334
// release. By default, random predecessors are used, but tests
332
335
// may choose to always use the latest predecessor as well.
@@ -456,6 +459,16 @@ func MinimumSupportedVersion(v string) CustomOption {
456
459
}
457
460
}
458
461
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
+
459
472
// ClusterSettingOption adds a cluster setting option, in addition to the
460
473
// default set of options.
461
474
func ClusterSettingOption (opt ... install.ClusterSettingOption ) CustomOption {
@@ -817,20 +830,19 @@ func (t *Test) plan() (plan *TestPlan, retErr error) {
817
830
}
818
831
}()
819
832
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.
821
834
// 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 ++ {
823
836
824
837
// Pick a random deployment mode to use in this test run among the
825
838
// list of enabled deployment modes enabled for this test.
826
839
deploymentMode := t .deploymentMode ()
827
840
t .updateOptionsForDeploymentMode (deploymentMode )
828
841
829
- upgradePath , err := t .choosePreviousReleases ()
842
+ upgradePath , err := t .chooseUpgradePath ()
830
843
if err != nil {
831
844
return nil , err
832
845
}
833
- upgradePath = append (upgradePath , clusterupgrade .CurrentVersion ())
834
846
835
847
if override := os .Getenv (upgradePathOverrideEnv ); override != "" {
836
848
upgradePath , err = parseUpgradePathOverride (override )
@@ -893,14 +905,15 @@ func (t *Test) runCommandFunc(nodes option.NodeListOption, cmd string) stepFunc
893
905
}
894
906
}
895
907
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 ) {
904
917
skipVersions := t .prng .Float64 () < t .options .skipVersionProbability
905
918
isAvailable := func (v * clusterupgrade.Version ) bool {
906
919
if t .clusterArch () != vm .ArchARM64 {
@@ -952,35 +965,79 @@ func (t *Test) choosePreviousReleases() ([]*clusterupgrade.Version, error) {
952
965
return []* clusterupgrade.Version {pred , predPred }, nil
953
966
}
954
967
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
+
955
1013
currentVersion := clusterupgrade .CurrentVersion ()
956
1014
var upgradePath []* clusterupgrade.Version
957
1015
numUpgrades := t .numUpgrades ()
958
1016
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 )
961
1026
if err != nil {
962
1027
return nil , err
963
1028
}
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 ))]
970
1031
break
971
1032
}
972
-
973
- chosenPredecessor := predecessors [t .prng .Intn (len (predecessors ))]
974
- upgradePath = append (upgradePath , chosenPredecessor )
975
- currentVersion = chosenPredecessor
976
1033
}
977
1034
978
1035
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
+ }
979
1039
t .logger .Printf ("WARNING: skipping upgrades as ARM64 is only supported on %s+" , minSupportedARM64Version )
980
1040
}
981
-
982
- // The upgrade path to be returned is from oldest to newest release.
983
- slices .Reverse (upgradePath )
984
1041
return upgradePath , nil
985
1042
}
986
1043
@@ -1045,7 +1102,7 @@ func randomPredecessor(
1045
1102
)
1046
1103
}
1047
1104
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
1049
1106
// can choose any patch release in the predecessor series. It is
1050
1107
// also safe to return `predV` here if the `minSupported` version is
1051
1108
// a pre-release: we already validated that `predV`is at least
@@ -1323,6 +1380,8 @@ func assertValidTest(test *Test, fatalFunc func(...interface{})) {
1323
1380
fail := func (err error ) {
1324
1381
fatalFunc (errors .Wrap (err , "mixedversion.NewTest" ))
1325
1382
}
1383
+ minUpgrades := test .options .minUpgrades
1384
+ maxUpgrades := test .options .maxUpgrades
1326
1385
1327
1386
if test .options .useFixturesProbability > 0 && len (test .crdbNodes ) != numNodesInFixtures {
1328
1387
fail (
@@ -1333,11 +1392,11 @@ func assertValidTest(test *Test, fatalFunc func(...interface{})) {
1333
1392
)
1334
1393
}
1335
1394
1336
- if test . options . minUpgrades > test . options . maxUpgrades {
1395
+ if minUpgrades > maxUpgrades {
1337
1396
fail (
1338
1397
fmt .Errorf (
1339
1398
"invalid test options: maxUpgrades (%d) must be greater than minUpgrades (%d)" ,
1340
- test . options . maxUpgrades , test . options . minUpgrades ,
1399
+ maxUpgrades , minUpgrades ,
1341
1400
),
1342
1401
)
1343
1402
}
@@ -1399,6 +1458,42 @@ func assertValidTest(test *Test, fatalFunc func(...interface{})) {
1399
1458
SeparateProcessDeployment , minSeparateProcessNodes ,
1400
1459
))
1401
1460
}
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
+ }
1402
1497
}
1403
1498
1404
1499
// parseUpgradePathOverride parses the upgrade path override and returns it as a list
0 commit comments