151 lines
5.7 KiB
Python
151 lines
5.7 KiB
Python
"""
|
|
Standalone mongod fixture for executing JSTests against.
|
|
"""
|
|
|
|
from __future__ import absolute_import
|
|
|
|
import os
|
|
import os.path
|
|
import shutil
|
|
import time
|
|
|
|
import pymongo
|
|
|
|
from . import interface
|
|
from ... import config
|
|
from ... import core
|
|
from ... import errors
|
|
from ... import utils
|
|
|
|
|
|
class MongoDFixture(interface.Fixture):
|
|
"""
|
|
Fixture which provides JSTests with a standalone mongod to run
|
|
against.
|
|
"""
|
|
|
|
AWAIT_READY_TIMEOUT_SECS = 300
|
|
|
|
def __init__(self,
|
|
logger,
|
|
job_num,
|
|
mongod_executable=None,
|
|
mongod_options=None,
|
|
dbpath_prefix=None,
|
|
preserve_dbpath=False):
|
|
|
|
interface.Fixture.__init__(self, logger, job_num)
|
|
|
|
if "dbpath" in mongod_options and dbpath_prefix is not None:
|
|
raise ValueError("Cannot specify both mongod_options.dbpath and dbpath_prefix")
|
|
|
|
# Command line options override the YAML configuration.
|
|
self.mongod_executable = utils.default_if_none(config.MONGOD_EXECUTABLE, mongod_executable)
|
|
|
|
self.mongod_options = utils.default_if_none(mongod_options, {}).copy()
|
|
self.preserve_dbpath = preserve_dbpath
|
|
|
|
# The dbpath in mongod_options takes precedence over other settings to make it easier for
|
|
# users to specify a dbpath containing data to test against.
|
|
if "dbpath" not in self.mongod_options:
|
|
# 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.mongod_options["dbpath"] = os.path.join(dbpath_prefix,
|
|
"job%d" % (self.job_num),
|
|
config.FIXTURE_SUBDIR)
|
|
self._dbpath = self.mongod_options["dbpath"]
|
|
|
|
self.mongod = None
|
|
|
|
def setup(self):
|
|
if not self.preserve_dbpath:
|
|
shutil.rmtree(self._dbpath, ignore_errors=True)
|
|
|
|
try:
|
|
os.makedirs(self._dbpath)
|
|
except os.error:
|
|
# Directory already exists.
|
|
pass
|
|
|
|
if "port" not in self.mongod_options:
|
|
self.mongod_options["port"] = core.network.PortAllocator.next_fixture_port(self.job_num)
|
|
self.port = self.mongod_options["port"]
|
|
|
|
mongod = core.programs.mongod_program(self.logger,
|
|
executable=self.mongod_executable,
|
|
**self.mongod_options)
|
|
try:
|
|
self.logger.info("Starting mongod on port %d...\n%s", self.port, mongod.as_command())
|
|
mongod.start()
|
|
self.logger.info("mongod started on port %d with pid %d.", self.port, mongod.pid)
|
|
except:
|
|
self.logger.exception("Failed to start mongod on port %d.", self.port)
|
|
raise
|
|
|
|
self.mongod = mongod
|
|
|
|
def await_ready(self):
|
|
deadline = time.time() + MongoDFixture.AWAIT_READY_TIMEOUT_SECS
|
|
|
|
# Wait until the mongod is accepting connections. The retry logic is necessary to support
|
|
# versions of PyMongo <3.0 that immediately raise a ConnectionFailure if a connection cannot
|
|
# be established.
|
|
while True:
|
|
# Check whether the mongod exited for some reason.
|
|
if self.mongod.poll() is not None:
|
|
raise errors.ServerFailure("Could not connect to mongod on port %d, process ended"
|
|
" unexpectedly." % (self.port))
|
|
|
|
try:
|
|
# Use a shorter connection timeout to more closely satisfy the requested deadline.
|
|
client = utils.new_mongo_client(self.port, timeout_millis=500)
|
|
client.admin.command("ping")
|
|
break
|
|
except pymongo.errors.ConnectionFailure:
|
|
remaining = deadline - time.time()
|
|
if remaining <= 0.0:
|
|
raise errors.ServerFailure(
|
|
"Failed to connect to mongod on port %d after %d seconds"
|
|
% (self.port, MongoDFixture.AWAIT_READY_TIMEOUT_SECS))
|
|
|
|
self.logger.info("Waiting to connect to mongod on port %d.", self.port)
|
|
time.sleep(0.1) # Wait a little bit before trying again.
|
|
|
|
self.logger.info("Successfully contacted the mongod on port %d.", self.port)
|
|
|
|
def teardown(self):
|
|
running_at_start = self.is_running()
|
|
success = True # Still a success even if nothing is running.
|
|
|
|
if not running_at_start and self.port is not None:
|
|
self.logger.info("mongod on port %d was expected to be running in teardown(), but"
|
|
" wasn't." % (self.port))
|
|
|
|
if self.mongod is not None:
|
|
if running_at_start:
|
|
self.logger.info("Stopping mongod on port %d with pid %d...",
|
|
self.port,
|
|
self.mongod.pid)
|
|
self.mongod.stop()
|
|
|
|
exit_code = self.mongod.wait()
|
|
success = exit_code == 0
|
|
|
|
if running_at_start:
|
|
self.logger.info("Successfully terminated the mongod on port %d, exited with code"
|
|
" %d.",
|
|
self.port,
|
|
exit_code)
|
|
|
|
return success
|
|
|
|
def is_running(self):
|
|
return self.mongod is not None and self.mongod.poll() is None
|
|
|
|
def get_connection_string(self):
|
|
if self.mongod is None:
|
|
raise ValueError("Must call setup() before calling get_connection_string()")
|
|
|
|
return "localhost:%d" % self.port
|