blob: 545fe98eba270ff553971302976ca13eca76cf98 [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.db.models import AutoField, BooleanField, IntegerField, FloatField, ForeignKey
18from django.forms import ValidationError
19from django.http import HttpResponse
20from django.utils import simplejson
21from functools import wraps
srikanthf2c888b2014-08-20 10:46:55 -070022from sdncon.controller.models import Controller
23#from sdncon.controller.models import ControllerAlias, ControllerDomainNameServer, ControllerInterface, FirewallRule
24from sdncon.controller.models import Switch, Port, PortAlias, Link
srikanth116e6e82014-08-19 07:22:37 -070025from sdncon.rest.models import UserData
26from sdncon.controller.notification import begin_batch_notification, end_batch_notification
27from django.views.decorators.csrf import csrf_exempt
28import base64
29import urllib
30import urllib2
31import sys
32import json
33import os
34import re
35import time
36import traceback
37import collections
38import uuid
39import subprocess
40from datetime import datetime
41from sdncon.controller.oswrapper import exec_os_wrapper
42from sdncon.controller.config import get_local_controller_id
43from sdncon.rest.config import config_check_state
44from sdncon.controller.oswrapper import get_system_version_string
45import sdncon
46
47TEXT_PLAIN_CONTENT_TYPE = 'text/plain'
48TEXT_JAVASCRIPT_CONTENT_TYPE = 'text/javascript'
49JSON_CONTENT_TYPE = 'application/json'
50BINARY_DATA_CONTENT_TYPE = 'application/octet-stream'
51
srikanthde50b092014-08-19 08:31:46 -070052onos = 1
53
54if onos == 1:
Srikanth Vavilapallif2add512014-09-12 11:58:15 -070055 #CONTROLLER_URL_PREFIX = 'http://localhost:9000/wm/'
56 CONTROLLER_URL_PREFIX = 'http://localhost:8080/wm/'
srikanthde50b092014-08-19 08:31:46 -070057else:
58 CONTROLLER_URL_PREFIX = 'http://localhost:8080/wm/'
srikanth116e6e82014-08-19 07:22:37 -070059
60def controller_url(*elements):
61 return CONTROLLER_URL_PREFIX + '/'.join(elements)
62
63class RestException(Exception):
64 pass
65
66class RestInvalidDataTypeException(RestException):
67 def __init__(self, name):
68 super(RestInvalidDataTypeException,self).__init__('Invalid data type: ' + name)
69
70class RestInvalidMethodException(RestException):
71 def __init__(self):
72 super(RestInvalidMethodException,self).__init__('Invalid HTTP method')
73
74class RestResourceNotFoundException(RestException):
75 def __init__(self, url):
76 super(RestResourceNotFoundException,self).__init__('Resource not found: ' + url)
77
78class RestDatabaseConnectionException(RestException):
79 def __init__(self):
80 super(RestDatabaseConnectionException,self).__init__('Error connecting to database')
81
82class RestAuthenticationRequiredException(RestException):
83 def __init__(self):
84 super(RestAuthenticationRequiredException,self).__init__('Authentication required')
85
86class RestInvalidQueryParameterException(RestException):
87 def __init__(self, param_name):
88 super(RestInvalidQueryParameterException, self).__init__('Invalid query parameter: ' + str(param_name))
89
90class RestInvalidFilterParameterException(RestException):
91 def __init__(self, param_name):
92 super(RestInvalidFilterParameterException, self).__init__('Filter query parameters not allowed when URL contains resource iD: ' + str(param_name))
93
94class RestNoListResultException(RestException):
95 def __init__(self):
96 super(RestNoListResultException, self).__init__('The query result must be a single instance if the "nolist" query param is set')
97
98class RestInvalidPutDataException(RestException):
99 def __init__(self):
100 super(RestInvalidPutDataException, self).__init__('The request data for a PUT request must be a JSON dictionary object')
101
102class RestMissingRequiredQueryParamException(RestException):
103 def __init__(self, param_name):
104 super(RestMissingRequiredQueryParamException, self).__init__('Missing required query parameter: ' + str(param_name))
105
106class RestValidationException(RestException):
107 def __init__(self, model_error=None, field_errors=None):
108 # Build the exception message from model error and field errors
109 message = 'Validation error'
110 if model_error:
111 message = message + '; ' + model_error
112 if field_errors:
113 message += '; invalid fields: {'
114 first_time = True
115 for field_name, field_message in field_errors.items():
116 if not first_time:
117 message += '; '
118 else:
119 first_time = False
120 message = message + field_name + ': ' + field_message
121
122 message += '}'
123
124 super(RestValidationException, self).__init__(message)
125 self.model_error = model_error
126 self.field_errors = field_errors
127
128class RestModelException(RestException):
129 def __init__(self, exc):
130 super(RestModelException, self).__init__('Error: ' + str(exc))
131
132class RestSaveException(RestException):
133 def __init__(self, exc):
134 super(RestSaveException, self).__init__('Error saving data: ' + str(exc))
135
136class RestInvalidOrderByException(RestException):
137 def __init__(self,field_name):
138 super(RestInvalidOrderByException, self).__init__('Invalid orderby field: ' + field_name)
139
140class RestInternalException(RestException):
141 def __init__(self, exc):
142 super(RestInternalException,self).__init__('Unknown REST error: ' + unicode(exc))
143
144class RestUpgradeException(RestException):
145 def __init__(self, exc):
146 super(RestUpgradeException, self).__init__('Error: ' + str(exc))
147
148class RestProvisionException(RestException):
149 def __init__(self, exc):
150 super(RestProvisionException, self).__init__('Error: ' + str(exc))
151
152class RestDecommissionException(RestException):
153 def __init__(self, exc):
154 super(RestDecommissionException, self).__init__('Error: ' + str(exc))
155
156class RestInvalidLog(RestException):
157 def __init__(self, exc):
158 super(RestInvalidLog, self).__init__('Error: ' + str(exc))
159
160def handle_validation_error(model_info, validation_error):
161 model_error = None
162 field_errors = None
163 if hasattr(validation_error, 'message_dict'):
164 # The field errors we get in the ValidationError are a bit different
165 # then what we want for the RestValidationException. First, we
166 # need to convert the Django field name to the (possibly renamed)
167 # REST field name. Second, the per-field error message is possibly a
168 # list of messages, which we concatenate into a single string for the
169 # RestValidationException
170 for field_name, field_message in validation_error.message_dict.items():
171 if type(field_message) in (list, tuple):
172 converted_field_message = ''
173 for msg in field_message:
174 converted_field_message = converted_field_message + msg + ' '
175 else:
176 converted_field_message += unicode(field_message)
177 if field_name == '__all__':
178 model_error = converted_field_message
179 else:
180 if not field_errors:
181 field_errors = {}
182 field_info = model_info.field_name_dict.get(field_name)
183 if field_info:
184 field_errors[field_info.rest_name] = converted_field_message
185 else:
186 field_errors[field_name] = 'Private field invalid; ' + converted_field_message
187 elif hasattr(validation_error, 'messages'):
188 model_error = ':'.join(validation_error.messages)
189 else:
190 model_error = str(validation_error)
191 raise RestValidationException(model_error, field_errors)
192
193def get_successful_response(description=None, status_code=200):
194 content = get_successful_response_data(description)
195 return HttpResponse(content, JSON_CONTENT_TYPE, status_code)
196
197def get_successful_response_data(description = 'success'):
198 obj = {'description': description}
199 return simplejson.dumps(obj)
200
201def get_sdnplatform_response(url, timeout = None):
202
203 try:
204 response_text = urllib2.urlopen(url, timeout=timeout).read()
205 return HttpResponse(response_text, JSON_CONTENT_TYPE)
206 except urllib2.HTTPError, e:
207 response_text = e.read()
208 response = simplejson.loads(response_text)
209 response['error_type'] = "SDNPlatformError"
210 return HttpResponse(content=simplejson.dumps(response),
211 status=e.code,
212 content_type=JSON_CONTENT_TYPE)
213
214def get_sdnplatform_query(request, path):
215 """
216 This returns controller-level storage table list
217 """
218 if request.method != 'GET':
219 raise RestInvalidMethodException()
220 url = controller_url(path) + '/?%s' % request.META['QUERY_STRING']
221 return get_sdnplatform_response(url)
222
223def safe_rest_view(func):
224 """
225 This is a decorator that takes care of exception handling for the
226 REST views so that we return an appropriate error HttpResponse if
227 an exception is thrown from the view
228 """
229 @wraps(func)
230 def _func(*args, **kwargs):
231 try:
232 response = func(*args, **kwargs)
233 except Exception, exc:
234 end_batch_notification(True)
235 if not isinstance(exc, RestException):
236 # traceback.print_exc()
237 exc = RestInternalException(exc)
238 response_obj = {'error_type': exc.__class__.__name__, 'description': unicode(exc)}
239 if isinstance(exc, RestValidationException):
240 if exc.model_error:
241 response_obj['model_error'] = exc.model_error
242 if exc.field_errors:
243 response_obj['field_errors'] = exc.field_errors
244 content = simplejson.dumps(response_obj)
245 content_type = JSON_CONTENT_TYPE
246
247 if isinstance(exc, RestInvalidMethodException):
248 status_code = 405
249 elif isinstance(exc, RestResourceNotFoundException):
250 status_code = 404
251 elif isinstance(exc, RestInternalException):
252 status_code = 500
253 else:
254 status_code = 400
255 response = HttpResponse(content, content_type, status_code)
256 if isinstance(exc, RestInvalidMethodException):
257 response['Allow'] = "GET, PUT, DELETE"
258 return response
259 return _func
260
261rest_model_info_dict = {}
262
263class RestFieldInfo(object):
264 def __init__(self, name, django_field_info, hidden=False,
265 rest_name=None, json_serialize=False):
266 self.name = name
267 self.django_field_info = django_field_info
268 self.rest_name = rest_name
269 self.json_serialize = json_serialize
270
271class RestModelInfo(object):
272 def __init__(self, rest_name, model_class):
273 self.rest_name = rest_name
274 self.model_class = model_class
275 self.primary_key = None
276 self.field_name_dict = {}
277 self.rest_name_dict = {}
278
279 for field in model_class._meta.local_fields:
280 field_name = field.name
281 rest_name = field.name
282 if field.primary_key:
283 self.primary_key = field_name
284 # TODO: Are there other field types that should be included here?
285 json_serialize = type(field) not in (AutoField, BooleanField, IntegerField, FloatField)
286 self.set_field_info(field_name, rest_name, field, json_serialize)
287
288
289 # this is how a RestFieldInfo is created - pass in django_field_info
290 def get_field_info(self, field_name, django_field_info=None):
291 field_info = self.field_name_dict.get(field_name)
292 if not field_info and django_field_info:
293 field_info = RestFieldInfo(field_name, django_field_info)
294 self.field_name_dict[field_name] = field_info
295 return field_info
296
297 def hide_field(self, field_name):
298 field_info = self.get_field_info(field_name)
299 del self.field_name_dict[field_name]
300 del self.rest_name_dict[field_info.rest_name]
301
302 def set_field_info(self, field_name, rest_name, django_field_info, json_serialize=None):
303 field_info = self.get_field_info(field_name, django_field_info)
304 if field_info.rest_name in self.rest_name_dict:
305 del self.rest_name_dict[field_info.rest_name]
306 field_info.rest_name = rest_name
307 if json_serialize != None:
308 field_info.json_serialize = json_serialize
309 self.rest_name_dict[rest_name] = field_info
310
311def get_rest_model_info(name):
312 return rest_model_info_dict[name]
313
314def add_rest_model_info(info):
315 if rest_model_info_dict.get(info.rest_name):
316 raise RestException('REST model info already exists')
317 rest_model_info_dict[info.rest_name] = info
318
319rest_initialized = False
320
321def get_default_rest_name(model):
322 # TODO: Ideally should do something a bit smarter here.
323 # Something like convert from camel-case class names to hyphenated names:
324 # For example:
325 # MyTestClass => my-test-class
326 # MyURLClass => my-url-class
327 #
328 # This isn't super-important for now, since you can set it explicitly
329 # with the nested Rest class.
330 return model.__name__.lower()
331
332def initialize_rest():
333 global rest_initialized
334 if rest_initialized:
335 return
336
337 from django.db.models import get_models
338 for model in get_models():
339
340 # If the model class has a nested class named 'Rest' then that means
341 # the model should be exposed in the REST API.
342 if hasattr(model, 'Rest'):
343 # By default the REST API uses the lower-case-converted name
344 # of the model class as the name in the REST URL, but this can
345 # be overridden by defining the 'NAME' attribute in the Rest class.
346 if hasattr(model.Rest, 'NAME'):
347 rest_name = model.Rest.NAME
348 else:
349 rest_name = get_default_rest_name(model)
350
351 if model._meta.proxy:
352 # This is a proxy class, drop through to the real one
353 base_model = model._meta.proxy_for_model
354 else:
355 base_model = model
356
357 # OK, we have the basic REST info, so we can create the info class
358 rest_model_info = RestModelInfo(rest_name, base_model)
359
360 # Check if there are any private or renamed fields
361 if hasattr(model.Rest, 'FIELD_INFO'):
362 for field_info in model.Rest.FIELD_INFO:
363 field_name = field_info['name']
364 rest_field_info = rest_model_info.get_field_info(field_name)
365 # Check if field exists in models - don't allow field only here in FIELD_INFO)
366 if not rest_field_info:
367 # LOOK! This field only exists in FIELD_INFO - skip
368 print "ERROR: %s for %s only in FIELD_INFO" % (field_name, rest_name)
369 continue
370
371 if field_info.get('private', False):
372 rest_model_info.hide_field(field_name)
373 else:
374 rest_name = field_info.get('rest_name')
375 if rest_name:
376 rest_model_info.set_field_info(field_name, rest_name, rest_field_info.django_field_info)
377
378 # Finished setting it up, so now add it to the list
379 add_rest_model_info(rest_model_info)
380
381 rest_initialized = True
382
383initialize_rest()
384
385@safe_rest_view
386def do_model_list(request):
387 """
388 This returns the list of models available in the REST API.
389 """
390
391 json_model_list = []
392 for model_name in rest_model_info_dict.keys():
393 json_model_info = {}
394 json_model_info["name"] = model_name
395 json_model_info["url_path"] = "rest/v1/model/" + model_name + "/"
396 json_model_list.append(json_model_info)
397
398 json_data = simplejson.dumps(json_model_list)
399 return HttpResponse(json_data, JSON_CONTENT_TYPE)
400
401@safe_rest_view
402def do_realtimestats(request, stattype, dpid):
403 """
404 This returns realtime statistics (flows, ports, table, aggregate,
405 desc, ...) for a dpid by calling the localhost sdnplatform
406 """
Fahad Naeem Khan0c1c7b32014-10-07 16:22:23 -0700407 #raise RestInvalidMethodException()
srikanth116e6e82014-08-19 07:22:37 -0700408 if request.method != 'GET':
409 raise RestInvalidMethodException()
Srikanth Vavilapallice98ea02014-09-28 22:36:32 -0700410 #url = controller_url('core', 'switch', dpid, stattype, 'json')
Srikanth Vavilapalli574f2702014-10-07 07:32:06 -0700411 if stattype == 'group':
412 stattype = 'groupStats'
Srikanth Vavilapalli8dff81f2014-10-07 14:25:32 -0700413 if stattype == 'groupdesc':
414 stattype = 'groupDesc'
Srikanth Vavilapallice98ea02014-09-28 22:36:32 -0700415 url = "http://localhost:8080/wm/floodlight/core/switch/%s/%s/json" % (dpid, stattype)
Fahad Naeem Khand563af62014-10-08 17:37:25 -0700416 return get_sdnplatform_response(url)
417
418@safe_rest_view
419def do_tablerealtimestats(request, tabletype, dpid):
420 """
421 This returns realtime statistics per table (flows (only)
422 current implementation) for a dpid by calling the localhost sdnplatform
423 """
424 #raise RestInvalidMethodException()
425 if request.method != 'GET':
426 raise RestInvalidMethodException()
427 #url = controller_url('core', 'switch', dpid, stattype, 'json')
428 url = "http://localhost:8080/wm/floodlight/core/switch/%s/table/%s/flow/json" % (dpid,tabletype)
Fahad Naeem Khan0c1c7b32014-10-07 16:22:23 -0700429 #url ="http://localhost:8080/wm/floodlight/core/switch/00:00:00:00:00:00:00:01/flow/json"
srikanth116e6e82014-08-19 07:22:37 -0700430 return get_sdnplatform_response(url)
431
432@safe_rest_view
433def do_sdnplatform_realtimestats(request, stattype, dpid=None, portid=None):
434 """
435 This returns realtime statistics from sdnplatform
436 """
437 if request.method != 'GET':
438 raise RestInvalidMethodException()
439 if dpid == None:
440 url = controller_url('core', 'counter', stattype, 'json')
441 elif portid == None:
442 url = controller_url('core', 'counter', dpid, stattype, 'json')
443 else:
444 url = controller_url('core', 'counter', dpid, portid, stattype, 'json')
445 return get_sdnplatform_response(url)
446
447@safe_rest_view
448def do_topology_tunnel_verify(request, srcdpid=None, dstdpid=None):
449 """
450 This initiates a liveness detection of tunnels.
451 """
452 if request.method != 'GET':
453 raise RestInvalidMethodException()
454
455 urlstring = srcdpid + '/' + dstdpid
456 url = controller_url('topology/tunnelverify', urlstring, 'json')
457
458 response_text = urllib2.urlopen(url).read()
459 time.sleep(4)
460 return do_topology_tunnel_status(request, srcdpid, dstdpid)
461
462@safe_rest_view
463def do_topology_tunnel_status(request, srcdpid='all', dstdpid='all'):
464 """
465 This returns the list of tunnels that have failed over the last observation interval.
466 """
467 if request.method != 'GET':
468 raise RestInvalidMethodException()
469
470 urlstring = srcdpid + '/' + dstdpid
471 url = controller_url('topology/tunnelstatus', urlstring, 'json')
472 response_text = urllib2.urlopen(url).read()
473 return HttpResponse(response_text, JSON_CONTENT_TYPE)
474
475@safe_rest_view
476def do_sdnplatform_realtimestatus(request, category=None, subcategory=None, srcdpid=None, dstdpid = None):
477 """
478 This returns realtime status of sdnplatform
479 """
480
481 if request.method != 'GET':
482 raise RestInvalidMethodException()
483
484 response_text = None
485 url = None
486 if category == 'network':
487 if subcategory == 'cluster':
488 url = controller_url('topology', 'switchclusters', 'json')
489 if subcategory == 'externalports':
490 url = controller_url('topology', 'externalports', 'json')
491 if subcategory == 'tunnelverify':
492 urlstring = subcategory+ '/' + srcdpid + '/' + dstdpid
493 url = controller_url('topology', urlstring, 'json')
494 if subcategory == 'tunnelstatus':
495 url = controller_url('topology', 'tunnelstatus', 'json')
496
497 if url:
498 response_text = urllib2.urlopen(url).read()
499 return HttpResponse(response_text, JSON_CONTENT_TYPE)
500
501@safe_rest_view
502def do_sdnplatform_realtimetest(http_request, category=None, subcategory=None):
503 """
504 This does a realtime test by sending an "explain packet" as packet in
505 and collecting the operations performed on the packet
506 """
507
508 if http_request.method != 'PUT':
509 raise RestInvalidMethodException()
510
511 response_text = None
512 url = None
513 if category == 'network':
514 if subcategory == 'explain-packet':
515 # set up the sdnplatform URL for explain packet (at internal port 8080
516 url = controller_url('vns', 'explain-packet', 'json')
517 post_data = http_request.raw_post_data
518 request = urllib2.Request(url, post_data)
519 request.add_header('Content-Type', 'application/json')
520 response = urllib2.urlopen(request)
521 response_text = response.read()
522 elif subcategory == 'path':
523 post_data = json.loads(http_request.raw_post_data)
524 url = controller_url('topology', 'route',
525 post_data['src-switch'],
526 str(post_data['src-switch-port']),
527 post_data['dst-switch'],
528 str(post_data['dst-switch-port']),
529 'json')
530
531 return get_sdnplatform_response(url)
532
533 return HttpResponse(response_text, JSON_CONTENT_TYPE)
534
535@safe_rest_view
536def do_sdnplatform_performance_monitor(http_request, category=None,
537 subcategory=None, type='all'):
538 """
539 This API returns performance related information from the sdnplatform and
540 sdnplatform components
541 """
542
543 if http_request.method != 'GET':
544 raise RestInvalidMethodException()
545
546 response_text = None
547 url = None
548 if category == 'performance-monitor':
549 # set up the sdnplatform URL for explain packet (at internal port 8080
550 url = controller_url('performance', type, 'json')
551 request = urllib2.Request(url)
552 response = urllib2.urlopen(request)
553 response_text = response.read()
554 return HttpResponse(response_text, JSON_CONTENT_TYPE)
555
556@safe_rest_view
557def do_sdnplatform_internal_debugs(http_request, category=None, subcategory=None,
558 query='all', component='device-manager'):
559 """
560 This API returns debugging related information from the sdnplatform and
561 sdnplatform components
562 """
563
564 if http_request.method != 'GET':
565 raise RestInvalidMethodException()
566
567 response_text = None
568 url = None
569 if category == 'internal-debugs':
570 # set up the sdnplatform URL for explain packet (at internal port 8080
571 url = controller_url('vns', 'internal-debugs', component, query, 'json')
572 request = urllib2.Request(url)
573 response = urllib2.urlopen(request)
574 response_text = response.read()
575 return HttpResponse(response_text, JSON_CONTENT_TYPE)
576
577@safe_rest_view
578def do_sdnplatform_event_history(http_request, category=None, subcategory=None,
579 evHistName='all', count='100'):
580 """
581 This API returns real-time event-history information from the sdnplatform and
582 sdnplatform components
583 """
584
585 if http_request.method != 'GET':
586 raise RestInvalidMethodException()
587
588 response_text = None
589 url = None
590 if category == 'event-history':
591 # set up the sdnplatform URL for explain packet (at internal port 8080
592 url = controller_url('core', 'event-history', evHistName, count, 'json')
593 request = urllib2.Request(url)
594 response = urllib2.urlopen(request)
595 response_text = response.read()
596 return HttpResponse(response_text, JSON_CONTENT_TYPE)
597
598@safe_rest_view
599def do_flow_cache(http_request, category=None, subcategory=None,
600 applName='None', applInstName='all', queryType='all'):
601 """
602 This API returns real-time event-history information from the sdnplatform and
603 sdnplatform components
604 """
605
606 if http_request.method != 'GET':
607 raise RestInvalidMethodException()
608
609 response_text = None
610 url = None
611 if category == 'flow-cache':
612 # set up the sdnplatform URL for explain packet (at internal port 8080
613 url = controller_url('vns', 'flow-cache', applName, applInstName, queryType, 'json')
614 request = urllib2.Request(url)
615 response = urllib2.urlopen(request)
616 response_text = response.read()
617 return HttpResponse(response_text, JSON_CONTENT_TYPE)
618
619
620@safe_rest_view
621def do_vns_realtimestats_flow(http_request, category=None, vnsName="all"):
622 """
623 This gets realtime flows for one or more vnses
624 """
625
626 if http_request.method != 'GET':
627 raise RestInvalidMethodException()
628
629 # set up the sdnplatform URL for per-vns flow (at internal port 8080
630 url = controller_url('vns', 'flow', vnsName, 'json')
631 return get_sdnplatform_response(url)
632
633@safe_rest_view
634def do_sdnplatform_counter_categories(request, stattype, layer, dpid=None, portid=None):
635 """
636 This returns counter categories from sdnplatform
637 """
638 if request.method != 'GET':
639 raise RestInvalidMethodException()
640 if dpid == None:
641 url = controller_url('core', 'counter', 'categories', stattype, layer, 'json')
642 elif portid == None:
643 url = controller_url('core', 'counter', 'categories', dpid, stattype, layer, 'json')
644 else:
645 url = controller_url('core', 'counter', 'categories', dpid, portid, stattype, layer, 'json')
646
647 return get_sdnplatform_response(url)
648
649@safe_rest_view
650@csrf_exempt
651def do_packettrace(request):
652 """
653 This sets a packet trace in sdnplatform.
654 period:
655 . >0 starts a trace session with the period
656 . =0 starts a trace session with no timeout
657 . <0 ends an ongoing session
658
659 The request method has to be POST since each request gets an unique sessionID
660 """
661 SESSIONID = 'sessionId'
662 sessionId = ""
663 filter = ""
664 if request.method != 'POST':
665 raise RestInvalidMethodException()
666
667 url = 'http://localhost:8080/wm/vns/packettrace/json'
668 request = urllib2.Request(url, request.raw_post_data, {'Content-Type':'application/json'})
669 try:
670 response = urllib2.urlopen(request)
671 response_text = response.read()
672 except Exception, e:
673 # SDNPlatform may not be running, but we don't want that to be a fatal
674 # error, so we just ignore the exception in that case.
675 pass
676
677 #response_data = json.loads(response_text)
678 #if SESSIONID in response_data:
679 # sessionId = response_data[SESSIONID]
680 #response_text = {SESSIONID:sessionId}
681
682 return HttpResponse(response_text, mimetype=JSON_CONTENT_TYPE)
683
684@safe_rest_view
685def do_controller_stats(request, stattype):
686 """
687 This returns controller-level statistics/info from sdnplatform
688 """
689 if request.method != 'GET':
690 raise RestInvalidMethodException()
691 url = 'http://127.0.0.1:8080/wm/core/controller/%s/json' % stattype
692 return get_sdnplatform_response(url)
693
694@safe_rest_view
695def do_controller_storage_table_list(request):
696 """
697 This returns controller-level storage table list
698 """
699 if request.method != 'GET':
700 raise RestInvalidMethodException()
701 url = 'http://127.0.0.1:8080/wm/core/storage/tables/json'
702 return get_sdnplatform_response(url)
703
704@safe_rest_view
705def do_device(request):
srikanthde50b092014-08-19 08:31:46 -0700706 if onos == 0:
707 return get_sdnplatform_query(request, "device")
708 else:
709 url = controller_url("onos", "topology", "hosts")
710 if request.META['QUERY_STRING']:
711 url += '?' + request.META['QUERY_STRING']
712 return get_sdnplatform_response(url)
srikanth116e6e82014-08-19 07:22:37 -0700713
714@safe_rest_view
715def do_switches(request):
srikanthde50b092014-08-19 08:31:46 -0700716 if onos == 0:
717 url = controller_url("core", "controller", "switches", "json")
718 else:
719 url = controller_url("onos", "topology", "switches")
srikanth116e6e82014-08-19 07:22:37 -0700720 if request.META['QUERY_STRING']:
721 url += '?' + request.META['QUERY_STRING']
722 return get_sdnplatform_response(url)
723
724@safe_rest_view
Fahad Naeem Khan1f8fb2e2014-09-25 16:39:18 -0700725def do_mastership(request):
726 url = controller_url("onos", "registry", "switches" ,"json")
727 #url = "http://127.0.0.1:8080/wm/onos/registry/switches/json"
728 if request.META['QUERY_STRING']:
729 url += '?' + request.META['QUERY_STRING']
Fahad Naeem Khan99ce8062014-10-01 11:03:54 -0700730 return get_sdnplatform_response(url)
731
732@safe_rest_view
733def do_controller(request):
734 url = controller_url("onos", "registry", "controllers" ,"json")
735 #url = "http://127.0.0.1:8080/wm/onos/registry/switches/json"
736 if request.META['QUERY_STRING']:
737 url += '?' + request.META['QUERY_STRING']
Fahad Naeem Khan1f8fb2e2014-09-25 16:39:18 -0700738 return get_sdnplatform_response(url)
739#'''
740
741@safe_rest_view
srikanth116e6e82014-08-19 07:22:37 -0700742def do_links(request):
srikanthde50b092014-08-19 08:31:46 -0700743 if onos == 0:
744 url = controller_url("topology", "links", "json")
745 else:
746 url = controller_url("onos", "topology", "links")
srikanth116e6e82014-08-19 07:22:37 -0700747 if request.META['QUERY_STRING']:
748 url += '?' + request.META['QUERY_STRING']
749 return get_sdnplatform_response(url)
750
751@safe_rest_view
752def do_vns_device_interface(request):
753 return get_sdnplatform_query(request, "vns/device-interface")
754
755@safe_rest_view
756def do_vns_interface(request):
757 return get_sdnplatform_query(request, "vns/interface")
758
759@safe_rest_view
760def do_vns(request):
761 return get_sdnplatform_query(request, "vns")
762
763@safe_rest_view
764def do_system_version(request):
765 if request.method != 'GET':
766 raise RestInvalidMethodException()
767 version = get_system_version_string()
768 response_text = simplejson.dumps([{ 'controller' : version }])
769 return HttpResponse(response_text, JSON_CONTENT_TYPE)
770
771available_log_files = {
772 'syslog' : '/var/log/syslog',
773 'sdnplatform' : '/opt/sdnplatform/sdnplatform/log/sdnplatform.log',
774 'console-access' : '/opt/sdnplatform/con/log/access.log',
775 'cassandra' : '/opt/sdnplatform/db/log/system.log',
776 'authlog' : '/var/log/auth.log',
777 'pre-start' : '/tmp/pre-start',
778 'post-start' : '/tmp/post-start',
779 # 'ftp' : '/var/log/ftp.log',
780}
781
782available_log_commands = {
783 'dmesg' : 'dmesg',
784 'process' : 'ps lax'
785}
786
787@safe_rest_view
788def do_system_log_list(request):
789 if request.method != 'GET':
790 raise RestInvalidMethodException()
791 existing_logs = []
792 for (log_name, log_path) in available_log_files.items():
793 try:
794 log_file = open(log_path, 'r')
795 existing_logs.append({ 'log' : log_name })
796 log_file.close()
797 except Exception, e:
798 pass
799
800 print '??'
801 for log_name in available_log_commands.keys():
802 print 'ADD', log_name
803 existing_logs.append({ 'log' : log_name })
804 response_text = simplejson.dumps(existing_logs)
805 return HttpResponse(response_text, JSON_CONTENT_TYPE)
806
807
808def generate_subprocess_output(cmd):
809
810 process = subprocess.Popen(cmd, shell=True,
811 stdout=subprocess.PIPE,
812 stderr=subprocess.STDOUT,
813 bufsize=1)
814 while True:
815 line = process.stdout.readline()
816 if line != None and line != "":
817 yield line
818 else:
819 break
820
821
822@safe_rest_view
823def do_system_log(request, log_name):
824 if request.method != 'GET':
825 raise RestInvalidMethodException()
826 print 'do system log', log_name
827
828 # manage command ouput differently
829 if log_name in available_log_commands:
830 cmd = available_log_commands[log_name]
831 print 'DOING COMMAND', cmd
832
833 return HttpResponse(generate_subprocess_output(cmd),
834 TEXT_PLAIN_CONTENT_TYPE)
835 return
836
837 log_path = available_log_files.get(log_name)
838 if log_name == None:
839 raise RestInvalidLog('No such log: %s' % log_name)
840
841 try:
842 log_file = open(log_path, 'r')
843 except Exception,e:
844 raise RestInvalidLog('Log does not exist: %s' % log_name)
845
846 # use a generator so that the complete log is not ever held in memory
847 def response(log_name, file):
848 for line in file:
849 yield line
850 file.close()
851
852 return HttpResponse(response(log_name, log_file), TEXT_PLAIN_CONTENT_TYPE)
853
854
855@safe_rest_view
856def do_system_uptime(request):
857 if request.method != 'GET':
858 raise RestInvalidMethodException()
859 url = controller_url('core', 'system', 'uptime', 'json')
860 return get_sdnplatform_response(url)
861
862
863def _collect_system_interfaces(lo = False):
864 from netifaces import interfaces, ifaddresses, AF_INET, AF_LINK
865 result = []
866 for iface in interfaces():
867 if iface.startswith('lo') and not lo:
868 continue # ignore loopback
869 addrs = ifaddresses(iface)
870 if AF_INET in addrs:
871 for addr in ifaddresses(iface)[AF_INET]:
872 result.append({'name' : iface,
873 'addr' : addr.get('addr', ''),
874 'netmask' : addr.get('netmask', ''),
875 'broadcast' : addr.get('broadcast', ''),
876 'peer' : addr.get('peer', '')})
877 return result
878
879
880def do_system_inet4_interfaces(request):
881 if request.method != 'GET':
882 raise RestInvalidMethodException()
883 response_text = simplejson.dumps(_collect_system_interfaces(lo = True))
884 return HttpResponse(response_text, JSON_CONTENT_TYPE)
885
886
887@safe_rest_view
888def do_system_time_zone_strings(request, list_type):
889 import pytz
890 if list_type == 'common':
891 string_list = pytz.common_timezones
892 elif list_type == "all":
893 string_list = pytz.all_timezones
894 else:
895 raise RestResourceNotFoundException(request.path)
896
897 response_text = simplejson.dumps(string_list)
898
899 return HttpResponse(response_text, JSON_CONTENT_TYPE)
900
901@safe_rest_view
902def do_check_config(request):
903 config_check_state()
904 return get_successful_response('checked config')
905
906@safe_rest_view
907def do_local_controller_id(request):
908 if request.method == 'GET':
909 controller_id = get_local_controller_id()
910 if not controller_id:
911 raise Exception("Unspecified local controller id")
912 response_text = simplejson.dumps({'id': controller_id})
913 elif request.method == 'PUT':
914 put_data = json.loads(request.raw_post_data)
915 controller_id = put_data.get('id')
916 _result = exec_os_wrapper("ControllerId", 'set', [controller_id])
917 response_text = get_successful_response_data('updated')
918 else:
919 raise RestInvalidMethodException()
920
921 response = HttpResponse(response_text, JSON_CONTENT_TYPE)
922
923 return response
924
925@safe_rest_view
926def do_ha_failback(request):
927 if request.method != 'PUT':
928 raise RestInvalidMethodException()
929 _result = exec_os_wrapper("HAFailback", 'set', [])
930 response_text = get_successful_response_data('forced failback')
931 response = HttpResponse(response_text, JSON_CONTENT_TYPE)
932 return response
933
934def delete_ha_firewall_rules(ip):
935 rules = FirewallRule.objects.filter(action='allow', src_ip=ip)
936 rules.filter(port=80).delete()
937 rules.filter(proto='tcp', port=7000).delete()
938 rules.filter(proto='vrrp').delete()
939
940def cascade_delete_controller_node(controller_id):
941 ControllerAlias.objects.filter(controller=controller_id).delete()
942 ControllerDomainNameServer.objects.filter(controller=controller_id).delete()
943 for iface in ControllerInterface.objects.filter(controller=controller_id):
944 FirewallRule.objects.filter(interface=iface.id).delete()
945 ControllerInterface.objects.filter(controller=controller_id).delete()
946 Controller.objects.filter(id=controller_id).delete()
947
948# FIXME: this assume a single-interface design and will look for the IP on eth0
949# need to fix this when we have a proper multi-interface design
950def get_controller_node_ip(controller_id):
951 node_ip = ''
952 iface = ControllerInterface.objects.filter(controller=controller_id, type='Ethernet', number=0)
953 if iface:
954 node_ip = iface[0].discovered_ip
955 return node_ip
956
957# This method is "external" facing
958# It is designed to be called by CLI or other REST clients
959# This should only run on the master node, where decommissioning of a remote node is initiated
960@safe_rest_view
961def do_decommission(request):
962 if request.method != 'PUT':
963 raise RestInvalidMethodException()
964 data = simplejson.loads(request.raw_post_data)
965 node_id = data['id']
966
967 # Disallow self-decommissioning
968 local_id = get_local_controller_id()
969 if local_id == node_id:
970 raise RestDecommissionException("Decommissioning of the master node is not allowed. " + \
971 "Please perform a failover first.")
972
973 try :
974 controller = Controller.objects.get(id=node_id)
975 except Controller.DoesNotExist:
976 raise RestDecommissionException("No controller found")
977
978 node_ip = get_controller_node_ip(node_id)
979
980 # Case 1: controller node has IP
981 if node_ip:
982 result = exec_os_wrapper("Decommission", 'set', [node_ip])
983 output = result['out'].strip()
984 if result['out'].strip().endswith('is already decommissioned'):
985 delete_ha_firewall_rules(node_ip)
986 cascade_delete_controller_node(node_id)
987
988 # Case 2: controller node has NO IP
989 else:
990 output = '%s is already decommissioned' % node_id
991 cascade_delete_controller_node(node_id)
992
993 jsondict = {}
994 jsondict['status'] = "OK"
995 jsondict['description'] = output
996 return HttpResponse(simplejson.dumps(jsondict), JSON_CONTENT_TYPE)
997
998# This method is "internal" facing
999# It is designed to be called only by sys/remove-node.sh
1000# This should only run on the node being decommissioned (slave)
1001@safe_rest_view
1002def do_decommission_internal(request):
1003 if request.method != 'PUT':
1004 raise RestInvalidMethodException()
1005 data = simplejson.loads(request.raw_post_data)
1006 node_ip = data['ip']
1007 exec_os_wrapper("DecommissionLocal", 'set', [node_ip])
1008
1009 jsondict = {}
1010 jsondict['status'] = "OK"
1011 return HttpResponse(simplejson.dumps(jsondict), JSON_CONTENT_TYPE)
1012
1013@safe_rest_view
1014def do_ha_provision(request):
1015 if request.method != 'PUT':
1016 raise RestInvalidMethodException()
1017 data = simplejson.loads(request.raw_post_data)
1018 node_ip = data['ip']
1019
1020 try :
1021 ci = ControllerInterface.objects.get(ip=node_ip)
1022 id = ci.controller.id
1023 print 'got id', id
1024 try:
1025 a = ControllerAlias.objects.get(controller=id)
1026 alias = a.alias
1027 except:
1028 alias = '(no controller alias)'
1029
1030 print 'alias:', alias
1031 raise RestProvisionException('ip address already in controller %s %s' %
1032 (id, alias))
1033
1034 except ControllerInterface.DoesNotExist:
1035 id = uuid.uuid4().urn[9:]
1036 print "generated id = ", id
1037 c = Controller(id=id)
1038 try:
1039 c.save()
1040 except:
1041 # describe failure
1042 raise RestProvisionException('can\t save controller')
1043 pass
1044 print "save controller"
1045 ci = ControllerInterface(controller=c,
1046 ip=node_ip,
1047 discovered_ip=node_ip)
1048 try:
1049 ci.save()
1050 except:
1051 # describe failure
1052 raise RestProvisionException('can\t save controllar interfacer')
1053
1054 for c in Controller.objects.all():
1055 if c.id != id:
1056 #if there are multiple interfaces, assume the
1057 # ethernet0 interface is for management purpose
1058 # XXX this could be better.
1059 iface = ControllerInterface.objects.get(controller=c.id,
1060 type='Ethernet',
1061 number=0)
1062 ip = iface.ip
1063 fw = FirewallRule(interface=iface, action='allow',
1064 src_ip=node_ip, port=80, proto='tcp')
1065 try:
1066 fw.save()
1067 except:
1068 # describe failure
1069 raise RestProvisionException('can\t save firewall rule from master')
1070
1071 fw = FirewallRule(interface=ci, action='allow',
1072 src_ip=ip, port=80, proto='tcp')
1073 try:
1074 fw.save()
1075 except:
1076 raise RestProvisionException('can\t save firewall from slave')
1077
1078
1079 response_text = get_successful_response_data(id)
1080 response = HttpResponse(response_text, JSON_CONTENT_TYPE)
1081
1082 return response
1083
1084
1085def get_clustername():
1086 name = os.popen("grep cluster_name /opt/sdnplatform/db/conf/cassandra.yaml | awk '{print $2}'").readline()
1087 # name may be '', perhaps this ought to None?
1088 return name
1089
1090
1091@safe_rest_view
1092def do_clustername(request):
1093 if request.method != 'GET':
1094 raise RestInvalidMethodException()
1095 response_text = simplejson.dumps([{ 'clustername' : get_clustername() }])
1096 return HttpResponse(response_text, JSON_CONTENT_TYPE)
1097
1098
1099@safe_rest_view
1100def do_local_ha_role(request):
1101 if request.method != 'GET':
1102 raise RestInvalidMethodException()
1103
1104 url = controller_url('core', 'role', 'json')
1105 try:
1106 response_text = urllib2.urlopen(url, timeout=2).read()
1107 response = json.loads(response_text)
1108 except Exception:
1109 response = HttpResponse('{"role":"UNAVAILABLE"}', JSON_CONTENT_TYPE)
1110 return response
1111 # now determine our controller id
1112 controller_id = get_local_controller_id()
1113
1114 # find all the interfaces
1115 ifs = ControllerInterface.objects.filter(controller=controller_id)
1116
1117 for intf in ifs:
1118 firewall_id = '%s|%s|%s' % (controller_id, intf.type, intf.number)
1119
1120 rules = FirewallRule.objects.filter(interface=firewall_id)
1121 for rule in rules:
1122 if rule.action == 'reject' and rule.proto == 'tcp' and rule.port == 6633:
1123 if response['role'] in {'MASTER', 'SLAVE'}:
1124 response['role']+='-BLOCKED'
1125
1126 response['clustername'] = get_clustername()
1127
1128 return HttpResponse(simplejson.dumps(response), JSON_CONTENT_TYPE)
1129
1130@safe_rest_view
1131def do_system_clock(request, local=True):
1132 local_or_utc_str = 'local' if local else 'utc'
1133 if request.method == 'GET':
1134 result = exec_os_wrapper("DateTime", 'get', [local_or_utc_str])
1135 elif request.method == 'PUT':
1136 time_info = simplejson.loads(request.raw_post_data)
1137 dt = datetime(**time_info)
1138 new_date_time_string = dt.strftime('%Y:%m:%d:%H:%M:%S')
1139 result = exec_os_wrapper("DateTime", 'set', [local_or_utc_str, new_date_time_string])
1140 else:
1141 raise RestInvalidMethodException()
1142
1143 if len(result) == 0:
1144 raise Exception('Error executing date command')
1145
1146 # The DateTime OS wrapper only has a single command so the return
1147 # date/time is the first line of the first element of the out array
1148 values = result['out'].strip().split(':')
1149 date_time_info = {
1150 'year': int(values[0]),
1151 'month': int(values[1]),
1152 'day': int(values[2]),
1153 'hour': int(values[3]),
1154 'minute': int(values[4]),
1155 'second': int(values[5]),
1156 'tz': values[6]
1157 }
1158 response_text = simplejson.dumps(date_time_info)
1159 response = HttpResponse(response_text, JSON_CONTENT_TYPE)
1160
1161 return response
1162
1163@safe_rest_view
1164def do_instance(request, model_name,id=None):
1165 """
1166 This function handles both GET and PUT methods.
1167
1168 For a GET request it returns a list of all of the instances of the
1169 model corresponding to the specified type that match the specified
1170 query parameters. If there are no query parameters then it returns
1171 a list of all of the instances of the specified type. The names of
1172 the query parameters can use the Django double underscore syntax
1173 for doing more complicated tests than just equality
1174 (e.g. mac__startswith=192.168).
1175
1176 For a PUT request it can either update an existing instance or
1177 insert one or more new instances. If there are any query parameters
1178 then it assumes that its the update case and that the query
1179 parameters identify exactly one instance. If that's not the case
1180 then an error response is returned. For the update case any subset
1181 of the fields can be updated with the PUT data. The format of the
1182 PUT data is a JSON dictionary
1183 """
1184
1185 # FIXME: Hack to remap 'localhost' id for the controller-node model
1186 # to the real ID for this controller
1187 if model_name == 'controller-node' and id == 'localhost':
1188 id = get_local_controller_id()
1189
1190 # Lookup the model class associated with the specified name
1191 model_info = rest_model_info_dict.get(model_name)
1192 if not model_info:
1193 raise RestInvalidDataTypeException(model_name)
1194
1195 # Set up the keyword argument dictionary we use to filter the QuerySet.
1196 filter_keyword_args = {}
1197
1198 jsonp_prefix = None
1199 nolist = False
1200 order_by = None
1201
1202 # Now iterate over the query params and add further filter keyword arguments
1203 query_param_dict = request.GET
1204 for query_param_name, query_param_value in query_param_dict.items():
1205 add_query_param = False
1206 query_param_name = str(query_param_name)
1207 query_param_value = str(query_param_value)
1208 if query_param_name == 'callback': #switching to match up with jquery getJSON call naming convention.
1209 jsonp_prefix = query_param_value
1210 elif query_param_name == 'nolist':
1211 if query_param_value not in ('False', 'false', '0', ''):
1212 nolist = True
1213 elif query_param_name == 'orderby':
1214 order_by = query_param_value.split(',')
1215 for i in range(len(order_by)):
1216 name = order_by[i]
1217 if name.startswith('-'):
1218 descending = True
1219 name = name[1:]
1220 else:
1221 descending = False
1222 field_info = model_info.rest_name_dict.get(name)
1223 if not field_info:
1224 raise RestInvalidOrderByException(name)
1225 name = field_info.name
1226 if descending:
1227 name = '-' + name
1228 order_by[i] = name
1229 elif query_param_name in model_info.rest_name_dict:
1230 field_info = model_info.rest_name_dict.get(query_param_name)
1231 # For booleans, translate True/False strings into 0/1.
1232 if field_info and type(field_info.django_field_info) == BooleanField:
1233 if query_param_value.lower() == 'false':
1234 query_param_value = 0
1235 elif query_param_value.lower() == 'true':
1236 query_param_value = 1
1237 query_param_name = field_info.name
1238 if model_name == 'controller-node' and \
1239 query_param_name == 'id' and query_param_value == 'localhost':
1240 query_param_value = get_local_controller_id()
1241 if model_name in 'controller-interface' and \
1242 query_param_name == 'controller' and \
1243 query_param_value == 'localhost':
1244 query_param_value = get_local_controller_id()
1245 add_query_param = True
1246 else:
1247 double_underscore_start = query_param_name.find("__")
1248 if double_underscore_start >= 0:
1249 rest_name = query_param_name[:double_underscore_start]
1250 field_info = model_info.rest_name_dict.get(rest_name)
1251 if field_info:
1252 operation = query_param_name[double_underscore_start:]
1253 query_param_name = field_info.name
1254 if type(field_info.django_field_info) == ForeignKey:
1255 query_param_name = query_param_name + '__' + field_info.django_field_info.rel.field_name
1256 # Substitute in the model field name for the (possible renamed) rest name
1257 query_param_name += operation
1258 add_query_param = True
1259 if add_query_param:
1260 filter_keyword_args[query_param_name] = query_param_value
1261
1262 if id != None:
1263 if len(filter_keyword_args) > 0:
1264 raise RestInvalidFilterParameterException(filter_keyword_args.keys()[0])
1265 try:
1266 get_args = {model_info.primary_key:id}
1267 instance = model_info.model_class.objects.get(**get_args)
1268 instance_list = (instance,)
1269 nolist = True
1270 except model_info.model_class.DoesNotExist,e:
1271 raise RestResourceNotFoundException(request.path)
1272 except model_info.model_class.MultipleObjectsReturned, exc:
1273 # traceback.print_exc()
1274 raise RestInternalException(exc)
1275 elif (request.method != 'PUT') or (len(filter_keyword_args) > 0):
1276 # Get the QuerySet based on the keyword arguments we constructed
1277 instance_list = model_info.model_class.objects.filter(**filter_keyword_args)
1278 if order_by:
1279 instance_list = instance_list.order_by(*order_by)
1280 else:
1281 # We're inserting new objects, so there's no need to do a query
1282 instance_list = None
1283
1284 response_content_type = JSON_CONTENT_TYPE
1285
1286 if request.method == 'GET':
1287 json_response_data = []
1288 for instance in instance_list:
1289 json_instance = {}
1290 for field_info in model_info.field_name_dict.values():
1291 # Made some minor edits to deal with a) fields that are empty and b) fields that are not strings -Kyle
1292 # Changed this to only do an explicit string conversion if it's a unicode string.
1293 # The controller is expecting to get the unstringified value (e.g. for boolean values)
1294 # Not sure if this will break things in the UI, but we'll need to resolve how
1295 # we want to handle this. Also, how do we want to handle unicode strings? -- robv
1296 field_name = field_info.name
1297 if type(field_info.django_field_info) == ForeignKey:
1298 field_name += '_id'
1299 value = instance.__dict__.get(field_name)
1300 if value != None:
1301 if field_info.json_serialize:
1302 value = str(value)
1303 json_instance[field_info.rest_name] = value
1304 json_response_data.append(json_instance)
1305
1306 # If the nolist query param was enabled then check to make sure
1307 # that there was only a single instance in the response list and,
1308 # if so, unpack it from the list
1309 if nolist:
1310 if len(json_response_data) != 1:
1311 raise RestNoListResultException()
1312 json_response_data = json_response_data[0]
1313
1314 # Convert to json
1315 response_data = simplejson.dumps(json_response_data)
1316
1317 # If the jsonp query parameter was specified, wrap the data with
1318 # the jsonp prefix
1319 if jsonp_prefix:
1320 response_data = jsonp_prefix + '(' + response_data + ')'
1321 # We don't really know what the content type is here, but it's typically javascript
1322 response_content_type = TEXT_JAVASCRIPT_CONTENT_TYPE
1323 elif request.method == 'PUT':
1324 response_data = get_successful_response_data('saved')
1325 response_content_type = JSON_CONTENT_TYPE
1326
1327 begin_batch_notification()
1328 json_object = simplejson.loads(request.raw_post_data)
1329 if instance_list is not None:
1330
1331 # don't allow the ip address of the first interface to
1332 # be updated once it is set. This really applies to
1333 # the interface cassandra uses to sync the db.
1334 if model_name == 'controller-interface':
1335 for instance in instance_list:
1336 if instance.number == 0 and instance.ip != '':
1337 if 'ip' in json_object and json_object['ip'] != instance.ip:
1338 raise RestModelException("In this version, ip-address of primary interface can't be updated after initial configuration")
1339
1340 # In this case the URL includes query parameter(s) which we assume
1341 # narrow the request to the instances of the model to be updated
1342 # updated with the PUT data. So we're updating existing instances
1343
1344 # If it's a list with one element then extract the single element
1345 if (type(json_object) == list) and (len(json_object) == 1):
1346 json_object = json_object[0]
1347
1348 # We're expecting a dictionary where the keys match the model field names
1349 # If the data isn't a dictionary then return an error
1350 if type(json_object) != dict:
1351 raise RestInvalidPutDataException() # TODO: Should return something different here
1352
1353 # Set the fields in the model instance with the data from the dictionary
1354 for instance in instance_list:
1355 for rest_name, value in json_object.items():
1356 if not rest_name in model_info.rest_name_dict:
1357 raise RestModelException("Model '%s' has no field '%s'" %
1358 (model_name, rest_name))
1359 field_info = model_info.rest_name_dict[rest_name]
1360 field_name = str(field_info.name) # FIXME: Do we need the str cast?
1361 if type(field_info.django_field_info) == ForeignKey:
1362 field_name += '_id'
1363 # TODO: Does Django still not like unicode strings here?
1364 if type(value) == unicode:
1365 value = str(value)
1366 instance.__dict__[field_name] = value
1367 # Save the updated model instance
1368 try:
1369 instance.full_clean()
1370 instance.save()
1371 except ValidationError, err:
1372 handle_validation_error(model_info, err)
1373 #raise RestValidationException(err)
1374 except Exception, exc:
1375 raise RestSaveException(exc)
1376 else:
1377 # In this case no query parameters or id were specified so we're inserting new
1378 # instances into the database. The PUT data can be either a list of new
1379 # items to add (i.e. top level json object is a list) or else a single
1380 # new element (i.e. top-level json object is a dict).
1381 #print "creating object(s)"
1382
1383 # To simplify the logic below we turn the single object case into a list
1384 if type(json_object) != list:
1385 json_object = [json_object]
1386
1387 # Create new model instances for all of the items in the list
1388 for instance_data_dict in json_object:
1389 # We expect the data to be a dictionary keyed by the field names
1390 # in the model. If it's not a dict return an error
1391 if type(instance_data_dict) != dict:
1392 raise RestInvalidPutDataException()
1393
1394 converted_dict = {}
1395
1396 # Now add the fields specified in the PUT data
1397 for rest_name, value in instance_data_dict.items():
1398
1399 #print " processing " + str(name) + " " + str(value)
1400
1401 if not rest_name in model_info.rest_name_dict:
1402 raise RestModelException("Model '%s' has no field '%s'" %
1403 (model_name, rest_name))
1404 field_info = model_info.rest_name_dict[rest_name]
1405 # simplejson uses unicode strings when it loads the objects which
1406 # Django doesn't like that, so we convert these to ASCII strings
1407 if type(rest_name) == unicode:
1408 rest_name = str(rest_name)
1409 if type(value) == unicode:
1410 value = str(value)
1411 field_name = field_info.name
1412 # FIXME: Hack to remap localhost controller node id alias to the actual
1413 # ID for the controller node. We shouldn't be doing this here (this code
1414 # shouldn't have anything about specific models), but it's the easiest
1415 # way to handle it for now and this code is likely going away sometime
1416 # pretty soon (written in May, 2012, let's see how long "pretty soon"
1417 # is :-) )
1418 if model_name == 'controller-node' and field_name == 'id' and value == 'localhost':
1419 value = get_local_controller_id()
1420 if type(field_info.django_field_info) == ForeignKey:
1421 field_name += '_id'
1422 converted_dict[field_name] = value
1423
1424 try:
1425 instance = model_info.model_class(**converted_dict)
1426 instance.full_clean()
1427 instance.save()
1428 except ValidationError, err:
1429 handle_validation_error(model_info, err)
1430 #raise RestValidationException(err)
1431 except Exception, e:
1432 # traceback.print_exc()
1433 raise RestSaveException(e)
1434
1435 end_batch_notification()
1436 elif request.method == 'DELETE':
1437 begin_batch_notification()
1438 for instance in instance_list:
1439 try:
1440 instance.delete()
1441 except ValidationError, err:
1442 handle_validation_error(model_info, err)
1443 except Exception, e:
1444 raise RestException(e)
1445 end_batch_notification()
1446 response_data = "deleted"
1447 response_content_type = 'text/plain'
1448 else:
1449 raise RestInvalidMethodException()
1450
1451 return HttpResponse(response_data, response_content_type)
1452
1453def synthetic_controller_interface(model_name, query_param_dict, json_response_data):
1454 # ---
1455 if model_name == 'controller-interface':
1456 # For controller-interfaces, when an ip address (netmask too)
1457 # is left unconfigured, then it may be possible to associate
1458 # ifconfig details with the interface.
1459 #
1460 # Since controller-interfaces has no mechanism to associate
1461 # specific ifconfig interfaces with rows, it's only possible to
1462 # associate ip's when a single unconfigured ip address exists,
1463 # using a process of elimination. For all ip address in the
1464 # ifconfig output, all statically configured controller-interface
1465 # items are removed. If only one result is left, and only
1466 # one controller-interface has an unconfigured ip address
1467 # (either a dhcp acquired address, or a static address where
1468 # the ip address is uncofigured), the ifconfig ip address
1469 # is very-likely to be the one associated with the
1470 # controller-interface row.
1471
1472 # Check the list of values to see if any are configured as dhcp
1473 dhcp_count = 0
1474 unconfigured_static_ip = 0
1475 this_host = get_local_controller_id()
1476
1477 for entry in json_response_data:
1478 if 'mode' in entry and entry['mode'] == 'dhcp' and \
1479 'controller' in entry and entry['controller'] == this_host:
1480 dhcp_count += 1
1481 if 'mode' in entry and entry['mode'] == 'static' and \
1482 'ip' in entry and entry['ip'] == '':
1483 unconfigured_static_ip += 1
1484 if dhcp_count + unconfigured_static_ip != 1:
1485 for entry in json_response_data:
1486 entry['found-ip'] = entry['ip']
1487 return
1488
1489 need_controller_query = False
1490 # determine whether the complete list of interfaces needs
1491 # to be collected to associate the dhcp address.
1492 for query_param_name, query_param_value in query_param_dict.items():
1493 if query_param_name != 'controller':
1494 need_controller_query = True
1495 if query_param_name == 'controller' and \
1496 query_param_value != this_host:
1497 need_controller_query = True
1498
1499 if need_controller_query == False:
1500 model_interfaces = [x for x in json_response_data
1501 if 'controller' in x and x['controller'] == this_host]
1502 else:
1503 # print 'need to collect all interfaces'
1504 filter_keyword_args = {'controller' : this_host}
1505 model_info = rest_model_info_dict.get(model_name)
1506 instance_list = model_info.model_class.objects.filter(**filter_keyword_args)
1507 response_data = []
1508 for instance in instance_list:
1509 data = {}
1510 for field_info in model_info.field_name_dict.values():
1511 field_name = field_info.name
1512 if type(field_info.django_field_info) == ForeignKey:
1513 field_name += '_id'
1514 value = instance.__dict__.get(field_name)
1515 if value != None:
1516 if field_info.json_serialize:
1517 value = str(value)
1518 data[field_info.rest_name] = value
1519 response_data.append(data)
1520 model_interfaces = response_data
1521
1522 # Recompute the number of dhcp configured interfaces,
1523 # model_interfaces is the collection of interface for 'this_host'
1524 dhcp_count = 0
1525 unconfigured_static_ip = 0
1526 for ifs in model_interfaces:
1527 if 'mode' in ifs and ifs['mode'] == 'dhcp':
1528 dhcp_count += 1
1529 if 'mode' in ifs and ifs['mode'] == 'static' and \
1530 'ip' in ifs and ifs['ip'] == '':
1531 unconfigured_static_ip += 1
1532
1533 if dhcp_count + unconfigured_static_ip != 1:
1534 # print "Sorry, %s dhcp + %s unconfigured static interfaces on %s" % \
1535 # (dhcp_count, unconfigured_static_ip, this_host)
1536 # copy over static ip's
1537 for entry in json_response_data:
1538 entry['found-ip'] = entry['ip']
1539 return
1540
1541 # collect current details for all the network interfaces
1542 inet4_ifs = _collect_system_interfaces()
1543
1544 # iterate over the model_interfaces's interfaces, and
1545 # remove ip addresses from inet4_ifs which are static, and
1546 # have the correct static value.
1547
1548 report_static = False
1549 match_id = ''
1550
1551 for ifs in model_interfaces:
1552 if 'mode' in ifs and ifs['mode'] == 'static':
1553 if 'ip' in ifs and ifs['ip'] == '':
1554 # print "Unconfigured static ip for %s", ifs['id']
1555 match_id = ifs['id']
1556 if 'ip' in ifs and ifs['ip'] != '':
1557 # find this address in the known addresses
1558 remove_entry = -1
1559 for index, inet4_if in enumerate(inet4_ifs):
1560 if inet4_if['addr'] == ifs['ip']:
1561 remove_entry = index
1562 break
1563 if remove_entry == -1:
1564 # print "Static ip %s not found" % ifs['ip']
1565 pass
1566 else:
1567 del inet4_ifs[remove_entry]
1568 elif 'mode' in ifs and ifs['mode'] == 'dhcp':
1569 match_id = ifs['id']
1570 else:
1571 # ought to assert here, not_reached()
1572 pass
1573
1574 # When only one entry is left in inet, its possible to do the assocation
1575 if len(inet4_ifs) != 1:
1576 # print "Incorrect number %s of inet4 interfaces left" % len(inet4_ifs)
1577 pass
1578
1579 for entry in json_response_data:
1580 entry['found-ip'] = entry['ip']
1581 entry['found-netmask'] = entry['netmask']
1582
1583 if entry['id'] == match_id:
1584 # make sure the address isn't set
1585 if entry['ip'] == '':
1586 entry['found-ip'] = inet4_ifs[0]['addr']
1587 entry['found-netmask'] = inet4_ifs[0]['netmask']
1588 entry['found-broadcast'] = inet4_ifs[0]['broadcast']
1589 else:
1590 # ought to assert here, not_reached()
1591 pass
1592
1593@safe_rest_view
1594def do_synthetic_instance(request, model_name, id=None):
1595
1596 if request.method != 'GET':
1597 raise RestInvalidMethodException()
1598
1599 # Lookup the model class associated with the specified name
1600 model_info = rest_model_info_dict.get(model_name)
1601 if not model_info:
1602 raise RestInvalidDataTypeException(model_name)
1603
1604 # Set up the keyword argument dictionary we use to filter the QuerySet.
1605 filter_keyword_args = {}
1606
1607 jsonp_prefix = None
1608 nolist = False
1609 order_by = None
1610
1611 # Now iterate over the query params and add further filter keyword arguments
1612 query_param_dict = request.GET
1613 for query_param_name, query_param_value in query_param_dict.items():
1614 add_query_param = False
1615 query_param_name = str(query_param_name)
1616 query_param_value = str(query_param_value)
1617 if query_param_name == 'callback': #switching to match up with jquery getJSON call naming convention.
1618 jsonp_prefix = query_param_value
1619 elif query_param_name == 'nolist':
1620 if query_param_value not in ('False', 'false', '0', ''):
1621 nolist = True
1622 elif query_param_name == 'orderby':
1623 order_by = query_param_value.split(',')
1624 for i in range(len(order_by)):
1625 name = order_by[i]
1626 if name.startswith('-'):
1627 descending = True
1628 name = name[1:]
1629 else:
1630 descending = False
1631 field_info = model_info.rest_name_dict.get(name)
1632 if not field_info:
1633 raise RestInvalidOrderByException(name)
1634 name = field_info.name
1635 if descending:
1636 name = '-' + name
1637 order_by[i] = name
1638 elif query_param_name in model_info.rest_name_dict:
1639 field_info = model_info.rest_name_dict.get(query_param_name)
1640 query_param_name = field_info.name
1641 add_query_param = True
1642 else:
1643 double_underscore_start = query_param_name.find("__")
1644 if double_underscore_start >= 0:
1645 rest_name = query_param_name[:double_underscore_start]
1646 field_info = model_info.rest_name_dict.get(rest_name)
1647 if field_info:
1648 operation = query_param_name[double_underscore_start:]
1649 query_param_name = field_info.name
1650 if type(field_info.django_field_info) == ForeignKey:
1651 query_param_name = query_param_name + '__' + field_info.django_field_info.rel.field_name
1652 # Substitute in the model field name for the (possible renamed) rest name
1653 query_param_name += operation
1654 add_query_param = True
1655 if add_query_param:
1656 filter_keyword_args[query_param_name] = query_param_value
1657
1658 if id != None:
1659 if len(filter_keyword_args) > 0:
1660 raise RestInvalidFilterParameterException(filter_keyword_args.keys()[0])
1661 try:
1662 get_args = {model_info.primary_key:id}
1663 instance = model_info.model_class.objects.get(**get_args)
1664 instance_list = (instance,)
1665 nolist = True
1666 except model_info.model_class.DoesNotExist,e:
1667 raise RestResourceNotFoundException(request.path)
1668 except model_info.model_class.MultipleObjectsReturned, exc:
1669 # traceback.print_exc()
1670 raise RestInternalException(exc)
1671 elif (request.method != 'PUT') or (len(filter_keyword_args) > 0):
1672 # Get the QuerySet based on the keyword arguments we constructed
1673 instance_list = model_info.model_class.objects.filter(**filter_keyword_args)
1674 if order_by:
1675 instance_list = instance_list.order_by(*order_by)
1676 else:
1677 # We're inserting new objects, so there's no need to do a query
1678 instance_list = None
1679
1680 response_content_type = JSON_CONTENT_TYPE
1681
1682 # Syntheric types only do requests --
1683 json_response_data = []
1684 for instance in instance_list:
1685 json_instance = {}
1686 for field_info in model_info.field_name_dict.values():
1687 # Made some minor edits to deal with a) fields that are empty and b) fields that are not strings -Kyle
1688 # Changed this to only do an explicit string conversion if it's a unicode string.
1689 # The controller is expecting to get the unstringified value (e.g. for boolean values)
1690 # Not sure if this will break things in the UI, but we'll need to resolve how
1691 # we want to handle this. Also, how do we want to handle unicode strings? -- robv
1692 field_name = field_info.name
1693 if type(field_info.django_field_info) == ForeignKey:
1694 field_name += '_id'
1695 value = instance.__dict__.get(field_name)
1696 if value != None:
1697 if field_info.json_serialize:
1698 value = str(value)
1699 json_instance[field_info.rest_name] = value
1700 json_response_data.append(json_instance)
1701
1702 # ---
1703 if model_name == 'controller-interface':
1704 synthetic_controller_interface(model_name, query_param_dict, json_response_data)
1705
1706 # Convert to json
1707 response_data = simplejson.dumps(json_response_data)
1708
1709 # If the nolist query param was enabled then check to make sure
1710 # that there was only a single instance in the response list and,
1711 # if so, unpack it from the list
1712 if nolist:
1713 if len(json_response_data) != 1:
1714 raise RestNoListResultException()
1715 json_response_data = json_response_data[0]
1716
1717 # If the jsonp query parameter was specified, wrap the data with
1718 # the jsonp prefix
1719 if jsonp_prefix:
1720 response_data = jsonp_prefix + '(' + response_data + ')'
1721 # We don't really know what the content type is here, but it's typically javascript
1722 response_content_type = TEXT_JAVASCRIPT_CONTENT_TYPE
1723
1724 return HttpResponse(response_data, response_content_type)
1725
1726@safe_rest_view
1727def do_user_data_list(request):
1728 # Now iterate over the query params and add any valid filter keyword arguments
1729 filter_keyword_args = {}
1730 for query_param_name, query_param_value in request.GET.items():
1731 query_param_name = str(query_param_name)
1732 double_underscore_start = query_param_name.find("__")
1733 if double_underscore_start >= 0:
1734 attribute_name = query_param_name[:double_underscore_start]
1735 else:
1736 attribute_name = query_param_name
1737
1738 # In the future, if we add support for things like mod_date, creation_date, etc.
1739 # which would be supported in query params, then they'd be added to this list/tuple.
1740 if attribute_name not in ('name',):
1741 raise RestInvalidFilterParameterException(query_param_name)
1742 filter_keyword_args[query_param_name] = query_param_value
1743
1744 instance_list = UserData.objects.filter(**filter_keyword_args)
1745
1746 if request.method == 'GET':
1747 user_data_info_list = []
1748
1749 # FIXME: robv: It's incorrect to *always* add this to the user data,
1750 # because it means we're not respecting the filter query parameters.
1751 # To work completely correctly we'd need to duplicate a lot of logic
1752 # for processing the query parameters, which would be tedious.
1753 # Should talk to Mandeep about why it was done this way. Maybe we
1754 # should expose these special cases in a different URL/view.
1755 for fn in ['startup-config', 'upgrade-config']:
1756 try:
1757 sc = "%s/run/%s" % (sdncon.SDN_ROOT, fn)
1758 f = open(sc, 'r')
1759 f.close()
1760 t = time.strftime("%Y-%m-%d.%H:%M:%S",
1761 time.localtime(os.path.getmtime(sc)))
1762 instance_name = fn + '/timestamp=' + t + \
1763 '/version=1/length=' + \
1764 str(os.path.getsize(sc))
1765 url_path = 'rest/v1/data/' + instance_name + '/'
1766
1767 user_data_info = { 'name' : instance_name,
1768 'url_path' : url_path, }
1769 user_data_info_list.append(user_data_info)
1770 except:
1771 pass
1772
1773 for instance in instance_list:
1774 user_data_info = {'name': instance.name,
1775 'url_path': 'rest/v1/data/' + instance.name + '/'}
1776 user_data_info_list.append(user_data_info)
1777
1778 response_data = simplejson.dumps(user_data_info_list)
1779 elif request.method == 'DELETE':
1780 instance_list.delete()
1781 response_data = {}
1782 response_data['status'] = 'success'
1783 response_data['message'] = 'user data deleted'
1784 response_data = simplejson.dumps(response_data)
1785 response_content_type = JSON_CONTENT_TYPE
1786 else:
1787 raise RestInvalidMethodException()
1788
1789 return HttpResponse(response_data, JSON_CONTENT_TYPE)
1790
1791@safe_rest_view
1792def do_user_data(request, name):
1793 query_param_dict = request.GET
1794 #
1795 # Manage startup-config/update-config differently
1796 if name.find('/') >= 0 and \
1797 name.split('/')[0] in ['startup-config', 'upgrade-config']:
1798 path = "%s/run/%s" % (sdncon.SDN_ROOT, name.split('/')[0])
1799 response_data = {}
1800
1801 if request.method == 'GET':
1802 with open(path, 'r') as f:
1803 response_data = f.read()
1804 response_content_type = "text/plain"
1805 elif request.method == 'PUT':
1806 try:
1807 with open(path, 'w') as f:
1808 f.write(request.raw_post_data)
1809 response_data['status'] = 'success'
1810 response_data['message'] = 'user data updated'
1811 except:
1812 response_data['status'] = 'failure'
1813 response_data['message'] = "can't write file"
1814 response_content_type = JSON_CONTENT_TYPE
1815 response_data = simplejson.dumps(response_data)
1816 elif request.method == 'DELETE':
1817 try:
1818 f = open(path, "r")
1819 f.close()
1820 except:
1821 raise RestResourceNotFoundException(request.path)
1822
1823 try:
1824 os.remove(path)
1825 response_data['status'] = 'success'
1826 response_data['message'] = 'user data deleted'
1827 except:
1828 response_data['status'] = 'failure'
1829 response_data['message'] = "can't delete file"
1830 response_data = simplejson.dumps(response_data)
1831 response_content_type = JSON_CONTENT_TYPE
1832 else:
1833 raise RestInvalidMethodException()
1834
1835 return HttpResponse(response_data, response_content_type)
1836
1837
1838 # Default values for optional query parameters
1839 #private = False
1840 binary = False
1841
1842 for param_name, param_value in query_param_dict.items():
1843 if param_name == 'binary':
1844 if request.method != 'PUT':
1845 raise RestInvalidQueryParameterException(name)
1846 binary = param_value.lower() == 'true' or param_value == '1'
1847 #elif param_name == 'private':
1848 # private = param_value
1849 else:
1850 raise RestInvalidQueryParameterException(param_name)
1851
1852 # FIXME: Need HTTP basic/digest auth support for the following
1853 # code to work.
1854 #if private:
1855 # user = request.user
1856 #else:
1857 # user = None
1858 #if user != None and not user.is_authenticated():
1859 # raise RestAuthenticationRequiredException()
1860 user = None
1861
1862 # There's currently an issue with filtering on the user when using the
1863 # Cassandra database backend. Since we don't support private per-user
1864 # data right now, I'm just disabling filtering on the user and only
1865 # filter on the name
1866 #user_data_query_set = UserData.objects.filter(user=user, name=name)
1867 user_data_query_set = UserData.objects.filter(name=name)
1868
1869 count = user_data_query_set.count()
1870 if count > 1:
1871 raise RestInternalException('Duplicate user data values for the same name')
1872
1873 if request.method == 'GET':
1874 if count == 0:
1875 raise RestResourceNotFoundException(request.path)
1876 user_data = user_data_query_set[0]
1877 response_data = user_data.data
1878 if user_data.binary:
1879 response_data = base64.b64decode(response_data)
1880 response_content_type = user_data.content_type
1881 elif request.method == 'PUT':
1882 content_type = request.META['CONTENT_TYPE']
1883 if content_type == None:
1884 if binary:
1885 content_type = BINARY_DATA_CONTENT_TYPE
1886 else:
1887 content_type = JSON_CONTENT_TYPE
1888 response_data = {}
1889 if count == 1:
1890 response_data['status'] = 'success'
1891 response_data['message'] = 'user data updated'
1892 user_data = user_data_query_set[0]
1893 else:
1894 response_data['status'] = 'success'
1895 response_data['message'] = 'user data created'
1896 user_data = UserData(user=user,name=name)
1897 user_data.binary = binary
1898 user_data.content_type = content_type
1899 data = request.raw_post_data
1900 if binary:
1901 data = base64.b64encode(data)
1902 user_data.data = data
1903 user_data.save()
1904 response_data = simplejson.dumps(response_data)
1905 response_content_type = JSON_CONTENT_TYPE
1906 elif request.method == 'DELETE':
1907 if count == 0:
1908 raise RestResourceNotFoundException(request.path)
1909 user_data = user_data_query_set[0]
1910 user_data.delete()
1911 response_data = {}
1912 response_data['status'] = 'success'
1913 response_data['message'] = 'user data deleted'
1914 response_data = simplejson.dumps(response_data)
1915 response_content_type = JSON_CONTENT_TYPE
1916 else:
1917 raise RestInvalidMethodException()
1918
1919 return HttpResponse(response_data, response_content_type)
1920
1921@safe_rest_view
1922def do_sdnplatform_tunnel_manager(request, dpid=None):
1923 """
1924 This returns realtime statistics from sdnplatform
1925 """
1926
1927 if request.method != 'GET':
1928 raise RestInvalidMethodException()
1929 if dpid == None:
1930 raise RestInvalidMethodException()
1931
1932 print 'DPID', dpid
1933 if dpid == 'all':
1934 url = controller_url('vns', 'tunnel-manager', 'all', 'json')
1935 else:
1936 url = controller_url('vns', 'tunnel-manager', 'switch='+dpid, 'json')
1937
1938 response_text = urllib2.urlopen(url).read()
1939 entries = simplejson.loads(response_text)
1940
1941 if 'error' in entries and entries['error'] != None:
1942 RestInternalException(entries['error'])
1943
1944 return HttpResponse(json.dumps(entries['tunnMap']), JSON_CONTENT_TYPE)
1945
1946@safe_rest_view
1947def do_sdnplatform_controller_summary(request):
1948 """
1949 This returns summary statistics from sdnplatform modules
1950 """
1951 if request.method != 'GET':
1952 raise RestInvalidMethodException()
1953
1954 url = controller_url('core', 'controller', 'summary', 'json')
1955 return get_sdnplatform_response(url)
1956
1957def filter_queries(choice_list, param_dict):
1958 return dict([[x, param_dict[x]] for x in choice_list
1959 if x in param_dict and param_dict[x] != 'all'])
1960
1961@safe_rest_view
1962def do_reload(request):
1963 """
1964 This calls an oswrapper that reloads the box.
1965 """
1966 if request.method != 'GET':
1967 raise RestInvalidMethodException()
1968 exec_os_wrapper("ReloadController", 'set', [])
1969 response_text = '{"status":"reloading"}'
1970 return HttpResponse(response_text, JSON_CONTENT_TYPE)
1971
1972@safe_rest_view
1973def do_resetbsc(request):
1974 """
1975 This calls an oswrapper that resets the box.
1976 """
1977 if request.method != 'PUT':
1978 raise RestInvalidMethodException()
1979 exec_os_wrapper("ResetBsc", 'set', [])
1980 response_text = '{"status":"resetting"}'
1981 return HttpResponse(response_text, JSON_CONTENT_TYPE)
1982
1983@safe_rest_view
1984def do_abort_upgrade(request):
1985 """
1986 This calls an oswrapper that reloads the box.
1987 """
1988 if request.method != 'PUT':
1989 raise RestInvalidMethodException()
1990 controller_id = get_local_controller_id()
1991 controller = Controller.objects.get(id=controller_id)
1992 if controller.status != 'Upgrading':
1993 raise RestUpgradeException("No Upgrade pending")
1994 exec_os_wrapper("AbortUpgrade", 'set', [])
1995 controller.status = 'Ready'
1996 controller.save()
1997 response_text = '{"status":"Ready"}'
1998 return HttpResponse(response_text, JSON_CONTENT_TYPE)
1999
2000@safe_rest_view
2001def do_config_rollback(request):
2002 data = simplejson.loads(request.raw_post_data)
2003 path = data['path']
2004 print "Executing config rollback with config @", path
2005
2006 if request.method != 'PUT':
2007 raise RestInvalidMethodException()
2008 exec_os_wrapper("RollbackConfig", 'set', [path])
2009 response_text = get_successful_response_data('prepared config rollbacked')
2010 return HttpResponse(response_text, JSON_CONTENT_TYPE)
2011
2012@safe_rest_view
2013def do_upload_data(request):
2014 if request.method != 'PUT':
2015 raise RestInvalidMethodException()
2016 data = simplejson.loads(request.raw_post_data)
2017 content = data['data']
2018 path = data['dst']
2019 print "Executing config rollback with config @", path
2020
2021 exec_os_wrapper("WriteDataToFile", 'set', [content, path])
2022 response_text = get_successful_response_data('written data')
2023 return HttpResponse(response_text, JSON_CONTENT_TYPE)
2024
2025@safe_rest_view
2026def do_diff_config(request):
2027 if request.method != 'PUT':
2028 raise RestInvalidMethodException()
2029 data = simplejson.loads(request.raw_post_data)
2030 config1 = data['config-1']
2031 config2 = data['config-2']
2032 print "diffing '%s' with '%s'" %(config1, config2)
2033
2034 result = exec_os_wrapper("DiffConfig", 'set', [config1, config2])
2035 return HttpResponse(simplejson.dumps(result), JSON_CONTENT_TYPE)
2036
2037@safe_rest_view
2038def do_extract_upgrade_pkg_manifest(request):
2039 """
2040 This calls an oswrapper that extracts the upgrade package.
2041 This returns the install package 'manifest'.
2042 """
2043 if request.method != 'GET':
2044 raise RestInvalidMethodException()
2045 exec_os_wrapper("GetLatestUpgradePkg", 'get', [])
2046 output = exec_os_wrapper("CatUpgradeImagesFile", 'get')
2047 upgradePkg = output['out'].strip()
2048 exec_os_wrapper("ExtractUpgradePkgManifest", 'set', [upgradePkg])
2049 output = exec_os_wrapper("ExtractUpgradePkgManifest", 'get')
2050 manifest = output['out']
2051 return HttpResponse(manifest, JSON_CONTENT_TYPE)
2052
2053@safe_rest_view
2054def do_extract_upgrade_pkg(request):
2055 if request.method != 'GET':
2056 raise RestInvalidMethodException()
2057 exec_os_wrapper("GetLatestUpgradePkg", 'get', [])
2058 output = exec_os_wrapper("CatUpgradeImagesFile", 'get')
2059 upgradePkg = output['out'].strip()
2060 exec_os_wrapper("ExtractUpgradePkg", 'set', [upgradePkg])
2061 return HttpResponse('{"status": "OK"}', JSON_CONTENT_TYPE)
2062
2063@safe_rest_view
2064def do_get_upgrade_pkg(request):
2065 """
2066 This calls an oswrapper to get the latest upgrade
2067 package uploaded to the controller.
2068 """
2069 if request.method != 'GET':
2070 raise RestInvalidMethodException()
2071 exec_os_wrapper("GetLatestUpgradePkg", 'get')
2072 result = exec_os_wrapper("CatUpgradeImagesFile", 'get')
2073 jsondict = {'file': result['out'].strip()}
2074 return HttpResponse(simplejson.dumps(jsondict), JSON_CONTENT_TYPE)
2075
2076@safe_rest_view
2077def do_cleanup_old_pkgs(request):
2078 if request.method != 'GET':
2079 raise RestInvalidMethodException()
2080 exec_os_wrapper("CleanupOldUpgradeImages", 'get')
2081 return HttpResponse('{"status": "OK"}', JSON_CONTENT_TYPE)
2082
2083@safe_rest_view
2084def do_execute_upgrade_step(request):
2085 """
2086 Executes a particular upgrade step according to the
2087 upgrade package manifest.
2088 """
2089 if request.method != 'PUT':
2090 raise RestInvalidMethodException()
2091
2092 put_data = json.loads(request.raw_post_data)
2093 imageName = put_data.get('imageName')
2094 stepNum = put_data.get('step')
2095 force = put_data.get('force')
2096
2097 args = [stepNum, imageName]
2098 if force:
2099 args.append("--force")
2100 result = exec_os_wrapper("ExecuteUpgradeStep", 'get',
2101 args)
2102 jsondict = {}
2103 if len(str(result['err']).strip()) > 0:
2104 jsondict['status'] = "ERROR"
2105 jsondict['description'] = str(result['err']).strip()
2106 else:
2107 jsondict['status'] = "OK"
2108 jsondict['description'] = str(result['out']).strip()
2109
2110 return HttpResponse(simplejson.dumps(jsondict), JSON_CONTENT_TYPE)
2111