/** * check all the easily testable fields in the document returned by the isMaster() command * also checks that fields that should not be in the document are absent */ load("jstests/replsets/rslib.js"); // function create the error message if an assert fails var generateErrorString = function (badFields, missingFields, badValues, result) { var str = "\nThe result was:\n" + tojson(result); if (badFields.length !== 0) { str += "\nIt had the following fields which it shouldn't have: "; str += badFields; } if (missingFields.length !== 0) { str += "\nIt lacked the following fields which it should have contained: "; str += missingFields; } if (badValues.length !== 0) { for (i = 0; i < badValues.length; i+=3) { str += "\nIts value for " + badValues[i] + " is " + badValues[i+1]; str += " but should be " + badValues[i+2]; } } return str; }; // function to check a single result var checkMember = function (memberInfo) { // run isMaster on the connection result = memberInfo.conn.getDB("admin").runCommand({isMaster:1}); // make sure result doesn't contain anything it shouldn't var badFields = []; for (field in result) { if (!result.hasOwnProperty(field)){ continue; } if (Array.contains(memberInfo.unwantedFields, field)) { badFields.push(field); } } // make sure result contains the fields we want var missingFields = []; for (i = 0; i < memberInfo.wantedFields.length; i++) { field = memberInfo.wantedFields[i]; if (!result.hasOwnProperty(field)) { missingFields.push(field); print(field); } } // make sure the result has proper values for fields with known values var badValues = []; // each mistake will be saved as three entries (key, badvalue, goodvalue) for (field in memberInfo.goodValues) { if (typeof(memberInfo.goodValues[field]) === "object") { // assumes nested obj is disk in tags this is currently true, but may change if (result[field].disk !== memberInfo.goodValues[field].disk) { badValues.push("tags.disk"); badValues.push(result[field].disk); badValues.push(memberInfo.goodValues[field].disk); } } else { if (result[field] !== memberInfo.goodValues[field]) { badValues.push(field); badValues.push(result[field]); badValues.push(memberInfo.goodValues[field]); } } } assert(badFields.length === 0 && missingFields.length === 0 && badValues.length === 0, memberInfo.name + " had the following problems." + generateErrorString(badFields, missingFields, badValues, result)); } // start of test code var name = "ismaster"; var replTest = new ReplSetTest({name: name, nodes: 4}); var nodes = replTest.startSet(); var config = replTest.getReplSetConfig(); config.members[1].priority = 0; config.members[2].priority = 0; config.members[2].hidden = true; config.members[2].slaveDelay = 3; config.members[2].buildIndexes = false; config.members[3].arbiterOnly = true; replTest.initiate(config); var agreeOnPrimaryAndSetVersion = function( setVersion ) { print( "Waiting for primary and replica set version " + setVersion ); var nodes = replTest.nodes; var primary = undefined; var lastSetVersion = setVersion; for ( var i = 0; i < nodes.length; i++ ) { try { var isMasterResult = nodes[i].getDB( "admin" ).runCommand({ isMaster : 1 }); } catch (e) { // handle reconnect errors due to step downs print("Error while calling isMaster on " + nodes[i] + ": " + e); return false; } printjson( isMasterResult ); if ( !primary ) primary = isMasterResult.primary; if ( !lastSetVersion ) lastSetVersion = isMasterResult.setVersion; if ( isMasterResult.primary != primary || !primary ) return false; if ( isMasterResult.setVersion != lastSetVersion ) return false; } return true; } var master = replTest.getMaster(); assert.soon( function() { return agreeOnPrimaryAndSetVersion( 1 ); }, "Nodes did not initiate in less than a minute", 60000 ); // check to see if the information from isMaster() is correct at each node // the checker only checks that the field exists when its value is "has" checkMember({ conn: master, name: "master", goodValues: { setName: "ismaster", setVersion: 1, ismaster: true, secondary: false, ok: 1 }, wantedFields: ["hosts", "passives", "arbiters", "primary", "me", "maxBsonObjectSize", "localTime"], unwantedFields: ["arbiterOnly", "passive", "slaveDelay", "hidden", "tags", "buildIndexes"] }); checkMember({ conn: replTest.liveNodes.slaves[0], name: "slave", goodValues: { setName: "ismaster", setVersion: 1, ismaster: false, secondary: true, passive: true, ok: 1 }, wantedFields: ["hosts", "passives", "arbiters", "primary", "me", "maxBsonObjectSize", "localTime"], unwantedFields: ["arbiterOnly", "slaveDelay", "hidden", "tags", "buildIndexes"] }); checkMember({ conn: replTest.liveNodes.slaves[1], name: "delayed_slave", goodValues: { setName: "ismaster", setVersion: 1, ismaster: false, secondary: true, passive: true, slaveDelay: 3, buildIndexes: false, ok: 1 }, wantedFields: ["hosts", "passives", "arbiters", "primary", "me", "maxBsonObjectSize", "localTime"], unwantedFields: ["arbiterOnly", "tags"] }); checkMember({ conn: replTest.liveNodes.slaves[2], name: "arbiter", goodValues: { setName: "ismaster", setVersion: 1, ismaster: false, secondary: false, arbiterOnly: true, ok: 1 }, wantedFields: ["hosts", "passives", "arbiters", "primary", "me", "maxBsonObjectSize", "localTime"], unwantedFields: ["slaveDelay", "hidden", "tags", "buildIndexes", "passive"] }); // reconfigure and make sure the changes show up in ismaster on all members config = master.getDB("local").system.replset.findOne(); config.version = config.version+1; config.members[0].tags = {disk: "ssd"}; config.members[1].tags = {disk: "ssd"}; config.members[1].hidden = true; config.members[2].slaveDelay = 300000; config.members[2].tags = {disk: "hdd"}; try { result = master.getDB("admin").runCommand({replSetReconfig : config}); } catch(e) { print(e); } master = replTest.getMaster(); assert.soon( function() { return agreeOnPrimaryAndSetVersion( 2 ); }, "Nodes did not sync in less than a minute", 60000 ); // check nodes for their new settings checkMember({ conn: master, name: "master2", goodValues: { setName: "ismaster", setVersion: 2, ismaster: true, secondary: false, tags: {"disk": "ssd"}, ok: 1 }, wantedFields: ["hosts", "arbiters", "primary", "me", "maxBsonObjectSize", "localTime"], unwantedFields: ["arbiterOnly", "passives", "passive", "slaveDelay", "hidden", "buildIndexes"] }); checkMember({ conn: replTest.liveNodes.slaves[0], name: "first_slave", goodValues: { setName: "ismaster", setVersion: 2, ismaster: false, secondary: true, tags: {"disk": "ssd"}, passive: true, hidden: true, ok: 1 }, wantedFields: ["hosts", "arbiters", "primary", "me", "maxBsonObjectSize", "localTime"], unwantedFields: ["arbiterOnly", "passives", "slaveDelayed", "buildIndexes"] }); checkMember({ conn: replTest.liveNodes.slaves[1], name: "very_delayed_slave", goodValues: { setName: "ismaster", setVersion: 2, ismaster: false, secondary: true, tags: {"disk": "hdd"}, passive: true, slaveDelay: 300000, buildIndexes: false, hidden: true, ok: 1 }, wantedFields: ["hosts", "arbiters", "primary", "me", "maxBsonObjectSize", "localTime"], unwantedFields: ["arbiterOnly", "passives"] }); checkMember({ conn: replTest.liveNodes.slaves[2], name: "arbiter", goodValues: { setName: "ismaster", setVersion: 2, ismaster: false, secondary: false, arbiterOnly: true, ok: 1 }, wantedFields: ["hosts", "arbiters", "primary", "me", "maxBsonObjectSize", "localTime"], unwantedFields: ["slaveDelay", "hidden", "tags", "buildIndexes", "passive"] }); // force reconfig and ensure all have the same setVersion afterwards config = master.getDB("local").system.replset.findOne(); master.getDB("admin").runCommand({replSetReconfig : config, force: true}); assert.soon( function() { return agreeOnPrimaryAndSetVersion(); }, "Nodes did not sync in less than a minute after forced reconfig", 60000 ); replTest.stopSet();