Merge lp:~tim-simpson/nova/multiple-notifiers into lp:~hudson-openstack/nova/trunk

Proposed by Tim Simpson
Status: Merged
Approved by: Sandy Walsh
Approved revision: 1444
Merged at revision: 1490
Proposed branch: lp:~tim-simpson/nova/multiple-notifiers
Merge into: lp:~hudson-openstack/nova/trunk
Diff against target: 199 lines (+173/-0)
4 files modified
Authors (+1/-0)
nova/notifier/list_notifier.py (+68/-0)
nova/tests/notifier/__init__.py (+16/-0)
nova/tests/notifier/test_list_notifier.py (+88/-0)
To merge this branch: bzr merge lp:~tim-simpson/nova/multiple-notifiers
Reviewer Review Type Date Requested Status
Thierry Carrez (community) ffe Approve
Monsyne Dragon (community) Approve
Matt Dietz (community) Abstain
Sandy Walsh (community) Approve
Review via email: mp+71370@code.launchpad.net

Description of the change

The notifiers API was changed to take a list of notifiers. Some people might want to use more than one notifier so hopefully this will be accepted into trunk.

To post a comment you must log in.
Revision history for this message
Matt Dietz (cerberus) wrote :

Tim, the code looks good. However, can you give me a hypothetical use case where someone would need to do more than one style of notification at a time? I'm not necessarily opposed to the idea, but I would like to minimize the burden of notifications on Nova if at all possible. That's why we're writing an application external to nova to aggregate and distribute notifications in a granular way.

review: Needs Fixing
Revision history for this message
Matt Dietz (cerberus) wrote :

Err

review: Needs Information
Revision history for this message
Jason Kölker (jason-koelker) wrote :

I would think that for this functionality, you would just write a fan_out notifier.

Revision history for this message
Tim Simpson (tim-simpson) wrote :

Hi Matt,

My case is that I want to write all notifications to the log files as well as do something more substantial in response to them. For project Red Dwarf (Database as a Service) we'll be needing to know if the compute manager fails during run_instance in order to free up additional resources that we are provisioning. Since we've forked Nova we could simply hack up the compute manager but would prefer to respond to the notifications to keep our changes easy to separate.

Jason,

I think you're right. My first reaction to the idea of a "fan_out" notifier is I almost can't see why someone would only use one.

However, thinking about it keeping only one "driver" per manager is consistent with the rest of Nova. When the external notifier application is created then only the driver for it will be needed in most situations and having a list will get in the way.

I'll go ahead and update this as you suggested.

Thank you both for the feedback.

Revision history for this message
Sandy Walsh (sandy-walsh) wrote :

Myself and Dragon discussed this a while back (specifically around retries for failed provisioning attempts and orchestration). I proposed the fanout solution, but Dragon talked me into the multiple notifiers scheme. I think it's a better solution, less to clean up if something fails.

Needs a proper test though. Specifically around one driver failing and the others continuing to work.

review: Needs Fixing
Revision history for this message
Tim Simpson (tim-simpson) wrote :

I pushed up a new version which instead of forcing the notifier.api to keep a list of drivers has a driver called "list_notifier" that can call a list of other drivers.

I thought calling it "fan_out_notifier" might infer additional qualities it lacked, so I went with a simpler name. I can rename it if anyone thinks the term "fan out" is a better fit.

Sandy this code has a test case for when one driver fails (it asserts an exception is logged) and another for when they all work.

Revision history for this message
Sandy Walsh (sandy-walsh) wrote :

Looks good ... minor nits

+34 single _ for private functions

+38 needs some error handling. If the import fails it'll bring down the service. It should simply not load that driver and log an error.

review: Needs Fixing
Revision history for this message
Sandy Walsh (sandy-walsh) wrote :

oh, and the tests should reflect the bad import case

Revision history for this message
Sandy Walsh (sandy-walsh) wrote :

oh, and you need to update the AUTHORS file

http://paste.openstack.org/show/2234/

review: Needs Fixing
Revision history for this message
Tim Simpson (tim-simpson) wrote :

Thanks Sandy.

When the notification driver can't be loaded I load a class which remembers the raised exception and raises it every time notify is called. So if a notification driver fails to load you see mention of it in the logs each time notify is called. The alternative was to log something the first time and never again. If people want to do this instead I'll go ahead and update the code.

I also changed the authors file and removed the extra underscore from the private function.

Revision history for this message
Sandy Walsh (sandy-walsh) wrote :

Hmm, you know what, I was thinking about this more last night. I don't think terminating the service is the right thing to do.

The reason being is we don't see the failure until a notification is sent. It's not like a failure on startup, it could happen any any random time in the future.

So, sadly, seems the right thing to do is to log the event, skip the failing import and press on.

review: Needs Fixing
1441. By Tim Simpson

Switched list_notifier to log an exception each time notify is called, for each notification driver that failed to import.

1442. By Tim Simpson

Merged from upstream.

Revision history for this message
Tim Simpson (tim-simpson) wrote :

