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