Base net-virt CLI files on top of which ONOS specific changes will be done
diff --git a/cli/sdncon/rest/views.py b/cli/sdncon/rest/views.py
new file mode 100755
index 0000000..6355c35
--- /dev/null
+++ b/cli/sdncon/rest/views.py
@@ -0,0 +1,2054 @@
+#
+# Copyright (c) 2013 Big Switch Networks, Inc.
+#
+# Licensed under the Eclipse Public License, Version 1.0 (the
+# "License"); you may not use this file except in compliance with the
+# License. You may obtain a copy of the License at
+#
+# http://www.eclipse.org/legal/epl-v10.html
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# permissions and limitations under the License.
+#
+
+from django.db.models import AutoField, BooleanField, IntegerField, FloatField, ForeignKey
+from django.forms import ValidationError
+from django.http import HttpResponse
+from django.utils import simplejson
+from functools import wraps
+from sdncon.controller.models import Controller, ControllerAlias, ControllerDomainNameServer, ControllerInterface, FirewallRule, Switch, Port, PortAlias, Link
+from sdncon.rest.models import UserData
+from sdncon.controller.notification import begin_batch_notification, end_batch_notification
+from django.views.decorators.csrf import csrf_exempt
+import base64
+import urllib
+import urllib2
+import sys
+import json
+import os
+import re
+import time
+import traceback
+import collections
+import uuid
+import subprocess
+from datetime import datetime
+from sdncon.controller.oswrapper import exec_os_wrapper
+from sdncon.controller.config import get_local_controller_id
+from sdncon.rest.config import config_check_state
+from sdncon.controller.oswrapper import get_system_version_string
+import sdncon
+
+TEXT_PLAIN_CONTENT_TYPE = 'text/plain'
+TEXT_JAVASCRIPT_CONTENT_TYPE = 'text/javascript'
+JSON_CONTENT_TYPE = 'application/json'
+BINARY_DATA_CONTENT_TYPE = 'application/octet-stream'
+
+CONTROLLER_URL_PREFIX = 'http://localhost:8080/wm/'
+
+def controller_url(*elements):
+ return CONTROLLER_URL_PREFIX + '/'.join(elements)
+
+class RestException(Exception):
+ pass
+
+class RestInvalidDataTypeException(RestException):
+ def __init__(self, name):
+ super(RestInvalidDataTypeException,self).__init__('Invalid data type: ' + name)
+
+class RestInvalidMethodException(RestException):
+ def __init__(self):
+ super(RestInvalidMethodException,self).__init__('Invalid HTTP method')
+
+class RestResourceNotFoundException(RestException):
+ def __init__(self, url):
+ super(RestResourceNotFoundException,self).__init__('Resource not found: ' + url)
+
+class RestDatabaseConnectionException(RestException):
+ def __init__(self):
+ super(RestDatabaseConnectionException,self).__init__('Error connecting to database')
+
+class RestAuthenticationRequiredException(RestException):
+ def __init__(self):
+ super(RestAuthenticationRequiredException,self).__init__('Authentication required')
+
+class RestInvalidQueryParameterException(RestException):
+ def __init__(self, param_name):
+ super(RestInvalidQueryParameterException, self).__init__('Invalid query parameter: ' + str(param_name))
+
+class RestInvalidFilterParameterException(RestException):
+ def __init__(self, param_name):
+ super(RestInvalidFilterParameterException, self).__init__('Filter query parameters not allowed when URL contains resource iD: ' + str(param_name))
+
+class RestNoListResultException(RestException):
+ def __init__(self):
+ super(RestNoListResultException, self).__init__('The query result must be a single instance if the "nolist" query param is set')
+
+class RestInvalidPutDataException(RestException):
+ def __init__(self):
+ super(RestInvalidPutDataException, self).__init__('The request data for a PUT request must be a JSON dictionary object')
+
+class RestMissingRequiredQueryParamException(RestException):
+ def __init__(self, param_name):
+ super(RestMissingRequiredQueryParamException, self).__init__('Missing required query parameter: ' + str(param_name))
+
+class RestValidationException(RestException):
+ def __init__(self, model_error=None, field_errors=None):
+ # Build the exception message from model error and field errors
+ message = 'Validation error'
+ if model_error:
+ message = message + '; ' + model_error
+ if field_errors:
+ message += '; invalid fields: {'
+ first_time = True
+ for field_name, field_message in field_errors.items():
+ if not first_time:
+ message += '; '
+ else:
+ first_time = False
+ message = message + field_name + ': ' + field_message
+
+ message += '}'
+
+ super(RestValidationException, self).__init__(message)
+ self.model_error = model_error
+ self.field_errors = field_errors
+
+class RestModelException(RestException):
+ def __init__(self, exc):
+ super(RestModelException, self).__init__('Error: ' + str(exc))
+
+class RestSaveException(RestException):
+ def __init__(self, exc):
+ super(RestSaveException, self).__init__('Error saving data: ' + str(exc))
+
+class RestInvalidOrderByException(RestException):
+ def __init__(self,field_name):
+ super(RestInvalidOrderByException, self).__init__('Invalid orderby field: ' + field_name)
+
+class RestInternalException(RestException):
+ def __init__(self, exc):
+ super(RestInternalException,self).__init__('Unknown REST error: ' + unicode(exc))
+
+class RestUpgradeException(RestException):
+ def __init__(self, exc):
+ super(RestUpgradeException, self).__init__('Error: ' + str(exc))
+
+class RestProvisionException(RestException):
+ def __init__(self, exc):
+ super(RestProvisionException, self).__init__('Error: ' + str(exc))
+
+class RestDecommissionException(RestException):
+ def __init__(self, exc):
+ super(RestDecommissionException, self).__init__('Error: ' + str(exc))
+
+class RestInvalidLog(RestException):
+ def __init__(self, exc):
+ super(RestInvalidLog, self).__init__('Error: ' + str(exc))
+
+def handle_validation_error(model_info, validation_error):
+ model_error = None
+ field_errors = None
+ if hasattr(validation_error, 'message_dict'):
+ # The field errors we get in the ValidationError are a bit different
+ # then what we want for the RestValidationException. First, we
+ # need to convert the Django field name to the (possibly renamed)
+ # REST field name. Second, the per-field error message is possibly a
+ # list of messages, which we concatenate into a single string for the
+ # RestValidationException
+ for field_name, field_message in validation_error.message_dict.items():
+ if type(field_message) in (list, tuple):
+ converted_field_message = ''
+ for msg in field_message:
+ converted_field_message = converted_field_message + msg + ' '
+ else:
+ converted_field_message += unicode(field_message)
+ if field_name == '__all__':
+ model_error = converted_field_message
+ else:
+ if not field_errors:
+ field_errors = {}
+ field_info = model_info.field_name_dict.get(field_name)
+ if field_info:
+ field_errors[field_info.rest_name] = converted_field_message
+ else:
+ field_errors[field_name] = 'Private field invalid; ' + converted_field_message
+ elif hasattr(validation_error, 'messages'):
+ model_error = ':'.join(validation_error.messages)
+ else:
+ model_error = str(validation_error)
+ raise RestValidationException(model_error, field_errors)
+
+def get_successful_response(description=None, status_code=200):
+ content = get_successful_response_data(description)
+ return HttpResponse(content, JSON_CONTENT_TYPE, status_code)
+
+def get_successful_response_data(description = 'success'):
+ obj = {'description': description}
+ return simplejson.dumps(obj)
+
+def get_sdnplatform_response(url, timeout = None):
+
+ try:
+ response_text = urllib2.urlopen(url, timeout=timeout).read()
+ return HttpResponse(response_text, JSON_CONTENT_TYPE)
+ except urllib2.HTTPError, e:
+ response_text = e.read()
+ response = simplejson.loads(response_text)
+ response['error_type'] = "SDNPlatformError"
+ return HttpResponse(content=simplejson.dumps(response),
+ status=e.code,
+ content_type=JSON_CONTENT_TYPE)
+
+def get_sdnplatform_query(request, path):
+ """
+ This returns controller-level storage table list
+ """
+ if request.method != 'GET':
+ raise RestInvalidMethodException()
+ url = controller_url(path) + '/?%s' % request.META['QUERY_STRING']
+ return get_sdnplatform_response(url)
+
+def safe_rest_view(func):
+ """
+ This is a decorator that takes care of exception handling for the
+ REST views so that we return an appropriate error HttpResponse if
+ an exception is thrown from the view
+ """
+ @wraps(func)
+ def _func(*args, **kwargs):
+ try:
+ response = func(*args, **kwargs)
+ except Exception, exc:
+ end_batch_notification(True)
+ if not isinstance(exc, RestException):
+ # traceback.print_exc()
+ exc = RestInternalException(exc)
+ response_obj = {'error_type': exc.__class__.__name__, 'description': unicode(exc)}
+ if isinstance(exc, RestValidationException):
+ if exc.model_error:
+ response_obj['model_error'] = exc.model_error
+ if exc.field_errors:
+ response_obj['field_errors'] = exc.field_errors
+ content = simplejson.dumps(response_obj)
+ content_type = JSON_CONTENT_TYPE
+
+ if isinstance(exc, RestInvalidMethodException):
+ status_code = 405
+ elif isinstance(exc, RestResourceNotFoundException):
+ status_code = 404
+ elif isinstance(exc, RestInternalException):
+ status_code = 500
+ else:
+ status_code = 400
+ response = HttpResponse(content, content_type, status_code)
+ if isinstance(exc, RestInvalidMethodException):
+ response['Allow'] = "GET, PUT, DELETE"
+ return response
+ return _func
+
+rest_model_info_dict = {}
+
+class RestFieldInfo(object):
+ def __init__(self, name, django_field_info, hidden=False,
+ rest_name=None, json_serialize=False):
+ self.name = name
+ self.django_field_info = django_field_info
+ self.rest_name = rest_name
+ self.json_serialize = json_serialize
+
+class RestModelInfo(object):
+ def __init__(self, rest_name, model_class):
+ self.rest_name = rest_name
+ self.model_class = model_class
+ self.primary_key = None
+ self.field_name_dict = {}
+ self.rest_name_dict = {}
+
+ for field in model_class._meta.local_fields:
+ field_name = field.name
+ rest_name = field.name
+ if field.primary_key:
+ self.primary_key = field_name
+ # TODO: Are there other field types that should be included here?
+ json_serialize = type(field) not in (AutoField, BooleanField, IntegerField, FloatField)
+ self.set_field_info(field_name, rest_name, field, json_serialize)
+
+
+ # this is how a RestFieldInfo is created - pass in django_field_info
+ def get_field_info(self, field_name, django_field_info=None):
+ field_info = self.field_name_dict.get(field_name)
+ if not field_info and django_field_info:
+ field_info = RestFieldInfo(field_name, django_field_info)
+ self.field_name_dict[field_name] = field_info
+ return field_info
+
+ def hide_field(self, field_name):
+ field_info = self.get_field_info(field_name)
+ del self.field_name_dict[field_name]
+ del self.rest_name_dict[field_info.rest_name]
+
+ def set_field_info(self, field_name, rest_name, django_field_info, json_serialize=None):
+ field_info = self.get_field_info(field_name, django_field_info)
+ if field_info.rest_name in self.rest_name_dict:
+ del self.rest_name_dict[field_info.rest_name]
+ field_info.rest_name = rest_name
+ if json_serialize != None:
+ field_info.json_serialize = json_serialize
+ self.rest_name_dict[rest_name] = field_info
+
+def get_rest_model_info(name):
+ return rest_model_info_dict[name]
+
+def add_rest_model_info(info):
+ if rest_model_info_dict.get(info.rest_name):
+ raise RestException('REST model info already exists')
+ rest_model_info_dict[info.rest_name] = info
+
+rest_initialized = False
+
+def get_default_rest_name(model):
+ # TODO: Ideally should do something a bit smarter here.
+ # Something like convert from camel-case class names to hyphenated names:
+ # For example:
+ # MyTestClass => my-test-class
+ # MyURLClass => my-url-class
+ #
+ # This isn't super-important for now, since you can set it explicitly
+ # with the nested Rest class.
+ return model.__name__.lower()
+
+def initialize_rest():
+ global rest_initialized
+ if rest_initialized:
+ return
+
+ from django.db.models import get_models
+ for model in get_models():
+
+ # If the model class has a nested class named 'Rest' then that means
+ # the model should be exposed in the REST API.
+ if hasattr(model, 'Rest'):
+ # By default the REST API uses the lower-case-converted name
+ # of the model class as the name in the REST URL, but this can
+ # be overridden by defining the 'NAME' attribute in the Rest class.
+ if hasattr(model.Rest, 'NAME'):
+ rest_name = model.Rest.NAME
+ else:
+ rest_name = get_default_rest_name(model)
+
+ if model._meta.proxy:
+ # This is a proxy class, drop through to the real one
+ base_model = model._meta.proxy_for_model
+ else:
+ base_model = model
+
+ # OK, we have the basic REST info, so we can create the info class
+ rest_model_info = RestModelInfo(rest_name, base_model)
+
+ # Check if there are any private or renamed fields
+ if hasattr(model.Rest, 'FIELD_INFO'):
+ for field_info in model.Rest.FIELD_INFO:
+ field_name = field_info['name']
+ rest_field_info = rest_model_info.get_field_info(field_name)
+ # Check if field exists in models - don't allow field only here in FIELD_INFO)
+ if not rest_field_info:
+ # LOOK! This field only exists in FIELD_INFO - skip
+ print "ERROR: %s for %s only in FIELD_INFO" % (field_name, rest_name)
+ continue
+
+ if field_info.get('private', False):
+ rest_model_info.hide_field(field_name)
+ else:
+ rest_name = field_info.get('rest_name')
+ if rest_name:
+ rest_model_info.set_field_info(field_name, rest_name, rest_field_info.django_field_info)
+
+ # Finished setting it up, so now add it to the list
+ add_rest_model_info(rest_model_info)
+
+ rest_initialized = True
+
+initialize_rest()
+
+@safe_rest_view
+def do_model_list(request):
+ """
+ This returns the list of models available in the REST API.
+ """
+
+ json_model_list = []
+ for model_name in rest_model_info_dict.keys():
+ json_model_info = {}
+ json_model_info["name"] = model_name
+ json_model_info["url_path"] = "rest/v1/model/" + model_name + "/"
+ json_model_list.append(json_model_info)
+
+ json_data = simplejson.dumps(json_model_list)
+ return HttpResponse(json_data, JSON_CONTENT_TYPE)
+
+@safe_rest_view
+def do_realtimestats(request, stattype, dpid):
+ """
+ This returns realtime statistics (flows, ports, table, aggregate,
+ desc, ...) for a dpid by calling the localhost sdnplatform
+ """
+ if request.method != 'GET':
+ raise RestInvalidMethodException()
+ url = controller_url('core', 'switch', dpid, stattype, 'json')
+ return get_sdnplatform_response(url)
+
+@safe_rest_view
+def do_sdnplatform_realtimestats(request, stattype, dpid=None, portid=None):
+ """
+ This returns realtime statistics from sdnplatform
+ """
+ if request.method != 'GET':
+ raise RestInvalidMethodException()
+ if dpid == None:
+ url = controller_url('core', 'counter', stattype, 'json')
+ elif portid == None:
+ url = controller_url('core', 'counter', dpid, stattype, 'json')
+ else:
+ url = controller_url('core', 'counter', dpid, portid, stattype, 'json')
+ return get_sdnplatform_response(url)
+
+@safe_rest_view
+def do_topology_tunnel_verify(request, srcdpid=None, dstdpid=None):
+ """
+ This initiates a liveness detection of tunnels.
+ """
+ if request.method != 'GET':
+ raise RestInvalidMethodException()
+
+ urlstring = srcdpid + '/' + dstdpid
+ url = controller_url('topology/tunnelverify', urlstring, 'json')
+
+ response_text = urllib2.urlopen(url).read()
+ time.sleep(4)
+ return do_topology_tunnel_status(request, srcdpid, dstdpid)
+
+@safe_rest_view
+def do_topology_tunnel_status(request, srcdpid='all', dstdpid='all'):
+ """
+ This returns the list of tunnels that have failed over the last observation interval.
+ """
+ if request.method != 'GET':
+ raise RestInvalidMethodException()
+
+ urlstring = srcdpid + '/' + dstdpid
+ url = controller_url('topology/tunnelstatus', urlstring, 'json')
+ response_text = urllib2.urlopen(url).read()
+ return HttpResponse(response_text, JSON_CONTENT_TYPE)
+
+@safe_rest_view
+def do_sdnplatform_realtimestatus(request, category=None, subcategory=None, srcdpid=None, dstdpid = None):
+ """
+ This returns realtime status of sdnplatform
+ """
+
+ if request.method != 'GET':
+ raise RestInvalidMethodException()
+
+ response_text = None
+ url = None
+ if category == 'network':
+ if subcategory == 'cluster':
+ url = controller_url('topology', 'switchclusters', 'json')
+ if subcategory == 'externalports':
+ url = controller_url('topology', 'externalports', 'json')
+ if subcategory == 'tunnelverify':
+ urlstring = subcategory+ '/' + srcdpid + '/' + dstdpid
+ url = controller_url('topology', urlstring, 'json')
+ if subcategory == 'tunnelstatus':
+ url = controller_url('topology', 'tunnelstatus', 'json')
+
+ if url:
+ response_text = urllib2.urlopen(url).read()
+ return HttpResponse(response_text, JSON_CONTENT_TYPE)
+
+@safe_rest_view
+def do_sdnplatform_realtimetest(http_request, category=None, subcategory=None):
+ """
+ This does a realtime test by sending an "explain packet" as packet in
+ and collecting the operations performed on the packet
+ """
+
+ if http_request.method != 'PUT':
+ raise RestInvalidMethodException()
+
+ response_text = None
+ url = None
+ if category == 'network':
+ if subcategory == 'explain-packet':
+ # set up the sdnplatform URL for explain packet (at internal port 8080
+ url = controller_url('vns', 'explain-packet', 'json')
+ post_data = http_request.raw_post_data
+ request = urllib2.Request(url, post_data)
+ request.add_header('Content-Type', 'application/json')
+ response = urllib2.urlopen(request)
+ response_text = response.read()
+ elif subcategory == 'path':
+ post_data = json.loads(http_request.raw_post_data)
+ url = controller_url('topology', 'route',
+ post_data['src-switch'],
+ str(post_data['src-switch-port']),
+ post_data['dst-switch'],
+ str(post_data['dst-switch-port']),
+ 'json')
+
+ return get_sdnplatform_response(url)
+
+ return HttpResponse(response_text, JSON_CONTENT_TYPE)
+
+@safe_rest_view
+def do_sdnplatform_performance_monitor(http_request, category=None,
+ subcategory=None, type='all'):
+ """
+ This API returns performance related information from the sdnplatform and
+ sdnplatform components
+ """
+
+ if http_request.method != 'GET':
+ raise RestInvalidMethodException()
+
+ response_text = None
+ url = None
+ if category == 'performance-monitor':
+ # set up the sdnplatform URL for explain packet (at internal port 8080
+ url = controller_url('performance', type, 'json')
+ request = urllib2.Request(url)
+ response = urllib2.urlopen(request)
+ response_text = response.read()
+ return HttpResponse(response_text, JSON_CONTENT_TYPE)
+
+@safe_rest_view
+def do_sdnplatform_internal_debugs(http_request, category=None, subcategory=None,
+ query='all', component='device-manager'):
+ """
+ This API returns debugging related information from the sdnplatform and
+ sdnplatform components
+ """
+
+ if http_request.method != 'GET':
+ raise RestInvalidMethodException()
+
+ response_text = None
+ url = None
+ if category == 'internal-debugs':
+ # set up the sdnplatform URL for explain packet (at internal port 8080
+ url = controller_url('vns', 'internal-debugs', component, query, 'json')
+ request = urllib2.Request(url)
+ response = urllib2.urlopen(request)
+ response_text = response.read()
+ return HttpResponse(response_text, JSON_CONTENT_TYPE)
+
+@safe_rest_view
+def do_sdnplatform_event_history(http_request, category=None, subcategory=None,
+ evHistName='all', count='100'):
+ """
+ This API returns real-time event-history information from the sdnplatform and
+ sdnplatform components
+ """
+
+ if http_request.method != 'GET':
+ raise RestInvalidMethodException()
+
+ response_text = None
+ url = None
+ if category == 'event-history':
+ # set up the sdnplatform URL for explain packet (at internal port 8080
+ url = controller_url('core', 'event-history', evHistName, count, 'json')
+ request = urllib2.Request(url)
+ response = urllib2.urlopen(request)
+ response_text = response.read()
+ return HttpResponse(response_text, JSON_CONTENT_TYPE)
+
+@safe_rest_view
+def do_flow_cache(http_request, category=None, subcategory=None,
+ applName='None', applInstName='all', queryType='all'):
+ """
+ This API returns real-time event-history information from the sdnplatform and
+ sdnplatform components
+ """
+
+ if http_request.method != 'GET':
+ raise RestInvalidMethodException()
+
+ response_text = None
+ url = None
+ if category == 'flow-cache':
+ # set up the sdnplatform URL for explain packet (at internal port 8080
+ url = controller_url('vns', 'flow-cache', applName, applInstName, queryType, 'json')
+ request = urllib2.Request(url)
+ response = urllib2.urlopen(request)
+ response_text = response.read()
+ return HttpResponse(response_text, JSON_CONTENT_TYPE)
+
+
+@safe_rest_view
+def do_vns_realtimestats_flow(http_request, category=None, vnsName="all"):
+ """
+ This gets realtime flows for one or more vnses
+ """
+
+ if http_request.method != 'GET':
+ raise RestInvalidMethodException()
+
+ # set up the sdnplatform URL for per-vns flow (at internal port 8080
+ url = controller_url('vns', 'flow', vnsName, 'json')
+ return get_sdnplatform_response(url)
+
+@safe_rest_view
+def do_sdnplatform_counter_categories(request, stattype, layer, dpid=None, portid=None):
+ """
+ This returns counter categories from sdnplatform
+ """
+ if request.method != 'GET':
+ raise RestInvalidMethodException()
+ if dpid == None:
+ url = controller_url('core', 'counter', 'categories', stattype, layer, 'json')
+ elif portid == None:
+ url = controller_url('core', 'counter', 'categories', dpid, stattype, layer, 'json')
+ else:
+ url = controller_url('core', 'counter', 'categories', dpid, portid, stattype, layer, 'json')
+
+ return get_sdnplatform_response(url)
+
+@safe_rest_view
+@csrf_exempt
+def do_packettrace(request):
+ """
+ This sets a packet trace in sdnplatform.
+ period:
+ . >0 starts a trace session with the period
+ . =0 starts a trace session with no timeout
+ . <0 ends an ongoing session
+
+ The request method has to be POST since each request gets an unique sessionID
+ """
+ SESSIONID = 'sessionId'
+ sessionId = ""
+ filter = ""
+ if request.method != 'POST':
+ raise RestInvalidMethodException()
+
+ url = 'http://localhost:8080/wm/vns/packettrace/json'
+ request = urllib2.Request(url, request.raw_post_data, {'Content-Type':'application/json'})
+ try:
+ response = urllib2.urlopen(request)
+ response_text = response.read()
+ except Exception, e:
+ # SDNPlatform may not be running, but we don't want that to be a fatal
+ # error, so we just ignore the exception in that case.
+ pass
+
+ #response_data = json.loads(response_text)
+ #if SESSIONID in response_data:
+ # sessionId = response_data[SESSIONID]
+ #response_text = {SESSIONID:sessionId}
+
+ return HttpResponse(response_text, mimetype=JSON_CONTENT_TYPE)
+
+@safe_rest_view
+def do_controller_stats(request, stattype):
+ """
+ This returns controller-level statistics/info from sdnplatform
+ """
+ if request.method != 'GET':
+ raise RestInvalidMethodException()
+ url = 'http://127.0.0.1:8080/wm/core/controller/%s/json' % stattype
+ return get_sdnplatform_response(url)
+
+@safe_rest_view
+def do_controller_storage_table_list(request):
+ """
+ This returns controller-level storage table list
+ """
+ if request.method != 'GET':
+ raise RestInvalidMethodException()
+ url = 'http://127.0.0.1:8080/wm/core/storage/tables/json'
+ return get_sdnplatform_response(url)
+
+@safe_rest_view
+def do_device(request):
+ return get_sdnplatform_query(request, "device")
+
+@safe_rest_view
+def do_switches(request):
+ url = controller_url("core", "controller", "switches", "json")
+ if request.META['QUERY_STRING']:
+ url += '?' + request.META['QUERY_STRING']
+ return get_sdnplatform_response(url)
+
+@safe_rest_view
+def do_links(request):
+ url = controller_url("topology", "links", "json")
+ if request.META['QUERY_STRING']:
+ url += '?' + request.META['QUERY_STRING']
+ return get_sdnplatform_response(url)
+
+@safe_rest_view
+def do_vns_device_interface(request):
+ return get_sdnplatform_query(request, "vns/device-interface")
+
+@safe_rest_view
+def do_vns_interface(request):
+ return get_sdnplatform_query(request, "vns/interface")
+
+@safe_rest_view
+def do_vns(request):
+ return get_sdnplatform_query(request, "vns")
+
+@safe_rest_view
+def do_system_version(request):
+ if request.method != 'GET':
+ raise RestInvalidMethodException()
+ version = get_system_version_string()
+ response_text = simplejson.dumps([{ 'controller' : version }])
+ return HttpResponse(response_text, JSON_CONTENT_TYPE)
+
+available_log_files = {
+ 'syslog' : '/var/log/syslog',
+ 'sdnplatform' : '/opt/sdnplatform/sdnplatform/log/sdnplatform.log',
+ 'console-access' : '/opt/sdnplatform/con/log/access.log',
+ 'cassandra' : '/opt/sdnplatform/db/log/system.log',
+ 'authlog' : '/var/log/auth.log',
+ 'pre-start' : '/tmp/pre-start',
+ 'post-start' : '/tmp/post-start',
+ # 'ftp' : '/var/log/ftp.log',
+}
+
+available_log_commands = {
+ 'dmesg' : 'dmesg',
+ 'process' : 'ps lax'
+}
+
+@safe_rest_view
+def do_system_log_list(request):
+ if request.method != 'GET':
+ raise RestInvalidMethodException()
+ existing_logs = []
+ for (log_name, log_path) in available_log_files.items():
+ try:
+ log_file = open(log_path, 'r')
+ existing_logs.append({ 'log' : log_name })
+ log_file.close()
+ except Exception, e:
+ pass
+
+ print '??'
+ for log_name in available_log_commands.keys():
+ print 'ADD', log_name
+ existing_logs.append({ 'log' : log_name })
+ response_text = simplejson.dumps(existing_logs)
+ return HttpResponse(response_text, JSON_CONTENT_TYPE)
+
+
+def generate_subprocess_output(cmd):
+
+ process = subprocess.Popen(cmd, shell=True,
+ stdout=subprocess.PIPE,
+ stderr=subprocess.STDOUT,
+ bufsize=1)
+ while True:
+ line = process.stdout.readline()
+ if line != None and line != "":
+ yield line
+ else:
+ break
+
+
+@safe_rest_view
+def do_system_log(request, log_name):
+ if request.method != 'GET':
+ raise RestInvalidMethodException()
+ print 'do system log', log_name
+
+ # manage command ouput differently
+ if log_name in available_log_commands:
+ cmd = available_log_commands[log_name]
+ print 'DOING COMMAND', cmd
+
+ return HttpResponse(generate_subprocess_output(cmd),
+ TEXT_PLAIN_CONTENT_TYPE)
+ return
+
+ log_path = available_log_files.get(log_name)
+ if log_name == None:
+ raise RestInvalidLog('No such log: %s' % log_name)
+
+ try:
+ log_file = open(log_path, 'r')
+ except Exception,e:
+ raise RestInvalidLog('Log does not exist: %s' % log_name)
+
+ # use a generator so that the complete log is not ever held in memory
+ def response(log_name, file):
+ for line in file:
+ yield line
+ file.close()
+
+ return HttpResponse(response(log_name, log_file), TEXT_PLAIN_CONTENT_TYPE)
+
+
+@safe_rest_view
+def do_system_uptime(request):
+ if request.method != 'GET':
+ raise RestInvalidMethodException()
+ url = controller_url('core', 'system', 'uptime', 'json')
+ return get_sdnplatform_response(url)
+
+
+def _collect_system_interfaces(lo = False):
+ from netifaces import interfaces, ifaddresses, AF_INET, AF_LINK
+ result = []
+ for iface in interfaces():
+ if iface.startswith('lo') and not lo:
+ continue # ignore loopback
+ addrs = ifaddresses(iface)
+ if AF_INET in addrs:
+ for addr in ifaddresses(iface)[AF_INET]:
+ result.append({'name' : iface,
+ 'addr' : addr.get('addr', ''),
+ 'netmask' : addr.get('netmask', ''),
+ 'broadcast' : addr.get('broadcast', ''),
+ 'peer' : addr.get('peer', '')})
+ return result
+
+
+def do_system_inet4_interfaces(request):
+ if request.method != 'GET':
+ raise RestInvalidMethodException()
+ response_text = simplejson.dumps(_collect_system_interfaces(lo = True))
+ return HttpResponse(response_text, JSON_CONTENT_TYPE)
+
+
+@safe_rest_view
+def do_system_time_zone_strings(request, list_type):
+ import pytz
+ if list_type == 'common':
+ string_list = pytz.common_timezones
+ elif list_type == "all":
+ string_list = pytz.all_timezones
+ else:
+ raise RestResourceNotFoundException(request.path)
+
+ response_text = simplejson.dumps(string_list)
+
+ return HttpResponse(response_text, JSON_CONTENT_TYPE)
+
+@safe_rest_view
+def do_check_config(request):
+ config_check_state()
+ return get_successful_response('checked config')
+
+@safe_rest_view
+def do_local_controller_id(request):
+ if request.method == 'GET':
+ controller_id = get_local_controller_id()
+ if not controller_id:
+ raise Exception("Unspecified local controller id")
+ response_text = simplejson.dumps({'id': controller_id})
+ elif request.method == 'PUT':
+ put_data = json.loads(request.raw_post_data)
+ controller_id = put_data.get('id')
+ _result = exec_os_wrapper("ControllerId", 'set', [controller_id])
+ response_text = get_successful_response_data('updated')
+ else:
+ raise RestInvalidMethodException()
+
+ response = HttpResponse(response_text, JSON_CONTENT_TYPE)
+
+ return response
+
+@safe_rest_view
+def do_ha_failback(request):
+ if request.method != 'PUT':
+ raise RestInvalidMethodException()
+ _result = exec_os_wrapper("HAFailback", 'set', [])
+ response_text = get_successful_response_data('forced failback')
+ response = HttpResponse(response_text, JSON_CONTENT_TYPE)
+ return response
+
+def delete_ha_firewall_rules(ip):
+ rules = FirewallRule.objects.filter(action='allow', src_ip=ip)
+ rules.filter(port=80).delete()
+ rules.filter(proto='tcp', port=7000).delete()
+ rules.filter(proto='vrrp').delete()
+
+def cascade_delete_controller_node(controller_id):
+ ControllerAlias.objects.filter(controller=controller_id).delete()
+ ControllerDomainNameServer.objects.filter(controller=controller_id).delete()
+ for iface in ControllerInterface.objects.filter(controller=controller_id):
+ FirewallRule.objects.filter(interface=iface.id).delete()
+ ControllerInterface.objects.filter(controller=controller_id).delete()
+ Controller.objects.filter(id=controller_id).delete()
+
+# FIXME: this assume a single-interface design and will look for the IP on eth0
+# need to fix this when we have a proper multi-interface design
+def get_controller_node_ip(controller_id):
+ node_ip = ''
+ iface = ControllerInterface.objects.filter(controller=controller_id, type='Ethernet', number=0)
+ if iface:
+ node_ip = iface[0].discovered_ip
+ return node_ip
+
+# This method is "external" facing
+# It is designed to be called by CLI or other REST clients
+# This should only run on the master node, where decommissioning of a remote node is initiated
+@safe_rest_view
+def do_decommission(request):
+ if request.method != 'PUT':
+ raise RestInvalidMethodException()
+ data = simplejson.loads(request.raw_post_data)
+ node_id = data['id']
+
+ # Disallow self-decommissioning
+ local_id = get_local_controller_id()
+ if local_id == node_id:
+ raise RestDecommissionException("Decommissioning of the master node is not allowed. " + \
+ "Please perform a failover first.")
+
+ try :
+ controller = Controller.objects.get(id=node_id)
+ except Controller.DoesNotExist:
+ raise RestDecommissionException("No controller found")
+
+ node_ip = get_controller_node_ip(node_id)
+
+ # Case 1: controller node has IP
+ if node_ip:
+ result = exec_os_wrapper("Decommission", 'set', [node_ip])
+ output = result['out'].strip()
+ if result['out'].strip().endswith('is already decommissioned'):
+ delete_ha_firewall_rules(node_ip)
+ cascade_delete_controller_node(node_id)
+
+ # Case 2: controller node has NO IP
+ else:
+ output = '%s is already decommissioned' % node_id
+ cascade_delete_controller_node(node_id)
+
+ jsondict = {}
+ jsondict['status'] = "OK"
+ jsondict['description'] = output
+ return HttpResponse(simplejson.dumps(jsondict), JSON_CONTENT_TYPE)
+
+# This method is "internal" facing
+# It is designed to be called only by sys/remove-node.sh
+# This should only run on the node being decommissioned (slave)
+@safe_rest_view
+def do_decommission_internal(request):
+ if request.method != 'PUT':
+ raise RestInvalidMethodException()
+ data = simplejson.loads(request.raw_post_data)
+ node_ip = data['ip']
+ exec_os_wrapper("DecommissionLocal", 'set', [node_ip])
+
+ jsondict = {}
+ jsondict['status'] = "OK"
+ return HttpResponse(simplejson.dumps(jsondict), JSON_CONTENT_TYPE)
+
+@safe_rest_view
+def do_ha_provision(request):
+ if request.method != 'PUT':
+ raise RestInvalidMethodException()
+ data = simplejson.loads(request.raw_post_data)
+ node_ip = data['ip']
+
+ try :
+ ci = ControllerInterface.objects.get(ip=node_ip)
+ id = ci.controller.id
+ print 'got id', id
+ try:
+ a = ControllerAlias.objects.get(controller=id)
+ alias = a.alias
+ except:
+ alias = '(no controller alias)'
+
+ print 'alias:', alias
+ raise RestProvisionException('ip address already in controller %s %s' %
+ (id, alias))
+
+ except ControllerInterface.DoesNotExist:
+ id = uuid.uuid4().urn[9:]
+ print "generated id = ", id
+ c = Controller(id=id)
+ try:
+ c.save()
+ except:
+ # describe failure
+ raise RestProvisionException('can\t save controller')
+ pass
+ print "save controller"
+ ci = ControllerInterface(controller=c,
+ ip=node_ip,
+ discovered_ip=node_ip)
+ try:
+ ci.save()
+ except:
+ # describe failure
+ raise RestProvisionException('can\t save controllar interfacer')
+
+ for c in Controller.objects.all():
+ if c.id != id:
+ #if there are multiple interfaces, assume the
+ # ethernet0 interface is for management purpose
+ # XXX this could be better.
+ iface = ControllerInterface.objects.get(controller=c.id,
+ type='Ethernet',
+ number=0)
+ ip = iface.ip
+ fw = FirewallRule(interface=iface, action='allow',
+ src_ip=node_ip, port=80, proto='tcp')
+ try:
+ fw.save()
+ except:
+ # describe failure
+ raise RestProvisionException('can\t save firewall rule from master')
+
+ fw = FirewallRule(interface=ci, action='allow',
+ src_ip=ip, port=80, proto='tcp')
+ try:
+ fw.save()
+ except:
+ raise RestProvisionException('can\t save firewall from slave')
+
+
+ response_text = get_successful_response_data(id)
+ response = HttpResponse(response_text, JSON_CONTENT_TYPE)
+
+ return response
+
+
+def get_clustername():
+ name = os.popen("grep cluster_name /opt/sdnplatform/db/conf/cassandra.yaml | awk '{print $2}'").readline()
+ # name may be '', perhaps this ought to None?
+ return name
+
+
+@safe_rest_view
+def do_clustername(request):
+ if request.method != 'GET':
+ raise RestInvalidMethodException()
+ response_text = simplejson.dumps([{ 'clustername' : get_clustername() }])
+ return HttpResponse(response_text, JSON_CONTENT_TYPE)
+
+
+@safe_rest_view
+def do_local_ha_role(request):
+ if request.method != 'GET':
+ raise RestInvalidMethodException()
+
+ url = controller_url('core', 'role', 'json')
+ try:
+ response_text = urllib2.urlopen(url, timeout=2).read()
+ response = json.loads(response_text)
+ except Exception:
+ response = HttpResponse('{"role":"UNAVAILABLE"}', JSON_CONTENT_TYPE)
+ return response
+ # now determine our controller id
+ controller_id = get_local_controller_id()
+
+ # find all the interfaces
+ ifs = ControllerInterface.objects.filter(controller=controller_id)
+
+ for intf in ifs:
+ firewall_id = '%s|%s|%s' % (controller_id, intf.type, intf.number)
+
+ rules = FirewallRule.objects.filter(interface=firewall_id)
+ for rule in rules:
+ if rule.action == 'reject' and rule.proto == 'tcp' and rule.port == 6633:
+ if response['role'] in {'MASTER', 'SLAVE'}:
+ response['role']+='-BLOCKED'
+
+ response['clustername'] = get_clustername()
+
+ return HttpResponse(simplejson.dumps(response), JSON_CONTENT_TYPE)
+
+@safe_rest_view
+def do_system_clock(request, local=True):
+ local_or_utc_str = 'local' if local else 'utc'
+ if request.method == 'GET':
+ result = exec_os_wrapper("DateTime", 'get', [local_or_utc_str])
+ elif request.method == 'PUT':
+ time_info = simplejson.loads(request.raw_post_data)
+ dt = datetime(**time_info)
+ new_date_time_string = dt.strftime('%Y:%m:%d:%H:%M:%S')
+ result = exec_os_wrapper("DateTime", 'set', [local_or_utc_str, new_date_time_string])
+ else:
+ raise RestInvalidMethodException()
+
+ if len(result) == 0:
+ raise Exception('Error executing date command')
+
+ # The DateTime OS wrapper only has a single command so the return
+ # date/time is the first line of the first element of the out array
+ values = result['out'].strip().split(':')
+ date_time_info = {
+ 'year': int(values[0]),
+ 'month': int(values[1]),
+ 'day': int(values[2]),
+ 'hour': int(values[3]),
+ 'minute': int(values[4]),
+ 'second': int(values[5]),
+ 'tz': values[6]
+ }
+ response_text = simplejson.dumps(date_time_info)
+ response = HttpResponse(response_text, JSON_CONTENT_TYPE)
+
+ return response
+
+@safe_rest_view
+def do_instance(request, model_name,id=None):
+ """
+ This function handles both GET and PUT methods.
+
+ For a GET request it returns a list of all of the instances of the
+ model corresponding to the specified type that match the specified
+ query parameters. If there are no query parameters then it returns
+ a list of all of the instances of the specified type. The names of
+ the query parameters can use the Django double underscore syntax
+ for doing more complicated tests than just equality
+ (e.g. mac__startswith=192.168).
+
+ For a PUT request it can either update an existing instance or
+ insert one or more new instances. If there are any query parameters
+ then it assumes that its the update case and that the query
+ parameters identify exactly one instance. If that's not the case
+ then an error response is returned. For the update case any subset
+ of the fields can be updated with the PUT data. The format of the
+ PUT data is a JSON dictionary
+ """
+
+ # FIXME: Hack to remap 'localhost' id for the controller-node model
+ # to the real ID for this controller
+ if model_name == 'controller-node' and id == 'localhost':
+ id = get_local_controller_id()
+
+ # Lookup the model class associated with the specified name
+ model_info = rest_model_info_dict.get(model_name)
+ if not model_info:
+ raise RestInvalidDataTypeException(model_name)
+
+ # Set up the keyword argument dictionary we use to filter the QuerySet.
+ filter_keyword_args = {}
+
+ jsonp_prefix = None
+ nolist = False
+ order_by = None
+
+ # Now iterate over the query params and add further filter keyword arguments
+ query_param_dict = request.GET
+ for query_param_name, query_param_value in query_param_dict.items():
+ add_query_param = False
+ query_param_name = str(query_param_name)
+ query_param_value = str(query_param_value)
+ if query_param_name == 'callback': #switching to match up with jquery getJSON call naming convention.
+ jsonp_prefix = query_param_value
+ elif query_param_name == 'nolist':
+ if query_param_value not in ('False', 'false', '0', ''):
+ nolist = True
+ elif query_param_name == 'orderby':
+ order_by = query_param_value.split(',')
+ for i in range(len(order_by)):
+ name = order_by[i]
+ if name.startswith('-'):
+ descending = True
+ name = name[1:]
+ else:
+ descending = False
+ field_info = model_info.rest_name_dict.get(name)
+ if not field_info:
+ raise RestInvalidOrderByException(name)
+ name = field_info.name
+ if descending:
+ name = '-' + name
+ order_by[i] = name
+ elif query_param_name in model_info.rest_name_dict:
+ field_info = model_info.rest_name_dict.get(query_param_name)
+ # For booleans, translate True/False strings into 0/1.
+ if field_info and type(field_info.django_field_info) == BooleanField:
+ if query_param_value.lower() == 'false':
+ query_param_value = 0
+ elif query_param_value.lower() == 'true':
+ query_param_value = 1
+ query_param_name = field_info.name
+ if model_name == 'controller-node' and \
+ query_param_name == 'id' and query_param_value == 'localhost':
+ query_param_value = get_local_controller_id()
+ if model_name in 'controller-interface' and \
+ query_param_name == 'controller' and \
+ query_param_value == 'localhost':
+ query_param_value = get_local_controller_id()
+ add_query_param = True
+ else:
+ double_underscore_start = query_param_name.find("__")
+ if double_underscore_start >= 0:
+ rest_name = query_param_name[:double_underscore_start]
+ field_info = model_info.rest_name_dict.get(rest_name)
+ if field_info:
+ operation = query_param_name[double_underscore_start:]
+ query_param_name = field_info.name
+ if type(field_info.django_field_info) == ForeignKey:
+ query_param_name = query_param_name + '__' + field_info.django_field_info.rel.field_name
+ # Substitute in the model field name for the (possible renamed) rest name
+ query_param_name += operation
+ add_query_param = True
+ if add_query_param:
+ filter_keyword_args[query_param_name] = query_param_value
+
+ if id != None:
+ if len(filter_keyword_args) > 0:
+ raise RestInvalidFilterParameterException(filter_keyword_args.keys()[0])
+ try:
+ get_args = {model_info.primary_key:id}
+ instance = model_info.model_class.objects.get(**get_args)
+ instance_list = (instance,)
+ nolist = True
+ except model_info.model_class.DoesNotExist,e:
+ raise RestResourceNotFoundException(request.path)
+ except model_info.model_class.MultipleObjectsReturned, exc:
+ # traceback.print_exc()
+ raise RestInternalException(exc)
+ elif (request.method != 'PUT') or (len(filter_keyword_args) > 0):
+ # Get the QuerySet based on the keyword arguments we constructed
+ instance_list = model_info.model_class.objects.filter(**filter_keyword_args)
+ if order_by:
+ instance_list = instance_list.order_by(*order_by)
+ else:
+ # We're inserting new objects, so there's no need to do a query
+ instance_list = None
+
+ response_content_type = JSON_CONTENT_TYPE
+
+ if request.method == 'GET':
+ json_response_data = []
+ for instance in instance_list:
+ json_instance = {}
+ for field_info in model_info.field_name_dict.values():
+ # Made some minor edits to deal with a) fields that are empty and b) fields that are not strings -Kyle
+ # Changed this to only do an explicit string conversion if it's a unicode string.
+ # The controller is expecting to get the unstringified value (e.g. for boolean values)
+ # Not sure if this will break things in the UI, but we'll need to resolve how
+ # we want to handle this. Also, how do we want to handle unicode strings? -- robv
+ field_name = field_info.name
+ if type(field_info.django_field_info) == ForeignKey:
+ field_name += '_id'
+ value = instance.__dict__.get(field_name)
+ if value != None:
+ if field_info.json_serialize:
+ value = str(value)
+ json_instance[field_info.rest_name] = value
+ json_response_data.append(json_instance)
+
+ # If the nolist query param was enabled then check to make sure
+ # that there was only a single instance in the response list and,
+ # if so, unpack it from the list
+ if nolist:
+ if len(json_response_data) != 1:
+ raise RestNoListResultException()
+ json_response_data = json_response_data[0]
+
+ # Convert to json
+ response_data = simplejson.dumps(json_response_data)
+
+ # If the jsonp query parameter was specified, wrap the data with
+ # the jsonp prefix
+ if jsonp_prefix:
+ response_data = jsonp_prefix + '(' + response_data + ')'
+ # We don't really know what the content type is here, but it's typically javascript
+ response_content_type = TEXT_JAVASCRIPT_CONTENT_TYPE
+ elif request.method == 'PUT':
+ response_data = get_successful_response_data('saved')
+ response_content_type = JSON_CONTENT_TYPE
+
+ begin_batch_notification()
+ json_object = simplejson.loads(request.raw_post_data)
+ if instance_list is not None:
+
+ # don't allow the ip address of the first interface to
+ # be updated once it is set. This really applies to
+ # the interface cassandra uses to sync the db.
+ if model_name == 'controller-interface':
+ for instance in instance_list:
+ if instance.number == 0 and instance.ip != '':
+ if 'ip' in json_object and json_object['ip'] != instance.ip:
+ raise RestModelException("In this version, ip-address of primary interface can't be updated after initial configuration")
+
+ # In this case the URL includes query parameter(s) which we assume
+ # narrow the request to the instances of the model to be updated
+ # updated with the PUT data. So we're updating existing instances
+
+ # If it's a list with one element then extract the single element
+ if (type(json_object) == list) and (len(json_object) == 1):
+ json_object = json_object[0]
+
+ # We're expecting a dictionary where the keys match the model field names
+ # If the data isn't a dictionary then return an error
+ if type(json_object) != dict:
+ raise RestInvalidPutDataException() # TODO: Should return something different here
+
+ # Set the fields in the model instance with the data from the dictionary
+ for instance in instance_list:
+ for rest_name, value in json_object.items():
+ if not rest_name in model_info.rest_name_dict:
+ raise RestModelException("Model '%s' has no field '%s'" %
+ (model_name, rest_name))
+ field_info = model_info.rest_name_dict[rest_name]
+ field_name = str(field_info.name) # FIXME: Do we need the str cast?
+ if type(field_info.django_field_info) == ForeignKey:
+ field_name += '_id'
+ # TODO: Does Django still not like unicode strings here?
+ if type(value) == unicode:
+ value = str(value)
+ instance.__dict__[field_name] = value
+ # Save the updated model instance
+ try:
+ instance.full_clean()
+ instance.save()
+ except ValidationError, err:
+ handle_validation_error(model_info, err)
+ #raise RestValidationException(err)
+ except Exception, exc:
+ raise RestSaveException(exc)
+ else:
+ # In this case no query parameters or id were specified so we're inserting new
+ # instances into the database. The PUT data can be either a list of new
+ # items to add (i.e. top level json object is a list) or else a single
+ # new element (i.e. top-level json object is a dict).
+ #print "creating object(s)"
+
+ # To simplify the logic below we turn the single object case into a list
+ if type(json_object) != list:
+ json_object = [json_object]
+
+ # Create new model instances for all of the items in the list
+ for instance_data_dict in json_object:
+ # We expect the data to be a dictionary keyed by the field names
+ # in the model. If it's not a dict return an error
+ if type(instance_data_dict) != dict:
+ raise RestInvalidPutDataException()
+
+ converted_dict = {}
+
+ # Now add the fields specified in the PUT data
+ for rest_name, value in instance_data_dict.items():
+
+ #print " processing " + str(name) + " " + str(value)
+
+ if not rest_name in model_info.rest_name_dict:
+ raise RestModelException("Model '%s' has no field '%s'" %
+ (model_name, rest_name))
+ field_info = model_info.rest_name_dict[rest_name]
+ # simplejson uses unicode strings when it loads the objects which
+ # Django doesn't like that, so we convert these to ASCII strings
+ if type(rest_name) == unicode:
+ rest_name = str(rest_name)
+ if type(value) == unicode:
+ value = str(value)
+ field_name = field_info.name
+ # FIXME: Hack to remap localhost controller node id alias to the actual
+ # ID for the controller node. We shouldn't be doing this here (this code
+ # shouldn't have anything about specific models), but it's the easiest
+ # way to handle it for now and this code is likely going away sometime
+ # pretty soon (written in May, 2012, let's see how long "pretty soon"
+ # is :-) )
+ if model_name == 'controller-node' and field_name == 'id' and value == 'localhost':
+ value = get_local_controller_id()
+ if type(field_info.django_field_info) == ForeignKey:
+ field_name += '_id'
+ converted_dict[field_name] = value
+
+ try:
+ instance = model_info.model_class(**converted_dict)
+ instance.full_clean()
+ instance.save()
+ except ValidationError, err:
+ handle_validation_error(model_info, err)
+ #raise RestValidationException(err)
+ except Exception, e:
+ # traceback.print_exc()
+ raise RestSaveException(e)
+
+ end_batch_notification()
+ elif request.method == 'DELETE':
+ begin_batch_notification()
+ for instance in instance_list:
+ try:
+ instance.delete()
+ except ValidationError, err:
+ handle_validation_error(model_info, err)
+ except Exception, e:
+ raise RestException(e)
+ end_batch_notification()
+ response_data = "deleted"
+ response_content_type = 'text/plain'
+ else:
+ raise RestInvalidMethodException()
+
+ return HttpResponse(response_data, response_content_type)
+
+def synthetic_controller_interface(model_name, query_param_dict, json_response_data):
+ # ---
+ if model_name == 'controller-interface':
+ # For controller-interfaces, when an ip address (netmask too)
+ # is left unconfigured, then it may be possible to associate
+ # ifconfig details with the interface.
+ #
+ # Since controller-interfaces has no mechanism to associate
+ # specific ifconfig interfaces with rows, it's only possible to
+ # associate ip's when a single unconfigured ip address exists,
+ # using a process of elimination. For all ip address in the
+ # ifconfig output, all statically configured controller-interface
+ # items are removed. If only one result is left, and only
+ # one controller-interface has an unconfigured ip address
+ # (either a dhcp acquired address, or a static address where
+ # the ip address is uncofigured), the ifconfig ip address
+ # is very-likely to be the one associated with the
+ # controller-interface row.
+
+ # Check the list of values to see if any are configured as dhcp
+ dhcp_count = 0
+ unconfigured_static_ip = 0
+ this_host = get_local_controller_id()
+
+ for entry in json_response_data:
+ if 'mode' in entry and entry['mode'] == 'dhcp' and \
+ 'controller' in entry and entry['controller'] == this_host:
+ dhcp_count += 1
+ if 'mode' in entry and entry['mode'] == 'static' and \
+ 'ip' in entry and entry['ip'] == '':
+ unconfigured_static_ip += 1
+ if dhcp_count + unconfigured_static_ip != 1:
+ for entry in json_response_data:
+ entry['found-ip'] = entry['ip']
+ return
+
+ need_controller_query = False
+ # determine whether the complete list of interfaces needs
+ # to be collected to associate the dhcp address.
+ for query_param_name, query_param_value in query_param_dict.items():
+ if query_param_name != 'controller':
+ need_controller_query = True
+ if query_param_name == 'controller' and \
+ query_param_value != this_host:
+ need_controller_query = True
+
+ if need_controller_query == False:
+ model_interfaces = [x for x in json_response_data
+ if 'controller' in x and x['controller'] == this_host]
+ else:
+ # print 'need to collect all interfaces'
+ filter_keyword_args = {'controller' : this_host}
+ model_info = rest_model_info_dict.get(model_name)
+ instance_list = model_info.model_class.objects.filter(**filter_keyword_args)
+ response_data = []
+ for instance in instance_list:
+ data = {}
+ for field_info in model_info.field_name_dict.values():
+ field_name = field_info.name
+ if type(field_info.django_field_info) == ForeignKey:
+ field_name += '_id'
+ value = instance.__dict__.get(field_name)
+ if value != None:
+ if field_info.json_serialize:
+ value = str(value)
+ data[field_info.rest_name] = value
+ response_data.append(data)
+ model_interfaces = response_data
+
+ # Recompute the number of dhcp configured interfaces,
+ # model_interfaces is the collection of interface for 'this_host'
+ dhcp_count = 0
+ unconfigured_static_ip = 0
+ for ifs in model_interfaces:
+ if 'mode' in ifs and ifs['mode'] == 'dhcp':
+ dhcp_count += 1
+ if 'mode' in ifs and ifs['mode'] == 'static' and \
+ 'ip' in ifs and ifs['ip'] == '':
+ unconfigured_static_ip += 1
+
+ if dhcp_count + unconfigured_static_ip != 1:
+ # print "Sorry, %s dhcp + %s unconfigured static interfaces on %s" % \
+ # (dhcp_count, unconfigured_static_ip, this_host)
+ # copy over static ip's
+ for entry in json_response_data:
+ entry['found-ip'] = entry['ip']
+ return
+
+ # collect current details for all the network interfaces
+ inet4_ifs = _collect_system_interfaces()
+
+ # iterate over the model_interfaces's interfaces, and
+ # remove ip addresses from inet4_ifs which are static, and
+ # have the correct static value.
+
+ report_static = False
+ match_id = ''
+
+ for ifs in model_interfaces:
+ if 'mode' in ifs and ifs['mode'] == 'static':
+ if 'ip' in ifs and ifs['ip'] == '':
+ # print "Unconfigured static ip for %s", ifs['id']
+ match_id = ifs['id']
+ if 'ip' in ifs and ifs['ip'] != '':
+ # find this address in the known addresses
+ remove_entry = -1
+ for index, inet4_if in enumerate(inet4_ifs):
+ if inet4_if['addr'] == ifs['ip']:
+ remove_entry = index
+ break
+ if remove_entry == -1:
+ # print "Static ip %s not found" % ifs['ip']
+ pass
+ else:
+ del inet4_ifs[remove_entry]
+ elif 'mode' in ifs and ifs['mode'] == 'dhcp':
+ match_id = ifs['id']
+ else:
+ # ought to assert here, not_reached()
+ pass
+
+ # When only one entry is left in inet, its possible to do the assocation
+ if len(inet4_ifs) != 1:
+ # print "Incorrect number %s of inet4 interfaces left" % len(inet4_ifs)
+ pass
+
+ for entry in json_response_data:
+ entry['found-ip'] = entry['ip']
+ entry['found-netmask'] = entry['netmask']
+
+ if entry['id'] == match_id:
+ # make sure the address isn't set
+ if entry['ip'] == '':
+ entry['found-ip'] = inet4_ifs[0]['addr']
+ entry['found-netmask'] = inet4_ifs[0]['netmask']
+ entry['found-broadcast'] = inet4_ifs[0]['broadcast']
+ else:
+ # ought to assert here, not_reached()
+ pass
+
+@safe_rest_view
+def do_synthetic_instance(request, model_name, id=None):
+
+ if request.method != 'GET':
+ raise RestInvalidMethodException()
+
+ # Lookup the model class associated with the specified name
+ model_info = rest_model_info_dict.get(model_name)
+ if not model_info:
+ raise RestInvalidDataTypeException(model_name)
+
+ # Set up the keyword argument dictionary we use to filter the QuerySet.
+ filter_keyword_args = {}
+
+ jsonp_prefix = None
+ nolist = False
+ order_by = None
+
+ # Now iterate over the query params and add further filter keyword arguments
+ query_param_dict = request.GET
+ for query_param_name, query_param_value in query_param_dict.items():
+ add_query_param = False
+ query_param_name = str(query_param_name)
+ query_param_value = str(query_param_value)
+ if query_param_name == 'callback': #switching to match up with jquery getJSON call naming convention.
+ jsonp_prefix = query_param_value
+ elif query_param_name == 'nolist':
+ if query_param_value not in ('False', 'false', '0', ''):
+ nolist = True
+ elif query_param_name == 'orderby':
+ order_by = query_param_value.split(',')
+ for i in range(len(order_by)):
+ name = order_by[i]
+ if name.startswith('-'):
+ descending = True
+ name = name[1:]
+ else:
+ descending = False
+ field_info = model_info.rest_name_dict.get(name)
+ if not field_info:
+ raise RestInvalidOrderByException(name)
+ name = field_info.name
+ if descending:
+ name = '-' + name
+ order_by[i] = name
+ elif query_param_name in model_info.rest_name_dict:
+ field_info = model_info.rest_name_dict.get(query_param_name)
+ query_param_name = field_info.name
+ add_query_param = True
+ else:
+ double_underscore_start = query_param_name.find("__")
+ if double_underscore_start >= 0:
+ rest_name = query_param_name[:double_underscore_start]
+ field_info = model_info.rest_name_dict.get(rest_name)
+ if field_info:
+ operation = query_param_name[double_underscore_start:]
+ query_param_name = field_info.name
+ if type(field_info.django_field_info) == ForeignKey:
+ query_param_name = query_param_name + '__' + field_info.django_field_info.rel.field_name
+ # Substitute in the model field name for the (possible renamed) rest name
+ query_param_name += operation
+ add_query_param = True
+ if add_query_param:
+ filter_keyword_args[query_param_name] = query_param_value
+
+ if id != None:
+ if len(filter_keyword_args) > 0:
+ raise RestInvalidFilterParameterException(filter_keyword_args.keys()[0])
+ try:
+ get_args = {model_info.primary_key:id}
+ instance = model_info.model_class.objects.get(**get_args)
+ instance_list = (instance,)
+ nolist = True
+ except model_info.model_class.DoesNotExist,e:
+ raise RestResourceNotFoundException(request.path)
+ except model_info.model_class.MultipleObjectsReturned, exc:
+ # traceback.print_exc()
+ raise RestInternalException(exc)
+ elif (request.method != 'PUT') or (len(filter_keyword_args) > 0):
+ # Get the QuerySet based on the keyword arguments we constructed
+ instance_list = model_info.model_class.objects.filter(**filter_keyword_args)
+ if order_by:
+ instance_list = instance_list.order_by(*order_by)
+ else:
+ # We're inserting new objects, so there's no need to do a query
+ instance_list = None
+
+ response_content_type = JSON_CONTENT_TYPE
+
+ # Syntheric types only do requests --
+ json_response_data = []
+ for instance in instance_list:
+ json_instance = {}
+ for field_info in model_info.field_name_dict.values():
+ # Made some minor edits to deal with a) fields that are empty and b) fields that are not strings -Kyle
+ # Changed this to only do an explicit string conversion if it's a unicode string.
+ # The controller is expecting to get the unstringified value (e.g. for boolean values)
+ # Not sure if this will break things in the UI, but we'll need to resolve how
+ # we want to handle this. Also, how do we want to handle unicode strings? -- robv
+ field_name = field_info.name
+ if type(field_info.django_field_info) == ForeignKey:
+ field_name += '_id'
+ value = instance.__dict__.get(field_name)
+ if value != None:
+ if field_info.json_serialize:
+ value = str(value)
+ json_instance[field_info.rest_name] = value
+ json_response_data.append(json_instance)
+
+ # ---
+ if model_name == 'controller-interface':
+ synthetic_controller_interface(model_name, query_param_dict, json_response_data)
+
+ # Convert to json
+ response_data = simplejson.dumps(json_response_data)
+
+ # If the nolist query param was enabled then check to make sure
+ # that there was only a single instance in the response list and,
+ # if so, unpack it from the list
+ if nolist:
+ if len(json_response_data) != 1:
+ raise RestNoListResultException()
+ json_response_data = json_response_data[0]
+
+ # If the jsonp query parameter was specified, wrap the data with
+ # the jsonp prefix
+ if jsonp_prefix:
+ response_data = jsonp_prefix + '(' + response_data + ')'
+ # We don't really know what the content type is here, but it's typically javascript
+ response_content_type = TEXT_JAVASCRIPT_CONTENT_TYPE
+
+ return HttpResponse(response_data, response_content_type)
+
+@safe_rest_view
+def do_user_data_list(request):
+ # Now iterate over the query params and add any valid filter keyword arguments
+ filter_keyword_args = {}
+ for query_param_name, query_param_value in request.GET.items():
+ query_param_name = str(query_param_name)
+ double_underscore_start = query_param_name.find("__")
+ if double_underscore_start >= 0:
+ attribute_name = query_param_name[:double_underscore_start]
+ else:
+ attribute_name = query_param_name
+
+ # In the future, if we add support for things like mod_date, creation_date, etc.
+ # which would be supported in query params, then they'd be added to this list/tuple.
+ if attribute_name not in ('name',):
+ raise RestInvalidFilterParameterException(query_param_name)
+ filter_keyword_args[query_param_name] = query_param_value
+
+ instance_list = UserData.objects.filter(**filter_keyword_args)
+
+ if request.method == 'GET':
+ user_data_info_list = []
+
+ # FIXME: robv: It's incorrect to *always* add this to the user data,
+ # because it means we're not respecting the filter query parameters.
+ # To work completely correctly we'd need to duplicate a lot of logic
+ # for processing the query parameters, which would be tedious.
+ # Should talk to Mandeep about why it was done this way. Maybe we
+ # should expose these special cases in a different URL/view.
+ for fn in ['startup-config', 'upgrade-config']:
+ try:
+ sc = "%s/run/%s" % (sdncon.SDN_ROOT, fn)
+ f = open(sc, 'r')
+ f.close()
+ t = time.strftime("%Y-%m-%d.%H:%M:%S",
+ time.localtime(os.path.getmtime(sc)))
+ instance_name = fn + '/timestamp=' + t + \
+ '/version=1/length=' + \
+ str(os.path.getsize(sc))
+ url_path = 'rest/v1/data/' + instance_name + '/'
+
+ user_data_info = { 'name' : instance_name,
+ 'url_path' : url_path, }
+ user_data_info_list.append(user_data_info)
+ except:
+ pass
+
+ for instance in instance_list:
+ user_data_info = {'name': instance.name,
+ 'url_path': 'rest/v1/data/' + instance.name + '/'}
+ user_data_info_list.append(user_data_info)
+
+ response_data = simplejson.dumps(user_data_info_list)
+ elif request.method == 'DELETE':
+ instance_list.delete()
+ response_data = {}
+ response_data['status'] = 'success'
+ response_data['message'] = 'user data deleted'
+ response_data = simplejson.dumps(response_data)
+ response_content_type = JSON_CONTENT_TYPE
+ else:
+ raise RestInvalidMethodException()
+
+ return HttpResponse(response_data, JSON_CONTENT_TYPE)
+
+@safe_rest_view
+def do_user_data(request, name):
+ query_param_dict = request.GET
+ #
+ # Manage startup-config/update-config differently
+ if name.find('/') >= 0 and \
+ name.split('/')[0] in ['startup-config', 'upgrade-config']:
+ path = "%s/run/%s" % (sdncon.SDN_ROOT, name.split('/')[0])
+ response_data = {}
+
+ if request.method == 'GET':
+ with open(path, 'r') as f:
+ response_data = f.read()
+ response_content_type = "text/plain"
+ elif request.method == 'PUT':
+ try:
+ with open(path, 'w') as f:
+ f.write(request.raw_post_data)
+ response_data['status'] = 'success'
+ response_data['message'] = 'user data updated'
+ except:
+ response_data['status'] = 'failure'
+ response_data['message'] = "can't write file"
+ response_content_type = JSON_CONTENT_TYPE
+ response_data = simplejson.dumps(response_data)
+ elif request.method == 'DELETE':
+ try:
+ f = open(path, "r")
+ f.close()
+ except:
+ raise RestResourceNotFoundException(request.path)
+
+ try:
+ os.remove(path)
+ response_data['status'] = 'success'
+ response_data['message'] = 'user data deleted'
+ except:
+ response_data['status'] = 'failure'
+ response_data['message'] = "can't delete file"
+ response_data = simplejson.dumps(response_data)
+ response_content_type = JSON_CONTENT_TYPE
+ else:
+ raise RestInvalidMethodException()
+
+ return HttpResponse(response_data, response_content_type)
+
+
+ # Default values for optional query parameters
+ #private = False
+ binary = False
+
+ for param_name, param_value in query_param_dict.items():
+ if param_name == 'binary':
+ if request.method != 'PUT':
+ raise RestInvalidQueryParameterException(name)
+ binary = param_value.lower() == 'true' or param_value == '1'
+ #elif param_name == 'private':
+ # private = param_value
+ else:
+ raise RestInvalidQueryParameterException(param_name)
+
+ # FIXME: Need HTTP basic/digest auth support for the following
+ # code to work.
+ #if private:
+ # user = request.user
+ #else:
+ # user = None
+ #if user != None and not user.is_authenticated():
+ # raise RestAuthenticationRequiredException()
+ user = None
+
+ # There's currently an issue with filtering on the user when using the
+ # Cassandra database backend. Since we don't support private per-user
+ # data right now, I'm just disabling filtering on the user and only
+ # filter on the name
+ #user_data_query_set = UserData.objects.filter(user=user, name=name)
+ user_data_query_set = UserData.objects.filter(name=name)
+
+ count = user_data_query_set.count()
+ if count > 1:
+ raise RestInternalException('Duplicate user data values for the same name')
+
+ if request.method == 'GET':
+ if count == 0:
+ raise RestResourceNotFoundException(request.path)
+ user_data = user_data_query_set[0]
+ response_data = user_data.data
+ if user_data.binary:
+ response_data = base64.b64decode(response_data)
+ response_content_type = user_data.content_type
+ elif request.method == 'PUT':
+ content_type = request.META['CONTENT_TYPE']
+ if content_type == None:
+ if binary:
+ content_type = BINARY_DATA_CONTENT_TYPE
+ else:
+ content_type = JSON_CONTENT_TYPE
+ response_data = {}
+ if count == 1:
+ response_data['status'] = 'success'
+ response_data['message'] = 'user data updated'
+ user_data = user_data_query_set[0]
+ else:
+ response_data['status'] = 'success'
+ response_data['message'] = 'user data created'
+ user_data = UserData(user=user,name=name)
+ user_data.binary = binary
+ user_data.content_type = content_type
+ data = request.raw_post_data
+ if binary:
+ data = base64.b64encode(data)
+ user_data.data = data
+ user_data.save()
+ response_data = simplejson.dumps(response_data)
+ response_content_type = JSON_CONTENT_TYPE
+ elif request.method == 'DELETE':
+ if count == 0:
+ raise RestResourceNotFoundException(request.path)
+ user_data = user_data_query_set[0]
+ user_data.delete()
+ response_data = {}
+ response_data['status'] = 'success'
+ response_data['message'] = 'user data deleted'
+ response_data = simplejson.dumps(response_data)
+ response_content_type = JSON_CONTENT_TYPE
+ else:
+ raise RestInvalidMethodException()
+
+ return HttpResponse(response_data, response_content_type)
+
+@safe_rest_view
+def do_sdnplatform_tunnel_manager(request, dpid=None):
+ """
+ This returns realtime statistics from sdnplatform
+ """
+
+ if request.method != 'GET':
+ raise RestInvalidMethodException()
+ if dpid == None:
+ raise RestInvalidMethodException()
+
+ print 'DPID', dpid
+ if dpid == 'all':
+ url = controller_url('vns', 'tunnel-manager', 'all', 'json')
+ else:
+ url = controller_url('vns', 'tunnel-manager', 'switch='+dpid, 'json')
+
+ response_text = urllib2.urlopen(url).read()
+ entries = simplejson.loads(response_text)
+
+ if 'error' in entries and entries['error'] != None:
+ RestInternalException(entries['error'])
+
+ return HttpResponse(json.dumps(entries['tunnMap']), JSON_CONTENT_TYPE)
+
+@safe_rest_view
+def do_sdnplatform_controller_summary(request):
+ """
+ This returns summary statistics from sdnplatform modules
+ """
+ if request.method != 'GET':
+ raise RestInvalidMethodException()
+
+ url = controller_url('core', 'controller', 'summary', 'json')
+ return get_sdnplatform_response(url)
+
+def filter_queries(choice_list, param_dict):
+ return dict([[x, param_dict[x]] for x in choice_list
+ if x in param_dict and param_dict[x] != 'all'])
+
+@safe_rest_view
+def do_reload(request):
+ """
+ This calls an oswrapper that reloads the box.
+ """
+ if request.method != 'GET':
+ raise RestInvalidMethodException()
+ exec_os_wrapper("ReloadController", 'set', [])
+ response_text = '{"status":"reloading"}'
+ return HttpResponse(response_text, JSON_CONTENT_TYPE)
+
+@safe_rest_view
+def do_resetbsc(request):
+ """
+ This calls an oswrapper that resets the box.
+ """
+ if request.method != 'PUT':
+ raise RestInvalidMethodException()
+ exec_os_wrapper("ResetBsc", 'set', [])
+ response_text = '{"status":"resetting"}'
+ return HttpResponse(response_text, JSON_CONTENT_TYPE)
+
+@safe_rest_view
+def do_abort_upgrade(request):
+ """
+ This calls an oswrapper that reloads the box.
+ """
+ if request.method != 'PUT':
+ raise RestInvalidMethodException()
+ controller_id = get_local_controller_id()
+ controller = Controller.objects.get(id=controller_id)
+ if controller.status != 'Upgrading':
+ raise RestUpgradeException("No Upgrade pending")
+ exec_os_wrapper("AbortUpgrade", 'set', [])
+ controller.status = 'Ready'
+ controller.save()
+ response_text = '{"status":"Ready"}'
+ return HttpResponse(response_text, JSON_CONTENT_TYPE)
+
+@safe_rest_view
+def do_config_rollback(request):
+ data = simplejson.loads(request.raw_post_data)
+ path = data['path']
+ print "Executing config rollback with config @", path
+
+ if request.method != 'PUT':
+ raise RestInvalidMethodException()
+ exec_os_wrapper("RollbackConfig", 'set', [path])
+ response_text = get_successful_response_data('prepared config rollbacked')
+ return HttpResponse(response_text, JSON_CONTENT_TYPE)
+
+@safe_rest_view
+def do_upload_data(request):
+ if request.method != 'PUT':
+ raise RestInvalidMethodException()
+ data = simplejson.loads(request.raw_post_data)
+ content = data['data']
+ path = data['dst']
+ print "Executing config rollback with config @", path
+
+ exec_os_wrapper("WriteDataToFile", 'set', [content, path])
+ response_text = get_successful_response_data('written data')
+ return HttpResponse(response_text, JSON_CONTENT_TYPE)
+
+@safe_rest_view
+def do_diff_config(request):
+ if request.method != 'PUT':
+ raise RestInvalidMethodException()
+ data = simplejson.loads(request.raw_post_data)
+ config1 = data['config-1']
+ config2 = data['config-2']
+ print "diffing '%s' with '%s'" %(config1, config2)
+
+ result = exec_os_wrapper("DiffConfig", 'set', [config1, config2])
+ return HttpResponse(simplejson.dumps(result), JSON_CONTENT_TYPE)
+
+@safe_rest_view
+def do_extract_upgrade_pkg_manifest(request):
+ """
+ This calls an oswrapper that extracts the upgrade package.
+ This returns the install package 'manifest'.
+ """
+ if request.method != 'GET':
+ raise RestInvalidMethodException()
+ exec_os_wrapper("GetLatestUpgradePkg", 'get', [])
+ output = exec_os_wrapper("CatUpgradeImagesFile", 'get')
+ upgradePkg = output['out'].strip()
+ exec_os_wrapper("ExtractUpgradePkgManifest", 'set', [upgradePkg])
+ output = exec_os_wrapper("ExtractUpgradePkgManifest", 'get')
+ manifest = output['out']
+ return HttpResponse(manifest, JSON_CONTENT_TYPE)
+
+@safe_rest_view
+def do_extract_upgrade_pkg(request):
+ if request.method != 'GET':
+ raise RestInvalidMethodException()
+ exec_os_wrapper("GetLatestUpgradePkg", 'get', [])
+ output = exec_os_wrapper("CatUpgradeImagesFile", 'get')
+ upgradePkg = output['out'].strip()
+ exec_os_wrapper("ExtractUpgradePkg", 'set', [upgradePkg])
+ return HttpResponse('{"status": "OK"}', JSON_CONTENT_TYPE)
+
+@safe_rest_view
+def do_get_upgrade_pkg(request):
+ """
+ This calls an oswrapper to get the latest upgrade
+ package uploaded to the controller.
+ """
+ if request.method != 'GET':
+ raise RestInvalidMethodException()
+ exec_os_wrapper("GetLatestUpgradePkg", 'get')
+ result = exec_os_wrapper("CatUpgradeImagesFile", 'get')
+ jsondict = {'file': result['out'].strip()}
+ return HttpResponse(simplejson.dumps(jsondict), JSON_CONTENT_TYPE)
+
+@safe_rest_view
+def do_cleanup_old_pkgs(request):
+ if request.method != 'GET':
+ raise RestInvalidMethodException()
+ exec_os_wrapper("CleanupOldUpgradeImages", 'get')
+ return HttpResponse('{"status": "OK"}', JSON_CONTENT_TYPE)
+
+@safe_rest_view
+def do_execute_upgrade_step(request):
+ """
+ Executes a particular upgrade step according to the
+ upgrade package manifest.
+ """
+ if request.method != 'PUT':
+ raise RestInvalidMethodException()
+
+ put_data = json.loads(request.raw_post_data)
+ imageName = put_data.get('imageName')
+ stepNum = put_data.get('step')
+ force = put_data.get('force')
+
+ args = [stepNum, imageName]
+ if force:
+ args.append("--force")
+ result = exec_os_wrapper("ExecuteUpgradeStep", 'get',
+ args)
+ jsondict = {}
+ if len(str(result['err']).strip()) > 0:
+ jsondict['status'] = "ERROR"
+ jsondict['description'] = str(result['err']).strip()
+ else:
+ jsondict['status'] = "OK"
+ jsondict['description'] = str(result['out']).strip()
+
+ return HttpResponse(simplejson.dumps(jsondict), JSON_CONTENT_TYPE)
+