Skip to content
This repository
Browse code

Merge pull request #58 from manover/add_replicaset_support

Add replicaset support
  • Loading branch information...
commit 810e11b8bca4393cd14815a3619467de02ca231d 2 parents 745c33c + 93282ad
Jehiah Czebotar jehiah authored
8 .travis.yml
@@ -5,8 +5,12 @@ python:
5 5 - "2.7"
6 6 services: mongodb
7 7 install:
8   - - "pip install tornado --use-mirrors"
  8 + - "pip install tornado==2.4.1 --use-mirrors"
9 9 - "pip install pymongo --use-mirrors"
10   -script: py.test
  10 +env:
  11 + - TEST_REPLICA_SETS="true"
  12 + - TEST_REPLICA_SETS="false"
  13 +script:
  14 + - if [[ $TEST_REPLICA_SETS == 'true' ]]; then py.test test/test_replica_set.py; else py.test --ignore=test/test_replica_set.py; fi
11 15 notifications:
12 16 email: false
2  MANIFEST
@@ -9,6 +9,7 @@ asyncmongo/errors.py
9 9 asyncmongo/helpers.py
10 10 asyncmongo/message.py
11 11 asyncmongo/pool.py
  12 +asyncmongo/asyncjobs.py
12 13 asyncmongo/backends/__init__.py
13 14 asyncmongo/backends/glib2_backend.py
14 15 asyncmongo/backends/glib3_backend.py
@@ -28,5 +29,6 @@ test/test_query.py
28 29 test/test_safe_updates.py
29 30 test/test_shunt.py
30 31 test/test_slave_only.py
  32 +test/test_replica_set.py
