SERVER-119484 Add kEnableTargetFeatures and kCommitAddedFeatures no-op phases to FCV protocol (#52032)

GitOrigin-RevId: acc64f79a875f00b50a6ed0ece4b12251147f278
This commit is contained in:
marcobizzarri-mdb 2026-05-06 12:14:31 +01:00 committed by MongoDB Bot
parent 75d8999664
commit 3750e4fefe
6 changed files with 118 additions and 18 deletions

View File

@ -25,10 +25,9 @@ assert.commandWorked(testDB.adminCommand({setFeatureCompatibilityVersion: lastLT
// Insert a document to cause an error in batch insertion
assert.commandWorked(testColl.insert([{"_id": "5"}]));
// Switch to upgrade FCV first. We have to stop this in the middle so we can get the collmods in
// _upgradeServerMetadata to finish before allowing FCV to actually change. This is a very
// unlikely race!
let upgradeFp = configureFailPoint(primary, "hangWhileUpgrading");
// Switch to upgrade FCV first. We have to stop this in the middle so we can get all upgrade
// metadata changes to finish before allowing FCV to actually change. This is a very unlikely race!
let upgradeFp = configureFailPoint(primary, "hangBeforeFinalizingFCV");
jsTestLog("Switching to upgrade FCV");
let upgradeFCVThread = new Thread(function (host) {
let conn = new Mongo(host);

View File

@ -417,10 +417,10 @@ rollbackFCVFromDowngradingOrUpgrading(latestFCV, lastLTSFCV);
// Tests the case where we roll back the FCV state from fully downgraded to downgrading (while in
// isCleaningServerMetadata state).
rollbackFCVFromDowngradedOrUpgraded(lastLTSFCV, latestFCV, "hangBeforeTransitioningToDowngraded");
rollbackFCVFromDowngradedOrUpgraded(lastLTSFCV, latestFCV, "hangBeforeFinalizingFCV");
// Tests the case where we roll back the FCV state from fully upgraded to upgrading.
rollbackFCVFromDowngradedOrUpgraded(latestFCV, lastLTSFCV, "hangWhileUpgrading");
rollbackFCVFromDowngradedOrUpgraded(latestFCV, lastLTSFCV, "hangBeforeFinalizingFCV");
// Tests the case where we roll back the FCV state from upgrading to downgrading.
rollbackFCV_FromUpgradingToDowngrading_or_FromDowngradingToUpgrading(latestFCV, lastLTSFCV);

View File

@ -283,7 +283,9 @@ public:
return ResolvedFCVTransition{it->second.transitionalVersion,
startPhase,
SetFCVPhaseEnum::kComplete,
gFeatureFlagSymmetricFCV.isEnabled()
? SetFCVPhaseEnum::kCommitAddedFeatures
: SetFCVPhaseEnum::kComplete,
changeTimestamp};
}

View File

@ -264,27 +264,78 @@ TEST_F(FeatureCompatibilityVersionTestFixture, ResolveReturnToOriginalFCVDuringC
struct FCVTestParams {
SetFCVPhaseEnum phase;
bool isCleaningServerMetadata;
boost::optional<bool> isCleaningServerMetadata;
bool symmetricFCVEnabled;
SetFCVPhaseEnum expectedStartPhase;
SetFCVPhaseEnum expectedEndPhase;
};
class SetFeatureCompatibilityVersionParamTestFixture
: public FeatureCompatibilityVersionTestFixture,
public testing::WithParamInterface<FCVTestParams> {};
INSTANTIATE_TEST_SUITE_P(UpgradingFromDifferentStartingPhases,
// Without symmetric FCV, the phase stored in the FCV document is ignored and the transition always
// restarts from kStart with a freshly generated timestamp (legacy "restart from beginning"
// behavior).
INSTANTIATE_TEST_SUITE_P(UpgradingFromDifferentStartingPhasesWithoutSymmetricFCV,
SetFeatureCompatibilityVersionParamTestFixture,
testing::ValuesIn({
FCVTestParams{SetFCVPhaseEnum::kStart, false},
FCVTestParams{SetFCVPhaseEnum::kPrepare, false},
FCVTestParams{SetFCVPhaseEnum::kComplete, true},
FCVTestParams{SetFCVPhaseEnum::kStart,
false,
false,
SetFCVPhaseEnum::kStart,
SetFCVPhaseEnum::kComplete},
FCVTestParams{SetFCVPhaseEnum::kPrepare,
false,
false,
SetFCVPhaseEnum::kStart,
SetFCVPhaseEnum::kComplete},
FCVTestParams{SetFCVPhaseEnum::kComplete,
true,
false,
SetFCVPhaseEnum::kStart,
SetFCVPhaseEnum::kComplete},
}));
// With symmetric FCV, the phase stored in the FCV document is used as the start phase, so the
// transition resumes from where it was interrupted and reuses the existing change timestamp.
INSTANTIATE_TEST_SUITE_P(UpgradingFromDifferentStartingPhasesWithSymmetricFCV,
SetFeatureCompatibilityVersionParamTestFixture,
testing::ValuesIn({
FCVTestParams{SetFCVPhaseEnum::kStart,
false,
true,
SetFCVPhaseEnum::kStart,
SetFCVPhaseEnum::kCommitAddedFeatures},
FCVTestParams{SetFCVPhaseEnum::kPrepare,
false,
true,
SetFCVPhaseEnum::kPrepare,
SetFCVPhaseEnum::kCommitAddedFeatures},
FCVTestParams{SetFCVPhaseEnum::kComplete,
true,
true,
SetFCVPhaseEnum::kComplete,
SetFCVPhaseEnum::kCommitAddedFeatures},
FCVTestParams{SetFCVPhaseEnum::kEnableTargetFeatures,
boost::none,
true,
SetFCVPhaseEnum::kEnableTargetFeatures,
SetFCVPhaseEnum::kCommitAddedFeatures},
FCVTestParams{SetFCVPhaseEnum::kCommitAddedFeatures,
boost::none,
true,
SetFCVPhaseEnum::kCommitAddedFeatures,
SetFCVPhaseEnum::kCommitAddedFeatures},
}));
TEST_P(SetFeatureCompatibilityVersionParamTestFixture, ResolveResumeInterruptedUpgrade) {
RAIIServerParameterControllerForTest symmetricFCV{"featureFlagSymmetricFCV", true};
const auto& params = GetParam();
RAIIServerParameterControllerForTest symmetricFCV{"featureFlagSymmetricFCV",
params.symmetricFCVEnabled};
const Timestamp lastChangeTimestamp =
VectorClockMutable::get(operationContext())->tickClusterTime(2).asTimestamp();
serverGlobalParams.clusterRole = {ClusterRole::ShardServer, ClusterRole::ConfigServer};
const auto& params = GetParam();
doStartupFCVSequence(multiversion::GenericFCV::kLastLTS);
FeatureCompatibilityVersion::updateFeatureCompatibilityVersionDocument(
@ -299,9 +350,13 @@ TEST_P(SetFeatureCompatibilityVersionParamTestFixture, ResolveResumeInterruptedU
operationContext(), request, multiversion::GenericFCV::kUpgradingFromLastLTSToLatest);
ASSERT_EQ(result.transitionalVersion, multiversion::GenericFCV::kUpgradingFromLastLTSToLatest);
ASSERT_EQ(result.startPhase, params.phase);
ASSERT_EQ(result.endPhase, SetFCVPhaseEnum::kComplete);
ASSERT_EQ(result.changeTimestamp, lastChangeTimestamp);
ASSERT_EQ(result.startPhase, params.expectedStartPhase);
ASSERT_EQ(result.endPhase, params.expectedEndPhase);
if (params.symmetricFCVEnabled) {
ASSERT_EQ(result.changeTimestamp, lastChangeTimestamp);
} else {
ASSERT_GT(result.changeTimestamp, lastChangeTimestamp);
}
}

View File

@ -132,6 +132,7 @@ MONGO_FAIL_POINT_DEFINE(failBeforeUpdatingFcvDoc);
MONGO_FAIL_POINT_DEFINE(failTransitionDuringIsCleaningServerMetadata);
MONGO_FAIL_POINT_DEFINE(hangBeforeTransitioningToDowngraded);
MONGO_FAIL_POINT_DEFINE(hangTransitionBeforeIsCleaningServerMetadata);
MONGO_FAIL_POINT_DEFINE(hangBeforeFinalizingFCV);
MONGO_FAIL_POINT_DEFINE(failAfterReachingTransitioningState);
MONGO_FAIL_POINT_DEFINE(hangAtSetFCVStart);
MONGO_FAIL_POINT_DEFINE(failAfterSendingShardsToDowngradingOrUpgrading);
@ -583,7 +584,6 @@ public:
if (resolvedTransition.shouldRun(SetFCVPhaseEnum::kComplete)) {
invariant(serverGlobalParams.featureCompatibility.acquireFCVSnapshot()
.isUpgradingOrDowngrading());
const bool isDowngradeTransition = requestedVersion < actualVersion;
if (isDowngradeTransition ||
repl::feature_flags::gFeatureFlagUpgradingToDowngrading.isEnabled()) {
@ -619,7 +619,45 @@ public:
} else {
_runUpgrade(opCtx, request, changeTimestamp);
}
}
// ---------- kEnableTargetFeatures phase ----------
if (resolvedTransition.shouldRun(SetFCVPhaseEnum::kEnableTargetFeatures)) {
FeatureCompatibilityVersion::updateFeatureCompatibilityVersionDocument(
opCtx,
resolvedTransition.transitionalVersion,
SetFCVPhaseEnum::kEnableTargetFeatures,
changeTimestamp,
boost::none /* setIsCleaningServerMetadata */);
if (role && role->has(ClusterRole::ConfigServer)) {
_sendEnterSetFCVPhaseRequestToShard(
opCtx, request, changeTimestamp, SetFCVPhaseEnum::kEnableTargetFeatures);
}
}
// ---------- kCommitAddedFeatures phase ----------
if (resolvedTransition.shouldRun(SetFCVPhaseEnum::kCommitAddedFeatures)) {
FeatureCompatibilityVersion::updateFeatureCompatibilityVersionDocument(
opCtx,
resolvedTransition.transitionalVersion,
SetFCVPhaseEnum::kCommitAddedFeatures,
changeTimestamp,
boost::none /* setIsCleaningServerMetadata */);
if (role && role->has(ClusterRole::ConfigServer)) {
_sendEnterSetFCVPhaseRequestToShard(
opCtx, request, changeTimestamp, SetFCVPhaseEnum::kCommitAddedFeatures);
}
}
// With symmetric FCV the last protocol phase is kCommitAddedFeatures; without it the last
// meaningful phase is kComplete (kEnableTargetFeatures/kCommitAddedFeatures are no-ops).
// Either way this finalization must execute after all feature-specific work is done.
if (gFeatureFlagSymmetricFCV.isEnabled()
? resolvedTransition.shouldRun(SetFCVPhaseEnum::kCommitAddedFeatures)
: resolvedTransition.shouldRun(SetFCVPhaseEnum::kComplete)) {
hangBeforeFinalizingFCV.pauseWhileSet(opCtx);
{
// Complete transition by updating the local FCV document to the fully upgraded or
// downgraded requestedVersion.

View File

@ -76,6 +76,8 @@ structs:
type: SetFCVPhase
optional: true
# Note: the order of the phases in the enum below matters, as the code relies on the order to determine whether a phase
# should be run or not based on the current phase stored in the FCV document.
enums:
SetFCVPhase:
description: "Enum that defines the phases of the 3-phase setFCV protocol."
@ -89,6 +91,10 @@ enums:
# Tells shards to execute possibly metadata-changing upgrade/downgrade actions and
# transition to the fully upgraded/downgraded FCV.
kComplete: "complete"
# Tells shards to enable target-version features.
kEnableTargetFeatures: "enable_target_features"
# Tells shards to commit features added in the target version.
kCommitAddedFeatures: "commit_added_features"
feature_flags:
featureFlagSymmetricFCV: