171 lines
5.0 KiB
Python
171 lines
5.0 KiB
Python
"""
|
|
Interface of the different fixtures for executing JSTests against.
|
|
"""
|
|
|
|
from __future__ import absolute_import
|
|
|
|
import time
|
|
|
|
import pymongo
|
|
|
|
from ... import errors
|
|
from ... import logging
|
|
from ...utils import registry
|
|
|
|
|
|
_FIXTURES = {}
|
|
|
|
|
|
def make_fixture(class_name, *args, **kwargs):
|
|
"""
|
|
Factory function for creating Fixture instances.
|
|
"""
|
|
|
|
if class_name not in _FIXTURES:
|
|
raise ValueError("Unknown fixture class '%s'" % (class_name))
|
|
return _FIXTURES[class_name](*args, **kwargs)
|
|
|
|
|
|
class Fixture(object):
|
|
"""
|
|
Base class for all fixtures.
|
|
"""
|
|
|
|
__metaclass__ = registry.make_registry_metaclass(_FIXTURES)
|
|
|
|
# We explicitly set the 'REGISTERED_NAME' attribute so that PyLint realizes that the attribute
|
|
# is defined for all subclasses of Fixture.
|
|
REGISTERED_NAME = "Fixture"
|
|
|
|
def __init__(self, logger, job_num):
|
|
"""
|
|
Initializes the fixture with a logger instance.
|
|
"""
|
|
|
|
if not isinstance(logger, logging.Logger):
|
|
raise TypeError("logger must be a Logger instance")
|
|
|
|
if not isinstance(job_num, int):
|
|
raise TypeError("job_num must be an integer")
|
|
elif job_num < 0:
|
|
raise ValueError("job_num must be a nonnegative integer")
|
|
|
|
self.logger = logger
|
|
self.job_num = job_num
|
|
|
|
self.port = None # Port that the mongo shell should connect to.
|
|
|
|
def setup(self):
|
|
"""
|
|
Creates the fixture.
|
|
"""
|
|
pass
|
|
|
|
def await_ready(self):
|
|
"""
|
|
Blocks until the fixture can be used for testing.
|
|
"""
|
|
pass
|
|
|
|
def teardown(self, finished=False):
|
|
"""
|
|
Destroys the fixture. Return true if was successful, and false
|
|
otherwise.
|
|
|
|
The fixture's logging handlers are closed if 'finished' is true,
|
|
which should happen when setup() won't be called again.
|
|
"""
|
|
|
|
try:
|
|
return self._do_teardown()
|
|
finally:
|
|
if finished:
|
|
for handler in self.logger.handlers:
|
|
# We ignore the cancellation token returned by close_later() since we always
|
|
# want the logs to eventually get flushed.
|
|
logging.flush.close_later(handler)
|
|
|
|
def _do_teardown(self):
|
|
"""
|
|
Destroys the fixture. Return true if was successful, and false
|
|
otherwise.
|
|
"""
|
|
return True
|
|
|
|
def is_running(self):
|
|
"""
|
|
Returns true if the fixture is still operating and more tests
|
|
can be run, and false otherwise.
|
|
"""
|
|
return True
|
|
|
|
def get_internal_connection_string(self):
|
|
"""
|
|
Returns the connection string for this fixture. This is NOT a
|
|
driver connection string, but a connection string of the format
|
|
expected by the mongo::ConnectionString class.
|
|
"""
|
|
raise NotImplementedError("get_connection_string must be implemented by Fixture subclasses")
|
|
|
|
def get_driver_connection_url(self):
|
|
"""
|
|
Return the mongodb connection string as defined here:
|
|
https://docs.mongodb.com/manual/reference/connection-string/
|
|
"""
|
|
raise NotImplementedError(
|
|
"get_driver_connection_url must be implemented by Fixture subclasses")
|
|
|
|
def __str__(self):
|
|
return "%s (Job #%d)" % (self.__class__.__name__, self.job_num)
|
|
|
|
def __repr__(self):
|
|
return "%r(%r, %r)" % (self.__class__.__name__, self.logger, self.job_num)
|
|
|
|
|
|
class ReplFixture(Fixture):
|
|
"""
|
|
Base class for all fixtures that support replication.
|
|
"""
|
|
|
|
REGISTERED_NAME = registry.LEAVE_UNREGISTERED
|
|
|
|
AWAIT_REPL_TIMEOUT_MINS = 5
|
|
|
|
def get_primary(self):
|
|
"""
|
|
Returns the primary of a replica set, or the master of a
|
|
master-slave deployment.
|
|
"""
|
|
raise NotImplementedError("get_primary must be implemented by ReplFixture subclasses")
|
|
|
|
def get_secondaries(self):
|
|
"""
|
|
Returns a list containing the secondaries of a replica set, or
|
|
the slave of a master-slave deployment.
|
|
"""
|
|
raise NotImplementedError("get_secondaries must be implemented by ReplFixture subclasses")
|
|
|
|
def retry_until_wtimeout(self, insert_fn):
|
|
"""
|
|
Given a callback function representing an insert operation on
|
|
the primary, handle any connection failures, and keep retrying
|
|
the operation for up to 'AWAIT_REPL_TIMEOUT_MINS' minutes.
|
|
|
|
The insert operation callback should take an argument for the
|
|
number of remaining seconds to provide as the timeout for the
|
|
operation.
|
|
"""
|
|
|
|
deadline = time.time() + ReplFixture.AWAIT_REPL_TIMEOUT_MINS * 60
|
|
|
|
while True:
|
|
try:
|
|
remaining = deadline - time.time()
|
|
insert_fn(remaining)
|
|
break
|
|
except pymongo.errors.ConnectionFailure:
|
|
remaining = deadline - time.time()
|
|
if remaining <= 0.0:
|
|
raise errors.ServerFailure("Failed to connect to the primary on port %d" %
|
|
self.port)
|