Merge lp:~soren/surveilr/make-it-slightly-useful into lp:surveilr

Proposed by Soren Hansen
Status: Merged
Approved by: Soren Hansen
Approved revision: 4
Merged at revision: 4
Proposed branch: lp:~soren/surveilr/make-it-slightly-useful
Merge into: lp:surveilr
Diff against target: 367 lines (+334/-0)
6 files modified
setup.cfg (+2/-0)
surveilr/__init__.py (+19/-0)
surveilr/api/server.py (+134/-0)
surveilr/models.py (+54/-0)
surveilr/tests/test_api_server.py (+90/-0)
surveilr/utils.py (+35/-0)
To merge this branch: bzr merge lp:~soren/surveilr/make-it-slightly-useful
Reviewer Review Type Date Requested Status
Soren Hansen Pending
Review via email: mp+82797@code.launchpad.net

Commit message

Add basic data model and API server

Description of the change

Add basic data model and API server

To post a comment you must log in.
Revision history for this message
Linux2Go Jenkins (linux2go-jenkins) wrote :

The attempt to merge lp:~soren/surveilr/make-it-slightly-useful into lp:surveilr failed. Below is the output from the failed tests.

running nosetests
running egg_info
creating surveilr.egg-info
writing requirements to surveilr.egg-info/requires.txt
writing surveilr.egg-info/PKG-INFO
writing top-level names to surveilr.egg-info/top_level.txt
writing dependency_links to surveilr.egg-info/dependency_links.txt
writing manifest file 'surveilr.egg-info/SOURCES.txt'
reading manifest file 'surveilr.egg-info/SOURCES.txt'
writing manifest file 'surveilr.egg-info/SOURCES.txt'
running build_ext
ok

E.E
======================================================================
ERROR: Failure: ImportError (No module named eventlet)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/usr/lib/pymodules/python2.7/nose/loader.py", line 390, in loadTestsFromName
    addr.filename, addr.module)
  File "/usr/lib/pymodules/python2.7/nose/importer.py", line 39, in importFromPath
    return self.importFromDir(dir_path, fqname)
  File "/usr/lib/pymodules/python2.7/nose/importer.py", line 86, in importFromDir
    mod = load_module(part_fqname, fh, filename, desc)
  File "/tmp/tmpBkJp3S/surveilr/api/server.py", line 24, in <module>
    import eventlet
ImportError: No module named eventlet

======================================================================
ERROR: Failure: ImportError (No module named mock)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/usr/lib/pymodules/python2.7/nose/loader.py", line 390, in loadTestsFromName
    addr.filename, addr.module)
  File "/usr/lib/pymodules/python2.7/nose/importer.py", line 39, in importFromPath
    return self.importFromDir(dir_path, fqname)
  File "/usr/lib/pymodules/python2.7/nose/importer.py", line 86, in importFromDir
    mod = load_module(part_fqname, fh, filename, desc)
  File "/tmp/tmpBkJp3S/surveilr/tests/test_api_server.py", line 24, in <module>
    import mock
ImportError: No module named mock

----------------------------------------------------------------------
Ran 3 tests in 0.418s

FAILED (errors=2)

Revision history for this message
Soren Hansen (soren) wrote :

Installed python-{mock,eventlet} on the Jenkis box.

Revision history for this message
Linux2Go Jenkins (linux2go-jenkins) wrote :

The attempt to merge lp:~soren/surveilr/make-it-slightly-useful into lp:surveilr failed. Below is the output from the failed tests.

running nosetests
running egg_info
creating surveilr.egg-info
writing requirements to surveilr.egg-info/requires.txt
writing surveilr.egg-info/PKG-INFO
writing top-level names to surveilr.egg-info/top_level.txt
writing dependency_links to surveilr.egg-info/dependency_links.txt
writing manifest file 'surveilr.egg-info/SOURCES.txt'
reading manifest file 'surveilr.egg-info/SOURCES.txt'
writing manifest file 'surveilr.egg-info/SOURCES.txt'
running build_ext
ok

