srikanth | 116e6e8 | 2014-08-19 07:22:37 -0700 | [diff] [blame] | 1 | # |
| 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 | |
| 17 | from django.test import TestCase, Client |
| 18 | import urllib |
| 19 | from django.utils import simplejson |
| 20 | from .data import set_use_test_keyspace, flush_stats_db |
| 21 | import time |
| 22 | from sdncon.controller.config import get_local_controller_id |
| 23 | |
| 24 | def setUpModule(): |
| 25 | set_use_test_keyspace() |
| 26 | |
| 27 | def 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 | |
| 34 | def 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 | |
| 40 | def 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 | |
| 47 | def 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 | |
| 53 | BASE_TIME = 1297278987000 |
| 54 | SECONDS_CONVERT = 1000 |
| 55 | MINUTES_CONVERT = 60 * SECONDS_CONVERT |
| 56 | HOURS_CONVERT = 60 * MINUTES_CONVERT |
| 57 | DAYS_CONVERT = 24 * HOURS_CONVERT |
| 58 | |
| 59 | def 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 | |
| 64 | class StatsTestCase(TestCase): |
| 65 | def tearDown(self): |
| 66 | flush_stats_db() |
| 67 | |
| 68 | class 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 | |
| 253 | class 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 | |
| 297 | class 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 | |
| 321 | class 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 | |