Merge lp:~vds/desktopcouch/views-with-reconnector-proxy into lp:desktopcouch

Proposed by Vincenzo Di Somma
Status: Merged
Approved by: Manuel de la Peña
Approved revision: 184
Merged at revision: 175
Proposed branch: lp:~vds/desktopcouch/views-with-reconnector-proxy
Merge into: lp:desktopcouch
Diff against target: 480 lines (+172/-21)
4 files modified
desktopcouch/records/server_base.py (+77/-15)
desktopcouch/records/tests/test_server.py (+86/-4)
desktopcouch/start_local_couchdb.py (+8/-1)
desktopcouch/stop_local_couchdb.py (+1/-1)
To merge this branch: bzr merge lp:~vds/desktopcouch/views-with-reconnector-proxy
Reviewer Review Type Date Requested Status
Manuel de la Peña (community) Approve
Eric Casteleijn (community) Approve
Review via email: mp+35853@code.launchpad.net

Commit message

Now View and ViewResults can reconnect if the couchdb server crashes.

Description of the change

Now ViewResult reconnects when the server crashes.

To post a comment you must log in.
Revision history for this message
Eric Casteleijn (thisfred) wrote :
Download full text (40.6 KiB)

Ooooh, live monkeys!

Code looks good, but the tests take ages now, and it looks like the server is killed and restarted way too often, though most of the tests seem to pass. Adding test output below. If this is all intentional, approved.

One test does fail, but I believe that's because I'm on lucid:

===============================================================================
[ERROR]: desktopcouch.pair.tests.test_ubuntuone_pairing

Traceback (most recent call last):
  File "/usr/lib/python2.6/dist-packages/twisted/trial/runner.py", line 563, in loadPackage
    module = modinfo.load()
  File "/usr/lib/python2.6/dist-packages/twisted/python/modules.py", line 381, in load
    return self.pathEntry.pythonPath.moduleLoader(self.name)
  File "/usr/lib/python2.6/dist-packages/twisted/python/reflect.py", line 464, in namedAny
    topLevelPackage = _importAndCheckStack(trialname)
  File "/home/eric/canonical/desktopcouch/r-vds/desktopcouch/pair/tests/test_ubuntuone_pairing.py", line 19, in <module>
    from mocker import Mocker
exceptions.ImportError: No module named mocker
-------------------------------------------------------------------------------

eric@eric-laptop:~/canonical/desktopcouch/r-vds$ PYTHONPATH=. trial desktopcouch
Apache CouchDB has started, time to relax.
Browse your desktop CouchDB at file:///tmp/tmpEaIpq4/data/couchdb.html
desktopcouch.bookmarks.tests.test_record
  TestBookmarkRecord
    test_bookmark_record ... [OK]
  TestFeedRecord
    test_feed_record ... [OK]
  TestFolderRecord
    test_folder_record ... [OK]
  TestSeparatorRecord
    test_separator_record ... [OK]
desktopcouch.contacts.tests.test_contactspicker
  TestContactsPicker
    test_can_contruct_contactspicker ... [OK]
desktopcouch.contacts.tests.test_create
  TestCreate
    test_create_many_contacts ... [OK]
    test_head_or_tails ... [OK]
    test_random_bools ... [OK]
desktopcouch.contacts.tests.test_record
  TestContactRecord
    test_contact_record ... [OK]
desktopcouch.contacts.tests.test_view
  TestLocalFiles
    test_find_contact_starting ... [OK]
    test_find_contacts_exact ... [OK]
desktopcouch.notes.tests.test_record
  TestNoteRecord
    test_note_record ... [OK]
desktopcouch.pair.tests.test_couchdb_io
  TestCouchdbIo
    test_get_database_names_replicatable ... [OK]
    test_get_database_names_replicatable_bad_server ... [SKIPPED]
    test_get_my_host_unique_id ... [OK]
    test_mkuri ... [OK]
    test_obsfuscation ... [OK]
   ...

review: Approve
Revision history for this message
Vincenzo Di Somma (vds) wrote :

> Ooooh, live monkeys!
>
> Code looks good, but the tests take ages now, and it looks like the server is
> killed and restarted way too often, though most of the tests seem to pass.
> Adding test output below. If this is all intentional, approved.
>
> One test does fail, but I believe that's because I'm on lucid:

About the test failing, mocker is in Lucid and should also be a developer dependency, if you install the package the test will pass, I'll file a bug to update our dependency packages.

The couchdb server is killed and restarted many time on purpose, to check that desktopcouch is able to reconnect to a different instance of couchdb if the previous one crashed.
It slows down the tests a lot I know. But there's not much we can do here apart from moving that logic somewhere else, like in python-couchdb.
the weird text couchdb's fault, I'll try to hide it in the next branch.

