diff options
Diffstat (limited to 'src/mongo/gotools/test/qa-tests/buildscripts/resmokelib/testing/fixtures/masterslave.py')
-rw-r--r-- | src/mongo/gotools/test/qa-tests/buildscripts/resmokelib/testing/fixtures/masterslave.py | 209 |
1 files changed, 209 insertions, 0 deletions
diff --git a/src/mongo/gotools/test/qa-tests/buildscripts/resmokelib/testing/fixtures/masterslave.py b/src/mongo/gotools/test/qa-tests/buildscripts/resmokelib/testing/fixtures/masterslave.py new file mode 100644 index 00000000000..f3dbf87eb91 --- /dev/null +++ b/src/mongo/gotools/test/qa-tests/buildscripts/resmokelib/testing/fixtures/masterslave.py @@ -0,0 +1,209 @@ +""" +Master/slave fixture for executing JSTests against. +""" + +from __future__ import absolute_import + +import os.path + +import pymongo + +from . import interface +from . import standalone +from ... import config +from ... import logging +from ... import utils + + +class MasterSlaveFixture(interface.ReplFixture): + """ + Fixture which provides JSTests with a master/slave deployment to + run against. + """ + + def __init__(self, + logger, + job_num, + mongod_executable=None, + mongod_options=None, + master_options=None, + slave_options=None, + dbpath_prefix=None, + preserve_dbpath=False): + + interface.ReplFixture.__init__(self, logger, job_num) + + if "dbpath" in mongod_options: + raise ValueError("Cannot specify mongod_options.dbpath") + + self.mongod_executable = mongod_executable + self.mongod_options = utils.default_if_none(mongod_options, {}) + self.master_options = utils.default_if_none(master_options, {}) + self.slave_options = utils.default_if_none(slave_options, {}) + self.preserve_dbpath = preserve_dbpath + + # Command line options override the YAML configuration. + dbpath_prefix = utils.default_if_none(config.DBPATH_PREFIX, dbpath_prefix) + dbpath_prefix = utils.default_if_none(dbpath_prefix, config.DEFAULT_DBPATH_PREFIX) + self._dbpath_prefix = os.path.join(dbpath_prefix, + "job%d" % (self.job_num), + config.FIXTURE_SUBDIR) + + self.master = None + self.slave = None + + def setup(self): + if self.master is None: + self.master = self._new_mongod_master() + self.master.setup() + self.port = self.master.port + + if self.slave is None: + self.slave = self._new_mongod_slave() + self.slave.setup() + + def await_ready(self): + self.master.await_ready() + self.slave.await_ready() + + # Do a replicated write to ensure that the slave has finished with its initial sync before + # starting to run any tests. + client = utils.new_mongo_client(self.port) + + # Keep retrying this until it times out waiting for replication. + def insert_fn(remaining_secs): + remaining_millis = int(round(remaining_secs * 1000)) + write_concern = pymongo.WriteConcern(w=2, wtimeout=remaining_millis) + coll = client.resmoke.get_collection("await_ready", write_concern=write_concern) + coll.insert_one({"awaiting": "ready"}) + + try: + self.retry_until_wtimeout(insert_fn) + except pymongo.errors.WTimeoutError: + self.logger.info("Replication of write operation timed out.") + raise + + def teardown(self): + running_at_start = self.is_running() + success = True # Still a success if nothing is running. + + if not running_at_start: + self.logger.info("Master-slave deployment was expected to be running in teardown()," + " but wasn't.") + + if self.slave is not None: + if running_at_start: + self.logger.info("Stopping slave...") + + success = self.slave.teardown() + + if running_at_start: + self.logger.info("Successfully stopped slave.") + + if self.master is not None: + if running_at_start: + self.logger.info("Stopping master...") + + success = self.master.teardown() and success + + if running_at_start: + self.logger.info("Successfully stopped master.") + + return success + + def is_running(self): + return (self.master is not None and self.master.is_running() and + self.slave is not None and self.slave.is_running()) + + def get_primary(self): + return self.master + + def get_secondaries(self): + return [self.slave] + + def await_repl(self): + """ + Inserts a document into each database on the master and waits + for all write operations to be acknowledged by the master-slave + deployment. + """ + + client = utils.new_mongo_client(self.port) + + # We verify that each database has replicated to the slave because in the case of an initial + # sync, the slave may acknowledge writes to one database before it has finished syncing + # others. + db_names = client.database_names() + self.logger.info("Awaiting replication of inserts to each of the following databases on" + " master on port %d: %s", + self.port, + db_names) + + for db_name in db_names: + if db_name == "local": + continue # The local database is expected to differ, ignore. + + self.logger.info("Awaiting replication of insert to database %s (w=2, wtimeout=%d min)" + " to master on port %d", + db_name, + interface.ReplFixture.AWAIT_REPL_TIMEOUT_MINS, + self.port) + + # Keep retrying this until it times out waiting for replication. + def insert_fn(remaining_secs): + remaining_millis = int(round(remaining_secs * 1000)) + write_concern = pymongo.WriteConcern(w=2, wtimeout=remaining_millis) + coll = client[db_name].get_collection("await_repl", write_concern=write_concern) + coll.insert_one({"awaiting": "repl"}) + + try: + self.retry_until_wtimeout(insert_fn) + except pymongo.errors.WTimeoutError: + self.logger.info("Replication of write operation timed out.") + raise + + self.logger.info("Replication of write operation completed for database %s.", db_name) + + self.logger.info("Finished awaiting replication.") + + def _new_mongod(self, mongod_logger, mongod_options): + """ + Returns a standalone.MongoDFixture with the specified logger and + options. + """ + return standalone.MongoDFixture(mongod_logger, + self.job_num, + mongod_executable=self.mongod_executable, + mongod_options=mongod_options, + preserve_dbpath=self.preserve_dbpath) + + def _new_mongod_master(self): + """ + Returns a standalone.MongoDFixture configured to be used as the + master of a master-slave deployment. + """ + + logger_name = "%s:master" % (self.logger.name) + mongod_logger = logging.loggers.new_logger(logger_name, parent=self.logger) + + mongod_options = self.mongod_options.copy() + mongod_options.update(self.master_options) + mongod_options["master"] = "" + mongod_options["dbpath"] = os.path.join(self._dbpath_prefix, "master") + return self._new_mongod(mongod_logger, mongod_options) + + def _new_mongod_slave(self): + """ + Returns a standalone.MongoDFixture configured to be used as the + slave of a master-slave deployment. + """ + + logger_name = "%s:slave" % (self.logger.name) + mongod_logger = logging.loggers.new_logger(logger_name, parent=self.logger) + + mongod_options = self.mongod_options.copy() + mongod_options.update(self.slave_options) + mongod_options["slave"] = "" + mongod_options["source"] = "localhost:%d" % (self.port) + mongod_options["dbpath"] = os.path.join(self._dbpath_prefix, "slave") + return self._new_mongod(mongod_logger, mongod_options) |