Files
mongo/jstests/sharding/read_write_concern_defaults_commands_api.js

367 lines
14 KiB
JavaScript

// Tests the basic API of the getDefaultRWConcern and setDefaultRWConcern commands and their
// associated persisted state against different topologies.
(function() {
"use strict";
// Asserts a set/get default RWC command response or persisted document contains the expected
// fields. Assumes a default read or write concern has been set previously and the response was not
// generated by a getDefaultRWConcern command with inMemory=true.
function verifyFields(res, {expectRC, expectWC, isPersistedDocument}) {
// These fields are always set once a read or write concern has been set at least once.
let expectedFields = ["updateOpTime", "updateWallClockTime", "localUpdateWallClockTime"];
let unexpectedFields = ["inMemory"];
if (expectRC) {
expectedFields.push("defaultReadConcern");
} else {
unexpectedFields.push("defaultReadConcern");
}
if (expectWC) {
expectedFields.push("defaultWriteConcern");
} else {
unexpectedFields.push("defaultWriteConcern");
}
// localUpdateWallClockTime is generated by the in-memory cache and is not stored in the
// persisted document.
if (isPersistedDocument) {
expectedFields = expectedFields.filter(field => field !== "localUpdateWallClockTime");
unexpectedFields.push("localUpdateWallClockTime");
}
assert.hasFields(res, expectedFields);
unexpectedFields.forEach(field => {
assert(!res.hasOwnProperty(field),
`response unexpectedly had field '${field}', res: ${tojson(res)}`);
});
}
function verifyDefaultRWCommandsInvalidInput(conn) {
//
// Test invalid parameters for getDefaultRWConcern.
//
// Invalid inMemory.
assert.commandFailedWithCode(conn.adminCommand({getDefaultRWConcern: 1, inMemory: "true"}),
ErrorCodes.TypeMismatch);
//
// Test invalid parameters for setDefaultRWConcern.
//
// Must include either wc or rc.
assert.commandFailedWithCode(conn.adminCommand({setDefaultRWConcern: 1}), ErrorCodes.BadValue);
// Invalid write concern.
assert.commandFailedWithCode(
conn.adminCommand({setDefaultRWConcern: 1, defaultWriteConcern: 1}),
ErrorCodes.TypeMismatch);
// w less than 1.
assert.commandFailedWithCode(conn.adminCommand({
setDefaultRWConcern: 1,
defaultWriteConcern: {w: 0},
}),
ErrorCodes.BadValue);
// Invalid read concern.
assert.commandFailedWithCode(conn.adminCommand({setDefaultRWConcern: 1, defaultReadConcern: 1}),
ErrorCodes.TypeMismatch);
// Non-existent level.
assert.commandFailedWithCode(
conn.adminCommand({setDefaultRWConcern: 1, defaultReadConcern: {level: "dummy"}}),
ErrorCodes.FailedToParse);
// Unsupported level.
assert.commandFailedWithCode(
conn.adminCommand({setDefaultRWConcern: 1, defaultReadConcern: {level: "linearizable"}}),
ErrorCodes.BadValue);
assert.commandFailedWithCode(
conn.adminCommand({setDefaultRWConcern: 1, defaultReadConcern: {level: "snapshot"}}),
ErrorCodes.BadValue);
// Fields other than level.
assert.commandFailedWithCode(conn.adminCommand({
setDefaultRWConcern: 1,
defaultReadConcern: {level: "local", afterClusterTime: Timestamp(50, 1)}
}),
ErrorCodes.BadValue);
assert.commandFailedWithCode(conn.adminCommand({
setDefaultRWConcern: 1,
defaultReadConcern: {level: "snapshot", atClusterTime: Timestamp(50, 1)}
}),
ErrorCodes.BadValue);
assert.commandFailedWithCode(conn.adminCommand({
setDefaultRWConcern: 1,
defaultReadConcern: {level: "local", afterOpTime: {ts: Timestamp(50, 1), t: 1}}
}),
ErrorCodes.BadValue);
}
// Sets a default read and write concern.
function setDefaultRWConcern(conn) {
assert.commandWorked(conn.adminCommand({
setDefaultRWConcern: 1,
defaultReadConcern: {level: "local"},
defaultWriteConcern: {w: 1}
}));
}
// Unsets the default read and write concerns.
function unsetDefaultRWConcern(conn) {
assert.commandWorked(conn.adminCommand(
{setDefaultRWConcern: 1, defaultReadConcern: {}, defaultWriteConcern: {}}));
}
// Verifies the default responses for the default RWC commands and the default persisted state.
function verifyDefaultState(conn) {
const res = assert.commandWorked(conn.adminCommand({getDefaultRWConcern: 1}));
const inMemoryRes =
assert.commandWorked(conn.adminCommand({getDefaultRWConcern: 1, inMemory: true}));
// localUpdateWallClockTime is set when a node refreshes its defaults, even if none are found.
const expectedFields = ["localUpdateWallClockTime"];
expectedFields.forEach(field => {
assert(res.hasOwnProperty(field),
`response did not have field '${field}', res: ${tojson(res)}`);
assert(inMemoryRes.hasOwnProperty(field),
`inMemory=true response did not have field '${field}', res: ${tojson(inMemoryRes)}`);
});
assert.eq(inMemoryRes.inMemory, true, tojson(inMemoryRes));
// No other fields should be returned if neither a default read nor write concern has been set.
const unexpectedFields =
["defaultReadConcern", "defaultWriteConcern", "updateOpTime", "updateWallClockTime"];
unexpectedFields.forEach(field => {
assert(!res.hasOwnProperty(field),
`response unexpectedly had field '${field}', res: ${tojson(res)}`);
assert(!inMemoryRes.hasOwnProperty(field),
`inMemory=true response unexpectedly had field '${field}', res: ${
tojson(inMemoryRes)}`);
});
assert.eq(undefined, res.inMemory, tojson(res));
// There should be no default RWC document.
assert.eq(null, getPersistedRWCDocument(conn));
}
function verifyDefaultRWCommandsValidInput(conn) {
//
// Test parameters for getDefaultRWConcern.
//
// No parameters is allowed.
assert.commandWorked(conn.adminCommand({getDefaultRWConcern: 1}));
// inMemory parameter is allowed.
assert.commandWorked(conn.adminCommand({getDefaultRWConcern: 1, inMemory: true}));
assert.commandWorked(conn.adminCommand({getDefaultRWConcern: 1, inMemory: false}));
//
// Test parameters for setDefaultRWConcern.
//
// Setting only rc is allowed.
assert.commandWorked(
conn.adminCommand({setDefaultRWConcern: 1, defaultReadConcern: {level: "local"}}));
assert.commandWorked(
conn.adminCommand({setDefaultRWConcern: 1, defaultReadConcern: {level: "majority"}}));
// Setting only wc is allowed.
assert.commandWorked(conn.adminCommand({setDefaultRWConcern: 1, defaultWriteConcern: {w: 1}}));
assert.commandWorked(
conn.adminCommand({setDefaultRWConcern: 1, defaultWriteConcern: {w: 1, j: false}}));
assert.commandWorked(
conn.adminCommand({setDefaultRWConcern: 1, defaultWriteConcern: {w: "majority"}}));
// Setting both wc and rc is allowed.
assert.commandWorked(conn.adminCommand({
setDefaultRWConcern: 1,
defaultWriteConcern: {w: 1},
defaultReadConcern: {level: "local"}
}));
// Empty write concern is allowed.
assert.commandWorked(conn.adminCommand({setDefaultRWConcern: 1, defaultWriteConcern: {}}));
// Empty read concern is allowed.
assert.commandWorked(conn.adminCommand({setDefaultRWConcern: 1, defaultReadConcern: {}}));
}
// Verifies the responses from successful rwc commands and the persisted state after they complete
// have the expected format.
function verifyDefaultRWCommandsOnSuccess(conn) {
//
// Test responses for getDefaultRWConcern.
//
// When neither read nor write concern is set.
unsetDefaultRWConcern(conn);
verifyFields(assert.commandWorked(conn.adminCommand({getDefaultRWConcern: 1})),
{expectRC: false, expectWC: false});
// When only read concern is set.
assert.commandWorked(conn.adminCommand(
{setDefaultRWConcern: 1, defaultReadConcern: {level: "local"}, defaultWriteConcern: {}}));
verifyFields(assert.commandWorked(conn.adminCommand({getDefaultRWConcern: 1})),
{expectRC: true, expectWC: false});
// When only write concern is set.
assert.commandWorked(conn.adminCommand(
{setDefaultRWConcern: 1, defaultReadConcern: {}, defaultWriteConcern: {w: 1}}));
verifyFields(assert.commandWorked(conn.adminCommand({getDefaultRWConcern: 1})),
{expectRC: false, expectWC: true});
// When both read and write concern are set.
assert.commandWorked(conn.adminCommand({
setDefaultRWConcern: 1,
defaultReadConcern: {level: "local"},
defaultWriteConcern: {w: 1}
}));
verifyFields(assert.commandWorked(conn.adminCommand({getDefaultRWConcern: 1})),
{expectRC: true, expectWC: true});
// An inMemory response should contain inMemory=true.
const inMemoryRes =
assert.commandWorked(conn.adminCommand({getDefaultRWConcern: 1, inMemory: true}));
assert.eq(inMemoryRes.inMemory, true, tojson(inMemoryRes));
//
// Test responses for setDefaultRWConcern and persisted state after.
//
// When unsetting both read and write concern.
setDefaultRWConcern(conn);
verifyFields(assert.commandWorked(conn.adminCommand(
{setDefaultRWConcern: 1, defaultReadConcern: {}, defaultWriteConcern: {}})),
{expectRC: false, expectWC: false});
verifyFields(getPersistedRWCDocument(conn),
{expectRC: false, expectWC: false, isPersistedDocument: true});
// When unsetting only read concern.
setDefaultRWConcern(conn);
verifyFields(
assert.commandWorked(conn.adminCommand({setDefaultRWConcern: 1, defaultReadConcern: {}})),
{expectRC: false, expectWC: true});
verifyFields(getPersistedRWCDocument(conn),
{expectRC: false, expectWC: true, isPersistedDocument: true});
// When unsetting only write concern.
setDefaultRWConcern(conn);
verifyFields(
assert.commandWorked(conn.adminCommand({setDefaultRWConcern: 1, defaultWriteConcern: {}})),
{expectRC: true, expectWC: false});
verifyFields(getPersistedRWCDocument(conn),
{expectRC: true, expectWC: false, isPersistedDocument: true});
// When setting only write concern.
unsetDefaultRWConcern(conn);
verifyFields(assert.commandWorked(
conn.adminCommand({setDefaultRWConcern: 1, defaultWriteConcern: {w: 1}})),
{expectRC: false, expectWC: true});
verifyFields(getPersistedRWCDocument(conn),
{expectRC: false, expectWC: true, isPersistedDocument: true});
// When setting only read concern.
unsetDefaultRWConcern(conn);
verifyFields(assert.commandWorked(conn.adminCommand(
{setDefaultRWConcern: 1, defaultReadConcern: {level: "local"}})),
{expectRC: true, expectWC: false});
verifyFields(getPersistedRWCDocument(conn),
{expectRC: true, expectWC: false, isPersistedDocument: true});
// When setting both read and write concern.
unsetDefaultRWConcern(conn);
verifyFields(assert.commandWorked(conn.adminCommand({
setDefaultRWConcern: 1,
defaultReadConcern: {level: "local"},
defaultWriteConcern: {w: 1}
})),
{expectRC: true, expectWC: true});
verifyFields(getPersistedRWCDocument(conn),
{expectRC: true, expectWC: true, isPersistedDocument: true});
}
function getPersistedRWCDocument(conn) {
return conn.getDB("config").settings.findOne({_id: "ReadWriteConcernDefaults"});
}
// Verifies the error code returned by connections to nodes that do not support the get/set default
// rw concern commands.
function verifyDefaultRWCommandsFailWithCode(conn, {failureCode}) {
assert.commandFailedWithCode(conn.adminCommand({getDefaultRWConcern: 1}), failureCode);
assert.commandFailedWithCode(
conn.adminCommand({setDefaultRWConcern: 1, defaultReadConcern: {level: "local"}}),
failureCode);
}
jsTestLog("Testing standalone mongod...");
{
const standalone = MongoRunner.runMongod();
// Standalone node fails.
verifyDefaultRWCommandsFailWithCode(standalone, {failureCode: 51300});
MongoRunner.stopMongod(standalone);
}
jsTestLog("Testing standalone replica set...");
{
const rst = new ReplSetTest({nodes: 2});
rst.startSet();
rst.initiate();
// Primary succeeds.
verifyDefaultState(rst.getPrimary());
verifyDefaultRWCommandsValidInput(rst.getPrimary());
verifyDefaultRWCommandsInvalidInput(rst.getPrimary());
verifyDefaultRWCommandsOnSuccess(rst.getPrimary());
// Secondary can run getDefaultRWConcern, but not setDefaultRWConcern.
assert.commandWorked(rst.getSecondary().adminCommand({getDefaultRWConcern: 1}));
assert.commandFailedWithCode(
rst.getSecondary().adminCommand(
{setDefaultRWConcern: 1, defaultReadConcern: {level: "local"}}),
ErrorCodes.NotWritablePrimary);
rst.stopSet();
}
jsTestLog("Testing sharded cluster...");
{
const st = new ShardingTest({shards: 1, rs: {nodes: 2}});
// Mongos succeeds.
verifyDefaultState(st.s);
verifyDefaultRWCommandsValidInput(st.s);
verifyDefaultRWCommandsInvalidInput(st.s);
verifyDefaultRWCommandsOnSuccess(st.s);
// Shard node fails.
verifyDefaultRWCommandsFailWithCode(st.rs0.getPrimary(), {failureCode: 51301});
assert.commandFailedWithCode(st.rs0.getSecondary().adminCommand({getDefaultRWConcern: 1}),
51301);
// Secondaries fail setDefaultRWConcern before executing the command.
assert.commandFailedWithCode(
st.rs0.getSecondary().adminCommand(
{setDefaultRWConcern: 1, defaultReadConcern: {level: "local"}}),
ErrorCodes.NotWritablePrimary);
// Config server primary succeeds.
verifyDefaultRWCommandsValidInput(st.configRS.getPrimary());
verifyDefaultRWCommandsInvalidInput(st.configRS.getPrimary());
verifyDefaultRWCommandsOnSuccess(st.configRS.getPrimary());
// Config server secondary can run getDefaultRWConcern, but not setDefaultRWConcern.
assert.commandWorked(st.configRS.getSecondary().adminCommand({getDefaultRWConcern: 1}));
assert.commandFailedWithCode(
st.configRS.getSecondary().adminCommand(
{setDefaultRWConcern: 1, defaultReadConcern: {level: "local"}}),
ErrorCodes.NotWritablePrimary);
st.stop();
}
})();