blob: 66ec9d73416c7ab9ce23187b33fffddebea4a074 [file] [log] [blame]
#
# 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
#from sdncon.controller.models import ControllerAlias, ControllerDomainNameServer, ControllerInterface, FirewallRule
#from sdncon.controller.models import 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'
onos = 1
if onos == 1:
#CONTROLLER_URL_PREFIX = 'http://localhost:9000/wm/'
CONTROLLER_URL_PREFIX = 'http://localhost:8080/wm/'
else:
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
"""
#raise RestInvalidMethodException()
if request.method != 'GET':
raise RestInvalidMethodException()
#url = controller_url('core', 'switch', dpid, stattype, 'json')
if stattype == 'group':
stattype = 'groupStats'
if stattype == 'groupdesc':
stattype = 'groupDesc'
url = "http://localhost:8080/wm/floodlight/core/switch/%s/%s/json" % (dpid, stattype)
return get_sdnplatform_response(url)
@safe_rest_view
def do_tablerealtimestats(request, tabletype, dpid):
"""
This returns realtime statistics per table (flows (only)
current implementation) for a dpid by calling the localhost sdnplatform
"""
#raise RestInvalidMethodException()
if request.method != 'GET':
raise RestInvalidMethodException()
#url = controller_url('core', 'switch', dpid, stattype, 'json')
url = "http://localhost:8080/wm/floodlight/core/switch/%s/table/%s/flow/json" % (dpid,tabletype)
#url ="http://localhost:8080/wm/floodlight/core/switch/00:00:00:00:00:00:00:01/flow/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):
if onos == 0:
return get_sdnplatform_query(request, "device")
else:
url = controller_url("onos", "topology", "hosts")
if request.META['QUERY_STRING']:
url += '?' + request.META['QUERY_STRING']
return get_sdnplatform_response(url)
@safe_rest_view
def do_switches(request):
if onos == 0:
url = controller_url("core", "controller", "switches", "json")
else:
url = controller_url("onos", "topology", "switches")
if request.META['QUERY_STRING']:
url += '?' + request.META['QUERY_STRING']
return get_sdnplatform_response(url)
@safe_rest_view
def do_routers(request):
if onos == 0:
url = controller_url("core", "controller", "switches", "json")
else:
url = controller_url("onos","segmentrouting", "routers")
if request.META['QUERY_STRING']:
url += '?' + request.META['QUERY_STRING']
return get_sdnplatform_response(url)
@safe_rest_view
def do_router_stats(request, stattype, dpid):
"""
This returns the subnets info about the specifed
router dpid. statetype should be 'port'.
"""
#raise RestInvalidMethodException()
if request.method != 'GET':
raise RestInvalidMethodException()
#url = controller_url('core', 'switch', dpid, stattype, 'json')
url = "http://localhost:8080/wm/onos/segmentrouting/router/%s/%s" % (dpid, stattype)
#raise RestInvalidMethodException(url)
#url = "http://localhost:8080/wm/onos/segementrouting/router/00:00:00:00:00:00:00:01/port"
return get_sdnplatform_response(url)
@safe_rest_view
def do_mastership(request):
url = controller_url("onos", "registry", "switches" ,"json")
#url = "http://127.0.0.1:8080/wm/onos/registry/switches/json"
if request.META['QUERY_STRING']:
url += '?' + request.META['QUERY_STRING']
return get_sdnplatform_response(url)
@safe_rest_view
def do_controller(request):
url = controller_url("onos", "registry", "controllers" ,"json")
#url = "http://127.0.0.1:8080/wm/onos/registry/switches/json"
if request.META['QUERY_STRING']:
url += '?' + request.META['QUERY_STRING']
return get_sdnplatform_response(url)
#'''
@safe_rest_view
def do_links(request):
if onos == 0:
url = controller_url("topology", "links", "json")
else:
url = controller_url("onos", "topology", "links")
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)
@safe_rest_view
def do_sdnplatform_tunnel_config(request):
if request.method != 'PUT' and request.method != 'DELETE':
raise RestInvalidMethodException()
url = controller_url('onos', 'segmentrouting', 'tunnel')
post_data = request.raw_post_data
put_request = urllib2.Request(url, post_data)
method = request.method
if method == 'PUT':
method = 'POST'
put_request.get_method = lambda: method
put_request.add_header('Content-Type', 'application/json')
response = urllib2.urlopen(put_request)
response_text = response.read()
response = HttpResponse(response_text, JSON_CONTENT_TYPE)
return response
@safe_rest_view
def do_sdnplatform_policy_config(request):
if request.method != 'PUT' and request.method != 'DELETE':
raise RestInvalidMethodException()
url = controller_url('onos', 'segmentrouting', 'policy')
post_data = request.raw_post_data
put_request = urllib2.Request(url, post_data)
method = request.method
if method == 'PUT':
method = 'POST'
put_request.get_method = lambda: method
put_request.add_header('Content-Type', 'application/json')
response = urllib2.urlopen(put_request)
response_text = response.read()
response = HttpResponse(response_text, JSON_CONTENT_TYPE)
return response