Merge branch 'packaging'
[osm/RO.git] / osm_ro / httpserver.py
1 # -*- coding: utf-8 -*-
2
3 ##
4 # Copyright 2015 Telefónica Investigación y Desarrollo, S.A.U.
5 # This file is part of openmano
6 # All Rights Reserved.
7 #
8 # Licensed under the Apache License, Version 2.0 (the "License"); you may
9 # not use this file except in compliance with the License. You may obtain
10 # a copy of the License at
11 #
12 # http://www.apache.org/licenses/LICENSE-2.0
13 #
14 # Unless required by applicable law or agreed to in writing, software
15 # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
16 # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
17 # License for the specific language governing permissions and limitations
18 # under the License.
19 #
20 # For those usages not covered by the Apache License, Version 2.0 please
21 # contact with: nfvlabs@tid.es
22 ##
23
24 '''
25 HTTP server implementing the openmano API. It will answer to POST, PUT, GET methods in the appropriate URLs
26 and will use the nfvo.py module to run the appropriate method.
27 Every YAML/JSON file is checked against a schema in openmano_schemas.py module.
28 '''
29 __author__="Alfonso Tierno, Gerardo Garcia"
30 __date__ ="$17-sep-2014 09:07:15$"
31
32 import bottle
33 import yaml
34 import json
35 import threading
36 import time
37 import logging
38
39 from jsonschema import validate as js_v, exceptions as js_e
40 from openmano_schemas import vnfd_schema_v01, vnfd_schema_v02, \
41 nsd_schema_v01, nsd_schema_v02, nsd_schema_v03, scenario_edit_schema, \
42 scenario_action_schema, instance_scenario_action_schema, instance_scenario_create_schema_v01, \
43 tenant_schema, tenant_edit_schema,\
44 datacenter_schema, datacenter_edit_schema, datacenter_action_schema, datacenter_associate_schema,\
45 object_schema, netmap_new_schema, netmap_edit_schema, sdn_controller_schema, sdn_controller_edit_schema, \
46 sdn_port_mapping_schema
47
48 import nfvo
49 import utils
50 from db_base import db_base_Exception
51 from functools import wraps
52
53 global mydb
54 global url_base
55 global logger
56 url_base="/openmano"
57 logger = None
58
59 HTTP_Bad_Request = 400
60 HTTP_Unauthorized = 401
61 HTTP_Not_Found = 404
62 HTTP_Forbidden = 403
63 HTTP_Method_Not_Allowed = 405
64 HTTP_Not_Acceptable = 406
65 HTTP_Service_Unavailable = 503
66 HTTP_Internal_Server_Error= 500
67
68 def delete_nulls(var):
69 if type(var) is dict:
70 for k in var.keys():
71 if var[k] is None: del var[k]
72 elif type(var[k]) is dict or type(var[k]) is list or type(var[k]) is tuple:
73 if delete_nulls(var[k]): del var[k]
74 if len(var) == 0: return True
75 elif type(var) is list or type(var) is tuple:
76 for k in var:
77 if type(k) is dict: delete_nulls(k)
78 if len(var) == 0: return True
79 return False
80
81 def convert_datetime2str(var):
82 '''Converts a datetime variable to a string with the format '%Y-%m-%dT%H:%i:%s'
83 It enters recursively in the dict var finding this kind of variables
84 '''
85 if type(var) is dict:
86 for k,v in var.items():
87 if type(v) is float and k in ("created_at", "modified_at"):
88 var[k] = time.strftime("%Y-%m-%dT%H:%M:%S", time.localtime(v) )
89 elif type(v) is dict or type(v) is list or type(v) is tuple:
90 convert_datetime2str(v)
91 if len(var) == 0: return True
92 elif type(var) is list or type(var) is tuple:
93 for v in var:
94 convert_datetime2str(v)
95
96 def log_to_logger(fn):
97 '''
98 Wrap a Bottle request so that a log line is emitted after it's handled.
99 (This decorator can be extended to take the desired logger as a param.)
100 '''
101 @wraps(fn)
102 def _log_to_logger(*args, **kwargs):
103 actual_response = fn(*args, **kwargs)
104 # modify this to log exactly what you need:
105 logger.info('FROM %s %s %s %s' % (bottle.request.remote_addr,
106 bottle.request.method,
107 bottle.request.url,
108 bottle.response.status))
109 return actual_response
110 return _log_to_logger
111
112 class httpserver(threading.Thread):
113 def __init__(self, db, admin=False, host='localhost', port=9090):
114 #global url_base
115 global mydb
116 global logger
117 #initialization
118 if not logger:
119 logger = logging.getLogger('openmano.http')
120 threading.Thread.__init__(self)
121 self.host = host
122 self.port = port #Port where the listen service must be started
123 if admin==True:
124 self.name = "http_admin"
125 else:
126 self.name = "http"
127 #self.url_preffix = 'http://' + host + ':' + str(port) + url_base
128 mydb = db
129 #self.first_usable_connection_index = 10
130 #self.next_connection_index = self.first_usable_connection_index #The next connection index to be used
131 #Ensure that when the main program exits the thread will also exit
132 self.daemon = True
133 self.setDaemon(True)
134
135 def run(self):
136 bottle.install(log_to_logger)
137 bottle.run(host=self.host, port=self.port, debug=False, quiet=True)
138
139 def run_bottle(db, host_='localhost', port_=9090):
140 '''used for launching in main thread, so that it can be debugged'''
141 global mydb
142 mydb = db
143 bottle.run(host=host_, port=port_, debug=True) #quiet=True
144
145
146 @bottle.route(url_base + '/', method='GET')
147 def http_get():
148 #print
149 return 'works' #TODO: to be completed
150
151 #
152 # Util functions
153 #
154
155 def change_keys_http2db(data, http_db, reverse=False):
156 '''Change keys of dictionary data acording to the key_dict values
157 This allow change from http interface names to database names.
158 When reverse is True, the change is otherwise
159 Attributes:
160 data: can be a dictionary or a list
161 http_db: is a dictionary with hhtp names as keys and database names as value
162 reverse: by default change is done from http api to database. If True change is done otherwise
163 Return: None, but data is modified'''
164 if type(data) is tuple or type(data) is list:
165 for d in data:
166 change_keys_http2db(d, http_db, reverse)
167 elif type(data) is dict or type(data) is bottle.FormsDict:
168 if reverse:
169 for k,v in http_db.items():
170 if v in data: data[k]=data.pop(v)
171 else:
172 for k,v in http_db.items():
173 if k in data: data[v]=data.pop(k)
174
175 def format_out(data):
176 '''return string of dictionary data according to requested json, yaml, xml. By default json'''
177 logger.debug("OUT: " + yaml.safe_dump(data, explicit_start=True, indent=4, default_flow_style=False, tags=False, encoding='utf-8', allow_unicode=True) )
178 if 'application/yaml' in bottle.request.headers.get('Accept'):
179 bottle.response.content_type='application/yaml'
180 return yaml.safe_dump(data, explicit_start=True, indent=4, default_flow_style=False, tags=False, encoding='utf-8', allow_unicode=True) #, canonical=True, default_style='"'
181 else: #by default json
182 bottle.response.content_type='application/json'
183 #return data #json no style
184 return json.dumps(data, indent=4) + "\n"
185
186 def format_in(default_schema, version_fields=None, version_dict_schema=None):
187 ''' Parse the content of HTTP request against a json_schema
188 Parameters
189 default_schema: The schema to be parsed by default if no version field is found in the client data
190 version_fields: If provided it contains a tuple or list with the fields to iterate across the client data to obtain the version
191 version_dict_schema: It contains a dictionary with the version as key, and json schema to apply as value
192 It can contain a None as key, and this is apply if the client data version does not match any key
193 Return:
194 user_data, used_schema: if the data is successfully decoded and matches the schema
195 launch a bottle abort if fails
196 '''
197 #print "HEADERS :" + str(bottle.request.headers.items())
198 try:
199 error_text = "Invalid header format "
200 format_type = bottle.request.headers.get('Content-Type', 'application/json')
201 if 'application/json' in format_type:
202 error_text = "Invalid json format "
203 #Use the json decoder instead of bottle decoder because it informs about the location of error formats with a ValueError exception
204 client_data = json.load(bottle.request.body)
205 #client_data = bottle.request.json()
206 elif 'application/yaml' in format_type:
207 error_text = "Invalid yaml format "
208 client_data = yaml.load(bottle.request.body)
209 elif 'application/xml' in format_type:
210 bottle.abort(501, "Content-Type: application/xml not supported yet.")
211 else:
212 logger.warning('Content-Type ' + str(format_type) + ' not supported.')
213 bottle.abort(HTTP_Not_Acceptable, 'Content-Type ' + str(format_type) + ' not supported.')
214 return
215 #if client_data == None:
216 # bottle.abort(HTTP_Bad_Request, "Content error, empty")
217 # return
218
219 logger.debug('IN: %s', yaml.safe_dump(client_data, explicit_start=True, indent=4, default_flow_style=False, tags=False, encoding='utf-8', allow_unicode=True) )
220 #look for the client provider version
221 error_text = "Invalid content "
222 client_version = None
223 used_schema = None
224 if version_fields != None:
225 client_version = client_data
226 for field in version_fields:
227 if field in client_version:
228 client_version = client_version[field]
229 else:
230 client_version=None
231 break
232 if client_version==None:
233 used_schema=default_schema
234 elif version_dict_schema!=None:
235 if client_version in version_dict_schema:
236 used_schema = version_dict_schema[client_version]
237 elif None in version_dict_schema:
238 used_schema = version_dict_schema[None]
239 if used_schema==None:
240 bottle.abort(HTTP_Bad_Request, "Invalid schema version or missing version field")
241
242 js_v(client_data, used_schema)
243 return client_data, used_schema
244 except (ValueError, yaml.YAMLError) as exc:
245 error_text += str(exc)
246 logger.error(error_text)
247 bottle.abort(HTTP_Bad_Request, error_text)
248 except js_e.ValidationError as exc:
249 logger.error("validate_in error, jsonschema exception at '%s' '%s' ", str(exc.path), str(exc.message))
250 error_pos = ""
251 if len(exc.path)>0: error_pos=" at " + ":".join(map(json.dumps, exc.path))
252 bottle.abort(HTTP_Bad_Request, error_text + exc.message + error_pos)
253 #except:
254 # bottle.abort(HTTP_Bad_Request, "Content error: Failed to parse Content-Type", error_pos)
255 # raise
256
257 def filter_query_string(qs, http2db, allowed):
258 '''Process query string (qs) checking that contains only valid tokens for avoiding SQL injection
259 Attributes:
260 'qs': bottle.FormsDict variable to be processed. None or empty is considered valid
261 'http2db': dictionary with change from http API naming (dictionary key) to database naming(dictionary value)
262 'allowed': list of allowed string tokens (API http naming). All the keys of 'qs' must be one of 'allowed'
263 Return: A tuple with the (select,where,limit) to be use in a database query. All of then transformed to the database naming
264 select: list of items to retrieve, filtered by query string 'field=token'. If no 'field' is present, allowed list is returned
265 where: dictionary with key, value, taken from the query string token=value. Empty if nothing is provided
266 limit: limit dictated by user with the query string 'limit'. 100 by default
267 abort if not permited, using bottel.abort
268 '''
269 where={}
270 limit=100
271 select=[]
272 #if type(qs) is not bottle.FormsDict:
273 # bottle.abort(HTTP_Internal_Server_Error, '!!!!!!!!!!!!!!invalid query string not a dictionary')
274 # #bottle.abort(HTTP_Internal_Server_Error, "call programmer")
275 for k in qs:
276 if k=='field':
277 select += qs.getall(k)
278 for v in select:
279 if v not in allowed:
280 bottle.abort(HTTP_Bad_Request, "Invalid query string at 'field="+v+"'")
281 elif k=='limit':
282 try:
283 limit=int(qs[k])
284 except:
285 bottle.abort(HTTP_Bad_Request, "Invalid query string at 'limit="+qs[k]+"'")
286 else:
287 if k not in allowed:
288 bottle.abort(HTTP_Bad_Request, "Invalid query string at '"+k+"="+qs[k]+"'")
289 if qs[k]!="null": where[k]=qs[k]
290 else: where[k]=None
291 if len(select)==0: select += allowed
292 #change from http api to database naming
293 for i in range(0,len(select)):
294 k=select[i]
295 if http2db and k in http2db:
296 select[i] = http2db[k]
297 if http2db:
298 change_keys_http2db(where, http2db)
299 #print "filter_query_string", select,where,limit
300
301 return select,where,limit
302
303 @bottle.hook('after_request')
304 def enable_cors():
305 '''Don't know yet if really needed. Keep it just in case'''
306 bottle.response.headers['Access-Control-Allow-Origin'] = '*'
307
308 #
309 # VNFs
310 #
311
312 @bottle.route(url_base + '/tenants', method='GET')
313 def http_get_tenants():
314 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
315 select_,where_,limit_ = filter_query_string(bottle.request.query, None,
316 ('uuid','name','description','created_at') )
317 try:
318 tenants = mydb.get_rows(FROM='nfvo_tenants', SELECT=select_,WHERE=where_,LIMIT=limit_)
319 #change_keys_http2db(content, http2db_tenant, reverse=True)
320 convert_datetime2str(tenants)
321 data={'tenants' : tenants}
322 return format_out(data)
323 except db_base_Exception as e:
324 logger.error("http_get_tenants error {}: {}".format(e.http_code, str(e)))
325 bottle.abort(e.http_code, str(e))
326 except Exception as e:
327 logger.error("Unexpected exception: ", exc_info=True)
328 bottle.abort(HTTP_Internal_Server_Error, type(e).__name__ + ": " + str(e))
329
330
331 @bottle.route(url_base + '/tenants/<tenant_id>', method='GET')
332 def http_get_tenant_id(tenant_id):
333 '''get tenant details, can use both uuid or name'''
334 #obtain data
335 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
336 try:
337 tenant = mydb.get_table_by_uuid_name('nfvo_tenants', tenant_id, "tenant")
338 #change_keys_http2db(content, http2db_tenant, reverse=True)
339 convert_datetime2str(tenant)
340 data={'tenant' : tenant}
341 return format_out(data)
342 except db_base_Exception as e:
343 logger.error("http_get_tenant_id error {}: {}".format(e.http_code, str(e)))
344 bottle.abort(e.http_code, str(e))
345 except Exception as e:
346 logger.error("Unexpected exception: ", exc_info=True)
347 bottle.abort(HTTP_Internal_Server_Error, type(e).__name__ + ": " + str(e))
348
349
350 @bottle.route(url_base + '/tenants', method='POST')
351 def http_post_tenants():
352 '''insert a tenant into the catalogue. '''
353 #parse input data
354 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
355 http_content,_ = format_in( tenant_schema )
356 r = utils.remove_extra_items(http_content, tenant_schema)
357 if r:
358 logger.debug("Remove received extra items %s", str(r))
359 try:
360 data = nfvo.new_tenant(mydb, http_content['tenant'])
361 return http_get_tenant_id(data)
362 except (nfvo.NfvoException, db_base_Exception) as e:
363 logger.error("http_post_tenants error {}: {}".format(e.http_code, str(e)))
364 bottle.abort(e.http_code, str(e))
365 except Exception as e:
366 logger.error("Unexpected exception: ", exc_info=True)
367 bottle.abort(HTTP_Internal_Server_Error, type(e).__name__ + ": " + str(e))
368
369
370 @bottle.route(url_base + '/tenants/<tenant_id>', method='PUT')
371 def http_edit_tenant_id(tenant_id):
372 '''edit tenant details, can use both uuid or name'''
373 #parse input data
374 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
375 http_content,_ = format_in( tenant_edit_schema )
376 r = utils.remove_extra_items(http_content, tenant_edit_schema)
377 if r:
378 logger.debug("Remove received extra items %s", str(r))
379
380 #obtain data, check that only one exist
381 try:
382 tenant = mydb.get_table_by_uuid_name('nfvo_tenants', tenant_id)
383 #edit data
384 tenant_id = tenant['uuid']
385 where={'uuid': tenant['uuid']}
386 mydb.update_rows('nfvo_tenants', http_content['tenant'], where)
387 return http_get_tenant_id(tenant_id)
388 except db_base_Exception as e:
389 logger.error("http_edit_tenant_id error {}: {}".format(e.http_code, str(e)))
390 bottle.abort(e.http_code, str(e))
391 except Exception as e:
392 logger.error("Unexpected exception: ", exc_info=True)
393 bottle.abort(HTTP_Internal_Server_Error, type(e).__name__ + ": " + str(e))
394
395
396 @bottle.route(url_base + '/tenants/<tenant_id>', method='DELETE')
397 def http_delete_tenant_id(tenant_id):
398 '''delete a tenant from database, can use both uuid or name'''
399 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
400 try:
401 data = nfvo.delete_tenant(mydb, tenant_id)
402 return format_out({"result":"tenant " + data + " deleted"})
403 except db_base_Exception as e:
404 logger.error("http_delete_tenant_id error {}: {}".format(e.http_code, str(e)))
405 bottle.abort(e.http_code, str(e))
406 except Exception as e:
407 logger.error("Unexpected exception: ", exc_info=True)
408 bottle.abort(HTTP_Internal_Server_Error, type(e).__name__ + ": " + str(e))
409
410
411 @bottle.route(url_base + '/<tenant_id>/datacenters', method='GET')
412 def http_get_datacenters(tenant_id):
413 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
414 try:
415 if tenant_id != 'any':
416 #check valid tenant_id
417 nfvo.check_tenant(mydb, tenant_id)
418 select_,where_,limit_ = filter_query_string(bottle.request.query, None,
419 ('uuid','name','vim_url','type','created_at') )
420 if tenant_id != 'any':
421 where_['nfvo_tenant_id'] = tenant_id
422 if 'created_at' in select_:
423 select_[ select_.index('created_at') ] = 'd.created_at as created_at'
424 if 'created_at' in where_:
425 where_['d.created_at'] = where_.pop('created_at')
426 datacenters = mydb.get_rows(FROM='datacenters as d join tenants_datacenters as td on d.uuid=td.datacenter_id',
427 SELECT=select_,WHERE=where_,LIMIT=limit_)
428 else:
429 datacenters = mydb.get_rows(FROM='datacenters',
430 SELECT=select_,WHERE=where_,LIMIT=limit_)
431 #change_keys_http2db(content, http2db_tenant, reverse=True)
432 convert_datetime2str(datacenters)
433 data={'datacenters' : datacenters}
434 return format_out(data)
435 except (nfvo.NfvoException, db_base_Exception) as e:
436 logger.error("http_get_datacenters error {}: {}".format(e.http_code, str(e)))
437 bottle.abort(e.http_code, str(e))
438 except Exception as e:
439 logger.error("Unexpected exception: ", exc_info=True)
440 bottle.abort(HTTP_Internal_Server_Error, type(e).__name__ + ": " + str(e))
441
442
443 @bottle.route(url_base + '/<tenant_id>/datacenters/<datacenter_id>', method='GET')
444 def http_get_datacenter_id(tenant_id, datacenter_id):
445 '''get datacenter details, can use both uuid or name'''
446 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
447 try:
448 if tenant_id != 'any':
449 #check valid tenant_id
450 nfvo.check_tenant(mydb, tenant_id)
451 #obtain data
452 what = 'uuid' if utils.check_valid_uuid(datacenter_id) else 'name'
453 where_={}
454 where_[what] = datacenter_id
455 select_=['uuid', 'name','vim_url', 'vim_url_admin', 'type', 'd.config as config', 'description', 'd.created_at as created_at']
456 if tenant_id != 'any':
457 select_.append("datacenter_tenant_id")
458 where_['td.nfvo_tenant_id']= tenant_id
459 from_='datacenters as d join tenants_datacenters as td on d.uuid=td.datacenter_id'
460 else:
461 from_='datacenters as d'
462 datacenters = mydb.get_rows(
463 SELECT=select_,
464 FROM=from_,
465 WHERE=where_)
466
467 if len(datacenters)==0:
468 bottle.abort( HTTP_Not_Found, "No datacenter found for tenant with {} '{}'".format(what, datacenter_id) )
469 elif len(datacenters)>1:
470 bottle.abort( HTTP_Bad_Request, "More than one datacenter found for tenant with {} '{}'".format(what, datacenter_id) )
471 datacenter = datacenters[0]
472 if tenant_id != 'any':
473 #get vim tenant info
474 vim_tenants = mydb.get_rows(
475 SELECT=("vim_tenant_name", "vim_tenant_id", "user", "passwd", "config"),
476 FROM="datacenter_tenants",
477 WHERE={"uuid": datacenters[0]["datacenter_tenant_id"]},
478 ORDER_BY=("created", ) )
479 del datacenter["datacenter_tenant_id"]
480 datacenter["vim_tenants"] = vim_tenants
481 for vim_tenant in vim_tenants:
482 if vim_tenant["passwd"]:
483 vim_tenant["passwd"] = "******"
484 if vim_tenant['config'] != None:
485 try:
486 config_dict = yaml.load(vim_tenant['config'])
487 vim_tenant['config'] = config_dict
488 except Exception as e:
489 logger.error("Exception '%s' while trying to load config information", str(e))
490
491 if datacenter['config'] != None:
492 try:
493 config_dict = yaml.load(datacenter['config'])
494 datacenter['config'] = config_dict
495 except Exception as e:
496 logger.error("Exception '%s' while trying to load config information", str(e))
497 #change_keys_http2db(content, http2db_datacenter, reverse=True)
498 convert_datetime2str(datacenter)
499 data={'datacenter' : datacenter}
500 return format_out(data)
501 except (nfvo.NfvoException, db_base_Exception) as e:
502 logger.error("http_get_datacenter_id error {}: {}".format(e.http_code, str(e)))
503 bottle.abort(e.http_code, str(e))
504 except Exception as e:
505 logger.error("Unexpected exception: ", exc_info=True)
506 bottle.abort(HTTP_Internal_Server_Error, type(e).__name__ + ": " + str(e))
507
508
509 @bottle.route(url_base + '/datacenters', method='POST')
510 def http_post_datacenters():
511 '''insert a datacenter into the catalogue. '''
512 #parse input data
513 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
514 http_content,_ = format_in( datacenter_schema )
515 r = utils.remove_extra_items(http_content, datacenter_schema)
516 if r:
517 logger.debug("Remove received extra items %s", str(r))
518 try:
519 data = nfvo.new_datacenter(mydb, http_content['datacenter'])
520 return http_get_datacenter_id('any', data)
521 except (nfvo.NfvoException, db_base_Exception) as e:
522 logger.error("http_post_datacenters error {}: {}".format(e.http_code, str(e)))
523 bottle.abort(e.http_code, str(e))
524 except Exception as e:
525 logger.error("Unexpected exception: ", exc_info=True)
526 bottle.abort(HTTP_Internal_Server_Error, type(e).__name__ + ": " + str(e))
527
528
529 @bottle.route(url_base + '/datacenters/<datacenter_id_name>', method='PUT')
530 def http_edit_datacenter_id(datacenter_id_name):
531 '''edit datacenter details, can use both uuid or name'''
532 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
533 #parse input data
534 http_content,_ = format_in( datacenter_edit_schema )
535 r = utils.remove_extra_items(http_content, datacenter_edit_schema)
536 if r:
537 logger.debug("Remove received extra items %s", str(r))
538
539 try:
540 datacenter_id = nfvo.edit_datacenter(mydb, datacenter_id_name, http_content['datacenter'])
541 return http_get_datacenter_id('any', datacenter_id)
542 except (nfvo.NfvoException, db_base_Exception) as e:
543 logger.error("http_edit_datacenter_id error {}: {}".format(e.http_code, str(e)))
544 bottle.abort(e.http_code, str(e))
545 except Exception as e:
546 logger.error("Unexpected exception: ", exc_info=True)
547 bottle.abort(HTTP_Internal_Server_Error, type(e).__name__ + ": " + str(e))
548
549 @bottle.route(url_base + '/<tenant_id>/sdn_controllers', method='POST')
550 def http_post_sdn_controller(tenant_id):
551 '''insert a sdn controller into the catalogue. '''
552 #parse input data
553 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
554 http_content,_ = format_in( sdn_controller_schema )
555 try:
556 logger.debug("tenant_id: "+tenant_id)
557 #logger.debug("content: {}".format(http_content['sdn_controller']))
558
559 data = nfvo.sdn_controller_create(mydb, tenant_id, http_content['sdn_controller'])
560 return format_out({"sdn_controller": nfvo.sdn_controller_list(mydb, tenant_id, data)})
561 except (nfvo.NfvoException, db_base_Exception) as e:
562 logger.error("http_post_sdn_controller error {}: {}".format(e.http_code, str(e)))
563 bottle.abort(e.http_code, str(e))
564 except Exception as e:
565 logger.error("Unexpected exception: ", exc_info=True)
566 bottle.abort(HTTP_Internal_Server_Error, type(e).__name__ + ": " + str(e))
567
568 @bottle.route(url_base + '/<tenant_id>/sdn_controllers/<controller_id>', method='PUT')
569 def http_put_sdn_controller_update(tenant_id, controller_id):
570 '''Update sdn controller'''
571 #parse input data
572 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
573 http_content,_ = format_in( sdn_controller_edit_schema )
574 # r = utils.remove_extra_items(http_content, datacenter_schema)
575 # if r:
576 # logger.debug("Remove received extra items %s", str(r))
577 try:
578 #logger.debug("tenant_id: "+tenant_id)
579 logger.debug("content: {}".format(http_content['sdn_controller']))
580
581 data = nfvo.sdn_controller_update(mydb, tenant_id, controller_id, http_content['sdn_controller'])
582 return format_out({"sdn_controller": nfvo.sdn_controller_list(mydb, tenant_id, controller_id)})
583
584 except (nfvo.NfvoException, db_base_Exception) as e:
585 logger.error("http_post_sdn_controller error {}: {}".format(e.http_code, str(e)))
586 bottle.abort(e.http_code, str(e))
587 except Exception as e:
588 logger.error("Unexpected exception: ", exc_info=True)
589 bottle.abort(HTTP_Internal_Server_Error, type(e).__name__ + ": " + str(e))
590
591 @bottle.route(url_base + '/<tenant_id>/sdn_controllers', method='GET')
592 def http_get_sdn_controller(tenant_id):
593 '''get sdn controllers list, can use both uuid or name'''
594 try:
595 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
596
597 data = {'sdn_controllers': nfvo.sdn_controller_list(mydb, tenant_id)}
598 return format_out(data)
599 except (nfvo.NfvoException, db_base_Exception) as e:
600 logger.error("http_get_sdn_controller error {}: {}".format(e.http_code, str(e)))
601 bottle.abort(e.http_code, str(e))
602 except Exception as e:
603 logger.error("Unexpected exception: ", exc_info=True)
604 bottle.abort(HTTP_Internal_Server_Error, type(e).__name__ + ": " + str(e))
605
606 @bottle.route(url_base + '/<tenant_id>/sdn_controllers/<controller_id>', method='GET')
607 def http_get_sdn_controller_id(tenant_id, controller_id):
608 '''get sdn controller details, can use both uuid or name'''
609 try:
610 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
611 data = nfvo.sdn_controller_list(mydb, tenant_id, controller_id)
612 return format_out({"sdn_controllers": data})
613 except (nfvo.NfvoException, db_base_Exception) as e:
614 logger.error("http_get_sdn_controller_id error {}: {}".format(e.http_code, str(e)))
615 bottle.abort(e.http_code, str(e))
616 except Exception as e:
617 logger.error("Unexpected exception: ", exc_info=True)
618 bottle.abort(HTTP_Internal_Server_Error, type(e).__name__ + ": " + str(e))
619
620 @bottle.route(url_base + '/<tenant_id>/sdn_controllers/<controller_id>', method='DELETE')
621 def http_delete_sdn_controller_id(tenant_id, controller_id):
622 '''delete sdn controller, can use both uuid or name'''
623 try:
624 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
625 data = nfvo.sdn_controller_delete(mydb, tenant_id, controller_id)
626 return format_out(data)
627 except (nfvo.NfvoException, db_base_Exception) as e:
628 logger.error("http_delete_sdn_controller_id error {}: {}".format(e.http_code, str(e)))
629 bottle.abort(e.http_code, str(e))
630 except Exception as e:
631 logger.error("Unexpected exception: ", exc_info=True)
632 bottle.abort(HTTP_Internal_Server_Error, type(e).__name__ + ": " + str(e))
633
634 @bottle.route(url_base + '/<tenant_id>/datacenters/<datacenter_id>/sdn_mapping', method='POST')
635 def http_post_datacenter_sdn_port_mapping(tenant_id, datacenter_id):
636 '''Set the sdn port mapping for a datacenter. '''
637 #parse input data
638 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
639 http_content, _ = format_in(sdn_port_mapping_schema)
640 # r = utils.remove_extra_items(http_content, datacenter_schema)
641 # if r:
642 # logger.debug("Remove received extra items %s", str(r))
643 try:
644 data = nfvo.datacenter_sdn_port_mapping_set(mydb, tenant_id, datacenter_id, http_content['sdn_port_mapping'])
645 return format_out({"sdn_port_mapping": data})
646 except (nfvo.NfvoException, db_base_Exception) as e:
647 logger.error("http_post_datacenter_sdn_port_mapping error {}: {}".format(e.http_code, str(e)))
648 bottle.abort(e.http_code, str(e))
649 except Exception as e:
650 logger.error("Unexpected exception: ", exc_info=True)
651 bottle.abort(HTTP_Internal_Server_Error, type(e).__name__ + ": " + str(e))
652
653 @bottle.route(url_base + '/<tenant_id>/datacenters/<datacenter_id>/sdn_mapping', method='GET')
654 def http_get_datacenter_sdn_port_mapping(tenant_id, datacenter_id):
655 '''get datacenter sdn mapping details, can use both uuid or name'''
656 try:
657 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
658
659 data = nfvo.datacenter_sdn_port_mapping_list(mydb, tenant_id, datacenter_id)
660 return format_out({"sdn_port_mapping": data})
661 except (nfvo.NfvoException, db_base_Exception) as e:
662 logger.error("http_get_datacenter_sdn_port_mapping error {}: {}".format(e.http_code, str(e)))
663 bottle.abort(e.http_code, str(e))
664 except Exception as e:
665 logger.error("Unexpected exception: ", exc_info=True)
666 bottle.abort(HTTP_Internal_Server_Error, type(e).__name__ + ": " + str(e))
667
668 @bottle.route(url_base + '/<tenant_id>/datacenters/<datacenter_id>/sdn_mapping', method='DELETE')
669 def http_delete_datacenter_sdn_port_mapping(tenant_id, datacenter_id):
670 '''clean datacenter sdn mapping, can use both uuid or name'''
671 try:
672 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
673 data = nfvo.datacenter_sdn_port_mapping_delete(mydb, tenant_id, datacenter_id)
674 return format_out({"result": data})
675 except (nfvo.NfvoException, db_base_Exception) as e:
676 logger.error("http_delete_datacenter_sdn_port_mapping error {}: {}".format(e.http_code, str(e)))
677 bottle.abort(e.http_code, str(e))
678 except Exception as e:
679 logger.error("Unexpected exception: ", exc_info=True)
680 bottle.abort(HTTP_Internal_Server_Error, type(e).__name__ + ": " + str(e))
681
682 @bottle.route(url_base + '/<tenant_id>/datacenters/<datacenter_id>/networks', method='GET') #deprecated
683 @bottle.route(url_base + '/<tenant_id>/datacenters/<datacenter_id>/netmaps', method='GET')
684 @bottle.route(url_base + '/<tenant_id>/datacenters/<datacenter_id>/netmaps/<netmap_id>', method='GET')
685 def http_getnetmap_datacenter_id(tenant_id, datacenter_id, netmap_id=None):
686 '''get datacenter networks, can use both uuid or name'''
687 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
688 #obtain data
689 try:
690 datacenter_dict = mydb.get_table_by_uuid_name('datacenters', datacenter_id, "datacenter")
691 where_= {"datacenter_id":datacenter_dict['uuid']}
692 if netmap_id:
693 if utils.check_valid_uuid(netmap_id):
694 where_["uuid"] = netmap_id
695 else:
696 where_["name"] = netmap_id
697 netmaps =mydb.get_rows(FROM='datacenter_nets',
698 SELECT=('name','vim_net_id as vim_id', 'uuid', 'type','multipoint','shared','description', 'created_at'),
699 WHERE=where_ )
700 convert_datetime2str(netmaps)
701 utils.convert_str2boolean(netmaps, ('shared', 'multipoint') )
702 if netmap_id and len(netmaps)==1:
703 data={'netmap' : netmaps[0]}
704 elif netmap_id and len(netmaps)==0:
705 bottle.abort(HTTP_Not_Found, "No netmap found with " + " and ".join(map(lambda x: str(x[0])+": "+str(x[1]), where_.iteritems())) )
706 return
707 else:
708 data={'netmaps' : netmaps}
709 return format_out(data)
710 except (nfvo.NfvoException, db_base_Exception) as e:
711 logger.error("http_getnetwork_datacenter_id error {}: {}".format(e.http_code, str(e)))
712 bottle.abort(e.http_code, str(e))
713 except Exception as e:
714 logger.error("Unexpected exception: ", exc_info=True)
715 bottle.abort(HTTP_Internal_Server_Error, type(e).__name__ + ": " + str(e))
716
717
718 @bottle.route(url_base + '/<tenant_id>/datacenters/<datacenter_id>/netmaps', method='DELETE')
719 @bottle.route(url_base + '/<tenant_id>/datacenters/<datacenter_id>/netmaps/<netmap_id>', method='DELETE')
720 def http_delnetmap_datacenter_id(tenant_id, datacenter_id, netmap_id=None):
721 '''get datacenter networks, can use both uuid or name'''
722 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
723 #obtain data
724 try:
725 datacenter_dict = mydb.get_table_by_uuid_name('datacenters', datacenter_id, "datacenter")
726 where_= {"datacenter_id":datacenter_dict['uuid']}
727 if netmap_id:
728 if utils.check_valid_uuid(netmap_id):
729 where_["uuid"] = netmap_id
730 else:
731 where_["name"] = netmap_id
732 #change_keys_http2db(content, http2db_tenant, reverse=True)
733 deleted = mydb.delete_row(FROM='datacenter_nets', WHERE= where_)
734 if deleted == 0 and netmap_id :
735 bottle.abort(HTTP_Not_Found, "No netmap found with " + " and ".join(map(lambda x: str(x[0])+": "+str(x[1]), where_.iteritems())) )
736 if netmap_id:
737 return format_out({"result": "netmap %s deleted" % netmap_id})
738 else:
739 return format_out({"result": "%d netmap deleted" % deleted})
740 except (nfvo.NfvoException, db_base_Exception) as e:
741 logger.error("http_delnetmap_datacenter_id error {}: {}".format(e.http_code, str(e)))
742 bottle.abort(e.http_code, str(e))
743 except Exception as e:
744 logger.error("Unexpected exception: ", exc_info=True)
745 bottle.abort(HTTP_Internal_Server_Error, type(e).__name__ + ": " + str(e))
746
747
748 @bottle.route(url_base + '/<tenant_id>/datacenters/<datacenter_id>/netmaps/upload', method='POST')
749 def http_uploadnetmap_datacenter_id(tenant_id, datacenter_id):
750 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
751 try:
752 netmaps = nfvo.datacenter_new_netmap(mydb, tenant_id, datacenter_id, None)
753 convert_datetime2str(netmaps)
754 utils.convert_str2boolean(netmaps, ('shared', 'multipoint') )
755 data={'netmaps' : netmaps}
756 return format_out(data)
757 except (nfvo.NfvoException, db_base_Exception) as e:
758 logger.error("http_uploadnetmap_datacenter_id error {}: {}".format(e.http_code, str(e)))
759 bottle.abort(e.http_code, str(e))
760 except Exception as e:
761 logger.error("Unexpected exception: ", exc_info=True)
762 bottle.abort(HTTP_Internal_Server_Error, type(e).__name__ + ": " + str(e))
763
764
765 @bottle.route(url_base + '/<tenant_id>/datacenters/<datacenter_id>/netmaps', method='POST')
766 def http_postnetmap_datacenter_id(tenant_id, datacenter_id):
767 '''creates a new netmap'''
768 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
769 #parse input data
770 http_content,_ = format_in( netmap_new_schema )
771 r = utils.remove_extra_items(http_content, netmap_new_schema)
772 if r:
773 logger.debug("Remove received extra items %s", str(r))
774 try:
775 #obtain data, check that only one exist
776 netmaps = nfvo.datacenter_new_netmap(mydb, tenant_id, datacenter_id, http_content)
777 convert_datetime2str(netmaps)
778 utils.convert_str2boolean(netmaps, ('shared', 'multipoint') )
779 data={'netmaps' : netmaps}
780 return format_out(data)
781 except (nfvo.NfvoException, db_base_Exception) as e:
782 logger.error("http_postnetmap_datacenter_id error {}: {}".format(e.http_code, str(e)))
783 bottle.abort(e.http_code, str(e))
784 except Exception as e:
785 logger.error("Unexpected exception: ", exc_info=True)
786 bottle.abort(HTTP_Internal_Server_Error, type(e).__name__ + ": " + str(e))
787
788
789 @bottle.route(url_base + '/<tenant_id>/datacenters/<datacenter_id>/netmaps/<netmap_id>', method='PUT')
790 def http_putnettmap_datacenter_id(tenant_id, datacenter_id, netmap_id):
791 '''edit a netmap'''
792 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
793 #parse input data
794 http_content,_ = format_in( netmap_edit_schema )
795 r = utils.remove_extra_items(http_content, netmap_edit_schema)
796 if r:
797 logger.debug("Remove received extra items %s", str(r))
798
799 #obtain data, check that only one exist
800 try:
801 nfvo.datacenter_edit_netmap(mydb, tenant_id, datacenter_id, netmap_id, http_content)
802 return http_getnetmap_datacenter_id(tenant_id, datacenter_id, netmap_id)
803 except (nfvo.NfvoException, db_base_Exception) as e:
804 logger.error("http_putnettmap_datacenter_id error {}: {}".format(e.http_code, str(e)))
805 bottle.abort(e.http_code, str(e))
806 except Exception as e:
807 logger.error("Unexpected exception: ", exc_info=True)
808 bottle.abort(HTTP_Internal_Server_Error, type(e).__name__ + ": " + str(e))
809
810
811 @bottle.route(url_base + '/<tenant_id>/datacenters/<datacenter_id>/action', method='POST')
812 def http_action_datacenter_id(tenant_id, datacenter_id):
813 '''perform an action over datacenter, can use both uuid or name'''
814 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
815 #parse input data
816 http_content,_ = format_in( datacenter_action_schema )
817 r = utils.remove_extra_items(http_content, datacenter_action_schema)
818 if r:
819 logger.debug("Remove received extra items %s", str(r))
820 try:
821 #obtain data, check that only one exist
822 result = nfvo.datacenter_action(mydb, tenant_id, datacenter_id, http_content)
823 if 'net-update' in http_content:
824 return http_getnetmap_datacenter_id(datacenter_id)
825 else:
826 return format_out(result)
827 except (nfvo.NfvoException, db_base_Exception) as e:
828 logger.error("http_action_datacenter_id error {}: {}".format(e.http_code, str(e)))
829 bottle.abort(e.http_code, str(e))
830 except Exception as e:
831 logger.error("Unexpected exception: ", exc_info=True)
832 bottle.abort(HTTP_Internal_Server_Error, type(e).__name__ + ": " + str(e))
833
834
835 @bottle.route(url_base + '/datacenters/<datacenter_id>', method='DELETE')
836 def http_delete_datacenter_id( datacenter_id):
837 '''delete a tenant from database, can use both uuid or name'''
838
839 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
840 try:
841 data = nfvo.delete_datacenter(mydb, datacenter_id)
842 return format_out({"result":"datacenter '" + data + "' deleted"})
843 except (nfvo.NfvoException, db_base_Exception) as e:
844 logger.error("http_delete_datacenter_id error {}: {}".format(e.http_code, str(e)))
845 bottle.abort(e.http_code, str(e))
846 except Exception as e:
847 logger.error("Unexpected exception: ", exc_info=True)
848 bottle.abort(HTTP_Internal_Server_Error, type(e).__name__ + ": " + str(e))
849
850
851 @bottle.route(url_base + '/<tenant_id>/datacenters/<datacenter_id>', method='POST')
852 def http_associate_datacenters(tenant_id, datacenter_id):
853 '''associate an existing datacenter to a this tenant. '''
854 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
855 #parse input data
856 http_content,_ = format_in( datacenter_associate_schema )
857 r = utils.remove_extra_items(http_content, datacenter_associate_schema)
858 if r:
859 logger.debug("Remove received extra items %s", str(r))
860 try:
861 id_ = nfvo.associate_datacenter_to_tenant(mydb, tenant_id, datacenter_id,
862 http_content['datacenter'].get('vim_tenant'),
863 http_content['datacenter'].get('vim_tenant_name'),
864 http_content['datacenter'].get('vim_username'),
865 http_content['datacenter'].get('vim_password'),
866 http_content['datacenter'].get('config')
867 )
868 return http_get_datacenter_id(tenant_id, id_)
869 except (nfvo.NfvoException, db_base_Exception) as e:
870 logger.error("http_associate_datacenters error {}: {}".format(e.http_code, str(e)))
871 bottle.abort(e.http_code, str(e))
872 except Exception as e:
873 logger.error("Unexpected exception: ", exc_info=True)
874 bottle.abort(HTTP_Internal_Server_Error, type(e).__name__ + ": " + str(e))
875
876 @bottle.route(url_base + '/<tenant_id>/datacenters/<datacenter_id>', method='PUT')
877 def http_associate_datacenters_edit(tenant_id, datacenter_id):
878 '''associate an existing datacenter to a this tenant. '''
879 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
880 #parse input data
881 http_content,_ = format_in( datacenter_associate_schema )
882 r = utils.remove_extra_items(http_content, datacenter_associate_schema)
883 if r:
884 logger.debug("Remove received extra items %s", str(r))
885 try:
886 id_ = nfvo.edit_datacenter_to_tenant(mydb, tenant_id, datacenter_id,
887 http_content['datacenter'].get('vim_tenant'),
888 http_content['datacenter'].get('vim_tenant_name'),
889 http_content['datacenter'].get('vim_username'),
890 http_content['datacenter'].get('vim_password'),
891 http_content['datacenter'].get('config')
892 )
893 return http_get_datacenter_id(tenant_id, id_)
894 except (nfvo.NfvoException, db_base_Exception) as e:
895 logger.error("http_associate_datacenters_edit error {}: {}".format(e.http_code, str(e)))
896 bottle.abort(e.http_code, str(e))
897 except Exception as e:
898 logger.error("Unexpected exception: ", exc_info=True)
899 bottle.abort(HTTP_Internal_Server_Error, type(e).__name__ + ": " + str(e))
900
901 @bottle.route(url_base + '/<tenant_id>/datacenters/<datacenter_id>', method='DELETE')
902 def http_deassociate_datacenters(tenant_id, datacenter_id):
903 '''deassociate an existing datacenter to a this tenant. '''
904 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
905 try:
906 data = nfvo.deassociate_datacenter_to_tenant(mydb, tenant_id, datacenter_id)
907 return format_out({"result": data})
908 except (nfvo.NfvoException, db_base_Exception) as e:
909 logger.error("http_deassociate_datacenters error {}: {}".format(e.http_code, str(e)))
910 bottle.abort(e.http_code, str(e))
911 except Exception as e:
912 logger.error("Unexpected exception: ", exc_info=True)
913 bottle.abort(HTTP_Internal_Server_Error, type(e).__name__ + ": " + str(e))
914
915
916 @bottle.route(url_base + '/<tenant_id>/vim/<datacenter_id>/<item>', method='GET')
917 @bottle.route(url_base + '/<tenant_id>/vim/<datacenter_id>/<item>/<name>', method='GET')
918 def http_get_vim_items(tenant_id, datacenter_id, item, name=None):
919 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
920 try:
921 data = nfvo.vim_action_get(mydb, tenant_id, datacenter_id, item, name)
922 return format_out(data)
923 except (nfvo.NfvoException, db_base_Exception) as e:
924 logger.error("http_get_vim_items error {}: {}".format(e.http_code, str(e)))
925 bottle.abort(e.http_code, str(e))
926 except Exception as e:
927 logger.error("Unexpected exception: ", exc_info=True)
928 bottle.abort(HTTP_Internal_Server_Error, type(e).__name__ + ": " + str(e))
929
930
931 @bottle.route(url_base + '/<tenant_id>/vim/<datacenter_id>/<item>/<name>', method='DELETE')
932 def http_del_vim_items(tenant_id, datacenter_id, item, name):
933 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
934 try:
935 data = nfvo.vim_action_delete(mydb, tenant_id, datacenter_id, item, name)
936 return format_out({"result":data})
937 except (nfvo.NfvoException, db_base_Exception) as e:
938 logger.error("http_del_vim_items error {}: {}".format(e.http_code, str(e)))
939 bottle.abort(e.http_code, str(e))
940 except Exception as e:
941 logger.error("Unexpected exception: ", exc_info=True)
942 bottle.abort(HTTP_Internal_Server_Error, type(e).__name__ + ": " + str(e))
943
944
945 @bottle.route(url_base + '/<tenant_id>/vim/<datacenter_id>/<item>', method='POST')
946 def http_post_vim_items(tenant_id, datacenter_id, item):
947 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
948 http_content,_ = format_in( object_schema )
949 try:
950 data = nfvo.vim_action_create(mydb, tenant_id, datacenter_id, item, http_content)
951 return format_out(data)
952 except (nfvo.NfvoException, db_base_Exception) as e:
953 logger.error("http_post_vim_items error {}: {}".format(e.http_code, str(e)))
954 bottle.abort(e.http_code, str(e))
955 except Exception as e:
956 logger.error("Unexpected exception: ", exc_info=True)
957 bottle.abort(HTTP_Internal_Server_Error, type(e).__name__ + ": " + str(e))
958
959
960 @bottle.route(url_base + '/<tenant_id>/vnfs', method='GET')
961 def http_get_vnfs(tenant_id):
962 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
963 try:
964 if tenant_id != 'any':
965 #check valid tenant_id
966 nfvo.check_tenant(mydb, tenant_id)
967 select_,where_,limit_ = filter_query_string(bottle.request.query, None,
968 ('uuid','name','description','public', "tenant_id", "created_at") )
969 where_or = {}
970 if tenant_id != "any":
971 where_or["tenant_id"] = tenant_id
972 where_or["public"] = True
973 vnfs = mydb.get_rows(FROM='vnfs', SELECT=select_,WHERE=where_,WHERE_OR=where_or, WHERE_AND_OR="AND",LIMIT=limit_)
974 #change_keys_http2db(content, http2db_vnf, reverse=True)
975 utils.convert_str2boolean(vnfs, ('public',))
976 convert_datetime2str(vnfs)
977 data={'vnfs' : vnfs}
978 return format_out(data)
979 except (nfvo.NfvoException, db_base_Exception) as e:
980 logger.error("http_get_vnfs error {}: {}".format(e.http_code, str(e)))
981 bottle.abort(e.http_code, str(e))
982 except Exception as e:
983 logger.error("Unexpected exception: ", exc_info=True)
984 bottle.abort(HTTP_Internal_Server_Error, type(e).__name__ + ": " + str(e))
985
986
987 @bottle.route(url_base + '/<tenant_id>/vnfs/<vnf_id>', method='GET')
988 def http_get_vnf_id(tenant_id,vnf_id):
989 '''get vnf details, can use both uuid or name'''
990 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
991 try:
992 vnf = nfvo.get_vnf_id(mydb,tenant_id,vnf_id)
993 utils.convert_str2boolean(vnf, ('public',))
994 convert_datetime2str(vnf)
995 return format_out(vnf)
996 except (nfvo.NfvoException, db_base_Exception) as e:
997 logger.error("http_get_vnf_id error {}: {}".format(e.http_code, str(e)))
998 bottle.abort(e.http_code, str(e))
999 except Exception as e:
1000 logger.error("Unexpected exception: ", exc_info=True)
1001 bottle.abort(HTTP_Internal_Server_Error, type(e).__name__ + ": " + str(e))
1002
1003
1004 @bottle.route(url_base + '/<tenant_id>/vnfs', method='POST')
1005 def http_post_vnfs(tenant_id):
1006 '''insert a vnf into the catalogue. Creates the flavor and images in the VIM, and creates the VNF and its internal structure in the OPENMANO DB'''
1007 #print "Parsing the YAML file of the VNF"
1008 #parse input data
1009 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
1010 http_content, used_schema = format_in( vnfd_schema_v01, ("schema_version",), {"0.2": vnfd_schema_v02})
1011 r = utils.remove_extra_items(http_content, used_schema)
1012 if r:
1013 logger.debug("Remove received extra items %s", str(r))
1014 try:
1015 if used_schema == vnfd_schema_v01:
1016 vnf_id = nfvo.new_vnf(mydb,tenant_id,http_content)
1017 elif used_schema == vnfd_schema_v02:
1018 vnf_id = nfvo.new_vnf_v02(mydb,tenant_id,http_content)
1019 else:
1020 logger.warning('Unexpected schema_version: %s', http_content.get("schema_version"))
1021 bottle.abort(HTTP_Bad_Request, "Invalid schema version")
1022 return http_get_vnf_id(tenant_id, vnf_id)
1023 except (nfvo.NfvoException, db_base_Exception) as e:
1024 logger.error("http_post_vnfs error {}: {}".format(e.http_code, str(e)))
1025 bottle.abort(e.http_code, str(e))
1026 except Exception as e:
1027 logger.error("Unexpected exception: ", exc_info=True)
1028 bottle.abort(HTTP_Internal_Server_Error, type(e).__name__ + ": " + str(e))
1029
1030
1031 @bottle.route(url_base + '/<tenant_id>/vnfs/<vnf_id>', method='DELETE')
1032 def http_delete_vnf_id(tenant_id,vnf_id):
1033 '''delete a vnf from database, and images and flavors in VIM when appropriate, can use both uuid or name'''
1034 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
1035 #check valid tenant_id and deletes the vnf, including images,
1036 try:
1037 data = nfvo.delete_vnf(mydb,tenant_id,vnf_id)
1038 #print json.dumps(data, indent=4)
1039 return format_out({"result":"VNF " + data + " deleted"})
1040 except (nfvo.NfvoException, db_base_Exception) as e:
1041 logger.error("http_delete_vnf_id error {}: {}".format(e.http_code, str(e)))
1042 bottle.abort(e.http_code, str(e))
1043 except Exception as e:
1044 logger.error("Unexpected exception: ", exc_info=True)
1045 bottle.abort(HTTP_Internal_Server_Error, type(e).__name__ + ": " + str(e))
1046
1047
1048 #@bottle.route(url_base + '/<tenant_id>/hosts/topology', method='GET')
1049 #@bottle.route(url_base + '/<tenant_id>/physicalview/Madrid-Alcantara', method='GET')
1050 @bottle.route(url_base + '/<tenant_id>/physicalview/<datacenter>', method='GET')
1051 def http_get_hosts(tenant_id, datacenter):
1052 '''get the tidvim host hopology from the vim.'''
1053 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
1054 #print "http_get_hosts received by tenant " + tenant_id + ' datacenter ' + datacenter
1055 try:
1056 if datacenter == 'treeview':
1057 data = nfvo.get_hosts(mydb, tenant_id)
1058 else:
1059 #openmano-gui is using a hardcoded value for the datacenter
1060 result, data = nfvo.get_hosts_info(mydb, tenant_id) #, datacenter)
1061
1062 if result < 0:
1063 #print "http_get_hosts error %d %s" % (-result, data)
1064 bottle.abort(-result, data)
1065 else:
1066 convert_datetime2str(data)
1067 #print json.dumps(data, indent=4)
1068 return format_out(data)
1069 except (nfvo.NfvoException, db_base_Exception) as e:
1070 logger.error("http_get_hosts error {}: {}".format(e.http_code, str(e)))
1071 bottle.abort(e.http_code, str(e))
1072 except Exception as e:
1073 logger.error("Unexpected exception: ", exc_info=True)
1074 bottle.abort(HTTP_Internal_Server_Error, type(e).__name__ + ": " + str(e))
1075
1076
1077 @bottle.route(url_base + '/<path:path>', method='OPTIONS')
1078 def http_options_deploy(path):
1079 '''For some reason GUI web ask for OPTIONS that must be responded'''
1080 #TODO: check correct path, and correct headers request
1081 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
1082 bottle.response.set_header('Access-Control-Allow-Methods','POST, GET, PUT, DELETE, OPTIONS')
1083 bottle.response.set_header('Accept','application/yaml,application/json')
1084 bottle.response.set_header('Content-Type','application/yaml,application/json')
1085 bottle.response.set_header('Access-Control-Allow-Headers','content-type')
1086 bottle.response.set_header('Access-Control-Allow-Origin','*')
1087 return
1088
1089 @bottle.route(url_base + '/<tenant_id>/topology/deploy', method='POST')
1090 def http_post_deploy(tenant_id):
1091 '''post topology deploy.'''
1092 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
1093
1094 http_content, used_schema = format_in( nsd_schema_v01, ("schema_version",), {2: nsd_schema_v02})
1095 #r = utils.remove_extra_items(http_content, used_schema)
1096 #if r is not None: print "http_post_deploy: Warning: remove extra items ", r
1097 #print "http_post_deploy input: ", http_content
1098
1099 try:
1100 scenario_id = nfvo.new_scenario(mydb, tenant_id, http_content)
1101 instance = nfvo.start_scenario(mydb, tenant_id, scenario_id, http_content['name'], http_content['name'])
1102 #print json.dumps(data, indent=4)
1103 return format_out(instance)
1104 except (nfvo.NfvoException, db_base_Exception) as e:
1105 logger.error("http_post_deploy error {}: {}".format(e.http_code, str(e)))
1106 bottle.abort(e.http_code, str(e))
1107 except Exception as e:
1108 logger.error("Unexpected exception: ", exc_info=True)
1109 bottle.abort(HTTP_Internal_Server_Error, type(e).__name__ + ": " + str(e))
1110
1111
1112 @bottle.route(url_base + '/<tenant_id>/topology/verify', method='POST')
1113 def http_post_verify(tenant_id):
1114 #TODO:
1115 # '''post topology verify'''
1116 # print "http_post_verify by tenant " + tenant_id + ' datacenter ' + datacenter
1117 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
1118 return
1119
1120 #
1121 # SCENARIOS
1122 #
1123
1124 @bottle.route(url_base + '/<tenant_id>/scenarios', method='POST')
1125 def http_post_scenarios(tenant_id):
1126 '''add a scenario into the catalogue. Creates the scenario and its internal structure in the OPENMANO DB'''
1127 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
1128 http_content, used_schema = format_in( nsd_schema_v01, ("schema_version",), {2: nsd_schema_v02, "0.3": nsd_schema_v03})
1129 #r = utils.remove_extra_items(http_content, used_schema)
1130 #if r is not None: print "http_post_scenarios: Warning: remove extra items ", r
1131 #print "http_post_scenarios input: ", http_content
1132 try:
1133 if used_schema == nsd_schema_v01:
1134 scenario_id = nfvo.new_scenario(mydb, tenant_id, http_content)
1135 elif used_schema == nsd_schema_v02:
1136 scenario_id = nfvo.new_scenario_v02(mydb, tenant_id, http_content, "0.2")
1137 elif used_schema == nsd_schema_v03:
1138 scenario_id = nfvo.new_scenario_v02(mydb, tenant_id, http_content, "0.3")
1139 else:
1140 logger.warning('Unexpected schema_version: %s', http_content.get("schema_version"))
1141 bottle.abort(HTTP_Bad_Request, "Invalid schema version")
1142 #print json.dumps(data, indent=4)
1143 #return format_out(data)
1144 return http_get_scenario_id(tenant_id, scenario_id)
1145 except (nfvo.NfvoException, db_base_Exception) as e:
1146 logger.error("http_post_scenarios error {}: {}".format(e.http_code, str(e)))
1147 bottle.abort(e.http_code, str(e))
1148 except Exception as e:
1149 logger.error("Unexpected exception: ", exc_info=True)
1150 bottle.abort(HTTP_Internal_Server_Error, type(e).__name__ + ": " + str(e))
1151
1152
1153 @bottle.route(url_base + '/<tenant_id>/scenarios/<scenario_id>/action', method='POST')
1154 def http_post_scenario_action(tenant_id, scenario_id):
1155 '''take an action over a scenario'''
1156 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
1157 # parse input data
1158 http_content, _ = format_in(scenario_action_schema)
1159 r = utils.remove_extra_items(http_content, scenario_action_schema)
1160 if r:
1161 logger.debug("Remove received extra items %s", str(r))
1162 try:
1163 # check valid tenant_id
1164 nfvo.check_tenant(mydb, tenant_id)
1165 if "start" in http_content:
1166 data = nfvo.start_scenario(mydb, tenant_id, scenario_id, http_content['start']['instance_name'], \
1167 http_content['start'].get('description',http_content['start']['instance_name']),
1168 http_content['start'].get('datacenter') )
1169 return format_out(data)
1170 elif "deploy" in http_content: #Equivalent to start
1171 data = nfvo.start_scenario(mydb, tenant_id, scenario_id, http_content['deploy']['instance_name'],
1172 http_content['deploy'].get('description',http_content['deploy']['instance_name']),
1173 http_content['deploy'].get('datacenter') )
1174 return format_out(data)
1175 elif "reserve" in http_content: #Reserve resources
1176 data = nfvo.start_scenario(mydb, tenant_id, scenario_id, http_content['reserve']['instance_name'],
1177 http_content['reserve'].get('description',http_content['reserve']['instance_name']),
1178 http_content['reserve'].get('datacenter'), startvms=False )
1179 return format_out(data)
1180 elif "verify" in http_content: #Equivalent to start and then delete
1181 data = nfvo.start_scenario(mydb, tenant_id, scenario_id, http_content['verify']['instance_name'],
1182 http_content['verify'].get('description',http_content['verify']['instance_name']),
1183 http_content['verify'].get('datacenter'), startvms=False )
1184 instance_id = data['uuid']
1185 nfvo.delete_instance(mydb, tenant_id,instance_id)
1186 return format_out({"result":"Verify OK"})
1187 except (nfvo.NfvoException, db_base_Exception) as e:
1188 logger.error("http_post_scenario_action error {}: {}".format(e.http_code, str(e)))
1189 bottle.abort(e.http_code, str(e))
1190 except Exception as e:
1191 logger.error("Unexpected exception: ", exc_info=True)
1192 bottle.abort(HTTP_Internal_Server_Error, type(e).__name__ + ": " + str(e))
1193
1194
1195 @bottle.route(url_base + '/<tenant_id>/scenarios', method='GET')
1196 def http_get_scenarios(tenant_id):
1197 '''get scenarios list'''
1198 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
1199 try:
1200 #check valid tenant_id
1201 if tenant_id != "any":
1202 nfvo.check_tenant(mydb, tenant_id)
1203 #obtain data
1204 s,w,l=filter_query_string(bottle.request.query, None, ('uuid', 'name', 'description', 'tenant_id', 'created_at', 'public'))
1205 where_or={}
1206 if tenant_id != "any":
1207 where_or["tenant_id"] = tenant_id
1208 where_or["public"] = True
1209 scenarios = mydb.get_rows(SELECT=s, WHERE=w, WHERE_OR=where_or, WHERE_AND_OR="AND", LIMIT=l, FROM='scenarios')
1210 convert_datetime2str(scenarios)
1211 utils.convert_str2boolean(scenarios, ('public',) )
1212 data={'scenarios':scenarios}
1213 #print json.dumps(scenarios, indent=4)
1214 return format_out(data)
1215 except (nfvo.NfvoException, db_base_Exception) as e:
1216 logger.error("http_get_scenarios error {}: {}".format(e.http_code, str(e)))
1217 bottle.abort(e.http_code, str(e))
1218 except Exception as e:
1219 logger.error("Unexpected exception: ", exc_info=True)
1220 bottle.abort(HTTP_Internal_Server_Error, type(e).__name__ + ": " + str(e))
1221
1222
1223 @bottle.route(url_base + '/<tenant_id>/scenarios/<scenario_id>', method='GET')
1224 def http_get_scenario_id(tenant_id, scenario_id):
1225 '''get scenario details, can use both uuid or name'''
1226 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
1227 try:
1228 #check valid tenant_id
1229 if tenant_id != "any":
1230 nfvo.check_tenant(mydb, tenant_id)
1231 #obtain data
1232 scenario = mydb.get_scenario(scenario_id, tenant_id)
1233 convert_datetime2str(scenario)
1234 data={'scenario' : scenario}
1235 return format_out(data)
1236 except (nfvo.NfvoException, db_base_Exception) as e:
1237 logger.error("http_get_scenarios error {}: {}".format(e.http_code, str(e)))
1238 bottle.abort(e.http_code, str(e))
1239 except Exception as e:
1240 logger.error("Unexpected exception: ", exc_info=True)
1241 bottle.abort(HTTP_Internal_Server_Error, type(e).__name__ + ": " + str(e))
1242
1243
1244 @bottle.route(url_base + '/<tenant_id>/scenarios/<scenario_id>', method='DELETE')
1245 def http_delete_scenario_id(tenant_id, scenario_id):
1246 '''delete a scenario from database, can use both uuid or name'''
1247 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
1248 try:
1249 #check valid tenant_id
1250 if tenant_id != "any":
1251 nfvo.check_tenant(mydb, tenant_id)
1252 #obtain data
1253 data = mydb.delete_scenario(scenario_id, tenant_id)
1254 #print json.dumps(data, indent=4)
1255 return format_out({"result":"scenario " + data + " deleted"})
1256 except (nfvo.NfvoException, db_base_Exception) as e:
1257 logger.error("http_delete_scenario_id error {}: {}".format(e.http_code, str(e)))
1258 bottle.abort(e.http_code, str(e))
1259 except Exception as e:
1260 logger.error("Unexpected exception: ", exc_info=True)
1261 bottle.abort(HTTP_Internal_Server_Error, type(e).__name__ + ": " + str(e))
1262
1263
1264 @bottle.route(url_base + '/<tenant_id>/scenarios/<scenario_id>', method='PUT')
1265 def http_put_scenario_id(tenant_id, scenario_id):
1266 '''edit an existing scenario id'''
1267 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
1268 http_content,_ = format_in( scenario_edit_schema )
1269 #r = utils.remove_extra_items(http_content, scenario_edit_schema)
1270 #if r is not None: print "http_put_scenario_id: Warning: remove extra items ", r
1271 #print "http_put_scenario_id input: ", http_content
1272 try:
1273 nfvo.edit_scenario(mydb, tenant_id, scenario_id, http_content)
1274 #print json.dumps(data, indent=4)
1275 #return format_out(data)
1276 return http_get_scenario_id(tenant_id, scenario_id)
1277 except (nfvo.NfvoException, db_base_Exception) as e:
1278 logger.error("http_put_scenario_id error {}: {}".format(e.http_code, str(e)))
1279 bottle.abort(e.http_code, str(e))
1280 except Exception as e:
1281 logger.error("Unexpected exception: ", exc_info=True)
1282 bottle.abort(HTTP_Internal_Server_Error, type(e).__name__ + ": " + str(e))
1283
1284 @bottle.route(url_base + '/<tenant_id>/instances', method='POST')
1285 def http_post_instances(tenant_id):
1286 '''create an instance-scenario'''
1287 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
1288 # parse input data
1289 http_content, used_schema = format_in(instance_scenario_create_schema_v01)
1290 r = utils.remove_extra_items(http_content, used_schema)
1291 if r is not None:
1292 logger.warning("http_post_instances: Warning: remove extra items %s", str(r))
1293 try:
1294 #check valid tenant_id
1295 if tenant_id != "any":
1296 nfvo.check_tenant(mydb, tenant_id)
1297 data = nfvo.create_instance(mydb, tenant_id, http_content["instance"])
1298 return format_out(data)
1299 except (nfvo.NfvoException, db_base_Exception) as e:
1300 logger.error("http_post_instances error {}: {}".format(e.http_code, str(e)))
1301 bottle.abort(e.http_code, str(e))
1302 except Exception as e:
1303 logger.error("Unexpected exception: ", exc_info=True)
1304 bottle.abort(HTTP_Internal_Server_Error, type(e).__name__ + ": " + str(e))
1305
1306 #
1307 # INSTANCES
1308 #
1309 @bottle.route(url_base + '/<tenant_id>/instances', method='GET')
1310 def http_get_instances(tenant_id):
1311 '''get instance list'''
1312 try:
1313 #check valid tenant_id
1314 if tenant_id != "any":
1315 nfvo.check_tenant(mydb, tenant_id)
1316 #obtain data
1317 s,w,l=filter_query_string(bottle.request.query, None, ('uuid', 'name', 'scenario_id', 'tenant_id', 'description', 'created_at'))
1318 if tenant_id != "any":
1319 w['tenant_id'] = tenant_id
1320 instances = mydb.get_rows(SELECT=s, WHERE=w, LIMIT=l, FROM='instance_scenarios')
1321 convert_datetime2str(instances)
1322 utils.convert_str2boolean(instances, ('public',) )
1323 data={'instances':instances}
1324 return format_out(data)
1325 except (nfvo.NfvoException, db_base_Exception) as e:
1326 logger.error("http_get_instances error {}: {}".format(e.http_code, str(e)))
1327 bottle.abort(e.http_code, str(e))
1328 except Exception as e:
1329 logger.error("Unexpected exception: ", exc_info=True)
1330 bottle.abort(HTTP_Internal_Server_Error, type(e).__name__ + ": " + str(e))
1331
1332
1333 @bottle.route(url_base + '/<tenant_id>/instances/<instance_id>', method='GET')
1334 def http_get_instance_id(tenant_id, instance_id):
1335 '''get instances details, can use both uuid or name'''
1336 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
1337 try:
1338 #check valid tenant_id
1339 if tenant_id != "any":
1340 nfvo.check_tenant(mydb, tenant_id)
1341 if tenant_id == "any":
1342 tenant_id = None
1343 #obtain data (first time is only to check that the instance exists)
1344 instance_dict = mydb.get_instance_scenario(instance_id, tenant_id, verbose=True)
1345 try:
1346 nfvo.refresh_instance(mydb, tenant_id, instance_dict)
1347 except (nfvo.NfvoException, db_base_Exception) as e:
1348 logger.warn("nfvo.refresh_instance couldn't refresh the status of the instance: %s" % str(e))
1349 #obtain data with results upated
1350 instance = mydb.get_instance_scenario(instance_id, tenant_id)
1351 convert_datetime2str(instance)
1352 #print json.dumps(instance, indent=4)
1353 return format_out(instance)
1354 except (nfvo.NfvoException, db_base_Exception) as e:
1355 logger.error("http_get_instance_id error {}: {}".format(e.http_code, str(e)))
1356 bottle.abort(e.http_code, str(e))
1357 except Exception as e:
1358 logger.error("Unexpected exception: ", exc_info=True)
1359 bottle.abort(HTTP_Internal_Server_Error, type(e).__name__ + ": " + str(e))
1360
1361
1362 @bottle.route(url_base + '/<tenant_id>/instances/<instance_id>', method='DELETE')
1363 def http_delete_instance_id(tenant_id, instance_id):
1364 '''delete instance from VIM and from database, can use both uuid or name'''
1365 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
1366 try:
1367 #check valid tenant_id
1368 if tenant_id != "any":
1369 nfvo.check_tenant(mydb, tenant_id)
1370 if tenant_id == "any":
1371 tenant_id = None
1372 #obtain data
1373 message = nfvo.delete_instance(mydb, tenant_id,instance_id)
1374 return format_out({"result":message})
1375 except (nfvo.NfvoException, db_base_Exception) as e:
1376 logger.error("http_delete_instance_id error {}: {}".format(e.http_code, str(e)))
1377 bottle.abort(e.http_code, str(e))
1378 except Exception as e:
1379 logger.error("Unexpected exception: ", exc_info=True)
1380 bottle.abort(HTTP_Internal_Server_Error, type(e).__name__ + ": " + str(e))
1381
1382
1383 @bottle.route(url_base + '/<tenant_id>/instances/<instance_id>/action', method='POST')
1384 def http_post_instance_scenario_action(tenant_id, instance_id):
1385 '''take an action over a scenario instance'''
1386 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
1387 # parse input data
1388 http_content, _ = format_in(instance_scenario_action_schema)
1389 r = utils.remove_extra_items(http_content, instance_scenario_action_schema)
1390 if r:
1391 logger.debug("Remove received extra items %s", str(r))
1392 try:
1393 #check valid tenant_id
1394 if tenant_id != "any":
1395 nfvo.check_tenant(mydb, tenant_id)
1396
1397 #print "http_post_instance_scenario_action input: ", http_content
1398 #obtain data
1399 instance = mydb.get_instance_scenario(instance_id, tenant_id)
1400 instance_id = instance["uuid"]
1401
1402 data = nfvo.instance_action(mydb, tenant_id, instance_id, http_content)
1403 return format_out(data)
1404 except (nfvo.NfvoException, db_base_Exception) as e:
1405 logger.error("http_post_instance_scenario_action error {}: {}".format(e.http_code, str(e)))
1406 bottle.abort(e.http_code, str(e))
1407 except Exception as e:
1408 logger.error("Unexpected exception: ", exc_info=True)
1409 bottle.abort(HTTP_Internal_Server_Error, type(e).__name__ + ": " + str(e))
1410
1411
1412 @bottle.error(400)
1413 @bottle.error(401)
1414 @bottle.error(404)
1415 @bottle.error(403)
1416 @bottle.error(405)
1417 @bottle.error(406)
1418 @bottle.error(409)
1419 @bottle.error(503)
1420 @bottle.error(500)
1421 def error400(error):
1422 e={"error":{"code":error.status_code, "type":error.status, "description":error.body}}
1423 bottle.response.headers['Access-Control-Allow-Origin'] = '*'
1424 return format_out(e)
1425