Merge lp:~soren/surveilr/make-it-slightly-useful into lp:surveilr
- make-it-slightly-useful
- Merge into trunk
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 |
Related bugs: |
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
Linux2Go Jenkins (linux2go-jenkins) wrote : | # |
Soren Hansen (soren) wrote : | # |
Installed python-
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.
writing surveilr.
writing top-level names to surveilr.
writing dependency_links to surveilr.
writing manifest file 'surveilr.
reading manifest file 'surveilr.
writing manifest file 'surveilr.
running build_ext
ok
E.E
=======
ERROR: Failure: ImportError (No module named routes)
-------
Traceback (most recent call last):
File "/usr/lib/
addr.filename, addr.module)
File "/usr/lib/
return self.importFrom
File "/usr/lib/
mod = load_module(
File "/tmp/tmpXx8lxx
from routes import Mapper
ImportError: No module named routes
=======
ERROR: Failure: ImportError (No module named webob)
-------
Traceback (most recent call last):
File "/usr/lib/
addr.filename, addr.module)
File "/usr/lib/
return self.importFrom
File "/usr/lib/
mod = load_module(
File "/tmp/tmpXx8lxx
from webob import Request
ImportError: No module named webob
-------
Ran 3 tests in 0.811s
FAILED (errors=2)
Soren Hansen (soren) wrote : | # |
...and python-
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.
writing surveilr.
writing top-level names to surveilr.
writing dependency_links to surveilr.
writing manifest file 'surveilr.
reading manifest file 'surveilr.
writing manifest file 'surveilr.
running build_ext
ok
.EE..
=======
ERROR: test_create_
-------
Traceback (most recent call last):
File "/tmp/tmpt9Dlnm
resp = application(req)
File "/usr/lib/
return self.func(req, *args, **kw)
File "/tmp/tmpt9Dlnm
return getattr(controller, method)(req, **kwargs)
File "/tmp/tmpt9Dlnm
service.save()
File "/var/lib/
self._riak_obj = bucket.
File "/usr/lib/
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
resp = application(req)
File "/usr/lib/
return self.func(req, *args, **kw)
File "/tmp/tmpt9Dlnm
return getattr(controller, method)(req, **kwargs)
File "/tmp/tmpt9Dlnm
service.save()
File "/var/lib/
self._riak_obj = bucket.
File "/usr/lib/
raise TypeError('Unicode data values are not supported.')
TypeError: Unicode data values are not supported.
-------
Ran 5 tests in 1.128s
FAILED (errors=2)
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.
writing surveilr.
writing top-level names to surveilr.
writing dependency_links to surveilr.
writing manifest file 'surveilr.
reading manifest file 'surveilr.
writing manifest file 'surveilr.
running build_ext
ok
.EE..
=======
ERROR: test_create_
-------
Traceback (most recent call last):
File "/tmp/tmpfUlS7f
resp = application(req)
File "/usr/lib/
return self.func(req, *args, **kw)
File "/tmp/tmpfUlS7f
return getattr(controller, method)(req, **kwargs)
File "/tmp/tmpfUlS7f
service.save()
File "/var/lib/
self._riak_obj = bucket.
File "/usr/lib/
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
resp = application(req)
File "/usr/lib/
return self.func(req, *args, **kw)
File "/tmp/tmpfUlS7f
return getattr(controller, method)(req, **kwargs)
File "/tmp/tmpfUlS7f
service.save()
File "/var/lib/
self._riak_obj = bucket.
File "/usr/lib/
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
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) |
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 egg-info/ requires. txt egg-info/ PKG-INFO egg-info/ top_level. txt egg-info/ dependency_ links.txt egg-info/ SOURCES. txt' egg-info/ SOURCES. txt' egg-info/ SOURCES. txt'
running egg_info
creating surveilr.egg-info
writing requirements to surveilr.
writing surveilr.
writing top-level names to surveilr.
writing dependency_links to surveilr.
writing manifest file 'surveilr.
reading manifest file 'surveilr.
writing manifest file 'surveilr.
running build_ext
ok
E.E ======= ======= ======= ======= ======= ======= ======= ======= ======= ------- ------- ------- ------- ------- ------- ------- ------- ------- pymodules/ python2. 7/nose/ loader. py", line 390, in loadTestsFromName pymodules/ python2. 7/nose/ importer. py", line 39, in importFromPath Dir(dir_ path, fqname) pymodules/ python2. 7/nose/ importer. py", line 86, in importFromDir part_fqname, fh, filename, desc) /surveilr/ api/server. py", line 24, in <module>
=======
ERROR: Failure: ImportError (No module named eventlet)
-------
Traceback (most recent call last):
File "/usr/lib/
addr.filename, addr.module)
File "/usr/lib/
return self.importFrom
File "/usr/lib/
mod = load_module(
File "/tmp/tmpBkJp3S
import eventlet
ImportError: No module named eventlet
======= ======= ======= ======= ======= ======= ======= ======= ======= ======= ------- ------- ------- ------- ------- ------- ------- ------- ------- pymodules/ python2. 7/nose/ loader. py", line 390, in loadTestsFromName pymodules/ python2. 7/nose/ importer. py", line 39, in importFromPath Dir(dir_ path, fqname) pymodules/ python2. 7/nose/ importer. py", line 86, in importFromDir part_fqname, fh, filename, desc) /surveilr/ tests/test_ api_server. py", line 24, in <module>
ERROR: Failure: ImportError (No module named mock)
-------
Traceback (most recent call last):
File "/usr/lib/
addr.filename, addr.module)
File "/usr/lib/
return self.importFrom
File "/usr/lib/
mod = load_module(
File "/tmp/tmpBkJp3S
import mock
ImportError: No module named mock
------- ------- ------- ------- ------- ------- ------- ------- ------- -------
Ran 3 tests in 0.418s
FAILED (errors=2)