1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
|
"""
Standalone mongod fixture for executing JSTests against.
"""
from __future__ import absolute_import
import os
import os.path
import shutil
import socket
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.
exit_code = self.mongod.poll()
if exit_code is not None:
raise errors.ServerFailure("Could not connect to mongod on port %d, process ended"
" unexpectedly with code %d." % (self.port, exit_code))
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 _do_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.mongod is not None:
self.logger.info(
"mongod on port %d was expected to be running in _do_teardown(), but wasn't. "
"Exited with code %d.",
self.port, self.mongod.poll())
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_internal_connection_string(self):
if self.mongod is None:
raise ValueError("Must call setup() before calling get_internal_connection_string()")
return "%s:%d" % (socket.gethostname(), self.port)
def get_driver_connection_url(self):
return "mongodb://" + self.get_internal_connection_string()
|