Merge lp:~soren/surveilr/messaging into lp:surveilr

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
Reviewer Review Type Date Requested Status
Soren Hansen Pending
Review via email: mp+83866@code.launchpad.net

Commit message

Add basic messaging subsystem.

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'}

Subscribers

People subscribed via source and target branches

to all changes: