blob: f2c69e1ffa8c01e1e1a7ed07188659b55c766507 [file] [log] [blame]
srikanth116e6e82014-08-19 07:22:37 -07001#
2# Copyright (c) 2013 Big Switch Networks, Inc.
3#
4# Licensed under the Eclipse Public License, Version 1.0 (the
5# "License"); you may not use this file except in compliance with the
6# License. You may obtain a copy of the License at
7#
8# http://www.eclipse.org/legal/epl-v10.html
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS,
12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
13# implied. See the License for the specific language governing
14# permissions and limitations under the License.
15#
16
17from django.test import TestCase, Client
18import urllib
19from django.utils import simplejson
20from .data import set_use_test_keyspace, flush_stats_db
21import time
22from sdncon.controller.config import get_local_controller_id
23
24def setUpModule():
25 set_use_test_keyspace()
26
27def test_construct_rest_url(path, query_params=None):
28 url = '/rest/%s' % path
29 if query_params:
30 url += '?'
31 url += urllib.urlencode(query_params)
32 return url
33
34def test_get_rest_data(path, query_params=None):
35 url = test_construct_rest_url(path, query_params)
36 c = Client()
37 response = c.get(url)
38 return response
39
40def test_put_rest_data(obj, path, query_params=None):
41 url = test_construct_rest_url(path, query_params)
42 data = simplejson.dumps(obj)
43 c = Client()
44 response = c.put(url , data, 'application/json')
45 return response
46
47def test_delete_rest_data(path, query_params=None):
48 url = test_construct_rest_url(path, query_params)
49 c = Client()
50 response = c.delete(url)
51 return response
52
53BASE_TIME = 1297278987000
54SECONDS_CONVERT = 1000
55MINUTES_CONVERT = 60 * SECONDS_CONVERT
56HOURS_CONVERT = 60 * MINUTES_CONVERT
57DAYS_CONVERT = 24 * HOURS_CONVERT
58
59def make_timestamp(day, hour=0, minute=0, second=0, milliseconds=0):
60 timestamp = BASE_TIME + (day * DAYS_CONVERT) + (hour * HOURS_CONVERT) + \
61 (minute * MINUTES_CONVERT) + (second * SECONDS_CONVERT) + milliseconds
62 return timestamp
63
64class StatsTestCase(TestCase):
65 def tearDown(self):
66 flush_stats_db()
67
68class BasicStatsTest(StatsTestCase):
69
70 STATS_DATA = {
71 'controller-stats': {
72 '192.168.1.1': {
73 'cpu-system': [
74 {'timestamp':make_timestamp(1,0),'value':1},
75 {'timestamp':make_timestamp(1,1),'value':2},
76 {'timestamp':make_timestamp(1,2),'value':3},
77 {'timestamp':make_timestamp(1,3),'value':4},
78 {'timestamp':make_timestamp(1,4),'value':5},
79 {'timestamp':make_timestamp(2,1),'value':6},
80 {'timestamp':make_timestamp(2,2),'value':7},
81 {'timestamp':make_timestamp(3,5),'value':8},
82 {'timestamp':make_timestamp(3,8),'value':9},
83 {'timestamp':make_timestamp(4,10),'value':10},
84 {'timestamp':make_timestamp(4,11),'value':11},
85 {'timestamp':make_timestamp(10,11),'value':12},
86 ],
87 'cpu-idle': [
88 {'timestamp':make_timestamp(1,1),'value':80},
89 {'timestamp':make_timestamp(1,2),'value':83},
90 {'timestamp':make_timestamp(1,3),'value':82},
91 {'timestamp':make_timestamp(1,4),'value':79},
92 {'timestamp':make_timestamp(1,5),'value':85},
93 ]
94 }
95 },
96 'switch-stats': {
97 '00:01:02:03:04:05': {
98 'flow-count': [
99 {'timestamp':make_timestamp(1,0),'value':60},
100 {'timestamp':make_timestamp(1,1),'value':88},
101 {'timestamp':make_timestamp(1,2),'value':102},
102 ],
103 'packet-count': [
104 {'timestamp':make_timestamp(1,0),'value':100},
105 {'timestamp':make_timestamp(1,1),'value':120},
106 {'timestamp':make_timestamp(1,2),'value':160},
107 ],
108 'packet-count__arp': [
109 {'timestamp':make_timestamp(1,0),'value':20},
110 {'timestamp':make_timestamp(1,3),'value':25},
111 ],
112 'packet-count__lldp': [
113 {'timestamp':make_timestamp(1,0),'value':30},
114 {'timestamp':make_timestamp(1,4),'value':15},
115 ]
116 }
117 }
118 }
119
120 def check_stats_results(self, returned_results, expected_results, message=None):
121 self.assertEqual(len(returned_results), len(expected_results), message)
122 for i in range(len(returned_results)):
123 expected_timestamp = expected_results[i]['timestamp']
124 returned_timestamp = returned_results[i][0]
125 expected_value = expected_results[i]['value']
126 returned_value = returned_results[i][1]
127 self.assertEqual(expected_timestamp, returned_timestamp, message)
128 self.assertEqual(expected_value, returned_value, message)
129
130 def setUp(self):
131 response = test_put_rest_data(self.STATS_DATA, 'v1/stats/data/local')
132 self.assertEqual(response.status_code, 200)
133
134 def test_get_stats(self):
135 # Get all of the cpu-system stat data
136 response = test_get_rest_data('v1/stats/data/local/controller/192.168.1.1/cpu-system', {'start-time':make_timestamp(1,0),'end-time':make_timestamp(10,11), 'sample-interval':0})
137 self.assertEqual(response.status_code, 200)
138 results = simplejson.loads(response.content)
139 self.check_stats_results(results, self.STATS_DATA['controller-stats']['192.168.1.1']['cpu-system'])
140
141 # Get just one days data of the cpu-system stat
142 response = test_get_rest_data('v1/stats/data/local/controller/192.168.1.1/cpu-system', {'start-time':make_timestamp(1,0),'end-time':make_timestamp(1,4), 'sample-interval':0})
143 self.assertEqual(response.status_code, 200)
144 results = simplejson.loads(response.content)
145 self.check_stats_results(results, self.STATS_DATA['controller-stats']['192.168.1.1']['cpu-system'][:5])
146
147 # Get two day range for cpu-system
148 response = test_get_rest_data('v1/stats/data/local/controller/192.168.1.1/cpu-system', {'start-time':make_timestamp(1,2,10),'end-time':make_timestamp(2,2,20), 'sample-interval':0})
149 self.assertEqual(response.status_code, 200)
150 results = simplejson.loads(response.content)
151 self.check_stats_results(results, self.STATS_DATA['controller-stats']['192.168.1.1']['cpu-system'][3:7])
152
153 # Get all of the flow-count switch stat data
154 response = test_get_rest_data('v1/stats/data/local/switch/00:01:02:03:04:05/flow-count', {'start-time':make_timestamp(1,0),'end-time':make_timestamp(2,0), 'sample-interval':0})
155 self.assertEqual(response.status_code, 200)
156 results = simplejson.loads(response.content)
157 self.check_stats_results(results, self.STATS_DATA['switch-stats']['00:01:02:03:04:05']['flow-count'])
158
159 # Get part of the packet-count switch stat data
160 response = test_get_rest_data('v1/stats/data/local/switch/00:01:02:03:04:05/packet-count', {'start-time':make_timestamp(1,0),'end-time':make_timestamp(1,1), 'sample-interval':0})
161 self.assertEqual(response.status_code, 200)
162 results = simplejson.loads(response.content)
163 self.check_stats_results(results, self.STATS_DATA['switch-stats']['00:01:02:03:04:05']['packet-count'][:2])
164
165 def test_delete_stats(self):
166 # Delete all but the first 2 and last 2 sample points
167 response = test_delete_rest_data('v1/stats/data/local/controller/192.168.1.1/cpu-system', {
168 'start-time': self.STATS_DATA['controller-stats']['192.168.1.1']['cpu-system'][2]['timestamp'],
169 'end-time':self.STATS_DATA['controller-stats']['192.168.1.1']['cpu-system'][-3]['timestamp']})
170 self.assertEquals(response.status_code, 200)
171
172 response = test_get_rest_data('v1/stats/data/local/controller/192.168.1.1/cpu-system', {'start-time':make_timestamp(1,0),'end-time':make_timestamp(10,11), 'sample-interval':0})
173 self.assertEqual(response.status_code, 200)
174 results = simplejson.loads(response.content)
175 self.check_stats_results(results, self.STATS_DATA['controller-stats']['192.168.1.1']['cpu-system'][:2] + self.STATS_DATA['controller-stats']['192.168.1.1']['cpu-system'][-2:])
176
177 def test_stats_target_types(self):
178
179 local_controller_id = get_local_controller_id()
180
181 # Check getting the list of all target types
182 response = test_get_rest_data('v1/stats/target/local/')
183 self.assertEqual(response.status_code, 200)
184 results = simplejson.loads(response.content)
185 self.assertEqual(len(results.keys()), 2)
186 self.assertTrue('controller' in results)
187 self.assertTrue('switch' in results)
188
189 # Check getting the info for the controller target type
190 response = test_get_rest_data('v1/stats/target/local/controller')
191 self.assertEqual(response.status_code, 200)
192 results = simplejson.loads(response.content)
193 self.assertEqual(len(results.keys()), 1)
194 self.assertTrue('192.168.1.1' in results)
195 controller_info = results['192.168.1.1']
196 #self.assertEqual(controller_info['controller'], local_controller_id)
197 self.assertEqual(controller_info['last-updated'], make_timestamp(10,11))
198
199 # Check getting the info for the switch target type
200 response = test_get_rest_data('v1/stats/target/local/switch')
201 self.assertEqual(response.status_code, 200)
202 results = simplejson.loads(response.content)
203 self.assertEqual(len(results.keys()), 1)
204 self.assertTrue('00:01:02:03:04:05' in results)
205 switch_info = results['00:01:02:03:04:05']
206 self.assertEqual(switch_info['controller'], local_controller_id)
207 self.assertEqual(switch_info['last-updated'], make_timestamp(1,4))
208
209 def check_stats_type_attributes(self, attributes, expected_last_updated,
210 expected_target_type):
211 last_updated = attributes.get('last-updated')
212 self.assertEqual(last_updated, expected_last_updated)
213 target_type = attributes.get('target-type')
214 self.assertEqual(target_type, expected_target_type)
215
216 def test_stats_type_index(self):
217 response = test_get_rest_data('v1/stats/index/local/controller/192.168.1.1')
218 self.assertEqual(response.status_code, 200)
219 results = simplejson.loads(response.content)
220 self.assertEqual(len(results), 2)
221 attributes = results['cpu-system']
222 self.assertEqual(len(attributes), 1)
223 self.assertEqual(attributes['last-updated'], make_timestamp(10,11))
224 attributes = results['cpu-idle']
225 self.assertEqual(len(attributes), 1)
226 self.assertEqual(attributes['last-updated'], make_timestamp(1,5))
227
228 response = test_get_rest_data('v1/stats/index/local/switch/00:01:02:03:04:05')
229 self.assertEqual(response.status_code, 200)
230 results = simplejson.loads(response.content)
231 self.assertEqual(len(results), 2)
232 attributes = results['flow-count']
233 self.assertEqual(len(attributes), 1)
234 self.assertEqual(attributes['last-updated'], make_timestamp(1,2))
235 attributes = results['packet-count']
236 self.assertEqual(len(attributes), 2)
237 self.assertEqual(attributes['last-updated'], make_timestamp(1,2))
238 parameters = attributes['parameters']
239 self.assertEqual(len(parameters), 2)
240 attributes = parameters['arp']
241 self.assertEqual(len(attributes), 1)
242 self.assertEqual(attributes['last-updated'], make_timestamp(1,3))
243 attributes = parameters['lldp']
244 self.assertEqual(len(attributes), 1)
245 self.assertEqual(attributes['last-updated'], make_timestamp(1,4))
246
247 response = test_get_rest_data('v1/stats/index/local/controller/192.168.1.1/cpu-system')
248 self.assertEqual(response.status_code, 200)
249 attributes = simplejson.loads(response.content)
250 self.assertEqual(len(attributes), 1)
251 self.assertEqual(attributes['last-updated'], make_timestamp(10,11))
252
253class StatsMetadataTest(StatsTestCase):
254
255 def check_metadata(self, stats_type, stats_metadata):
256 # The name in the metadata should match the stats_type
257 name = stats_metadata.get('name')
258 self.assertEqual(stats_type, name)
259
260 # The target_type is a required value, so it should be present in the metadata
261 target_type = stats_metadata.get('target_type')
262 self.assertNotEqual(target_type, None)
263
264 # NOTE: The following assertion works for now, since we only support these
265 # two target types. Eventually we may support other target types (in which
266 # case we'd need to add the new ones to the tuple) or else custom target
267 # types can be added (in which case we'd maybe want to remove this assertion.
268 self.assertTrue(target_type in ('controller','switch'))
269
270 # verbose_name is set automatically by the REST code if it isn't set
271 # explicitly in the metadata, so it should always be present.
272 verbose_name = stats_metadata.get('verbose_name')
273 self.assertNotEqual(verbose_name, None)
274
275 def test_stats_metadata(self):
276 response = test_get_rest_data('v1/stats/metadata/default')
277 self.assertEqual(response.status_code, 200)
278 stats_metadata_dict = simplejson.loads(response.content)
279 self.assertEqual(type(stats_metadata_dict), dict)
280 for stats_type, stats_metadata in stats_metadata_dict.items():
281 # Check that the metadata looks reasonable
282 self.check_metadata(stats_type, stats_metadata)
283
284 # Fetch the metadata for the individual stats type and check that it matches
285 response2 = test_get_rest_data('v1/stats/metadata/default/' + stats_type)
286 self.assertEqual(response2.status_code, 200)
287 stats_metadata2 = simplejson.loads(response2.content)
288 self.assertEqual(stats_metadata, stats_metadata2)
289
290 def test_invalid_stats_type(self):
291 # Try getting a stats type that doesn't exist
292 response = test_get_rest_data('v1/stats/metadata/default/foobar')
293 self.assertEqual(response.status_code, 404)
294 error_result = simplejson.loads(response.content)
295 self.assertEqual(error_result.get('error_type'), 'RestResourceNotFoundException')
296
297class LatestStatTest(StatsTestCase):
298
299 def do_latest_stat(self, target_type, target_id):
300 current_timestamp = int(time.time() * 1000)
301 for i in range(23,-1,-1):
302 # Try with different offsets. Sort of arbitrary list here. Potentially add
303 # new offsets to test specific edge cases
304 offset_list = [0, 3599999, 100, 30000, 400000, 3000000]
305 timestamp = current_timestamp - (i * 3600000) - offset_list[(i+1)%len(offset_list)]
306 stats_data = {target_type + '-stats': {target_id: {'test-stat': [{'timestamp': timestamp, 'value': i}]}}}
307 response = test_put_rest_data(stats_data, 'v1/stats/data/local')
308 self.assertEqual(response.status_code, 200)
309 response = test_get_rest_data('v1/stats/data/local/%s/%s/test-stat' % (target_type, target_id))
310 self.assertEqual(response.status_code, 200)
311 results = simplejson.loads(response.content)
312 self.assertEqual(timestamp, results[0])
313 self.assertEqual(i, results[1])
314
315 def test_controller_latest_stat(self):
316 self.do_latest_stat('controller', '192.168.1.1')
317
318 def test_switch_latest_stat(self):
319 self.do_latest_stat('switch', '00:01:02:03:04:05')
320
321class BasicEventsTest(StatsTestCase):
322
323 EVENTS_DATA = {
324 '192.168.1.1': [
325 {'timestamp': make_timestamp(1,0), 'component': 'sdnplatform', 'log-level': 'Error', 'message': 'Something bad happened'},
326 {'timestamp': make_timestamp(1,1), 'component': 'sdnplatform', 'log-level': 'Info', 'message': 'Something else happened', 'package': 'net.sdnplatformcontroller.core'},
327 {'timestamp': make_timestamp(1,4), 'component': 'sdnplatform', 'log-level': 'Info', 'message': 'Switch connected: 01:02:03:04:45:56', 'package': 'net.sdnplatformcontroller.core', 'dpid': '01:02:03:04:45:56'},
328 {'timestamp': make_timestamp(2,4), 'component': 'django', 'log-level': 'Info', 'message': 'GET: /rest/v1/model/foo'},
329 {'timestamp': make_timestamp(4,10), 'component': 'cassandra', 'log-level': 'Info', 'message': 'Compaction occurred'},
330 {'timestamp': make_timestamp(4,11), 'component': 'cassandra', 'log-level': 'Info', 'message': 'One more compaction occurred'},
331 {'timestamp': make_timestamp(7,10), 'component': 'cassandra', 'log-level': 'Info', 'message': 'Another compaction occurred'},
332 ]
333 }
334
335 TAGGED_EVENTS_DATA = {
336 '192.168.1.1': [
337 {'timestamp': make_timestamp(10,0), 'pk-tag':'1234', 'component': 'sdnplatform', 'log-level': 'Error', 'message': 'Something bad happened'},
338 {'timestamp': make_timestamp(10,1), 'pk-tag':'5678', 'component': 'sdnplatform', 'log-level': 'Info', 'message': 'Something else happened', 'package': 'net.sdnplatformcontroller.core'},
339 ]
340 }
341
342
343 def check_events_results(self, returned_results, expected_results, message=None):
344 self.assertEqual(expected_results, returned_results, message)
345 #self.assertEqual(len(returned_results), len(expected_results), message)
346 #for i in range(len(returned_results)):
347 # self.assertEqual(returned_results[i], expected_results[i])
348
349 def test_events(self):
350 response = test_put_rest_data(self.EVENTS_DATA, 'v1/events/data/default')
351 self.assertEqual(response.status_code, 200)
352
353 # Get all of the data
354 response = test_get_rest_data('v1/events/data/default/192.168.1.1', {'start-time':make_timestamp(1,0),'end-time':make_timestamp(7,10)})
355 self.assertEqual(response.status_code, 200)
356 results = simplejson.loads(response.content)
357 self.check_events_results(results, self.EVENTS_DATA['192.168.1.1'])
358
359 # Get just one days data
360 response = test_get_rest_data('v1/events/data/default/192.168.1.1', {'start-time':make_timestamp(1,0),'end-time':make_timestamp(1,4)})
361 self.assertEqual(response.status_code, 200)
362 results = simplejson.loads(response.content)
363 self.check_events_results(results, self.EVENTS_DATA['192.168.1.1'][:3])
364
365 # Get two day range
366 response = test_get_rest_data('v1/events/data/default/192.168.1.1', {'start-time':make_timestamp(1,2),'end-time':make_timestamp(4,11)})
367 self.assertEqual(response.status_code, 200)
368 results = simplejson.loads(response.content)
369 self.check_events_results(results, self.EVENTS_DATA['192.168.1.1'][2:6])
370
371 def test_tagged_events(self):
372 response = test_put_rest_data(self.TAGGED_EVENTS_DATA, 'v1/events/data/default')
373 self.assertEqual(response.status_code, 200)
374
375 response = test_get_rest_data('v1/events/data/default/192.168.1.1', {'start-time':make_timestamp(10,0),'end-time':make_timestamp(10,2),'include-pk-tag':'true'})
376 self.assertEqual(response.status_code, 200)
377 results = simplejson.loads(response.content)
378 self.check_events_results(results, self.TAGGED_EVENTS_DATA['192.168.1.1'])
379
380 def test_delete_events(self):
381 response = test_put_rest_data(self.EVENTS_DATA, 'v1/events/data/default')
382 self.assertEqual(response.status_code, 200)
383
384 # Delete all but the first 2 and last 2 events
385 response = test_delete_rest_data('v1/events/data/default/192.168.1.1', {
386 'start-time': self.EVENTS_DATA['192.168.1.1'][2]['timestamp'],
387 'end-time':self.EVENTS_DATA['192.168.1.1'][-3]['timestamp']})
388 self.assertEquals(response.status_code, 200)
389
390 response = test_get_rest_data('v1/events/data/default/192.168.1.1', {'start-time':make_timestamp(1,0),'end-time':make_timestamp(7,10)})
391 self.assertEqual(response.status_code, 200)
392 results = simplejson.loads(response.content)
393 self.check_events_results(results, self.EVENTS_DATA['192.168.1.1'][:2] + self.EVENTS_DATA['192.168.1.1'][-2:])
394