Merge lp:~lifeless/python-oops-tools/amqp into lp:python-oops-tools
- amqp
- Merge into trunk
Proposed by
Robert Collins
Status: | Merged |
---|---|
Approved by: | Robert Collins |
Approved revision: | no longer in the source branch. |
Merged at revision: | 2 |
Proposed branch: | lp:~lifeless/python-oops-tools/amqp |
Merge into: | lp:python-oops-tools |
Diff against target: |
408 lines (+242/-29) 8 files modified
buildout.cfg (+1/-1) setup.py (+2/-0) src/oopstools/NEWS.txt (+7/-2) src/oopstools/README.txt (+15/-4) src/oopstools/oops/models.py (+37/-20) src/oopstools/oops/test/test_amqp2disk.py (+40/-0) src/oopstools/scripts/amqp2disk.py (+137/-0) versions.cfg (+3/-2) |
To merge this branch: | bzr merge lp:~lifeless/python-oops-tools/amqp |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Steve Kowalik (community) | code | Approve | |
Review via email: mp+79505@code.launchpad.net |
Commit message
Add AMQP support and improve docs.
Description of the change
Build on the recent improvements in oops-datedir-repo and oops-amqp to add an amqp queue worker that will take oopses from amqp and load them straight into OOPS-tools. Also fix docs (0.6 is the current release) and stop telling folk to edit product.cfg in-place.
To post a comment you must log in.
- 2. By Robert Collins
-
Add AMQP support and improve docs.
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'buildout.cfg' | |||
2 | --- buildout.cfg 2011-10-13 20:18:51 +0000 | |||
3 | +++ buildout.cfg 2011-10-16 23:09:17 +0000 | |||
4 | @@ -54,7 +54,7 @@ | |||
5 | 54 | recipe = djangorecipe | 54 | recipe = djangorecipe |
6 | 55 | version = 1.3 | 55 | version = 1.3 |
7 | 56 | project = oopstools | 56 | project = oopstools |
9 | 57 | projectegg = oops-tools | 57 | projectegg = oopstools |
10 | 58 | settings = settings | 58 | settings = settings |
11 | 59 | test = oopstools | 59 | test = oopstools |
12 | 60 | eggs = oops-tools | 60 | eggs = oops-tools |
13 | 61 | 61 | ||
14 | === modified file 'setup.py' | |||
15 | --- setup.py 2011-10-13 20:18:51 +0000 | |||
16 | +++ setup.py 2011-10-16 23:09:17 +0000 | |||
17 | @@ -57,6 +57,7 @@ | |||
18 | 57 | 'launchpadlib', | 57 | 'launchpadlib', |
19 | 58 | 'lazr.config', | 58 | 'lazr.config', |
20 | 59 | 'oops', | 59 | 'oops', |
21 | 60 | 'oops-amqp', | ||
22 | 60 | 'oops-datedir-repo', | 61 | 'oops-datedir-repo', |
23 | 61 | 'pytz', | 62 | 'pytz', |
24 | 62 | 'setuptools', | 63 | 'setuptools', |
25 | @@ -79,6 +80,7 @@ | |||
26 | 79 | ), | 80 | ), |
27 | 80 | entry_points=dict( | 81 | entry_points=dict( |
28 | 81 | console_scripts=[ # `console_scripts` is a magic name to setuptools | 82 | console_scripts=[ # `console_scripts` is a magic name to setuptools |
29 | 83 | 'amqp2disk = oopstools.scripts.amqp2disk:main', | ||
30 | 82 | 'analyse_error_reports = oopstools.scripts.analyse_error_reports:main', | 84 | 'analyse_error_reports = oopstools.scripts.analyse_error_reports:main', |
31 | 83 | 'load_sample_data = oopstools.scripts.load_sample_data:main', | 85 | 'load_sample_data = oopstools.scripts.load_sample_data:main', |
32 | 84 | 'update_db = oopstools.scripts.update_db:main', | 86 | 'update_db = oopstools.scripts.update_db:main', |
33 | 85 | 87 | ||
34 | === modified file 'src/oopstools/NEWS.txt' | |||
35 | --- src/oopstools/NEWS.txt 2011-10-13 20:18:51 +0000 | |||
36 | +++ src/oopstools/NEWS.txt 2011-10-16 23:09:17 +0000 | |||
37 | @@ -2,8 +2,13 @@ | |||
38 | 2 | NEWS for oopstools | 2 | NEWS for oopstools |
39 | 3 | =================== | 3 | =================== |
40 | 4 | 4 | ||
43 | 5 | 0.5 (UNRELEASED) | 5 | NEXT |
44 | 6 | ================ | 6 | ==== |
45 | 7 | |||
46 | 8 | * Added AMQP support via the bin/amqp2disk script. (Robert Collins) | ||
47 | 9 | |||
48 | 10 | 0.6 | ||
49 | 11 | === | ||
50 | 7 | 12 | ||
51 | 8 | * Initial release | 13 | * Initial release |
52 | 9 | 14 | ||
53 | 10 | 15 | ||
54 | === modified file 'src/oopstools/README.txt' | |||
55 | --- src/oopstools/README.txt 2011-10-13 20:18:51 +0000 | |||
56 | +++ src/oopstools/README.txt 2011-10-16 23:09:17 +0000 | |||
57 | @@ -51,19 +51,21 @@ | |||
58 | 51 | Deployment using mod_wsgi | 51 | Deployment using mod_wsgi |
59 | 52 | ========================= | 52 | ========================= |
60 | 53 | 53 | ||
63 | 54 | Update the production.cfg file with your database configuration and the paths | 54 | Create a custom cfg file - start with production.cfg and take a copy. Update |
64 | 55 | to your OOPS directories: | 55 | your copy with your database configuration and the paths to your OOPS |
65 | 56 | directories: | ||
66 | 56 | 57 | ||
67 | 57 | [configuration] | 58 | [configuration] |
68 | 58 | db-name = /path/to/your/oops.db | 59 | db-name = /path/to/your/oops.db |
69 | 59 | index-template = 'index.html' | 60 | index-template = 'index.html' |
71 | 60 | oopsdir = /path/to/oops/reports | 61 | oopsdir = /path/where/rsynced/oopses/are/found |
72 | 62 | /another/such/path | ||
73 | 61 | 63 | ||
74 | 62 | Update settings.py setting a custom SECRET_KEY | 64 | Update settings.py setting a custom SECRET_KEY |
75 | 63 | 65 | ||
76 | 64 | To deploy oops tools make sure all the dependecies are installed. | 66 | To deploy oops tools make sure all the dependecies are installed. |
77 | 65 | 67 | ||
79 | 66 | * bin/buildout -c production.cfg | 68 | * bin/buildout -c yourfilename.cfg |
80 | 67 | 69 | ||
81 | 68 | * Run bin/django syncdb | 70 | * Run bin/django syncdb |
82 | 69 | 71 | ||
83 | @@ -71,6 +73,15 @@ | |||
84 | 71 | 73 | ||
85 | 72 | * Copy apache/oops-tools.dev.mod_wsgi to /etc/apache2/sites-available/ | 74 | * Copy apache/oops-tools.dev.mod_wsgi to /etc/apache2/sites-available/ |
86 | 73 | 75 | ||
87 | 76 | AMQP Integration | ||
88 | 77 | ================ | ||
89 | 78 | |||
90 | 79 | The script bin/amqp2disk is an AMQP handler that will receive OOPS reports over | ||
91 | 80 | AMQP and publish them locally to disk as well as loading the metadata directly | ||
92 | 81 | into the oops-tools database. To use this you will need to config your OOPS | ||
93 | 82 | creation to publish over AMQP. If you are using Python then the oops-amqp | ||
94 | 83 | module will help you do this. | ||
95 | 84 | |||
96 | 74 | Running locally | 85 | Running locally |
97 | 75 | =============== | 86 | =============== |
98 | 76 | 87 | ||
99 | 77 | 88 | ||
100 | === modified file 'src/oopstools/oops/models.py' | |||
101 | --- src/oopstools/oops/models.py 2011-10-13 20:18:51 +0000 | |||
102 | +++ src/oopstools/oops/models.py 2011-10-16 23:09:17 +0000 | |||
103 | @@ -21,6 +21,8 @@ | |||
104 | 21 | import datetime | 21 | import datetime |
105 | 22 | import os.path | 22 | import os.path |
106 | 23 | 23 | ||
107 | 24 | from pytz import utc | ||
108 | 25 | |||
109 | 24 | from django.db import models | 26 | from django.db import models |
110 | 25 | from django.db.models.signals import pre_save | 27 | from django.db.models.signals import pre_save |
111 | 26 | import oops_datedir_repo.serializer | 28 | import oops_datedir_repo.serializer |
112 | @@ -278,7 +280,11 @@ | |||
113 | 278 | prefix = oops.get('reporter') | 280 | prefix = oops.get('reporter') |
114 | 279 | if not prefix: | 281 | if not prefix: |
115 | 280 | # Legacy support for pre-reporter using OOPSes. | 282 | # Legacy support for pre-reporter using OOPSes. |
117 | 281 | prefix = oops_re.match(oopsid).group('oopsprefix') | 283 | prefix_match = oops_re.match(oopsid) |
118 | 284 | if prefix_match is not None: | ||
119 | 285 | prefix = prefix_match.group('oopsprefix') | ||
120 | 286 | else: | ||
121 | 287 | prefix = 'UNKNOWN' | ||
122 | 282 | prefix = prefix.upper() | 288 | prefix = prefix.upper() |
123 | 283 | try: | 289 | try: |
124 | 284 | prefix = Prefix.objects.get(value__exact=prefix) | 290 | prefix = Prefix.objects.get(value__exact=prefix) |
125 | @@ -329,9 +335,10 @@ | |||
126 | 329 | if total_time < 0: | 335 | if total_time < 0: |
127 | 330 | total_time = 0 | 336 | total_time = 0 |
128 | 331 | # Get the oops infestation | 337 | # Get the oops infestation |
130 | 332 | exception_type = oops.get('type') | 338 | exception_type = oops.get('type') or '' |
131 | 339 | exception_value = oops.get('value') or '' | ||
132 | 333 | exception_value = _normalize_exception_value( | 340 | exception_value = _normalize_exception_value( |
134 | 334 | exception_type, oops.get('value'), prefix) | 341 | exception_type, exception_value, prefix) |
135 | 335 | try: | 342 | try: |
136 | 336 | infestation = Infestation.objects.get( | 343 | infestation = Infestation.objects.get( |
137 | 337 | exception_type__exact=exception_type, | 344 | exception_type__exact=exception_type, |
138 | @@ -361,10 +368,13 @@ | |||
139 | 361 | most_expensive_statement = conform(most_expensive_statement, 200) | 368 | most_expensive_statement = conform(most_expensive_statement, 200) |
140 | 362 | url = conform(oops.get('url') or '', MAX_URL_LEN) | 369 | url = conform(oops.get('url') or '', MAX_URL_LEN) |
141 | 363 | informational = oops.get('informational', 'False').lower() == 'true' | 370 | informational = oops.get('informational', 'False').lower() == 'true' |
142 | 371 | oops_date = oops.get('time') | ||
143 | 372 | if oops_date is None: | ||
144 | 373 | oops_date = datetime.datetime.now(utc) | ||
145 | 364 | data.update( | 374 | data.update( |
146 | 365 | oopsid = oopsid, | 375 | oopsid = oopsid, |
147 | 366 | prefix = prefix, | 376 | prefix = prefix, |
149 | 367 | date = oops.get('time').replace(microsecond=0), | 377 | date = oops_date.replace(microsecond=0), |
150 | 368 | # Missing pageids are urls because that suits our queries. | 378 | # Missing pageids are urls because that suits our queries. |
151 | 369 | pageid = oops.get('topic') or url, | 379 | pageid = oops.get('topic') or url, |
152 | 370 | url = url, | 380 | url = url, |
153 | @@ -377,7 +387,28 @@ | |||
154 | 377 | is_bot = _robot_pat.search(data['user_agent']) is not None, | 387 | is_bot = _robot_pat.search(data['user_agent']) is not None, |
155 | 378 | statements_count = len(statements), | 388 | statements_count = len(statements), |
156 | 379 | ) | 389 | ) |
158 | 380 | return data, req_vars, statements, oops.get('tb_text') | 390 | return data, req_vars, statements, oops.get('tb_text') or '' |
159 | 391 | |||
160 | 392 | |||
161 | 393 | def parsed_oops_to_model_oops(parsed_oops, pathname): | ||
162 | 394 | """Convert an oops report dict to an Oops object.""" | ||
163 | 395 | data, req_vars, statements, traceback = _get_oops_tuple(parsed_oops) | ||
164 | 396 | data['pathname'] = pathname | ||
165 | 397 | res = Oops(**data) | ||
166 | 398 | res.appinstance = res.get_appinstance() | ||
167 | 399 | res.save() | ||
168 | 400 | # Get it again. Otherwise we have discrepancies between old and | ||
169 | 401 | # new oops objects: old ones have unicode attributes, and new | ||
170 | 402 | # ones have string attributes, for instance. Ideally the message | ||
171 | 403 | # conversion would have converted everything to unicode, but it | ||
172 | 404 | # doesn't easily. | ||
173 | 405 | res = Oops.objects.get(oopsid__exact=parsed_oops['id']) | ||
174 | 406 | res.parsed_oops = parsed_oops | ||
175 | 407 | res.req_vars = req_vars | ||
176 | 408 | res.statements = statements | ||
177 | 409 | res.traceback = traceback | ||
178 | 410 | res.save() | ||
179 | 411 | return res | ||
180 | 381 | 412 | ||
181 | 382 | 413 | ||
182 | 383 | class Oops(models.Model): | 414 | class Oops(models.Model): |
183 | @@ -432,21 +463,7 @@ | |||
184 | 432 | try: | 463 | try: |
185 | 433 | res = cls.objects.get(oopsid__exact=oopsid) | 464 | res = cls.objects.get(oopsid__exact=oopsid) |
186 | 434 | except cls.DoesNotExist: | 465 | except cls.DoesNotExist: |
202 | 435 | data, req_vars, statements, traceback = _get_oops_tuple(parsed_oops) | 466 | res = parsed_oops_to_model_oops(parsed_oops, pathname) |
188 | 436 | data['pathname'] = pathname | ||
189 | 437 | res = cls(**data) | ||
190 | 438 | res.appinstance = res.get_appinstance() | ||
191 | 439 | res.save() | ||
192 | 440 | # Get it again. Otherwise we have discrepancies between old and | ||
193 | 441 | # new oops objects: old ones have unicode attributes, and new | ||
194 | 442 | # ones have string attributes, for instance. Ideally the message | ||
195 | 443 | # conversion would have converted everything to unicode, but it | ||
196 | 444 | # doesn't easily. | ||
197 | 445 | res = cls.objects.get(oopsid__exact=oopsid) | ||
198 | 446 | res.parsed_oops = parsed_oops | ||
199 | 447 | res.req_vars = req_vars | ||
200 | 448 | res.statements = statements | ||
201 | 449 | res.traceback = traceback | ||
203 | 450 | return res | 467 | return res |
204 | 451 | 468 | ||
205 | 452 | @readproperty | 469 | @readproperty |
206 | 453 | 470 | ||
207 | === added file 'src/oopstools/oops/test/test_amqp2disk.py' | |||
208 | --- src/oopstools/oops/test/test_amqp2disk.py 1970-01-01 00:00:00 +0000 | |||
209 | +++ src/oopstools/oops/test/test_amqp2disk.py 2011-10-16 23:09:17 +0000 | |||
210 | @@ -0,0 +1,40 @@ | |||
211 | 1 | # Copyright 2005-2011 Canonical Ltd. All rights reserved. | ||
212 | 2 | # | ||
213 | 3 | # This program is free software: you can redistribute it and/or modify | ||
214 | 4 | # it under the terms of the GNU Affero General Public License as published by | ||
215 | 5 | # the Free Software Foundation, either version 3 of the License, or | ||
216 | 6 | # (at your option) any later version. | ||
217 | 7 | # | ||
218 | 8 | # This program is distributed in the hope that it will be useful, | ||
219 | 9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
220 | 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
221 | 11 | # GNU Affero General Public License for more details. | ||
222 | 12 | # | ||
223 | 13 | # You should have received a copy of the GNU Affero General Public License | ||
224 | 14 | # along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
225 | 15 | |||
226 | 16 | |||
227 | 17 | import os.path | ||
228 | 18 | |||
229 | 19 | import bson | ||
230 | 20 | from fixtures import TempDir | ||
231 | 21 | from testtools import TestCase | ||
232 | 22 | |||
233 | 23 | from oopstools.oops.models import Oops | ||
234 | 24 | from oopstools.scripts import amqp2disk | ||
235 | 25 | |||
236 | 26 | |||
237 | 27 | class TestOOPSConfig(TestCase): | ||
238 | 28 | |||
239 | 29 | def test_publishes_disk_and_DB(self): | ||
240 | 30 | self.root_dir = self.useFixture(TempDir()).path | ||
241 | 31 | config = amqp2disk.make_amqp_config(self.root_dir) | ||
242 | 32 | orig_report = {'id': '12345'} | ||
243 | 33 | report = dict(orig_report) | ||
244 | 34 | ids = config.publish(report) | ||
245 | 35 | self.assertEqual(['12345', '12345'], ids) | ||
246 | 36 | with open(report['datedir_repo_filepath'], 'rb') as fp: | ||
247 | 37 | disk_report = bson.loads(fp.read()) | ||
248 | 38 | self.assertEqual(disk_report, orig_report) | ||
249 | 39 | model_report = Oops.objects.get(oopsid='12345') | ||
250 | 40 | self.assertNotEqual(None, model_report) | ||
251 | 0 | 41 | ||
252 | === added file 'src/oopstools/scripts/amqp2disk.py' | |||
253 | --- src/oopstools/scripts/amqp2disk.py 1970-01-01 00:00:00 +0000 | |||
254 | +++ src/oopstools/scripts/amqp2disk.py 2011-10-16 23:09:17 +0000 | |||
255 | @@ -0,0 +1,137 @@ | |||
256 | 1 | # Copyright 2011 Canonical Ltd. All rights reserved. | ||
257 | 2 | # | ||
258 | 3 | # This program is free software: you can redistribute it and/or modify | ||
259 | 4 | # it under the terms of the GNU Affero General Public License as published by | ||
260 | 5 | # the Free Software Foundation, either version 3 of the License, or | ||
261 | 6 | # (at your option) any later version. | ||
262 | 7 | # | ||
263 | 8 | # This program is distributed in the hope that it will be useful, | ||
264 | 9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
265 | 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
266 | 11 | # GNU Affero General Public License for more details. | ||
267 | 12 | # | ||
268 | 13 | # You should have received a copy of the GNU Affero General Public License | ||
269 | 14 | # along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
270 | 15 | |||
271 | 16 | # Receive OOPS reports from AMQP and publish them into the oops-tools | ||
272 | 17 | # repository. | ||
273 | 18 | |||
274 | 19 | __metaclass__ = type | ||
275 | 20 | |||
276 | 21 | import sys | ||
277 | 22 | import optparse | ||
278 | 23 | import StringIO | ||
279 | 24 | from textwrap import dedent | ||
280 | 25 | |||
281 | 26 | import amqplib.client_0_8 as amqp | ||
282 | 27 | from oops import Config | ||
283 | 28 | import oops_amqp | ||
284 | 29 | import oops_datedir_repo | ||
285 | 30 | |||
286 | 31 | from oopstools.oops.helpers import parsedate, load_prefixes | ||
287 | 32 | from oopstools.oops.models import ( | ||
288 | 33 | Oops, | ||
289 | 34 | parsed_oops_to_model_oops, | ||
290 | 35 | Prefix, | ||
291 | 36 | Report, | ||
292 | 37 | ) | ||
293 | 38 | from oopstools.oops.oopsstore import OopsStore | ||
294 | 39 | from oopstools.oops import dbsummaries | ||
295 | 40 | from oopstools.oops.summaries import ( | ||
296 | 41 | WebAppErrorSummary, | ||
297 | 42 | CheckwatchesErrorSummary, | ||
298 | 43 | CodeHostingWithRemoteSectionSummary, | ||
299 | 44 | GenericErrorSummary, | ||
300 | 45 | ) | ||
301 | 46 | |||
302 | 47 | |||
303 | 48 | def main(argv=None): | ||
304 | 49 | if argv is None: | ||
305 | 50 | argv=sys.argv | ||
306 | 51 | usage = dedent("""\ | ||
307 | 52 | %prog [options] | ||
308 | 53 | |||
309 | 54 | The following options must be supplied: | ||
310 | 55 | --output | ||
311 | 56 | --host | ||
312 | 57 | --username | ||
313 | 58 | --password | ||
314 | 59 | --vhost | ||
315 | 60 | --queue | ||
316 | 61 | |||
317 | 62 | e.g. | ||
318 | 63 | amqp2disk --output /srv/oops-tools/amqpoopses --host "localhost:3472" \\ | ||
319 | 64 | --username "guest" --password "guest" --vhost "/" --queue "oops" | ||
320 | 65 | |||
321 | 66 | The AMQP environment should be setup in advance with a persistent queue | ||
322 | 67 | bound to your exchange : using transient queues would allow OOPSes to | ||
323 | 68 | be lost if the amqp2disk process were to be shutdown for a non-trivial | ||
324 | 69 | duration. The --bind-to option will cause the queue to be created and | ||
325 | 70 | bound to the given exchange. This is only needed the first time as it | ||
326 | 71 | is created persistently. | ||
327 | 72 | """) | ||
328 | 73 | description = "Load OOPS reports into oops-tools from AMQP." | ||
329 | 74 | parser = optparse.OptionParser( | ||
330 | 75 | description=description, usage=usage) | ||
331 | 76 | parser.add_option('--output', help="Root directory to store OOPSes in.") | ||
332 | 77 | parser.add_option('--host', help="AQMP host / host:port.") | ||
333 | 78 | parser.add_option('--username', help="AQMP username.") | ||
334 | 79 | parser.add_option('--password', help="AQMP password.") | ||
335 | 80 | parser.add_option('--vhost', help="AMQP vhost.") | ||
336 | 81 | parser.add_option('--queue', help="AMQP queue name.") | ||
337 | 82 | parser.add_option( | ||
338 | 83 | '--bind-to', help="AMQP exchange to bind to (only needed once).") | ||
339 | 84 | options, args = parser.parse_args(argv[1:]) | ||
340 | 85 | def needed(optname): | ||
341 | 86 | if getattr(options, optname, None) is None: | ||
342 | 87 | raise ValueError('option "%s" must be supplied' % optname) | ||
343 | 88 | needed('host') | ||
344 | 89 | needed('output') | ||
345 | 90 | needed('username') | ||
346 | 91 | needed('password') | ||
347 | 92 | needed('vhost') | ||
348 | 93 | needed('queue') | ||
349 | 94 | connection = amqp.Connection(host=options.host, userid=options.username, | ||
350 | 95 | password=options.password, virtual_host=options.vhost) | ||
351 | 96 | channel = connection.channel() | ||
352 | 97 | if options.bind_to: | ||
353 | 98 | channel.queue_declare(options.queue, durable=True, auto_delete=False) | ||
354 | 99 | channel.queue_bind(options.queue, options.bind_to) | ||
355 | 100 | config = make_amqp_config(options.output) | ||
356 | 101 | receiver = oops_amqp.Receiver(config, channel, options.queue) | ||
357 | 102 | try: | ||
358 | 103 | receiver.run_forever() | ||
359 | 104 | except KeyboardInterrupt: | ||
360 | 105 | pass | ||
361 | 106 | |||
362 | 107 | |||
363 | 108 | def db_publisher(report): | ||
364 | 109 | """Publish OOPS reports to the oops-tools django store.""" | ||
365 | 110 | # the first publisher will either inherit or assign, so this should be | ||
366 | 111 | # impossible. | ||
367 | 112 | assert report['id'] is not None | ||
368 | 113 | # Some fallback methods could lead to duplicate paths into the DB: exit | ||
369 | 114 | # early if the OOPS is already loaded. | ||
370 | 115 | try: | ||
371 | 116 | res = Oops.objects.get(oopsid__exact=report['id']) | ||
372 | 117 | except Oops.DoesNotExist: | ||
373 | 118 | res = parsed_oops_to_model_oops( | ||
374 | 119 | report, report['datedir_repo_filepath']) | ||
375 | 120 | return res.oopsid | ||
376 | 121 | return None | ||
377 | 122 | |||
378 | 123 | |||
379 | 124 | def make_amqp_config(output_dir): | ||
380 | 125 | """Create an OOPS Config for republishing amqp OOPSes. | ||
381 | 126 | |||
382 | 127 | An OOPS published to this config will be written to disk and then loaded | ||
383 | 128 | into the database. | ||
384 | 129 | |||
385 | 130 | :param output_dir: The directory to write OOPSes too. | ||
386 | 131 | """ | ||
387 | 132 | config = Config() | ||
388 | 133 | disk_publisher = oops_datedir_repo.DateDirRepo( | ||
389 | 134 | output_dir, inherit_id=True, stash_path=True) | ||
390 | 135 | config.publishers.append(disk_publisher.publish) | ||
391 | 136 | config.publishers.append(db_publisher) | ||
392 | 137 | return config | ||
393 | 0 | 138 | ||
394 | === modified file 'versions.cfg' | |||
395 | --- versions.cfg 2011-10-13 20:18:51 +0000 | |||
396 | +++ versions.cfg 2011-10-16 23:09:17 +0000 | |||
397 | @@ -19,8 +19,9 @@ | |||
398 | 19 | launchpadlib = 1.6.0 | 19 | launchpadlib = 1.6.0 |
399 | 20 | lazr.config = 1.1.3 | 20 | lazr.config = 1.1.3 |
400 | 21 | mechanize = 0.1.11 | 21 | mechanize = 0.1.11 |
403 | 22 | oops = 0.0.7 | 22 | oops = 0.0.9 |
404 | 23 | oops-datedir-repo = 0.0.7 | 23 | oops-amqp = 0.0.1 |
405 | 24 | oops-datedir-repo = 0.0.9 | ||
406 | 24 | setuptools = 0.6c11 | 25 | setuptools = 0.6c11 |
407 | 25 | z3c.recipe.filetemplate = 2.0.3 | 26 | z3c.recipe.filetemplate = 2.0.3 |
408 | 26 | z3c.recipe.sphinxdoc = 0.0.8 | 27 | z3c.recipe.sphinxdoc = 0.0.8 |
This looks fine to me.