blob: 5172cc1dd533ca89d21aa32abda5a39e441e1c3d [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 cassandra.ttypes import *
18from django.conf import settings
19from django.http import HttpResponse
20from django.utils import simplejson
21from functools import wraps
22import time
23from .data import StatsException, StatsInvalidStatsDataException, \
24 StatsInvalidStatsTypeException, \
25 get_stats_db_connection, init_stats_db_connection, \
26 get_stats_metadata, get_stats_type_index, \
27 get_stats_target_types, get_stats_targets, delete_stats_data, \
28 get_stats_data, get_latest_stat_data, put_stats_data, \
29 get_closest_sample_interval, get_closest_window_interval, \
30 get_log_event_data, put_log_event_data, delete_log_event_data, \
31 VALUE_DATA_FORMAT
32
33from sdncon.rest.views import RestException, \
34 RestInvalidPutDataException, RestMissingRequiredQueryParamException,\
35 RestInvalidMethodException, RestDatabaseConnectionException,\
36 RestInternalException, RestResourceNotFoundException, \
37 safe_rest_view, JSON_CONTENT_TYPE, get_successful_response
38from sdncon.controller.config import get_local_controller_id
39
40
41class RestStatsException(RestException):
42 def __init__(self, stats_exception):
43 super(RestStatsException,self).__init__('Error accessing stats: ' + str(stats_exception))
44
45class RestStatsInvalidTimeDurationUnitsException(RestException):
46 def __init__(self, units):
47 super(RestStatsInvalidTimeDurationUnitsException,self).__init__('Invalid time duration units: ' + str(units))
48
49
50class RestStatsInvalidTimeRangeException(RestException):
51 def __init__(self):
52 super(RestStatsInvalidTimeRangeException,self).__init__('Invalid time range specified in stats REST API. '
53 '2 out of 3 of start-time, end-time, and duration params must be specified.')
54
55
56@safe_rest_view
57def safe_stats_rest_view(func, *args, **kwargs):
58 try:
59 request = args[0]
60 response = func(*args, **kwargs)
61 except RestException:
62 raise
63 except StatsInvalidStatsDataException:
64 raise RestInvalidPutDataException()
65 except StatsInvalidStatsTypeException:
66 raise RestResourceNotFoundException(request.path)
67 except StatsException, e:
68 raise RestStatsException(e)
69 except Exception, e:
70 raise RestInternalException(e)
71 return response
72
73
74def safe_stats_view(func):
75 """
76 This is a decorator that takes care of exception handling for the
77 stats views so that stats exceptions are converted to the appropriate
78 REST exception.
79 """
80 @wraps(func)
81 def _func(*args, **kwargs):
82 response = safe_stats_rest_view(func, *args, **kwargs)
83 return response
84
85 return _func
86
87
88def init_db_connection():
89 db_connection = get_stats_db_connection()
90 if not db_connection:
91 try:
92 stats_db_settings = settings.STATS_DATABASE
93 except Exception:
94 stats_db_settings = {}
95
96 host = stats_db_settings.get('HOST', 'localhost')
97 port = stats_db_settings.get('PORT', 9160)
98 keyspace = stats_db_settings.get('NAME', 'sdnstats')
99 user = stats_db_settings.get('USER')
100 password = stats_db_settings.get('PASSWORD')
101 replication_factor = stats_db_settings.get('CASSANDRA_REPLICATION_FACTOR', 1)
102 column_family_def_default_settings = stats_db_settings.get('CASSANDRA_COLUMN_FAMILY_DEF_DEFAULT_SETTINGS', {})
103
104 init_stats_db_connection(host, port, keyspace, user, password, replication_factor, column_family_def_default_settings)
105
106 db_connection = get_stats_db_connection()
107 assert(db_connection is not None)
108
109START_TIME_QUERY_PARAM = 'start-time'
110END_TIME_QUERY_PARAM = 'end-time'
111DURATION_QUERY_PARAM = 'duration'
112SAMPLE_INTERVAL_QUERY_PARAM = 'sample-interval'
113SAMPLE_COUNT_QUERY_PARAM = 'sample-count'
114SAMPLE_WINDOW_QUERY_PARAM = 'sample-window'
115DATA_FORMAT_QUERY_PARAM = 'data-format'
116LIMIT_QUERY_PARAM = 'limit'
117INCLUDE_PK_TAG_QUERY_PARAM = 'include-pk-tag'
118
119DEFAULT_SAMPLE_COUNT = 50
120
121def convert_time_point(time_point):
122
123 if time_point is None:
124 return None
125
126 if time_point:
127 time_point = time_point.lower()
128 if time_point in ('now', 'current'):
129 time_point = int(time.time() * 1000)
130 else:
131 time_point = int(time_point)
132
133 return time_point
134
135
136UNIT_CONVERSIONS = (
137 (('h', 'hour', 'hours'), 3600000),
138 (('d', 'day', 'days'), 86400000),
139 (('w', 'week', 'weeks'), 604800000),
140 (('m', 'min', 'mins', 'minute', 'minutes'), 60000),
141 (('s', 'sec', 'secs', 'second', 'seconds'), 1000),
142 (('ms', 'millisecond', 'milliseconds'), 1)
143)
144
145def convert_time_duration(duration):
146
147 if duration is None:
148 return None
149
150 value = ""
151 for c in duration:
152 if not c.isdigit():
153 break
154 value += c
155
156 units = duration[len(value):].lower()
157 value = int(value)
158
159 if units:
160 converted_value = None
161 for conversion in UNIT_CONVERSIONS:
162 if units in conversion[0]:
163 converted_value = value * conversion[1]
164 break
165 if converted_value is None:
166 raise RestStatsInvalidTimeDurationUnitsException(units)
167
168 value = converted_value
169
170 return value
171
172
173def get_time_range(start_time, end_time, duration):
174
175 if not start_time and not end_time and not duration:
176 return (None, None)
177
178 start_time = convert_time_point(start_time)
179 end_time = convert_time_point(end_time)
180 duration = convert_time_duration(duration)
181
182 if start_time:
183 if not end_time and duration:
184 end_time = start_time + duration
185 elif end_time and duration:
186 start_time = end_time - duration
187
188 if not start_time or not end_time:
189 raise RestStatsInvalidTimeRangeException()
190
191 return (start_time, end_time)
192
193
194def get_time_range_from_request(request):
195 start_time = request.GET.get(START_TIME_QUERY_PARAM)
196 end_time = request.GET.get(END_TIME_QUERY_PARAM)
197 duration = request.GET.get(DURATION_QUERY_PARAM)
198
199 return get_time_range(start_time, end_time, duration)
200
201#def get_stats_time_range(request):
202# start_time = request.GET.get(START_TIME_QUERY_PARAM)
203# end_time = request.GET.get(END_TIME_QUERY_PARAM)
204#
205# if not start_time and not end_time:
206# return None
207#
208# if not start_time:
209# raise RestMissingRequiredQueryParamException(START_TIME_QUERY_PARAM)
210# if not end_time:
211# raise RestMissingRequiredQueryParamException(END_TIME_QUERY_PARAM)
212#
213# return (start_time, end_time)
214
215
216@safe_stats_view
217def do_get_stats(request, cluster, target_type, target_id, stats_type):
218
219 # FIXME: Hack to handle the old hard-coded controller id value
220 if target_type == 'controller' and target_id == 'localhost':
221 target_id = get_local_controller_id()
222
223 # Get the time range over which we're getting the stats
224 start_time, end_time = get_time_range_from_request(request)
225
226 init_db_connection()
227
228 if request.method == 'GET':
229 window = request.GET.get(SAMPLE_WINDOW_QUERY_PARAM, 0)
230 if window:
231 window = convert_time_duration(window)
232 if window != 0:
233 window = get_closest_window_interval(int(window))
234 # FIXME: Error checking on window value
235
236 data_format = request.GET.get(DATA_FORMAT_QUERY_PARAM, VALUE_DATA_FORMAT)
237 # FIXME: Error checking on data_format value
238
239 limit = request.GET.get(LIMIT_QUERY_PARAM)
240 if limit:
241 limit = int(limit)
242 # FIXME: Error checking on limit value
243
244 if start_time is not None and end_time is not None:
245 # FIXME: Error checking on start_time and end_time values
246 sample_interval = request.GET.get(SAMPLE_INTERVAL_QUERY_PARAM)
247 if not sample_interval:
248 # FIXME: Error checking on sample_period value
249 sample_count = request.GET.get(SAMPLE_COUNT_QUERY_PARAM, DEFAULT_SAMPLE_COUNT)
250 # FIXME: Error checking on sample_count value
251
252 sample_interval = (end_time - start_time) / int(sample_count)
253 else:
254 sample_interval = convert_time_duration(sample_interval)
255
256 if sample_interval != 0:
257 sample_interval = get_closest_sample_interval(sample_interval)
258
259 stats_data = get_stats_data(cluster, target_type, target_id,
260 stats_type, start_time, end_time, sample_interval, window, data_format, limit)
261 else:
262 stats_data = get_latest_stat_data(cluster, target_type, target_id, stats_type, window, data_format)
263
264 response_data = simplejson.dumps(stats_data)
265 response = HttpResponse(response_data, JSON_CONTENT_TYPE)
266
267 elif request.method == 'DELETE':
268 delete_stats_data(cluster, target_type, target_id, stats_type,
269 start_time, end_time)
270 response = get_successful_response()
271 else:
272 raise RestInvalidMethodException()
273
274 return response
275
276
277@safe_stats_view
278def do_get_stats_metadata(request, cluster, stats_type=None):
279 metadata = get_stats_metadata(cluster, stats_type)
280 response_data = simplejson.dumps(metadata)
281 return HttpResponse(response_data, JSON_CONTENT_TYPE)
282
283
284@safe_stats_view
285def do_get_stats_type_index(request, cluster, target_type, target_id, stats_type=None):
286 # FIXME: Hack to handle the old hard-coded controller id value
287 if target_type == 'controller' and target_id == 'localhost':
288 target_id = get_local_controller_id()
289 init_db_connection()
290 index_data = get_stats_type_index(cluster, target_type, target_id, stats_type)
291 response_data = simplejson.dumps(index_data)
292 return HttpResponse(response_data, JSON_CONTENT_TYPE)
293
294
295@safe_stats_view
296def do_get_stats_target_types(request, cluster):
297 init_db_connection()
298 target_type_data = get_stats_target_types(cluster)
299 response_data = simplejson.dumps(target_type_data)
300 return HttpResponse(response_data, JSON_CONTENT_TYPE)
301
302
303@safe_stats_view
304def do_get_stats_targets(request, cluster, target_type=None):
305 init_db_connection()
306 target_data = get_stats_targets(cluster, target_type)
307 response_data = simplejson.dumps(target_data)
308 return HttpResponse(response_data, JSON_CONTENT_TYPE)
309
310
311@safe_stats_view
312def do_put_stats(request, cluster):
313 if request.method != 'PUT':
314 raise RestInvalidMethodException()
315
316 init_db_connection()
317
318 stats_data = simplejson.loads(request.raw_post_data)
319 put_stats_data(cluster, stats_data)
320
321 response = get_successful_response()
322
323 return response
324
325
326@safe_stats_view
327def do_get_events(request, cluster, node_id):
328 # FIXME: Hack to handle the old hard-coded controller id value
329 if node_id == 'localhost':
330 node_id = get_local_controller_id()
331
332 # Get the time range over which we're getting the events
333 start_time, end_time = get_time_range_from_request(request)
334
335 init_db_connection()
336
337 if request.method == 'GET':
338 include_pk_tag_param = request.GET.get(INCLUDE_PK_TAG_QUERY_PARAM, 'false')
339 include_pk_tag = include_pk_tag_param.lower() == 'true'
340 events_list = get_log_event_data(cluster, node_id, start_time, end_time, include_pk_tag)
341 response_data = simplejson.dumps(events_list)
342 response = HttpResponse(response_data, JSON_CONTENT_TYPE)
343 elif request.method == 'DELETE':
344 delete_log_event_data(cluster, node_id, start_time, end_time)
345 response = get_successful_response()
346 else:
347 raise RestInvalidMethodException()
348
349 return response
350
351@safe_stats_view
352def do_put_events(request, cluster):
353 if request.method != 'PUT':
354 raise RestInvalidMethodException()
355
356 init_db_connection()
357
358 events_data = simplejson.loads(request.raw_post_data)
359 put_log_event_data(cluster, events_data)
360
361 response = get_successful_response()
362
363 return response
364