31 33 test/testgtk2/test.py
32 34 test/testgtk3/test.py
191 asyncmongo/asyncjobs.py
... ... @@ -0,0 +1,191 @@
  1 +#!/bin/env python
  2 +#
  3 +# Copyright 2013 bit.ly
  4 +#
  5 +# Licensed under the Apache License, Version 2.0 (the "License"); you may
  6 +# not use this file except in compliance with the License. You may obtain
  7 +# a copy of the License at
  8 +#
  9 +# http://www.apache.org/licenses/LICENSE-2.0
  10 +#
  11 +# Unless required by applicable law or agreed to in writing, software
  12 +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
  13 +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
  14 +# License for the specific language governing permissions and limitations
  15 +# under the License.
  16 +
  17 +"""Tools for creating `messages
  18 +<http://www.mongodb.org/display/DOCS/Mongo+Wire+Protocol>`_ to be sent to
  19 +MongoDB.
  20 +
  21 +.. note:: This module is for internal use and is generally not needed by
  22 + application developers.
  23 +"""
  24 +
  25 +import logging
  26 +from bson import SON
  27 +
  28 +import message
  29 +import helpers
  30 +from errors import AuthenticationError, RSConnectionError, InterfaceError
  31 +
  32 +class AsyncMessage(object):
  33 + def __init__(self, connection, message, callback):
  34 + super(AsyncMessage, self).__init__()
  35 + self.connection = connection
  36 + self.message = message
  37 + self.callback = callback
  38 +
  39 + def process(self, *args, **kwargs):
  40 + try:
  41 + self.connection._send_message(self.message, self.callback)
  42 + except Exception, e:
  43 + if self.callback is None:
  44 + logging.error("Error occurred in safe update mode: %s", e)
  45 + else:
  46 + self.callback(None, e)
  47 +
  48 +class AuthorizeJob(object):
  49 + def __init__(self, connection, dbuser, dbpass, pool):
  50 + super(AuthorizeJob, self).__init__()
  51 + self.connection = connection
  52 + self._state = "start"
  53 + self.dbuser = dbuser
  54 + self.dbpass = dbpass
  55 + self.pool = pool
  56 +
  57 + def __repr__(self):
  58 + return "AuthorizeJob at 0x%X, state = %r" % (id(self), self._state)
  59 +
  60 + def process(self, response=None, error=None):
  61 + if error:
  62 + logging.debug(error)
  63 + logging.debug(response)
  64 + raise AuthenticationError(error)
  65 +
  66 + if self._state == "start":
  67 + self._state = "nonce"
  68 + logging.debug("Sending nonce")
  69 + msg = message.query(
  70 + 0,
  71 + "%s.$cmd" % self.pool._dbname,
  72 + 0,
  73 + 1,
  74 + SON({'getnonce': 1}),
  75 + SON({})
  76 + )
  77 + self.connection._send_message(msg, self.process)
  78 + elif self._state == "nonce":
  79 + # this is the nonce response
  80 + self._state = "finish"
  81 + nonce = response['data'][0]['nonce']
  82 + logging.debug("Nonce received: %r", nonce)
  83 + key = helpers._auth_key(nonce, self.dbuser, self.dbpass)
  84 +
  85 + msg = message.query(
  86 + 0,
  87 + "%s.$cmd" % self.pool._dbname,
  88 + 0,
  89 + 1,
  90 + SON([('authenticate', 1),
  91 + ('user', self.dbuser),
  92 + ('nonce', nonce),
  93 + ('key', key)]),
  94 + SON({})
  95 + )
  96 + self.connection._send_message(msg, self.process)
  97 + elif self._state == "finish":
  98 + self._state = "done"
  99 + assert response['number_returned'] == 1
  100 + response = response['data'][0]
  101 + if response['ok'] != 1:
  102 + logging.debug('Failed authentication %s' % response['errmsg'])
  103 + raise AuthenticationError(response['errmsg'])
  104 + self.connection._next_job()
  105 + else:
  106 + raise ValueError("Unexpected state: %s" % self._state)
  107 +
  108 +class ConnectRSJob(object):
  109 + def __init__(self, connection, seed, rs):
  110 + self.connection = connection
  111 + self.known_hosts = set(seed)
  112 + self.rs = rs
  113 + self._tried_hosts = set()
  114 + self._state = "seed"
  115 + self._primary = None
  116 +
  117 + def __repr__(self):
  118 + return "ConnectRSJob at 0x%X, state = %s" % (id(self), self._state)
  119 +
  120 + def process(self, response=None, error=None):
  121 + if error:
  122 + logging.debug("Problem connecting: %s", error)
  123 +
  124 + if self._state == "ismaster":
  125 + self._state = "seed"
  126 +
  127 + if self._state == "seed":
  128 + fresh = self.known_hosts ^ self._tried_hosts
  129 + logging.debug("Working through the rest of the host list: %r", fresh)
  130 +
  131 + while fresh:
  132 + if self._primary and self._primary not in self._tried_hosts:
  133 + # Try primary first
  134 + h = self._primary
  135 + else:
  136 + h = fresh.pop()
  137 +
  138 + self._tried_hosts.add(h)
  139 +
  140 + logging.debug("Connecting to %s:%s", *h)
  141 + self.connection._host, self.connection._port = h
  142 + try:
  143 + self.connection._socket_connect()
  144 + logging.debug("Connected to %s", h)
  145 + except InterfaceError, e:
  146 + logging.error("Failed to connect to the host: %s", e)
  147 + else:
  148 + break
  149 +
  150 + else:
  151 + raise RSConnectionError("No more hosts to try, tried: %s" % self.known_hosts)
  152 +
  153 + self._state = "ismaster"
  154 + msg = message.query(
  155 + options=0,
  156 + collection_name="admin.$cmd",
  157 + num_to_skip=0,
  158 + num_to_return=-1,
  159 + query=SON([("ismaster", 1)])
  160 + )
  161 + self.connection._send_message(msg, self.process)
  162 +
  163 + elif self._state == "ismaster":
  164 + logging.debug("ismaster response: %r", response)
  165 +
  166 + if len(response["data"]) == 1:
  167 + res = response["data"][0]
  168 + else:
  169 + raise RSConnectionError("Invalid response data: %r" % response["data"])
  170 +
  171 + rs_name = res.get("setName")
  172 + if rs_name:
  173 + if rs_name != self.rs:
  174 + raise RSConnectionError("Wrong replica set: %s, expected: %s" %
  175 + (rs_name, self.rs))
  176 + hosts = res.get("hosts")
  177 + if hosts:
  178 + self.known_hosts.update(helpers._parse_host(h) for h in hosts)
  179 +
  180 + ismaster = res.get("ismaster")
  181 + if ismaster:
  182 + logging.info("Connected to master")
  183 + self._state = "done"
  184 + self.connection._next_job()
  185 + else:
  186 + primary = res.get("primary")
  187 + if primary:
  188 + self._primary = helpers._parse_host(primary)
  189 +
  190 + self._state = "seed"
  191 + self.process()