Yeah, killing it when the notification could happen later seems bad too.
I've changed the code back to logging an exception for drivers which couldn't be found.

Revision history for this message
Sandy Walsh (sandy-walsh) wrote :

looks good ... just some pep8 issues

http://paste.openstack.org/show/2258/

review: Needs Fixing
1443. By Tim Simpson

Fixed some pep8 and pylint issues.

1444. By Tim Simpson

Merged from upstream.

Revision history for this message
Sandy Walsh (sandy-walsh) wrote :

Others might complain about ImportFailureNotifier being too noisy and blowing out the log files, but generally looks good to me. Nice work Tim!

review: Approve
Revision history for this message
Matt Dietz (cerberus) wrote :

Tim,

    That's actually *exactly* the reason I moved that kind of functionality external to Nova. I believe we should be focused on developing the minimum amount of functionality inside of Nova because anything else interferes with the core competency of virtualization.
    I'm afraid things like this ending up in the code open up the floodgate for people to create insanely complex notification systems. I also wanted Nova to have an "indifferent" attitude to notifying end users. I believe this allows Nova to send notification as quickly as possible in as non-specific a way as I could muster.
    With that said, I'm not actually worried so much about *this* particular patch. Because this is more of a philosophical argument than one pertaining to the functionality around this patch, I'm going to abstain. I'll have Dragon take a look.

review: Abstain
Revision history for this message
Thierry Carrez (ttx) wrote :

This should wait for Essex ?

review: Disapprove
Revision history for this message
Matt Dietz (cerberus) wrote :

ttx: ahh, indeed. Wasn't thinking of timelines here.

Revision history for this message
Monsyne Dragon (mdragon) wrote :

Now that this is changed to be a driver, it looks good to me. The import error logging is a bit... vocal, but I suppose that might be a good thing.

review: Approve
Revision history for this message
Tim Simpson (tim-simpson) wrote :

> This should wait for Essex ?
Is the motivation for this just the deadline? As its an additional driver, at the very least we know it won't break notifications, and the tests seem to prove it will work for anyone who needs it.

Revision history for this message
Thierry Carrez (ttx) wrote :

Sounds self-contained indeed, and doesn't change existing behavior, so go for it !

