Merge lp:~adeuring/charmworld/fixed-interfaces-mapping into lp:~juju-jitsu/charmworld/trunk

Proposed by Abel Deuring
Status: Merged
Approved by: Abel Deuring
Approved revision: 383
Merged at revision: 393
Proposed branch: lp:~adeuring/charmworld/fixed-interfaces-mapping
Merge into: lp:~juju-jitsu/charmworld/trunk
Diff against target: 229 lines (+89/-42)
4 files modified
charmworld/migrations/versions/019_fixed_mapping_for_interfaces.py (+10/-0)
charmworld/migrations/versions/tests/test_migrations.py (+44/-0)
charmworld/search.py (+30/-20)
charmworld/tests/test_search.py (+5/-22)
To merge this branch: bzr merge lp:~adeuring/charmworld/fixed-interfaces-mapping
Reviewer Review Type Date Requested Status
Abel Deuring (community) Approve
Benji York (community) Approve
Review via email: mp+184805@code.launchpad.net

Commit message

use a static mapping for the charm index.

Description of the change

his branch removes the last remainig dynamic part of the mapping used
by the ElasticSearch index for charms.

The properties "requires" and "provides" of a charm are dictionaries
of the form

    {'relation_name': {'interface': 'http', ...}, ...}

which means that each relation name must appear as a property in the mapping.

The ES server gets charm data in the form

    {'_id': ...,
     'data': real_charm_data
     }

ElasticSearchClient._wrap_charm() now adds two entries "requires" and
"provides" that are lists of the relation data from charm["provides"]
and charm["requires"], respectively. The relation name is added to
the list entries.

This allows to define a static mapping for the relation data.

test_mapping_changes_during_indexing() requires an obvious change.
The test itself is now a bit flaky -- it might succeed accidentally:
As the comment for the old version notes, the result of the call of
get_mapping() does not necessarily incorporate possible mapping
changes after data has been added to the index. Hence it might make
sense to add a sleep(0.5) call to the test.

The change of the mapping and the related change of
ElasticSearchClient._get_text_query() require to reindex all charms.
This is done in an exodus.

To post a comment you must log in.
Revision history for this message
Benji York (benji) wrote :

This branch looks good to me.

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

The Jenkins job https://jenkins.qa.ubuntu.com/job/charmworld-merger-trunk/453/console reported an error when processing this lp:~adeuring/charmworld/fixed-interfaces-mapping branch.
Not merging it.

383. By Abel Deuring

test failure fixed.