169 asyncmongo/connection.py
@@ -14,58 +14,70 @@
14 14 # License for the specific language governing permissions and limitations
15 15 # under the License.
16 16
17   -"""Tools for creating `messages
18   -<http://www.mongodb.org/display/DOCS/Mongo+Wire+Protocol>`_ to be sent to
19   -MongoDB.
20   -
21   -.. note:: This module is for internal use and is generally not needed by
22   - application developers.
23   -"""
24   -
25 17 import sys
26 18 import socket
27 19 import struct
28 20 import logging
  21 +from types import NoneType
29 22
30   -from bson import SON
31   -from errors import ProgrammingError, IntegrityError, InterfaceError, AuthenticationError
32   -import message
  23 +from errors import ProgrammingError, IntegrityError, InterfaceError
33 24 import helpers
  25 +import asyncjobs
  26 +
34 27
35 28 class Connection(object):
36 29 """
37 30 :Parameters:
38   - - `host`: hostname or ip of mongo host
39   - - `port`: port to connect to
  31 + - `host`: hostname or ip of mongo host (not allowed when replica sets are used)
  32 + - `port`: port to connect to (not allowed when replica sets are used)
40 33 - `dbuser`: db user to connect with
41 34 - `dbpass`: db password
42 35 - `autoreconnect` (optional): auto reconnect on interface errors
  36 + - `rs`: replica set name (required when replica sets are used)
  37 + - `seed`: seed list to connect to a replica set (required when replica sets are used)
43 38 - `**kwargs`: passed to `backends.AsyncBackend.register_stream`
44   -
  39 +
45 40 """
46   - def __init__(self, host, port, dbuser=None, dbpass=None, autoreconnect=True, pool=None,
47   - backend="tornado", **kwargs):
48   - assert isinstance(host, (str, unicode))
49   - assert isinstance(port, int)
  41 + def __init__(self,
  42 + host=None,
  43 + port=None,
  44 + dbuser=None,
  45 + dbpass=None,
  46 + autoreconnect=True,
  47 + pool=None,
  48 + backend="tornado",
  49 + rs=None,
  50 + seed=None,
  51 + **kwargs):
50 52 assert isinstance(autoreconnect, bool)
51   - assert isinstance(dbuser, (str, unicode, None.__class__))
52   - assert isinstance(dbpass, (str, unicode, None.__class__))
53   - assert isinstance(kwargs, (dict, None.__class__))
  53 + assert isinstance(dbuser, (str, unicode, NoneType))
  54 + assert isinstance(dbpass, (str, unicode, NoneType))
  55 + assert isinstance(rs, (str, NoneType))
54 56 assert pool
55   - self.__host = host
56   - self.__port = port
  57 +
  58 + if rs:
  59 + assert host is None
  60 + assert port is None
  61 + assert isinstance(seed, (set, list))
  62 + else:
  63 + assert isinstance(host, (str, unicode))
  64 + assert isinstance(port, int)
  65 + assert seed is None
  66 +
  67 + self._host = host
  68 + self._port = port
  69 + self.__rs = rs
  70 + self.__seed = seed
