Merge lp:~abentley/charmworld/store-data-on-enqueue into lp:~juju-jitsu/charmworld/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
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.

Revision history for this message
Curtis Hovey (sinzui) wrote :

Thank you. This can land once you reconcile the argument changes show in the conflicts.

review: Approve (code)
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
=== modified file 'charmworld/charmstore.py'
--- charmworld/charmstore.py 2013-07-01 14:45:02 +0000
+++ charmworld/charmstore.py 2013-07-22 20:55:30 +0000
@@ -7,7 +7,6 @@
7from datetime import timedelta7from datetime import timedelta
88
99
10import requests
11from requests import (10from requests import (
12 Request,11 Request,
13 Session,12 Session,
@@ -71,12 +70,15 @@
71 params=params70 params=params
72 )71 )
7372
74 def get_charm_info(self, address):73 @classmethod
75 url = self.STORE_URL + "/charm-info?charms=%s&stats=0" % address74 def get_charms_info_request(cls, addresses):
76 data = requests.get(url).json()75 return Request('POST', cls.STORE_URL + '/charm-info',
77 data = data[address]76 data={'stats': '0', 'charms': addresses})
78 data['address'] = address77
79 return data78 def get_charms_info(self, addresses):
79 request = self.get_charms_info_request(addresses)
80 response = self.session.send(request.prepare())
81 return response.json()
8082
81 def get_download_counts(self, charm, start=None, end=None):83 def get_download_counts(self, charm, start=None, end=None):
82 """Retrieve download counts for each day in the specified range.84 """Retrieve download counts for each day in the specified range.
@@ -106,3 +108,24 @@
106 start = end - timedelta(days=days)108 start = end - timedelta(days=days)
107 counts = self.get_download_counts(charm, start, end)109 counts = self.get_download_counts(charm, start, end)
108 return sum(downloads for date, downloads in counts)110 return sum(downloads for date, downloads in counts)
111
112
113def get_store_charm_info(store, charms):
114 addresses = []
115 charm_addresses = []
116 for charm in charms:
117 possible_addresses = {'long': get_address(charm, False)}
118 if charm['promulgated']:
119 possible_addresses['short'] = get_address(charm, True)
120 charm_addresses.append(possible_addresses)
121 addresses.extend(possible_addresses.values())
122 info = store.get_charms_info(addresses)
123 result = []
124 for possible_addresses in charm_addresses:
125 if ('short' in possible_addresses and
126 'errors' not in info[possible_addresses['short']]):
127 address = possible_addresses['short']
128 else:
129 address = possible_addresses['long']
130 result.append((address, info[address]))
131 return result
109132
=== modified file 'charmworld/jobs/ingest.py'
--- charmworld/jobs/ingest.py 2013-07-19 20:14:26 +0000
+++ charmworld/jobs/ingest.py 2013-07-22 20:55:30 +0000
@@ -225,15 +225,17 @@
225 return True225 return True
226226
227227
228def update_charm(charm_data, db):228def update_charm(charm_data, db, store):
229 charm_data["_id"] = charm_data["branch_spec"]229 charm_data["_id"] = charm_data["branch_spec"]
230 # Drop existing error data so that charms can lose their error status.230 # Drop existing error data so that charms can lose their error status.
231 charm_data.pop('error', None)231 charm_data.pop('error', None)
232 if 'errors' in charm_data.get('store_data', {}):
233 return
232 log = logging.getLogger("charm.update_charm")234 log = logging.getLogger("charm.update_charm")
233 fs = getfs(db)235 fs = getfs(db)
234 try:236 try:
235 update_from_store(charm_data, CharmStore(), log)
236 do_bzr_update(charm_data, db, fs, log)237 do_bzr_update(charm_data, db, fs, log)
238 update_download_count(store, charm_data)
237 update_proof_data(charm_data, log)239 update_proof_data(charm_data, log)
238 update_jenkins_data(db, charm_data, log)240 update_jenkins_data(db, charm_data, log)
239 update_from_revisions(charm_data)241 update_from_revisions(charm_data)
@@ -257,7 +259,7 @@
257 if charm_data is None:259 if charm_data is None:
258 charm_data = {}260 charm_data = {}
259 charm_data.update(payload)261 charm_data.update(payload)
260 update_charm(charm_data, self.db)262 update_charm(charm_data, self.db, CharmStore())
261 index_client = ElasticSearchClient.from_settings(settings)263 index_client = ElasticSearchClient.from_settings(settings)
262 self.log.info('Saving %s' % charm_data['branch_spec'])264 self.log.info('Saving %s' % charm_data['branch_spec'])
263 CharmSource(self.db, index_client).save(charm_data)265 CharmSource(self.db, index_client).save(charm_data)
@@ -689,24 +691,13 @@
689 charm['date_created'] = timestamp(date_created.replace(microsecond=0))691 charm['date_created'] = timestamp(date_created.replace(microsecond=0))
690692
691693
692def update_from_store(charm, store, log):694def update_from_store(charm, address, data, check_time, log):
693 old_address = None
694 for address in addresses(charm):
695 if old_address is not None:
696 log.info("rechecking %s with %s", old_address, address)
697 old_address = address
698 data = store.get_charm_info(address)
699 if 'errors' not in data and 'warnings' not in data:
700 break
701
702 if 'errors' in data or 'warnings' in data:695 if 'errors' in data or 'warnings' in data:
703 log.warning("store error on %s %s" % (address, data))696 log.warning("store error on %s %s" % (address, data))
704697 data["store_checked"] = check_time
705 data["store_checked"] = datetime.now().ctime()698 charm['address'] = address
706
707 charm['store_data'] = data699 charm['store_data'] = data
708 charm['store_url'] = make_store_url(data['revision'], address)700 charm['store_url'] = make_store_url(data['revision'], address)
709 update_download_count(store, charm)
710701
711702
712# XXX j.c.sackett Jan 31 2013 Bug:1111708 scan_repo is swapped for703# XXX j.c.sackett Jan 31 2013 Bug:1111708 scan_repo is swapped for
713704
=== modified file 'charmworld/jobs/lp.py'
--- charmworld/jobs/lp.py 2013-07-22 17:57:14 +0000
+++ charmworld/jobs/lp.py 2013-07-22 20:55:30 +0000
@@ -3,9 +3,17 @@
3# the file LICENSE).3# the file LICENSE).
44
5import argparse5import argparse
6from datetime import (
7 datetime,
8)
6import logging9import logging
7import pymongo10import pymongo
811
12from charmworld.charmstore import (
13 CharmStore,
14 get_store_charm_info,
15)
16from charmworld.jobs.ingest import update_from_store
9from charmworld.lp import (17from charmworld.lp import (
10 BASKET_SERIES,18 BASKET_SERIES,
11 get_branch_tips,19 get_branch_tips,
@@ -52,7 +60,7 @@
52 baskets.append(data)60 baskets.append(data)
53 continue61 continue
5462
55 if data["bname"] not in ("trunk", "trunk-1"):63 if data["bname"] != "trunk":
56 log.debug("Skipped branch %s", repo)64 log.debug("Skipped branch %s", repo)
57 continue65 continue
5866
@@ -103,12 +111,17 @@
103 break111 break
104112
105113
106def queue_from_branches(db, charm_queue, basket_queue, limit=None,114def queue_from_branches(db, store, charm_queue, basket_queue, limit=None,
107 import_filter=default):115 import_filter=default, check_time=None):
108 log = logging.getLogger("charm.launchpad")116 log = logging.getLogger("charm.launchpad")
109 charm_data, baskets = all_branch_data()117 charm_data, baskets = all_branch_data()
110 for charm in all_charms(db, charm_data, limit=limit,118 charms = list(all_charms(db, charm_data, limit=limit,
111 import_filter=import_filter):119 import_filter=import_filter))
120 charms_info = get_store_charm_info(store, charms)
121 if check_time is None:
122 check_time = datetime.now().ctime()
123 for charm, (address, store_data) in zip(charms, charms_info):
124 update_from_store(charm, address, store_data, check_time, log)
112 added = charm_queue.put(charm)125 added = charm_queue.put(charm)
113 if added and 0:126 if added and 0:
114 log.info("Queued %s", charm)127 log.info("Queued %s", charm)
@@ -167,7 +180,7 @@
167 db, log):180 db, log):
168 charm_queue = get_queue(db, CHARM_QUEUE)181 charm_queue = get_queue(db, CHARM_QUEUE)
169 basket_queue = get_queue(db, BASKET_QUEUE)182 basket_queue = get_queue(db, BASKET_QUEUE)
170 queue_from_branches(db, charm_queue, basket_queue,183 queue_from_branches(db, CharmStore(), charm_queue, basket_queue,
171 charm_import_limit, args.prefix)184 charm_import_limit, args.prefix)
172 except LockHeld, error:185 except LockHeld, error:
173 log.warn(str(error))186 log.warn(str(error))
174187
=== modified file 'charmworld/jobs/tests/test_ingest.py'
--- charmworld/jobs/tests/test_ingest.py 2013-07-19 15:05:00 +0000
+++ charmworld/jobs/tests/test_ingest.py 2013-07-22 20:55:30 +0000
@@ -22,7 +22,10 @@
22from mock import patch22from mock import patch
23from pyelasticsearch.exceptions import ElasticHttpNotFoundError23from pyelasticsearch.exceptions import ElasticHttpNotFoundError
2424
25from charmworld.charmstore import get_address25from charmworld.charmstore import (
26 CharmStore,
27 get_address,
28)
26from charmworld.jobs.config import CHARM_DIR29from charmworld.jobs.config import CHARM_DIR
27from charmworld.jobs.ingest import (30from charmworld.jobs.ingest import (
28 IngestError,31 IngestError,
@@ -415,7 +418,7 @@
415 def test_update_charm_does_not_index(self):418 def test_update_charm_does_not_index(self):
416 charm_data = factory.get_charm_json()419 charm_data = factory.get_charm_json()
417 with charm_update_environment(charm_data, self.use_index_client()):420 with charm_update_environment(charm_data, self.use_index_client()):
418 update_charm(charm_data, self.db)421 update_charm(charm_data, self.db, CharmStore())
419 self.index_client.wait_for_startup()422 self.index_client.wait_for_startup()
420 with self.assertRaises(ElasticHttpNotFoundError):423 with self.assertRaises(ElasticHttpNotFoundError):
421 self.index_client.get(charm_data['_id'])424 self.index_client.get(charm_data['_id'])
@@ -423,16 +426,33 @@
423 def test_update_charm_does_not_update_mongo(self):426 def test_update_charm_does_not_update_mongo(self):
424 charm_data = factory.get_charm_json()427 charm_data = factory.get_charm_json()
425 with charm_update_environment(charm_data, self.use_index_client()):428 with charm_update_environment(charm_data, self.use_index_client()):
426 update_charm(charm_data, self.db)429 update_charm(charm_data, self.db, CharmStore())
427 self.assertIs(None, self.db.charms.find_one(charm_data['_id']))430 self.assertIs(None, self.db.charms.find_one(charm_data['_id']))
428431
429 def test_forgets_past_error(self):432 def test_forgets_past_error(self):
430 charm_data = factory.get_charm_json(promulgated=True)433 charm_data = factory.get_charm_json(promulgated=True)
431 charm_data['error'] = {'error_stage': 'foo', 'error': 'bar'}434 charm_data['error'] = {'error_stage': 'foo', 'error': 'bar'}
432 with charm_update_environment(charm_data, self.use_index_client()):435 with charm_update_environment(charm_data, self.use_index_client()):
433 update_charm(charm_data, self.db)436 update_charm(charm_data, self.db, CharmStore())
434 self.assertNotIn('error', charm_data)437 self.assertNotIn('error', charm_data)
435438
439 def test_updates_download_info(self):
440 charm_data = factory.get_charm_json()
441 del charm_data['downloads']
442
443 class FakeCharmStore:
444 @staticmethod
445 def count_downloads_in_days(charm, days, end):
446 return 0
447
448 @staticmethod
449 def get_download_counts(charm, start=None, end=None):
450 return [[5]]
451
452 with charm_update_environment(charm_data, self.use_index_client()):
453 update_charm(charm_data, self.db, FakeCharmStore)
454 self.assertEqual(5, charm_data['downloads'])
455
436456
437class TestJob(IngestJob):457class TestJob(IngestJob):
438 name = 'test'458 name = 'test'
439459
=== modified file 'charmworld/jobs/tests/test_lp.py'
--- charmworld/jobs/tests/test_lp.py 2013-07-22 17:57:14 +0000
+++ charmworld/jobs/tests/test_lp.py 2013-07-22 20:55:30 +0000
@@ -26,10 +26,13 @@
26 queue_from_branches,26 queue_from_branches,
27)27)
28from charmworld.jobs.utils import get_queue28from charmworld.jobs.utils import get_queue
29from charmworld.tests.test_charmstore import FakeCharmStore
2930
3031
31def all_branch_data_mock(charm_data=None, limit=None, import_filter=None):32def all_branch_data_mock(charm_data=None, limit=None, import_filter=None):
32 return [{'branch_dir': 'foo', 'branch_spec': 'bar'}], [{'foo': 'bar'}]33 return ([{'branch_dir': 'foo', 'branch_spec': 'bar', 'owner': 'baz',
34 'series': 'qux', 'name': 'quxx', 'promulgated': False}],
35 [{'foo': 'bar'}])
3336
3437
35def failing_requests_get(*args, **kwargs):38def failing_requests_get(*args, **kwargs):
@@ -127,8 +130,8 @@
127 [charm['branch_spec'] for charm in charms])130 [charm['branch_spec'] for charm in charms])
128131
129 def test_all_branch_data_not_trunk(self):132 def test_all_branch_data_not_trunk(self):
130 # If the branch is not trunk or trunk-1, the charm is not returned by133 # If the branch is not trunk, the charm is not returned by
131 # available charms.134 # all_branch_data.
132 handler = self.get_handler("charm.launchpad")135 handler = self.get_handler("charm.launchpad")
133 charm_data = [[u'~charmers/charms/precise/someproject/foo',136 charm_data = [[u'~charmers/charms/precise/someproject/foo',
134 u'ja@appflower.com-20120329093714-s2m9e28dwotmijqc',137 u'ja@appflower.com-20120329093714-s2m9e28dwotmijqc',
@@ -261,10 +264,26 @@
261 charm_queue = get_queue(self.db, 'test_charm_queue')264 charm_queue = get_queue(self.db, 'test_charm_queue')
262 basket_queue = get_queue(self.db, 'test_basket_queue')265 basket_queue = get_queue(self.db, 'test_basket_queue')
263 self.addCleanup(basket_queue.clear)266 self.addCleanup(basket_queue.clear)
264 queue_from_branches(self.db, charm_queue, basket_queue)267 queue_from_branches(self.db, FakeCharmStore(), charm_queue,
268 basket_queue, check_time='lambada')
265 item = charm_queue.next()269 item = charm_queue.next()
270 expected = {
271 'address': 'cs:~baz/qux/quxx',
272 'branch_dir': 'foo',
273 'branch_spec': 'bar',
274 'name': 'quxx',
275 'owner': 'baz',
276 'promulgated': False,
277 'series': 'qux',
278 'store_data': {
279 'errors': ['entry not found'],
280 'revision': 0,
281 'store_checked': 'lambada'
282 },
283 'store_url': 'cs:~baz/qux/quxx-0',
284 }
266 self.assertEqual(285 self.assertEqual(
267 {'branch_dir': 'foo', 'branch_spec': 'bar'}, item.payload)286 expected, item.payload)
268 item = basket_queue.next()287 item = basket_queue.next()
269 self.assertEqual({'foo': 'bar'}, item.payload)288 self.assertEqual({'foo': 'bar'}, item.payload)
270289
@@ -293,6 +312,6 @@
293 out_queue = get_queue(self.db, 'test_queue')312 out_queue = get_queue(self.db, 'test_queue')
294 factory.makeCharm(self.db)313 factory.makeCharm(self.db)
295 self.assertRaises(314 self.assertRaises(
296 ConnectionError, queue_from_branches, self.db,315 ConnectionError, queue_from_branches, self.db, FakeCharmStore(),
297 out_queue, out_queue)316 out_queue, out_queue)
298 self.assertEqual(0, out_queue.size())317 self.assertEqual(0, out_queue.size())
299318
=== modified file 'charmworld/jobs/tests/test_store.py'
--- charmworld/jobs/tests/test_store.py 2013-06-24 16:32:22 +0000
+++ charmworld/jobs/tests/test_store.py 2013-07-22 20:55:30 +0000
@@ -2,12 +2,12 @@
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 logging import getLogger4from logging import getLogger
5from mock import patch
6from requests import Response5from requests import Response
76
8from charmworld.charmstore import CharmStore7from charmworld.charmstore import CharmStore
9from charmworld.jobs.ingest import (8from charmworld.jobs.ingest import (
10 addresses,9 addresses,
10 get_address,
11 update_from_store,11 update_from_store,
12)12)
13from charmworld.testing import factory13from charmworld.testing import factory
@@ -45,37 +45,23 @@
45 store = CharmStore()45 store = CharmStore()
46 self.falsify_session(store)46 self.falsify_session(store)
47 ignore, charm = factory.makeCharm(self.db, promulgated=True)47 ignore, charm = factory.makeCharm(self.db, promulgated=True)
48 with patch.object(store, 'get_charm_info',48 address = get_address(charm, True)
49 lambda x: self._mock_data(error=True)):49 update_from_store(charm, address, self._mock_data(error=True), 'time',
50 update_from_store(charm, store, self.log)50 self.log)
51
52 first_address = "cs:%s/%s" % (charm["series"], charm["name"])
53 second_address = "cs:~%s/%s/%s" % (
54 charm["owner"], charm["series"], charm["name"])
55 log_messages = [record.getMessage() for record in handler.buffer]51 log_messages = [record.getMessage() for record in handler.buffer]
56 err_msg = "rechecking %s with %s" % (first_address, second_address)
57 self.assertIn(err_msg, log_messages)
58 err_msg = ("store error on %s {'errors': 'Error forced by mock.',"52 err_msg = ("store error on %s {'errors': 'Error forced by mock.',"
59 " 'revision': 1}" % second_address)53 " 'revision': 1}" % address)
60 self.assertIn(err_msg, log_messages)54 self.assertIn(err_msg, log_messages)
6155
62 def test_error_handling_warning(self):56 def test_error_handling_warning(self):
63 handler = self.get_handler('charm.store')57 handler = self.get_handler(self.log.name)
64 store = CharmStore()
65 self.falsify_session(store)
66 ignore, charm = factory.makeCharm(self.db, promulgated=True)58 ignore, charm = factory.makeCharm(self.db, promulgated=True)
67 with patch.object(store, 'get_charm_info',59 address = get_address(charm, True)
68 lambda x: self._mock_data(warning=True)):60 update_from_store(charm, address, self._mock_data(warning=True),
69 update_from_store(charm, store, self.log)61 'time', self.log)
70
71 first_address = "cs:%s/%s" % (charm["series"], charm["name"])
72 second_address = "cs:~%s/%s/%s" % (
73 charm["owner"], charm["series"], charm["name"])
74 log_messages = [record.getMessage() for record in handler.buffer]62 log_messages = [record.getMessage() for record in handler.buffer]
75 err_msg = "rechecking %s with %s" % (first_address, second_address)
76 self.assertIn(err_msg, log_messages)
77 err_msg = ("store error on %s {'warnings': 'Warning forced by mock.',"63 err_msg = ("store error on %s {'warnings': 'Warning forced by mock.',"
78 " 'revision': 1}" % second_address)64 " 'revision': 1}" % address)
79 self.assertIn(err_msg, log_messages)65 self.assertIn(err_msg, log_messages)
8066
81 def test_run(self):67 def test_run(self):
@@ -83,16 +69,14 @@
83 self.falsify_session(store, '[["2010-12-24", 4], ["2010-12-25", 1]]')69 self.falsify_session(store, '[["2010-12-24", 4], ["2010-12-25", 1]]')
84 ignore, charm = factory.makeCharm(self.db, promulgated=True)70 ignore, charm = factory.makeCharm(self.db, promulgated=True)
85 del charm['store_url']71 del charm['store_url']
86 with patch.object(store, 'get_charm_info',72 update_from_store(charm, get_address(charm, True), self._mock_data(),
87 lambda x: self._mock_data()):73 'foo', self.log)
88 update_from_store(charm, store, self.log)
89 data = charm['store_data']74 data = charm['store_data']
90 self.assertIn('store_checked', data.keys())75 self.assertIn('store_checked', data.keys())
91 self.assertEqual(1, data['revision'])76 self.assertEqual(1, data['revision'])
92 self.assertEqual(77 self.assertEqual(
93 "cs:%s/%s-%d" % (charm["series"], charm["name"], 1),78 "cs:%s/%s-%d" % (charm["series"], charm["name"], 1),
94 charm['store_url'])79 charm['store_url'])
95 self.assertEqual(5, charm['downloads_in_past_30_days'])
9680
97 def test_addresses(self):81 def test_addresses(self):
98 # The helper function addresses() returns the cs:~owner variant82 # The helper function addresses() returns the cs:~owner variant
9983
=== modified file 'charmworld/models.py'
--- charmworld/models.py 2013-07-22 17:57:14 +0000
+++ charmworld/models.py 2013-07-22 20:55:30 +0000
@@ -334,7 +334,7 @@
334 def bname(self):334 def bname(self):
335 """The charm's branch name.335 """The charm's branch name.
336336
337 Only branches named trunk or trunk-1 are ingested from Launchpad.337 Only branches named trunk are ingested from Launchpad.
338 """338 """
339 return self._representation['bname']339 return self._representation['bname']
340340
341341
=== modified file 'charmworld/tests/test_charmstore.py'
--- charmworld/tests/test_charmstore.py 2013-07-01 14:45:02 +0000
+++ charmworld/tests/test_charmstore.py 2013-07-22 20:55:30 +0000
@@ -8,17 +8,37 @@
8 date,8 date,
9 datetime,9 datetime,
10)10)
11import json
11import urlparse12import urlparse
1213
13from requests import Response14from requests import Response
1415
15from charmworld.charmstore import CharmStore16from charmworld.charmstore import (
17 CharmStore,
18 get_address,
19 get_store_charm_info,
20)
16from charmworld.testing import (21from charmworld.testing import (
17 factory,22 factory,
18 TestCase,23 TestCase,
19)24)
2025
2126
27class FakeSessionBase:
28
29 @classmethod
30 def send(cls, request):
31 parts = urlparse.urlsplit(request.url)
32 query = urlparse.parse_qs(parts.query)
33 if request.body is not None:
34 data = urlparse.parse_qs(request.body)
35 else:
36 data = None
37 response = Response()
38 response._content = cls.parsed_send(request, parts, query, data)
39 return response
40
41
22class TestCharmStore(TestCase):42class TestCharmStore(TestCase):
2343
24 def assertStoreURL(self, expected_path, url):44 def assertStoreURL(self, expected_path, url):
@@ -69,22 +89,18 @@
69 end = date(2012, 12, 31)89 end = date(2012, 12, 31)
70 cs = CharmStore()90 cs = CharmStore()
7191
72 class FakeSession:92 class FakeSession(FakeSessionBase):
7393
74 @staticmethod94 @staticmethod
75 def send(request):95 def parsed_send(request, parts, query, data):
76 parts = urlparse.urlsplit(request.url)
77 self.assertEqual('/stats/counter/charm-bundle:foo:bar:baz',96 self.assertEqual('/stats/counter/charm-bundle:foo:bar:baz',
78 parts.path)97 parts.path)
79 query = urlparse.parse_qs(parts.query)
80 self.assertEqual({98 self.assertEqual({
81 'start': ['2011-01-02'],99 'start': ['2011-01-02'],
82 'end': ['2012-12-31'],100 'end': ['2012-12-31'],
83 'by': ['day'],101 'by': ['day'],
84 'format': ['json']}, query)102 'format': ['json']}, query)
85 response = Response()103 return '[[0]]'
86 response._content = '[[0]]'
87 return response
88104
89 cs.session = FakeSession()105 cs.session = FakeSession()
90 result = cs.get_download_counts(charm, start, end)106 result = cs.get_download_counts(charm, start, end)
@@ -123,3 +139,88 @@
123 cs.session = FakeSession()139 cs.session = FakeSession()
124140
125 self.assertEqual(5, cs.count_downloads_in_days(charm, 30, today))141 self.assertEqual(5, cs.count_downloads_in_days(charm, 30, today))
142
143 def test_get_charms_info_request(self):
144 req = CharmStore.get_charms_info_request(
145 ['cs:asdf/qsf', 'cs:~foo/bar/baz'])
146 self.assertEqual('POST', req.method)
147 self.assertEqual(CharmStore.STORE_URL + '/charm-info', req.url)
148 self.assertEqual('0', req.data['stats'])
149 self.assertEqual(['cs:asdf/qsf', 'cs:~foo/bar/baz'],
150 req.data['charms'])
151
152 def test_get_charms_info(self):
153 class FakeSession(FakeSessionBase):
154 @staticmethod
155 def parsed_send(req, parts, query, data):
156 self.assertEqual(
157 data['charms'], ['cs:asdf/qsf', 'cs:~foo/bar/baz'])
158 return json.dumps({})
159 store = CharmStore()
160 store.session = FakeSession
161 info = store.get_charms_info(['cs:asdf/qsf', 'cs:~foo/bar/baz'])
162 self.assertEqual({}, info)
163
164
165MISSING_CHARM = {'revision': 0, 'errors': ['entry not found']}
166
167
168class FakeCharmStore:
169
170 def __init__(self, store_data=None):
171 if store_data is None:
172 store_data = {}
173 self.store_data = store_data
174
175 def get_charms_info(self, addresses):
176 return dict((a, self.store_data.get(a, MISSING_CHARM))
177 for a in addresses)
178
179
180class TestGetStoreCharmInfo(TestCase):
181
182 def test_get_store_charm_info(self):
183 payload1 = factory.get_payload_json()
184 addr1 = get_address(payload1, False)
185 payload2 = factory.get_payload_json()
186 addr2 = get_address(payload2, False)
187 payload3 = factory.get_payload_json()
188 addr3 = get_address(payload3, False)
189 store = FakeCharmStore({addr1: {'1'}, addr2: {'2'}})
190 result = get_store_charm_info(store, [payload1, payload2, payload3])
191 self.assertEqual(
192 [(addr1, {'1'}), (addr2, {'2'}), (addr3, MISSING_CHARM)], result)
193 result = get_store_charm_info(store, [payload3, payload1, payload2])
194 self.assertEqual(
195 [(addr3, MISSING_CHARM), (addr1, {'1'}), (addr2, {'2'})], result)
196
197 def test_short_address(self):
198 payload = factory.get_payload_json(promulgated=True)
199 short_addr = get_address(payload, True)
200 store = FakeCharmStore({short_addr: {'foo': 'bar'}})
201 result = get_store_charm_info(store, [payload])
202 self.assertEqual([(short_addr, {'foo': 'bar'})], result)
203
204 def test_long_address_if_not_promulgated(self):
205 payload = factory.get_payload_json(promulgated=False)
206 short_addr = get_address(payload, True)
207 long_addr = get_address(payload, False)
208 store = FakeCharmStore(
209 {short_addr: {'foo': 'bar'}, long_addr: {'foo': 'bar'}})
210 result = get_store_charm_info(store, [payload])
211 self.assertEqual([(long_addr, {'foo': 'bar'})], result)
212
213 def test_long_address_if_short_not_in_store(self):
214 payload = factory.get_payload_json(promulgated=True)
215 long_addr = get_address(payload, False)
216 store = FakeCharmStore(
217 {long_addr: {'foo': 'bar'}})
218 result = get_store_charm_info(store, [payload])
219 self.assertEqual([(long_addr, {'foo': 'bar'})], result)
220
221 def test_long_address_if_neither_in_store(self):
222 payload = factory.get_payload_json(promulgated=True)
223 long_addr = get_address(payload, False)
224 store = FakeCharmStore({})
225 result = get_store_charm_info(store, [payload])
226 self.assertEqual([(long_addr, MISSING_CHARM)], result)
126227
=== removed file 'migrations/versions/001_load_qa_questions.py'
--- migrations/versions/001_load_qa_questions.py 2013-06-04 15:36:45 +0000
+++ migrations/versions/001_load_qa_questions.py 1970-01-01 00:00:00 +0000
@@ -1,137 +0,0 @@
1# Copyright 2012, 2013 Canonical Ltd. This software is licensed under the
2# GNU Affero General Public License version 3 (see the file LICENSE).
3
4# -*- coding: utf-8 -*-
5"""
6Load qa questions into the collection for populating the scoring parts of
7the quality assessment form.
8
9"""
10
11
12def upgrade(db, index_client):
13 """Complete this function with work to be done for the migration/update.
14
15 db is the pymongo db instance for our datastore. Charms are in db.charms
16 for instance.
17 """
18
19 initial = [
20 (
21 u'reliable',
22 u'Reliable',
23 [
24 (u'AWS', 1, u''),
25 (u'HP Cloud', 1, u''),
26 (u'OpenStack', 1, u''),
27 (u'LXC', 1, u''),
28 (u'MAAS', 1, u''),
29 (u'Check for integrity from upstream source', 1, u''),
30 (u'Fail gracefully if upstream source goes missing', 1, u''),
31 (u'Contain a suite of tests with the charm that pass', 1, u''),
32 (u'Passes tests from Jenkins on jujucharms.com', 1, u''),
33 ]
34 ),
35
36 (
37 u'secure',
38 u'Secure',
39 [
40 (u'Contain a well tested AppArmor profile', 1, ''),
41 (u'Conform to security policies of the charm store', 1, 'Tight access control'),
42 (u"Doesn't run as root", 1, ''),
43 (u'Per instance or service access control', 1, ''),
44 ]
45 ),
46
47 (
48 u'flexible',
49 u'Flexible',
50 [
51 (
52 u'Contain opinionated tuning options', 1,
53 u"""
54 Examples (depends on the service): "safe", "default", "fast",
55 "real fast, not so safe". Don't expose every configuration,
56 "pick that reflect real world usage. Make it so I don't have to read the book.
57 """
58 ),
59 (u'Use existing interfaces with other charms', 1, u'Highly relatable'),
60 ]
61 ),
62
63 (
64 u'data_handling',
65 u'Data Handling',
66 [
67 (u'Integrate data storage best practices', 1, u'Backups based on service usage'),
68 (u"Handle the service's user data", 1, u'Version control'),
69 (u"Handle the service's user data", 1, u'Automated snapshots and backup.'),
70 ]
71 ),
72
73 (
74 u'scalable',
75 u'Scaleable',
76 [
77 (u"Responds to add-unit based on the service's needs", 1,
78 u'Configuration should not require additional steps to scale horizontally'),
79 (u'Be tested with a real workload, not just a synthetic benchmark', 1, ''),
80 (u'From upstream and existing devops practices for that service', 1, ''),
81 (u'Community peer reviewed', 1, ''),
82 (u'Have a configure option for most performant configuration if not the default', 1, ''),
83 ]
84 ),
85
86 (
87 u'easy_deploy',
88 u'Easy to Deploy',
89 [
90 (u'README with examples of use for a typical workload', 1, ''),
91 (u'README with examples of use for workloads at scale', 1, ''),
92 (u'README with examples of use recommend best-practice relationships', 1, ''),
93 (u'Allow installation from pure upstream source', 1, ''),
94 (u'Allow installation from your local source', 1, ''),
95 (u'Allow installation from PPA (if available)', 1, ''),
96 (u'Allow installation from the Ubuntu repository', 1, ''),
97 ]
98 ),
99
100 (
101 u'responsive',
102 u'Responsive to DevOps Needs',
103 [
104 (u'Allow for easy upgrade via juju upgrade-charm', 1, ''),
105 (u'Allow upgrading the service itself.', 1, ''),
106 (u'Responsive to user bug reports and concerns', 1, ''),
107 (u'Maintainable, easy to read and modify', 1, ''),
108 ]
109 ),
110
111 (
112 u'upstream',
113 u'Upstream Friendly',
114 [
115 (u'Follow upstream best practices', 1,
116 u'Provide an option for a barebones "pure upstream" configuration'),
117 (u'Should go lock-step with deployment recommendations', 1,
118 u'Provide tip-of-trunk testing if feasible'),
119 (u'Fresh charm on release day!', 1, ''),
120 (u"Endeavour to be upstream's recommended way to deploy that service in the cloud (website mention or something)", 1, ''),
121 ]
122 ),
123 ]
124
125 """Add the sample data into the db."""
126 for cat in initial:
127 category_dict = {
128 'name': cat[0],
129 'description': cat[1],
130 'questions': [{
131 'id': '{0}_{1}'.format(cat[0].lower(), i),
132 'description': q[0],
133 'points': q[1],
134 'extended_description': q[2]
135 } for i, q in enumerate(cat[2])]
136 }
137 db.qa.insert(category_dict)
1380
=== removed file 'migrations/versions/002_Escape_dots_dollar_signs_percent_signs_in_mongo_keys.py'
--- migrations/versions/002_Escape_dots_dollar_signs_percent_signs_in_mongo_keys.py 2013-06-07 19:55:11 +0000
+++ migrations/versions/002_Escape_dots_dollar_signs_percent_signs_in_mongo_keys.py 1970-01-01 00:00:00 +0000
@@ -1,7 +0,0 @@
1# Copyright 2012, 2013 Canonical Ltd. This software is licensed under the
2# GNU Affero General Public License version 3 (see the file LICENSE).
3
4# Escape dots, dollar signs, percent signs in mongo keys
5
6def upgrade(db, index_client):
7 """OBSOLETE; all instances are now migrated."""
80
=== removed file 'migrations/versions/003_remove_provider_qa_questions.py'
--- migrations/versions/003_remove_provider_qa_questions.py 2013-06-04 15:36:45 +0000
+++ migrations/versions/003_remove_provider_qa_questions.py 1970-01-01 00:00:00 +0000
@@ -1,42 +0,0 @@
1# Copyright 2013 Canonical Ltd. This software is licensed under the
2# GNU Affero General Public License version 3 (see the file LICENSE).
3"""
4Update the qa questions collection used by the qa form.
5
6Removes the providers questions from the reliable section because that
7information is automated.
8"""
9
10new_category_data = [
11 (
12 u'reliable',
13 u'Reliable',
14 [
15 (u'Check for integrity from upstream source', 1, u''),
16 (u'Fail gracefully if upstream source goes missing', 1, u''),
17 (u'Contain a suite of tests with the charm that pass', 1, u''),
18 (u'Passes tests from Jenkins on jujucharms.com', 1, u''),
19 ]
20 ),
21]
22
23
24def upgrade(db, index_client):
25 """Update each category of questions from new_category_data.
26
27 This update elaborates on the 001_* migration. Subsequent qa form
28 mirgrations can replace new_category_data and run this same method.
29 """
30 for cat in new_category_data:
31 name = cat[0]
32 category_dict = {
33 'name': name,
34 'description': cat[1],
35 'questions': [{
36 'id': '{0}_{1}'.format(cat[0].lower(), i),
37 'description': q[0],
38 'points': q[1],
39 'extended_description': q[2]
40 } for i, q in enumerate(cat[2])]
41 }
42 db.qa.update({'name': name}, category_dict)
430
=== removed file 'migrations/versions/004_remove_charm_errors.py'
--- migrations/versions/004_remove_charm_errors.py 2013-06-04 15:36:45 +0000
+++ migrations/versions/004_remove_charm_errors.py 1970-01-01 00:00:00 +0000
@@ -1,4 +0,0 @@
1# Copyright 2013 Canonical Ltd. This software is licensed under the
2# GNU Affero General Public License version 3 (see the file LICENSE).
3def upgrade(db, index_client):
4 db.drop_collection('charm-errors')
50
=== removed file 'migrations/versions/008_delete_icon_field.py'
--- migrations/versions/008_delete_icon_field.py 2013-06-11 19:13:07 +0000
+++ migrations/versions/008_delete_icon_field.py 1970-01-01 00:00:00 +0000
@@ -1,8 +0,0 @@
1# Copyright 2013 Canonical Ltd. This software is licensed under the
2# GNU Affero General Public License version 3 (see the file LICENSE).
3from charmworld.search import reindex
4
5
6def upgrade(db, index_client):
7 db.charms.update({}, {'$unset': {'icon': ''}}, multi=True)
8 reindex(index_client, list(db.charms.find({})))
90
=== removed file 'migrations/versions/009_add_downloads_attribute.py'
--- migrations/versions/009_add_downloads_attribute.py 2013-07-01 17:58:12 +0000
+++ migrations/versions/009_add_downloads_attribute.py 1970-01-01 00:00:00 +0000
@@ -1,21 +0,0 @@
1# Copyright 2013 Canonical Ltd. This software is licensed under the
2# GNU Affero General Public License version 3 (see the file LICENSE).
3from charmworld.search import reindex
4
5
6def upgrade(db, index_client):
7 """Add the downloads attribute to each charm.
8
9 Default to using their 30day acount and it'll be updated to be more
10 accurate on the next injest run.
11
12 """
13 charms = db.charms.find({})
14 for charm in charms:
15 if 'downloads_in_past_30_days' in charm:
16 charm['downloads'] = charm['downloads_in_past_30_days']
17 else:
18 charm['downloads'] = 0
19 db.charms.save(charm)
20
21 reindex(index_client, list(db.charms.find({})))
220
=== modified file 'migrations/versions/tests/test_migrations.py'
--- migrations/versions/tests/test_migrations.py 2013-07-15 21:31:05 +0000
+++ migrations/versions/tests/test_migrations.py 2013-07-22 20:55:30 +0000
@@ -26,31 +26,6 @@
26 'comfigure_logging', true_configure_logging)26 'comfigure_logging', true_configure_logging)
2727
2828
29class TestMigration004(MigrationTestBase):
30
31 def test_migration(self):
32 self.use_index_client()
33 self.db.create_collection('charm-errors')
34 self.versions.run_migration(self.db, self.index_client,
35 '004_remove_charm_errors.py')
36 self.assertNotIn('charm-errors', self.db.collection_names())
37
38
39class TestMigration008(MigrationTestBase):
40
41 def test_migration(self):
42 self.use_index_client()
43 source = CharmSource.from_request(self)
44 source.save({'_id': 'a', 'icon': 'asdf', 'asdf': 'asdf'})
45 source.save({'_id': 'b', 'icon': 'asdf', 'asdf': 'asdf'})
46 self.versions.run_migration(self.db, self.index_client,
47 '008_delete_icon_field.py')
48 for charm in source._get_all('a'):
49 self.assertNotIn('icon', charm)
50 for charm in source._get_all('b'):
51 self.assertNotIn('icon', charm)
52
53
54class TestMigration010(MigrationTestBase):29class TestMigration010(MigrationTestBase):
5530
56 def test_migration(self):31 def test_migration(self):

Subscribers

People subscribed via source and target branches