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
=== added file 'charmworld/migrations/versions/019_fixed_mapping_for_interfaces.py'
--- charmworld/migrations/versions/019_fixed_mapping_for_interfaces.py 1970-01-01 00:00:00 +0000
+++ charmworld/migrations/versions/019_fixed_mapping_for_interfaces.py 2013-09-11 09:52:08 +0000
@@ -0,0 +1,10 @@
1
2# fixed mapping for interfaces.
3
4def exodus_update(source, target, current_version):
5 """Just copy the data from source to target.
6
7 The goal of this exodus is to use a new index with a static mapping.
8 """
9 for charm in source.collection.find():
10 target.save(charm)
011
=== modified file 'charmworld/migrations/versions/tests/test_migrations.py'
--- charmworld/migrations/versions/tests/test_migrations.py 2013-09-05 21:36:14 +0000
+++ charmworld/migrations/versions/tests/test_migrations.py 2013-09-11 09:52:08 +0000
@@ -6,6 +6,7 @@
66
7from charmworld.models import (7from charmworld.models import (
8 Bundle,8 Bundle,
9 CharmSource,
9 store_bundles,10 store_bundles,
10)11)
11from charmworld.search import (12from charmworld.search import (
@@ -13,6 +14,7 @@
13 IndexMissing,14 IndexMissing,
14)15)
15from charmworld.testing import (16from charmworld.testing import (
17 factory,
16 MongoTestBase,18 MongoTestBase,
17)19)
18from charmworld.migrations import migrate20from charmworld.migrations import migrate
@@ -84,3 +86,45 @@
84 self.db, self.index_client, '018_delete_all_bundles.py')86 self.db, self.index_client, '018_delete_all_bundles.py')
85 # Put the index back for the test case to handle.87 # Put the index back for the test case to handle.
86 self.index_client.create_index()88 self.index_client.create_index()
89
90
91class TestMigration019(MigrationTestBase):
92
93 def setUp(self):
94 super(TestMigration019, self).setUp()
95 self.use_index_client()
96
97 def test_new_db_and_index_have_all_old_data(self):
98 source = CharmSource.from_request(self)
99 charm_1 = factory.get_charm_json(
100 name='one',
101 provides={
102 'website': {
103 'interface': 'http',
104 },
105 },
106 requires={
107 'database': {
108 'interface': 'mysql',
109 }
110 })
111 source.save(charm_1)
112 charm_2 = factory.get_charm_json(
113 name='two',
114 provides={
115 'database': {
116 'interface': 'mysql',
117 }
118 })
119 source.save(charm_2)
120
121 exodus_update = self.versions.get_exodus(
122 '019_fixed_mapping_for_interfaces.py')
123 target = self.versions.get_pending_source(source, 19)
124 target.index_client.create_index()
125 target.index_client.put_mapping()
126 self.addCleanup(target.index_client.delete_index)
127 exodus_update(source, target, 19)
128 self.assertEqual(2, target.collection.find().count())
129 self.assertEqual(2, target.index_client.search('mysql')['matches'])
130 self.assertEqual(1, target.index_client.search('http')['matches'])
87131
=== modified file 'charmworld/search.py'
--- charmworld/search.py 2013-09-04 14:48:30 +0000
+++ charmworld/search.py 2013-09-11 09:52:08 +0000
@@ -27,8 +27,6 @@
27charm_exact_fields = [27charm_exact_fields = [
28 'owner',28 'owner',
29 'series',29 'series',
30 'provides.*.interface',
31 'requires.*.interface',
32]30]
33charm_free_text_fields = {31charm_free_text_fields = {
34 'name': 10,32 'name': 10,
@@ -238,19 +236,6 @@
238 'errors': {'type': 'string'},236 'errors': {'type': 'string'},
239 }237 }
240 }238 }
241 # XXX Abel Deuring 2013-08-19 bug=1194907: The fields requires
242 # and provides are user defined dictionaries. We can't specify
243 # all possible keys. This partly reintroduces a large mapping;
244 # the proper way to fix this is described in bug 1194907.
245 inner_charm_properties['requires'] = {
246 'type': 'object',
247 'dynamic': True,
248 }
249 inner_charm_properties['provides'] = {
250 'type': 'object',
251 'dynamic': True,
252 }
253
254 inner_charm_properties['config'] = {239 inner_charm_properties['config'] = {
255 'properties': {240 'properties': {
256 'options': {241 'options': {
@@ -261,10 +246,23 @@
261 }246 }
262 }247 }
263 }248 }
249
250 searchable_interfaces = {
251 'type': 'object',
252 'dynamic': False,
253 'properties': {
254 'relation_name': {'type': 'string', 'index': 'not_analyzed'},
255 'interface': {'type': 'string', 'index': 'not_analyzed'},
256 'scope': {'type': 'string', 'index': 'not_analyzed'},
257 },
258 }
259
264 charm_properties = {260 charm_properties = {
265 'data': {261 'data': {
266 'properties': inner_charm_properties,262 'properties': inner_charm_properties,
267 }263 },
264 'requires': searchable_interfaces,
265 'provides': searchable_interfaces,
268 }266 }
269267
270 bundle_exact_index = [268 bundle_exact_index = [
@@ -315,10 +313,19 @@
315 def _wrap_charm(charm_data):313 def _wrap_charm(charm_data):
316 # Avoid circular import.314 # Avoid circular import.
317 from charmworld.models import construct_charm_id315 from charmworld.models import construct_charm_id
318 return {316 result = {
319 '_id': construct_charm_id(charm_data, use_revision=False),317 '_id': construct_charm_id(charm_data, use_revision=False),
320 'data': charm_data,318 'data': charm_data,
321 }319 }
320 for relation_type in ('requires', 'provides'):
321 if relation_type in charm_data:
322 searchable_infos = []
323 for name, info in charm_data[relation_type].items():
324 info = info.copy()
325 info['relation_name'] = name
326 searchable_infos.append(info)
327 result[relation_type] = searchable_infos
328 return result
322329
323 def index_charm(self, charm):330 def index_charm(self, charm):
324 self._index_item(self._wrap_charm(charm), CHARM)331 self._index_item(self._wrap_charm(charm), CHARM)
@@ -383,15 +390,18 @@
383 bundle_fields.extend(bundle_exact_fields)390 bundle_fields.extend(bundle_exact_fields)
384391
385 if text != '':392 if text != '':
386 def make_query(fields):393 def make_query(data_fields, other_fields=[]):
394 fields = ['data.' + field for field in data_fields]
395 fields.extend(other_fields)
387 return {396 return {
388 'query_string': {397 'query_string': {
389 'query': text,398 'query': text,
390 'fields': ['data.' + field for field in fields],399 'fields': fields,
391 }}400 }}
392401
393 charm_dsl = {'filtered': {402 charm_dsl = {'filtered': {
394 'query': make_query(charm_fields),403 'query': make_query(
404 charm_fields, ['requires.*', 'provides.*']),
395 'filter': {'type': {'value': CHARM}}405 'filter': {'type': {'value': CHARM}}
396 }}406 }}
397 bundle_dsl = {'filtered': {407 bundle_dsl = {'filtered': {
398408
=== modified file 'charmworld/tests/test_search.py'
--- charmworld/tests/test_search.py 2013-09-05 20:17:16 +0000
+++ charmworld/tests/test_search.py 2013-09-11 09:52:08 +0000
@@ -261,6 +261,7 @@
261 def test_search_matches_on_search_terms(self):261 def test_search_matches_on_search_terms(self):
262 fields = charm_free_text_fields.keys() + charm_exact_fields262 fields = charm_free_text_fields.keys() + charm_exact_fields
263 fields = [field.split('.')[0] for field in fields]263 fields = [field.split('.')[0] for field in fields]
264 fields.extend(['requires', 'provides'])
264 charm = factory.get_charm_json(categories=['unknown'])265 charm = factory.get_charm_json(categories=['unknown'])
265 # Ensure all fields have unique values.266 # Ensure all fields have unique values.
266 charm['address'] = 'cs:bogus-address'267 charm['address'] = 'cs:bogus-address'
@@ -894,31 +895,13 @@
894 "Character '%s' at the end of a search term causes "895 "Character '%s' at the end of a search term causes "
895 "ElasticHttpError" % char)896 "ElasticHttpError" % char)
896897
897 def test_mapping_changes_during_indexing(self):898 def test_no_mapping_changes_during_indexing(self):
898 # When a charm is indexed, only the mappings for the properties899 # The mapping does not change when charm is indexed.
899 # "requires" and "provides" change.900 before = self.index_client.get_mapping()
900 before = self.index_client.get_mapping()['charm']['properties']
901 before = before['data']['properties']
902 before_provides = before.pop('provides')
903 before_requires = before.pop('requires')
904 charm = factory.get_charm_json()901 charm = factory.get_charm_json()
905 self.index_client.index_charm(charm)902 self.index_client.index_charm(charm)
906 # Avoid spurious failures. The new mapping is not immediately903 after = self.index_client.get_mapping()
907 # available after the index_charm() call. Calling
908 # index_client.wait_for_green_status() does not help here.
909 retry = 0
910 while retry < 10:
911 after = self.index_client.get_mapping()['charm']['properties']
912 after = after['data']['properties']
913 after_provides = after.pop('provides')
914 after_requires = after.pop('requires')
915 if after_provides != before_provides:
916 break
917 sleep(0.1)
918 retry += 1
919 self.assertEqual(before, after)904 self.assertEqual(before, after)
920 self.assertNotEqual(before_provides, after_provides)
921 self.assertNotEqual(before_requires, after_requires)
922905
923906
924class TestIndexingBundles(TestCase):907class TestIndexingBundles(TestCase):

Subscribers

People subscribed via source and target branches