Revision history for this message
Manuel de la Peña (mandel) wrote :

All tests pass. I just have a question, dont we have a better name for a method than _is_bug_lp539674. I know we can go to lp and check that out, but it would be nice a more self explanatory name.

Revision history for this message
Manuel de la Peña (mandel) wrote :

Cool +!

review: Approve
Revision history for this message
dobey (dobey) wrote :
Download full text (109.7 KiB)

The attempt to merge lp:~vds/desktopcouch/views-with-reconnector-proxy into lp:desktopcouch failed. Below is the output from the failed tests.

running build
running build_py
creating build
creating build/lib.linux-i686-2.6
creating build/lib.linux-i686-2.6/desktopcouch
copying desktopcouch/stop_local_couchdb.py -> build/lib.linux-i686-2.6/desktopcouch
copying desktopcouch/start_local_couchdb.py -> build/lib.linux-i686-2.6/desktopcouch
copying desktopcouch/replication.py -> build/lib.linux-i686-2.6/desktopcouch
copying desktopcouch/local_files.py -> build/lib.linux-i686-2.6/desktopcouch
copying desktopcouch/__init__.py -> build/lib.linux-i686-2.6/desktopcouch
creating build/lib.linux-i686-2.6/desktopcouch/tests
copying desktopcouch/tests/test_start_local_couchdb.py -> build/lib.linux-i686-2.6/desktopcouch/tests
copying desktopcouch/tests/test_local_files.py -> build/lib.linux-i686-2.6/desktopcouch/tests
copying desktopcouch/tests/__init__.py -> build/lib.linux-i686-2.6/desktopcouch/tests
copying desktopcouch/tests/test_replication.py -> build/lib.linux-i686-2.6/desktopcouch/tests
creating build/lib.linux-i686-2.6/desktopcouch/tasks
copying desktopcouch/tasks/record.py -> build/lib.linux-i686-2.6/desktopcouch/tasks
copying desktopcouch/tasks/__init__.py -> build/lib.linux-i686-2.6/desktopcouch/tasks
creating build/lib.linux-i686-2.6/desktopcouch/pair
copying desktopcouch/pair/__init__.py -> build/lib.linux-i686-2.6/desktopcouch/pair
creating build/lib.linux-i686-2.6/desktopcouch/records
copying desktopcouch/records/field_registry.py -> build/lib.linux-i686-2.6/desktopcouch/records
copying desktopcouch/records/record.py -> build/lib.linux-i686-2.6/desktopcouch/records
copying desktopcouch/records/server.py -> build/lib.linux-i686-2.6/desktopcouch/records
copying desktopcouch/records/couchgrid.py -> build/lib.linux-i686-2.6/desktopcouch/records
copying desktopcouch/records/__init__.py -> build/lib.linux-i686-2.6/desktopcouch/records
copying desktopcouch/records/server_base.py -> build/lib.linux-i686-2.6/desktopcouch/records
creating build/lib.linux-i686-2.6/desktopcouch/replication_services
copying desktopcouch/replication_services/ubuntuone.py -> build/lib.linux-i686-2.6/desktopcouch/replication_services
copying desktopcouch/replication_services/__init__.py -> build/lib.linux-i686-2.6/desktopcouch/replication_services
copying desktopcouch/replication_services/example.py -> build/lib.linux-i686-2.6/desktopcouch/replication_services
creating build/lib.linux-i686-2.6/desktopcouch/notes
copying desktopcouch/notes/record.py -> build/lib.linux-i686-2.6/desktopcouch/notes
copying desktopcouch/notes/__init__.py -> build/lib.linux-i686-2.6/desktopcouch/notes
creating build/lib.linux-i686-2.6/desktopcouch/bookmarks
copying desktopcouch/bookmarks/record.py -> build/lib.linux-i686-2.6/desktopcouch/bookmarks
copying desktopcouch/bookmarks/__init__.py -> build/lib.linux-i686-2.6/desktopcouch/bookmarks
creating build/lib.linux-i686-2.6/desktopcouch/contacts
copying desktopcouch/contacts/record.py -> build/lib.linux-i686-2.6/desktopcouch/contacts
copying desktopcouch/contacts/view.py -> build/lib.linux-i686-2.6/desktopcouch/contacts
copying desktopcouch/contac...

Revision history for this message
dobey (dobey) wrote :
Download full text (112.7 KiB)

The attempt to merge lp:~vds/desktopcouch/views-with-reconnector-proxy into lp:desktopcouch failed. Below is the output from the failed tests.

