Merge lp:~abentley/charmworld/remove-api-0 into lp:~juju-jitsu/charmworld/trunk

Proposed by Aaron Bentley
Status: Merged
Approved by: Aaron Bentley
Approved revision: 250
Merged at revision: 243
Proposed branch: lp:~abentley/charmworld/remove-api-0
Merge into: lp:~juju-jitsu/charmworld/trunk
Diff against target: 727 lines (+140/-317)
13 files modified
charmworld/routes.py (+0/-3)
charmworld/search.py (+24/-7)
charmworld/tests/test_search.py (+30/-6)
charmworld/views/api.py (+0/-30)
charmworld/views/tests/test_api.py (+33/-252)
migrations/migrate.py (+11/-6)
migrations/test_migrate.py (+8/-4)
migrations/versions/001_load_qa_questions.py (+1/-1)
migrations/versions/002_Escape_dots_dollar_signs_percent_signs_in_mongo_keys.py (+1/-1)
migrations/versions/003_remove_provider_qa_questions.py (+1/-1)
migrations/versions/004_remove_charm_errors.py (+3/-1)
migrations/versions/005_delete_icon_field.py (+8/-0)
migrations/versions/tests/test_migrations.py (+20/-5)
To merge this branch: bzr merge lp:~abentley/charmworld/remove-api-0
Reviewer Review Type Date Requested Status
Curtis Hovey (community) code Approve
Review via email: mp+167324@code.launchpad.net

Commit message

Remove API 0

Description of the change

Remove API 0 and introduce a migration to remove icons from charm data.

Since the charm formatting changes, the migration must also update elasticsearch. This is done using reindex, which gains a charms parameter so that mongodb can be used as a data source, and now handles the case where the index does not already exist.

Much test refactoring was done, since all current APIs (i.e. API1) run all
tests now.

copy_index was broken, because it invoked index_charms on itself, not the pending copy. This is now fixed. It is also renamed to "create_replacement", to better-describe its purpose.

unlimited_search is updated to raise IndexMissing if the search errors out with an IndexMissingException.

To post a comment you must log in.
Revision history for this message
Curtis Hovey (sinzui) wrote :

Thank you.

review: Approve (code)
Revision history for this message
Charmworld Lander (charmworld-lander) wrote :

There are additional revisions which have not been approved in review. Please seek review and approval of these new revisions.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'charmworld/routes.py'
2--- charmworld/routes.py 2013-04-29 18:21:35 +0000
3+++ charmworld/routes.py 2013-06-04 16:12:32 +0000
4@@ -80,9 +80,6 @@
5 config.add_route('auth_callback', '/auth_callback')
6 config.add_route('logout', '/logout')
7
8- # API
9- config.add_route('api_0', '/api/0/{endpoint}{remainder:.*}')
10- config.add_view('charmworld.views.api.API0', route_name='api_0')
11 config.add_route('api_1', '/api/1/{endpoint}{remainder:.*}')
12 config.add_view('charmworld.views.api.API1', route_name='api_1')
13
14
15=== modified file 'charmworld/search.py'
16--- charmworld/search.py 2013-05-29 12:36:35 +0000
17+++ charmworld/search.py 2013-06-04 16:12:32 +0000
18@@ -233,7 +233,8 @@
19 kwargs = {'index': self.index_name}
20 if limit is not None:
21 kwargs['size'] = limit
22- hits = self._client.search(dsl, **kwargs)
23+ with translate_error():
24+ hits = self._client.search(dsl, **kwargs)
25 if limit is None and len(hits['hits']['hits']) < hits['hits']['total']:
26 kwargs['size'] = hits['hits']['total']
27 hits = self._client.search(dsl, **kwargs)
28@@ -348,40 +349,56 @@
29 name = '%s-%d' % (self.index_name, random.randint(1, 99999))
30 return self.__class__(self._client, name)
31
32- def copy_index(self, name=None):
33- """Create a copy of this index with the specified name.
34+ def create_replacement(self, name=None, charms=None):
35+ """Create a replacement for this index.
36
37 The mapping will not be this index's mapping, it will be the current
38 mapping (e.g. put_mapping).
39
40+ :param name: If not supplied, a name based on this index's name will
41+ be used.
42+ :param charms: A list of charms. If None, the charms in the current
43+ index will be used.
44 :return: The ElasticSearchClient for the supplied mapping.
45 """
46 copy = self.get_aliasable_client(name)
47 copy.create_index()
48 try:
49 copy.put_mapping()
50- self.index_charms(self.api_search(valid_charms_only=False))
51+ if charms is None:
52+ try:
53+ charms = self.api_search(valid_charms_only=False)
54+ except IndexMissing:
55+ charms = []
56+ copy.index_charms(charms)
57 return copy
58 except:
59 copy.delete_index()
60 raise
61
62
63-def reindex(index_client):
64+def reindex(index_client, charms=None):
65 """Reindex documents with the current mapping.
66
67 This works by creating a new index and then aliasing it to the old index.
68 If the old index was already an alias, this is a simple swap. If not, the
69 old index is deleted.
70+
71+ :param charms: A list of charms to use. If None, the charms in the index
72+ will be used.
73 """
74- new_index = index_client.copy_index()
75+ new_index = index_client.create_replacement(charms=charms)
76 try:
77 aliased = index_client.get_aliased()
78 if aliased == []:
79- index_client.delete_index()
80+ try:
81+ index_client.delete_index()
82+ except IndexMissing:
83+ pass
84 index_client.update_aliased(new_index.index_name, aliased)
85 except:
86 new_index.delete_index()
87+ raise
88 if aliased != []:
89 for name in aliased:
90 ElasticSearchClient(index_client._client, name).delete_index()
91
92=== modified file 'charmworld/tests/test_search.py'
93--- charmworld/tests/test_search.py 2013-05-29 12:36:35 +0000
94+++ charmworld/tests/test_search.py 2013-06-04 16:12:32 +0000
95@@ -444,12 +444,29 @@
96 alias.update_aliased('bar', alias.get_aliased())
97 self.assertEqual(['bar'], alias.get_aliased())
98
99- def test_copy_index(self):
100- self.index_client.index_charm({'_id': 'a', 'name': 'foo'})
101- copy = self.index_client.copy_index('index-copy')
102- self.addCleanup(copy.delete_index)
103- copy.wait_for_startup()
104- self.assertIn('series', copy.get_mapping()['charm']['properties'])
105+ def test_create_replacement(self):
106+ self.index_client.index_charm({'_id': 'a', 'name': 'foo'})
107+ copy = self.index_client.create_replacement('index-copy')
108+ self.addCleanup(copy.delete_index)
109+ copy.wait_for_startup()
110+ self.assertIn('series', copy.get_mapping()['charm']['properties'])
111+ self.assertEqual({'_id': 'a', 'name': 'foo'}, copy.get('a'))
112+
113+ def test_create_replacement_charms(self):
114+ self.index_client.index_charm({'_id': 'a', 'name': 'foo'})
115+ copy = self.index_client.create_replacement(
116+ charms=[{'_id': 'a', 'name': 'bar'}])
117+ self.addCleanup(copy.delete_index)
118+ copy.wait_for_startup()
119+ self.assertIn('series', copy.get_mapping()['charm']['properties'])
120+ self.assertEqual({'_id': 'a', 'name': 'bar'}, copy.get('a'))
121+
122+ def test_create_replacement_misssing(self):
123+ client = ElasticSearchClient.from_settings(get_ini(), 'temp-index')
124+ # This should not raise an exception, even though the index has not
125+ # been created.
126+ copy = client.create_replacement()
127+ self.addCleanup(copy.delete_index)
128
129 def test_put_mapping_incompatible_mapping_error(self):
130 # The error we get from an incompatible mapping is IncompatibleMapping
131@@ -600,3 +617,10 @@
132 mapping = alias.get_mapping()
133 self.assertEqual('not_analyzed',
134 mapping['charm']['properties']['name']['index'])
135+
136+ def test_reindexed_no_client_charms(self):
137+ client = ElasticSearchClient.from_settings(get_ini())
138+ # This should not raise an exception, even though the index does not
139+ # exist.
140+ new_client = reindex(client, charms=[])
141+ new_client.delete_index()
142
143=== modified file 'charmworld/views/api.py'
144--- charmworld/views/api.py 2013-06-03 16:18:58 +0000
145+++ charmworld/views/api.py 2013-06-04 16:12:32 +0000
146@@ -412,33 +412,3 @@
147 'popular': self._charm_results(popular),
148 }
149 }
150-
151-
152-class API0(API1):
153- """Implementation of API 0.
154-
155- All methods whose names do not begin with an underscore are exposed.
156- Methods may return a webob Response, which is returned directly, or a
157- json-serializable value, which will be returned as a json HTTP response.
158- """
159-
160- @classmethod
161- def _charm_result(cls, charm, requires, provides):
162- return cls._format_charm(charm)
163-
164- def _charm_results(self, charms):
165- return [self._charm_result(charm, {}, {}) for charm in charms]
166-
167- def _charms(self, limit=None, name=None, series=None, owner=None,
168- provides=None, requires=None, type_=None, provider=None,
169- scope=None, category=None, categories=None, text=None):
170- return super(API0, self)._charms(
171- limit, name, series, owner, provides, requires, type_, provider,
172- scope, categories, text)
173-
174- @staticmethod
175- def _format_charm(charm):
176- output = API1._format_charm(charm)
177- if 'icon' in charm:
178- output['icon'] = charm['icon']
179- return output
180
181=== modified file 'charmworld/views/tests/test_api.py'
182--- charmworld/views/tests/test_api.py 2013-06-03 16:18:58 +0000
183+++ charmworld/views/tests/test_api.py 2013-06-04 16:12:32 +0000
184@@ -15,7 +15,6 @@
185 )
186 from charmworld.models import CharmSource
187 from charmworld.views.api import (
188- API0,
189 API1,
190 json_response,
191 )
192@@ -73,6 +72,30 @@
193 return self.api_class(request)()
194
195
196+class API1Mixin(APITestBase):
197+
198+ api_class = API1
199+
200+ @staticmethod
201+ def weightless(charm):
202+ """Remove weight from charm because we cannot predict its value."""
203+ charm = dict(charm)
204+ del charm['weight']
205+ return charm
206+
207+ @classmethod
208+ def weightless_relation(cls, relation):
209+ new_relation = {}
210+ for interface, charms in relation.items():
211+ charms = [cls.weightless(charm) for charm in charms]
212+ new_relation[interface] = charms
213+ return new_relation
214+
215+ @classmethod
216+ def related_weightless(cls, charm):
217+ return cls.weightless(cls.api_class._format_related(charm, 0))
218+
219+
220 class TestAPICharms:
221
222 def setUp(self):
223@@ -259,19 +282,20 @@
224 self.assertIn('new', data['result'])
225 self.assertIn('featured', data['result'])
226 self.assertEqual(['pop%d' % num for num in range(14, 4, -1)],
227- [self.get_name(r) for r in data['result']['popular']])
228+ [r['charm']['name']
229+ for r in data['result']['popular']])
230 self.assertEqual(self.api_class._charm_result(last_popular_charm, {},
231 {}),
232 data['result']['popular'][0])
233 self.assertEqual(['new%d' % num for num in range(14, 4, -1)],
234- [self.get_name(r) for r in data['result']['new']])
235+ [r['charm']['name'] for r in data['result']['new']])
236 self.assertEqual(self.api_class._charm_result(last_new_charm, {}, {}),
237 data['result']['new'][0])
238 self.assertItemsEqual(
239 ['featured%d' % num for num in range(14, -1, -1)],
240- [self.get_name(r) for r in data['result']['featured']])
241+ [r['charm']['name'] for r in data['result']['featured']])
242 featured14, = [charm for charm in data['result']['featured'] if
243- self.get_name(charm) == 'featured14']
244+ charm['charm']['name'] == 'featured14']
245 self.assertEqual(
246 self.api_class._charm_result(last_featured_charm, {}, {}),
247 featured14)
248@@ -281,40 +305,6 @@
249 response = self.get_response('charms', categories='asdf')
250 self.assertEqual(200, response.status_code)
251
252-
253-class API1Mixin:
254-
255- api_class = API1
256-
257- @staticmethod
258- def weightless(charm):
259- """Remove weight from charm because we cannot predict its value."""
260- charm = dict(charm)
261- del charm['weight']
262- return charm
263-
264- @classmethod
265- def weightless_relation(cls, relation):
266- new_relation = {}
267- for interface, charms in relation.items():
268- charms = [cls.weightless(charm) for charm in charms]
269- new_relation[interface] = charms
270- return new_relation
271-
272- @classmethod
273- def related_weightless(cls, charm):
274- return cls.weightless(cls.api_class._format_related(charm, 0))
275-
276-
277-class TestAPI1Charms(TestAPICharms, APITestBase, API1Mixin):
278-
279- def setUp(self):
280- super(TestAPI1Charms, self).setUp()
281-
282- @staticmethod
283- def get_name(charm):
284- return charm['charm']['name']
285-
286 def test_charms(self):
287 """Charms endpoint produces expected result."""
288 one_changes = [factory.makeChange(created=datetime(1999, 12, 31),
289@@ -585,190 +575,8 @@
290 )
291
292
293-class TestAPI0Charms(TestAPICharms, APITestBase):
294-
295- api_class = API0
296-
297- @staticmethod
298- def get_name(charm):
299- return charm['name']
300-
301- def test_charms(self):
302- """Charms endpoint produces expected result."""
303- one_changes = [factory.makeChange(created=datetime(1999, 12, 31),
304- revno=37, message='Hello')]
305- one_id, one = self.makeCharm(name='name1', changes=one_changes,
306- date_created=datetime(1997, 10, 29,
307- 12, 10, 9),
308- promulgated=True)
309- self.index_client.index_charm(one)
310- maintainer2 = 'jrandom@example.com (J. Random Hacker)'
311- provides2 = {'cookie-factory': {
312- 'interface': 'imap',
313- }}
314- requires2 = {'postal-service': {
315- 'interface': 'envelope',
316- }}
317- options2 = {
318- 'foobar': {
319- 'type': 'str',
320- 'default': 'baz',
321- 'description': 'quxx',
322- },
323- }
324- files2 = {
325- 'readme': {
326- 'filename': 'README.md',
327- 'subdir': '',
328- },
329- 'config-changed': {
330- 'filename': 'config-changed',
331- 'subdir': 'hooks',
332- },
333- }
334- two_changes = [factory.makeChange(created=datetime(1998, 11, 30),
335- revno=32, message='Hi',
336- authors=['Baz <baz@example.org>'])]
337- two_id, two = self.makeCharm(owner='jrandom', revision=3,
338- series='warty', summary='Charm two',
339- name='name2', revno=13,
340- maintainer=maintainer2,
341- commit_message='message2',
342- provides=provides2,
343- requires=requires2, options=options2,
344- files=files2, subordinate=True,
345- changes=two_changes,
346- date_created=datetime(1996, 9, 28,
347- 11, 9, 8))
348- response = self.get_response('charms')
349- self.assertEqual(200, response.status_code)
350- self.assertEqual('application/json', response.content_type)
351- result = response.json_body['result']
352- self.assertEqual(2, len(result))
353- one_url = make_store_url(1, get_address(one, short=True))
354- self.assertEqual({'owner': 'charmers',
355- 'maintainer': {
356- 'name': 'John Doe',
357- 'email': 'jdoe@example.com',
358- },
359- 'id': one_url[3:],
360- 'categories': [],
361- 'date_created': '1997-10-29T12:10:09',
362- 'name': 'name1',
363- 'url': one_url,
364- 'description': one['description'],
365- 'revision': 1,
366- 'summary': "Remote terminal classroom over https",
367- 'distro_series': 'precise',
368- 'is_approved': True,
369- 'rating_numerator': 0,
370- 'rating_denominator': 0,
371- 'code_source': {
372- 'type': 'bzr',
373- 'location':
374- 'lp:~charmers/charms/precise/name1/trunk',
375- 'revision': '12',
376- 'last_log': 'maintainer',
377- 'bugs_link':
378- 'https://bugs.launchpad.net/charms/'
379- '+source/name1',
380- 'revisions': [{
381- 'revno': 37,
382- 'message': 'Hello',
383- 'date': '1999-12-31T00:00:00Z',
384- 'authors': [
385- {
386- 'name': 'Foo',
387- 'email': 'foo@example.com'
388- },
389- {
390- 'name': 'Bar',
391- 'email': 'bar@example.com'
392- },
393- ],
394- }],
395- },
396- 'relations': {
397- 'provides': {
398- 'website': {
399- 'interface': 'https',
400- },
401- },
402- 'requires': {
403- 'database': {
404- 'interface': 'mongodb',
405- },
406- },
407- },
408- 'options': {
409- 'script-interval': {
410- 'type': 'int',
411- 'default': 5,
412- 'description':
413- 'The interval between script runs.'
414- },
415- },
416- 'files': ['README', 'hooks/install'],
417- 'tested_providers': {},
418- 'downloads_in_past_30_days': 0,
419- 'is_subordinate': False,
420- }, result[0])
421- two_url = make_store_url(1, get_address(two, short=False))
422- self.assertEqual({'owner': 'jrandom',
423- 'maintainer': {
424- 'name': 'J. Random Hacker',
425- 'email': 'jrandom@example.com',
426- },
427- 'id': two_url[3:],
428- 'categories': [],
429- 'date_created': '1996-09-28T11:09:08',
430- 'name': 'name2',
431- 'url': two_url,
432- 'description': two['description'],
433- 'revision': 3,
434- 'summary': 'Charm two',
435- 'distro_series': 'warty',
436- 'is_approved': False,
437- 'rating_numerator': 0,
438- 'rating_denominator': 0,
439- 'code_source': {
440- 'type': 'bzr',
441- 'location':
442- 'lp:~jrandom/charms/warty/name2/trunk',
443- 'revision': '13',
444- 'revisions': [{
445- 'revno': 32,
446- 'message': 'Hi',
447- 'date': '1998-11-30T00:00:00Z',
448- 'authors': [
449- {
450- 'name': 'Baz',
451- 'email': 'baz@example.org'
452- },
453- ],
454- }],
455- 'last_log': 'message2',
456- 'bugs_link':
457- 'https://bugs.launchpad.net/charms/+source/name2'
458- },
459- 'relations': {
460- 'provides': provides2,
461- 'requires': requires2,
462-
463- },
464- 'options': options2,
465- 'files': ['README.md', 'hooks/config-changed'],
466- 'tested_providers': {},
467- 'downloads_in_past_30_days': 0,
468- 'is_subordinate': True,
469- }, result[1])
470- self.assertNotEqual(one_id, two_id)
471- self.assertNotEqual(one_url, two_url)
472-
473- def test_charms_accepts_category(self):
474- """Category is accepted (but ignored)."""
475- response = self.get_response('charms', category='asdf')
476- self.assertEqual(200, response.status_code)
477+class TestAPI1Charms(TestAPICharms, API1Mixin):
478+ """Test the AP1 implementation of charms endpoint."""
479
480
481 class TestAPI:
482@@ -1151,9 +959,6 @@
483 # Only the headers matter for a CORS preflight response.
484 self.assertEqual(0, response.content_length)
485
486-
487-class TestAPI1(APITestBase, TestAPI, API1Mixin):
488-
489 def test_charm_result_includes_charm_and_metadata(self):
490 charm = factory.get_charm_json(promulgated=False)
491 result = self.api_class._charm_result(charm, {}, {})
492@@ -1283,29 +1088,5 @@
493 self.assertNotIn('icon', formatted)
494
495
496-class TestAPI0(APITestBase, TestAPI):
497-
498- api_class = API0
499-
500- @staticmethod
501- def get_id(charm):
502- return charm['id']
503-
504- def test_charm_result_excludes_charm_and_metadata(self):
505- charm = factory.get_charm_json(promulgated=False)
506- result = self.api_class._charm_result(charm, {}, {})
507- self.assertNotIn('metadata', result)
508- self.assertNotIn('charm', result)
509-
510- def test_format_charm_adds_icon(self):
511- """If a charm provides an icon, it is added to the data returned
512- by the API.
513- """
514- charmid, charm = factory.makeCharm(self.db)
515- # The test data does not contain an icon.
516- self.assertFalse('icon' in charm)
517- formatted = self.api_class._format_charm(charm)
518- self.assertFalse('icon' in formatted)
519- charm['icon'] = 'fake'
520- formatted = self.api_class._format_charm(charm)
521- self.assertTrue('icon' in formatted)
522+class TestAPI1(TestAPI, API1Mixin):
523+ """Test API 1."""
524
525=== modified file 'migrations/migrate.py'
526--- migrations/migrate.py 2013-02-12 11:36:14 +0000
527+++ migrations/migrate.py 2013-06-04 16:12:32 +0000
528@@ -17,6 +17,7 @@
529
530 from charmworld.models import getconnection
531 from charmworld.models import getdb
532+from charmworld.search import ElasticSearchClient
533 from charmworld.utils import get_ini
534
535 SCRIPT_TEMPLATE = """
536@@ -152,7 +153,13 @@
537 """:returns: Latest version in Collection"""
538 return self.version_indexes[-1] if len(self.version_indexes) > 0 else 0
539
540- def upgrade(self, datastore, init):
541+ def run_migration(self, db, index_client, module_name):
542+ module = imp.load_source(
543+ module_name.strip('.py'),
544+ join(self.versions_dir, module_name))
545+ getattr(module, 'upgrade')(db, index_client)
546+
547+ def upgrade(self, datastore, index_client, init):
548 """Run `upgrade` methods for required version files.
549
550 :param datastore: An instance of DataStore
551@@ -176,10 +183,7 @@
552 # Let's get processing.
553 next_version = current_version + 1
554 module_name = self.versions[next_version]
555- module = imp.load_source(
556- module_name.strip('.py'),
557- join(self.versions_dir, module_name))
558- getattr(module, 'upgrade')(datastore.db)
559+ self.run_migration(datastore.db, index_client, module_name)
560 current_version = next_version
561 datastore.update_version(current_version)
562 return current_version
563@@ -252,8 +256,9 @@
564 def upgrade(cls, ini, args):
565 """Upgrade the data store to the latest available migration."""
566 ds = cls.get_datastore(ini)
567+ index_client = ElasticSearchClient.from_settings(ini)
568 migrations = Versions(ini['migrations'])
569- new_version = migrations.upgrade(ds, args.init)
570+ new_version = migrations.upgrade(ds, index_client, args.init)
571
572 if new_version is None:
573 print 'There are no new migrations to apply'
574
575=== modified file 'migrations/test_migrate.py'
576--- migrations/test_migrate.py 2013-02-12 18:57:02 +0000
577+++ migrations/test_migrate.py 2013-06-04 16:12:32 +0000
578@@ -6,10 +6,10 @@
579 from os.path import exists
580 import pymongo
581 from tempfile import mkdtemp
582-from unittest import TestCase
583
584 from charmworld.models import getconnection
585 from charmworld.models import getdb
586+from charmworld.testing import TestCase
587 from charmworld.testing.factory import random_string
588 from charmworld.utils import get_ini
589
590@@ -155,16 +155,19 @@
591
592 def test_upgrade_wo_migrations(self):
593 """Upgrade runs silently if no migrations are created yet."""
594+ self.use_index_client()
595 self.ds.version_datastore()
596 tmpdir = mkdtemp()
597 versions = Versions(tmpdir)
598- self.assertEqual(versions.upgrade(self.ds, False), None)
599+ self.assertEqual(versions.upgrade(self.ds, self.index_client, False),
600+ None)
601
602 def test_upgrade_succeeds(self):
603 """upgrade will run the methods and we get a version of 4."""
604 self.ds.version_datastore()
605+ self.use_index_client()
606 # We should get 4 versions done. The upgrade returns 4
607- last_version = self.version.upgrade(self.ds, False)
608+ last_version = self.version.upgrade(self.ds, self.index_client, False)
609 self.assertEqual(4, last_version)
610
611 # and we can query the db for the revision of 4
612@@ -172,7 +175,8 @@
613
614 def test_upgrade_succeeds_from_unversioned(self):
615 """Forcing init no an unversioned db will upgrade successfully."""
616- last_version = self.version.upgrade(self.ds, True)
617+ self.use_index_client()
618+ last_version = self.version.upgrade(self.ds, self.index_client, True)
619 self.assertEqual(4, last_version)
620
621 # and we can query the db for the revision of 4
622
623=== modified file 'migrations/versions/001_load_qa_questions.py'
624--- migrations/versions/001_load_qa_questions.py 2013-02-12 11:36:14 +0000
625+++ migrations/versions/001_load_qa_questions.py 2013-06-04 16:12:32 +0000
626@@ -9,7 +9,7 @@
627 """
628
629
630-def upgrade(db):
631+def upgrade(db, index_client):
632 """Complete this function with work to be done for the migration/update.
633
634 db is the pymongo db instance for our datastore. Charms are in db.charms
635
636=== modified file 'migrations/versions/002_Escape_dots_dollar_signs_percent_signs_in_mongo_keys.py'
637--- migrations/versions/002_Escape_dots_dollar_signs_percent_signs_in_mongo_keys.py 2013-02-12 11:36:14 +0000
638+++ migrations/versions/002_Escape_dots_dollar_signs_percent_signs_in_mongo_keys.py 2013-06-04 16:12:32 +0000
639@@ -5,7 +5,7 @@
640
641 from charmworld.utils import quote_yaml
642
643-def upgrade(db):
644+def upgrade(db, index_client):
645 """Iterate all charm data and replace all dictionary keys containing
646 a '.', '$' or '%' with an escaped variant.
647 """
648
649=== modified file 'migrations/versions/003_remove_provider_qa_questions.py'
650--- migrations/versions/003_remove_provider_qa_questions.py 2013-04-23 19:23:17 +0000
651+++ migrations/versions/003_remove_provider_qa_questions.py 2013-06-04 16:12:32 +0000
652@@ -21,7 +21,7 @@
653 ]
654
655
656-def upgrade(db):
657+def upgrade(db, index_client):
658 """Update each category of questions from new_category_data.
659
660 This update elaborates on the 001_* migration. Subsequent qa form
661
662=== modified file 'migrations/versions/004_remove_charm_errors.py'
663--- migrations/versions/004_remove_charm_errors.py 2013-05-27 19:02:19 +0000
664+++ migrations/versions/004_remove_charm_errors.py 2013-06-04 16:12:32 +0000
665@@ -1,2 +1,4 @@
666-def upgrade(db):
667+# Copyright 2013 Canonical Ltd. This software is licensed under the
668+# GNU Affero General Public License version 3 (see the file LICENSE).
669+def upgrade(db, index_client):
670 db.drop_collection('charm-errors')
671
672=== added file 'migrations/versions/005_delete_icon_field.py'
673--- migrations/versions/005_delete_icon_field.py 1970-01-01 00:00:00 +0000
674+++ migrations/versions/005_delete_icon_field.py 2013-06-04 16:12:32 +0000
675@@ -0,0 +1,8 @@
676+# Copyright 2013 Canonical Ltd. This software is licensed under the
677+# GNU Affero General Public License version 3 (see the file LICENSE).
678+from charmworld.search import reindex
679+
680+
681+def upgrade(db, index_client):
682+ db.charms.update({}, {'$unset': {'icon':''}})
683+ reindex(index_client, list(db.charms.find({})))
684
685=== modified file 'migrations/versions/tests/test_migrations.py'
686--- migrations/versions/tests/test_migrations.py 2013-05-27 19:02:19 +0000
687+++ migrations/versions/tests/test_migrations.py 2013-06-04 16:12:32 +0000
688@@ -2,8 +2,15 @@
689 # GNU Affero General Public License version 3 (see the file LICENSE).
690
691 import imp
692+from charmworld.models import CharmSource
693 from charmworld.testing import MongoTestBase
694 from charmworld.testing.factory import random_string
695+from migrations.migrate import Versions
696+
697+
698+def run_migration(db, index_client, module_name):
699+ versions = Versions('migrations/versions/')
700+ versions.run_migration(db, index_client, module_name)
701
702
703 class TestMigration002(MongoTestBase):
704@@ -53,10 +60,18 @@
705 class TestMigration004(MongoTestBase):
706
707 def test_migration(self):
708+ self.use_index_client()
709 self.db.create_collection('charm-errors')
710- module = imp.load_source(
711- '004_remove_charm_errors',
712- 'migrations/versions/'
713- '004_remove_charm_errors.py')
714- module.upgrade(self.db)
715+ run_migration(self.db, self.index_client, '004_remove_charm_errors.py')
716 self.assertNotIn('charm-errors', self.db.collection_names())
717+
718+
719+class TestMigration005(MongoTestBase):
720+
721+ def test_migration(self):
722+ self.use_index_client()
723+ source = CharmSource.from_request(self)
724+ source.save({'_id': 'a', 'icon': 'asdf', 'asdf': 'asdf'})
725+ run_migration(self.db, self.index_client, '005_delete_icon_field.py')
726+ for charm in source._get_all('a'):
727+ self.assertNotIn('icon', charm)

Subscribers

People subscribed via source and target branches