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