E.E
======================================================================
ERROR: Failure: ImportError (No module named routes)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/usr/lib/pymodules/python2.7/nose/loader.py", line 390, in loadTestsFromName
    addr.filename, addr.module)
  File "/usr/lib/pymodules/python2.7/nose/importer.py", line 39, in importFromPath
    return self.importFromDir(dir_path, fqname)
  File "/usr/lib/pymodules/python2.7/nose/importer.py", line 86, in importFromDir
    mod = load_module(part_fqname, fh, filename, desc)
  File "/tmp/tmpXx8lxx/surveilr/api/server.py", line 30, in <module>
    from routes import Mapper
ImportError: No module named routes

======================================================================
ERROR: Failure: ImportError (No module named webob)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/usr/lib/pymodules/python2.7/nose/loader.py", line 390, in loadTestsFromName
    addr.filename, addr.module)
  File "/usr/lib/pymodules/python2.7/nose/importer.py", line 39, in importFromPath
    return self.importFromDir(dir_path, fqname)
  File "/usr/lib/pymodules/python2.7/nose/importer.py", line 86, in importFromDir
    mod = load_module(part_fqname, fh, filename, desc)
  File "/tmp/tmpXx8lxx/surveilr/tests/test_api_server.py", line 26, in <module>
    from webob import Request
ImportError: No module named webob

----------------------------------------------------------------------
Ran 3 tests in 0.811s

FAILED (errors=2)

Revision history for this message
Soren Hansen (soren) wrote :

...and python-{routes,webob}.

Revision history for this message
Linux2Go Jenkins (linux2go-jenkins) wrote :

The attempt to merge lp:~soren/surveilr/make-it-slightly-useful into lp:surveilr failed. Below is the output from the failed tests.

running nosetests
running egg_info
creating surveilr.egg-info
writing requirements to surveilr.egg-info/requires.txt
writing surveilr.egg-info/PKG-INFO
writing top-level names to surveilr.egg-info/top_level.txt
writing dependency_links to surveilr.egg-info/dependency_links.txt
writing manifest file 'surveilr.egg-info/SOURCES.txt'
reading manifest file 'surveilr.egg-info/SOURCES.txt'
writing manifest file 'surveilr.egg-info/SOURCES.txt'
running build_ext
ok

.EE..
======================================================================
ERROR: test_create_retrieve_metric (test_api_server.APIServerTests)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/tmp/tmpt9Dlnm/surveilr/tests/test_api_server.py", line 61, in test_create_retrieve_metric
    resp = application(req)
  File "/usr/lib/python2.7/dist-packages/webob/dec.py", line 161, in __call__
    return self.func(req, *args, **kw)
  File "/tmp/tmpt9Dlnm/surveilr/api/server.py", line 125, in __call__
    return getattr(controller, method)(req, **kwargs)
  File "/tmp/tmpt9Dlnm/surveilr/api/server.py", line 51, in create
    service.save()
  File "/var/lib/jenkins/riakalchemy/riakalchemy/__init__.py", line 182, in save
    self._riak_obj = bucket.new(self.key, data=data_dict)
  File "/usr/lib/python2.7/dist-packages/riak/bucket.py", line 218, in new
    raise TypeError('Unicode data values are not supported.')
TypeError: Unicode data values are not supported.

======================================================================
ERROR: Create, retrieve, delete, attempt to retrieve again
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/tmp/tmpt9Dlnm/surveilr/tests/test_api_server.py", line 42, in test_create_retrieve_service
    resp = application(req)
  File "/usr/lib/python2.7/dist-packages/webob/dec.py", line 161, in __call__
    return self.func(req, *args, **kw)
  File "/tmp/tmpt9Dlnm/surveilr/api/server.py", line 125, in __call__
    return getattr(controller, method)(req, **kwargs)
  File "/tmp/tmpt9Dlnm/surveilr/api/server.py", line 51, in create
    service.save()
  File "/var/lib/jenkins/riakalchemy/riakalchemy/__init__.py", line 182, in save
    self._riak_obj = bucket.new(self.key, data=data_dict)
  File "/usr/lib/python2.7/dist-packages/riak/bucket.py", line 218, in new
    raise TypeError('Unicode data values are not supported.')
