Merge lp:~james-w/python-oops-dictconfig/new-publisher-api into lp:python-oops-dictconfig

Proposed by James Westby
Status: Merged
Approved by: James Westby
Approved revision: 21
Merged at revision: 18
Proposed branch: lp:~james-w/python-oops-dictconfig/new-publisher-api
Merge into: lp:python-oops-dictconfig
Diff against target: 366 lines (+229/-29)
7 files modified
README (+48/-16)
oops_dictconfig/configglue_options.py (+24/-1)
oops_dictconfig/dictconfig.py (+27/-11)
oops_dictconfig/tests/__init__.py (+1/-0)
oops_dictconfig/tests/test_configglue_options.py (+114/-0)
oops_dictconfig/tests/test_dictconfig.py (+14/-0)
tarmac_tests.sh (+1/-1)
To merge this branch: bzr merge lp:~james-w/python-oops-dictconfig/new-publisher-api
Reviewer Review Type Date Requested Status
James Westby (community) Approve
Diogo Baeder (community) Approve
Review via email: mp+159677@code.launchpad.net

Commit message

Add a better way to specify fallback publishers now that oops allows it.

Description of the change

Hi,

Now that oops supports the better publish_with_fallback option, allow
that to be specified in the config, and deprecate the 'new_only' method
of doing fallback (as it is deprecated in oops itself).

Thanks,

James

To post a comment you must log in.
Revision history for this message
Diogo Baeder (diogobaeder) :
review: Approve
Revision history for this message
James Westby (james-w) :
review: Approve
Revision history for this message
Ubuntu One Auto Pilot (otto-pilot) wrote :
Download full text (12.2 KiB)

The attempt to merge lp:~james-w/python-oops-dictconfig/new-publisher-api into lp:python-oops-dictconfig failed. Below is the output from the failed tests.

Downloading/unpacking testtools
  Running setup.py egg_info for package testtools

    no previously-included directories found matching 'doc/_build'
Downloading/unpacking oops-amqp
  Downloading oops_amqp-0.0.7.tar.gz
  Running setup.py egg_info for package oops-amqp

Downloading/unpacking oops-datedir-repo
  Downloading oops_datedir_repo-0.0.21.tar.gz
  Running setup.py egg_info for package oops-datedir-repo

Downloading/unpacking extras (from testtools)
  Downloading extras-0.0.3.tar.gz
  Running setup.py egg_info for package extras

Downloading/unpacking python-mimeparse (from testtools)
  Downloading python-mimeparse-0.1.4.tar.gz
  Running setup.py egg_info for package python-mimeparse

Downloading/unpacking pymongo (from oops-amqp)
  Running setup.py egg_info for package pymongo

Requirement already satisfied (use --upgrade to upgrade): oops>=0.0.11 in ./virtualenv/lib/python2.7/site-packages/oops-0.0.13-py2.7.egg (from oops-amqp)
Downloading/unpacking amqplib (from oops-amqp)
  Running setup.py egg_info for package amqplib

Downloading/unpacking bson (from oops-datedir-repo)
  Downloading bson-0.3.3.tar.gz
  Running setup.py egg_info for package bson

Downloading/unpacking iso8601 (from oops-datedir-repo)
  Downloading iso8601-0.1.4.tar.gz
  Running setup.py egg_info for package iso8601

Downloading/unpacking launchpadlib (from oops-datedir-repo)
  Running setup.py egg_info for package launchpadlib

Requirement already satisfied (use --upgrade to upgrade): pytz in ./virtualenv/lib/python2.7/site-packages/pytz-2013b-py2.7.egg (from oops-datedir-repo)
Downloading/unpacking httplib2 (from launchpadlib->oops-datedir-repo)
  Running setup.py egg_info for package httplib2

Downloading/unpacking keyring (from launchpadlib->oops-datedir-repo)
  Running setup.py egg_info for package keyring
    zip_safe flag not set; analyzing archive contents...

    Installed /tmp/easy_install-bwAGVc/pytest-runner-1.2/hgtools-3.0.2-py2.7.egg

    Installed /mnt/tarmac/cache/python-oops-dictconfig/virtualenv/build/keyring/pytest_runner-1.2-py2.7.egg

    warning: no previously-included files found matching '.hg/last-message.txt'
Downloading/unpacking lazr.restfulclient>=0.9.19 (from launchpadlib->oops-datedir-repo)
  Running setup.py egg_info for package lazr.restfulclient

Downloading/unpacking lazr.uri (from launchpadlib->oops-datedir-repo)
  Downloading lazr.uri-1.0.3.tar.gz
  Running setup.py egg_info for package lazr.uri

