Merge lp:~adeuring/charmworld/check-charm-exists-in-store-1160527 into lp:~juju-jitsu/charmworld/trunk

Proposed by Abel Deuring
Status: Merged
Approved by: Abel Deuring
Approved revision: 192
Merged at revision: 195
Proposed branch: lp:~adeuring/charmworld/check-charm-exists-in-store-1160527
Merge into: lp:~juju-jitsu/charmworld/trunk
Diff against target: 162 lines (+57/-15)
3 files modified
charmworld/search.py (+11/-5)
charmworld/test_search.py (+29/-0)
charmworld/testing/factory.py (+17/-10)
To merge this branch: bzr merge lp:~adeuring/charmworld/check-charm-exists-in-store-1160527
Reviewer Review Type Date Requested Status
Abel Deuring (community) Approve
Review via email: mp+158951@code.launchpad.net

Commit message

return only charms without errors in search() and api_search() by default.

Description of the change

Return only valid charms in elasticsearch results.

For each (possible) charm it knows about, charmworld records its status
in the main charm store (store.juju.ubuntu.com), but we did not
use this status information.

One important detail we ignored is if the charm store says that a given
charm is valid, or if it exists at all. We store this kind of status
information as a dictionary charm['store_data']; if the store "sees" an
error, this dictionary has the key 'errors'.

This branch adds an additional elasticsearch filter to the "search
expression" used by ElasticSearchClient.api_search() and
ElasticSearchClient.search() that ensures that only valid charms
are returned in search results. This behaviour can be changed by
setting the new parameter valid_charm_only to False.

As a drive-by fix, I changed the the conversion from a datetime instance
to "seconds since the Unix epoch" so that the local timezone is not
used. This fixes a failure of test_api.TestAPI0Charms.test_charms()
that is not related to my changes.

https://codereview.appspot.com/8641044/

To post a comment you must log in.
Revision history for this message
Aaron Bentley (abentley) wrote :

On 2013/04/15 15:02:29, abel.deuring wrote:
> Please take a look.

lgtm

