Merge lp:~thisfred/desktopcouch/migrate_or_bust into lp:desktopcouch

Proposed by Eric Casteleijn
Status: Merged
Approved by: Eric Casteleijn
Approved revision: 237
Merged at revision: 230
Proposed branch: lp:~thisfred/desktopcouch/migrate_or_bust
Merge into: lp:desktopcouch
Diff against target: 356 lines (+109/-103)
6 files modified
bin/desktopcouch-get-port (+0/-2)
desktopcouch/application/migration/__init__.py (+40/-35)
desktopcouch/application/migration/tests/test_migration.py (+61/-60)
desktopcouch/application/platform/linux/__init__.py (+1/-0)
desktopcouch/application/start_local_couchdb.py (+6/-3)
desktopcouch/records/database.py (+1/-3)
To merge this branch: bzr merge lp:~thisfred/desktopcouch/migrate_or_bust
Reviewer Review Type Date Requested Status
Martin Albisetti (community) Approve
Vincenzo Di Somma (community) Approve
Review via email: mp+41974@code.launchpad.net

Commit message

This adds the migration from 'deleted' flags in records, to moving records into the 'dc_trash' database, where deleted records now should live.

Description of the change

This adds the migration from 'deleted' flags in records, to moving records into the 'dc_trash' database, where deleted records now should live.

To post a comment you must log in.
Revision history for this message
Vincenzo Di Somma (vds) :
review: Approve
Revision history for this message
Martin Albisetti (beuno) wrote :

+ """Migrate from deleted falg to the _trash database"""

Typo! You must be so embarrassed....

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

Fixed!

Revision history for this message
Ubuntu One Auto Pilot (otto-pilot) wrote :
Download full text (22.9 KiB)

The attempt to merge lp:~thisfred/desktopcouch/migrate_or_bust into lp:desktopcouch failed. Below is the output from the failed tests.

Apache CouchDB has started, time to relax.
Browse your desktop CouchDB at file:///tmp/tmpN8Q8Ep/data/couchdb.html
desktopcouch.application.tests.test_start_local_couchdb
  TestUpdateDesignDocuments
    test_create_databases_and_design_docs ... [OK]
desktopcouch.application.tests.test_service
  TestService
    test_start_desktopcouch_replication ... [OK]
    test_start_new_desktopcouch_extensions ... [OK]
    test_start_new_desktopcouch_no_extensions ... [OK]
desktopcouch.application.tests.test_local_files
  TestKeyringIntegration
    test_with_auth ... [OK]
    test_with_no_auth ... [OK]
  TestLocalFiles
    test_all_files_returned ... [OK]
    test_bind_address ... [OK]
    test_couch_chain_ini_files ... [OK]
    test_xdg_overwrite_works ... [OK]
desktopcouch.application.tests.test_replication
  TestReplication
    test_creation ... Apache CouchDB has started, time to relax.
Browse your desktop CouchDB at file:///tmp/tmp2SNWIR/data/couchdb.html
                                                     [OK]
  TestUbuntuoneReplication
    test_exclusion ... Apache CouchDB has started, time to relax.
Browse your desktop CouchDB at file:///tmp/tmpJHrddm/data/couchdb.html
                                                    [OK]
desktopcouch.application.migration.tests.test_migration
  TestMigration
    test_migration_deleted_flag_to_trash ... Traceback (most recent call last):
Failure: testtools.testresult.real._StringException: Text attachment: traceback
------------
Traceback (most recent call last):
  File "/usr/lib/python2.6/dist-packages/testtools/runtest.py", line 144, in _run_user
    return fn(*args)
  File "/usr/lib/python2.6/dist-packages/testtools/testcase.py", line 425, in _run_test_method
    testMethod()
  File "/var/cache/tarmac/desktopcouch/trunk/desktopcouch/application/migration/tests/test_migration.py", line 177, in test_migration_deleted_flag_to_trash
    self.assertEqual(self.dbname, private['original_database_name'])
AssertionError: 'test_migration_deleted_flag_to_trash' != 'dc_trash'
------------

[FAIL]
    test_migration_script_is_run ... [OK]
    test_migration_script_is_run_and_can_access_view ... [OK]
  TestRegistration
    test_register_migration_is_added_to_the_registry ... [OK]
desktopcouch.application.pair.tests.test_couchdb_io
  TestCouchdbIo
    test_get_database_names_replicatable ... [OK]
    test_get_my_host_unique_id ... [OK]
    test_mkuri ... [OK]
    test_obsfuscation ... ...

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

Oops, forgot to exclude dc_trash from migration.

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-11-22 21:36:06 +0000
3+++ bin/desktopcouch-get-port 2010-11-26 19:54:11 +0000
4@@ -36,8 +36,6 @@
5 # Don't let any of our output interfere
6 sys.stdout = DEVNULL
7 PORT = desktopcouch.application.platform.find_port()
8- # FIXME log the result of the migration
9- desktopcouch.application.migration.run_needed_migrations()
10 finally:
11 sys.stdout = SAVED_STDOUT
12 DEVNULL.close()
13
14=== modified file 'desktopcouch/application/migration/__init__.py'
15--- desktopcouch/application/migration/__init__.py 2010-11-23 18:47:46 +0000
16+++ desktopcouch/application/migration/__init__.py 2010-11-26 19:54:11 +0000
17@@ -21,64 +21,69 @@
18
19 import logging
20
21-from couchdb.client import ResourceConflict
22-
23 from desktopcouch.records import database
24 from desktopcouch.application import server
25 from desktopcouch.application.platform import find_port
26
27 MIGRATION_DESIGN_DOCUMENT = 'dc_migration'
28
29-MIGRATIONS_REGISTRY = {}
30-
31
32 def _all_dbs(ctx):
33- """Get all the non private dbs from a server"""
34+ """Get all the non private dbs from a server."""
35 port = find_port(ctx=ctx)
36 uri = "http://localhost:%s" % port
37 couchdb_server = server.DesktopServer(uri, oauth_tokens=None, ctx=ctx)
38- return [x for x in couchdb_server.resource.get('/_all_dbs')[1]]
39-
40-
41-def _add_view(view_name, view_code, dbs, ctx):
42- """add the provided view to the dbs"""
43- if not dbs:
44- dbs = _all_dbs(ctx)
45- for db_name in [db for db in dbs if not db.startswith('_')]:
46- try:
47- db = server.DesktopDatabase(database=db_name, ctx=ctx)
48- except database.NoSuchDatabase:
49- return
50- try:
51- db.add_view(view_name, map_js=view_code, reduce_js='',
52- design_doc=MIGRATION_DESIGN_DOCUMENT)
53- logging.info("Migrating DB: %s", db.db.name)
54- except ResourceConflict:
55- pass
56-
57-
58-def register(view_name, view_code, migration_method, ctx, dbs=None):
59- """register a migration script, with the view and the dbs it is meant to
60- migrate"""
61+ return [x for x in couchdb_server.resource.get('/_all_dbs')[1] if not
62+ x.startswith('_') and not x == database.DCTRASH]
63+
64+
65+def register(migration_method, dbs=None):
66+ """Register a migration script, with the view and the dbs it is meant to
67+ migrate."""
68 if dbs is None:
69 dbs = []
70- MIGRATIONS_REGISTRY[view_name] = {'method': migration_method, 'dbs': dbs}
71- _add_view(view_name, view_code, dbs, ctx)
72+ MIGRATIONS_REGISTRY.append(
73+ {'method': migration_method, 'dbs': dbs})
74
75
76 def run_needed_migrations(ctx=None):
77- """Run the actual migration"""
78+ """Run the actual migration."""
79 from desktopcouch.application import local_files
80 if ctx is None:
81 ctx = local_files.DEFAULT_CONTEXT
82 all_dbs = _all_dbs(ctx)
83 for migration in MIGRATIONS_REGISTRY:
84- dbs = MIGRATIONS_REGISTRY[migration]['dbs']
85+ dbs = migration['dbs']
86 if not dbs:
87 dbs = all_dbs
88- for db in dbs:
89+ for db_name in dbs:
90+ db = server.DesktopDatabase(database=db_name, ctx=ctx)
91 try:
92- MIGRATIONS_REGISTRY[migration]['method'](
93- server.DesktopDatabase(database=db, ctx=ctx))
94+ migration['method'](db)
95+ logging.info("Migrating DB: %s", db)
96 except database.NoSuchDatabase:
97 pass
98+
99+DELETION_VIEW_NAME = 'migrate_deleted'
100+
101+DELETION_VIEW_CODE = """
102+ function(doc) {
103+ if (doc['application_annotations']['Ubuntu One']
104+ ['private_application_annotations']['deleted']) {
105+ emit(doc.id, doc.id);
106+ }
107+ }"""
108+
109+
110+def migrate_from_deleted_to_trash(migrateable_db):
111+ """Migrate from deleted flag to the dc_trash database."""
112+ migrateable_db.add_view(
113+ DELETION_VIEW_NAME, map_js=DELETION_VIEW_CODE,
114+ design_doc=MIGRATION_DESIGN_DOCUMENT)
115+ view_result = migrateable_db.execute_view(
116+ view_name=DELETION_VIEW_NAME, design_doc=MIGRATION_DESIGN_DOCUMENT)
117+ for result in view_result:
118+ migrateable_db.delete_record(result.id)
119+
120+
121+MIGRATIONS_REGISTRY = [{'method': migrate_from_deleted_to_trash, 'dbs': []}]
122
123=== modified file 'desktopcouch/application/migration/tests/test_migration.py'
124--- desktopcouch/application/migration/tests/test_migration.py 2010-11-23 18:47:46 +0000
125+++ desktopcouch/application/migration/tests/test_migration.py 2010-11-26 19:54:11 +0000
126@@ -27,7 +27,8 @@
127 from desktopcouch.application import migration
128 import desktopcouch.application.tests as test_environment
129 from desktopcouch.application.server import DesktopDatabase
130-from desktopcouch.records import Record
131+from desktopcouch.records import Record, NoRecordTypeSpecified
132+from desktopcouch.records.database import DCTRASH
133 from desktopcouch.application.stop_local_couchdb import stop_couchdb
134
135
136@@ -77,55 +78,25 @@
137
138 def test_register_migration_is_added_to_the_registry(self):
139 """Test that the migration script is correctly registered."""
140- fake_view_code = ''
141 size = len(migration.MIGRATIONS_REGISTRY)
142- migration.register(view_name=self.dbname,
143- view_code=fake_view_code,
144- migration_method=fake_migration,
145- dbs=[self.dbname],
146- ctx=self.ctx)
147- self.assertEqual(
148- {'method': fake_migration,
149- 'dbs': [self.dbname]},
150- migration.MIGRATIONS_REGISTRY[self.dbname])
151+ migration.register(migration_method=fake_migration, dbs=[self.dbname])
152+ self.assertEqual([{'method': fake_migration, 'dbs': [self.dbname]}],
153+ migration.MIGRATIONS_REGISTRY)
154 self.assertEqual(size + 1, len(migration.MIGRATIONS_REGISTRY))
155
156- def test_register_migration_add_view_to_a_given_db(self):
157- """Test that the migration is correctly registered."""
158- fake_view_code = ''
159- migration.register(view_name=self.dbname,
160- view_code=fake_view_code,
161- migration_method=fake_migration,
162- dbs=[self.dbname],
163- ctx=self.ctx)
164- self.assert_(self.database.view_exists(self.dbname, 'dc_migration'))
165- self.failIf(self.one_more_database.view_exists(
166- self.dbname, 'dc_migration'))
167-
168- def test_register_migration_add_view_to_all_the_dbs(self):
169- """Test that the migration is correctly registered."""
170- fake_view_code = ''
171- migration.register(view_name=self.dbname,
172- view_code=fake_view_code,
173- migration_method=fake_migration,
174- dbs=[],
175- ctx=self.ctx)
176- self.assertEqual(
177- {'method': fake_migration,
178- 'dbs': []},
179- migration.MIGRATIONS_REGISTRY[self.dbname])
180- for db in [db for db in self.server if not db.startswith('_')]:
181- target = DesktopDatabase(database=db, ctx=self.ctx)
182- self.assert_(target.view_exists(self.dbname, 'dc_migration'))
183-
184
185 class TestMigration(MigrationBase):
186 """Tests the actual migration script"""
187
188+ def setUp(self):
189+ super(TestMigration, self).setUp()
190+ self.trash = DesktopDatabase(DCTRASH, create=True, ctx=self.ctx)
191+
192 def tearDown(self):
193 """tear down each test"""
194+ del self.database._server[DCTRASH]
195 super(TestMigration, self).tearDown()
196- migration.MIGRATIONS_REGISTRY = {}
197+ migration.MIGRATIONS_REGISTRY = []
198
199 def test_migration_script_is_run(self):
200 """Test that the migration script is run."""
201@@ -136,19 +107,20 @@
202 # and get the right db as argument
203 self.assertEqual(self.dbname, database.db.name)
204
205- fake_view_code = ''
206- migration.register(view_name=self.dbname,
207- view_code=fake_view_code,
208- migration_method=simple_migration,
209- dbs=[self.dbname],
210- ctx=self.ctx)
211+ migration.register(
212+ migration_method=simple_migration, dbs=[self.dbname])
213 migration.run_needed_migrations(ctx=self.ctx)
214
215 def test_migration_script_is_run_and_can_access_view(self):
216 """Test that the migration script is run."""
217
218+ simple_view_code = 'function(doc){emit(doc._id, doc.record_type)}'
219+
220 def simple_migration(database):
221 """simple migration code"""
222+ database.add_view(
223+ 'simple_view', map_js=simple_view_code,
224+ design_doc=migration.MIGRATION_DESIGN_DOCUMENT)
225 results = database.execute_view(
226 'simple_view',
227 design_doc='dc_migration')
228@@ -161,17 +133,46 @@
229 self.database.put_record(Record({
230 'key13_1': 'va31_1', 'record_type': 'test.com'}))
231
232- simple_view_code = 'function(doc){emit(doc._id, doc.record_type)}'
233- migration.register(view_name='simple_view',
234- view_code=simple_view_code,
235- migration_method=simple_migration,
236- dbs=[self.dbname],
237- ctx=self.ctx)
238- migration.run_needed_migrations(ctx=self.ctx)
239- migration.MIGRATIONS_REGISTRY = {}
240- migration.register(view_name='simple_view',
241- view_code=simple_view_code,
242- migration_method=simple_migration,
243- dbs=['db_that_doesnt_exixts'],
244- ctx=self.ctx)
245- self.assertIs(None, migration.run_needed_migrations(ctx=self.ctx))
246+ migration.register(
247+ migration_method=simple_migration, dbs=[self.dbname])
248+ migration.run_needed_migrations(ctx=self.ctx)
249+
250+ def test_migration_deleted_flag_to_trash(self):
251+ """Test that the migration script is run."""
252+ record_id = self.database.put_record(
253+ Record({
254+ 'key1_1': 'val1_1',
255+ 'record_type': 'test.com',
256+ 'application_annotations': {
257+ 'Ubuntu One': {
258+ 'private_application_annotations': {
259+ 'deleted': True}}}}))
260+ undeleted_record_id1 = self.database.put_record(
261+ Record({
262+ 'key1_1': 'val1_1',
263+ 'record_type': 'test.com',
264+ 'application_annotations': {
265+ 'Ubuntu One': {
266+ 'private_application_annotations': {
267+ 'deleted': False}}}}))
268+ undeleted_record_id2 = self.database.put_record(
269+ Record({
270+ 'key1_1': 'val1_1',
271+ 'record_type': 'test.com'}))
272+ migration.run_needed_migrations(ctx=self.ctx)
273+ # Record no longer exists in database
274+ self.assertIs(None, self.database.get_record(record_id))
275+ # Undeleted records still exist
276+ self.assertIsNot(None, self.database.get_record(undeleted_record_id1))
277+ self.assertIsNot(None, self.database.get_record(undeleted_record_id2))
278+
279+ # Known deleted record is only record in trash
280+ for document_id in self.trash.db:
281+ try:
282+ record = self.trash.get_record(document_id)
283+ except NoRecordTypeSpecified:
284+ continue
285+ private = record.application_annotations['desktopcouch'][
286+ 'private_application_annotations']
287+ self.assertEqual(self.dbname, private['original_database_name'])
288+ self.assertEqual(record_id, private['original_id'])
289
290=== modified file 'desktopcouch/application/platform/linux/__init__.py'
291--- desktopcouch/application/platform/linux/__init__.py 2010-11-23 18:47:46 +0000
292+++ desktopcouch/application/platform/linux/__init__.py 2010-11-26 19:54:11 +0000
293@@ -78,6 +78,7 @@
294 # now load the design documents and pair records updates,
295 # because it's started
296 start_local_couchdb.update_design_documents()
297+ start_local_couchdb.run_needed_migrations()
298 if not process_is_couchdb(pid):
299 logging.error("CouchDB process did not start up")
300 raise RuntimeError("desktop-couch not started")
301
302=== modified file 'desktopcouch/application/start_local_couchdb.py'
303--- desktopcouch/application/start_local_couchdb.py 2010-11-24 14:40:03 +0000
304+++ desktopcouch/application/start_local_couchdb.py 2010-11-26 19:54:11 +0000
305@@ -46,7 +46,7 @@
306 import sys
307 import time
308
309-from desktopcouch.application import local_files
310+from desktopcouch.application import local_files, migration
311 from desktopcouch.application.platform import (
312 read_pidfile, process_is_couchdb, find_port)
313 from desktopcouch.application.server import DesktopDatabase
314@@ -118,7 +118,6 @@
315 return pid, port
316
317
318-# pylint: disable=R0914
319 def update_design_documents(ctx=local_files.DEFAULT_CONTEXT):
320 """Check system design documents and update any that need updating
321
322@@ -176,7 +175,11 @@
323 else:
324 db.add_view(
325 view_name, mapjs, design_doc=dd_name)
326-# pylint: enable=R0914
327+
328+
329+def run_needed_migrations(ctx=local_files.DEFAULT_CONTEXT):
330+ """Run any necessary migrations."""
331+ migration.run_needed_migrations(ctx=ctx)
332
333
334 def update_bookmark_file(port, ctx=local_files.DEFAULT_CONTEXT):
335
336=== modified file 'desktopcouch/records/database.py'
337--- desktopcouch/records/database.py 2010-11-24 19:31:39 +0000
338+++ desktopcouch/records/database.py 2010-11-26 19:54:11 +0000
339@@ -219,7 +219,7 @@
340 return self.db.query(map_fun, reduce_fun, language,
341 wrapper, **options)
342
343- def get_record(self, record_id, hide_deleted=True):
344+ def get_record(self, record_id):
345 """Get a record from back end storage."""
346
347 def make_getter(source_db, document_id, attachment_name, content_type):
348@@ -239,8 +239,6 @@
349 return None
350 data = {}
351
352- if row_is_deleted(couch_record) and hide_deleted:
353- return None
354 data.update(couch_record)
355 record = self.record_factory(data=data)
356 if "_attachments" in data:

Subscribers

People subscribed via source and target branches