// 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(); } })();