text: """
  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)
  
          self.logger.info("Starting fsync on master on port %d to flush all pending writes",
                           self.port)
          client.fsync()
          self.logger.info("fsync on master completed")
  
          # 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)
  