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
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):

Subscribers

People subscribed via source and target branches