summaryrefslogtreecommitdiff
path: root/src/mongo/gotools/test/qa-tests/buildscripts/resmokelib/testing/fixtures/masterslave.py
diff options
context:
space:
mode:
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.py209
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)