Merge lp:~vds/desktopcouch/simple_migration_infrastructure into lp:desktopcouch

Proposed by Vincenzo Di Somma
Status: Merged
Approved by: Chad Miller
Approved revision: 212
Merged at revision: 210
Proposed branch: lp:~vds/desktopcouch/simple_migration_infrastructure
Merge into: lp:desktopcouch
Diff against target: 308 lines (+263/-1)
5 files modified
bin/desktopcouch-get-port (+5/-0)
desktopcouch/migration/__init__.py (+80/-0)
desktopcouch/migration/tests/__init__.py (+1/-0)
desktopcouch/migration/tests/test_migration.py (+176/-0)
runtests.py (+1/-1)
To merge this branch: bzr merge lp:~vds/desktopcouch/simple_migration_infrastructure
Reviewer Review Type Date Requested Status
Chad Miller (community) Approve
Manuel de la Peña (community) Approve
Eric Casteleijn (community) Approve
Review via email: mp+40988@code.launchpad.net

Commit message

Add basic migration infrastructure, to facilitate at least a planned switch from deleted-flagged records to a separate Trash database. (LP: #670700, #675590)

Description of the change

Basic migration infrastructure.

To post a comment you must log in.
211. By Vincenzo Di Somma

pylint happy (again)

Revision history for this message
Eric Casteleijn (thisfred) wrote :

Looks good, tests pass.

review: Approve
212. By Vincenzo Di Somma

thisfred happy

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

This is a +1 from me. Nevertheless we should aim to get all our unittests to use mocker to mock the db access. I have added a bug report and assign it to vds so that we do not forget, bug number is lp:676476

review: Approve
Revision history for this message
Chad Miller (cmiller) :
review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'bin/desktopcouch-get-port'
2--- bin/desktopcouch-get-port 2010-02-25 15:50:50 +0000
3+++ bin/desktopcouch-get-port 2010-11-16 18:24:15 +0000
4@@ -16,6 +16,9 @@
5 # along with desktopcouch. If not, see <http://www.gnu.org/licenses/>.
6 #
7 # Authors: Chad Miller <chad.miller@canonical.com>
8+# Eric Casteleijn <eric.casteleijn@canonical.com>
9+# Vincenzo Di Somma <vincenzo.di.somma@canonical.com>
10+
11
12 """Return on stdout the port that the personal couchdb is listening on.
13 As always, success returns a zero exit code, and an exception in Python
14@@ -32,6 +35,8 @@
15 # Don't let any of our output interfere
16 sys.stdout = devnull
17 port = desktopcouch.find_port()
18+ # FIXME log the result of the migration
19+ desktopcouch.migration.run_needed_migrations()
20 finally:
21 sys.stdout = saved_stdout
22 devnull.close()
23
24=== added directory 'desktopcouch/migration'
25=== added file 'desktopcouch/migration/__init__.py'
26--- desktopcouch/migration/__init__.py 1970-01-01 00:00:00 +0000
27+++ desktopcouch/migration/__init__.py 2010-11-16 18:24:15 +0000
28@@ -0,0 +1,80 @@
29+# Copyright 2010 Canonical Ltd.
30+#
31+# This file is part of desktopcouch.
32+#
33+# desktopcouch is free software: you can redistribute it and/or modify
34+# it under the terms of the GNU Lesser General Public License version 3
35+# as published by the Free Software Foundation.
36+#
37+# desktopcouch is distributed in the hope that it will be useful,
38+# but WITHOUT ANY WARRANTY; without even the implied warranty of
39+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
40+# GNU Lesser General Public License for more details.
41+#
42+# You should have received a copy of the GNU Lesser General Public License
43+# along with desktopcouch. If not, see <http://www.gnu.org/licenses/>.
44+#
45+# Authors: Eric Casteleijn <eric.casteleijn@canonical.com>
46+# Vincenzo Di Somma <vincenzo.di.somma@canonical.com>
47+
48+"""Basic migration infrastructure"""
49+
50+import logging
51+
52+from couchdb.client import ResourceConflict
53+
54+from desktopcouch.records import server, server_base
55+from desktopcouch.platform import find_port
56+
57+MIGRATION_DESIGN_DOCUMENT = 'dc_migration'
58+
59+MIGRATIONS_REGISTRY = {}
60+
61+def _all_dbs(ctx):
62+ """Get all the non private dbs from a server"""
63+ port = find_port(ctx=ctx)
64+ uri = "http://localhost:%s" % port
65+ couchdb_server = server.OAuthCapableServer(
66+ uri, oauth_tokens=None, ctx=ctx)
67+ return [x for x in couchdb_server.resource.get('/_all_dbs')[1]]
68+
69+def _add_view(view_name, view_code, dbs, ctx):
70+ """add the provided view to the dbs"""
71+ if not dbs:
72+ dbs = _all_dbs(ctx)
73+ for db_name in [db for db in dbs if not db.startswith('_')]:
74+ try:
75+ db = server.CouchDatabase(database=db_name, ctx=ctx)
76+ except server_base.NoSuchDatabase:
77+ return
78+ try:
79+ db.add_view(view_name, map_js=view_code, reduce_js='',
80+ design_doc=MIGRATION_DESIGN_DOCUMENT)
81+ logging.info("Migrating DB: ", db.db.name)
82+ except ResourceConflict:
83+ pass
84+
85+def register(view_name, view_code, migration_method, ctx, dbs=None):
86+ """register a migration script, with the view and the dbs it is meant to
87+ migrate"""
88+ if dbs is None:
89+ dbs = []
90+ MIGRATIONS_REGISTRY[view_name] = {'method': migration_method, 'dbs': dbs}
91+ _add_view(view_name, view_code, dbs, ctx)
92+
93+def run_needed_migrations(ctx=None):
94+ """Run the actual migration"""
95+ from desktopcouch import local_files
96+ if ctx is None:
97+ ctx = local_files.DEFAULT_CONTEXT
98+ all_dbs = _all_dbs(ctx)
99+ for migration in MIGRATIONS_REGISTRY:
100+ dbs = MIGRATIONS_REGISTRY[migration]['dbs']
101+ if not dbs:
102+ dbs = all_dbs
103+ for db in dbs:
104+ try:
105+ MIGRATIONS_REGISTRY[migration]['method'](
106+ server.CouchDatabase(database=db, ctx=ctx))
107+ except server_base.NoSuchDatabase:
108+ pass
109
110=== added directory 'desktopcouch/migration/tests'
111=== added file 'desktopcouch/migration/tests/__init__.py'
112--- desktopcouch/migration/tests/__init__.py 1970-01-01 00:00:00 +0000
113+++ desktopcouch/migration/tests/__init__.py 2010-11-16 18:24:15 +0000
114@@ -0,0 +1,1 @@
115+"""Tests suites for migration infrastructure"""
116
117=== added file 'desktopcouch/migration/tests/test_migration.py'
118--- desktopcouch/migration/tests/test_migration.py 1970-01-01 00:00:00 +0000
119+++ desktopcouch/migration/tests/test_migration.py 2010-11-16 18:24:15 +0000
120@@ -0,0 +1,176 @@
121+# Copyright 2010 Canonical Ltd.
122+#
123+# This file is part of desktopcouch.
124+#
125+# desktopcouch is free software: you can redistribute it and/or modify
126+# it under the terms of the GNU Lesser General Public License version 3
127+# as published by the Free Software Foundation.
128+#
129+# desktopcouch is distributed in the hope that it will be useful,
130+# but WITHOUT ANY WARRANTY; without even the implied warranty of
131+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
132+# GNU Lesser General Public License for more details.
133+#
134+# You should have received a copy of the GNU Lesser General Public License
135+# along with desktopcouch. If not, see <http://www.gnu.org/licenses/>.
136+#
137+# Authors: Eric Casteleijn <eric.casteleijn@canonical.com>
138+# Vincenzo Di Somma <vincenzo.di.somma@canonical.com>
139+
140+"""Tests for migration infrastructure"""
141+
142+#pylint: disable=W0212
143+#tests can access private attributes
144+
145+import testtools
146+
147+from desktopcouch import migration
148+import desktopcouch.tests as test_environment
149+from desktopcouch.records.server import CouchDatabase
150+from desktopcouch.records.record import Record
151+from desktopcouch.stop_local_couchdb import stop_couchdb
152+
153+
154+def get_test_context():
155+ """Return test context."""
156+ return test_environment.test_context
157+
158+def fake_migration():
159+ """fake migration code"""
160+ pass
161+
162+
163+class MigrationBase(testtools.TestCase):
164+ """Tests the registration of a migration script"""
165+
166+ def setUp(self):
167+ """setup each test"""
168+ super(MigrationBase, self).setUp()
169+ self.ctx = get_test_context()
170+ self.dbname = self._testMethodName
171+ self.database = CouchDatabase(self.dbname, create=True, ctx=self.ctx)
172+ self.server = self.database._server
173+
174+ def tearDown(self):
175+ """tear down each test"""
176+ del self.database._server[self.database._database_name]
177+ this_context = get_test_context()
178+ if this_context != test_environment.test_context:
179+ stop_couchdb(ctx=this_context)
180+ super(MigrationBase, self).tearDown()
181+
182+class TestRegistration(MigrationBase):
183+ """Tests the registration of a migration script"""
184+
185+ def setUp(self):
186+ """setup each test"""
187+ super(TestRegistration, self).setUp()
188+ self.one_more_database = CouchDatabase('one_more_database',
189+ create=True,
190+ ctx=self.ctx)
191+
192+ def tearDown(self):
193+ """tear down each test"""
194+ del self.database._server[self.one_more_database._database_name]
195+ super(TestRegistration, self).tearDown()
196+
197+ def test_register_migration_is_added_to_the_registry(self):
198+ """Test that the migration script is correctly registered."""
199+ fake_view_code = ''
200+ size = len(migration.MIGRATIONS_REGISTRY)
201+ migration.register(view_name=self.dbname,
202+ view_code=fake_view_code,
203+ migration_method=fake_migration,
204+ dbs=[self.dbname],
205+ ctx=self.ctx)
206+ self.assertEqual(
207+ {'method': fake_migration,
208+ 'dbs':[self.dbname]},
209+ migration.MIGRATIONS_REGISTRY[self.dbname])
210+ self.assertEqual(size+1, len(migration.MIGRATIONS_REGISTRY))
211+
212+ def test_register_migration_add_view_to_a_given_db(self):
213+ """Test that the migration is correctly registered."""
214+ fake_view_code = ''
215+ migration.register(view_name=self.dbname,
216+ view_code=fake_view_code,
217+ migration_method=fake_migration,
218+ dbs=[self.dbname],
219+ ctx=self.ctx)
220+ self.assert_(self.database.view_exists(self.dbname, 'dc_migration'))
221+ self.failIf(self.one_more_database.view_exists(
222+ self.dbname, 'dc_migration'))
223+
224+ def test_register_migration_add_view_to_all_the_dbs(self):
225+ """Test that the migration is correctly registered."""
226+ fake_view_code = ''
227+ migration.register(view_name=self.dbname,
228+ view_code=fake_view_code,
229+ migration_method=fake_migration,
230+ dbs=[],
231+ ctx=self.ctx)
232+ self.assertEqual(
233+ {'method': fake_migration,
234+ 'dbs':[]},
235+ migration.MIGRATIONS_REGISTRY[self.dbname])
236+ for db in [db for db in self.server if not db.startswith('_')]:
237+ target = CouchDatabase(database=db, ctx=self.ctx)
238+ self.assert_(target.view_exists(self.dbname, 'dc_migration'))
239+
240+
241+class TestMigration(MigrationBase):
242+ """Tests the actual migration script"""
243+
244+ def tearDown(self):
245+ """tear down each test"""
246+ super(TestMigration, self).tearDown()
247+ migration.MIGRATIONS_REGISTRY = {}
248+ def test_migration_script_is_run(self):
249+ """Test that the migration script is run."""
250+
251+ def simple_migration(database):
252+ """simple migration code"""
253+ # just asserting something to check this code is run
254+ # and get the right db as argument
255+ self.assertEqual(self.dbname, database.db.name)
256+
257+ fake_view_code = ''
258+ migration.register(view_name=self.dbname,
259+ view_code=fake_view_code,
260+ migration_method=simple_migration,
261+ dbs=[self.dbname],
262+ ctx=self.ctx)
263+ migration.run_needed_migrations(ctx=self.ctx)
264+
265+ def test_migration_script_is_run_and_can_access_view(self):
266+ """Test that the migration script is run."""
267+
268+ def simple_migration(database):
269+ """simple migration code"""
270+ results = database.execute_view(
271+ 'simple_view',
272+ design_doc='dc_migration')
273+ self.assertEqual(3, len(results))
274+
275+ self.database.put_record(Record({
276+ 'key1_1': 'val1_1', 'record_type': 'test.com'}))
277+ self.database.put_record(Record({
278+ 'key2_1': 'val2_1', 'record_type': 'test.com'}))
279+ self.database.put_record(Record({
280+ 'key13_1': 'va31_1', 'record_type': 'test.com'}))
281+
282+ simple_view_code = 'function(doc){emit(doc._id, doc.record_type)}'
283+ migration.register(view_name='simple_view',
284+ view_code=simple_view_code,
285+ migration_method=simple_migration,
286+ dbs=[self.dbname],
287+ ctx=self.ctx)
288+ migration.run_needed_migrations(ctx=self.ctx)
289+ migration.MIGRATIONS_REGISTRY = {}
290+ migration.register(view_name='simple_view',
291+ view_code=simple_view_code,
292+ migration_method=simple_migration,
293+ dbs=['db_that_doesnt_exixts'],
294+ ctx=self.ctx)
295+ self.assertIs(None, migration.run_needed_migrations(ctx=self.ctx))
296+
297
298=== modified file 'runtests.py'
299--- runtests.py 2010-11-12 11:56:35 +0000
300+++ runtests.py 2010-11-16 18:24:15 +0000
301@@ -42,6 +42,6 @@
302 coverage.stop()
303 subprocess.call(["utilities/lint.sh", ""], shell=True)
304 source_files = ffind('desktopcouch')
305- coverage.report(source_files, ignore_errors=1, show_missing=0)
306+ coverage.report(source_files, ignore_errors=1, show_missing=1)
307 sys.exit(not return_code)
308 # pylint: enable-msg=C0103

Subscribers

People subscribed via source and target branches