running build
running build_py
creating build
creating build/lib.linux-i686-2.6
creating build/lib.linux-i686-2.6/desktopcouch
copying desktopcouch/stop_local_couchdb.py -> build/lib.linux-i686-2.6/desktopcouch
copying desktopcouch/start_local_couchdb.py -> build/lib.linux-i686-2.6/desktopcouch
copying desktopcouch/replication.py -> build/lib.linux-i686-2.6/desktopcouch
copying desktopcouch/local_files.py -> build/lib.linux-i686-2.6/desktopcouch
copying desktopcouch/__init__.py -> build/lib.linux-i686-2.6/desktopcouch
creating build/lib.linux-i686-2.6/desktopcouch/tests
copying desktopcouch/tests/test_start_local_couchdb.py -> build/lib.linux-i686-2.6/desktopcouch/tests
copying desktopcouch/tests/test_local_files.py -> build/lib.linux-i686-2.6/desktopcouch/tests
copying desktopcouch/tests/__init__.py -> build/lib.linux-i686-2.6/desktopcouch/tests
copying desktopcouch/tests/test_replication.py -> build/lib.linux-i686-2.6/desktopcouch/tests
creating build/lib.linux-i686-2.6/desktopcouch/tasks
copying desktopcouch/tasks/record.py -> build/lib.linux-i686-2.6/desktopcouch/tasks
copying desktopcouch/tasks/__init__.py -> build/lib.linux-i686-2.6/desktopcouch/tasks
creating build/lib.linux-i686-2.6/desktopcouch/pair
copying desktopcouch/pair/__init__.py -> build/lib.linux-i686-2.6/desktopcouch/pair
creating build/lib.linux-i686-2.6/desktopcouch/records
copying desktopcouch/records/field_registry.py -> build/lib.linux-i686-2.6/desktopcouch/records
copying desktopcouch/records/record.py -> build/lib.linux-i686-2.6/desktopcouch/records
copying desktopcouch/records/server.py -> build/lib.linux-i686-2.6/desktopcouch/records
copying desktopcouch/records/couchgrid.py -> build/lib.linux-i686-2.6/desktopcouch/records
copying desktopcouch/records/__init__.py -> build/lib.linux-i686-2.6/desktopcouch/records
copying desktopcouch/records/server_base.py -> build/lib.linux-i686-2.6/desktopcouch/records
creating build/lib.linux-i686-2.6/desktopcouch/replication_services
copying desktopcouch/replication_services/ubuntuone.py -> build/lib.linux-i686-2.6/desktopcouch/replication_services
copying desktopcouch/replication_services/__init__.py -> build/lib.linux-i686-2.6/desktopcouch/replication_services
copying desktopcouch/replication_services/example.py -> build/lib.linux-i686-2.6/desktopcouch/replication_services
creating build/lib.linux-i686-2.6/desktopcouch/notes
copying desktopcouch/notes/record.py -> build/lib.linux-i686-2.6/desktopcouch/notes
copying desktopcouch/notes/__init__.py -> build/lib.linux-i686-2.6/desktopcouch/notes
creating build/lib.linux-i686-2.6/desktopcouch/bookmarks
copying desktopcouch/bookmarks/record.py -> build/lib.linux-i686-2.6/desktopcouch/bookmarks
copying desktopcouch/bookmarks/__init__.py -> build/lib.linux-i686-2.6/desktopcouch/bookmarks
creating build/lib.linux-i686-2.6/desktopcouch/contacts
copying desktopcouch/contacts/record.py -> build/lib.linux-i686-2.6/desktopcouch/contacts
copying desktopcouch/contacts/view.py -> build/lib.linux-i686-2.6/desktopcouch/contacts
copying desktopcouch/contac...

Revision history for this message
dobey (dobey) wrote :
Download full text (112.3 KiB)

The attempt to merge lp:~vds/desktopcouch/views-with-reconnector-proxy into lp:desktopcouch failed. Below is the output from the failed tests.

running build
running build_py
creating build
creating build/lib.linux-i686-2.6
creating build/lib.linux-i686-2.6/desktopcouch
copying desktopcouch/stop_local_couchdb.py -> build/lib.linux-i686-2.6/desktopcouch
copying desktopcouch/start_local_couchdb.py -> build/lib.linux-i686-2.6/desktopcouch
copying desktopcouch/replication.py -> build/lib.linux-i686-2.6/desktopcouch
copying desktopcouch/local_files.py -> build/lib.linux-i686-2.6/desktopcouch
copying desktopcouch/__init__.py -> build/lib.linux-i686-2.6/desktopcouch
creating build/lib.linux-i686-2.6/desktopcouch/tests
copying desktopcouch/tests/test_start_local_couchdb.py -> build/lib.linux-i686-2.6/desktopcouch/tests
copying desktopcouch/tests/test_local_files.py -> build/lib.linux-i686-2.6/desktopcouch/tests
copying desktopcouch/tests/__init__.py -> build/lib.linux-i686-2.6/desktopcouch/tests
copying desktopcouch/tests/test_replication.py -> build/lib.linux-i686-2.6/desktopcouch/tests
creating build/lib.linux-i686-2.6/desktopcouch/tasks
copying desktopcouch/tasks/record.py -> build/lib.linux-i686-2.6/desktopcouch/tasks
copying desktopcouch/tasks/__init__.py -> build/lib.linux-i686-2.6/desktopcouch/tasks
creating build/lib.linux-i686-2.6/desktopcouch/pair
copying desktopcouch/pair/__init__.py -> build/lib.linux-i686-2.6/desktopcouch/pair
creating build/lib.linux-i686-2.6/desktopcouch/records
copying desktopcouch/records/field_registry.py -> build/lib.linux-i686-2.6/desktopcouch/records
copying desktopcouch/records/record.py -> build/lib.linux-i686-2.6/desktopcouch/records
copying desktopcouch/records/server.py -> build/lib.linux-i686-2.6/desktopcouch/records
copying desktopcouch/records/couchgrid.py -> build/lib.linux-i686-2.6/desktopcouch/records
copying desktopcouch/records/__init__.py -> build/lib.linux-i686-2.6/desktopcouch/records
copying desktopcouch/records/server_base.py -> build/lib.linux-i686-2.6/desktopcouch/records
creating build/lib.linux-i686-2.6/desktopcouch/replication_services
copying desktopcouch/replication_services/ubuntuone.py -> build/lib.linux-i686-2.6/desktopcouch/replication_services
copying desktopcouch/replication_services/__init__.py -> build/lib.linux-i686-2.6/desktopcouch/replication_services
copying desktopcouch/replication_services/example.py -> build/lib.linux-i686-2.6/desktopcouch/replication_services
creating build/lib.linux-i686-2.6/desktopcouch/notes
copying desktopcouch/notes/record.py -> build/lib.linux-i686-2.6/desktopcouch/notes
copying desktopcouch/notes/__init__.py -> build/lib.linux-i686-2.6/desktopcouch/notes
creating build/lib.linux-i686-2.6/desktopcouch/bookmarks
copying desktopcouch/bookmarks/record.py -> build/lib.linux-i686-2.6/desktopcouch/bookmarks
copying desktopcouch/bookmarks/__init__.py -> build/lib.linux-i686-2.6/desktopcouch/bookmarks
creating build/lib.linux-i686-2.6/desktopcouch/contacts
copying desktopcouch/contacts/record.py -> build/lib.linux-i686-2.6/desktopcouch/contacts
copying desktopcouch/contacts/view.py -> build/lib.linux-i686-2.6/desktopcouch/contacts
copying desktopcouch/contac...

183. By Vincenzo Di Somma

fixed issue with keyring

184. By Vincenzo Di Somma

