Merge lp:~corey.bryant/ceilometer/2014.2.2 into lp:~ubuntu-server-dev/ceilometer/juno
- 2014.2.2
- Merge into juno
Proposed by
Corey Bryant
Status: | Merged |
---|---|
Merged at revision: | 126 |
Proposed branch: | lp:~corey.bryant/ceilometer/2014.2.2 |
Merge into: | lp:~ubuntu-server-dev/ceilometer/juno |
Diff against target: |
433 lines (+7/-407) 3 files modified
debian/changelog (+7/-0) debian/patches/mongodb-autoreconnect.patch (+0/-406) debian/patches/series (+0/-1) |
To merge this branch: | bzr merge lp:~corey.bryant/ceilometer/2014.2.2 |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Ubuntu Server Developers | Pending | ||
Review via email: mp+249657@code.launchpad.net |
Commit message
Description of the change
To post a comment you must log in.
- 126. By Corey Bryant
-
/d/p/mongodb-
autoreconnect. patch: Dropped as it is breaking
ceilometer-dbsync (LP: #1421663).
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'debian/changelog' |
2 | --- debian/changelog 2015-02-09 19:57:46 +0000 |
3 | +++ debian/changelog 2015-02-13 15:02:15 +0000 |
4 | @@ -1,3 +1,10 @@ |
5 | +ceilometer (2014.2.2-0ubuntu2) UNRELEASED; urgency=medium |
6 | + |
7 | + * /d/p/mongodb-autoreconnect.patch: Dropped as it is breaking |
8 | + ceilometer-dbsync (LP: #1421663). |
9 | + |
10 | + -- Corey Bryant <corey.bryant@canonical.com> Fri, 13 Feb 2015 08:54:31 -0500 |
11 | + |
12 | ceilometer (2014.2.2-0ubuntu1) utopic; urgency=medium |
13 | |
14 | [ Corey Bryant ] |
15 | |
16 | === removed file 'debian/patches/mongodb-autoreconnect.patch' |
17 | --- debian/patches/mongodb-autoreconnect.patch 2015-02-09 13:55:53 +0000 |
18 | +++ debian/patches/mongodb-autoreconnect.patch 1970-01-01 00:00:00 +0000 |
19 | @@ -1,406 +0,0 @@ |
20 | -From cfc2de1d1994b5a1fc9e2f94a6ba4e20303c3ed1 Mon Sep 17 00:00:00 2001 |
21 | -From: Igor Degtiarov <idegtiarov@mirantis.com> |
22 | -Date: Thu, 23 Oct 2014 14:05:38 +0300 |
23 | -Subject: [PATCH] [MongoDB] Fix bug with reconnection to new master node |
24 | - |
25 | -Fixes bug with raising AutoReconnect exception when MongoDB ReplicaSet |
26 | -loses connection to primary node. |
27 | - |
28 | -Closes-Bug: #1309555 |
29 | - |
30 | -Conflicts: |
31 | - ceilometer/event/storage/impl_db2.py |
32 | - ceilometer/event/storage/impl_mongodb.py |
33 | - ceilometer/storage/__init__.py |
34 | - ceilometer/storage/mongo/utils.py |
35 | - ceilometer/tests/storage/test_pymongo_base.py |
36 | - |
37 | -Conflicts are due to refactoring of the storage drivers and this patch |
38 | -has been modified to account for the refactor. The test case within the |
39 | -file test_pymongo_base.py was removed since equivalent test coverage |
40 | -was included in the cherry picked commit in the test_storage_scenarios.py |
41 | -file. |
42 | - |
43 | -Change-Id: Id0e81ba60b28d09adff6a10d04b412f25257d8ce |
44 | -(cherry-picked from commit 21d882c96cbbaeb8b78ff91e06e3615be97bff07) |
45 | ---- |
46 | - ceilometer/alarm/storage/impl_db2.py | 2 +- |
47 | - ceilometer/alarm/storage/impl_mongodb.py | 2 +- |
48 | - ceilometer/storage/__init__.py | 4 + |
49 | - ceilometer/storage/impl_db2.py | 2 +- |
50 | - ceilometer/storage/impl_mongodb.py | 2 +- |
51 | - ceilometer/storage/mongo/utils.py | 119 +++++++++++++++++---- |
52 | - ceilometer/tests/storage/test_pymongo_base.py | 42 -------- |
53 | - ceilometer/tests/storage/test_storage_scenarios.py | 88 +++++++++++++++ |
54 | - 8 files changed, 196 insertions(+), 65 deletions(-) |
55 | - |
56 | -diff --git a/ceilometer/alarm/storage/impl_db2.py b/ceilometer/alarm/storage/impl_db2.py |
57 | -index 9ca37f2..92db547 100644 |
58 | ---- a/ceilometer/alarm/storage/impl_db2.py |
59 | -+++ b/ceilometer/alarm/storage/impl_db2.py |
60 | -@@ -73,5 +73,5 @@ class Connection(pymongo_base.Connection): |
61 | - # not been implemented. However calling this method is important for |
62 | - # removal of all the empty dbs created during the test runs since |
63 | - # test run is against mongodb on Jenkins |
64 | -- self.conn.drop_database(self.db) |
65 | -+ self.conn.drop_database(self.db.name) |
66 | - self.conn.close() |
67 | -diff --git a/ceilometer/alarm/storage/impl_mongodb.py b/ceilometer/alarm/storage/impl_mongodb.py |
68 | -index 19fff00..60c0ca4 100644 |
69 | ---- a/ceilometer/alarm/storage/impl_mongodb.py |
70 | -+++ b/ceilometer/alarm/storage/impl_mongodb.py |
71 | -@@ -63,6 +63,6 @@ class Connection(pymongo_base.Connection): |
72 | - self.upgrade() |
73 | - |
74 | - def clear(self): |
75 | -- self.conn.drop_database(self.db) |
76 | -+ self.conn.drop_database(self.db.name) |
77 | - # Connection will be reopened automatically if needed |
78 | - self.conn.close() |
79 | -diff --git a/ceilometer/storage/__init__.py b/ceilometer/storage/__init__.py |
80 | -index 36d91cc..b338363 100644 |
81 | ---- a/ceilometer/storage/__init__.py |
82 | -+++ b/ceilometer/storage/__init__.py |
83 | -@@ -53,6 +53,10 @@ STORAGE_OPTS = [ |
84 | - default=None, |
85 | - help='The connection string used to connect to the alarm ' |
86 | - 'database. (if unset, connection is used)'), |
87 | -+ cfg.StrOpt('mongodb_replica_set', |
88 | -+ default='', |
89 | -+ help="The connection string used to connect to mongo database, " |
90 | -+ "if mongodb replica set was chosen."), |
91 | - ] |
92 | - |
93 | - cfg.CONF.register_opts(STORAGE_OPTS, group='database') |
94 | -diff --git a/ceilometer/storage/impl_db2.py b/ceilometer/storage/impl_db2.py |
95 | -index 4326162..e5c819e 100644 |
96 | ---- a/ceilometer/storage/impl_db2.py |
97 | -+++ b/ceilometer/storage/impl_db2.py |
98 | -@@ -200,7 +200,7 @@ class Connection(pymongo_base.Connection): |
99 | - # not been implemented. However calling this method is important for |
100 | - # removal of all the empty dbs created during the test runs since |
101 | - # test run is against mongodb on Jenkins |
102 | -- self.conn.drop_database(self.db) |
103 | -+ self.conn.drop_database(self.db.name) |
104 | - self.conn.close() |
105 | - |
106 | - def record_metering_data(self, data): |
107 | -diff --git a/ceilometer/storage/impl_mongodb.py b/ceilometer/storage/impl_mongodb.py |
108 | -index b9ce7a9..490eb52 100644 |
109 | ---- a/ceilometer/storage/impl_mongodb.py |
110 | -+++ b/ceilometer/storage/impl_mongodb.py |
111 | -@@ -470,7 +470,7 @@ class Connection(pymongo_base.Connection): |
112 | - ) |
113 | - |
114 | - def clear(self): |
115 | -- self.conn.drop_database(self.db) |
116 | -+ self.conn.drop_database(self.db.name) |
117 | - # Connection will be reopened automatically if needed |
118 | - self.conn.close() |
119 | - |
120 | -diff --git a/ceilometer/storage/mongo/utils.py b/ceilometer/storage/mongo/utils.py |
121 | -index 5aa8832..3d851e0 100644 |
122 | ---- a/ceilometer/storage/mongo/utils.py |
123 | -+++ b/ceilometer/storage/mongo/utils.py |
124 | -@@ -179,26 +179,21 @@ class ConnectionPool(object): |
125 | - |
126 | - @staticmethod |
127 | - def _mongo_connect(url): |
128 | -- max_retries = cfg.CONF.database.max_retries |
129 | -- retry_interval = cfg.CONF.database.retry_interval |
130 | -- attempts = 0 |
131 | -- while True: |
132 | -- try: |
133 | -- client = pymongo.MongoClient(url, safe=True) |
134 | -- except pymongo.errors.ConnectionFailure as e: |
135 | -- if 0 <= max_retries <= attempts: |
136 | -- LOG.error(_('Unable to connect to the database after ' |
137 | -- '%(retries)d retries. Giving up.') % |
138 | -- {'retries': max_retries}) |
139 | -- raise |
140 | -- LOG.warn(_('Unable to connect to the database server: ' |
141 | -- '%(errmsg)s. Trying again in %(retry_interval)d ' |
142 | -- 'seconds.') % |
143 | -- {'errmsg': e, 'retry_interval': retry_interval}) |
144 | -- attempts += 1 |
145 | -- time.sleep(retry_interval) |
146 | -+ try: |
147 | -+ if cfg.CONF.database.mongodb_replica_set: |
148 | -+ client = MongoProxy( |
149 | -+ Prefection( |
150 | -+ pymongo.MongoReplicaSetClient( |
151 | -+ url, |
152 | -+ replicaSet=cfg.CONF.database.mongodb_replica_set))) |
153 | - else: |
154 | -- return client |
155 | -+ client = MongoProxy( |
156 | -+ Prefection(pymongo.MongoClient(url, safe=True))) |
157 | -+ return client |
158 | -+ except pymongo.errors.ConnectionFailure as e: |
159 | -+ LOG.warn(_('Unable to connect to the database server: ' |
160 | -+ '%(errmsg)s.') % {'errmsg': e}) |
161 | -+ raise |
162 | - |
163 | - |
164 | - class QueryTransformer(object): |
165 | -@@ -321,3 +316,89 @@ class QueryTransformer(object): |
166 | - return self._handle_not_op(negated_tree) |
167 | - |
168 | - return self._handle_simple_op(operator_node, nodes) |
169 | -+ |
170 | -+ |
171 | -+def safe_mongo_call(call): |
172 | -+ def closure(*args, **kwargs): |
173 | -+ max_retries = cfg.CONF.database.max_retries |
174 | -+ retry_interval = cfg.CONF.database.retry_interval |
175 | -+ attempts = 0 |
176 | -+ while True: |
177 | -+ try: |
178 | -+ return call(*args, **kwargs) |
179 | -+ except pymongo.errors.AutoReconnect as err: |
180 | -+ if 0 <= max_retries <= attempts: |
181 | -+ LOG.error(_('Unable to reconnect to the primary mongodb ' |
182 | -+ 'after %(retries)d retries. Giving up.') % |
183 | -+ {'retries': max_retries}) |
184 | -+ raise |
185 | -+ LOG.warn(_('Unable to reconnect to the primary mongodb: ' |
186 | -+ '%(errmsg)s. Trying again in %(retry_interval)d ' |
187 | -+ 'seconds.') % |
188 | -+ {'errmsg': err, 'retry_interval': retry_interval}) |
189 | -+ attempts += 1 |
190 | -+ time.sleep(retry_interval) |
191 | -+ return closure |
192 | -+ |
193 | -+ |
194 | -+class MongoConn(object): |
195 | -+ def __init__(self, method): |
196 | -+ self.method = method |
197 | -+ |
198 | -+ @safe_mongo_call |
199 | -+ def __call__(self, *args, **kwargs): |
200 | -+ return self.method(*args, **kwargs) |
201 | -+ |
202 | -+MONGO_METHODS = set([typ for typ in dir(pymongo.collection.Collection) |
203 | -+ if not typ.startswith('_')]) |
204 | -+MONGO_METHODS.update(set([typ for typ in dir(pymongo.MongoClient) |
205 | -+ if not typ.startswith('_')])) |
206 | -+MONGO_METHODS.update(set([typ for typ in dir(pymongo) |
207 | -+ if not typ.startswith('_')])) |
208 | -+ |
209 | -+ |
210 | -+class MongoProxy(object): |
211 | -+ def __init__(self, conn): |
212 | -+ self.conn = conn |
213 | -+ |
214 | -+ def __getitem__(self, item): |
215 | -+ """Create and return proxy around the method in the connection. |
216 | -+ |
217 | -+ :param item: name of the connection |
218 | -+ """ |
219 | -+ return MongoProxy(self.conn[item]) |
220 | -+ |
221 | -+ def __getattr__(self, item): |
222 | -+ """Wrap MongoDB connection. |
223 | -+ |
224 | -+ If item is the name of an executable method, for example find or |
225 | -+ insert, wrap this method in the MongoConn. |
226 | -+ Else wrap getting attribute with MongoProxy. |
227 | -+ """ |
228 | -+ if item == 'name': |
229 | -+ return getattr(self.conn, item) |
230 | -+ if item in MONGO_METHODS: |
231 | -+ return MongoConn(getattr(self.conn, item)) |
232 | -+ return MongoProxy(getattr(self.conn, item)) |
233 | -+ |
234 | -+ def __call__(self, *args, **kwargs): |
235 | -+ return self.conn(*args, **kwargs) |
236 | -+ |
237 | -+ |
238 | -+class Prefection(pymongo.collection.Collection): |
239 | -+ def __init__(self, conn): |
240 | -+ self.conn = conn |
241 | -+ |
242 | -+ def find(self, *args, **kwargs): |
243 | -+ # We need this modifying method to check a connection for MongoDB |
244 | -+ # in context of MongoProxy approach. Initially 'find' returns Cursor |
245 | -+ # object and doesn't connect to db while Cursor is not used. |
246 | -+ found = self.find(*args, **kwargs) |
247 | -+ try: |
248 | -+ found[0] |
249 | -+ except IndexError: |
250 | -+ pass |
251 | -+ return found |
252 | -+ |
253 | -+ def __getattr__(self, item): |
254 | -+ return getattr(self.conn, item) |
255 | -diff --git a/ceilometer/tests/storage/test_pymongo_base.py b/ceilometer/tests/storage/test_pymongo_base.py |
256 | -index c40bc7c..28c40a9 100644 |
257 | ---- a/ceilometer/tests/storage/test_pymongo_base.py |
258 | -+++ b/ceilometer/tests/storage/test_pymongo_base.py |
259 | -@@ -12,17 +12,13 @@ |
260 | - """Tests the mongodb and db2 common functionality |
261 | - """ |
262 | - |
263 | --import contextlib |
264 | - import copy |
265 | - import datetime |
266 | - |
267 | - import mock |
268 | --import pymongo |
269 | - |
270 | --from ceilometer.openstack.common.gettextutils import _ |
271 | - from ceilometer.publisher import utils |
272 | - from ceilometer import sample |
273 | --from ceilometer.storage.mongo import utils as pymongo_utils |
274 | - from ceilometer.tests import db as tests_db |
275 | - from ceilometer.tests.storage import test_storage_scenarios |
276 | - |
277 | -@@ -168,41 +164,3 @@ class CompatibilityTest(test_storage_scenarios.DBTestBase, |
278 | - def test_counter_unit(self): |
279 | - meters = list(self.conn.get_meters()) |
280 | - self.assertEqual(1, len(meters)) |
281 | -- |
282 | -- def test_mongodb_connect_raises_after_custom_number_of_attempts(self): |
283 | -- retry_interval = 13 |
284 | -- max_retries = 37 |
285 | -- self.CONF.set_override( |
286 | -- 'retry_interval', retry_interval, group='database') |
287 | -- self.CONF.set_override( |
288 | -- 'max_retries', max_retries, group='database') |
289 | -- # PyMongo is being used to connect even to DB2, but it only |
290 | -- # accepts URLs with the 'mongodb' scheme. This replacement is |
291 | -- # usually done in the DB2 connection implementation, but since |
292 | -- # we don't call that, we have to do it here. |
293 | -- self.CONF.set_override( |
294 | -- 'connection', self.db_manager.url.replace('db2:', 'mongodb:', 1), |
295 | -- group='database') |
296 | -- |
297 | -- pool = pymongo_utils.ConnectionPool() |
298 | -- with contextlib.nested( |
299 | -- mock.patch( |
300 | -- 'pymongo.MongoClient', |
301 | -- side_effect=pymongo.errors.ConnectionFailure('foo')), |
302 | -- mock.patch.object(pymongo_utils.LOG, 'error'), |
303 | -- mock.patch.object(pymongo_utils.LOG, 'warn'), |
304 | -- mock.patch.object(pymongo_utils.time, 'sleep') |
305 | -- ) as (MockMongo, MockLOGerror, MockLOGwarn, Mocksleep): |
306 | -- self.assertRaises(pymongo.errors.ConnectionFailure, |
307 | -- pool.connect, self.CONF.database.connection) |
308 | -- Mocksleep.assert_has_calls([mock.call(retry_interval) |
309 | -- for i in range(max_retries)]) |
310 | -- MockLOGwarn.assert_any_call( |
311 | -- _('Unable to connect to the database server: %(errmsg)s.' |
312 | -- ' Trying again in %(retry_interval)d seconds.') % |
313 | -- {'errmsg': 'foo', |
314 | -- 'retry_interval': retry_interval}) |
315 | -- MockLOGerror.assert_called_with( |
316 | -- _('Unable to connect to the database after ' |
317 | -- '%(retries)d retries. Giving up.') % |
318 | -- {'retries': max_retries}) |
319 | -diff --git a/ceilometer/tests/storage/test_storage_scenarios.py b/ceilometer/tests/storage/test_storage_scenarios.py |
320 | -index 5318e99..8024378 100644 |
321 | ---- a/ceilometer/tests/storage/test_storage_scenarios.py |
322 | -+++ b/ceilometer/tests/storage/test_storage_scenarios.py |
323 | -@@ -23,7 +23,9 @@ import datetime |
324 | - import operator |
325 | - |
326 | - import mock |
327 | -+from oslo.config import cfg |
328 | - from oslo.utils import timeutils |
329 | -+import pymongo |
330 | - |
331 | - import ceilometer |
332 | - from ceilometer.alarm.storage import models as alarm_models |
333 | -@@ -3099,3 +3101,89 @@ class BigIntegerTest(tests_db.TestBase, |
334 | - msg = utils.meter_message_from_counter( |
335 | - s, self.CONF.publisher.metering_secret) |
336 | - self.conn.record_metering_data(msg) |
337 | -+ |
338 | -+ |
339 | -+class MongoAutoReconnectTest(DBTestBase, |
340 | -+ tests_db.MixinTestsWithBackendScenarios): |
341 | -+ cfg.CONF.set_override('retry_interval', 1, group='database') |
342 | -+ |
343 | -+ @tests_db.run_with('mongodb') |
344 | -+ def test_mongo_client(self): |
345 | -+ if cfg.CONF.database.mongodb_replica_set: |
346 | -+ self.assertIsInstance(self.conn.conn.conn.conn, |
347 | -+ pymongo.MongoReplicaSetClient) |
348 | -+ else: |
349 | -+ self.assertIsInstance(self.conn.conn.conn.conn, |
350 | -+ pymongo.MongoClient) |
351 | -+ |
352 | -+ @staticmethod |
353 | -+ def create_side_effect(method, test_exception): |
354 | -+ def side_effect(*args, **kwargs): |
355 | -+ if test_exception.pop(): |
356 | -+ raise pymongo.errors.AutoReconnect |
357 | -+ else: |
358 | -+ return method(*args, **kwargs) |
359 | -+ return side_effect |
360 | -+ |
361 | -+ @tests_db.run_with('mongodb') |
362 | -+ def test_mongo_find(self): |
363 | -+ raise_exc = [False, True] |
364 | -+ method = self.conn.db.resource.find |
365 | -+ |
366 | -+ with mock.patch('pymongo.collection.Collection.find', |
367 | -+ mock.Mock()) as mock_find: |
368 | -+ mock_find.side_effect = self.create_side_effect(method, raise_exc) |
369 | -+ mock_find.__name__ = 'find' |
370 | -+ resources = list(self.conn.get_resources()) |
371 | -+ self.assertEqual(9, len(resources)) |
372 | -+ |
373 | -+ @tests_db.run_with('mongodb') |
374 | -+ def test_mongo_insert(self): |
375 | -+ raise_exc = [False, True] |
376 | -+ method = self.conn.db.meter.insert |
377 | -+ |
378 | -+ with mock.patch('pymongo.collection.Collection.insert', |
379 | -+ mock.Mock(return_value=method)) as mock_insert: |
380 | -+ mock_insert.side_effect = self.create_side_effect(method, |
381 | -+ raise_exc) |
382 | -+ mock_insert.__name__ = 'insert' |
383 | -+ self.create_and_store_sample( |
384 | -+ timestamp=datetime.datetime(2014, 10, 15, 14, 39), |
385 | -+ source='test-proxy') |
386 | -+ meters = list(self.conn.db.meter.find()) |
387 | -+ self.assertEqual(12, len(meters)) |
388 | -+ |
389 | -+ @tests_db.run_with('mongodb') |
390 | -+ def test_mongo_find_and_modify(self): |
391 | -+ raise_exc = [False, True] |
392 | -+ method = self.conn.db.resource.find_and_modify |
393 | -+ |
394 | -+ with mock.patch('pymongo.collection.Collection.find_and_modify', |
395 | -+ mock.Mock()) as mock_fam: |
396 | -+ mock_fam.side_effect = self.create_side_effect(method, raise_exc) |
397 | -+ mock_fam.__name__ = 'find_and_modify' |
398 | -+ self.create_and_store_sample( |
399 | -+ timestamp=datetime.datetime(2014, 10, 15, 14, 39), |
400 | -+ source='test-proxy') |
401 | -+ data = self.conn.db.resource.find( |
402 | -+ {'last_sample_timestamp': |
403 | -+ datetime.datetime(2014, 10, 15, 14, 39)})[0]['source'] |
404 | -+ self.assertEqual('test-proxy', data) |
405 | -+ |
406 | -+ @tests_db.run_with('mongodb') |
407 | -+ def test_mongo_update(self): |
408 | -+ raise_exc = [False, True] |
409 | -+ method = self.conn.db.resource.update |
410 | -+ |
411 | -+ with mock.patch('pymongo.collection.Collection.update', |
412 | -+ mock.Mock()) as mock_update: |
413 | -+ mock_update.side_effect = self.create_side_effect(method, |
414 | -+ raise_exc) |
415 | -+ mock_update.__name__ = 'update' |
416 | -+ self.create_and_store_sample( |
417 | -+ timestamp=datetime.datetime(2014, 10, 15, 17, 39), |
418 | -+ source='test-proxy-update') |
419 | -+ data = self.conn.db.resource.find( |
420 | -+ {'last_sample_timestamp': |
421 | -+ datetime.datetime(2014, 10, 15, 17, 39)})[0]['source'] |
422 | -+ self.assertEqual('test-proxy-update', data) |
423 | --- |
424 | -2.1.0 |
425 | - |
426 | |
427 | === modified file 'debian/patches/series' |
428 | --- debian/patches/series 2015-02-09 13:55:53 +0000 |
429 | +++ debian/patches/series 2015-02-13 15:02:15 +0000 |
430 | @@ -1,3 +1,2 @@ |
431 | fix-requirements.patch |
432 | skip-db-tests.patch |
433 | -mongodb-autoreconnect.patch |