https://codereview.appspot.com/8641044/

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=== modified file 'charmworld/search.py'
2--- charmworld/search.py 2013-04-11 16:40:32 +0000
3+++ charmworld/search.py 2013-04-15 15:08:44 +0000
4@@ -123,12 +123,16 @@
5 return dsl
6
7 @staticmethod
8- def _get_filtered(dsl, filters, type_):
9+ def _get_filtered(dsl, filters, type_, valid_charms_only):
10 and_clause = {
11 'and': [
12 {'terms': {key: value}} for key, value in filters.items()
13 ]
14 }
15+ if valid_charms_only:
16+ and_clause['and'].append(
17+ {'missing': {'field': 'store_data.errors'}}
18+ )
19 if type_ is not None:
20 typeclauses = []
21 for value in type_:
22@@ -164,10 +168,11 @@
23 result = self._client.get(self.index_name, 'charm', charm_id)
24 return result['_source']
25
26- def api_search(self, text, filters, type_, limit=None):
27+ def api_search(self, text, filters, type_, limit=None,
28+ valid_charms_only=True):
29 dsl = self._get_text_query(text)
30 dsl = self._get_official_boost(dsl)
31- dsl = self._get_filtered(dsl, filters, type_)
32+ dsl = self._get_filtered(dsl, filters, type_, valid_charms_only)
33 if limit is None:
34 status = self._client.status(index=self.index_name)['indices']
35 limit = status[self.index_name]['docs']['num_docs']
36@@ -176,13 +181,14 @@
37 hits = self._client.search(dsl, index=self.index_name, size=limit)
38 return [hit['_source'] for hit in hits['hits']['hits']]
39
40- def search(self, terms):
41+ def search(self, terms, valid_charms_only=True):
42 size = None
43 if terms == '':
44 size = 0
45
46 dsl = self._get_text_query(terms)
47- dsl = {'query': self._get_official_boost(dsl)}
48+ dsl = self._get_official_boost(dsl)
49+ dsl = self._get_filtered(dsl, {}, None, valid_charms_only)
50
51 with Timer() as timer:
52 status = self._client.status(index=self.index_name)['indices']
53
54=== modified file 'charmworld/test_search.py'
55--- charmworld/test_search.py 2013-04-11 18:08:49 +0000
56+++ charmworld/test_search.py 2013-04-15 15:08:44 +0000
57@@ -296,3 +296,32 @@
58 self.assertEqual(0, len(result))
59 with self.assertRaises(NegativeLimit):
60 result = self.index_client.api_search('', {}, None, -1)
61+
62+ def test_api_search_charm_with_error(self):
63+ """api_search() does not return charms withs errors by default."""
64+ self.makeCharm(name='evil-charm', charm_error=True)
65+ self.makeCharm(name='good-charm')
66+ result = self.index_client.api_search('', {}, None)
67+ self.assertEqual(1, len(result))
68+ self.assertEqual('good-charm', result[0]['name'])
69+ # Charms with error are included in the search result
70+ # if the parameter valid_charms_only is set to False.
71+ result = self.index_client.api_search(
72+ '', {}, None, valid_charms_only=False)
73+ self.assertEqual(2, len(result))
74+ names = set(charm['name'] for charm in result)
75+ self.assertEqual(set(('good-charm', 'evil-charm')), names)
76+
77+ def test_search_charm_with_error(self):
78+ """search() does not return charms withs errors by default."""
79+ self.makeCharm(name='evil-charm', owner='foo', charm_error=True)
80+ self.makeCharm(name='good-charm', owner='foo')
81+ result = self.index_client.search('foo')
82+ self.assertEqual(1, result['matches'])
83+ self.assertEqual('good-charm', result['results'][0]['data']['name'])
84+ # Charms with error are included in the search result
85+ # if the parameter valid_charms_only is set to False.
86+ result = self.index_client.search('foo', valid_charms_only=False)
87+ self.assertEqual(2, result['matches'])
88+ names = set(entry['data']['name'] for entry in result['results'])
89+ self.assertEqual(set(('good-charm', 'evil-charm')), names)
90
91=== modified file 'charmworld/testing/factory.py'
92--- charmworld/testing/factory.py 2013-04-11 18:08:49 +0000
93+++ charmworld/testing/factory.py 2013-04-15 15:08:44 +0000
94@@ -2,6 +2,7 @@
95 # GNU Affero General Public License version 3 (see the file LICENSE).
96
97 """Helpers to generate test data for use."""
98+from calendar import timegm
99 from datetime import date
100 import hashlib
101 import os
102@@ -9,7 +10,7 @@
103 from random import randint
104 import random
105 import string
106-from time import mktime, time
107+from time import time
108
109 from charmworld.jobs.ingest import (
110 JENKINS_PROVIDERS,
111@@ -93,7 +94,7 @@
112
113 if isinstance(created, date):
114 # Change `created` dates should be timestamps.
115- created = mktime(created.timetuple())
116+ created = timegm(created.timetuple())
117 return {
118 'authors': authors,
119 'committer': committer,
120@@ -107,7 +108,7 @@
121 series=None, owner=None, description=None, revision=1,
122 summary=None, revno=12, maintainer=None,
123 commit_message='maintainer', provides=None, requires=None,
124- options=None, files=None):
125+ options=None, files=None, charm_error=False):
126 """Return the json of a charm."""
127 if not description:
128 description = """byobu-class provides remote terminal access through
129@@ -165,6 +166,18 @@
130 'subdir': 'hooks',
131 },
132 }
133+ store_data = {
134+ "address": "cs:precise/byobu-classroom",
135+ "store_checked": "Thu Dec 6 17:42:05 2012"
136+ }
137+ if charm_error:
138+ store_data["errors"] = ['entry not found']
139+ else:
140+ store_data.update({
141+ "digest": "jdoe@example.com-20120723223205-n5zke9r9ahb40su",
142+ "revision": 1,
143+ "sha256": "107c230dc3481f8b8de9decfff6439a23caae2ea670bddb413ddf1",
144+ })
145 charm = {
146 "bname": bname,
147 "branch_dir": os.path.join(branch_root, name),
148@@ -210,13 +223,7 @@
149 "revision": revision,
150 "series": series,
151 "short_url": "/charms/precise/byobu-classroom",
152- "store_data": {
153- "address": "cs:precise/byobu-classroom",
154- "digest": "jdoe@example.com-20120723223205-n5zke9r9ahb40su",
155- "revision": 1,
156- "sha256": "107c230dc3481f8b8de9decfff6439a23caae2ea670bddb413ddf1",
157- "store_checked": "Thu Dec 6 17:42:05 2012"
158- },
159+ "store_data": store_data,
160 "summary": summary,
161 'config': {
162 'options': options,

Subscribers

People subscribed via source and target branches