Merge lp:~soren/surveilr/messaging into lp:surveilr
- messaging
- Merge into trunk
Proposed by
Soren Hansen
Status: | Merged |
---|---|
Approved by: | Soren Hansen |
Approved revision: | 15 |
Merged at revision: | 11 |
Proposed branch: | lp:~soren/surveilr/messaging |
Merge into: | lp:surveilr |
Diff against target: |
466 lines (+336/-16) 11 files modified
surveilr/api/server.py (+22/-1) surveilr/defaults.cfg (+1/-0) surveilr/messaging/__init__.py (+34/-0) surveilr/messaging/fake.py (+32/-0) surveilr/messaging/sms.py (+52/-0) surveilr/models.py (+4/-0) surveilr/tests/__init__.py (+1/-2) surveilr/tests/test_api_server.py (+36/-13) surveilr/tests/test_messaging.cfg (+5/-0) surveilr/tests/test_messaging.py (+114/-0) surveilr/tests/utils.py (+35/-0) |
To merge this branch: | bzr merge lp:~soren/surveilr/messaging |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Soren Hansen | Pending | ||
Review via email: mp+83866@code.launchpad.net |
Commit message
Add basic messaging subsystem.
Description of the change
To post a comment you must log in.
lp:~soren/surveilr/messaging
updated
- 11. By Soren Hansen
-
Merge trunk
- 12. By Soren Hansen
-
Add support for sender id in SMS messaging driver. Add tests for Clickatell client instantiation.
- 13. By Soren Hansen
-
Only define User model once.
- 14. By Soren Hansen
-
Include messaging settings in GET /users/{id} response
- 15. By Soren Hansen
-
PEP-8 fixes
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'surveilr/api/server.py' |
2 | --- surveilr/api/server.py 2011-11-29 20:07:19 +0000 |
3 | +++ surveilr/api/server.py 2011-11-30 09:36:24 +0000 |
4 | @@ -35,10 +35,25 @@ |
5 | from webob.dec import wsgify |
6 | from webob.exc import HTTPNotFound |
7 | |
8 | +from surveilr import messaging |
9 | from surveilr import models |
10 | from surveilr import utils |
11 | |
12 | |
13 | +class NotificationController(object): |
14 | + """Routes style controller for notifications""" |
15 | + |
16 | + def create(self, req, user_id): |
17 | + """Called for POST requests to /user/{id}/notifications |
18 | + |
19 | + Sends a notification to the given user""" |
20 | + |
21 | + user = models.User.get(key=user_id) |
22 | + messaging.send(user, json.loads(req.body)) |
23 | + response = {} |
24 | + return Response(json.dumps(response)) |
25 | + |
26 | + |
27 | class UserController(object): |
28 | """Routes style controller for actions related to users""" |
29 | |
30 | @@ -59,7 +74,10 @@ |
31 | Returns information for the given service""" |
32 | try: |
33 | user = models.User.get(id) |
34 | - return Response({'id': user.key}) |
35 | + resp_dict = {'id': user.key, |
36 | + 'messaging_driver': user.messaging_driver, |
37 | + 'messaging_address': user.messaging_address} |
38 | + return Response(json.dumps(resp_dict)) |
39 | except riakalchemy.NoSuchObjectError: |
40 | return HTTPNotFound() |
41 | |
42 | @@ -138,6 +156,9 @@ |
43 | path_prefix='/services/{service_name}') |
44 | map.resource("service", "services", controller='ServiceController') |
45 | map.resource("user", "users", controller='UserController') |
46 | + map.resource("notification", "notifications", |
47 | + controller='NotificationController', |
48 | + path_prefix='/users/{user_id}') |
49 | |
50 | @wsgify |
51 | def __call__(self, req): |
52 | |
53 | === modified file 'surveilr/defaults.cfg' |
54 | --- surveilr/defaults.cfg 2011-11-28 22:05:12 +0000 |
55 | +++ surveilr/defaults.cfg 2011-11-30 09:36:24 +0000 |
56 | @@ -4,3 +4,4 @@ |
57 | username = |
58 | password = |
59 | api_id = |
60 | +sender = |
61 | |
62 | === added directory 'surveilr/messaging' |
63 | === added file 'surveilr/messaging/__init__.py' |
64 | --- surveilr/messaging/__init__.py 1970-01-01 00:00:00 +0000 |
65 | +++ surveilr/messaging/__init__.py 2011-11-30 09:36:24 +0000 |
66 | @@ -0,0 +1,34 @@ |
67 | +""" |
68 | + Surveilr - Log aggregation, analysis and visualisation |
69 | + |
70 | + Copyright (C) 2011 Linux2Go |
71 | + |
72 | + This program is free software: you can redistribute it and/or |
73 | + modify it under the terms of the GNU Affero General Public License |
74 | + as published by the Free Software Foundation, either version 3 of |
75 | + the License, or (at your option) any later version. |
76 | + |
77 | + This program is distributed in the hope that it will be useful, |
78 | + but WITHOUT ANY WARRANTY; without even the implied warranty of |
79 | + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
80 | + GNU Affero General Public License for more details. |
81 | + |
82 | + You should have received a copy of the GNU Affero General Public |
83 | + License along with this program. If not, see |
84 | + <http://www.gnu.org/licenses/>. |
85 | +""" |
86 | + |
87 | +import surveilr.messaging.fake |
88 | +import surveilr.messaging.sms |
89 | + |
90 | +from surveilr import drivers |
91 | + |
92 | + |
93 | +def _get_driver(recipient): |
94 | + return drivers.get_driver('messaging', |
95 | + getattr(recipient, 'messaging_driver', 'fake')) |
96 | + |
97 | + |
98 | +def send(recipient, info): |
99 | + driver = _get_driver(recipient) |
100 | + driver.send(recipient, info) |
101 | |
102 | === added file 'surveilr/messaging/fake.py' |
103 | --- surveilr/messaging/fake.py 1970-01-01 00:00:00 +0000 |
104 | +++ surveilr/messaging/fake.py 2011-11-30 09:36:24 +0000 |
105 | @@ -0,0 +1,32 @@ |
106 | +""" |
107 | + Surveilr - Log aggregation, analysis and visualisation |
108 | + |
109 | + Copyright (C) 2011 Linux2Go |
110 | + |
111 | + This program is free software: you can redistribute it and/or |
112 | + modify it under the terms of the GNU Affero General Public License |
113 | + as published by the Free Software Foundation, either version 3 of |
114 | + the License, or (at your option) any later version. |
115 | + |
116 | + This program is distributed in the hope that it will be useful, |
117 | + but WITHOUT ANY WARRANTY; without even the implied warranty of |
118 | + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
119 | + GNU Affero General Public License for more details. |
120 | + |
121 | + You should have received a copy of the GNU Affero General Public |
122 | + License along with this program. If not, see |
123 | + <http://www.gnu.org/licenses/>. |
124 | + |
125 | + Fake messaging driver |
126 | +""" |
127 | + |
128 | +import surveilr.drivers |
129 | + |
130 | + |
131 | +class FakeMessaging(object): |
132 | + messages = [] |
133 | + |
134 | + def send(self, recipient, info): |
135 | + self.messages.append((recipient, info)) |
136 | + |
137 | +surveilr.drivers.register_driver('messaging', 'fake', FakeMessaging()) |
138 | |
139 | === added file 'surveilr/messaging/sms.py' |
140 | --- surveilr/messaging/sms.py 1970-01-01 00:00:00 +0000 |
141 | +++ surveilr/messaging/sms.py 2011-11-30 09:36:24 +0000 |
142 | @@ -0,0 +1,52 @@ |
143 | +""" |
144 | + Surveilr - Log aggregation, analysis and visualisation |
145 | + |
146 | + Copyright (C) 2011 Linux2Go |
147 | + |
148 | + This program is free software: you can redistribute it and/or |
149 | + modify it under the terms of the GNU Affero General Public License |
150 | + as published by the Free Software Foundation, either version 3 of |
151 | + the License, or (at your option) any later version. |
152 | + |
153 | + This program is distributed in the hope that it will be useful, |
154 | + but WITHOUT ANY WARRANTY; without even the implied warranty of |
155 | + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
156 | + GNU Affero General Public License for more details. |
157 | + |
158 | + You should have received a copy of the GNU Affero General Public |
159 | + License along with this program. If not, see |
160 | + <http://www.gnu.org/licenses/>. |
161 | + |
162 | + SMS messaging driver |
163 | +""" |
164 | + |
165 | +from surveilr import config |
166 | +from surveilr import drivers |
167 | + |
168 | +from clickatell.api import Clickatell |
169 | +from clickatell import constants as cc |
170 | + |
171 | + |
172 | +class SMSMessaging(object): |
173 | + def __init__(self): |
174 | + username = config.get_str('sms', 'username') |
175 | + password = config.get_str('sms', 'password') |
176 | + api_id = config.get_str('sms', 'api_id') |
177 | + self.client = Clickatell(username, password, api_id, |
178 | + sendmsg_defaults={ |
179 | + 'callback': cc.YES, |
180 | + 'msg_type': cc.SMS_DEFAULT, |
181 | + 'deliv_ack': cc.YES, |
182 | + 'req_feat': (cc.FEAT_ALPHA + |
183 | + cc.FEAT_NUMER + |
184 | + cc.FEAT_DELIVACK) |
185 | + }) |
186 | + |
187 | + def send(self, recipient, info): |
188 | + sender = config.get_str('sms', 'sender') |
189 | + self.client.sendmsg(recipients=[recipient.messaging_address], |
190 | + sender=sender, |
191 | + text=str(info)) |
192 | + |
193 | + |
194 | +drivers.register_driver('messaging', 'sms', SMSMessaging()) |
195 | |
196 | === modified file 'surveilr/models.py' |
197 | --- surveilr/models.py 2011-11-29 10:27:40 +0000 |
198 | +++ surveilr/models.py 2011-11-30 09:36:24 +0000 |
199 | @@ -35,12 +35,16 @@ |
200 | |
201 | name = String() |
202 | most_recent_log_entry = RelatedObjects() |
203 | + user = RelatedObjects(backref=True) |
204 | |
205 | |
206 | class User(RiakObject): |
207 | """A user of the service""" |
208 | bucket_name = 'users' |
209 | |
210 | + messaging_driver = String() |
211 | + messaging_address = String() |
212 | + |
213 | |
214 | class LogEntry(RiakObject): |
215 | """A log entry holding one or more metrics |
216 | |
217 | === modified file 'surveilr/tests/__init__.py' |
218 | --- surveilr/tests/__init__.py 2011-11-29 23:14:20 +0000 |
219 | +++ surveilr/tests/__init__.py 2011-11-30 09:36:24 +0000 |
220 | @@ -24,6 +24,7 @@ |
221 | |
222 | from surveilr import config |
223 | |
224 | + |
225 | class TestCase(unittest.TestCase): |
226 | def config_files(self): |
227 | module = self.__module__ |
228 | @@ -49,5 +50,3 @@ |
229 | |
230 | def tearDown(self): |
231 | config.load_default_config() |
232 | - |
233 | - |
234 | |
235 | === modified file 'surveilr/tests/test_api_server.py' |
236 | --- surveilr/tests/test_api_server.py 2011-11-29 10:27:40 +0000 |
237 | +++ surveilr/tests/test_api_server.py 2011-11-30 09:36:24 +0000 |
238 | @@ -38,23 +38,46 @@ |
239 | """Create, retrieve, delete, attempt to retrieve again""" |
240 | req = Request.blank('/users', |
241 | method='POST', |
242 | + POST=json.dumps({'messaging_driver': 'fake', |
243 | + 'messaging_address': 'foo'})) |
244 | + resp = application(req) |
245 | + self.assertEquals(resp.status_int, 200) |
246 | + |
247 | + service_id = json.loads(resp.body)['id'] |
248 | + |
249 | + req = Request.blank('/users/%s' % service_id) |
250 | + resp = application(req) |
251 | + self.assertEquals(resp.status_int, 200) |
252 | + |
253 | + print resp.body, type(resp.body) |
254 | + user = json.loads(resp.body) |
255 | + self.assertEquals(user['messaging_driver'], 'fake') |
256 | + self.assertEquals(user['messaging_address'], 'foo') |
257 | + |
258 | + req = Request.blank('/users/%s' % service_id, method='DELETE') |
259 | + resp = application(req) |
260 | + self.assertEquals(resp.status_int, 200) |
261 | + |
262 | + req = Request.blank('/users/%s' % service_id) |
263 | + resp = application(req) |
264 | + self.assertEquals(resp.status_int, 404) |
265 | + |
266 | + def test_send_notification(self): |
267 | + req = Request.blank('/users', |
268 | + method='POST', |
269 | POST=json.dumps({})) |
270 | resp = application(req) |
271 | self.assertEquals(resp.status_int, 200) |
272 | |
273 | - service_id = json.loads(resp.body)['id'] |
274 | - |
275 | - req = Request.blank('/users/%s' % service_id) |
276 | - resp = application(req) |
277 | - self.assertEquals(resp.status_int, 200) |
278 | - |
279 | - req = Request.blank('/users/%s' % service_id, method='DELETE') |
280 | - resp = application(req) |
281 | - self.assertEquals(resp.status_int, 200) |
282 | - |
283 | - req = Request.blank('/users/%s' % service_id) |
284 | - resp = application(req) |
285 | - self.assertEquals(resp.status_int, 404) |
286 | + user_id = json.loads(resp.body)['id'] |
287 | + req = Request.blank('/users/%s/notifications' % user_id, |
288 | + method='POST', |
289 | + POST=json.dumps({ |
290 | + 'timestamp': 13217362355575, |
291 | + 'metrics': {'duration': 85000, |
292 | + 'response_size': 12435}})) |
293 | + resp = application(req) |
294 | + self.assertEquals(resp.status_int, 200) |
295 | |
296 | def test_create_retrieve_service(self): |
297 | """Create, retrieve, delete, attempt to retrieve again""" |
298 | |
299 | === added file 'surveilr/tests/test_messaging.cfg' |
300 | --- surveilr/tests/test_messaging.cfg 1970-01-01 00:00:00 +0000 |
301 | +++ surveilr/tests/test_messaging.cfg 2011-11-30 09:36:24 +0000 |
302 | @@ -0,0 +1,5 @@ |
303 | +[sms] |
304 | +username = testuser |
305 | +password = testpassword |
306 | +api_id = testapiid |
307 | +sender = testsender |
308 | |
309 | === added file 'surveilr/tests/test_messaging.py' |
310 | --- surveilr/tests/test_messaging.py 1970-01-01 00:00:00 +0000 |
311 | +++ surveilr/tests/test_messaging.py 2011-11-30 09:36:24 +0000 |
312 | @@ -0,0 +1,114 @@ |
313 | +""" |
314 | + Surveilr - Log aggregation, analysis and visualisation |
315 | + |
316 | + Copyright (C) 2011 Linux2Go |
317 | + |
318 | + This program is free software: you can redistribute it and/or |
319 | + modify it under the terms of the GNU Affero General Public License |
320 | + as published by the Free Software Foundation, either version 3 of |
321 | + the License, or (at your option) any later version. |
322 | + |
323 | + This program is distributed in the hope that it will be useful, |
324 | + but WITHOUT ANY WARRANTY; without even the implied warranty of |
325 | + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
326 | + GNU Affero General Public License for more details. |
327 | + |
328 | + You should have received a copy of the GNU Affero General Public |
329 | + License along with this program. If not, see |
330 | + <http://www.gnu.org/licenses/>. |
331 | + |
332 | + Tests for messaging layer |
333 | +""" |
334 | + |
335 | +import mock |
336 | +import unittest |
337 | + |
338 | +from surveilr import drivers |
339 | +from surveilr import messaging |
340 | +from surveilr import tests |
341 | +from surveilr.messaging import sms |
342 | +from surveilr.tests import utils |
343 | + |
344 | + |
345 | +class FakeMessagingDriverTests(unittest.TestCase): |
346 | + def setUp(self): |
347 | + import surveilr.messaging.fake |
348 | + # This does nothing, but pyflakes gets upset if we import it |
349 | + # and never "use" it. |
350 | + surveilr.messaging.fake |
351 | + self.driver = drivers.get_driver('messaging', 'fake') |
352 | + |
353 | + def test_send(self): |
354 | + user = utils.get_test_user() |
355 | + info = {'service': 'service_id', |
356 | + 'state': 'normal', |
357 | + 'previous_state': 'unexpected high'} |
358 | + self.driver.send(user, info) |
359 | + |
360 | + |
361 | +class SMSMessagingDriverTests(tests.TestCase): |
362 | + @mock.patch('surveilr.messaging.sms.Clickatell') |
363 | + def test_instantiate_client(self, clickatell): |
364 | + self.driver = sms.SMSMessaging() |
365 | + |
366 | + sendmsg_defaults = {'callback': 1, |
367 | + 'req_feat': 8240, |
368 | + 'deliv_ack': 1, |
369 | + 'msg_type': 'SMS_TEXT'} |
370 | + clickatell.assert_called_with('testuser', 'testpassword', 'testapiid', |
371 | + sendmsg_defaults=sendmsg_defaults) |
372 | + |
373 | + def test_send(self): |
374 | + driver = sms.SMSMessaging() |
375 | + driver.client = mock.Mock() |
376 | + |
377 | + msisdn = '12345678' |
378 | + user = utils.get_test_user(messaging_driver='sms', |
379 | + messaging_address=msisdn) |
380 | + info = utils.get_test_notification_info() |
381 | + |
382 | + driver.send(user, info) |
383 | + |
384 | + driver.client.sendmsg.assert_called_with(recipients=[msisdn], |
385 | + sender='testsender', |
386 | + text=str(info)) |
387 | + |
388 | + |
389 | +class MessagingAPITests(unittest.TestCase): |
390 | + def test_send(self): |
391 | + user = utils.get_test_user() |
392 | + info = {'service': 'service_id', |
393 | + 'state': 'normal', |
394 | + 'previous_state': 'unexpected high'} |
395 | + |
396 | + with mock.patch('surveilr.messaging._get_driver') as _get_driver: |
397 | + messaging.send(user, info) |
398 | + |
399 | + # Check that _get_driver gets called with the user as its argument |
400 | + _get_driver.assert_called_with(user) |
401 | + |
402 | + # Check that _get_driver's return value (i.e. the driver) |
403 | + # gets its .send() method called with user and info as its |
404 | + # arguments |
405 | + _get_driver.return_value.send.assert_called_with(user, info) |
406 | + |
407 | + def test_get_driver_default(self): |
408 | + user = utils.get_test_user() |
409 | + expected_driver = drivers.get_driver('messaging', 'fake') |
410 | + |
411 | + actual_driver = messaging._get_driver(user) |
412 | + self.assertEquals(actual_driver, expected_driver) |
413 | + |
414 | + def test_get_driver_sms(self): |
415 | + user = utils.get_test_user(messaging_driver='sms') |
416 | + expected_driver = drivers.get_driver('messaging', 'sms') |
417 | + |
418 | + actual_driver = messaging._get_driver(user) |
419 | + self.assertEquals(actual_driver, expected_driver) |
420 | + |
421 | + def test_get_driver_fake(self): |
422 | + user = utils.get_test_user(messaging_driver='fake') |
423 | + expected_driver = drivers.get_driver('messaging', 'fake') |
424 | + |
425 | + actual_driver = messaging._get_driver(user) |
426 | + self.assertEquals(actual_driver, expected_driver) |
427 | |
428 | === added file 'surveilr/tests/utils.py' |
429 | --- surveilr/tests/utils.py 1970-01-01 00:00:00 +0000 |
430 | +++ surveilr/tests/utils.py 2011-11-30 09:36:24 +0000 |
431 | @@ -0,0 +1,35 @@ |
432 | +""" |
433 | + Surveilr - Log aggregation, analysis and visualisation |
434 | + |
435 | + Copyright (C) 2011 Linux2Go |
436 | + |
437 | + This program is free software: you can redistribute it and/or |
438 | + modify it under the terms of the GNU Affero General Public License |
439 | + as published by the Free Software Foundation, either version 3 of |
440 | + the License, or (at your option) any later version. |
441 | + |
442 | + This program is distributed in the hope that it will be useful, |
443 | + but WITHOUT ANY WARRANTY; without even the implied warranty of |
444 | + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
445 | + GNU Affero General Public License for more details. |
446 | + |
447 | + You should have received a copy of the GNU Affero General Public |
448 | + License along with this program. If not, see |
449 | + <http://www.gnu.org/licenses/>. |
450 | + |
451 | + Utilities for unit tests |
452 | +""" |
453 | + |
454 | +from surveilr import models |
455 | + |
456 | + |
457 | +def get_test_user(messaging_driver='fake', messaging_address='somewhere'): |
458 | + return models.User(**{'id': 'user_id', |
459 | + 'messaging_driver': messaging_driver, |
460 | + 'messaging_address': messaging_address}) |
461 | + |
462 | + |
463 | +def get_test_notification_info(): |
464 | + return {'service': 'service_id', |
465 | + 'state': 'normal', |
466 | + 'previous_state': 'unexpected high'} |