Revision history for this message
Abel Deuring (adeuring) :
review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== added file 'charmworld/migrations/versions/019_fixed_mapping_for_interfaces.py'
2--- charmworld/migrations/versions/019_fixed_mapping_for_interfaces.py 1970-01-01 00:00:00 +0000
3+++ charmworld/migrations/versions/019_fixed_mapping_for_interfaces.py 2013-09-11 09:52:08 +0000
4@@ -0,0 +1,10 @@
5+
6+# fixed mapping for interfaces.
7+
8+def exodus_update(source, target, current_version):
9+ """Just copy the data from source to target.
10+
11+ The goal of this exodus is to use a new index with a static mapping.
12+ """
13+ for charm in source.collection.find():
14+ target.save(charm)
15
16=== modified file 'charmworld/migrations/versions/tests/test_migrations.py'
17--- charmworld/migrations/versions/tests/test_migrations.py 2013-09-05 21:36:14 +0000
18+++ charmworld/migrations/versions/tests/test_migrations.py 2013-09-11 09:52:08 +0000
19@@ -6,6 +6,7 @@
20
21 from charmworld.models import (
22 Bundle,
23+ CharmSource,
24 store_bundles,
25 )
26 from charmworld.search import (
27@@ -13,6 +14,7 @@
28 IndexMissing,
29 )
30 from charmworld.testing import (
31+ factory,
32 MongoTestBase,
33 )
34 from charmworld.migrations import migrate
35@@ -84,3 +86,45 @@
36 self.db, self.index_client, '018_delete_all_bundles.py')
37 # Put the index back for the test case to handle.
38 self.index_client.create_index()
39+
40+
41+class TestMigration019(MigrationTestBase):
42+
43+ def setUp(self):
44+ super(TestMigration019, self).setUp()
45+ self.use_index_client()
46+
47+ def test_new_db_and_index_have_all_old_data(self):
48+ source = CharmSource.from_request(self)
49+ charm_1 = factory.get_charm_json(
50+ name='one',
51+ provides={
52+ 'website': {
53+ 'interface': 'http',
54+ },
55+ },
56+ requires={
57+ 'database': {
58+ 'interface': 'mysql',
59+ }
60+ })
61+ source.save(charm_1)
62+ charm_2 = factory.get_charm_json(
63+ name='two',
64+ provides={
65+ 'database': {
66+ 'interface': 'mysql',
67+ }
68+ })
69+ source.save(charm_2)
70+
71+ exodus_update = self.versions.get_exodus(
72+ '019_fixed_mapping_for_interfaces.py')
73+ target = self.versions.get_pending_source(source, 19)
74+ target.index_client.create_index()
75+ target.index_client.put_mapping()
76+ self.addCleanup(target.index_client.delete_index)
77+ exodus_update(source, target, 19)
78+ self.assertEqual(2, target.collection.find().count())
79+ self.assertEqual(2, target.index_client.search('mysql')['matches'])
80+ self.assertEqual(1, target.index_client.search('http')['matches'])
81
82=== modified file 'charmworld/search.py'
83--- charmworld/search.py 2013-09-04 14:48:30 +0000
84+++ charmworld/search.py 2013-09-11 09:52:08 +0000
85@@ -27,8 +27,6 @@
86 charm_exact_fields = [
87 'owner',
88 'series',
89- 'provides.*.interface',
90- 'requires.*.interface',
91 ]
92 charm_free_text_fields = {
93 'name': 10,
94@@ -238,19 +236,6 @@
95 'errors': {'type': 'string'},
96 }
97 }
98- # XXX Abel Deuring 2013-08-19 bug=1194907: The fields requires
99- # and provides are user defined dictionaries. We can't specify
100- # all possible keys. This partly reintroduces a large mapping;
101- # the proper way to fix this is described in bug 1194907.
102- inner_charm_properties['requires'] = {
103- 'type': 'object',
104- 'dynamic': True,
105- }
106- inner_charm_properties['provides'] = {
107- 'type': 'object',
108- 'dynamic': True,
109- }
110-
111 inner_charm_properties['config'] = {
112 'properties': {
113 'options': {
114@@ -261,10 +246,23 @@
115 }
116 }
117 }
118+
119+ searchable_interfaces = {
120+ 'type': 'object',
121+ 'dynamic': False,
122+ 'properties': {
123+ 'relation_name': {'type': 'string', 'index': 'not_analyzed'},
124+ 'interface': {'type': 'string', 'index': 'not_analyzed'},
125+ 'scope': {'type': 'string', 'index': 'not_analyzed'},
126+ },
127+ }
128+
129 charm_properties = {
130 'data': {
131 'properties': inner_charm_properties,
132- }
133+ },
134+ 'requires': searchable_interfaces,
135+ 'provides': searchable_interfaces,
136 }
137
138 bundle_exact_index = [
139@@ -315,10 +313,19 @@
140 def _wrap_charm(charm_data):
141 # Avoid circular import.
142 from charmworld.models import construct_charm_id
143- return {
144+ result = {
145 '_id': construct_charm_id(charm_data, use_revision=False),
146 'data': charm_data,
147 }
148+ for relation_type in ('requires', 'provides'):
149+ if relation_type in charm_data:
150+ searchable_infos = []
151+ for name, info in charm_data[relation_type].items():
152+ info = info.copy()
153+ info['relation_name'] = name
154+ searchable_infos.append(info)
155+ result[relation_type] = searchable_infos
156+ return result
157
158 def index_charm(self, charm):
159 self._index_item(self._wrap_charm(charm), CHARM)
160@@ -383,15 +390,18 @@
161 bundle_fields.extend(bundle_exact_fields)
162
163 if text != '':
164- def make_query(fields):
165+ def make_query(data_fields, other_fields=[]):
166+ fields = ['data.' + field for field in data_fields]
167+ fields.extend(other_fields)
168 return {
169 'query_string': {
170 'query': text,
171- 'fields': ['data.' + field for field in fields],
172+ 'fields': fields,
173 }}
174
175 charm_dsl = {'filtered': {
176- 'query': make_query(charm_fields),
177+ 'query': make_query(
178+ charm_fields, ['requires.*', 'provides.*']),
179 'filter': {'type': {'value': CHARM}}
180 }}
181 bundle_dsl = {'filtered': {
182
183=== modified file 'charmworld/tests/test_search.py'
184--- charmworld/tests/test_search.py 2013-09-05 20:17:16 +0000
185+++ charmworld/tests/test_search.py 2013-09-11 09:52:08 +0000
186@@ -261,6 +261,7 @@
187 def test_search_matches_on_search_terms(self):
188 fields = charm_free_text_fields.keys() + charm_exact_fields
189 fields = [field.split('.')[0] for field in fields]
190+ fields.extend(['requires', 'provides'])
191 charm = factory.get_charm_json(categories=['unknown'])
192 # Ensure all fields have unique values.
193 charm['address'] = 'cs:bogus-address'
194@@ -894,31 +895,13 @@
195 "Character '%s' at the end of a search term causes "
196 "ElasticHttpError" % char)
197
198- def test_mapping_changes_during_indexing(self):
199- # When a charm is indexed, only the mappings for the properties
200- # "requires" and "provides" change.
201- before = self.index_client.get_mapping()['charm']['properties']
202- before = before['data']['properties']
203- before_provides = before.pop('provides')
204- before_requires = before.pop('requires')
205+ def test_no_mapping_changes_during_indexing(self):
206+ # The mapping does not change when charm is indexed.
207+ before = self.index_client.get_mapping()
208 charm = factory.get_charm_json()
209 self.index_client.index_charm(charm)
210- # Avoid spurious failures. The new mapping is not immediately
211- # available after the index_charm() call. Calling
212- # index_client.wait_for_green_status() does not help here.
213- retry = 0
214- while retry < 10:
215- after = self.index_client.get_mapping()['charm']['properties']
216- after = after['data']['properties']
217- after_provides = after.pop('provides')
218- after_requires = after.pop('requires')
219- if after_provides != before_provides:
220- break
221- sleep(0.1)
222- retry += 1
223+ after = self.index_client.get_mapping()
224 self.assertEqual(before, after)
225- self.assertNotEqual(before_provides, after_provides)
226- self.assertNotEqual(before_requires, after_requires)
227
228
229 class TestIndexingBundles(TestCase):

Subscribers

People subscribed via source and target branches