// Tests the basic API of the getDefaultRWConcern and setDefaultRWConcern commands and their // associated persisted state against different topologies. // @tags: [ // ] (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}, 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); // 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 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(); } 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(); } })();