Downloading/unpacking oauth (from launchpadlib->oops-datedir-repo)
  Downloading oauth-1.0.1.tar.gz
  Running setup.py egg_info for package oauth

Requirement already satisfied (use --upgrade to upgrade): distribute in ./virtualenv/lib/python2.7/site-packages/distribute-0.6.24-py2.7.egg (from launchpadlib->oops-datedir-repo)
Downloading/unpacking simplejson (from launchpadlib->oops-datedir-repo)
  Running setup.py egg_info for package simplejson

Downloading/unpacking testresources (from launchpadlib->oops-datedi...

Revision history for this message
Ubuntu One Auto Pilot (otto-pilot) wrote :
Download full text (5.3 KiB)

The attempt to merge lp:~james-w/python-oops-dictconfig/new-publisher-api into lp:python-oops-dictconfig failed. Below is the output from the failed tests.

Downloading/unpacking testtools
  Running setup.py egg_info for package testtools

    no previously-included directories found matching 'doc/_build'
Downloading/unpacking oops-amqp
  Downloading oops_amqp-0.0.7.tar.gz
  Running setup.py egg_info for package oops-amqp

Downloading/unpacking oops-datedir-repo
  Downloading oops_datedir_repo-0.0.21.tar.gz
  Running setup.py egg_info for package oops-datedir-repo

Downloading/unpacking configglue
  Downloading configglue-1.0.3.tar.gz
  Running setup.py egg_info for package configglue

Downloading/unpacking extras (from testtools)
  Downloading extras-0.0.3.tar.gz
  Running setup.py egg_info for package extras

Downloading/unpacking python-mimeparse (from testtools)
  Downloading python-mimeparse-0.1.4.tar.gz
  Running setup.py egg_info for package python-mimeparse

Downloading/unpacking pymongo (from oops-amqp)
  Running setup.py egg_info for package pymongo

Requirement already satisfied (use --upgrade to upgrade): oops>=0.0.11 in ./virtualenv/lib/python2.7/site-packages/oops-0.0.13-py2.7.egg (from oops-amqp)
Downloading/unpacking amqplib (from oops-amqp)
  Running setup.py egg_info for package amqplib

Downloading/unpacking bson (from oops-datedir-repo)
  Downloading bson-0.3.3.tar.gz
  Running setup.py egg_info for package bson

Downloading/unpacking iso8601 (from oops-datedir-repo)
  Downloading iso8601-0.1.4.tar.gz
  Running setup.py egg_info for package iso8601

Downloading/unpacking launchpadlib (from oops-datedir-repo)
  Running setup.py egg_info for package launchpadlib

Requirement already satisfied (use --upgrade to upgrade): pytz in ./virtualenv/lib/python2.7/site-packages/pytz-2013b-py2.7.egg (from oops-datedir-repo)
Downloading/unpacking pyxdg (from configglue)
  Running setup.py egg_info for package pyxdg

Downloading/unpacking httplib2 (from launchpadlib->oops-datedir-repo)
  Running setup.py egg_info for package httplib2

Downloading/unpacking keyring (from launchpadlib->oops-datedir-repo)
  Running setup.py egg_info for package keyring
    zip_safe flag not set; analyzing archive contents...

    Installed /tmp/easy_install-SF0734/pytest-runner-1.2/hgtools-3.0.2-py2.7.egg

    Installed /mnt/tarmac/cache/python-oops-dictconfig/virtualenv/build/keyring/pytest_runner-1.2-py2.7.egg

    warning: no previously-included files found matching '.hg/last-message.txt'
Downloading/unpacking lazr.restfulclient>=0.9.19 (from launchpadlib->oops-datedir-repo)
  Running setup.py egg_info for package lazr.restfulclient

Downloading/unpacking lazr.uri (from launchpadlib->oops-datedir-repo)
  Downloading lazr.uri-1.0.3.tar.gz
  Running setup.py egg_info for package lazr.uri

Downloading/unpacking oauth (from launchpadlib->oops-datedir-repo)
  Error <urlopen error timed out> while getting http://pypi.python.org/packages/source/o/oauth/oauth-1.0.1.tar.gz#md5=30ed3cc8c11d7841a89feab437aabf81 (from http://pypi.python.org/simple/oauth/)
Exception:
Traceback (most recent call l...

Read more...

Revision history for this message
Diogo Baeder (diogobaeder) wrote :

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'README'
2--- README 2013-02-06 17:00:45 +0000
3+++ README 2013-05-01 15:40:31 +0000
4@@ -41,7 +41,8 @@
5 * type: the type of publisher, currently `"datedir"` and `"amqp"` are
6 supported.
7
8-All publishers support the following optional key:
9+All publishers support the following (deprecated) optional key. Instead of
10+using this, you should see the section on fallback publishers:
11
12 * new_only: if this is `True` then the publisher will be wrapped in
13 `oops.publish_only_new`, meaning that the oops will only
14@@ -93,6 +94,32 @@
15 to a user is being published (but uniqueness cannot be
16 guaranteed).
17
18+Fallback publishers
19+-------------------
20+
21+Sometimes it is desirable to have a chain of publishers, where the next in
22+the chain is used if the current attempt reported failure. For example, you
23+can have an amqp publisher with a datedir fallback, so that if the amqp
24+broker is not reachable the oops are saved to a datedir, where they can
25+be forwarded later.
26+
27+To do this create a publisher definition with a 'fallback_chain' key,
28+whose value is a list of publishers to chain, e.g.
29+
30+ {
31+ 'fallback_chain': [
32+ {'type': 'amqp',
33+ ...
34+ },
35+ {'type': 'datedir',
36+ ...
37+ }
38+ ]
39+ }
40+
41+Note that these chains can't be nested, so 'fallback_chain' can only appear
42+at the top level of the publishers list.
43+
44 Examples
45 --------
46
47@@ -129,18 +156,21 @@
48 {
49 'publishers': [
50 {
51- 'type': 'amqp',
52- 'host': 'amqp.example.com:5302',
53- 'user': 'oopsuser',
54- 'password': 'oopspassword',
55- 'vhost': 'oopses',
56- 'exchange_name': 'oopses',
57- 'routing_key': 'oopses',
58- },
59- {
60- 'type': 'datedir',
61- 'error_dir': '/var/log/oopses/',
62- 'new_only': True,
63+ 'fallback_chain': [
64+ {
65+ 'type': 'amqp',
66+ 'host': 'amqp.example.com:5302',
67+ 'user': 'oopsuser',
68+ 'password': 'oopspassword',
69+ 'vhost': 'oopses',
70+ 'exchange_name': 'oopses',
71+ 'routing_key': 'oopses',
72+ },
73+ {
74+ 'type': 'datedir',
75+ 'error_dir': '/var/log/oopses/',
76+ },
77+ ],
78 },
79 ],
80 }
81@@ -183,8 +213,11 @@
82 oopses = oopses_config
83
84 [oopses_config]
85- publishers = amqp_publisher
86- datedir_publisher
87+ publishers = fallback_chain
88+
89+ [fallback_chain]
90+ fallback_chain = amqp_publisher
91+ datedir_publisher
92
93 [amqp_publisher]
94 type = amqp
95@@ -198,7 +231,6 @@
96 [datedir_publisher]
97 type = datedir
98 error_dir = /var/log/oopses
99- only_new = True
100
101
102 Running Tests
103
104=== modified file 'oops_dictconfig/configglue_options.py'
105--- oops_dictconfig/configglue_options.py 2013-02-26 16:15:03 +0000
106+++ oops_dictconfig/configglue_options.py 2013-05-01 15:40:31 +0000
107@@ -16,7 +16,7 @@
108 from configglue import schema
109
110
111-class PublisherDescriptionOption(schema.DictOption):
112+class BasePublisherDescriptionOption(schema.DictOption):
113
114 def __init__(self, **kwargs):
115 if 'spec' not in kwargs:
116@@ -55,7 +55,30 @@
117 )
118 if 'help' not in kwargs:
119 kwargs['help'] = "Config for an oops publisher."
120+ super(BasePublisherDescriptionOption, self).__init__(**kwargs)
121+
122+
123+class FallbackChainOption(schema.ListOption):
124+
125+ def __init__(self, **kwargs):
126+ if 'item' not in kwargs:
127+ kwargs['item'] = BasePublisherDescriptionOption()
128+ if 'help' not in kwargs:
129+ kwargs['help'] = ("List of oops publishers in order to publish to."
130+ " Each publisher will only be used if all of the publishers"
131+ " earlier in the list reported a failure to publish.")
132+ super(FallbackChainOption, self).__init__(**kwargs)
133+
134+
135+class PublisherDescriptionOption(BasePublisherDescriptionOption):
136+
137+ def __init__(self, **kwargs):
138+ add_fallback_chain = False
139+ if 'spec' not in kwargs:
140+ add_fallback_chain = True
141 super(PublisherDescriptionOption, self).__init__(**kwargs)
142+ if add_fallback_chain:
143+ self.spec['fallback_chain'] = FallbackChainOption()
144
145
146 class PublishersOption(schema.ListOption):
147
148=== modified file 'oops_dictconfig/dictconfig.py'
149--- oops_dictconfig/dictconfig.py 2013-04-17 16:39:49 +0000
150+++ oops_dictconfig/dictconfig.py 2013-05-01 15:40:31 +0000
151@@ -60,6 +60,28 @@
152 }
153
154
155+def publisher_from_definition(publisher_defn):
156+ publisher_type = publisher_defn.get('type')
157+ if publisher_type is None:
158+ raise AssertionError(
159+ "Missing publisher type: %s" % str(publisher_defn))
160+ publisher_factory = PUBLISHERS.get(publisher_type)
161+ if publisher_factory is None:
162+ raise AssertionError(
163+ "Unknown publisher type: %s" % str(publisher_type))
164+ publish_method = publisher_factory(publisher_defn)
165+ if 'new_only' in publisher_defn and publisher_defn['new_only']:
166+ publish_method = oops.publish_new_only(publish_method)
167+ return publish_method
168+
169+
170+def make_fallback_chain(publisher_defns):
171+ publishers = []
172+ for publisher_defn in publisher_defns:
173+ publishers.append(publisher_from_definition(publisher_defn))
174+ return oops.publish_with_fallback(*publishers)
175+
176+
177 def config_from_dict(dict_config):
178 """Create an `oops.Config` object from `dict_config`.
179
180@@ -88,17 +110,11 @@
181 publishers = []
182 if 'publishers' in dict_config:
183 for publisher_defn in dict_config['publishers']:
184- publisher_type = publisher_defn.get('type')
185- if publisher_type is None:
186- raise AssertionError(
187- "Missing publisher type: %s" % str(publisher_defn))
188- publisher_factory = PUBLISHERS.get(publisher_type)
189- if publisher_factory is None:
190- raise AssertionError(
191- "Unknown publisher type: %s" % str(publisher_type))
192- publish_method = publisher_factory(publisher_defn)
193- if 'new_only' in publisher_defn and publisher_defn['new_only']:
194- publish_method = oops.publish_new_only(publish_method)
195+ if 'fallback_chain' in publisher_defn:
196+ publish_method = make_fallback_chain(
197+ publisher_defn['fallback_chain'])
198+ else:
199+ publish_method = publisher_from_definition(publisher_defn)
200 publishers.append(publish_method)
201 oops_config.publisher = oops.publish_to_many(*publishers)
202 oops_config.template.update(dict_config.get('template', {}))
203
204=== modified file 'oops_dictconfig/tests/__init__.py'
205--- oops_dictconfig/tests/__init__.py 2012-02-02 22:18:43 +0000
206+++ oops_dictconfig/tests/__init__.py 2013-05-01 15:40:31 +0000
207@@ -17,6 +17,7 @@
208
209 def test_suite():
210 module_names = [
211+ 'oops_dictconfig.tests.test_configglue_options',
212 'oops_dictconfig.tests.test_dictconfig',
213 ]
214 loader = unittest.TestLoader()
215
216=== added file 'oops_dictconfig/tests/test_configglue_options.py'
217--- oops_dictconfig/tests/test_configglue_options.py 1970-01-01 00:00:00 +0000
218+++ oops_dictconfig/tests/test_configglue_options.py 2013-05-01 15:40:31 +0000
219@@ -0,0 +1,114 @@
220+# Copyright (c) 2013, Canonical Ltd
221+#
222+# This program is free software: you can redistribute it and/or modify
223+# it under the terms of the GNU Lesser General Public License as published by
224+# the Free Software Foundation, version 3 only.
225+#
226+# This program is distributed in the hope that it will be useful,
227+# but WITHOUT ANY WARRANTY; without even the implied warranty of
228+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
229+# GNU Lesser General Public License for more details.
230+#
231+# You should have received a copy of the GNU Lesser General Public License
232+# along with this program. If not, see <http://www.gnu.org/licenses/>.
233+# GNU Lesser General Public License version 3 (see the file LICENSE).
234+
235+from StringIO import StringIO
236+
237+from configglue.glue import schemaconfigglue
238+from configglue.parser import SchemaConfigParser
239+from configglue.schema import Schema, Section
240+from testtools import TestCase
241+
242+from oops_dictconfig.configglue_options import OopsOption
243+
244+
245+class BasicSchema(Schema):
246+
247+ class oops(Section):
248+
249+ oops = OopsOption()
250+
251+
252+class ConfigglueOptionsTests(TestCase):
253+
254+ def get_parser(self, schema_class):
255+ return SchemaConfigParser(schema_class())
256+
257+ def get_options_from_config(self, schema_class, config):
258+ """`config` is a file-like object containing the config"""
259+ parser = self.get_parser(schema_class)
260+ parser.readfp(config)
261+ return schemaconfigglue(parser, argv=[])[1]
262+
263+ def test_emtpy_gives_empty(self):
264+ options = self.get_options_from_config(BasicSchema,
265+ StringIO(""))
266+ self.assertEqual([], options.oops_oops['publishers'])
267+ self.assertEqual({}, options.oops_oops['template'])
268+
269+ def test_basic_amqp_definition(self):
270+ options = self.get_options_from_config(BasicSchema,
271+ StringIO("""
272+[oops]
273+oops = oops_config
274+
275+[oops_config]
276+publishers = amqp_publisher
277+
278+[amqp_publisher]
279+type = amqp
280+host = example.com
281+"""))
282+ publishers = options.oops_oops['publishers']
283+ self.assertEqual(1, len(publishers))
284+ self.assertEqual('amqp', publishers[0]['type'])
285+ self.assertEqual('example.com', publishers[0]['host'])
286+
287+ def test_basic_datedir_definition(self):
288+ options = self.get_options_from_config(BasicSchema,
289+ StringIO("""
290+[oops]
291+oops = oops_config
292+
293+[oops_config]
294+publishers = datedir_publisher
295+
296+[datedir_publisher]
297+type = datedir
298+error_dir = some/dir
299+"""))
300+ publishers = options.oops_oops['publishers']
301+ self.assertEqual(1, len(publishers))
302+ self.assertEqual('datedir', publishers[0]['type'])
303+ self.assertEqual('some/dir', publishers[0]['error_dir'])
304+
305+ def test_fallback_chain(self):
306+ options = self.get_options_from_config(BasicSchema,
307+ StringIO("""
308+[oops]
309+oops = oops_config
310+
311+[oops_config]
312+publishers = fallback_chain
313+
314+[fallback_chain]
315+fallback_chain = amqp_publisher
316+ datedir_publisher
317+
318+[amqp_publisher]
319+type = amqp
320+host = example.com
321+
322+[datedir_publisher]
323+type = datedir
324+error_dir = some/dir
325+"""))
326+ publishers = options.oops_oops['publishers']
327+ self.assertEqual(1, len(publishers))
328+ self.assertEqual(2, len(publishers[0]['fallback_chain']))
329+ fallback_chain = publishers[0]['fallback_chain']
330+ self.assertEqual('amqp', fallback_chain[0]['type'])
331+ self.assertEqual('example.com', fallback_chain[0]['host'])
332+ self.assertEqual('datedir', fallback_chain[1]['type'])
333+ self.assertEqual('some/dir', fallback_chain[1]['error_dir'])
334
335=== modified file 'oops_dictconfig/tests/test_dictconfig.py'
336--- oops_dictconfig/tests/test_dictconfig.py 2013-04-17 16:39:49 +0000
337+++ oops_dictconfig/tests/test_dictconfig.py 2013-05-01 15:40:31 +0000
338@@ -271,3 +271,17 @@
339 def test_non_empty_template_gives_non_empty_template(self):
340 config = config_from_dict(dict(template=dict(foo='bar')))
341 self.assertEqual(dict(foo='bar'), config.template)
342+
343+ def test_fallback_chain_creates_fallback_chain(self):
344+ amqp_defn = self.get_basic_ampq_definition()
345+ datedir_defn = dict(type='datedir', error_dir='error_dir')
346+ config = self.get_config_from_dict(
347+ [dict(fallback_chain=[amqp_defn, datedir_defn])])
348+ fallback_fn = get_publishers_from_function(config.publisher)[0]
349+ # FIXME: brittle test, dependent on implementation details of
350+ # oops.publish_with_fallback. Find a better way of testing
351+ # that the function is used.
352+ self.assertEquals('result', fallback_fn.func_name)
353+ chain = get_publishers_from_function(fallback_fn)
354+ self.assertIsInstance(chain[0], oops_amqp.Publisher)
355+ self.assertIsInstance(chain[1].im_self, DateDirRepo)
356
357=== modified file 'tarmac_tests.sh'
358--- tarmac_tests.sh 2012-02-24 21:22:56 +0000
359+++ tarmac_tests.sh 2013-05-01 15:40:31 +0000
360@@ -6,5 +6,5 @@
361 virtualenv --no-site-packages virtualenv > log
362 . virtualenv/bin/activate >> log
363 python setup.py develop >> log
364-pip install testtools oops_amqp oops_datedir_repo
365+pip install testtools oops_amqp oops_datedir_repo configglue
366 python -m testtools.run oops_dictconfig.tests.test_suite

Subscribers

People subscribed via source and target branches