review: Approve (ffe)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'Authors'
2--- Authors 2011-08-23 04:17:57 +0000
3+++ Authors 2011-08-23 20:52:26 +0000
4@@ -100,6 +100,7 @@
5 Soren Hansen <soren.hansen@rackspace.com>
6 Stephanie Reese <reese.sm@gmail.com>
7 Thierry Carrez <thierry@openstack.org>
8+Tim Simpson <tim.simpson@rackspace.com>
9 Todd Willey <todd@ansolabs.com>
10 Trey Morris <trey.morris@rackspace.com>
11 Troy Toman <troy.toman@rackspace.com>
12
13=== added file 'nova/notifier/list_notifier.py'
14--- nova/notifier/list_notifier.py 1970-01-01 00:00:00 +0000
15+++ nova/notifier/list_notifier.py 2011-08-23 20:52:26 +0000
16@@ -0,0 +1,68 @@
17+# Copyright 2011 OpenStack LLC.
18+# All Rights Reserved.
19+#
20+# Licensed under the Apache License, Version 2.0 (the "License"); you may
21+# not use this file except in compliance with the License. You may obtain
22+# a copy of the License at
23+#
24+# http://www.apache.org/licenses/LICENSE-2.0
25+#
26+# Unless required by applicable law or agreed to in writing, software
27+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
28+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
29+# License for the specific language governing permissions and limitations
30+# under the License.
31+
32+from nova import flags
33+from nova import log as logging
34+from nova import utils
35+from nova.exception import ClassNotFound
36+
37+flags.DEFINE_multistring('list_notifier_drivers',
38+ ['nova.notifier.no_op_notifier'],
39+ 'List of drivers to send notifications')
40+
41+FLAGS = flags.FLAGS
42+
43+LOG = logging.getLogger('nova.notifier.list_notifier')
44+
45+drivers = None
46+
47+
48+class ImportFailureNotifier(object):
49+ """Noisily re-raises some exception over-and-over when notify is called."""
50+
51+ def __init__(self, exception):
52+ self.exception = exception
53+
54+ def notify(self, message):
55+ raise self.exception
56+
57+
58+def _get_drivers():
59+ """Instantiates and returns drivers based on the flag values."""
60+ global drivers
61+ if not drivers:
62+ drivers = []
63+ for notification_driver in FLAGS.list_notifier_drivers:
64+ try:
65+ drivers.append(utils.import_object(notification_driver))
66+ except ClassNotFound as e:
67+ drivers.append(ImportFailureNotifier(e))
68+ return drivers
69+
70+
71+def notify(message):
72+ """Passes notification to mulitple notifiers in a list."""
73+ for driver in _get_drivers():
74+ try:
75+ driver.notify(message)
76+ except Exception as e:
77+ LOG.exception(_("Problem '%(e)s' attempting to send to "
78+ "notification driver %(driver)s." % locals()))
79+
80+
81+def _reset_drivers():
82+ """Used by unit tests to reset the drivers."""
83+ global drivers
84+ drivers = None
85
86=== added directory 'nova/tests/notifier'
87=== added file 'nova/tests/notifier/__init__.py'
88--- nova/tests/notifier/__init__.py 1970-01-01 00:00:00 +0000
89+++ nova/tests/notifier/__init__.py 2011-08-23 20:52:26 +0000
90@@ -0,0 +1,16 @@
91+# Copyright 2011 Openstack LLC.
92+# All Rights Reserved.
93+#
94+# Licensed under the Apache License, Version 2.0 (the "License"); you may
95+# not use this file except in compliance with the License. You may obtain
96+# a copy of the License at
97+#
98+# http://www.apache.org/licenses/LICENSE-2.0
99+#
100+# Unless required by applicable law or agreed to in writing, software
101+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
102+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
103+# License for the specific language governing permissions and limitations
104+# under the License.
105+
106+from nova.tests import *
107
108=== added file 'nova/tests/notifier/test_list_notifier.py'
109--- nova/tests/notifier/test_list_notifier.py 1970-01-01 00:00:00 +0000
110+++ nova/tests/notifier/test_list_notifier.py 2011-08-23 20:52:26 +0000
111@@ -0,0 +1,88 @@
112+# Copyright 2011 OpenStack LLC.
113+# All Rights Reserved.
114+#
115+# Licensed under the Apache License, Version 2.0 (the "License"); you may
116+# not use this file except in compliance with the License. You may obtain
117+# a copy of the License at
118+#
119+# http://www.apache.org/licenses/LICENSE-2.0
120+#
121+# Unless required by applicable law or agreed to in writing, software
122+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
123+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
124+# License for the specific language governing permissions and limitations
125+# under the License.
126+
127+import stubout
128+import sys
129+
130+import nova
131+from nova import log as logging
132+import nova.notifier.api
133+from nova.notifier.api import notify
134+from nova.notifier import log_notifier
135+from nova.notifier import no_op_notifier
136+from nova.notifier import list_notifier
137+from nova import test
138+
139+
140+class NotifierListTestCase(test.TestCase):
141+ """Test case for notifications"""
142+
143+ def setUp(self):
144+ super(NotifierListTestCase, self).setUp()
145+ list_notifier._reset_drivers()
146+ self.stubs = stubout.StubOutForTesting()
147+ # Mock log to add one to exception_count when log.exception is called
148+
149+ def mock_exception(cls, *args):
150+ self.exception_count += 1
151+
152+ self.exception_count = 0
153+ list_notifier_log = logging.getLogger('nova.notifier.list_notifier')
154+ self.stubs.Set(list_notifier_log, "exception", mock_exception)
155+ # Mock no_op notifier to add one to notify_count when called.
156+
157+ def mock_notify(cls, *args):
158+ self.notify_count += 1
159+
160+ self.notify_count = 0
161+ self.stubs.Set(nova.notifier.no_op_notifier, 'notify', mock_notify)
162+ # Mock log_notifier to raise RuntimeError when called.
163+
164+ def mock_notify2(cls, *args):
165+ raise RuntimeError("Bad notifier.")
166+
167+ self.stubs.Set(nova.notifier.log_notifier, 'notify', mock_notify2)
168+
169+ def tearDown(self):
170+ self.stubs.UnsetAll()
171+ list_notifier._reset_drivers()
172+ super(NotifierListTestCase, self).tearDown()
173+
174+ def test_send_notifications_successfully(self):
175+ self.flags(notification_driver='nova.notifier.list_notifier',
176+ list_notifier_drivers=['nova.notifier.no_op_notifier',
177+ 'nova.notifier.no_op_notifier'])
178+ notify('publisher_id', 'event_type',
179+ nova.notifier.api.WARN, dict(a=3))
180+ self.assertEqual(self.notify_count, 2)
181+ self.assertEqual(self.exception_count, 0)
182+
183+ def test_send_notifications_with_errors(self):
184+
185+ self.flags(notification_driver='nova.notifier.list_notifier',
186+ list_notifier_drivers=['nova.notifier.no_op_notifier',
187+ 'nova.notifier.log_notifier'])
188+ notify('publisher_id', 'event_type', nova.notifier.api.WARN, dict(a=3))
189+ self.assertEqual(self.notify_count, 1)
190+ self.assertEqual(self.exception_count, 1)
191+
192+ def test_when_driver_fails_to_import(self):
193+ self.flags(notification_driver='nova.notifier.list_notifier',
194+ list_notifier_drivers=['nova.notifier.no_op_notifier',
195+ 'nova.notifier.logo_notifier',
196+ 'fdsjgsdfhjkhgsfkj'])
197+ notify('publisher_id', 'event_type', nova.notifier.api.WARN, dict(a=3))
198+ self.assertEqual(self.exception_count, 2)
199+ self.assertEqual(self.notify_count, 1)