TypeError: Unicode data values are not supported.

----------------------------------------------------------------------
Ran 5 tests in 1.128s

FAILED (errors=2)

Revision history for this message
Linux2Go Jenkins (linux2go-jenkins) wrote :

The attempt to merge lp:~soren/surveilr/make-it-slightly-useful into lp:surveilr failed. Below is the output from the failed tests.

running nosetests
running egg_info
creating surveilr.egg-info
writing requirements to surveilr.egg-info/requires.txt
writing surveilr.egg-info/PKG-INFO
writing top-level names to surveilr.egg-info/top_level.txt
writing dependency_links to surveilr.egg-info/dependency_links.txt
writing manifest file 'surveilr.egg-info/SOURCES.txt'
reading manifest file 'surveilr.egg-info/SOURCES.txt'
writing manifest file 'surveilr.egg-info/SOURCES.txt'
running build_ext
ok

.EE..
======================================================================
ERROR: test_create_retrieve_metric (test_api_server.APIServerTests)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/tmp/tmpfUlS7f/surveilr/tests/test_api_server.py", line 61, in test_create_retrieve_metric
    resp = application(req)
  File "/usr/lib/python2.7/dist-packages/webob/dec.py", line 161, in __call__
    return self.func(req, *args, **kw)
  File "/tmp/tmpfUlS7f/surveilr/api/server.py", line 125, in __call__
    return getattr(controller, method)(req, **kwargs)
  File "/tmp/tmpfUlS7f/surveilr/api/server.py", line 51, in create
    service.save()
  File "/var/lib/jenkins/riakalchemy/riakalchemy/model.py", line 182, in save
    self._riak_obj = bucket.new(self.key, data=data_dict)
  File "/usr/lib/python2.7/dist-packages/riak/bucket.py", line 218, in new
    raise TypeError('Unicode data values are not supported.')
TypeError: Unicode data values are not supported.

======================================================================
ERROR: Create, retrieve, delete, attempt to retrieve again
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/tmp/tmpfUlS7f/surveilr/tests/test_api_server.py", line 42, in test_create_retrieve_service
    resp = application(req)
  File "/usr/lib/python2.7/dist-packages/webob/dec.py", line 161, in __call__
    return self.func(req, *args, **kw)
  File "/tmp/tmpfUlS7f/surveilr/api/server.py", line 125, in __call__
    return getattr(controller, method)(req, **kwargs)
  File "/tmp/tmpfUlS7f/surveilr/api/server.py", line 51, in create
    service.save()
  File "/var/lib/jenkins/riakalchemy/riakalchemy/model.py", line 182, in save
    self._riak_obj = bucket.new(self.key, data=data_dict)
  File "/usr/lib/python2.7/dist-packages/riak/bucket.py", line 218, in new
    raise TypeError('Unicode data values are not supported.')
TypeError: Unicode data values are not supported.

----------------------------------------------------------------------
Ran 5 tests in 0.382s