logging when the issue happens

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'desktopcouch/records/server_base.py'
2--- desktopcouch/records/server_base.py 2010-08-23 22:51:48 +0000
3+++ desktopcouch/records/server_base.py 2010-09-22 14:14:44 +0000
4@@ -150,22 +150,24 @@
5 self._server_class_extras = server_class_extras
6 self.record_factory = record_factory or Record
7 self.server_uri = uri
8+ self._server = None
9+ self.db = None
10 self._reconnect()
11 self._changes_since = self.db.info()["update_seq"]
12 self._changes_last_used = 0 # Immediate run works.
13
14 @staticmethod
15- def _is_bug_lp539674(ex):
16+ def _is_reconnection_fail(ex):
17 return isinstance(ex, AttributeError) and \
18 ex.args == ("'NoneType' object has no attribute 'makefile'",)
19
20 def with_reconnects(self, func, *args, **kwargs):
21- for retry in (2, 1, None):
22+ for retry in (3, 2, 1, None):
23 try:
24 return func(*args, **kwargs)
25 except Exception, e:
26- if self._is_bug_lp539674(e) and retry:
27- logging.warn("DB connection timed out. Reconnecting.")
28+ if self._is_reconnection_fail(e) and retry:
29+ logging.warn("DB connection failed. Reconnecting.")
30 self._reconnect()
31 continue
32 elif isinstance(e, socket.error):
33@@ -175,9 +177,10 @@
34 continue
35 else:
36 raise
37+ raise ValueError("failed to (re-)connect to couchdb server")
38
39 def _reconnect(self, uri=None):
40- logging.info("Connecting to %s." % (self.server_uri or "discovered local port",))
41+ logging.info("Connecting to %s.", self.server_uri or "discovered local port")
42
43 self._server = self._server_class(uri or self.server_uri,
44 **self._server_class_extras)
45@@ -186,7 +189,12 @@
46 self._server.create(self._database_name)
47 else:
48 raise NoSuchDatabase(self._database_name)
49- self.db = self._server[self._database_name]
50+ if self.db is None:
51+ self.db = self._server[self._database_name]
52+ else:
53+ # Monkey-patch the object the user already uses. Oook!
54+ new_db = self._server[self._database_name]
55+ self.db.resource = new_db.resource
56
57 def _temporary_query(self, map_fun, reduce_fun=None, language='javascript',
58 wrapper=None, **options):
59@@ -381,9 +389,10 @@
60
61 def record_exists(self, record_id):
62 """Check if record with given id exists."""
63- if record_id not in self.db:
64+ try:
65+ record = self.with_reconnects(self.db.__getitem__, record_id)
66+ except ResourceNotFound:
67 return False
68- record = self.with_reconnects(self.db.__getitem__, record_id)
69 return not row_is_deleted(record)
70
71 def delete_view(self, view_name, design_doc=DEFAULT_DESIGN_DOCUMENT):
72@@ -434,23 +443,28 @@
73 **params):
74 """Execute view and return results."""
75
76- class ReconnectingViewWrapper:
77+ class ReconnectingViewWrapper(object):
78 """A view from python-couchdb is an object with attributes that
79 cause HTTP requests to be fired off when accessed. If we wish
80 to be able to reconnect on disappearance of the server, then
81 we must intercept calls from user to python-couchdb."""
82
83- def __init__(wrapper, view, *args, **kwargs):
84- wrapper.view = self.with_reconnects(view, *args, **kwargs)
85+ def __init__(wrapper, obj, *args, **kwargs):
86+ wrapper.obj = self.with_reconnects(obj, *args, **kwargs)
87
88 def ___call__(wrapper, **options):
89- return self.with_reconnects(wrapper.view.__call__, **options)
90+ return self.with_reconnects(wrapper.obj.__call__, **options)
91
92 def __iter__(wrapper):
93- return self.with_reconnects(wrapper.view.__iter__)
94+ return self.view_with_reconnects(wrapper.obj.__iter__,
95+ wrapper.obj)
96+
97+ def __len__(wrapper):
98+ return self.view_with_reconnects(wrapper.obj.__len__,
99+ wrapper.obj)
100
101 def __getitem__(wrapper, key):
102- return self.with_reconnects(wrapper.view.__getitem__, key)
103+ return ReconnectingViewWrapper(wrapper.obj.__getitem__, key)
104
105 if design_doc is None:
106 design_doc = view_name
107@@ -461,6 +475,44 @@
108 **params)
109 return wrapper
110
111+ def view_with_reconnects(self, func, view, *args, **kwargs):
112+ for retry in (3, 2, 1, None):
113+ try:
114+ return func(*args, **kwargs)
115+ except Exception, e:
116+ if self._is_reconnection_fail(e) and retry:
117+ logging.warn("DB connection failed. Reconnecting.")
118+ self._reconnect_view(view_result=view)
119+ continue
120+ elif isinstance(e, socket.error):
121+ logging.warn("Other socket error %s. Reconnecting.", e)
122+ sleep(0.3)
123+ self._reconnect()
124+ continue
125+ else:
126+ raise
127+ raise ValueError("failed to (re-)connect to couchdb server")
128+
129+ def _reconnect_view(self, uri=None, view_result=None):
130+ logging.info("Connecting to %s.",
131+ self.server_uri or "discovered local port")
132+ self._server = self._server_class(
133+ uri or '/'.join(self.db.resource.uri.split('/')[:-1]),
134+ **self._server_class_extras)
135+ if self._database_name not in self._server:
136+ if self._create:
137+ self._server.create(self._database_name)
138+ else:
139+ raise NoSuchDatabase(self._database_name)
140+ if self.db is None:
141+ self.db = self._server[self._database_name]
142+ else:
143+ # Monkey-patch the object the user already uses. Oook!
144+ view_url = urlparse.urlparse(view_result.view.resource.uri)
145+ db_url = urlparse.urlparse(self.db.resource.uri)
146+ view_result.view.resource.uri = urlparse.urlunparse(
147+ [view_url[0], db_url[1]] + list(view_url[2:]))
148+
149 def add_view(self, view_name, map_js, reduce_js,
150 design_doc=DEFAULT_DESIGN_DOCUMENT):
151 """Create a view, given a name and the two parts (map and reduce).
152@@ -469,7 +521,7 @@
153 design_doc = view_name
154
155 view = ViewDefinition(design_doc, view_name, map_js, reduce_js)
156- view.sync(self.db)
157+ self.with_reconnects(view.sync, self.db)
158 assert self.view_exists(view_name, design_doc)
159
160 def view_exists(self, view_name, design_doc=DEFAULT_DESIGN_DOCUMENT):
161@@ -595,6 +647,7 @@
162 uri = couchdburi(
163 self._server.resource.uri, self.db.name, "_changes",
164 since=self._changes_since)
165+ ## Assume server has not crashed and URI is the same. FIXME
166 resp, data = self._server.resource.http.request(uri, "GET", "", {})
167 if resp["status"] != '200':
168 raise IOError(
169@@ -611,3 +664,12 @@
170 # If exception in cb, we never update governor.
171 self._changes_last_used = now
172 return call_count
173+
174+ def ensure_full_commit(self):
175+ """
176+ Make sure that CouchDb flushes all writes to the database,
177+ flushing all delayed commits, before going on.
178+ """
179+ self.db.resource.post(
180+ path='_ensure_full_commit',
181+ headers={'Content-Type': 'application/json'})
182
183=== modified file 'desktopcouch/records/tests/test_server.py'
184--- desktopcouch/records/tests/test_server.py 2010-07-02 15:42:05 +0000
185+++ desktopcouch/records/tests/test_server.py 2010-09-22 14:14:44 +0000
186@@ -19,12 +19,17 @@
187
188 """testing database/contact.py module"""
189 import testtools
190+import os
191+import signal
192+import time
193
194 import desktopcouch.tests as test_environment
195 from desktopcouch.records.server import CouchDatabase
196 from desktopcouch.records.server_base import (
197 row_is_deleted, NoSuchDatabase, FieldsConflict, ResourceConflict)
198 from desktopcouch.records.record import Record
199+from desktopcouch.stop_local_couchdb import stop_couchdb
200+from desktopcouch import find_pid
201
202 # pylint can't deal with failing imports even when they're handled
203 # pylint: disable-msg=F0401
204@@ -53,7 +58,7 @@
205 # Connect to CouchDB server
206 self.dbname = self._testMethodName
207 self.database = CouchDatabase(self.dbname, create=True,
208- ctx=test_environment.test_context)
209+ ctx=self.get_test_context())
210 #create some records to pull out and test
211 self.database.put_record(Record({
212 "key1_1": "val1_1", "key1_2": "val1_2", "key1_3": "val1_3",
213@@ -67,8 +72,28 @@
214
215 def tearDown(self):
216 """tear down each test"""
217+ this_context = self.get_test_context()
218+ if this_context != test_environment.test_context:
219+ stop_couchdb(ctx=this_context)
220 super(TestCouchDatabase, self).tearDown()
221- del self.database._server[self.dbname]
222+
223+ def get_test_context(self):
224+ return test_environment.test_context
225+
226+ def maybe_die(self):
227+ pass
228+
229+ def wait_until_server_dead(self, pid=None):
230+ if pid is not None:
231+ pid = find_pid(start_if_not_running=False, ctx=self.get_test_context())
232+ if pid is None:
233+ return
234+ while True:
235+ try:
236+ os.kill(pid, 0) # Wait until exited
237+ time.sleep(0.1)
238+ except OSError:
239+ break
240
241 def test_database_not_exists(self):
242 self.assertRaises(
243@@ -78,12 +103,14 @@
244 """Test getting mutliple records by type"""
245 records = self.database.get_records(
246 record_type="test.com",create_view=True)
247- self.assertEqual(3,len(records))
248+ self.maybe_die() # should be able to survive couchdb death
249+ self.assertEqual(3, len(records))
250
251 def test_get_record(self):
252 """Test getting a record."""
253 record = Record({'record_number': 0}, record_type="http://example.com/")
254 record_id = self.database.put_record(record)
255+ self.maybe_die() # should be able to survive couchdb death
256 retrieved_record = self.database.get_record(record_id)
257 self.assertEqual(0, retrieved_record['record_number'])
258
259@@ -91,7 +118,8 @@
260 """Test putting a record."""
261 record = Record({'record_number': 0}, record_type="http://example.com/")
262 record_id = self.database.put_record(record)
263- retrieved_record = self.database._server[self.dbname][record_id]
264+ self.maybe_die() # should be able to survive couchdb death
265+ retrieved_record = self.database.get_record(record_id)
266 self.assertEqual(
267 record['record_number'], retrieved_record['record_number'])
268
269@@ -121,7 +149,9 @@
270 record = Record({'record_number': 0}, record_type="http://example.com/")
271 record_id = self.database.put_record(record)
272 self.database.delete_record(record_id)
273+ ###self.maybe_die() # should be able to survive couchdb death
274 deleted_record = self.database._server[self.dbname][record_id]
275+ #deleted_record = self.database.get_record(record_id)
276 self.assert_(deleted_record['application_annotations']['Ubuntu One'][
277 'private_application_annotations']['deleted'])
278
279@@ -154,6 +184,7 @@
280 record = Record({'record_number': 0}, record_type="http://example.com/")
281 record_id = self.database.put_record(record)
282 self.database.delete_record(record_id)
283+ self.maybe_die() # should be able to survive couchdb death
284 retrieved_record = self.database.get_record(record_id)
285 self.assertEqual(None, retrieved_record)
286
287@@ -162,6 +193,7 @@
288 record = Record({'record_number': 0}, record_type="http://example.com/")
289 self.assert_(not self.database.record_exists("ThisMustNotExist"))
290 record_id = self.database.put_record(record)
291+ self.maybe_die() # should be able to survive couchdb death
292 self.assert_(self.database.record_exists(record_id))
293 self.database.delete_record(record_id)
294 self.assert_(not self.database.record_exists(record_id))
295@@ -171,12 +203,14 @@
296 dictionary = {'record_number': 0, 'field1': 1, 'field2': 2}
297 record = Record(dictionary, record_type="http://example.com/")
298 record_id = self.database.put_record(record)
299+ self.maybe_die() # should be able to survive couchdb death
300 # manipulate the database 'out of view'
301 non_working_copy = self.database.get_record(record_id)
302 non_working_copy['field2'] = 22
303 non_working_copy['field3'] = 3
304 self.database.put_record(non_working_copy)
305 self.database.update_fields(record_id, {'field1': 11})
306+ self.maybe_die() # should be able to survive couchdb death
307 working_copy = self.database.get_record(record_id)
308 self.assertEqual(0, working_copy['record_number'])
309 self.assertEqual(11, working_copy['field1'])
310@@ -200,10 +234,12 @@
311 self.assertRaises(
312 KeyError, self.database.delete_view, view2_name, design_doc)
313 self.database.add_view(view1_name, map_js, reduce_js, design_doc)
314+ self.maybe_die() # should be able to survive couchdb death
315 self.database.add_view(view2_name, map_js, reduce_js, design_doc)
316 self.database.delete_view(view1_name, design_doc)
317 self.assertRaises(
318 KeyError, self.database.delete_view, view1_name, design_doc)
319+ self.maybe_die() # should be able to survive couchdb death
320 self.database.delete_view(view2_name, design_doc)
321 self.assertRaises(
322 KeyError, self.database.delete_view, view2_name, design_doc)
323@@ -237,6 +273,7 @@
324 record_ids_we_care_about.remove(row.id)
325 self.assertFalse(row_is_deleted(row))
326
327+ self.maybe_die() # should be able to survive couchdb death
328 self.assertTrue(len(record_ids_we_care_about) == 0, "expected zero")
329
330 self.assertRaises(KeyError, self.database.get_records,
331@@ -250,6 +287,7 @@
332 map_js = """function(doc) { emit(doc._id, null) }"""
333 self.database.add_view(view_name, map_js, None, design_doc)
334
335+ self.maybe_die() # should be able to survive couchdb death
336 self.assertEqual(self.database.list_views(design_doc), [view_name])
337 self.database.delete_view(view_name, design_doc)
338
339@@ -257,11 +295,13 @@
340
341 def test_get_view_by_type_new_but_already(self):
342 self.database.get_records(create_view=True)
343+ self.maybe_die() # should be able to survive couchdb death
344 self.database.get_records(create_view=True)
345 # No exceptions on second run? Yay.
346
347 def test_get_view_by_type_createxcl_fail(self):
348 self.database.get_records(create_view=True)
349+ self.maybe_die() # should be able to survive couchdb death
350 self.assertRaises(KeyError, self.database.get_records, create_view=None)
351
352 def test_get_changes(self):
353@@ -321,6 +361,8 @@
354 # Ensure time is same.
355 self.assertEqual(saved_time, self.database._changes_last_used)
356
357+ ###self.maybe_die() # should be able to survive couchdb death
358+
359 # Next time we run, we get the same event again.
360 # Consume queued changes.
361 count = self.database.report_changes(rep)
362@@ -370,6 +412,7 @@
363 constructed_record.attach(content, "nu/mbe/rs", "text/plain")
364 constructed_record.attach("string", "another document", "text/plain")
365
366+ ###self.maybe_die() # should be able to survive couchdb death
367 constructed_record.attach("XXXXXXXXX", "never used", "text/plain")
368 constructed_record.detach("never used") # detach works before commit.
369
370@@ -382,6 +425,7 @@
371 self.assertRaises(KeyError, constructed_record.attach, content,
372 "another document", "text/x-rst")
373
374+ ###self.maybe_die() # should be able to survive couchdb death
375 record_id = self.database.put_record(constructed_record)
376 retrieved_record = self.database.get_record(record_id)
377
378@@ -402,6 +446,7 @@
379 # get new
380 retrieved_record = self.database.get_record(record_id)
381
382+ ###self.maybe_die() # should be able to survive couchdb death
383 # We can get a list of attachments.
384 self.assertEqual(set(retrieved_record.list_attachments()),
385 set(["nu/mbe/rs", "Document"]))
386@@ -424,6 +469,7 @@
387 self.assertEqual(out_data, content.getvalue())
388 self.assertEqual(out_content_type, "text/plain")
389
390+ ###self.maybe_die() # should be able to survive couchdb death
391 # Asking for a named document that does not exist causes KeyError.
392 self.assertRaises(KeyError, retrieved_record.attachment_data,
393 "NoExist")
394@@ -454,6 +500,7 @@
395 # ordinary requests are in key order
396 self.assertEqual(data, sorted(data))
397
398+ self.maybe_die() # should be able to survive couchdb death
399 # now request descending order and confirm that it *is* descending
400 descdata = [i.key for i in
401 list(self.database.execute_view(view1_name, design_doc,
402@@ -513,3 +560,38 @@
403 self.fail()
404 except FieldsConflict, e:
405 self.assertEqual({('field1',): (22, 11)}, e.conflicts)
406+
407+
408+class TestServerDiesSegv(TestCouchDatabase):
409+ def get_test_context(self):
410+ try:
411+ return self.ctx
412+ except AttributeError:
413+ self.ctx = test_environment.create_new_test_environment()
414+ return self.ctx
415+
416+ def maybe_die(self):
417+ self.database.ensure_full_commit()
418+ time.sleep(2)
419+ pid = find_pid(start_if_not_running=False, ctx=self.get_test_context())
420+ if pid is None:
421+ print "couchdb has already quit! That's unexpected."
422+ return
423+ print "DIE, process", pid, "!"
424+ os.kill(pid, signal.SIGSEGV)
425+ self.wait_until_server_dead(pid=pid)
426+
427+
428+class TestServerDiesNormal(TestCouchDatabase):
429+ def get_test_context(self):
430+ try:
431+ return self.ctx
432+ except AttributeError:
433+ self.ctx = test_environment.create_new_test_environment()
434+ return self.ctx
435+
436+ def maybe_die(self):
437+ self.database.ensure_full_commit()
438+ time.sleep(2)
439+ stop_couchdb(ctx=self.get_test_context())
440+ self.wait_until_server_dead()
441
442=== modified file 'desktopcouch/start_local_couchdb.py'
443--- desktopcouch/start_local_couchdb.py 2010-09-17 15:24:19 +0000
444+++ desktopcouch/start_local_couchdb.py 2010-09-22 14:14:44 +0000
445@@ -47,6 +47,8 @@
446 import sys
447 import time
448
449+from gnomekeyring import NoKeyringDaemonError
450+
451 import desktopcouch
452 from desktopcouch import local_files, read_pidfile, process_is_couchdb
453 from desktopcouch.records.server import CouchDatabase
454@@ -216,7 +218,12 @@
455 logging.info("Ubuntu SSO dbus service could not be used.")
456
457 if sso:
458- credentials = sso.find_credentials('Ubuntu One')
459+ try:
460+ credentials = sso.find_credentials('Ubuntu One')
461+ except NoKeyringDaemonError:
462+ logging.info(
463+ "NoKeyringDaemonError exception raised.")
464+ credentials = None
465 if credentials and len(credentials) > 0:
466 pair_with_ubuntuone()
467
468
469=== modified file 'desktopcouch/stop_local_couchdb.py'
470--- desktopcouch/stop_local_couchdb.py 2010-02-04 22:31:41 +0000
471+++ desktopcouch/stop_local_couchdb.py 2010-09-22 14:14:44 +0000
472@@ -25,7 +25,7 @@
473 from desktopcouch import local_files
474
475 def stop_couchdb(ctx=local_files.DEFAULT_CONTEXT):
476- local_exec = ctx.couch_exec_command + ["-k"]
477+ local_exec = ctx.couch_exec_command + ["-d"]
478 try:
479 retcode = subprocess.call(local_exec, shell=False)
480 if retcode < 0:

Subscribers

People subscribed via source and target branches