57 71 self.__dbuser = dbuser
58 72 self.__dbpass = dbpass
59 73 self.__stream = None
60 74 self.__callback = None
61 75 self.__alive = False
62   - self.__authenticate = False
63 76 self.__autoreconnect = autoreconnect
64 77 self.__pool = pool
65   - self.__deferred_message = None
66   - self.__deferred_callback = None
67 78 self.__kwargs = kwargs
68 79 self.__backend = self.__load_backend(backend)
  80 + self.__job_queue = []
69 81 self.usage_count = 0
70 82 self.__connect()
71 83
@@ -75,18 +87,27 @@ def __load_backend(self, name):
75 87 return mod.AsyncBackend()
76 88
77 89 def __connect(self):
  90 + if self.__dbuser and self.__dbpass:
  91 + self._put_job(asyncjobs.AuthorizeJob(self, self.__dbuser, self.__dbpass, self.__pool))
  92 +
  93 + if self.__rs:
  94 + self._put_job(asyncjobs.ConnectRSJob(self, self.__seed, self.__rs))
  95 + # Mark the connection as alive, even though it's not alive yet to prevent double-connecting
  96 + self.__alive = True
  97 + else:
  98 + self._socket_connect()
  99 +
  100 + def _socket_connect(self):
  101 + """create a socket, connect, register a stream with the async backend"""
78 102 self.usage_count = 0
79 103 try:
80 104 s = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0)
81   - s.connect((self.__host, self.__port))
  105 + s.connect((self._host, self._port))
82 106 self.__stream = self.__backend.register_stream(s, **self.__kwargs)
83 107 self.__stream.set_close_callback(self._socket_close)
84 108 self.__alive = True
85 109 except socket.error, error:
86 110 raise InterfaceError(error)
87   -
88   - if self.__dbuser and self.__dbpass:
89   - self.__authenticate = True
90 111
91 112 def _socket_close(self):
92 113 """cleanup after the socket is closed by the other end"""
@@ -108,7 +129,7 @@ def close(self):
108 129 """close this connection; re-cache this connection object"""
109 130 self._close()
110 131 self.__pool.cache(self)
111   -
  132 +
112 133 def send_message(self, message, callback):
113 134 """ send a message over the wire; callback=None indicates a safe=False call where we write and forget about it"""
114 135
@@ -121,15 +142,28 @@ def send_message(self, message, callback):
121 142 else:
122 143 raise InterfaceError('connection invalid. autoreconnect=False')
123 144
124   - if self.__authenticate:
125   - self.__deferred_message = message
126   - self.__deferred_callback = callback
127   - self._get_nonce(self._start_authentication)
128   - else:
129   - self.__callback = callback
130   - self._send_message(message)
  145 + # Put the current message on the bottom of the queue
  146 + self._put_job(asyncjobs.AsyncMessage(self, message, callback), 0)
  147 + self._next_job()
  148 +
  149 + def _put_job(self, job, pos=None):
  150 + if pos is None:
  151 + pos = len(self.__job_queue)
  152 + self.__job_queue.insert(pos, job)
  153 +
  154 + def _next_job(self):
  155 + """execute the next job from the top of the queue"""
  156 + if self.__job_queue:
  157 + # Produce message from the top of the queue
  158 + job = self.__job_queue.pop()
  159 + # logging.debug("queue = %s, popped %r", self.__job_queue, job)
  160 + job.process()
131 161
132   - def _send_message(self, message):
  162 + def _send_message(self, message, callback):
  163 + # logging.debug("_send_message, msg = %r: queue = %r, self.__callback = %r, callback = %r",
  164 + # message, self.__job_queue, self.__callback, callback)
  165 +
  166 + self.__callback = callback
133 167 self.usage_count +=1
134 168 # __request_id used by get_more()
135 169 (self.__request_id, data) = message
@@ -140,7 +174,7 @@ def _send_message(self, message):
140 174 else:
141 175 self.__request_id = None
142 176 self.__pool.cache(self)
143   -
  177 +