FAILED (errors=2)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== added file 'setup.cfg'
2--- setup.cfg 1970-01-01 00:00:00 +0000
3+++ setup.cfg 2011-11-19 22:08:22 +0000
4@@ -0,0 +1,2 @@
5+[nosetests]
6+with-doctest=1
7
8=== added directory 'surveilr'
9=== added file 'surveilr/__init__.py'
10--- surveilr/__init__.py 1970-01-01 00:00:00 +0000
11+++ surveilr/__init__.py 2011-11-19 22:08:22 +0000
12@@ -0,0 +1,19 @@
13+"""
14+ Surveilr - Log aggregation, analysis and visualisation
15+
16+ Copyright (C) 2011 Linux2Go
17+
18+ This program is free software: you can redistribute it and/or
19+ modify it under the terms of the GNU Affero General Public License
20+ as published by the Free Software Foundation, either version 3 of
21+ the License, or (at your option) any later version.
22+
23+ This program is distributed in the hope that it will be useful,
24+ but WITHOUT ANY WARRANTY; without even the implied warranty of
25+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
26+ GNU Affero General Public License for more details.
27+
28+ You should have received a copy of the GNU Affero General Public
29+ License along with this program. If not, see
30+ <http://www.gnu.org/licenses/>.
31+"""
32
33=== added directory 'surveilr/api'
34=== added file 'surveilr/api/__init__.py'
35=== added file 'surveilr/api/server.py'
36--- surveilr/api/server.py 1970-01-01 00:00:00 +0000
37+++ surveilr/api/server.py 2011-11-19 22:08:22 +0000
38@@ -0,0 +1,134 @@
39+#!/usr/bin/python
40+"""
41+ Surveilr - Log aggregation, analysis and visualisation
42+
43+ Copyright (C) 2011 Linux2Go
44+
45+ This program is free software: you can redistribute it and/or
46+ modify it under the terms of the GNU Affero General Public License
47+ as published by the Free Software Foundation, either version 3 of
48+ the License, or (at your option) any later version.
49+
50+ This program is distributed in the hope that it will be useful,
51+ but WITHOUT ANY WARRANTY; without even the implied warranty of
52+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
53+ GNU Affero General Public License for more details.
54+
55+ You should have received a copy of the GNU Affero General Public
56+ License along with this program. If not, see
57+ <http://www.gnu.org/licenses/>.
58+
59+ Log collection server implementation
60+"""
61+
62+import eventlet
63+import eventlet.wsgi
64+import json
65+import time
66+
67+import riakalchemy
68+from routes import Mapper
69+from routes.util import URLGenerator
70+
71+from webob import exc
72+from webob import Response
73+from webob.dec import wsgify
74+from webob.exc import HTTPNotFound
75+
76+from surveilr import models
77+from surveilr import utils
78+
79+class ServiceController(object):
80+ """Routes style controller for actions related to services"""
81+
82+ def create(self, req):
83+ """Called for POST requests to /services
84+
85+ Creates the service, returns a JSON object with the ID assigned
86+ to the service"""
87+ data = json.loads(req.body)
88+ service = models.Service(**data)
89+ service.save()
90+ response = {'id':service.key}
91+ return Response(json.dumps(response))
92+
93+ def show(self, req, id):
94+ """Called for GET requests to /services/{id}
95+
96+ Returns information for the given service"""
97+ try:
98+ service = models.Service.get(id)
99+ return Response({'id': service.key})
100+ except riakalchemy.NoSuchObjectError:
101+ return HTTPNotFound()
102+
103+ def delete(self, req, id):
104+ """Called for DELETE requests to /services/{id}
105+
106+ Delete the given service"""
107+ models.Service.get(id).delete()
108+ return Response('')
109+
110+
111+class MetricController(object):
112+ """Routes style controller for actions related to log entries"""
113+ def create(self, req, service_name):
114+ """Called for POST requests to /services/{id}/metrics
115+
116+ Logs a measurement against the service identified by {id}.
117+ Returns an empty response"""
118+ data = json.loads(req.body)
119+ service = models.Service.get(service_name)
120+ data['service'] = [service]
121+ data['timestamp'] = utils.truncate(time.time(), 60)
122+ models.LogEntry(**data).save()
123+ return Response('')
124+
125+ def index(self, req, service_name):
126+ """Called for GET requests to /services/{id}/metrics
127+
128+ Returns a list of metrics logged against the service identified
129+ by {id}."""
130+ service = models.Service.get(service_name)
131+ retval = []
132+ for x in models.LogEntry.get(service=service).all():
133+ retval += [{'metrics': x.metrics, 'timestamp': x.timestamp}]
134+ return Response(json.dumps(retval))
135+
136+class SurveilrApplication(object):
137+ """The core Surveilr Monitoring WSGI application"""
138+ controllers = {}
139+
140+ map = Mapper()
141+ map.resource("metric", "metrics", controller='MetricController',
142+ path_prefix='/services/{service_name}')
143+ map.resource("service", "services", controller='ServiceController')
144+
145+ @wsgify
146+ def __call__(self, req):
147+ """Where it all happens
148+
149+ Using the Mapper object, it finds the relevant controller
150+ based on the URL and delegates the call to that."""
151+ results = self.map.routematch(environ=req.environ)
152+ if not results:
153+ return exc.HTTPNotFound()
154+ match, route = results
155+ link = URLGenerator(self.map, req.environ)
156+ req.urlvars = ((), match)
157+ kwargs = match.copy()
158+ controller = globals()[kwargs.pop('controller')]()
159+ method = kwargs.pop('action')
160+ req.link = link
161+ req.route_name = route.name
162+
163+ return getattr(controller, method)(req, **kwargs)
164+
165+application = SurveilrApplication()
166+
167+def main():
168+ socket = eventlet.listen(('', 9877))
169+ eventlet.wsgi.server(socket, application)
170+
171+if __name__ == '__main__': # pragma: nocover
172+ main()
173
174=== added file 'surveilr/models.py'
175--- surveilr/models.py 1970-01-01 00:00:00 +0000
176+++ surveilr/models.py 2011-11-19 22:08:22 +0000
177@@ -0,0 +1,54 @@
178+"""
179+ Surveilr - Log aggregation, analysis and visualisation
180+
181+ Copyright (C) 2011 Linux2Go
182+
183+ This program is free software: you can redistribute it and/or
184+ modify it under the terms of the GNU Affero General Public License
185+ as published by the Free Software Foundation, either version 3 of
186+ the License, or (at your option) any later version.
187+
188+ This program is distributed in the hope that it will be useful,
189+ but WITHOUT ANY WARRANTY; without even the implied warranty of
190+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
191+ GNU Affero General Public License for more details.
192+
193+ You should have received a copy of the GNU Affero General Public
194+ License along with this program. If not, see
195+ <http://www.gnu.org/licenses/>.
196+
197+ Model definitions
198+"""
199+from riakalchemy import RiakObject
200+from riakalchemy.types import Integer, String, Dict, RelatedObjects
201+
202+class Service(RiakObject):
203+ """A service that is referenced by many LogEntry's
204+
205+ If the same logical service gets information from multiple sources,
206+ it needs separate Service objects. However, if several metrics are
207+ provided for a single service in each log entry, only one Service
208+ is needed"""
209+ bucket_name = 'services'
210+ searchable = True
211+
212+ name = String()
213+ most_recent_log_entry = RelatedObjects()
214+
215+class LogEntry(RiakObject):
216+ """A log entry holding one or more metrics
217+
218+ A log entry "belongs" to a single Service. Each LogEntry
219+ can hold multiple metrics, but all LogEntry's must
220+ have the same set of metrics."""
221+ bucket_name = 'log_entries'
222+
223+ timestamp = Integer()
224+ metrics = Dict()
225+ service = RelatedObjects(backref=True)
226+
227+ def post_save(self):
228+ super(LogEntry, self).post_save()
229+ service = self.service[0]
230+ service.most_recent_log_entry = [self]
231+ service.save()
232
233=== added directory 'surveilr/tests'
234=== added file 'surveilr/tests/test_api_server.py'
235--- surveilr/tests/test_api_server.py 1970-01-01 00:00:00 +0000
236+++ surveilr/tests/test_api_server.py 2011-11-19 22:08:22 +0000
237@@ -0,0 +1,90 @@
238+"""
239+ Surveilr - Log aggregation, analysis and visualisation
240+
241+ Copyright (C) 2011 Linux2Go
242+
243+ This program is free software: you can redistribute it and/or
244+ modify it under the terms of the GNU Affero General Public License
245+ as published by the Free Software Foundation, either version 3 of
246+ the License, or (at your option) any later version.
247+
248+ This program is distributed in the hope that it will be useful,
249+ but WITHOUT ANY WARRANTY; without even the implied warranty of
250+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
251+ GNU Affero General Public License for more details.
252+
253+ You should have received a copy of the GNU Affero General Public
254+ License along with this program. If not, see
255+ <http://www.gnu.org/licenses/>.
256+
257+ Tests for API server
258+"""
259+
260+import json
261+import mock
262+import unittest
263+from webob import Request
264+
265+from surveilr.api import server
266+from surveilr.api.server import application
267+from surveilr.api.server import main as server_main
268+
269+class APIServerTests(unittest.TestCase):
270+ def setUp(self):
271+ import riakalchemy
272+ riakalchemy.connect()
273+
274+ def test_create_retrieve_service(self):
275+ """Create, retrieve, delete, attempt to retrieve again"""
276+ req = Request.blank('/services',
277+ method='POST',
278+ POST=json.dumps({'name': 'this_or_the_other'}))
279+ resp = application(req)
280+ self.assertEquals(resp.status_int, 200)
281+
282+ service_id = json.loads(resp.body)['id']
283+
284+ req = Request.blank('/services/%s' % service_id)
285+ resp = application(req)
286+ self.assertEquals(resp.status_int, 200)
287+
288+ req = Request.blank('/services/%s' % service_id, method='DELETE')
289+ resp = application(req)
290+ self.assertEquals(resp.status_int, 200)
291+
292+ req = Request.blank('/services/%s' % service_id)
293+ resp = application(req)
294+ self.assertEquals(resp.status_int, 404)
295+
296+ def test_create_retrieve_metric(self):
297+ req = Request.blank('/services', method='POST', POST='{"name": "this_or_the_other"}')
298+ resp = application(req)
299+ self.assertEquals(resp.status_int, 200)
300+
301+ service_id = json.loads(resp.body)['id']
302+ req = Request.blank('/services/%s/metrics' % service_id,
303+ method='POST',
304+ POST=json.dumps({
305+ 'timestamp': 13217362355575,
306+ 'metrics': {'duration': 85000,
307+ 'response_size': 12435}}))
308+ resp = application(req)
309+ self.assertEquals(resp.status_int, 200)
310+
311+ req = Request.blank('/services/%s/metrics' % service_id)
312+ resp = application(req)
313+
314+ def test_invalid_url(self):
315+ req = Request.blank('/stuff')
316+ resp = application(req)
317+ self.assertEquals(resp.status_int, 404)
318+
319+ def test_main(self):
320+ with mock.patch('surveilr.api.server.eventlet',
321+ spec=['listen', 'wsgi']) as eventlet:
322+ socket_sentinel = mock.sentinel.return_value
323+ eventlet.listen.return_value = socket_sentinel
324+ server.main()
325+
326+ eventlet.listen.assert_called_with(('', 9877))
327+ eventlet.wsgi.server.assert_called_with(socket_sentinel, application)
328
329=== added file 'surveilr/utils.py'
330--- surveilr/utils.py 1970-01-01 00:00:00 +0000
331+++ surveilr/utils.py 2011-11-19 22:08:22 +0000
332@@ -0,0 +1,35 @@
333+"""
334+ Surveilr - Log aggregation, analysis and visualisation
335+
336+ Copyright (C) 2011 Linux2Go
337+
338+ This program is free software: you can redistribute it and/or
339+ modify it under the terms of the GNU Affero General Public License
340+ as published by the Free Software Foundation, either version 3 of
341+ the License, or (at your option) any later version.
342+
343+ This program is distributed in the hope that it will be useful,
344+ but WITHOUT ANY WARRANTY; without even the implied warranty of
345+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
346+ GNU Affero General Public License for more details.
347+
348+ You should have received a copy of the GNU Affero General Public
349+ License along with this program. If not, see
350+ <http://www.gnu.org/licenses/>.
351+
352+ Utility functions
353+"""
354+
355+def truncate(number, rounding_factor):
356+ """Truncate to nearest arbitrary multiple
357+
358+ >>> truncate(1000, 100)
359+ 1000
360+ >>> truncate(1099, 100)
361+ 1000
362+ >>> truncate(1099, 50)
363+ 1050
364+ >>> truncate(1099, 50)
365+ 1050
366+ """
367+ return (int(number) / int(rounding_factor)) * int(rounding_factor)

Subscribers

People subscribed via source and target branches

to all changes: