Merge lp:~reedobrien/charmworld/es-migration into lp:charmworld

Proposed by Reed O'Brien
Status: Merged
Approved by: Reed O'Brien
Approved revision: 517
Merged at revision: 512
Proposed branch: lp:~reedobrien/charmworld/es-migration
Merge into: lp:charmworld
Diff against target: 370 lines (+281/-41)
5 files modified
charmworld/migrations/versions/027_use_ngrams.py (+35/-0)
charmworld/migrations/versions/tests/__init__.py (+1/-0)
charmworld/migrations/versions/tests/data/migration_027.py (+166/-0)
charmworld/migrations/versions/tests/test_migrations.py (+79/-4)
charmworld/search.py (+0/-37)
To merge this branch: bzr merge lp:~reedobrien/charmworld/es-migration
Reviewer Review Type Date Requested Status
Juju Gui Bot continuous-integration Approve
Brad Crittenden (community) Approve
Review via email: mp+220144@code.launchpad.net

Commit message

This makes the ngram work migrate cleanly.

Description of the change

Hopefully this makes the ngram work migrate cleanly.

QA:

Go back to r508

$ bzr revert -r 508

Drop the version and delte the index:

$ mongo juju --eval "db.migration_version.drop()"
$ curl -XDELETE http://localhost:9200/charms?pretty=true

Create the r508 index:

$ bin/es-update

Setup the migration system and update to r508 version (026)

$ bin/migrations current
$ bin/migrations prepare-upgrade -i
$ bin/migrations upgrade
$ bin/migrations latest

Reindex the stuff...without ngrams

$ bin/sync-index

Go back to tip (r515)

$ bzr revert

Do the migration...

$ bin/migrations latest
$ bin/migrations current
$ bin/es-update
$ bin/migrations upgrade

Verify it worked

$ curl localhost:9200/charms/_count?pretty=true
$ curl localhost:9200/charms/charm/_count?q=ngrams:crm\&pretty=true&& echo
$ curl localhost:9200/charms/charm/_count?q=ngrams:couch\&pretty=true&& echo

Make sure the tests all pass...

$ make test

To post a comment you must log in.
516. By Reed O'Brien

Remove unused bits of the exists in index helper method.

Revision history for this message
Brad Crittenden (bac) wrote :

Reed this branch looks great, modulo some stylistic issues.

The QA steps you provide are fantastic. The last two steps do make assumptions about what charms have been ingested locally.

Note this review is based on the version of your code as of 8:00am, not the branch you subsequently pushed.

I've marked the branch as approved, which means you are free to land after making (or discussing) the suggested changes. Had I wanted to see the changes made before you land I would've used 'needs fixing' but I don't, so I didn't.

Thanks for persevering and getting this in so we can do the deploy.

review: Approve (bac)
Revision history for this message
Brad Crittenden (bac) wrote :

Oh, and do see the in-line comments!

Revision history for this message
Brad Crittenden (bac) wrote :

Actually you should pass the kind in the exists method:

    def exists_in_index(self, id_, kind=CHARM):
        client = self.index_client
        return client.get(id_, kind) is not None

Revision history for this message
Reed O'Brien (reedobrien) wrote :

> Reed this branch looks great, modulo some stylistic issues.
>
> The QA steps you provide are fantastic. The last two steps do make
> assumptions about what charms have been ingested locally.

Thanks. I thought I had put some ingest bits at the top. I missed that I guess -- nearly fantastic...

> I've marked the branch as approved, which means you are free to land after
> making (or discussing) the suggested changes. Had I wanted to see the changes
> made before you land I would've used 'needs fixing' but I don't, so I didn't.

I'll resubmit for sanity check.

Revision history for this message
Reed O'Brien (reedobrien) wrote :

> Oh, and do see the in-line comments!

Waiting for them to show here. But will work from email until they do.

Revision history for this message
Reed O'Brien (reedobrien) wrote :

> Actually you should pass the kind in the exists method:
>
> def exists_in_index(self, id_, kind=CHARM):
> client = self.index_client
> return client.get(id_, kind) is not None

This shrank to:

def exists_in_index(self, id_):
    return self.index_client.get(id_) is not None

in this test I am only inserting a charm, so adding a specifier seems like noise. If adding a charm let's you find something else, there is a problem and that should be tested elsewhere, IMO. Unless we are aiming to test that bundles upgrade as well, which AFAIU we are not.

517. By Reed O'Brien

Assuages stylistic deviations.

Revision history for this message
Reed O'Brien (reedobrien) wrote :
Download full text (6.0 KiB)

On Tue, May 20, 2014 at 9:04 AM, Brad Crittenden <email address hidden> wrote:

> Review: Approve bac
>
> Reed this branch looks great, modulo some stylistic issues.
>
> The QA steps you provide are fantastic. The last two steps do make
> assumptions about what charms have been ingested locally.
>
> Note this review is based on the version of your code as of 8:00am, not
> the branch you subsequently pushed.
>
> I've marked the branch as approved, which means you are free to land after
> making (or discussing) the suggested changes. Had I wanted to see the
> changes made before you land I would've used 'needs fixing' but I don't, so
> I didn't.
>
> Thanks for persevering and getting this in so we can do the deploy.
>
> Inline comments:
>
>
> > + except IndexMissing:
> > + # for whatever reason it is updated already
> > + pass
>
> Remember to Capitalize and punctuate comments.
>

Done.

> > --- charmworld/migrations/versions/tests/__init__.py 1970-01-01
> 00:00:00 +0000
> > +++ charmworld/migrations/versions/tests/__init__.py 2014-05-20
> 01:08:32 +0000
> > @@ -0,0 +1,1 @@
> > +# a package
> >
>
> I'd actually prefer to see a totally empty file than just this comment.
> See
>

Emptied. This is a vestigial habit from some old VCS that wouldn't commit
empty files and directories.
I can't even remember what that was... Removed.

> > +old_charm_mapping = {"charm": {
> > + "type": "object",
>
> Thanks for figuring out the old mapping structure to provide the test.
> Makes for much more thorough testing.
>

Getting the mapping was easy. Formatting it and converting to python were
less so.

> > === modified file
> 'charmworld/migrations/versions/tests/test_migrations.py'
> > --- charmworld/migrations/versions/tests/test_migrations.py 2014-04-22
> 20:36:12 +0000
> > +
> > +from charmworld.search import CHARM
> > +
>
> If you import BUNDLE too you can use them instead of strings, like at line
> 304.
>

I am actually not importing CHARM at this point either. See other comment
here.

>
> > +from pyelasticsearch.exceptions import (
> > + ElasticHttpError,
> > + ElasticHttpNotFoundError,
> > +)
> >
> > import logging
> > import mock
> > @@ -126,3 +138,79 @@
> > def test_exodus_restrictions(self):
> > """Ensure that deploy-from-scratch does not violate exodus
> rules."""
> > self.versions._check_exodus(self.versions.list_pending(0))
> > +
> > +
> > +class TestUseNgrams027(MigrationTestBase):
> > +
> > + version = 27
> > + module_name = '027_use_ngrams.py'
> > +
> > + def setUp(self):
> > + super(TestUseNgrams027, self).setUp()
> > + self.use_index_client()
> > + self.ds = DataStore(self.db)
> > + self.cs = CharmSource(self.db, self.index_client)
>
> If this were long-lived code I'd suggest more explicit names. We tend to
> the follow the Python guide that code is to be read many more times than
> written so more readable names are better at the sake of brevity.
>

Embiggened.

> > +
> > + def exists_in_index(self, id_, kind=CHARM):
> > + client = self.index_client
> > + if kind == CHARM:
> > + return client.get(id_) is not None
> > +...

Read more...

Revision history for this message
Juju Gui Bot (juju-gui-bot) :
review: Approve (continuous-integration)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== added file 'charmworld/migrations/versions/027_use_ngrams.py'
--- charmworld/migrations/versions/027_use_ngrams.py 1970-01-01 00:00:00 +0000
+++ charmworld/migrations/versions/027_use_ngrams.py 2014-05-20 14:13:54 +0000
@@ -0,0 +1,35 @@
1# use ngrams
2import logging
3
4from charmworld.models import CharmSource
5
6from charmworld.search import (
7 IndexMissing,
8 update,
9)
10
11
12def upgrade(db, index_client):
13 """db is the pymongo db instance for our datastore. Charms are in db.charms
14 for instance.
15
16 index_client is the ElasticSearch client.
17 """
18 log = logging.getLogger('migration_027')
19 log.warning('Starting')
20 charm_source = CharmSource(db, index_client)
21 try:
22 index_client.delete_index()
23 log.warning("deleted index")
24 except IndexMissing:
25 # It is already updated.
26 pass
27 log.warning("Index already deleted")
28 update(index_client)
29 cids = charm_source.sync_index()
30 while True:
31 try:
32 next(cids)
33 except StopIteration:
34 break
35 log.warning("Index updated")
036
=== added file 'charmworld/migrations/versions/tests/__init__.py'
--- charmworld/migrations/versions/tests/__init__.py 1970-01-01 00:00:00 +0000
+++ charmworld/migrations/versions/tests/__init__.py 2014-05-20 14:13:54 +0000
@@ -0,0 +1,1 @@
1# a package
02
=== added directory 'charmworld/migrations/versions/tests/data'
=== added file 'charmworld/migrations/versions/tests/data/__init__.py'
=== added file 'charmworld/migrations/versions/tests/data/migration_027.py'
--- charmworld/migrations/versions/tests/data/migration_027.py 1970-01-01 00:00:00 +0000
+++ charmworld/migrations/versions/tests/data/migration_027.py 2014-05-20 14:13:54 +0000
@@ -0,0 +1,166 @@
1old_charm_mapping = {"charm": {
2 "type": "object",
3 "dynamic": "false",
4 "properties": {
5 "data": {
6 "properties": {
7 "_id": {
8 "type": "string",
9 "index": "not_analyzed",
10 "omit_norms": True,
11 "index_options": "docs"
12 },
13 "categories": {
14 "type": "string",
15 "index": "not_analyzed",
16 "omit_norms": True,
17 "index_options": "docs"
18 },
19 "config": {
20 "properties": {
21 "options": {
22 "properties": {
23 "default": {
24 "type": "string"},
25 "description": {
26 "type": "string"}
27 }
28 }
29 }
30 },
31 "date_created": {
32 "type": "date",
33 "format": "dateOptionalTime"
34 },
35 "description": {"type": "string"},
36 "downloads": {"type": "long"},
37 "downloads_in_past_30_days": {"type": "long"},
38 "downloads_in_past_7_days": {"type": "long"},
39 "downloads_in_past_half_year": {"type": "long"},
40 "i_provides": {
41 "type": "string",
42 "index": "not_analyzed",
43 "omit_norms": True,
44 "index_options": "docs"
45 },
46 "i_requires": {
47 "type": "string",
48 "index": "not_analyzed",
49 "omit_norms": True,
50 "index_options": "docs"
51 },
52 "is_featured": {"type": "boolean"},
53 "name": {
54 "type": "string",
55 "index": "not_analyzed",
56 "omit_norms": True,
57 "index_options": "docs"
58 },
59 "owner": {
60 "type": "string",
61 "index": "not_analyzed", "omit_norms": True,
62 "index_options": "docs"
63 },
64 "series": {
65 "type": "string",
66 "index": "not_analyzed",
67 "omit_norms": True,
68 "index_options": "docs"
69 },
70 "store_data": {
71 "properties": {
72 "errors": {"type": "string"}
73 }
74 },
75 "store_url": {
76 "type": "string",
77 "index": "not_analyzed",
78 "omit_norms": True,
79 "index_options": "docs"
80 },
81 "summary": {"type": "string"}
82 }
83 },
84 "provides": {
85 "dynamic": "false",
86 "properties": {
87 "interface": {
88 "type": "string",
89 "index": "not_analyzed",
90 "omit_norms": True,
91 "index_options": "docs"
92 },
93 "relation_name": {
94 "type": "string",
95 "index": "not_analyzed",
96 "omit_norms": True,
97 "index_options": "docs"
98 },
99 "scope": {
100 "type": "string",
101 "index": "not_analyzed",
102 "omit_norms": True,
103 "index_options": "docs"
104 }
105 }
106 },
107 "requires": {
108 "dynamic": "false",
109 "properties": {
110 "interface": {
111 "type": "string",
112 "index": "not_analyzed",
113 "omit_norms": True,
114 "index_options": "docs"
115 },
116 "relation_name": {
117 "type": "string",
118 "index": "not_analyzed",
119 "omit_norms": True,
120 "index_options": "docs"
121 },
122 "scope": {
123 "type": "string",
124 "index": "not_analyzed",
125 "omit_norms": True,
126 "index_options": "docs"
127 }
128 }
129 }
130 }
131}}
132
133old_bundle_mapping = {"bundle": {
134 "type": "object",
135 "dynamic": "false",
136 "properties": {
137 "data": {
138 "properties": {
139 "name": {
140 "type": "string",
141 "index": "not_analyzed",
142 "omit_norms": True,
143 "index_options": "docs"
144 },
145 "owner": {
146 "type": "string",
147 "index": "not_analyzed",
148 "omit_norms": True,
149 "index_options": "docs"
150 },
151 "series": {
152 "type": "string",
153 "index": "not_analyzed",
154 "omit_norms": True,
155 "index_options": "docs"
156 },
157 "title": {
158 "type": "string",
159 "index": "not_analyzed",
160 "omit_norms": True,
161 "index_options": "docs"
162 }
163 }
164 }
165 }
166}}
0167
=== modified file 'charmworld/migrations/versions/tests/test_migrations.py'
--- charmworld/migrations/versions/tests/test_migrations.py 2014-04-22 20:36:12 +0000
+++ charmworld/migrations/versions/tests/test_migrations.py 2014-05-20 14:13:54 +0000
@@ -1,16 +1,22 @@
1# Copyright 2012, 2013 Canonical Ltd. This software is licensed under the1# Copyright 2012, 2013 Canonical Ltd. This software is licensed under the
2# GNU Affero General Public License version 3 (see the file LICENSE).2# GNU Affero General Public License version 3 (see the file LICENSE).
33
4from charmworld.models import (
5 CharmSource,
6)
7from charmworld.testing import (4from charmworld.testing import (
8 factory,5 factory,
9 MongoTestBase,6 MongoTestBase,
10)7)
118
12from charmworld.migrations import migrate9from charmworld.migrations import migrate
13from charmworld.migrations.migrate import Versions10from charmworld.migrations.migrate import (
11 DataStore,
12 Versions
13)
14
15from charmworld.migrations.versions.tests.data import migration_027 as mdata
16
17from charmworld.models import CharmSource
18
19from pyelasticsearch.exceptions import ElasticHttpError
1420
15import logging21import logging
16import mock22import mock
@@ -126,3 +132,72 @@
126 def test_exodus_restrictions(self):132 def test_exodus_restrictions(self):
127 """Ensure that deploy-from-scratch does not violate exodus rules."""133 """Ensure that deploy-from-scratch does not violate exodus rules."""
128 self.versions._check_exodus(self.versions.list_pending(0))134 self.versions._check_exodus(self.versions.list_pending(0))
135
136
137class TestUseNgrams027(MigrationTestBase):
138
139 version = 27
140 module_name = '027_use_ngrams.py'
141
142 def setUp(self):
143 super(TestUseNgrams027, self).setUp()
144 self.use_index_client()
145 self.data_store = DataStore(self.db)
146 self.charm_source = CharmSource(self.db, self.index_client)
147
148 def exists_in_index(self, id_):
149 return self.index_client.get(id_) is not None
150
151 def sync_index(self):
152 charm_ids = self.charm_source.sync_index()
153 while True:
154 try:
155 next(charm_ids)
156 except StopIteration:
157 break
158
159 def put_old_mapping(self):
160 # Delete the temp_index.
161 self.index_client.delete_index()
162 # Create an empty index.
163 self.index_client._client.create_index(self.index_client.index_name)
164 # Put up the old mappings.
165 for type_ in ('charm', 'bundle'):
166 self.index_client._client.put_mapping(
167 self.index_client.index_name,
168 type_,
169 getattr(mdata, 'old_{}_mapping'.format(type_)))
170
171 def test_index_updated(self):
172 """Ensure that running upgrade reindexes charms."""
173 self.put_old_mapping()
174 self.versions.ensure_initialized(self.data_store, True)
175 ## Pretend upgrades through 26 are complete.
176 self.data_store.update_version(26)
177 charm = factory.get_charm_json()
178 self.db.charms.insert(charm)
179 self.assertFalse(self.exists_in_index(charm['_id']))
180 self.sync_index()
181 self.assertTrue(self.exists_in_index(charm['_id']))
182 self.versions.upgrade(self.data_store, self.index_client, True)
183 self.assertEquals(self.data_store.current_version, self.version)
184 self.assertTrue(self.exists_in_index(charm['_id']))
185
186 def test_exception_raised(self):
187 """Putting new mapping without creating the filter and analyzer
188 raises the expected exception.
189 """
190 self.versions.ensure_initialized(self.data_store, True)
191 ## Pretend upgrades through 26 are complete.
192 self.data_store.update_version(26)
193 self.put_old_mapping()
194 charm = factory.get_charm_json()
195 self.assertFalse(self.exists_in_index(charm['_id']))
196 self.index_client.index_charm(charm)
197 self.assertTrue(self.exists_in_index(charm['_id']))
198 with self.assertRaises(ElasticHttpError) as e:
199 self.index_client.put_mapping()
200 self.assertEquals(e.status_code, 400)
201 self.assertEquals(e.error,
202 u'MapperParsingException[Analyzer [n3_20grams] '
203 u'not found for field [ngrams]]')
129204
=== modified file 'charmworld/search.py'
--- charmworld/search.py 2014-05-15 20:36:22 +0000
+++ charmworld/search.py 2014-05-20 14:13:54 +0000
@@ -180,43 +180,6 @@
180 ]180 ]
181 }181 }
182 }182 }
183 },
184 "mappings": {
185 "charm": {
186 "properties": {
187 "name": {
188 "type": "multi_field",
189 "fields": {
190 "ngrams": {
191 "type": "string",
192 "analyzer": "n3_20grams"
193 },
194 "name": {
195 "type": "string",
196 "index": "not_analyzed"
197 }
198 }
199 }
200 }
201
202 },
203 "bundle": {
204 "properties": {
205 "name": {
206 "type": "multi_field",
207 "fields": {
208 "ngrams": {
209 "type": "string",
210 "analyzer": "n3_20grams"
211 },
212 "name": {
213 "type": "string",
214 "index": "not_analyzed"
215 }
216 }
217 }
218 }
219 }
220 }183 }
221 }184 }
222 })185 })

Subscribers

People subscribed via source and target branches

to all changes: