208 lines
7.5 KiB
Python
208 lines
7.5 KiB
Python
"""
|
|
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):
|
|
self.master = self._new_mongod_master()
|
|
self.master.setup()
|
|
self.port = self.master.port
|
|
|
|
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))
|
|
client.resmoke.await_ready.insert({"awaiting": "ready"},
|
|
w=2,
|
|
wtimeout=remaining_millis)
|
|
|
|
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))
|
|
client[db_name].resmoke_await_repl.insert({"awaiting": "repl"},
|
|
w=2,
|
|
wtimeout=remaining_millis)
|
|
|
|
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)
|