Merge lp:~abentley/charmworld/store-data-on-enqueue into lp:~juju-jitsu/charmworld/trunk
- store-data-on-enqueue
- Merge into trunk
Proposed by
Aaron Bentley
Status: | Merged |
---|---|
Approved by: | Aaron Bentley |
Approved revision: | 318 |
Merged at revision: | 321 |
Proposed branch: | lp:~abentley/charmworld/store-data-on-enqueue |
Merge into: | lp:~juju-jitsu/charmworld/trunk |
Diff against target: |
849 lines (+228/-321) 15 files modified
charmworld/charmstore.py (+30/-7) charmworld/jobs/ingest.py (+8/-17) charmworld/jobs/lp.py (+19/-6) charmworld/jobs/tests/test_ingest.py (+24/-4) charmworld/jobs/tests/test_lp.py (+25/-6) charmworld/jobs/tests/test_store.py (+12/-28) charmworld/models.py (+1/-1) charmworld/tests/test_charmstore.py (+109/-8) migrations/versions/001_load_qa_questions.py (+0/-137) migrations/versions/002_Escape_dots_dollar_signs_percent_signs_in_mongo_keys.py (+0/-7) migrations/versions/003_remove_provider_qa_questions.py (+0/-42) migrations/versions/004_remove_charm_errors.py (+0/-4) migrations/versions/008_delete_icon_field.py (+0/-8) migrations/versions/009_add_downloads_attribute.py (+0/-21) migrations/versions/tests/test_migrations.py (+0/-25) |
To merge this branch: | bzr merge lp:~abentley/charmworld/store-data-on-enqueue |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Curtis Hovey (community) | code | Approve | |
Review via email: mp+174827@code.launchpad.net |
Commit message
Get store data when querying lp.
Description of the change
This branch ensures we get the store data at the same time we get the LP data, so that we have all we need when we create the mongodb primary key
To post a comment you must log in.
- 317. By Aaron Bentley
-
Remove requests import.
- 318. By Aaron Bentley
-
Merged trunk into store-data-
on-enqueue.
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'charmworld/charmstore.py' |
2 | --- charmworld/charmstore.py 2013-07-01 14:45:02 +0000 |
3 | +++ charmworld/charmstore.py 2013-07-22 20:55:30 +0000 |
4 | @@ -7,7 +7,6 @@ |
5 | from datetime import timedelta |
6 | |
7 | |
8 | -import requests |
9 | from requests import ( |
10 | Request, |
11 | Session, |
12 | @@ -71,12 +70,15 @@ |
13 | params=params |
14 | ) |
15 | |
16 | - def get_charm_info(self, address): |
17 | - url = self.STORE_URL + "/charm-info?charms=%s&stats=0" % address |
18 | - data = requests.get(url).json() |
19 | - data = data[address] |
20 | - data['address'] = address |
21 | - return data |
22 | + @classmethod |
23 | + def get_charms_info_request(cls, addresses): |
24 | + return Request('POST', cls.STORE_URL + '/charm-info', |
25 | + data={'stats': '0', 'charms': addresses}) |
26 | + |
27 | + def get_charms_info(self, addresses): |
28 | + request = self.get_charms_info_request(addresses) |
29 | + response = self.session.send(request.prepare()) |
30 | + return response.json() |
31 | |
32 | def get_download_counts(self, charm, start=None, end=None): |
33 | """Retrieve download counts for each day in the specified range. |
34 | @@ -106,3 +108,24 @@ |
35 | start = end - timedelta(days=days) |
36 | counts = self.get_download_counts(charm, start, end) |
37 | return sum(downloads for date, downloads in counts) |
38 | + |
39 | + |
40 | +def get_store_charm_info(store, charms): |
41 | + addresses = [] |
42 | + charm_addresses = [] |
43 | + for charm in charms: |
44 | + possible_addresses = {'long': get_address(charm, False)} |
45 | + if charm['promulgated']: |
46 | + possible_addresses['short'] = get_address(charm, True) |
47 | + charm_addresses.append(possible_addresses) |
48 | + addresses.extend(possible_addresses.values()) |
49 | + info = store.get_charms_info(addresses) |
50 | + result = [] |
51 | + for possible_addresses in charm_addresses: |
52 | + if ('short' in possible_addresses and |
53 | + 'errors' not in info[possible_addresses['short']]): |
54 | + address = possible_addresses['short'] |
55 | + else: |
56 | + address = possible_addresses['long'] |
57 | + result.append((address, info[address])) |
58 | + return result |
59 | |
60 | === modified file 'charmworld/jobs/ingest.py' |
61 | --- charmworld/jobs/ingest.py 2013-07-19 20:14:26 +0000 |
62 | +++ charmworld/jobs/ingest.py 2013-07-22 20:55:30 +0000 |
63 | @@ -225,15 +225,17 @@ |
64 | return True |
65 | |
66 | |
67 | -def update_charm(charm_data, db): |
68 | +def update_charm(charm_data, db, store): |
69 | charm_data["_id"] = charm_data["branch_spec"] |
70 | # Drop existing error data so that charms can lose their error status. |
71 | charm_data.pop('error', None) |
72 | + if 'errors' in charm_data.get('store_data', {}): |
73 | + return |
74 | log = logging.getLogger("charm.update_charm") |
75 | fs = getfs(db) |
76 | try: |
77 | - update_from_store(charm_data, CharmStore(), log) |
78 | do_bzr_update(charm_data, db, fs, log) |
79 | + update_download_count(store, charm_data) |
80 | update_proof_data(charm_data, log) |
81 | update_jenkins_data(db, charm_data, log) |
82 | update_from_revisions(charm_data) |
83 | @@ -257,7 +259,7 @@ |
84 | if charm_data is None: |
85 | charm_data = {} |
86 | charm_data.update(payload) |
87 | - update_charm(charm_data, self.db) |
88 | + update_charm(charm_data, self.db, CharmStore()) |
89 | index_client = ElasticSearchClient.from_settings(settings) |
90 | self.log.info('Saving %s' % charm_data['branch_spec']) |
91 | CharmSource(self.db, index_client).save(charm_data) |
92 | @@ -689,24 +691,13 @@ |
93 | charm['date_created'] = timestamp(date_created.replace(microsecond=0)) |
94 | |
95 | |
96 | -def update_from_store(charm, store, log): |
97 | - old_address = None |
98 | - for address in addresses(charm): |
99 | - if old_address is not None: |
100 | - log.info("rechecking %s with %s", old_address, address) |
101 | - old_address = address |
102 | - data = store.get_charm_info(address) |
103 | - if 'errors' not in data and 'warnings' not in data: |
104 | - break |
105 | - |
106 | +def update_from_store(charm, address, data, check_time, log): |
107 | if 'errors' in data or 'warnings' in data: |
108 | log.warning("store error on %s %s" % (address, data)) |
109 | - |
110 | - data["store_checked"] = datetime.now().ctime() |
111 | - |
112 | + data["store_checked"] = check_time |
113 | + charm['address'] = address |
114 | charm['store_data'] = data |
115 | charm['store_url'] = make_store_url(data['revision'], address) |
116 | - update_download_count(store, charm) |
117 | |
118 | |
119 | # XXX j.c.sackett Jan 31 2013 Bug:1111708 scan_repo is swapped for |
120 | |
121 | === modified file 'charmworld/jobs/lp.py' |
122 | --- charmworld/jobs/lp.py 2013-07-22 17:57:14 +0000 |
123 | +++ charmworld/jobs/lp.py 2013-07-22 20:55:30 +0000 |
124 | @@ -3,9 +3,17 @@ |
125 | # the file LICENSE). |
126 | |
127 | import argparse |
128 | +from datetime import ( |
129 | + datetime, |
130 | +) |
131 | import logging |
132 | import pymongo |
133 | |
134 | +from charmworld.charmstore import ( |
135 | + CharmStore, |
136 | + get_store_charm_info, |
137 | +) |
138 | +from charmworld.jobs.ingest import update_from_store |
139 | from charmworld.lp import ( |
140 | BASKET_SERIES, |
141 | get_branch_tips, |
142 | @@ -52,7 +60,7 @@ |
143 | baskets.append(data) |
144 | continue |
145 | |
146 | - if data["bname"] not in ("trunk", "trunk-1"): |
147 | + if data["bname"] != "trunk": |
148 | log.debug("Skipped branch %s", repo) |
149 | continue |
150 | |
151 | @@ -103,12 +111,17 @@ |
152 | break |
153 | |
154 | |
155 | -def queue_from_branches(db, charm_queue, basket_queue, limit=None, |
156 | - import_filter=default): |
157 | +def queue_from_branches(db, store, charm_queue, basket_queue, limit=None, |
158 | + import_filter=default, check_time=None): |
159 | log = logging.getLogger("charm.launchpad") |
160 | charm_data, baskets = all_branch_data() |
161 | - for charm in all_charms(db, charm_data, limit=limit, |
162 | - import_filter=import_filter): |
163 | + charms = list(all_charms(db, charm_data, limit=limit, |
164 | + import_filter=import_filter)) |
165 | + charms_info = get_store_charm_info(store, charms) |
166 | + if check_time is None: |
167 | + check_time = datetime.now().ctime() |
168 | + for charm, (address, store_data) in zip(charms, charms_info): |
169 | + update_from_store(charm, address, store_data, check_time, log) |
170 | added = charm_queue.put(charm) |
171 | if added and 0: |
172 | log.info("Queued %s", charm) |
173 | @@ -167,7 +180,7 @@ |
174 | db, log): |
175 | charm_queue = get_queue(db, CHARM_QUEUE) |
176 | basket_queue = get_queue(db, BASKET_QUEUE) |
177 | - queue_from_branches(db, charm_queue, basket_queue, |
178 | + queue_from_branches(db, CharmStore(), charm_queue, basket_queue, |
179 | charm_import_limit, args.prefix) |
180 | except LockHeld, error: |
181 | log.warn(str(error)) |
182 | |
183 | === modified file 'charmworld/jobs/tests/test_ingest.py' |
184 | --- charmworld/jobs/tests/test_ingest.py 2013-07-19 15:05:00 +0000 |
185 | +++ charmworld/jobs/tests/test_ingest.py 2013-07-22 20:55:30 +0000 |
186 | @@ -22,7 +22,10 @@ |
187 | from mock import patch |
188 | from pyelasticsearch.exceptions import ElasticHttpNotFoundError |
189 | |
190 | -from charmworld.charmstore import get_address |
191 | +from charmworld.charmstore import ( |
192 | + CharmStore, |
193 | + get_address, |
194 | +) |
195 | from charmworld.jobs.config import CHARM_DIR |
196 | from charmworld.jobs.ingest import ( |
197 | IngestError, |
198 | @@ -415,7 +418,7 @@ |
199 | def test_update_charm_does_not_index(self): |
200 | charm_data = factory.get_charm_json() |
201 | with charm_update_environment(charm_data, self.use_index_client()): |
202 | - update_charm(charm_data, self.db) |
203 | + update_charm(charm_data, self.db, CharmStore()) |
204 | self.index_client.wait_for_startup() |
205 | with self.assertRaises(ElasticHttpNotFoundError): |
206 | self.index_client.get(charm_data['_id']) |
207 | @@ -423,16 +426,33 @@ |
208 | def test_update_charm_does_not_update_mongo(self): |
209 | charm_data = factory.get_charm_json() |
210 | with charm_update_environment(charm_data, self.use_index_client()): |
211 | - update_charm(charm_data, self.db) |
212 | + update_charm(charm_data, self.db, CharmStore()) |
213 | self.assertIs(None, self.db.charms.find_one(charm_data['_id'])) |
214 | |
215 | def test_forgets_past_error(self): |
216 | charm_data = factory.get_charm_json(promulgated=True) |
217 | charm_data['error'] = {'error_stage': 'foo', 'error': 'bar'} |
218 | with charm_update_environment(charm_data, self.use_index_client()): |
219 | - update_charm(charm_data, self.db) |
220 | + update_charm(charm_data, self.db, CharmStore()) |
221 | self.assertNotIn('error', charm_data) |
222 | |
223 | + def test_updates_download_info(self): |
224 | + charm_data = factory.get_charm_json() |
225 | + del charm_data['downloads'] |
226 | + |
227 | + class FakeCharmStore: |
228 | + @staticmethod |
229 | + def count_downloads_in_days(charm, days, end): |
230 | + return 0 |
231 | + |
232 | + @staticmethod |
233 | + def get_download_counts(charm, start=None, end=None): |
234 | + return [[5]] |
235 | + |
236 | + with charm_update_environment(charm_data, self.use_index_client()): |
237 | + update_charm(charm_data, self.db, FakeCharmStore) |
238 | + self.assertEqual(5, charm_data['downloads']) |
239 | + |
240 | |
241 | class TestJob(IngestJob): |
242 | name = 'test' |
243 | |
244 | === modified file 'charmworld/jobs/tests/test_lp.py' |
245 | --- charmworld/jobs/tests/test_lp.py 2013-07-22 17:57:14 +0000 |
246 | +++ charmworld/jobs/tests/test_lp.py 2013-07-22 20:55:30 +0000 |
247 | @@ -26,10 +26,13 @@ |
248 | queue_from_branches, |
249 | ) |
250 | from charmworld.jobs.utils import get_queue |
251 | +from charmworld.tests.test_charmstore import FakeCharmStore |
252 | |
253 | |
254 | def all_branch_data_mock(charm_data=None, limit=None, import_filter=None): |
255 | - return [{'branch_dir': 'foo', 'branch_spec': 'bar'}], [{'foo': 'bar'}] |
256 | + return ([{'branch_dir': 'foo', 'branch_spec': 'bar', 'owner': 'baz', |
257 | + 'series': 'qux', 'name': 'quxx', 'promulgated': False}], |
258 | + [{'foo': 'bar'}]) |
259 | |
260 | |
261 | def failing_requests_get(*args, **kwargs): |
262 | @@ -127,8 +130,8 @@ |
263 | [charm['branch_spec'] for charm in charms]) |
264 | |
265 | def test_all_branch_data_not_trunk(self): |
266 | - # If the branch is not trunk or trunk-1, the charm is not returned by |
267 | - # available charms. |
268 | + # If the branch is not trunk, the charm is not returned by |
269 | + # all_branch_data. |
270 | handler = self.get_handler("charm.launchpad") |
271 | charm_data = [[u'~charmers/charms/precise/someproject/foo', |
272 | u'ja@appflower.com-20120329093714-s2m9e28dwotmijqc', |
273 | @@ -261,10 +264,26 @@ |
274 | charm_queue = get_queue(self.db, 'test_charm_queue') |
275 | basket_queue = get_queue(self.db, 'test_basket_queue') |
276 | self.addCleanup(basket_queue.clear) |
277 | - queue_from_branches(self.db, charm_queue, basket_queue) |
278 | + queue_from_branches(self.db, FakeCharmStore(), charm_queue, |
279 | + basket_queue, check_time='lambada') |
280 | item = charm_queue.next() |
281 | + expected = { |
282 | + 'address': 'cs:~baz/qux/quxx', |
283 | + 'branch_dir': 'foo', |
284 | + 'branch_spec': 'bar', |
285 | + 'name': 'quxx', |
286 | + 'owner': 'baz', |
287 | + 'promulgated': False, |
288 | + 'series': 'qux', |
289 | + 'store_data': { |
290 | + 'errors': ['entry not found'], |
291 | + 'revision': 0, |
292 | + 'store_checked': 'lambada' |
293 | + }, |
294 | + 'store_url': 'cs:~baz/qux/quxx-0', |
295 | + } |
296 | self.assertEqual( |
297 | - {'branch_dir': 'foo', 'branch_spec': 'bar'}, item.payload) |
298 | + expected, item.payload) |
299 | item = basket_queue.next() |
300 | self.assertEqual({'foo': 'bar'}, item.payload) |
301 | |
302 | @@ -293,6 +312,6 @@ |
303 | out_queue = get_queue(self.db, 'test_queue') |
304 | factory.makeCharm(self.db) |
305 | self.assertRaises( |
306 | - ConnectionError, queue_from_branches, self.db, |
307 | + ConnectionError, queue_from_branches, self.db, FakeCharmStore(), |
308 | out_queue, out_queue) |
309 | self.assertEqual(0, out_queue.size()) |
310 | |
311 | === modified file 'charmworld/jobs/tests/test_store.py' |
312 | --- charmworld/jobs/tests/test_store.py 2013-06-24 16:32:22 +0000 |
313 | +++ charmworld/jobs/tests/test_store.py 2013-07-22 20:55:30 +0000 |
314 | @@ -2,12 +2,12 @@ |
315 | # GNU Affero General Public License version 3 (see the file LICENSE). |
316 | |
317 | from logging import getLogger |
318 | -from mock import patch |
319 | from requests import Response |
320 | |
321 | from charmworld.charmstore import CharmStore |
322 | from charmworld.jobs.ingest import ( |
323 | addresses, |
324 | + get_address, |
325 | update_from_store, |
326 | ) |
327 | from charmworld.testing import factory |
328 | @@ -45,37 +45,23 @@ |
329 | store = CharmStore() |
330 | self.falsify_session(store) |
331 | ignore, charm = factory.makeCharm(self.db, promulgated=True) |
332 | - with patch.object(store, 'get_charm_info', |
333 | - lambda x: self._mock_data(error=True)): |
334 | - update_from_store(charm, store, self.log) |
335 | - |
336 | - first_address = "cs:%s/%s" % (charm["series"], charm["name"]) |
337 | - second_address = "cs:~%s/%s/%s" % ( |
338 | - charm["owner"], charm["series"], charm["name"]) |
339 | + address = get_address(charm, True) |
340 | + update_from_store(charm, address, self._mock_data(error=True), 'time', |
341 | + self.log) |
342 | log_messages = [record.getMessage() for record in handler.buffer] |
343 | - err_msg = "rechecking %s with %s" % (first_address, second_address) |
344 | - self.assertIn(err_msg, log_messages) |
345 | err_msg = ("store error on %s {'errors': 'Error forced by mock.'," |
346 | - " 'revision': 1}" % second_address) |
347 | + " 'revision': 1}" % address) |
348 | self.assertIn(err_msg, log_messages) |
349 | |
350 | def test_error_handling_warning(self): |
351 | - handler = self.get_handler('charm.store') |
352 | - store = CharmStore() |
353 | - self.falsify_session(store) |
354 | + handler = self.get_handler(self.log.name) |
355 | ignore, charm = factory.makeCharm(self.db, promulgated=True) |
356 | - with patch.object(store, 'get_charm_info', |
357 | - lambda x: self._mock_data(warning=True)): |
358 | - update_from_store(charm, store, self.log) |
359 | - |
360 | - first_address = "cs:%s/%s" % (charm["series"], charm["name"]) |
361 | - second_address = "cs:~%s/%s/%s" % ( |
362 | - charm["owner"], charm["series"], charm["name"]) |
363 | + address = get_address(charm, True) |
364 | + update_from_store(charm, address, self._mock_data(warning=True), |
365 | + 'time', self.log) |
366 | log_messages = [record.getMessage() for record in handler.buffer] |
367 | - err_msg = "rechecking %s with %s" % (first_address, second_address) |
368 | - self.assertIn(err_msg, log_messages) |
369 | err_msg = ("store error on %s {'warnings': 'Warning forced by mock.'," |
370 | - " 'revision': 1}" % second_address) |
371 | + " 'revision': 1}" % address) |
372 | self.assertIn(err_msg, log_messages) |
373 | |
374 | def test_run(self): |
375 | @@ -83,16 +69,14 @@ |
376 | self.falsify_session(store, '[["2010-12-24", 4], ["2010-12-25", 1]]') |
377 | ignore, charm = factory.makeCharm(self.db, promulgated=True) |
378 | del charm['store_url'] |
379 | - with patch.object(store, 'get_charm_info', |
380 | - lambda x: self._mock_data()): |
381 | - update_from_store(charm, store, self.log) |
382 | + update_from_store(charm, get_address(charm, True), self._mock_data(), |
383 | + 'foo', self.log) |
384 | data = charm['store_data'] |
385 | self.assertIn('store_checked', data.keys()) |
386 | self.assertEqual(1, data['revision']) |
387 | self.assertEqual( |
388 | "cs:%s/%s-%d" % (charm["series"], charm["name"], 1), |
389 | charm['store_url']) |
390 | - self.assertEqual(5, charm['downloads_in_past_30_days']) |
391 | |
392 | def test_addresses(self): |
393 | # The helper function addresses() returns the cs:~owner variant |
394 | |
395 | === modified file 'charmworld/models.py' |
396 | --- charmworld/models.py 2013-07-22 17:57:14 +0000 |
397 | +++ charmworld/models.py 2013-07-22 20:55:30 +0000 |
398 | @@ -334,7 +334,7 @@ |
399 | def bname(self): |
400 | """The charm's branch name. |
401 | |
402 | - Only branches named trunk or trunk-1 are ingested from Launchpad. |
403 | + Only branches named trunk are ingested from Launchpad. |
404 | """ |
405 | return self._representation['bname'] |
406 | |
407 | |
408 | === modified file 'charmworld/tests/test_charmstore.py' |
409 | --- charmworld/tests/test_charmstore.py 2013-07-01 14:45:02 +0000 |
410 | +++ charmworld/tests/test_charmstore.py 2013-07-22 20:55:30 +0000 |
411 | @@ -8,17 +8,37 @@ |
412 | date, |
413 | datetime, |
414 | ) |
415 | +import json |
416 | import urlparse |
417 | |
418 | from requests import Response |
419 | |
420 | -from charmworld.charmstore import CharmStore |
421 | +from charmworld.charmstore import ( |
422 | + CharmStore, |
423 | + get_address, |
424 | + get_store_charm_info, |
425 | +) |
426 | from charmworld.testing import ( |
427 | factory, |
428 | TestCase, |
429 | ) |
430 | |
431 | |
432 | +class FakeSessionBase: |
433 | + |
434 | + @classmethod |
435 | + def send(cls, request): |
436 | + parts = urlparse.urlsplit(request.url) |
437 | + query = urlparse.parse_qs(parts.query) |
438 | + if request.body is not None: |
439 | + data = urlparse.parse_qs(request.body) |
440 | + else: |
441 | + data = None |
442 | + response = Response() |
443 | + response._content = cls.parsed_send(request, parts, query, data) |
444 | + return response |
445 | + |
446 | + |
447 | class TestCharmStore(TestCase): |
448 | |
449 | def assertStoreURL(self, expected_path, url): |
450 | @@ -69,22 +89,18 @@ |
451 | end = date(2012, 12, 31) |
452 | cs = CharmStore() |
453 | |
454 | - class FakeSession: |
455 | + class FakeSession(FakeSessionBase): |
456 | |
457 | @staticmethod |
458 | - def send(request): |
459 | - parts = urlparse.urlsplit(request.url) |
460 | + def parsed_send(request, parts, query, data): |
461 | self.assertEqual('/stats/counter/charm-bundle:foo:bar:baz', |
462 | parts.path) |
463 | - query = urlparse.parse_qs(parts.query) |
464 | self.assertEqual({ |
465 | 'start': ['2011-01-02'], |
466 | 'end': ['2012-12-31'], |
467 | 'by': ['day'], |
468 | 'format': ['json']}, query) |
469 | - response = Response() |
470 | - response._content = '[[0]]' |
471 | - return response |
472 | + return '[[0]]' |
473 | |
474 | cs.session = FakeSession() |
475 | result = cs.get_download_counts(charm, start, end) |
476 | @@ -123,3 +139,88 @@ |
477 | cs.session = FakeSession() |
478 | |
479 | self.assertEqual(5, cs.count_downloads_in_days(charm, 30, today)) |
480 | + |
481 | + def test_get_charms_info_request(self): |
482 | + req = CharmStore.get_charms_info_request( |
483 | + ['cs:asdf/qsf', 'cs:~foo/bar/baz']) |
484 | + self.assertEqual('POST', req.method) |
485 | + self.assertEqual(CharmStore.STORE_URL + '/charm-info', req.url) |
486 | + self.assertEqual('0', req.data['stats']) |
487 | + self.assertEqual(['cs:asdf/qsf', 'cs:~foo/bar/baz'], |
488 | + req.data['charms']) |
489 | + |
490 | + def test_get_charms_info(self): |
491 | + class FakeSession(FakeSessionBase): |
492 | + @staticmethod |
493 | + def parsed_send(req, parts, query, data): |
494 | + self.assertEqual( |
495 | + data['charms'], ['cs:asdf/qsf', 'cs:~foo/bar/baz']) |
496 | + return json.dumps({}) |
497 | + store = CharmStore() |
498 | + store.session = FakeSession |
499 | + info = store.get_charms_info(['cs:asdf/qsf', 'cs:~foo/bar/baz']) |
500 | + self.assertEqual({}, info) |
501 | + |
502 | + |
503 | +MISSING_CHARM = {'revision': 0, 'errors': ['entry not found']} |
504 | + |
505 | + |
506 | +class FakeCharmStore: |
507 | + |
508 | + def __init__(self, store_data=None): |
509 | + if store_data is None: |
510 | + store_data = {} |
511 | + self.store_data = store_data |
512 | + |
513 | + def get_charms_info(self, addresses): |
514 | + return dict((a, self.store_data.get(a, MISSING_CHARM)) |
515 | + for a in addresses) |
516 | + |
517 | + |
518 | +class TestGetStoreCharmInfo(TestCase): |
519 | + |
520 | + def test_get_store_charm_info(self): |
521 | + payload1 = factory.get_payload_json() |
522 | + addr1 = get_address(payload1, False) |
523 | + payload2 = factory.get_payload_json() |
524 | + addr2 = get_address(payload2, False) |
525 | + payload3 = factory.get_payload_json() |
526 | + addr3 = get_address(payload3, False) |
527 | + store = FakeCharmStore({addr1: {'1'}, addr2: {'2'}}) |
528 | + result = get_store_charm_info(store, [payload1, payload2, payload3]) |
529 | + self.assertEqual( |
530 | + [(addr1, {'1'}), (addr2, {'2'}), (addr3, MISSING_CHARM)], result) |
531 | + result = get_store_charm_info(store, [payload3, payload1, payload2]) |
532 | + self.assertEqual( |
533 | + [(addr3, MISSING_CHARM), (addr1, {'1'}), (addr2, {'2'})], result) |
534 | + |
535 | + def test_short_address(self): |
536 | + payload = factory.get_payload_json(promulgated=True) |
537 | + short_addr = get_address(payload, True) |
538 | + store = FakeCharmStore({short_addr: {'foo': 'bar'}}) |
539 | + result = get_store_charm_info(store, [payload]) |
540 | + self.assertEqual([(short_addr, {'foo': 'bar'})], result) |
541 | + |
542 | + def test_long_address_if_not_promulgated(self): |
543 | + payload = factory.get_payload_json(promulgated=False) |
544 | + short_addr = get_address(payload, True) |
545 | + long_addr = get_address(payload, False) |
546 | + store = FakeCharmStore( |
547 | + {short_addr: {'foo': 'bar'}, long_addr: {'foo': 'bar'}}) |
548 | + result = get_store_charm_info(store, [payload]) |
549 | + self.assertEqual([(long_addr, {'foo': 'bar'})], result) |
550 | + |
551 | + def test_long_address_if_short_not_in_store(self): |
552 | + payload = factory.get_payload_json(promulgated=True) |
553 | + long_addr = get_address(payload, False) |
554 | + store = FakeCharmStore( |
555 | + {long_addr: {'foo': 'bar'}}) |
556 | + result = get_store_charm_info(store, [payload]) |
557 | + self.assertEqual([(long_addr, {'foo': 'bar'})], result) |
558 | + |
559 | + def test_long_address_if_neither_in_store(self): |
560 | + payload = factory.get_payload_json(promulgated=True) |
561 | + long_addr = get_address(payload, False) |
562 | + store = FakeCharmStore({}) |
563 | + result = get_store_charm_info(store, [payload]) |
564 | + self.assertEqual([(long_addr, MISSING_CHARM)], result) |
565 | |
566 | === removed file 'migrations/versions/001_load_qa_questions.py' |
567 | --- migrations/versions/001_load_qa_questions.py 2013-06-04 15:36:45 +0000 |
568 | +++ migrations/versions/001_load_qa_questions.py 1970-01-01 00:00:00 +0000 |
569 | @@ -1,137 +0,0 @@ |
570 | -# Copyright 2012, 2013 Canonical Ltd. This software is licensed under the |
571 | -# GNU Affero General Public License version 3 (see the file LICENSE). |
572 | - |
573 | -# -*- coding: utf-8 -*- |
574 | -""" |
575 | -Load qa questions into the collection for populating the scoring parts of |
576 | -the quality assessment form. |
577 | - |
578 | -""" |
579 | - |
580 | - |
581 | -def upgrade(db, index_client): |
582 | - """Complete this function with work to be done for the migration/update. |
583 | - |
584 | - db is the pymongo db instance for our datastore. Charms are in db.charms |
585 | - for instance. |
586 | - """ |
587 | - |
588 | - initial = [ |
589 | - ( |
590 | - u'reliable', |
591 | - u'Reliable', |
592 | - [ |
593 | - (u'AWS', 1, u''), |
594 | - (u'HP Cloud', 1, u''), |
595 | - (u'OpenStack', 1, u''), |
596 | - (u'LXC', 1, u''), |
597 | - (u'MAAS', 1, u''), |
598 | - (u'Check for integrity from upstream source', 1, u''), |
599 | - (u'Fail gracefully if upstream source goes missing', 1, u''), |
600 | - (u'Contain a suite of tests with the charm that pass', 1, u''), |
601 | - (u'Passes tests from Jenkins on jujucharms.com', 1, u''), |
602 | - ] |
603 | - ), |
604 | - |
605 | - ( |
606 | - u'secure', |
607 | - u'Secure', |
608 | - [ |
609 | - (u'Contain a well tested AppArmor profile', 1, ''), |
610 | - (u'Conform to security policies of the charm store', 1, 'Tight access control'), |
611 | - (u"Doesn't run as root", 1, ''), |
612 | - (u'Per instance or service access control', 1, ''), |
613 | - ] |
614 | - ), |
615 | - |
616 | - ( |
617 | - u'flexible', |
618 | - u'Flexible', |
619 | - [ |
620 | - ( |
621 | - u'Contain opinionated tuning options', 1, |
622 | - u""" |
623 | - Examples (depends on the service): "safe", "default", "fast", |
624 | - "real fast, not so safe". Don't expose every configuration, |
625 | - "pick that reflect real world usage. Make it so I don't have to read the book. |
626 | - """ |
627 | - ), |
628 | - (u'Use existing interfaces with other charms', 1, u'Highly relatable'), |
629 | - ] |
630 | - ), |
631 | - |
632 | - ( |
633 | - u'data_handling', |
634 | - u'Data Handling', |
635 | - [ |
636 | - (u'Integrate data storage best practices', 1, u'Backups based on service usage'), |
637 | - (u"Handle the service's user data", 1, u'Version control'), |
638 | - (u"Handle the service's user data", 1, u'Automated snapshots and backup.'), |
639 | - ] |
640 | - ), |
641 | - |
642 | - ( |
643 | - u'scalable', |
644 | - u'Scaleable', |
645 | - [ |
646 | - (u"Responds to add-unit based on the service's needs", 1, |
647 | - u'Configuration should not require additional steps to scale horizontally'), |
648 | - (u'Be tested with a real workload, not just a synthetic benchmark', 1, ''), |
649 | - (u'From upstream and existing devops practices for that service', 1, ''), |
650 | - (u'Community peer reviewed', 1, ''), |
651 | - (u'Have a configure option for most performant configuration if not the default', 1, ''), |
652 | - ] |
653 | - ), |
654 | - |
655 | - ( |
656 | - u'easy_deploy', |
657 | - u'Easy to Deploy', |
658 | - [ |
659 | - (u'README with examples of use for a typical workload', 1, ''), |
660 | - (u'README with examples of use for workloads at scale', 1, ''), |
661 | - (u'README with examples of use recommend best-practice relationships', 1, ''), |
662 | - (u'Allow installation from pure upstream source', 1, ''), |
663 | - (u'Allow installation from your local source', 1, ''), |
664 | - (u'Allow installation from PPA (if available)', 1, ''), |
665 | - (u'Allow installation from the Ubuntu repository', 1, ''), |
666 | - ] |
667 | - ), |
668 | - |
669 | - ( |
670 | - u'responsive', |
671 | - u'Responsive to DevOps Needs', |
672 | - [ |
673 | - (u'Allow for easy upgrade via juju upgrade-charm', 1, ''), |
674 | - (u'Allow upgrading the service itself.', 1, ''), |
675 | - (u'Responsive to user bug reports and concerns', 1, ''), |
676 | - (u'Maintainable, easy to read and modify', 1, ''), |
677 | - ] |
678 | - ), |
679 | - |
680 | - ( |
681 | - u'upstream', |
682 | - u'Upstream Friendly', |
683 | - [ |
684 | - (u'Follow upstream best practices', 1, |
685 | - u'Provide an option for a barebones "pure upstream" configuration'), |
686 | - (u'Should go lock-step with deployment recommendations', 1, |
687 | - u'Provide tip-of-trunk testing if feasible'), |
688 | - (u'Fresh charm on release day!', 1, ''), |
689 | - (u"Endeavour to be upstream's recommended way to deploy that service in the cloud (website mention or something)", 1, ''), |
690 | - ] |
691 | - ), |
692 | - ] |
693 | - |
694 | - """Add the sample data into the db.""" |
695 | - for cat in initial: |
696 | - category_dict = { |
697 | - 'name': cat[0], |
698 | - 'description': cat[1], |
699 | - 'questions': [{ |
700 | - 'id': '{0}_{1}'.format(cat[0].lower(), i), |
701 | - 'description': q[0], |
702 | - 'points': q[1], |
703 | - 'extended_description': q[2] |
704 | - } for i, q in enumerate(cat[2])] |
705 | - } |
706 | - db.qa.insert(category_dict) |
707 | |
708 | === removed file 'migrations/versions/002_Escape_dots_dollar_signs_percent_signs_in_mongo_keys.py' |
709 | --- migrations/versions/002_Escape_dots_dollar_signs_percent_signs_in_mongo_keys.py 2013-06-07 19:55:11 +0000 |
710 | +++ migrations/versions/002_Escape_dots_dollar_signs_percent_signs_in_mongo_keys.py 1970-01-01 00:00:00 +0000 |
711 | @@ -1,7 +0,0 @@ |
712 | -# Copyright 2012, 2013 Canonical Ltd. This software is licensed under the |
713 | -# GNU Affero General Public License version 3 (see the file LICENSE). |
714 | - |
715 | -# Escape dots, dollar signs, percent signs in mongo keys |
716 | - |
717 | -def upgrade(db, index_client): |
718 | - """OBSOLETE; all instances are now migrated.""" |
719 | |
720 | === removed file 'migrations/versions/003_remove_provider_qa_questions.py' |
721 | --- migrations/versions/003_remove_provider_qa_questions.py 2013-06-04 15:36:45 +0000 |
722 | +++ migrations/versions/003_remove_provider_qa_questions.py 1970-01-01 00:00:00 +0000 |
723 | @@ -1,42 +0,0 @@ |
724 | -# Copyright 2013 Canonical Ltd. This software is licensed under the |
725 | -# GNU Affero General Public License version 3 (see the file LICENSE). |
726 | -""" |
727 | -Update the qa questions collection used by the qa form. |
728 | - |
729 | -Removes the providers questions from the reliable section because that |
730 | -information is automated. |
731 | -""" |
732 | - |
733 | -new_category_data = [ |
734 | - ( |
735 | - u'reliable', |
736 | - u'Reliable', |
737 | - [ |
738 | - (u'Check for integrity from upstream source', 1, u''), |
739 | - (u'Fail gracefully if upstream source goes missing', 1, u''), |
740 | - (u'Contain a suite of tests with the charm that pass', 1, u''), |
741 | - (u'Passes tests from Jenkins on jujucharms.com', 1, u''), |
742 | - ] |
743 | - ), |
744 | -] |
745 | - |
746 | - |
747 | -def upgrade(db, index_client): |
748 | - """Update each category of questions from new_category_data. |
749 | - |
750 | - This update elaborates on the 001_* migration. Subsequent qa form |
751 | - mirgrations can replace new_category_data and run this same method. |
752 | - """ |
753 | - for cat in new_category_data: |
754 | - name = cat[0] |
755 | - category_dict = { |
756 | - 'name': name, |
757 | - 'description': cat[1], |
758 | - 'questions': [{ |
759 | - 'id': '{0}_{1}'.format(cat[0].lower(), i), |
760 | - 'description': q[0], |
761 | - 'points': q[1], |
762 | - 'extended_description': q[2] |
763 | - } for i, q in enumerate(cat[2])] |
764 | - } |
765 | - db.qa.update({'name': name}, category_dict) |
766 | |
767 | === removed file 'migrations/versions/004_remove_charm_errors.py' |
768 | --- migrations/versions/004_remove_charm_errors.py 2013-06-04 15:36:45 +0000 |
769 | +++ migrations/versions/004_remove_charm_errors.py 1970-01-01 00:00:00 +0000 |
770 | @@ -1,4 +0,0 @@ |
771 | -# Copyright 2013 Canonical Ltd. This software is licensed under the |
772 | -# GNU Affero General Public License version 3 (see the file LICENSE). |
773 | -def upgrade(db, index_client): |
774 | - db.drop_collection('charm-errors') |
775 | |
776 | === removed file 'migrations/versions/008_delete_icon_field.py' |
777 | --- migrations/versions/008_delete_icon_field.py 2013-06-11 19:13:07 +0000 |
778 | +++ migrations/versions/008_delete_icon_field.py 1970-01-01 00:00:00 +0000 |
779 | @@ -1,8 +0,0 @@ |
780 | -# Copyright 2013 Canonical Ltd. This software is licensed under the |
781 | -# GNU Affero General Public License version 3 (see the file LICENSE). |
782 | -from charmworld.search import reindex |
783 | - |
784 | - |
785 | -def upgrade(db, index_client): |
786 | - db.charms.update({}, {'$unset': {'icon': ''}}, multi=True) |
787 | - reindex(index_client, list(db.charms.find({}))) |
788 | |
789 | === removed file 'migrations/versions/009_add_downloads_attribute.py' |
790 | --- migrations/versions/009_add_downloads_attribute.py 2013-07-01 17:58:12 +0000 |
791 | +++ migrations/versions/009_add_downloads_attribute.py 1970-01-01 00:00:00 +0000 |
792 | @@ -1,21 +0,0 @@ |
793 | -# Copyright 2013 Canonical Ltd. This software is licensed under the |
794 | -# GNU Affero General Public License version 3 (see the file LICENSE). |
795 | -from charmworld.search import reindex |
796 | - |
797 | - |
798 | -def upgrade(db, index_client): |
799 | - """Add the downloads attribute to each charm. |
800 | - |
801 | - Default to using their 30day acount and it'll be updated to be more |
802 | - accurate on the next injest run. |
803 | - |
804 | - """ |
805 | - charms = db.charms.find({}) |
806 | - for charm in charms: |
807 | - if 'downloads_in_past_30_days' in charm: |
808 | - charm['downloads'] = charm['downloads_in_past_30_days'] |
809 | - else: |
810 | - charm['downloads'] = 0 |
811 | - db.charms.save(charm) |
812 | - |
813 | - reindex(index_client, list(db.charms.find({}))) |
814 | |
815 | === modified file 'migrations/versions/tests/test_migrations.py' |
816 | --- migrations/versions/tests/test_migrations.py 2013-07-15 21:31:05 +0000 |
817 | +++ migrations/versions/tests/test_migrations.py 2013-07-22 20:55:30 +0000 |
818 | @@ -26,31 +26,6 @@ |
819 | 'comfigure_logging', true_configure_logging) |
820 | |
821 | |
822 | -class TestMigration004(MigrationTestBase): |
823 | - |
824 | - def test_migration(self): |
825 | - self.use_index_client() |
826 | - self.db.create_collection('charm-errors') |
827 | - self.versions.run_migration(self.db, self.index_client, |
828 | - '004_remove_charm_errors.py') |
829 | - self.assertNotIn('charm-errors', self.db.collection_names()) |
830 | - |
831 | - |
832 | -class TestMigration008(MigrationTestBase): |
833 | - |
834 | - def test_migration(self): |
835 | - self.use_index_client() |
836 | - source = CharmSource.from_request(self) |
837 | - source.save({'_id': 'a', 'icon': 'asdf', 'asdf': 'asdf'}) |
838 | - source.save({'_id': 'b', 'icon': 'asdf', 'asdf': 'asdf'}) |
839 | - self.versions.run_migration(self.db, self.index_client, |
840 | - '008_delete_icon_field.py') |
841 | - for charm in source._get_all('a'): |
842 | - self.assertNotIn('icon', charm) |
843 | - for charm in source._get_all('b'): |
844 | - self.assertNotIn('icon', charm) |
845 | - |
846 | - |
847 | class TestMigration010(MigrationTestBase): |
848 | |
849 | def test_migration(self): |
Thank you. This can land once you reconcile the argument changes show in the conflicts.