mirror of https://github.com/mongodb/mongo
425 lines
16 KiB
JavaScript
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();
|
|
}
|