Merge lp:~tim-simpson/nova/multiple-notifiers into lp:~hudson-openstack/nova/trunk
- multiple-notifiers
- Merge into trunk
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 |
Related bugs: |
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 |
Commit message
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.
Jason Kölker (jason-koelker) wrote : | # |
I would think that for this functionality, you would just write a fan_out notifier.
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.
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.
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.
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.
Sandy Walsh (sandy-walsh) wrote : | # |
oh, and the tests should reflect the bad import case
Sandy Walsh (sandy-walsh) wrote : | # |
oh, and you need to update the AUTHORS file
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.
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.
- 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.
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.
Sandy Walsh (sandy-walsh) wrote : | # |
looks good ... just some pep8 issues
- 1443. By Tim Simpson
-
Fixed some pep8 and pylint issues.
- 1444. By Tim Simpson
-
Merged from upstream.
Sandy Walsh (sandy-walsh) wrote : | # |
Others might complain about ImportFailureNo
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.
Thierry Carrez (ttx) wrote : | # |
This should wait for Essex ?
Matt Dietz (cerberus) wrote : | # |
ttx: ahh, indeed. Wasn't thinking of timelines here.
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.
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.
Thierry Carrez (ttx) wrote : | # |
Sounds self-contained indeed, and doesn't change existing behavior, so go for it !
Preview Diff
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) |
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.