/** * 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.getPrimary(); var expectedVersion = replTest.getReplSetConfigFromNode().version; assert.soon(function() { return agreeOnPrimaryAndSetVersion(expectedVersion); }, "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: expectedVersion, 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: expectedVersion, 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: expectedVersion, 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: expectedVersion, 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.getPrimary(); expectedVersion = config.version; assert.soon(function() { return agreeOnPrimaryAndSetVersion(expectedVersion); }, "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: expectedVersion, 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: expectedVersion, 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: expectedVersion, 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: expectedVersion, 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();