0
0
mirror of https://github.com/mongodb/mongo.git synced 2024-11-27 15:06:34 +01:00
mongodb/jstests/libs/feature_flag_util.js
Erin McNulty 8e04853009 SERVER-94626 Enable sharding core passthroughs with gRPC (#26928)
GitOrigin-RevId: 0b3e76ce08cb028be1c5551f697bea4bfb42e056
2024-09-27 21:12:02 +00:00

187 lines
8.4 KiB
JavaScript

import {FixtureHelpers} from "jstests/libs/fixture_helpers.js";
/**
* Utilities for feature flags.
*/
export var FeatureFlagUtil = (function() {
// A JS attempt at an enum.
const FlagStatus = {
kEnabled: 'kEnabled',
kDisabled: 'kDisabled',
kNotFound: 'kNotFound',
};
/**
* @param 'featureFlag' - the name of the flag you want to check, but *without* the
* 'featureFlag' prefix. For example, just "Toaster" instead of "featureFlagToaster."
*
* @param 'ignoreFcv' - If true, return whether or not the given feature flag is enabled,
* regardless of the current FCV version. This is used when a feature flag needs to be
* enabled in downgraded FCV versions. If 'ignoreFCV' is false or null, we only return true
* if the flag is enabled and this FCV version is greater or equal to the required version
* for the flag.
*
* @returns one of the 'FlagStatus' values indicating whether the flag is enabled, disabled, or
* not found. A flag may be not found because it was recently deleted or because the test is
* running on an older mongod version for example.
*/
function getStatus(db, featureFlag, user, ignoreFCV) {
// In order to get an accurate answer for whether a feature flag is enabled, we need to ask
// a mongod. If db represents a connection to mongos, or some other configuration, we need
// to obtain the correct connection to a mongod.
let conn = null;
const setConn = (db) => {
if (!FixtureHelpers.isMongos(db)) {
conn = db;
return;
}
// For sharded cluster get a connection to the first replicaset through a Mongo
// object. We may fail to connect if we are in a stepdown/terminate passthrough, so
// retry on retryable errors. After the connection is established, runCommand overrides
// should guarantee that subsequent operations on the connection are retried in the
// event of network errors in suites where that possibility exists.
retryOnRetryableError(() => {
conn = new Mongo(
FixtureHelpers.getAllReplicas(db)[0].getURL(), undefined, {gRPC: false});
});
};
try {
setConn(db);
} catch (err) {
// Some db-like objects (e.g. ShardingTest.shard0) aren't supported by FixtureHelpers,
// but we can replace it with an object that should work and try again.
if (typeof db.getDB === typeof Function) {
setConn(db.getDB(db.defaultDB));
} else {
// Some db-like objects (e.g ShardedClusterFixture) have a getSiblingDB method
// instead of getDB, use that here to avoid an undefined error.
setConn(db.getSiblingDB(db.getMongo().defaultDB));
}
}
if (user) {
conn.auth(user.username, user.password);
}
const fcvDoc = assert.commandWorked(
conn.adminCommand({getParameter: 1, featureCompatibilityVersion: 1}));
assert(fcvDoc.hasOwnProperty("featureCompatibilityVersion"), fcvDoc);
assert(!featureFlag.startsWith("featureFlag"),
`unexpected prefix in feature flag name: "${featureFlag}". Use "${
featureFlag.replace(/^featureFlag/, '')}" instead.`);
const fullFlagName = `featureFlag${featureFlag}`;
const flagDoc = conn.adminCommand({getParameter: 1, [fullFlagName]: 1});
if (!flagDoc.ok || !flagDoc.hasOwnProperty(fullFlagName)) {
// Feature flag not found.
if (!flagDoc.ok) {
assert.eq(flagDoc.errmsg, "no option found to get");
}
return FlagStatus.kNotFound;
}
const flagIsEnabled = flagDoc[fullFlagName].value;
const flagVersionIsValid =
MongoRunner.compareBinVersions(fcvDoc.featureCompatibilityVersion.version,
flagDoc[fullFlagName].version) >= 0;
const flagShouldBeFCVGated = flagDoc[fullFlagName].shouldBeFCVGated;
if (flagIsEnabled && (!flagShouldBeFCVGated || ignoreFCV || flagVersionIsValid)) {
return FlagStatus.kEnabled;
}
return FlagStatus.kDisabled;
}
/**
* @param 'featureFlag' - the name of the flag you want to check, but *without* the
* 'featureFlag' prefix. For example, just "Toaster" instead of "featureFlagToaster."
*
* Wrapper around 'getStatus' - see that function for more details on the arguemnts.
*
* This wrapper checks for 'kEnabled' but raises an error for 'kNotFound' - if the flag is not
* known. This can be useful if you want to gate an entire test on a feature flag like so:
* if (!FeatureFlagUtil.isEnabled(db, "myFlag")) {
* jsTestLog("Skipping test because my flag is not enabled");
* return;
* }
*
* The advantage of this throwing API is that such a test will start complaining in evergreen
* when you delete the feature flag, rather than passing by not actually running any assertions.
*/
function isEnabled(db, featureFlag, user, ignoreFCV) {
let status = getStatus(db, featureFlag, user, ignoreFCV);
assert(
status != FlagStatus.kNotFound,
`You asked about a feature flag ${featureFlag} which wasn't present. If this is a ` +
"multiversion test and you want the coverage despite the flag not existing on an " +
"older version, consider using 'isPresentAndEnabled()' instead of 'isEnabled()'");
return status == FlagStatus.kEnabled;
}
/**
*
* Wrapper around 'getStatus' - see that function for more details on the arguemnts.
*
* @param 'featureFlag' - the name of the flag you want to check, but *without* the
* 'featureFlag' prefix. For example, just "Toaster" instead of "featureFlagToaster."
*
* @returns true if the provided feature flag is known and also enabled. Returns false otherwise
* (either disabled or not known), unlike 'isEnabled()' which would raise an error if the
* flag is not found.
*
* This can be useful if you'd like to have your test conditionally add extra assertions, or
* conditionally change the assertion being made, like so:
*
* // TODO SERVER-XYZ remove 'featureFlagMyFlag'.
* if (FeatureFlagUtil.isPresentAndEnabled(db, "MyFlag")) {
* // I expect to see some new return value.
* } else {
* // I expect *not* to see some return value.
* }
*
* Note that this API should always be used with an accompanying TODO like the one in the
* example above. This is because it is all too easy to have a test like so which will silently
* stop testing anything if we remove the feature flag without updating the test:
*
* if (FeatureFlagUtil.isPresentAndEnabled(db, "MyFlag")) {
* // Assert on something new.
* }
* // No else clause.
*
* That code is dangerous because we may forget to delete it when "featureFlagMyFlag" is
* removed, and the test would keep passing but stop testing.
*/
function isPresentAndEnabled(db, featureFlag, user, ignoreFCV) {
return getStatus(db, featureFlag, user, ignoreFCV) == FlagStatus.kEnabled;
}
/**
*
* Wrapper around 'getStatus' - see that function for more details on the arguemnts.
*
* @param 'featureFlag' - the name of the flag you want to check, but *without* the
* 'featureFlag' prefix. For example, just "Toaster" instead of "featureFlagToaster."
*
* @returns true if the provided feature flag is known and disabled. Returns false otherwise
* (either enabled or not known).
*
* This can be helpful if you want to check that a feature flag has been properly initialized
* in your project.
*
* assert(FeatureFlagUtil.isPresentAndDisabled(db, "MyFlag"))
*/
function isPresentAndDisabled(db, featureFlag, user, ignoreFCV) {
return getStatus(db, featureFlag, user, ignoreFCV) == FlagStatus.kDisabled;
}
return {
FlagStatus: FlagStatus,
isEnabled: isEnabled,
isPresentAndEnabled: isPresentAndEnabled,
isPresentAndDisabled: isPresentAndDisabled,
getStatus: getStatus,
};
})();