144 178 except IOError:
145 179 self.__alive = False
146 180 raise
@@ -166,12 +200,12 @@ def _parse_response(self, response):
166 200 request_id = self.__request_id
167 201 self.__request_id = None
168 202 self.__callback = None
169   - if not self.__deferred_message:
  203 + if not self.__job_queue:
170 204 # skip adding to the cache because there is something else
171 205 # that needs to be called on this connection for this request
172 206 # (ie: we authenticted, but still have to send the real req)
173 207 self.__pool.cache(self)
174   -
  208 +
175 209 try:
176 210 response = helpers._unpack_response(response, request_id) # TODO: pass tz_awar
177 211 except Exception, e:
@@ -183,54 +217,3 @@ def _parse_response(self, response):
183 217 callback(response, IntegrityError(response['data'][0]['err'], code=response['data'][0]['code']))
184 218 return
185 219 callback(response)
186   -
187   - def _start_authentication(self, response, error=None):
188   - # this is the nonce response
189   - if error:
190   - logging.debug(error)
191   - logging.debug(response)
192   - raise AuthenticationError(error)
193   - nonce = response['data'][0]['nonce']
194   - key = helpers._auth_key(nonce, self.__dbuser, self.__dbpass)
195   -
196   - self.__callback = self._finish_authentication
197   - self._send_message(
198   - message.query(0,
199   - "%s.$cmd" % self.__pool._dbname,
200   - 0,
201   - 1,
202   - SON([('authenticate', 1), ('user' , self.__dbuser), ('nonce' , nonce), ('key' , key)]),
203   - SON({})))
204   -
205   - def _finish_authentication(self, response, error=None):
206   - if error:
207   - self.__deferred_message = None
208   - self.__deferred_callback = None
209   - raise AuthenticationError(error)
210   - assert response['number_returned'] == 1
211   - response = response['data'][0]
212   - if response['ok'] != 1:
213   - logging.debug('Failed authentication %s' % response['errmsg'])
214   - self.__deferred_message = None
215   - self.__deferred_callback = None
216   - raise AuthenticationError(response['errmsg'])
217   -
218   - message = self.__deferred_message
219   - callback = self.__deferred_callback
220   - self.__deferred_message = None
221   - self.__deferred_callback = None
222   - self.__callback = callback
223   - # continue the original request
224   - self._send_message(message)
225   -
226   - def _get_nonce(self, callback):
227   - assert self.__callback is None
228   - self.__callback = callback
229   - self._send_message(
230   - message.query(0,
231   - "%s.$cmd" % self.__pool._dbname,
232   - 0,
233   - 1,
234   - SON({'getnonce' : 1}),
235   - SON({})
236   - ))
3  asyncmongo/errors.py
@@ -29,6 +29,9 @@ class Error(StandardError):
29 29 class InterfaceError(Error):
30 30 pass
31 31
  32 +class RSConnectionError(Error):
  33 + pass
  34 +
32 35 class DatabaseError(Error):
33 36 pass
34 37
9 asyncmongo/helpers.py
@@ -7,6 +7,15 @@
7 7 from asyncmongo.errors import (DatabaseError, InterfaceError)
8 8
9 9
  10 +def _parse_host(h):
  11 + try:
  12 + host, port = h.split(":", 1)
  13 + port = int(port)
  14 + except ValueError:
  15 + raise ValueError("Wrong host:port value: %s" % h)
  16 +
  17 + return host, port
  18 +
