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