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