mongo/jstests/libs/read_write_concern_defaults...

187 lines
8.2 KiB
JavaScript

export var ReadWriteConcernDefaultsPropagation = (function () {
const kDefaultReadConcernField = "defaultReadConcern";
const kDefaultWriteConcernField = "defaultWriteConcern";
const kUpdateOpTimeField = "updateOpTime";
const kUpdateWallClockTimeField = "updateWallClockTime";
const kLocalUpdateWallClockTimeField = "localUpdateWallClockTime";
const kDefaultRWCFields = [kDefaultReadConcernField, kDefaultWriteConcernField];
const kExtraSetFields = [kUpdateOpTimeField, kUpdateWallClockTimeField];
const kExtraLocalFields = [kLocalUpdateWallClockTimeField];
const kExtraFields = [...kExtraSetFields, ...kExtraLocalFields];
const kSetFields = [...kDefaultRWCFields, ...kExtraSetFields];
const timeoutSecs = 2 * 60;
const intervalSecs = 5;
// Check that setting the defaults on setConn propagates correctly across checkConns.
function setDefaultsAndVerifyPropagation(setConn, checkConns, inMemory) {
// Get the current defaults from setConn.
let initialSetConnDefaults = assert.commandWorked(setConn.adminCommand({getDefaultRWConcern: 1}));
// Ensure that all checkConns agree with this. Use a loop in case the initial defaults were
// recently set and have not yet propagated to all nodes.
let checkConnsDefaults = [];
let initialCheckConnsDefaults = [];
assert.soon(
() => {
initialCheckConnsDefaults = checkConns.map((conn) =>
assert.commandWorked(conn.adminCommand({getDefaultRWConcern: 1})),
);
return initialCheckConnsDefaults.every((checkConnDefaults) =>
kSetFields.every((field) => friendlyEqual(checkConnDefaults[field], initialSetConnDefaults[field])),
);
},
() =>
"expected initial defaults to be present on all nodes within" +
timeoutSecs +
" secs. Expected defaults: " +
tojson(initialSetConnDefaults) +
", checkConns: " +
tojson(checkConns) +
", current state: " +
tojson(initialCheckConnsDefaults),
timeoutSecs * 1000,
intervalSecs * 1000,
{runHangAnalyzer: false},
);
// Set new defaults on setConn.
let newDefaults = {
defaultReadConcern: {level: "majority"},
defaultWriteConcern: {w: 2, wtimeout: 0},
};
// If these happen to match what's already there, adjust them.
if (
initialSetConnDefaults.defaultReadConcern &&
friendlyEqual(initialSetConnDefaults.defaultReadConcern, newDefaults.defaultReadConcern)
) {
newDefaults.defaultReadConcern.level = "local";
}
if (
initialSetConnDefaults.defaultWriteConcern &&
friendlyEqual(initialSetConnDefaults.defaultWriteConcern, newDefaults.defaultWriteConcern)
) {
newDefaults.defaultWriteConcern.w++;
}
let cmd = {setDefaultRWConcern: 1};
Object.extend(cmd, newDefaults);
cmd.writeConcern = {
w: 1,
}; // Prevent any existing default WC (which may be unsatisfiable) from being applied.
let newDefaultsRes = assert.commandWorked(setConn.adminCommand(cmd));
kDefaultRWCFields.forEach((field) =>
assert.eq(newDefaultsRes[field], newDefaults[field], field + " was not set correctly"),
);
assert.hasFields(newDefaultsRes, kExtraFields, "missing field in result of setDefaultRWConcern");
// Check that updateOpTime has increased. Since everything is running on one host, we can
// also check that each of kUpdateOpTime and localUpdateWallClockTime have increased.
kExtraFields.forEach((field) => {
if (field in initialSetConnDefaults) {
assert.gt(
newDefaultsRes[field],
initialSetConnDefaults[field],
field + " did not increase after setting new defaults",
);
}
});
// Ensure that all checkConns agree with this.
assert.soon(
function () {
// Get the defaults from all the connections.
checkConnsDefaults = checkConns.map((conn) =>
assert.commandWorked(conn.adminCommand({getDefaultRWConcern: 1, inMemory})),
);
// Check if they all match the recently-set values.
for (let connDefault of checkConnsDefaults) {
if (inMemory) {
assert.eq(true, connDefault.inMemory, tojson(connDefault));
} else {
assert.eq(undefined, connDefault.inMemory, tojson(connDefault));
}
for (let field of kSetFields) {
if (!friendlyEqual(connDefault[field], newDefaultsRes[field])) {
return false;
}
}
}
return true;
},
() =>
"updated defaults failed to propagate to all nodes within " +
timeoutSecs +
" secs. Expected defaults: " +
tojson(newDefaults) +
", checkConns: " +
tojson(checkConns) +
", current state: " +
tojson(checkConnsDefaults) +
", inMemory: " +
inMemory,
timeoutSecs * 1000,
intervalSecs * 1000,
{runHangAnalyzer: false},
);
}
/**
* Tests propagation of RWC defaults across an environment. Updated RWC defaults are set using
* setConn, and then each connection in the checkConns array is checked to ensure that it
* becomes aware of the new defaults (within the acceptable window of 2 minutes).
*/
let runTests = function (setConn, checkConns, inMemory) {
// Since these connections are on a brand new replset/cluster, this checks the propagation
// of the initial setting of defaults.
setDefaultsAndVerifyPropagation(setConn, checkConns, inMemory);
// Do it again to check that updating the defaults also propagates correctly.
setDefaultsAndVerifyPropagation(setConn, checkConns, inMemory);
};
/**
* Asserts eventually all given nodes have no default RWC in their in-memory cache.
*/
function verifyPropgationOfNoDefaults(checkConns) {
assert.soon(
() =>
checkConns.every((checkConn) => {
const defaultsRes = assert.commandWorked(
checkConn.adminCommand({getDefaultRWConcern: 1, inMemory: true}),
);
// Note localUpdateWallClockTime is generated by the in-memory cache, so it will be
// present even if there are no defaults.
const unexpectedFields = kDefaultRWCFields.concat(kExtraSetFields).filter((field) => {
return field !== kDefaultWriteConcernField && field !== kDefaultReadConcernField;
});
return unexpectedFields.every((field) => !defaultsRes.hasOwnProperty(field));
}),
"deleted/dropped defaults failed to propagate to all nodes within " +
timeoutSecs +
" secs. checkConns: " +
tojson(checkConns),
timeoutSecs * 1000,
intervalSecs * 1000,
{runHangAnalyzer: false},
);
}
/**
* Tests that when the RWC defaults document is removed, either through a delete or drop of
* config.settings, the RWC defaults cache is invalidated.
*/
let runDropAndDeleteTests = function (mainConn, checkConns) {
// Set the defaults to some value other than the implicit server defaults. Then remove the
// defaults document and verify the cache is invalidated on all nodes.
setDefaultsAndVerifyPropagation(mainConn, checkConns);
assert.commandWorked(mainConn.getDB("config").settings.remove({_id: "ReadWriteConcernDefaults"}));
verifyPropgationOfNoDefaults(checkConns);
};
return {runTests, runDropAndDeleteTests};
})();