mongo/jstests/sharding/read_write_concern_defaults...

425 lines
16 KiB
JavaScript

// Tests the basic API of the getDefaultRWConcern and setDefaultRWConcern commands and their
// associated persisted state against different topologies.
// 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.
import {ReplSetTest} from "jstests/libs/replsettest.js";
import {ShardingTest} from "jstests/libs/shardingtest.js";
// This test requires running commands directly against the shard.
TestData.replicaSetEndpointIncompatible = true;
function verifyFields(res, {expectRC, expectWC, isPersistedDocument}, isImplicitDefaultWCMajority) {
// 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 || !isPersistedDocument) {
expectedFields.push("defaultReadConcern");
} else {
unexpectedFields.push("defaultReadConcern");
}
if (!isPersistedDocument) {
expectedFields.push("defaultReadConcernSource");
} else {
unexpectedFields.push("defaultReadConcernSource");
}
if (expectWC || (isImplicitDefaultWCMajority && !isPersistedDocument)) {
expectedFields.push("defaultWriteConcern");
} else {
unexpectedFields.push("defaultWriteConcern");
}
if (!isPersistedDocument) {
expectedFields.push("defaultWriteConcernSource");
} else {
unexpectedFields.push("defaultWriteConcernSource");
}
// 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)}`);
});
if (!isPersistedDocument) {
if (expectWC) {
assert.eq(res.defaultWriteConcernSource, "global", tojson(res));
} else {
assert.eq(res.defaultWriteConcernSource, "implicit", 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,
);
// Empty write concern is not allowed if write concern has already been set.
assert.commandFailedWithCode(
conn.adminCommand({setDefaultRWConcern: 1, defaultWriteConcern: {}}),
ErrorCodes.IllegalOperation,
);
// 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,
ErrorCodes.BadValue,
]);
// 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,
);
}
// Verifies the default responses for the default RWC commands and the default persisted state.
function verifyDefaultState(conn, isImplicitDefaultWCMajority) {
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"];
if (isImplicitDefaultWCMajority) {
expectedFields.push("defaultWriteConcern");
}
expectedFields.push("defaultWriteConcernSource");
expectedFields.push("defaultReadConcern");
expectedFields.push("defaultReadConcernSource");
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));
assert.eq(res.defaultWriteConcernSource, "implicit", tojson(res));
assert.eq(inMemoryRes.defaultWriteConcernSource, "implicit", tojson(inMemoryRes));
// No other fields should be returned if neither a default read nor write concern has been set.
const unexpectedFields = ["updateOpTime", "updateWallClockTime"];
if (!isImplicitDefaultWCMajority) {
unexpectedFields.push("defaultWriteConcern");
}
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 verifyDefaultRWCommandsValidInputOnSuccess(conn, isImplicitDefaultWCMajority) {
//
// Test getDefaultRWConcern when neither read nor write concern are set.
//
// 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}));
// 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 getting and setting read concern.
//
// Test setDefaultRWConcern when only read concern is set.
verifyFields(
assert.commandWorked(conn.adminCommand({setDefaultRWConcern: 1, defaultReadConcern: {level: "local"}})),
{expectRC: true, expectWC: false},
isImplicitDefaultWCMajority,
);
verifyFields(
getPersistedRWCDocument(conn),
{expectRC: true, expectWC: false, isPersistedDocument: true},
isImplicitDefaultWCMajority,
);
// Test getDefaultRWConcern when only read concern is set.
verifyFields(
assert.commandWorked(conn.adminCommand({getDefaultRWConcern: 1})),
{expectRC: true, expectWC: false},
isImplicitDefaultWCMajority,
);
// Test unsetting read concern.
verifyFields(
assert.commandWorked(conn.adminCommand({setDefaultRWConcern: 1, defaultReadConcern: {}})),
{expectRC: false, expectWC: false},
isImplicitDefaultWCMajority,
);
verifyFields(
getPersistedRWCDocument(conn),
{expectRC: false, expectWC: false, isPersistedDocument: true},
isImplicitDefaultWCMajority,
);
verifyFields(
assert.commandWorked(conn.adminCommand({getDefaultRWConcern: 1})),
{expectRC: false, expectWC: false},
isImplicitDefaultWCMajority,
);
//
// Test getting and setting write concern.
//
// Empty write concern is allowed if write concern has not already been set.
verifyFields(
assert.commandWorked(conn.adminCommand({setDefaultRWConcern: 1, defaultWriteConcern: {}})),
{expectRC: false, expectWC: false},
isImplicitDefaultWCMajority,
);
verifyFields(
getPersistedRWCDocument(conn),
{expectRC: false, expectWC: false, isPersistedDocument: true},
isImplicitDefaultWCMajority,
);
// Test setRWConcern when only write concern is set.
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"}}));
verifyFields(
assert.commandWorked(conn.adminCommand({setDefaultRWConcern: 1, defaultWriteConcern: {w: 1}})),
{expectRC: false, expectWC: true},
isImplicitDefaultWCMajority,
);
verifyFields(
getPersistedRWCDocument(conn),
{expectRC: false, expectWC: true, isPersistedDocument: true},
isImplicitDefaultWCMajority,
);
// Test getRWConcern when only write concern is set.
verifyFields(
assert.commandWorked(conn.adminCommand({getDefaultRWConcern: 1})),
{expectRC: false, expectWC: true},
isImplicitDefaultWCMajority,
);
//
// Test getting and setting both read and write concern.
//
verifyFields(
assert.commandWorked(
conn.adminCommand({
setDefaultRWConcern: 1,
defaultReadConcern: {level: "local"},
defaultWriteConcern: {w: 1},
}),
),
{expectRC: true, expectWC: true},
isImplicitDefaultWCMajority,
);
verifyFields(
getPersistedRWCDocument(conn),
{expectRC: true, expectWC: true, isPersistedDocument: true},
isImplicitDefaultWCMajority,
);
// Test getRWConcern when both read and write concern are set.
verifyFields(
assert.commandWorked(conn.adminCommand({getDefaultRWConcern: 1})),
{expectRC: true, expectWC: true},
isImplicitDefaultWCMajority,
);
}
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 sharded cluster with implicit default write concern majority...");
{
let st = new ShardingTest({shards: 2, rs: {nodes: 2}});
// Mongos succeeds.
verifyDefaultState(st.s, true /* isImplicitDefaultWCMajority */);
verifyDefaultRWCommandsValidInputOnSuccess(st.s, true /* isImplicitDefaultWCMajority */);
verifyDefaultRWCommandsInvalidInput(st.s);
// Shard node fails.
verifyDefaultRWCommandsFailWithCode(st.rs1.getPrimary(), {failureCode: 51301});
assert.commandFailedWithCode(st.rs1.getSecondary().adminCommand({getDefaultRWConcern: 1}), 51301);
// Secondaries fail setDefaultRWConcern before executing the command.
assert.commandFailedWithCode(
st.rs1.getSecondary().adminCommand({setDefaultRWConcern: 1, defaultReadConcern: {level: "local"}}),
ErrorCodes.NotWritablePrimary,
);
st.stop();
st = new ShardingTest({shards: 1, rs: {nodes: 2}});
// Config server primary succeeds.
verifyDefaultState(st.configRS.getPrimary(), true /* isImplicitDefaultWCMajority */);
verifyDefaultRWCommandsValidInputOnSuccess(st.configRS.getPrimary(), true /* isImplicitDefaultWCMajority */);
verifyDefaultRWCommandsInvalidInput(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();
}
if (jsTestOptions().useAutoBootstrapProcedure) {
// TODO: SERVER-80318 Delete tests below
quit();
}
jsTestLog("Testing standalone mongod...");
{
const standalone = MongoRunner.runMongod();
// Standalone node fails.
verifyDefaultRWCommandsFailWithCode(standalone, {failureCode: 51300});
MongoRunner.stopMongod(standalone);
}
jsTestLog("Testing standalone replica set with implicit default write concern majority...");
{
const rst = new ReplSetTest({nodes: 2});
rst.startSet();
rst.initiate();
// Primary succeeds.
const primary = rst.getPrimary();
verifyDefaultState(primary, true /* isImplicitDefaultWCMajority */);
verifyDefaultRWCommandsValidInputOnSuccess(primary, true /* isImplicitDefaultWCMajority */);
verifyDefaultRWCommandsInvalidInput(primary);
// 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 standalone replica set with implicit default write concern {w:1}...");
{
const rst = new ReplSetTest({nodes: [{}, {}, {arbiter: true}]});
rst.startSet();
rst.initiate();
// Primary succeeds.
const primary = rst.getPrimary();
verifyDefaultState(primary, false /* isImplicitDefaultWCMajority */);
verifyDefaultRWCommandsValidInputOnSuccess(primary, false /* isImplicitDefaultWCMajority */);
verifyDefaultRWCommandsInvalidInput(primary);
// 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();
}