10 19 def _unpack_response(response, cursor_id=None, as_class=dict, tz_aware=False):
11 20 """Unpack a response from the database.
12 21
1  test/test_authentication.py
@@ -21,6 +21,7 @@ def test_authentication(self):
21 21 db = asyncmongo.Client(pool_id='testauth', host='127.0.0.1', port=27018, dbname='test', dbuser='testuser', dbpass='testpass', maxconnections=2)
22 22
23 23 def update_callback(response, error):
  24 + logging.info("UPDATE:")
24 25 tornado.ioloop.IOLoop.instance().stop()
25 26 logging.info(response)
26 27 assert len(response) == 1
161 test/test_replica_set.py
... ... @@ -0,0 +1,161 @@
  1 +import tornado.ioloop
  2 +import time
  3 +import logging
  4 +import subprocess
  5 +import socket
  6 +
  7 +import test_shunt
  8 +import asyncmongo
  9 +import asyncmongo.connection
  10 +import asyncmongo.errors
  11 +
  12 +TEST_TIMESTAMP = int(time.time())
  13 +
  14 +class ReplicaSetTest(test_shunt.MongoTest):
  15 + mongod_options = [
  16 + ('--port', '27018', '--replSet', 'rs0'),
  17 + ('--port', '27019', '--replSet', 'rs0'),
  18 + ('--port', '27020', '--replSet', 'rs0'),
  19 + ]
  20 +
  21 + def mongo_cmd(self, cmd, port=27018, res='"ok" : 1'):
  22 + pipe = subprocess.Popen("mongo --port %d" % port, shell=True,
  23 + stdout=subprocess.PIPE, stdin=subprocess.PIPE)
  24 + reply = pipe.communicate(cmd)[0]
  25 + assert reply.find(res) > 0
  26 + return reply
  27 +
  28 + def wait_master(self, port):
  29 + while True:
  30 + if self.mongo_cmd("db.isMaster();", port).find('"ismaster" : true') > 0:
  31 + logging.info("%d is a master", port)
  32 + break
  33 + else:
  34 + logging.info("Waiting for %d to become master", port)
  35 + time.sleep(5)
  36 +
  37 + def setUp(self):
  38 + super(ReplicaSetTest, self).setUp()
  39 + hostname = socket.gethostname()
  40 + logging.info("configuring a replica set at %s" % hostname)
  41 + cfg = """
  42 + {
  43 + "_id" : "rs0",
  44 + "members" : [
  45 + {
  46 + "_id" : 0,
  47 + "host" : "%(hostname)s:27018"
  48 + },
  49 + {
  50 + "_id" : 1,
  51 + "host" : "%(hostname)s:27019",
  52 + "priority" : 2
  53 + },
  54 + {
  55 + "_id" : 2,
  56 + "host" : "%(hostname)s:27020",
  57 + "priority" : 0
  58 + }
  59 + ]
  60 + }
  61 + """ % dict(hostname=hostname)
  62 + self.mongo_cmd("rs.initiate(%s);" % cfg)
  63 + logging.info("waiting for replica set to finish configuring")
  64 + self.wait_master(27019)
  65 +
  66 + def test_connection(self):
  67 + class Pool(object):
  68 + def __init__(self):
  69 + super(Pool, self).__init__()
  70 + self._cache = []
  71 +
  72 + def cache(self, c):
  73 + self._cache.append(c)
  74 +
  75 + class AsyncClose(object):
  76 + def process(self, *args, **kwargs):
  77 + tornado.ioloop.IOLoop.instance().stop()
  78 +
  79 + hostname = socket.gethostname()
  80 + try:
  81 + conn = asyncmongo.connection.Connection(pool=Pool(),
  82 + seed=[(hostname, 27018), (hostname, 27020)],
  83 + rs="rs0")
  84 +
  85 + conn._put_job(AsyncClose(), 0)
  86 + conn._next_job()
  87 + tornado.ioloop.IOLoop.instance().start()
  88 +
  89 + assert conn._host == hostname
  90 + assert conn._port == 27019
  91 +
  92 + except:
  93 + tornado.ioloop.IOLoop.instance().stop()
  94 + raise
  95 +
  96 + def test_update(self):
  97 + try:
  98 + test_shunt.setup()
  99 + db = asyncmongo.Client(pool_id='testrs', rs="rs0", seed=[("127.0.0.1", 27020)], dbname='test', maxconnections=2)
  100 +
  101 + # Update
  102 + def update_callback(response, error):
  103 + logging.info("UPDATE:")
  104 + tornado.ioloop.IOLoop.instance().stop()
  105 + logging.info(response)
  106 + assert len(response) == 1
  107 + test_shunt.register_called('update')
  108 +
  109 + db.test_stats.update({"_id" : TEST_TIMESTAMP}, {'$inc' : {'test_count' : 1}}, upsert=True, callback=update_callback)
  110 +
  111 + tornado.ioloop.IOLoop.instance().start()
  112 + test_shunt.assert_called('update')
  113 +
  114 + # Retrieve the updated value
  115 + def query_callback(response, error):
  116 + tornado.ioloop.IOLoop.instance().stop()
  117 + logging.info(response)
  118 + logging.info(error)
  119 + assert error is None
  120 + assert isinstance(response, dict)
  121 + assert response['_id'] == TEST_TIMESTAMP
  122 + assert response['test_count'] == 1
  123 + test_shunt.register_called('retrieved')
  124 +
  125 + db.test_stats.find_one({"_id" : TEST_TIMESTAMP}, callback=query_callback)
  126 + tornado.ioloop.IOLoop.instance().start()
  127 + test_shunt.assert_called('retrieved')
  128 +
  129 + # Switch the master
  130 + self.mongo_cmd(
  131 + "cfg = rs.conf(); cfg.members[1].priority = 1; cfg.members[0].priority = 2; rs.reconfig(cfg);",
  132 + 27019, "reconnected to server")
  133 + self.wait_master(27018)
  134 +
  135 + # Expect the connection to be closed
  136 + def query_err_callback(response, error):
  137 + tornado.ioloop.IOLoop.instance().stop()
  138 + logging.info(response)
  139 + logging.info(error)
  140 + assert isinstance(error, Exception)
  141 +
  142 + db.test_stats.find_one({"_id" : TEST_TIMESTAMP}, callback=query_err_callback)
  143 + tornado.ioloop.IOLoop.instance().start()
  144 +
  145 + # Retrieve the updated value again, from the new master
  146 + def query_again_callback(response, error):
  147 + tornado.ioloop.IOLoop.instance().stop()
  148 + logging.info(response)
  149 + logging.info(error)
  150 + assert error is None
  151 + assert isinstance(response, dict)
  152 + assert response['_id'] == TEST_TIMESTAMP
  153 + assert response['test_count'] == 1
  154 + test_shunt.register_called('retrieved_again')
  155 +
  156 + db.test_stats.find_one({"_id" : TEST_TIMESTAMP}, callback=query_again_callback)
  157 + tornado.ioloop.IOLoop.instance().start()
  158 + test_shunt.assert_called('retrieved_again')
  159 + except:
  160 + tornado.ioloop.IOLoop.instance().stop()
  161 + raise
6 test/test_shunt.py
@@ -46,7 +46,7 @@ def setUp(self):
46 46 # PuritanicalIOLoop instead of a default loop.
47 47 if not tornado.ioloop.IOLoop.initialized():
48 48 self.loop = PuritanicalIOLoop()
49   - self.loop.install()
  49 + tornado.ioloop.IOLoop._instance = self.loop
50 50 else:
51 51 self.loop = tornado.ioloop.IOLoop.instance()
52 52 self.assert_(
@@ -61,8 +61,8 @@ def setUp(self):
61 61 os.makedirs(dirname)
62 62 self.temp_dirs.append(dirname)
63 63
64   - options = ['mongod', '--bind_ip', '127.0.0.1', '--oplogSize', '10',
65   - '--dbpath', dirname, '--smallfiles', '-v', '--nojournal'] + list(options)
  64 + options = ['mongod', '--oplogSize', '2', '--dbpath', dirname,
  65 + '--smallfiles', '-v', '--nojournal'] + list(options)
66 66 logging.debug(options)
67 67 pipe = subprocess.Popen(options)
68 68 self.mongods.append(pipe)

0 comments on commit 810e11b

Please sign in to comment.
Something went wrong with that request. Please try again.