6c7e720f3908d30e776c56b6476061f0ced542f7
[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, sdn_external_port_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, confidential_data=False):
187 """
188 Parse the content of HTTP request against a json_schema
189 :param default_schema: The schema to be parsed by default if no version field is found in the client data. In None
190 no validation is done
191 :param version_fields: If provided it contains a tuple or list with the fields to iterate across the client data to
192 obtain the version
193 :param version_dict_schema: It contains a dictionary with the version as key, and json schema to apply as value.
194 It can contain a None as key, and this is apply if the client data version does not match any key
195 :return: user_data, used_schema: if the data is successfully decoded and matches the schema.
196 Launch a bottle abort if fails
197 """
198 #print "HEADERS :" + str(bottle.request.headers.items())
199 try:
200 error_text = "Invalid header format "
201 format_type = bottle.request.headers.get('Content-Type', 'application/json')
202 if 'application/json' in format_type:
203 error_text = "Invalid json format "
204 #Use the json decoder instead of bottle decoder because it informs about the location of error formats with a ValueError exception
205 client_data = json.load(bottle.request.body)
206 #client_data = bottle.request.json()
207 elif 'application/yaml' in format_type:
208 error_text = "Invalid yaml format "
209 client_data = yaml.load(bottle.request.body)
210 elif 'application/xml' in format_type:
211 bottle.abort(501, "Content-Type: application/xml not supported yet.")
212 else:
213 logger.warning('Content-Type ' + str(format_type) + ' not supported.')
214 bottle.abort(HTTP_Not_Acceptable, 'Content-Type ' + str(format_type) + ' not supported.')
215 return
216 # if client_data == None:
217 # bottle.abort(HTTP_Bad_Request, "Content error, empty")
218 # return
219 if confidential_data:
220 logger.debug('IN: %s', remove_clear_passwd (yaml.safe_dump(client_data, explicit_start=True, indent=4, default_flow_style=False,
221 tags=False, encoding='utf-8', allow_unicode=True)))
222 else:
223 logger.debug('IN: %s', yaml.safe_dump(client_data, explicit_start=True, indent=4, default_flow_style=False,
224 tags=False, encoding='utf-8', allow_unicode=True) )
225 # look for the client provider version
226 error_text = "Invalid content "
227 if not default_schema and not version_fields:
228 return client_data, None
229 client_version = None
230 used_schema = None
231 if version_fields != None:
232 client_version = client_data
233 for field in version_fields:
234 if field in client_version:
235 client_version = client_version[field]
236 else:
237 client_version=None
238 break
239 if client_version == None:
240 used_schema = default_schema
241 elif version_dict_schema != None:
242 if client_version in version_dict_schema:
243 used_schema = version_dict_schema[client_version]
244 elif None in version_dict_schema:
245 used_schema = version_dict_schema[None]
246 if used_schema==None:
247 bottle.abort(HTTP_Bad_Request, "Invalid schema version or missing version field")
248
249 js_v(client_data, used_schema)
250 return client_data, used_schema
251 except (ValueError, yaml.YAMLError) as exc:
252 error_text += str(exc)
253 logger.error(error_text)
254 bottle.abort(HTTP_Bad_Request, error_text)
255 except js_e.ValidationError as exc:
256 logger.error("validate_in error, jsonschema exception at '%s' '%s' ", str(exc.path), str(exc.message))
257 error_pos = ""
258 if len(exc.path)>0: error_pos=" at " + ":".join(map(json.dumps, exc.path))
259 bottle.abort(HTTP_Bad_Request, error_text + exc.message + error_pos)
260 #except:
261 # bottle.abort(HTTP_Bad_Request, "Content error: Failed to parse Content-Type", error_pos)
262 # raise
263
264 def filter_query_string(qs, http2db, allowed):
265 '''Process query string (qs) checking that contains only valid tokens for avoiding SQL injection
266 Attributes:
267 'qs': bottle.FormsDict variable to be processed. None or empty is considered valid
268 'http2db': dictionary with change from http API naming (dictionary key) to database naming(dictionary value)
269 'allowed': list of allowed string tokens (API http naming). All the keys of 'qs' must be one of 'allowed'
270 Return: A tuple with the (select,where,limit) to be use in a database query. All of then transformed to the database naming
271 select: list of items to retrieve, filtered by query string 'field=token'. If no 'field' is present, allowed list is returned
272 where: dictionary with key, value, taken from the query string token=value. Empty if nothing is provided
273 limit: limit dictated by user with the query string 'limit'. 100 by default
274 abort if not permited, using bottel.abort
275 '''
276 where={}
277 limit=100
278 select=[]
279 #if type(qs) is not bottle.FormsDict:
280 # bottle.abort(HTTP_Internal_Server_Error, '!!!!!!!!!!!!!!invalid query string not a dictionary')
281 # #bottle.abort(HTTP_Internal_Server_Error, "call programmer")
282 for k in qs:
283 if k=='field':
284 select += qs.getall(k)
285 for v in select:
286 if v not in allowed:
287 bottle.abort(HTTP_Bad_Request, "Invalid query string at 'field="+v+"'")
288 elif k=='limit':
289 try:
290 limit=int(qs[k])
291 except:
292 bottle.abort(HTTP_Bad_Request, "Invalid query string at 'limit="+qs[k]+"'")
293 else:
294 if k not in allowed:
295 bottle.abort(HTTP_Bad_Request, "Invalid query string at '"+k+"="+qs[k]+"'")
296 if qs[k]!="null": where[k]=qs[k]
297 else: where[k]=None
298 if len(select)==0: select += allowed
299 #change from http api to database naming
300 for i in range(0,len(select)):
301 k=select[i]
302 if http2db and k in http2db:
303 select[i] = http2db[k]
304 if http2db:
305 change_keys_http2db(where, http2db)
306 #print "filter_query_string", select,where,limit
307
308 return select,where,limit
309
310 @bottle.hook('after_request')
311 def enable_cors():
312 '''Don't know yet if really needed. Keep it just in case'''
313 bottle.response.headers['Access-Control-Allow-Origin'] = '*'
314
315 @bottle.route(url_base + '/version', method='GET')
316 def http_get_version():
317 return nfvo.get_version()
318 #
319 # VNFs
320 #
321
322 @bottle.route(url_base + '/tenants', method='GET')
323 def http_get_tenants():
324 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
325 select_,where_,limit_ = filter_query_string(bottle.request.query, None,
326 ('uuid','name','description','created_at') )
327 try:
328 tenants = mydb.get_rows(FROM='nfvo_tenants', SELECT=select_,WHERE=where_,LIMIT=limit_)
329 #change_keys_http2db(content, http2db_tenant, reverse=True)
330 convert_datetime2str(tenants)
331 data={'tenants' : tenants}
332 return format_out(data)
333 except bottle.HTTPError:
334 raise
335 except db_base_Exception as e:
336 logger.error("http_get_tenants error {}: {}".format(e.http_code, str(e)))
337 bottle.abort(e.http_code, str(e))
338 except Exception as e:
339 logger.error("Unexpected exception: ", exc_info=True)
340 bottle.abort(HTTP_Internal_Server_Error, type(e).__name__ + ": " + str(e))
341
342
343 @bottle.route(url_base + '/tenants/<tenant_id>', method='GET')
344 def http_get_tenant_id(tenant_id):
345 '''get tenant details, can use both uuid or name'''
346 #obtain data
347 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
348 try:
349 from_ = 'nfvo_tenants'
350 select_, where_, limit_ = filter_query_string(bottle.request.query, None,
351 ('uuid', 'name', 'description', 'created_at'))
352 what = 'uuid' if utils.check_valid_uuid(tenant_id) else 'name'
353 where_[what] = tenant_id
354 tenants = mydb.get_rows(FROM=from_, SELECT=select_,WHERE=where_)
355 #change_keys_http2db(content, http2db_tenant, reverse=True)
356 if len(tenants) == 0:
357 bottle.abort(HTTP_Not_Found, "No tenant found with {}='{}'".format(what, tenant_id))
358 elif len(tenants) > 1:
359 bottle.abort(HTTP_Bad_Request, "More than one tenant found with {}='{}'".format(what, tenant_id))
360 convert_datetime2str(tenants[0])
361 data = {'tenant': tenants[0]}
362 return format_out(data)
363 except bottle.HTTPError:
364 raise
365 except db_base_Exception as e:
366 logger.error("http_get_tenant_id error {}: {}".format(e.http_code, str(e)))
367 bottle.abort(e.http_code, str(e))
368 except Exception as e:
369 logger.error("Unexpected exception: ", exc_info=True)
370 bottle.abort(HTTP_Internal_Server_Error, type(e).__name__ + ": " + str(e))
371
372
373 @bottle.route(url_base + '/tenants', method='POST')
374 def http_post_tenants():
375 '''insert a tenant into the catalogue. '''
376 #parse input data
377 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
378 http_content,_ = format_in( tenant_schema )
379 r = utils.remove_extra_items(http_content, tenant_schema)
380 if r:
381 logger.debug("Remove received extra items %s", str(r))
382 try:
383 data = nfvo.new_tenant(mydb, http_content['tenant'])
384 return http_get_tenant_id(data)
385 except bottle.HTTPError:
386 raise
387 except (nfvo.NfvoException, db_base_Exception) as e:
388 logger.error("http_post_tenants error {}: {}".format(e.http_code, str(e)))
389 bottle.abort(e.http_code, str(e))
390 except Exception as e:
391 logger.error("Unexpected exception: ", exc_info=True)
392 bottle.abort(HTTP_Internal_Server_Error, type(e).__name__ + ": " + str(e))
393
394
395 @bottle.route(url_base + '/tenants/<tenant_id>', method='PUT')
396 def http_edit_tenant_id(tenant_id):
397 '''edit tenant details, can use both uuid or name'''
398 #parse input data
399 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
400 http_content,_ = format_in( tenant_edit_schema )
401 r = utils.remove_extra_items(http_content, tenant_edit_schema)
402 if r:
403 logger.debug("Remove received extra items %s", str(r))
404
405 #obtain data, check that only one exist
406 try:
407 tenant = mydb.get_table_by_uuid_name('nfvo_tenants', tenant_id)
408 #edit data
409 tenant_id = tenant['uuid']
410 where={'uuid': tenant['uuid']}
411 mydb.update_rows('nfvo_tenants', http_content['tenant'], where)
412 return http_get_tenant_id(tenant_id)
413 except bottle.HTTPError:
414 raise
415 except db_base_Exception as e:
416 logger.error("http_edit_tenant_id error {}: {}".format(e.http_code, str(e)))
417 bottle.abort(e.http_code, str(e))
418 except Exception as e:
419 logger.error("Unexpected exception: ", exc_info=True)
420 bottle.abort(HTTP_Internal_Server_Error, type(e).__name__ + ": " + str(e))
421
422
423 @bottle.route(url_base + '/tenants/<tenant_id>', method='DELETE')
424 def http_delete_tenant_id(tenant_id):
425 '''delete a tenant from database, can use both uuid or name'''
426 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
427 try:
428 data = nfvo.delete_tenant(mydb, tenant_id)
429 return format_out({"result":"tenant " + data + " deleted"})
430 except bottle.HTTPError:
431 raise
432 except db_base_Exception as e:
433 logger.error("http_delete_tenant_id error {}: {}".format(e.http_code, str(e)))
434 bottle.abort(e.http_code, str(e))
435 except Exception as e:
436 logger.error("Unexpected exception: ", exc_info=True)
437 bottle.abort(HTTP_Internal_Server_Error, type(e).__name__ + ": " + str(e))
438
439
440 @bottle.route(url_base + '/<tenant_id>/datacenters', method='GET')
441 def http_get_datacenters(tenant_id):
442 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
443 try:
444 if tenant_id != 'any':
445 #check valid tenant_id
446 nfvo.check_tenant(mydb, tenant_id)
447 select_,where_,limit_ = filter_query_string(bottle.request.query, None,
448 ('uuid','name','vim_url','type','created_at') )
449 if tenant_id != 'any':
450 where_['nfvo_tenant_id'] = tenant_id
451 if 'created_at' in select_:
452 select_[ select_.index('created_at') ] = 'd.created_at as created_at'
453 if 'created_at' in where_:
454 where_['d.created_at'] = where_.pop('created_at')
455 datacenters = mydb.get_rows(FROM='datacenters as d join tenants_datacenters as td on d.uuid=td.datacenter_id',
456 SELECT=select_,WHERE=where_,LIMIT=limit_)
457 else:
458 datacenters = mydb.get_rows(FROM='datacenters',
459 SELECT=select_,WHERE=where_,LIMIT=limit_)
460 #change_keys_http2db(content, http2db_tenant, reverse=True)
461 convert_datetime2str(datacenters)
462 data={'datacenters' : datacenters}
463 return format_out(data)
464 except bottle.HTTPError:
465 raise
466 except (nfvo.NfvoException, db_base_Exception) as e:
467 logger.error("http_get_datacenters error {}: {}".format(e.http_code, str(e)))
468 bottle.abort(e.http_code, str(e))
469 except Exception as e:
470 logger.error("Unexpected exception: ", exc_info=True)
471 bottle.abort(HTTP_Internal_Server_Error, type(e).__name__ + ": " + str(e))
472
473
474 @bottle.route(url_base + '/<tenant_id>/datacenters/<datacenter_id>', method='GET')
475 def http_get_datacenter_id(tenant_id, datacenter_id):
476 '''get datacenter details, can use both uuid or name'''
477 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
478 try:
479 if tenant_id != 'any':
480 #check valid tenant_id
481 nfvo.check_tenant(mydb, tenant_id)
482 #obtain data
483 what = 'uuid' if utils.check_valid_uuid(datacenter_id) else 'name'
484 where_={}
485 where_[what] = datacenter_id
486 select_=['uuid', 'name','vim_url', 'vim_url_admin', 'type', 'd.config as config', 'description', 'd.created_at as created_at']
487 if tenant_id != 'any':
488 select_.append("datacenter_tenant_id")
489 where_['td.nfvo_tenant_id']= tenant_id
490 from_='datacenters as d join tenants_datacenters as td on d.uuid=td.datacenter_id'
491 else:
492 from_='datacenters as d'
493 datacenters = mydb.get_rows(
494 SELECT=select_,
495 FROM=from_,
496 WHERE=where_)
497
498 if len(datacenters)==0:
499 bottle.abort( HTTP_Not_Found, "No datacenter found for tenant with {} '{}'".format(what, datacenter_id) )
500 elif len(datacenters)>1:
501 bottle.abort( HTTP_Bad_Request, "More than one datacenter found for tenant with {} '{}'".format(what, datacenter_id) )
502 datacenter = datacenters[0]
503 if tenant_id != 'any':
504 #get vim tenant info
505 vim_tenants = mydb.get_rows(
506 SELECT=("vim_tenant_name", "vim_tenant_id", "user", "passwd", "config"),
507 FROM="datacenter_tenants",
508 WHERE={"uuid": datacenters[0]["datacenter_tenant_id"]},
509 ORDER_BY=("created", ) )
510 del datacenter["datacenter_tenant_id"]
511 datacenter["vim_tenants"] = vim_tenants
512 for vim_tenant in vim_tenants:
513 if vim_tenant["passwd"]:
514 vim_tenant["passwd"] = "******"
515 if vim_tenant['config'] != None:
516 try:
517 config_dict = yaml.load(vim_tenant['config'])
518 vim_tenant['config'] = config_dict
519 if vim_tenant['config'].get('admin_password'):
520 vim_tenant['config']['admin_password'] = "******"
521 if vim_tenant['config'].get('vcenter_password'):
522 vim_tenant['config']['vcenter_password'] = "******"
523 if vim_tenant['config'].get('nsx_password'):
524 vim_tenant['config']['nsx_password'] = "******"
525 except Exception as e:
526 logger.error("Exception '%s' while trying to load config information", str(e))
527
528 if datacenter['config'] != None:
529 try:
530 config_dict = yaml.load(datacenter['config'])
531 datacenter['config'] = config_dict
532 if datacenter['config'].get('admin_password'):
533 datacenter['config']['admin_password'] = "******"
534 if datacenter['config'].get('vcenter_password'):
535 datacenter['config']['vcenter_password'] = "******"
536 if datacenter['config'].get('nsx_password'):
537 datacenter['config']['nsx_password'] = "******"
538 except Exception as e:
539 logger.error("Exception '%s' while trying to load config information", str(e))
540 #change_keys_http2db(content, http2db_datacenter, reverse=True)
541 convert_datetime2str(datacenter)
542 data={'datacenter' : datacenter}
543 return format_out(data)
544 except bottle.HTTPError:
545 raise
546 except (nfvo.NfvoException, db_base_Exception) as e:
547 logger.error("http_get_datacenter_id error {}: {}".format(e.http_code, str(e)))
548 bottle.abort(e.http_code, str(e))
549 except Exception as e:
550 logger.error("Unexpected exception: ", exc_info=True)
551 bottle.abort(HTTP_Internal_Server_Error, type(e).__name__ + ": " + str(e))
552
553
554 @bottle.route(url_base + '/datacenters', method='POST')
555 def http_post_datacenters():
556 '''insert a datacenter into the catalogue. '''
557 #parse input data
558 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
559 http_content,_ = format_in(datacenter_schema, confidential_data=True)
560 r = utils.remove_extra_items(http_content, datacenter_schema)
561 if r:
562 logger.debug("Remove received extra items %s", str(r))
563 try:
564 data = nfvo.new_datacenter(mydb, http_content['datacenter'])
565 return http_get_datacenter_id('any', data)
566 except bottle.HTTPError:
567 raise
568 except (nfvo.NfvoException, db_base_Exception) as e:
569 logger.error("http_post_datacenters error {}: {}".format(e.http_code, str(e)))
570 bottle.abort(e.http_code, str(e))
571 except Exception as e:
572 logger.error("Unexpected exception: ", exc_info=True)
573 bottle.abort(HTTP_Internal_Server_Error, type(e).__name__ + ": " + str(e))
574
575
576 @bottle.route(url_base + '/datacenters/<datacenter_id_name>', method='PUT')
577 def http_edit_datacenter_id(datacenter_id_name):
578 '''edit datacenter details, can use both uuid or name'''
579 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
580 #parse input data
581 http_content,_ = format_in( datacenter_edit_schema )
582 r = utils.remove_extra_items(http_content, datacenter_edit_schema)
583 if r:
584 logger.debug("Remove received extra items %s", str(r))
585
586 try:
587 datacenter_id = nfvo.edit_datacenter(mydb, datacenter_id_name, http_content['datacenter'])
588 return http_get_datacenter_id('any', datacenter_id)
589 except bottle.HTTPError:
590 raise
591 except (nfvo.NfvoException, db_base_Exception) as e:
592 logger.error("http_edit_datacenter_id error {}: {}".format(e.http_code, str(e)))
593 bottle.abort(e.http_code, str(e))
594 except Exception as e:
595 logger.error("Unexpected exception: ", exc_info=True)
596 bottle.abort(HTTP_Internal_Server_Error, type(e).__name__ + ": " + str(e))
597
598 @bottle.route(url_base + '/<tenant_id>/sdn_controllers', method='POST')
599 def http_post_sdn_controller(tenant_id):
600 '''insert a sdn controller into the catalogue. '''
601 #parse input data
602 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
603 http_content,_ = format_in( sdn_controller_schema )
604 try:
605 logger.debug("tenant_id: "+tenant_id)
606 #logger.debug("content: {}".format(http_content['sdn_controller']))
607
608 data = nfvo.sdn_controller_create(mydb, tenant_id, http_content['sdn_controller'])
609 return format_out({"sdn_controller": nfvo.sdn_controller_list(mydb, tenant_id, data)})
610 except bottle.HTTPError:
611 raise
612 except (nfvo.NfvoException, db_base_Exception) as e:
613 logger.error("http_post_sdn_controller error {}: {}".format(e.http_code, str(e)))
614 bottle.abort(e.http_code, str(e))
615 except Exception as e:
616 logger.error("Unexpected exception: ", exc_info=True)
617 bottle.abort(HTTP_Internal_Server_Error, type(e).__name__ + ": " + str(e))
618
619 @bottle.route(url_base + '/<tenant_id>/sdn_controllers/<controller_id>', method='PUT')
620 def http_put_sdn_controller_update(tenant_id, controller_id):
621 '''Update sdn controller'''
622 #parse input data
623 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
624 http_content,_ = format_in( sdn_controller_edit_schema )
625 # r = utils.remove_extra_items(http_content, datacenter_schema)
626 # if r:
627 # logger.debug("Remove received extra items %s", str(r))
628 try:
629 #logger.debug("tenant_id: "+tenant_id)
630 logger.debug("content: {}".format(http_content['sdn_controller']))
631
632 data = nfvo.sdn_controller_update(mydb, tenant_id, controller_id, http_content['sdn_controller'])
633 return format_out({"sdn_controller": nfvo.sdn_controller_list(mydb, tenant_id, controller_id)})
634
635 except bottle.HTTPError:
636 raise
637 except (nfvo.NfvoException, db_base_Exception) as e:
638 logger.error("http_post_sdn_controller error {}: {}".format(e.http_code, str(e)))
639 bottle.abort(e.http_code, str(e))
640 except Exception as e:
641 logger.error("Unexpected exception: ", exc_info=True)
642 bottle.abort(HTTP_Internal_Server_Error, type(e).__name__ + ": " + str(e))
643
644 @bottle.route(url_base + '/<tenant_id>/sdn_controllers', method='GET')
645 def http_get_sdn_controller(tenant_id):
646 '''get sdn controllers list, can use both uuid or name'''
647 try:
648 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
649
650 data = {'sdn_controllers': nfvo.sdn_controller_list(mydb, tenant_id)}
651 return format_out(data)
652 except bottle.HTTPError:
653 raise
654 except (nfvo.NfvoException, db_base_Exception) as e:
655 logger.error("http_get_sdn_controller error {}: {}".format(e.http_code, str(e)))
656 bottle.abort(e.http_code, str(e))
657 except Exception as e:
658 logger.error("Unexpected exception: ", exc_info=True)
659 bottle.abort(HTTP_Internal_Server_Error, type(e).__name__ + ": " + str(e))
660
661 @bottle.route(url_base + '/<tenant_id>/sdn_controllers/<controller_id>', method='GET')
662 def http_get_sdn_controller_id(tenant_id, controller_id):
663 '''get sdn controller details, can use both uuid or name'''
664 try:
665 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
666 data = nfvo.sdn_controller_list(mydb, tenant_id, controller_id)
667 return format_out({"sdn_controllers": data})
668 except bottle.HTTPError:
669 raise
670 except (nfvo.NfvoException, db_base_Exception) as e:
671 logger.error("http_get_sdn_controller_id error {}: {}".format(e.http_code, str(e)))
672 bottle.abort(e.http_code, str(e))
673 except Exception as e:
674 logger.error("Unexpected exception: ", exc_info=True)
675 bottle.abort(HTTP_Internal_Server_Error, type(e).__name__ + ": " + str(e))
676
677 @bottle.route(url_base + '/<tenant_id>/sdn_controllers/<controller_id>', method='DELETE')
678 def http_delete_sdn_controller_id(tenant_id, controller_id):
679 '''delete sdn controller, can use both uuid or name'''
680 try:
681 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
682 data = nfvo.sdn_controller_delete(mydb, tenant_id, controller_id)
683 return format_out(data)
684 except bottle.HTTPError:
685 raise
686 except (nfvo.NfvoException, db_base_Exception) as e:
687 logger.error("http_delete_sdn_controller_id error {}: {}".format(e.http_code, str(e)))
688 bottle.abort(e.http_code, str(e))
689 except Exception as e:
690 logger.error("Unexpected exception: ", exc_info=True)
691 bottle.abort(HTTP_Internal_Server_Error, type(e).__name__ + ": " + str(e))
692
693 @bottle.route(url_base + '/<tenant_id>/datacenters/<datacenter_id>/sdn_mapping', method='POST')
694 def http_post_datacenter_sdn_port_mapping(tenant_id, datacenter_id):
695 '''Set the sdn port mapping for a datacenter. '''
696 #parse input data
697 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
698 http_content, _ = format_in(sdn_port_mapping_schema)
699 # r = utils.remove_extra_items(http_content, datacenter_schema)
700 # if r:
701 # logger.debug("Remove received extra items %s", str(r))
702 try:
703 data = nfvo.datacenter_sdn_port_mapping_set(mydb, tenant_id, datacenter_id, http_content['sdn_port_mapping'])
704 return format_out({"sdn_port_mapping": data})
705 except bottle.HTTPError:
706 raise
707 except (nfvo.NfvoException, db_base_Exception) as e:
708 logger.error("http_post_datacenter_sdn_port_mapping error {}: {}".format(e.http_code, str(e)))
709 bottle.abort(e.http_code, str(e))
710 except Exception as e:
711 logger.error("Unexpected exception: ", exc_info=True)
712 bottle.abort(HTTP_Internal_Server_Error, type(e).__name__ + ": " + str(e))
713
714 @bottle.route(url_base + '/<tenant_id>/datacenters/<datacenter_id>/sdn_mapping', method='GET')
715 def http_get_datacenter_sdn_port_mapping(tenant_id, datacenter_id):
716 '''get datacenter sdn mapping details, can use both uuid or name'''
717 try:
718 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
719
720 data = nfvo.datacenter_sdn_port_mapping_list(mydb, tenant_id, datacenter_id)
721 return format_out({"sdn_port_mapping": data})
722 except bottle.HTTPError:
723 raise
724 except (nfvo.NfvoException, db_base_Exception) as e:
725 logger.error("http_get_datacenter_sdn_port_mapping error {}: {}".format(e.http_code, str(e)))
726 bottle.abort(e.http_code, str(e))
727 except Exception as e:
728 logger.error("Unexpected exception: ", exc_info=True)
729 bottle.abort(HTTP_Internal_Server_Error, type(e).__name__ + ": " + str(e))
730
731 @bottle.route(url_base + '/<tenant_id>/datacenters/<datacenter_id>/sdn_mapping', method='DELETE')
732 def http_delete_datacenter_sdn_port_mapping(tenant_id, datacenter_id):
733 '''clean datacenter sdn mapping, can use both uuid or name'''
734 try:
735 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
736 data = nfvo.datacenter_sdn_port_mapping_delete(mydb, tenant_id, datacenter_id)
737 return format_out({"result": data})
738 except bottle.HTTPError:
739 raise
740 except (nfvo.NfvoException, db_base_Exception) as e:
741 logger.error("http_delete_datacenter_sdn_port_mapping 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 @bottle.route(url_base + '/<tenant_id>/datacenters/<datacenter_id>/networks', method='GET') #deprecated
748 @bottle.route(url_base + '/<tenant_id>/datacenters/<datacenter_id>/netmaps', method='GET')
749 @bottle.route(url_base + '/<tenant_id>/datacenters/<datacenter_id>/netmaps/<netmap_id>', method='GET')
750 def http_getnetmap_datacenter_id(tenant_id, datacenter_id, netmap_id=None):
751 '''get datacenter networks, can use both uuid or name'''
752 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
753 #obtain data
754 try:
755 datacenter_dict = mydb.get_table_by_uuid_name('datacenters', datacenter_id, "datacenter")
756 where_= {"datacenter_id":datacenter_dict['uuid']}
757 if netmap_id:
758 if utils.check_valid_uuid(netmap_id):
759 where_["uuid"] = netmap_id
760 else:
761 where_["name"] = netmap_id
762 netmaps =mydb.get_rows(FROM='datacenter_nets',
763 SELECT=('name','vim_net_id as vim_id', 'uuid', 'type','multipoint','shared','description', 'created_at'),
764 WHERE=where_ )
765 convert_datetime2str(netmaps)
766 utils.convert_str2boolean(netmaps, ('shared', 'multipoint') )
767 if netmap_id and len(netmaps)==1:
768 data={'netmap' : netmaps[0]}
769 elif netmap_id and len(netmaps)==0:
770 bottle.abort(HTTP_Not_Found, "No netmap found with " + " and ".join(map(lambda x: str(x[0])+": "+str(x[1]), where_.iteritems())) )
771 return
772 else:
773 data={'netmaps' : netmaps}
774 return format_out(data)
775 except bottle.HTTPError:
776 raise
777 except (nfvo.NfvoException, db_base_Exception) as e:
778 logger.error("http_getnetwork_datacenter_id error {}: {}".format(e.http_code, str(e)))
779 bottle.abort(e.http_code, str(e))
780 except Exception as e:
781 logger.error("Unexpected exception: ", exc_info=True)
782 bottle.abort(HTTP_Internal_Server_Error, type(e).__name__ + ": " + str(e))
783
784
785 @bottle.route(url_base + '/<tenant_id>/datacenters/<datacenter_id>/netmaps', method='DELETE')
786 @bottle.route(url_base + '/<tenant_id>/datacenters/<datacenter_id>/netmaps/<netmap_id>', method='DELETE')
787 def http_delnetmap_datacenter_id(tenant_id, datacenter_id, netmap_id=None):
788 '''get datacenter networks, can use both uuid or name'''
789 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
790 #obtain data
791 try:
792 datacenter_dict = mydb.get_table_by_uuid_name('datacenters', datacenter_id, "datacenter")
793 where_= {"datacenter_id":datacenter_dict['uuid']}
794 if netmap_id:
795 if utils.check_valid_uuid(netmap_id):
796 where_["uuid"] = netmap_id
797 else:
798 where_["name"] = netmap_id
799 #change_keys_http2db(content, http2db_tenant, reverse=True)
800 deleted = mydb.delete_row(FROM='datacenter_nets', WHERE= where_)
801 if deleted == 0 and netmap_id:
802 bottle.abort(HTTP_Not_Found, "No netmap found with " + " and ".join(map(lambda x: str(x[0])+": "+str(x[1]), where_.iteritems())) )
803 if netmap_id:
804 return format_out({"result": "netmap %s deleted" % netmap_id})
805 else:
806 return format_out({"result": "%d netmap deleted" % deleted})
807 except bottle.HTTPError:
808 raise
809 except (nfvo.NfvoException, db_base_Exception) as e:
810 logger.error("http_delnetmap_datacenter_id error {}: {}".format(e.http_code, str(e)))
811 bottle.abort(e.http_code, str(e))
812 except Exception as e:
813 logger.error("Unexpected exception: ", exc_info=True)
814 bottle.abort(HTTP_Internal_Server_Error, type(e).__name__ + ": " + str(e))
815
816
817 @bottle.route(url_base + '/<tenant_id>/datacenters/<datacenter_id>/netmaps/upload', method='POST')
818 def http_uploadnetmap_datacenter_id(tenant_id, datacenter_id):
819 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
820 try:
821 netmaps = nfvo.datacenter_new_netmap(mydb, tenant_id, datacenter_id, None)
822 convert_datetime2str(netmaps)
823 utils.convert_str2boolean(netmaps, ('shared', 'multipoint') )
824 data={'netmaps' : netmaps}
825 return format_out(data)
826 except bottle.HTTPError:
827 raise
828 except (nfvo.NfvoException, db_base_Exception) as e:
829 logger.error("http_uploadnetmap_datacenter_id error {}: {}".format(e.http_code, str(e)))
830 bottle.abort(e.http_code, str(e))
831 except Exception as e:
832 logger.error("Unexpected exception: ", exc_info=True)
833 bottle.abort(HTTP_Internal_Server_Error, type(e).__name__ + ": " + str(e))
834
835
836 @bottle.route(url_base + '/<tenant_id>/datacenters/<datacenter_id>/netmaps', method='POST')
837 def http_postnetmap_datacenter_id(tenant_id, datacenter_id):
838 '''creates a new netmap'''
839 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
840 #parse input data
841 http_content,_ = format_in( netmap_new_schema )
842 r = utils.remove_extra_items(http_content, netmap_new_schema)
843 if r:
844 logger.debug("Remove received extra items %s", str(r))
845 try:
846 #obtain data, check that only one exist
847 netmaps = nfvo.datacenter_new_netmap(mydb, tenant_id, datacenter_id, http_content)
848 convert_datetime2str(netmaps)
849 utils.convert_str2boolean(netmaps, ('shared', 'multipoint') )
850 data={'netmaps' : netmaps}
851 return format_out(data)
852 except bottle.HTTPError:
853 raise
854 except (nfvo.NfvoException, db_base_Exception) as e:
855 logger.error("http_postnetmap_datacenter_id error {}: {}".format(e.http_code, str(e)))
856 bottle.abort(e.http_code, str(e))
857 except Exception as e:
858 logger.error("Unexpected exception: ", exc_info=True)
859 bottle.abort(HTTP_Internal_Server_Error, type(e).__name__ + ": " + str(e))
860
861
862 @bottle.route(url_base + '/<tenant_id>/datacenters/<datacenter_id>/netmaps/<netmap_id>', method='PUT')
863 def http_putnettmap_datacenter_id(tenant_id, datacenter_id, netmap_id):
864 '''edit a netmap'''
865 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
866 #parse input data
867 http_content,_ = format_in( netmap_edit_schema )
868 r = utils.remove_extra_items(http_content, netmap_edit_schema)
869 if r:
870 logger.debug("Remove received extra items %s", str(r))
871
872 #obtain data, check that only one exist
873 try:
874 nfvo.datacenter_edit_netmap(mydb, tenant_id, datacenter_id, netmap_id, http_content)
875 return http_getnetmap_datacenter_id(tenant_id, datacenter_id, netmap_id)
876 except bottle.HTTPError:
877 raise
878 except (nfvo.NfvoException, db_base_Exception) as e:
879 logger.error("http_putnettmap_datacenter_id error {}: {}".format(e.http_code, str(e)))
880 bottle.abort(e.http_code, str(e))
881 except Exception as e:
882 logger.error("Unexpected exception: ", exc_info=True)
883 bottle.abort(HTTP_Internal_Server_Error, type(e).__name__ + ": " + str(e))
884
885
886 @bottle.route(url_base + '/<tenant_id>/datacenters/<datacenter_id>/action', method='POST')
887 def http_action_datacenter_id(tenant_id, datacenter_id):
888 '''perform an action over datacenter, can use both uuid or name'''
889 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
890 #parse input data
891 http_content,_ = format_in( datacenter_action_schema )
892 r = utils.remove_extra_items(http_content, datacenter_action_schema)
893 if r:
894 logger.debug("Remove received extra items %s", str(r))
895 try:
896 #obtain data, check that only one exist
897 result = nfvo.datacenter_action(mydb, tenant_id, datacenter_id, http_content)
898 if 'net-update' in http_content:
899 return http_getnetmap_datacenter_id(datacenter_id)
900 else:
901 return format_out(result)
902 except bottle.HTTPError:
903 raise
904 except (nfvo.NfvoException, db_base_Exception) as e:
905 logger.error("http_action_datacenter_id error {}: {}".format(e.http_code, str(e)))
906 bottle.abort(e.http_code, str(e))
907 except Exception as e:
908 logger.error("Unexpected exception: ", exc_info=True)
909 bottle.abort(HTTP_Internal_Server_Error, type(e).__name__ + ": " + str(e))
910
911
912 @bottle.route(url_base + '/datacenters/<datacenter_id>', method='DELETE')
913 def http_delete_datacenter_id( datacenter_id):
914 '''delete a tenant from database, can use both uuid or name'''
915
916 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
917 try:
918 data = nfvo.delete_datacenter(mydb, datacenter_id)
919 return format_out({"result":"datacenter '" + data + "' deleted"})
920 except bottle.HTTPError:
921 raise
922 except (nfvo.NfvoException, db_base_Exception) as e:
923 logger.error("http_delete_datacenter_id error {}: {}".format(e.http_code, str(e)))
924 bottle.abort(e.http_code, str(e))
925 except Exception as e:
926 logger.error("Unexpected exception: ", exc_info=True)
927 bottle.abort(HTTP_Internal_Server_Error, type(e).__name__ + ": " + str(e))
928
929
930 @bottle.route(url_base + '/<tenant_id>/datacenters/<datacenter_id>', method='POST')
931 def http_associate_datacenters(tenant_id, datacenter_id):
932 '''associate an existing datacenter to a this tenant. '''
933 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
934 #parse input data
935 http_content,_ = format_in(datacenter_associate_schema, confidential_data=True)
936 r = utils.remove_extra_items(http_content, datacenter_associate_schema)
937 if r:
938 logger.debug("Remove received extra items %s", str(r))
939 try:
940 id_ = nfvo.associate_datacenter_to_tenant(mydb, tenant_id, datacenter_id,
941 http_content['datacenter'].get('vim_tenant'),
942 http_content['datacenter'].get('vim_tenant_name'),
943 http_content['datacenter'].get('vim_username'),
944 http_content['datacenter'].get('vim_password'),
945 http_content['datacenter'].get('config')
946 )
947 return http_get_datacenter_id(tenant_id, id_)
948 except bottle.HTTPError:
949 raise
950 except (nfvo.NfvoException, db_base_Exception) as e:
951 logger.error("http_associate_datacenters error {}: {}".format(e.http_code, str(e)))
952 bottle.abort(e.http_code, str(e))
953 except Exception as e:
954 logger.error("Unexpected exception: ", exc_info=True)
955 bottle.abort(HTTP_Internal_Server_Error, type(e).__name__ + ": " + str(e))
956
957 @bottle.route(url_base + '/<tenant_id>/datacenters/<datacenter_id>', method='PUT')
958 def http_associate_datacenters_edit(tenant_id, datacenter_id):
959 '''associate an existing datacenter to a this tenant. '''
960 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
961 #parse input data
962 http_content,_ = format_in( datacenter_associate_schema )
963 r = utils.remove_extra_items(http_content, datacenter_associate_schema)
964 if r:
965 logger.debug("Remove received extra items %s", str(r))
966 try:
967 id_ = nfvo.edit_datacenter_to_tenant(mydb, tenant_id, datacenter_id,
968 http_content['datacenter'].get('vim_tenant'),
969 http_content['datacenter'].get('vim_tenant_name'),
970 http_content['datacenter'].get('vim_username'),
971 http_content['datacenter'].get('vim_password'),
972 http_content['datacenter'].get('config')
973 )
974 return http_get_datacenter_id(tenant_id, id_)
975 except bottle.HTTPError:
976 raise
977 except (nfvo.NfvoException, db_base_Exception) as e:
978 logger.error("http_associate_datacenters_edit error {}: {}".format(e.http_code, str(e)))
979 bottle.abort(e.http_code, str(e))
980 except Exception as e:
981 logger.error("Unexpected exception: ", exc_info=True)
982 bottle.abort(HTTP_Internal_Server_Error, type(e).__name__ + ": " + str(e))
983
984 @bottle.route(url_base + '/<tenant_id>/datacenters/<datacenter_id>', method='DELETE')
985 def http_deassociate_datacenters(tenant_id, datacenter_id):
986 '''deassociate an existing datacenter to a this tenant. '''
987 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
988 try:
989 data = nfvo.deassociate_datacenter_to_tenant(mydb, tenant_id, datacenter_id)
990 return format_out({"result": data})
991 except bottle.HTTPError:
992 raise
993 except (nfvo.NfvoException, db_base_Exception) as e:
994 logger.error("http_deassociate_datacenters error {}: {}".format(e.http_code, str(e)))
995 bottle.abort(e.http_code, str(e))
996 except Exception as e:
997 logger.error("Unexpected exception: ", exc_info=True)
998 bottle.abort(HTTP_Internal_Server_Error, type(e).__name__ + ": " + str(e))
999
1000 @bottle.route(url_base + '/<tenant_id>/vim/<datacenter_id>/network/<network_id>/attach', method='POST')
1001 def http_post_vim_net_sdn_attach(tenant_id, datacenter_id, network_id):
1002 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
1003 http_content, _ = format_in(sdn_external_port_schema)
1004 try:
1005 data = nfvo.vim_net_sdn_attach(mydb, tenant_id, datacenter_id, network_id, http_content)
1006 return format_out(data)
1007 except bottle.HTTPError:
1008 raise
1009 except (nfvo.NfvoException, db_base_Exception) as e:
1010 logger.error("http_post_vim_net_sdn_attach error {}: {}".format(e.http_code, str(e)))
1011 bottle.abort(e.http_code, str(e))
1012 except Exception as e:
1013 logger.error("Unexpected exception: ", exc_info=True)
1014 bottle.abort(HTTP_Internal_Server_Error, type(e).__name__ + ": " + str(e))
1015
1016 @bottle.route(url_base + '/<tenant_id>/vim/<datacenter_id>/network/<network_id>/detach', method='DELETE')
1017 @bottle.route(url_base + '/<tenant_id>/vim/<datacenter_id>/network/<network_id>/detach/<port_id>', method='DELETE')
1018 def http_delete_vim_net_sdn_detach(tenant_id, datacenter_id, network_id, port_id=None):
1019 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
1020 try:
1021 data = nfvo.vim_net_sdn_detach(mydb, tenant_id, datacenter_id, network_id, port_id)
1022 return format_out(data)
1023 except bottle.HTTPError:
1024 raise
1025 except (nfvo.NfvoException, db_base_Exception) as e:
1026 logger.error("http_delete_vim_net_sdn_detach error {}: {}".format(e.http_code, str(e)))
1027 bottle.abort(e.http_code, str(e))
1028 except Exception as e:
1029 logger.error("Unexpected exception: ", exc_info=True)
1030 bottle.abort(HTTP_Internal_Server_Error, type(e).__name__ + ": " + str(e))
1031
1032 @bottle.route(url_base + '/<tenant_id>/vim/<datacenter_id>/<item>', method='GET')
1033 @bottle.route(url_base + '/<tenant_id>/vim/<datacenter_id>/<item>/<name>', method='GET')
1034 def http_get_vim_items(tenant_id, datacenter_id, item, name=None):
1035 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
1036 try:
1037 data = nfvo.vim_action_get(mydb, tenant_id, datacenter_id, item, name)
1038 return format_out(data)
1039 except bottle.HTTPError:
1040 raise
1041 except (nfvo.NfvoException, db_base_Exception) as e:
1042 logger.error("http_get_vim_items error {}: {}".format(e.http_code, str(e)))
1043 bottle.abort(e.http_code, str(e))
1044 except Exception as e:
1045 logger.error("Unexpected exception: ", exc_info=True)
1046 bottle.abort(HTTP_Internal_Server_Error, type(e).__name__ + ": " + str(e))
1047
1048
1049 @bottle.route(url_base + '/<tenant_id>/vim/<datacenter_id>/<item>/<name>', method='DELETE')
1050 def http_del_vim_items(tenant_id, datacenter_id, item, name):
1051 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
1052 try:
1053 data = nfvo.vim_action_delete(mydb, tenant_id, datacenter_id, item, name)
1054 return format_out({"result":data})
1055 except bottle.HTTPError:
1056 raise
1057 except (nfvo.NfvoException, db_base_Exception) as e:
1058 logger.error("http_del_vim_items error {}: {}".format(e.http_code, str(e)))
1059 bottle.abort(e.http_code, str(e))
1060 except Exception as e:
1061 logger.error("Unexpected exception: ", exc_info=True)
1062 bottle.abort(HTTP_Internal_Server_Error, type(e).__name__ + ": " + str(e))
1063
1064
1065 @bottle.route(url_base + '/<tenant_id>/vim/<datacenter_id>/<item>', method='POST')
1066 def http_post_vim_items(tenant_id, datacenter_id, item):
1067 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
1068 http_content,_ = format_in( object_schema )
1069 try:
1070 data = nfvo.vim_action_create(mydb, tenant_id, datacenter_id, item, http_content)
1071 return format_out(data)
1072 except bottle.HTTPError:
1073 raise
1074 except (nfvo.NfvoException, db_base_Exception) as e:
1075 logger.error("http_post_vim_items error {}: {}".format(e.http_code, str(e)))
1076 bottle.abort(e.http_code, str(e))
1077 except Exception as e:
1078 logger.error("Unexpected exception: ", exc_info=True)
1079 bottle.abort(HTTP_Internal_Server_Error, type(e).__name__ + ": " + str(e))
1080
1081
1082 @bottle.route(url_base + '/<tenant_id>/vnfs', method='GET')
1083 def http_get_vnfs(tenant_id):
1084 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
1085 try:
1086 if tenant_id != 'any':
1087 #check valid tenant_id
1088 nfvo.check_tenant(mydb, tenant_id)
1089 select_,where_,limit_ = filter_query_string(bottle.request.query, None,
1090 ('uuid', 'name', 'osm_id', 'description', 'public', "tenant_id", "created_at") )
1091 if tenant_id != "any":
1092 where_["OR"]={"tenant_id": tenant_id, "public": True}
1093 vnfs = mydb.get_rows(FROM='vnfs', SELECT=select_, WHERE=where_, LIMIT=limit_)
1094 # change_keys_http2db(content, http2db_vnf, reverse=True)
1095 utils.convert_str2boolean(vnfs, ('public',))
1096 convert_datetime2str(vnfs)
1097 data={'vnfs': vnfs}
1098 return format_out(data)
1099 except bottle.HTTPError:
1100 raise
1101 except (nfvo.NfvoException, db_base_Exception) as e:
1102 logger.error("http_get_vnfs error {}: {}".format(e.http_code, str(e)))
1103 bottle.abort(e.http_code, str(e))
1104 except Exception as e:
1105 logger.error("Unexpected exception: ", exc_info=True)
1106 bottle.abort(HTTP_Internal_Server_Error, type(e).__name__ + ": " + str(e))
1107
1108
1109 @bottle.route(url_base + '/<tenant_id>/vnfs/<vnf_id>', method='GET')
1110 def http_get_vnf_id(tenant_id,vnf_id):
1111 '''get vnf details, can use both uuid or name'''
1112 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
1113 try:
1114 vnf = nfvo.get_vnf_id(mydb,tenant_id,vnf_id)
1115 utils.convert_str2boolean(vnf, ('public',))
1116 convert_datetime2str(vnf)
1117 return format_out(vnf)
1118 except bottle.HTTPError:
1119 raise
1120 except (nfvo.NfvoException, db_base_Exception) as e:
1121 logger.error("http_get_vnf_id error {}: {}".format(e.http_code, str(e)))
1122 bottle.abort(e.http_code, str(e))
1123 except Exception as e:
1124 logger.error("Unexpected exception: ", exc_info=True)
1125 bottle.abort(HTTP_Internal_Server_Error, type(e).__name__ + ": " + str(e))
1126
1127
1128 @bottle.route(url_base + '/<tenant_id>/vnfs', method='POST')
1129 def http_post_vnfs(tenant_id):
1130 """ Insert a vnf into the catalogue. Creates the flavor and images, and fill the tables at database
1131 :param tenant_id: tenant that this vnf belongs to
1132 :return:
1133 """
1134 # print "Parsing the YAML file of the VNF"
1135 # parse input data
1136 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
1137 http_content, used_schema = format_in( vnfd_schema_v01, ("schema_version",), {"0.2": vnfd_schema_v02})
1138 r = utils.remove_extra_items(http_content, used_schema)
1139 if r:
1140 logger.debug("Remove received extra items %s", str(r))
1141 try:
1142 if used_schema == vnfd_schema_v01:
1143 vnf_id = nfvo.new_vnf(mydb,tenant_id,http_content)
1144 elif used_schema == vnfd_schema_v02:
1145 vnf_id = nfvo.new_vnf_v02(mydb,tenant_id,http_content)
1146 else:
1147 logger.warning('Unexpected schema_version: %s', http_content.get("schema_version"))
1148 bottle.abort(HTTP_Bad_Request, "Invalid schema version")
1149 return http_get_vnf_id(tenant_id, vnf_id)
1150 except bottle.HTTPError:
1151 raise
1152 except (nfvo.NfvoException, db_base_Exception) as e:
1153 logger.error("http_post_vnfs error {}: {}".format(e.http_code, str(e)))
1154 bottle.abort(e.http_code, str(e))
1155 except Exception as e:
1156 logger.error("Unexpected exception: ", exc_info=True)
1157 bottle.abort(HTTP_Internal_Server_Error, type(e).__name__ + ": " + str(e))
1158
1159
1160 @bottle.route(url_base + '/v3/<tenant_id>/vnfd', method='POST')
1161 def http_post_vnfs_v3(tenant_id):
1162 """
1163 Insert one or several VNFs in the catalog, following OSM IM
1164 :param tenant_id: tenant owner of the VNF
1165 :return: The detailed list of inserted VNFs, following the old format
1166 """
1167 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
1168 http_content, _ = format_in(None)
1169 try:
1170 vnfd_uuid_list = nfvo.new_vnfd_v3(mydb, tenant_id, http_content)
1171 vnfd_list = []
1172 for vnfd_uuid in vnfd_uuid_list:
1173 vnf = nfvo.get_vnf_id(mydb, tenant_id, vnfd_uuid)
1174 utils.convert_str2boolean(vnf, ('public',))
1175 convert_datetime2str(vnf)
1176 vnfd_list.append(vnf["vnf"])
1177 return format_out({"vnfd": vnfd_list})
1178 except bottle.HTTPError:
1179 raise
1180 except (nfvo.NfvoException, db_base_Exception) as e:
1181 logger.error("http_post_vnfs error {}: {}".format(e.http_code, str(e)))
1182 bottle.abort(e.http_code, str(e))
1183 except Exception as e:
1184 logger.error("Unexpected exception: ", exc_info=True)
1185 bottle.abort(HTTP_Internal_Server_Error, type(e).__name__ + ": " + str(e))
1186
1187 @bottle.route(url_base + '/<tenant_id>/vnfs/<vnf_id>', method='DELETE')
1188 def http_delete_vnf_id(tenant_id, vnf_id):
1189 '''delete a vnf from database, and images and flavors in VIM when appropriate, can use both uuid or name'''
1190 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
1191 #check valid tenant_id and deletes the vnf, including images,
1192 try:
1193 data = nfvo.delete_vnf(mydb,tenant_id,vnf_id)
1194 #print json.dumps(data, indent=4)
1195 return format_out({"result":"VNF " + data + " deleted"})
1196 except bottle.HTTPError:
1197 raise
1198 except (nfvo.NfvoException, db_base_Exception) as e:
1199 logger.error("http_delete_vnf_id error {}: {}".format(e.http_code, str(e)))
1200 bottle.abort(e.http_code, str(e))
1201 except Exception as e:
1202 logger.error("Unexpected exception: ", exc_info=True)
1203 bottle.abort(HTTP_Internal_Server_Error, type(e).__name__ + ": " + str(e))
1204
1205
1206 #@bottle.route(url_base + '/<tenant_id>/hosts/topology', method='GET')
1207 #@bottle.route(url_base + '/<tenant_id>/physicalview/Madrid-Alcantara', method='GET')
1208 @bottle.route(url_base + '/<tenant_id>/physicalview/<datacenter>', method='GET')
1209 def http_get_hosts(tenant_id, datacenter):
1210 '''get the tidvim host hopology from the vim.'''
1211 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
1212 #print "http_get_hosts received by tenant " + tenant_id + ' datacenter ' + datacenter
1213 try:
1214 if datacenter == 'treeview':
1215 data = nfvo.get_hosts(mydb, tenant_id)
1216 else:
1217 #openmano-gui is using a hardcoded value for the datacenter
1218 result, data = nfvo.get_hosts_info(mydb, tenant_id) #, datacenter)
1219
1220 if result < 0:
1221 #print "http_get_hosts error %d %s" % (-result, data)
1222 bottle.abort(-result, data)
1223 else:
1224 convert_datetime2str(data)
1225 #print json.dumps(data, indent=4)
1226 return format_out(data)
1227 except bottle.HTTPError:
1228 raise
1229 except (nfvo.NfvoException, db_base_Exception) as e:
1230 logger.error("http_get_hosts error {}: {}".format(e.http_code, str(e)))
1231 bottle.abort(e.http_code, str(e))
1232 except Exception as e:
1233 logger.error("Unexpected exception: ", exc_info=True)
1234 bottle.abort(HTTP_Internal_Server_Error, type(e).__name__ + ": " + str(e))
1235
1236
1237 @bottle.route(url_base + '/<path:path>', method='OPTIONS')
1238 def http_options_deploy(path):
1239 '''For some reason GUI web ask for OPTIONS that must be responded'''
1240 #TODO: check correct path, and correct headers request
1241 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
1242 bottle.response.set_header('Access-Control-Allow-Methods','POST, GET, PUT, DELETE, OPTIONS')
1243 bottle.response.set_header('Accept','application/yaml,application/json')
1244 bottle.response.set_header('Content-Type','application/yaml,application/json')
1245 bottle.response.set_header('Access-Control-Allow-Headers','content-type')
1246 bottle.response.set_header('Access-Control-Allow-Origin','*')
1247 return
1248
1249 @bottle.route(url_base + '/<tenant_id>/topology/deploy', method='POST')
1250 def http_post_deploy(tenant_id):
1251 '''post topology deploy.'''
1252 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
1253
1254 http_content, used_schema = format_in( nsd_schema_v01, ("schema_version",), {2: nsd_schema_v02})
1255 #r = utils.remove_extra_items(http_content, used_schema)
1256 #if r is not None: print "http_post_deploy: Warning: remove extra items ", r
1257 #print "http_post_deploy input: ", http_content
1258
1259 try:
1260 scenario_id = nfvo.new_scenario(mydb, tenant_id, http_content)
1261 instance = nfvo.start_scenario(mydb, tenant_id, scenario_id, http_content['name'], http_content['name'])
1262 #print json.dumps(data, indent=4)
1263 return format_out(instance)
1264 except bottle.HTTPError:
1265 raise
1266 except (nfvo.NfvoException, db_base_Exception) as e:
1267 logger.error("http_post_deploy error {}: {}".format(e.http_code, str(e)))
1268 bottle.abort(e.http_code, str(e))
1269 except Exception as e:
1270 logger.error("Unexpected exception: ", exc_info=True)
1271 bottle.abort(HTTP_Internal_Server_Error, type(e).__name__ + ": " + str(e))
1272
1273
1274 @bottle.route(url_base + '/<tenant_id>/topology/verify', method='POST')
1275 def http_post_verify(tenant_id):
1276 #TODO:
1277 # '''post topology verify'''
1278 # print "http_post_verify by tenant " + tenant_id + ' datacenter ' + datacenter
1279 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
1280 return
1281
1282 #
1283 # SCENARIOS
1284 #
1285
1286 @bottle.route(url_base + '/<tenant_id>/scenarios', method='POST')
1287 def http_post_scenarios(tenant_id):
1288 '''add a scenario into the catalogue. Creates the scenario and its internal structure in the OPENMANO DB'''
1289 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
1290 http_content, used_schema = format_in( nsd_schema_v01, ("schema_version",), {2: nsd_schema_v02, "0.3": nsd_schema_v03})
1291 #r = utils.remove_extra_items(http_content, used_schema)
1292 #if r is not None: print "http_post_scenarios: Warning: remove extra items ", r
1293 #print "http_post_scenarios input: ", http_content
1294 try:
1295 if used_schema == nsd_schema_v01:
1296 scenario_id = nfvo.new_scenario(mydb, tenant_id, http_content)
1297 elif used_schema == nsd_schema_v02:
1298 scenario_id = nfvo.new_scenario_v02(mydb, tenant_id, http_content, "0.2")
1299 elif used_schema == nsd_schema_v03:
1300 scenario_id = nfvo.new_scenario_v02(mydb, tenant_id, http_content, "0.3")
1301 else:
1302 logger.warning('Unexpected schema_version: %s', http_content.get("schema_version"))
1303 bottle.abort(HTTP_Bad_Request, "Invalid schema version")
1304 #print json.dumps(data, indent=4)
1305 #return format_out(data)
1306 return http_get_scenario_id(tenant_id, scenario_id)
1307 except bottle.HTTPError:
1308 raise
1309 except (nfvo.NfvoException, db_base_Exception) as e:
1310 logger.error("http_post_scenarios error {}: {}".format(e.http_code, str(e)))
1311 bottle.abort(e.http_code, str(e))
1312 except Exception as e:
1313 logger.error("Unexpected exception: ", exc_info=True)
1314 bottle.abort(HTTP_Internal_Server_Error, type(e).__name__ + ": " + str(e))
1315
1316 @bottle.route(url_base + '/v3/<tenant_id>/nsd', method='POST')
1317 def http_post_nsds_v3(tenant_id):
1318 """
1319 Insert one or several NSDs in the catalog, following OSM IM
1320 :param tenant_id: tenant owner of the NSD
1321 :return: The detailed list of inserted NSDs, following the old format
1322 """
1323 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
1324 http_content, _ = format_in(None)
1325 try:
1326 nsd_uuid_list = nfvo.new_nsd_v3(mydb, tenant_id, http_content)
1327 nsd_list = []
1328 for nsd_uuid in nsd_uuid_list:
1329 scenario = mydb.get_scenario(nsd_uuid, tenant_id)
1330 convert_datetime2str(scenario)
1331 nsd_list.append(scenario)
1332 data = {'nsd': nsd_list}
1333 return format_out(data)
1334 except bottle.HTTPError:
1335 raise
1336 except (nfvo.NfvoException, db_base_Exception) as e:
1337 logger.error("http_post_nsds_v3 error {}: {}".format(e.http_code, str(e)))
1338 bottle.abort(e.http_code, str(e))
1339 except Exception as e:
1340 logger.error("Unexpected exception: ", exc_info=True)
1341 bottle.abort(HTTP_Internal_Server_Error, type(e).__name__ + ": " + str(e))
1342
1343
1344 @bottle.route(url_base + '/<tenant_id>/scenarios/<scenario_id>/action', method='POST')
1345 def http_post_scenario_action(tenant_id, scenario_id):
1346 '''take an action over a scenario'''
1347 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
1348 # parse input data
1349 http_content, _ = format_in(scenario_action_schema)
1350 r = utils.remove_extra_items(http_content, scenario_action_schema)
1351 if r:
1352 logger.debug("Remove received extra items %s", str(r))
1353 try:
1354 # check valid tenant_id
1355 nfvo.check_tenant(mydb, tenant_id)
1356 if "start" in http_content:
1357 data = nfvo.start_scenario(mydb, tenant_id, scenario_id, http_content['start']['instance_name'], \
1358 http_content['start'].get('description',http_content['start']['instance_name']),
1359 http_content['start'].get('datacenter') )
1360 return format_out(data)
1361 elif "deploy" in http_content: #Equivalent to start
1362 data = nfvo.start_scenario(mydb, tenant_id, scenario_id, http_content['deploy']['instance_name'],
1363 http_content['deploy'].get('description',http_content['deploy']['instance_name']),
1364 http_content['deploy'].get('datacenter') )
1365 return format_out(data)
1366 elif "reserve" in http_content: #Reserve resources
1367 data = nfvo.start_scenario(mydb, tenant_id, scenario_id, http_content['reserve']['instance_name'],
1368 http_content['reserve'].get('description',http_content['reserve']['instance_name']),
1369 http_content['reserve'].get('datacenter'), startvms=False )
1370 return format_out(data)
1371 elif "verify" in http_content: #Equivalent to start and then delete
1372 data = nfvo.start_scenario(mydb, tenant_id, scenario_id, http_content['verify']['instance_name'],
1373 http_content['verify'].get('description',http_content['verify']['instance_name']),
1374 http_content['verify'].get('datacenter'), startvms=False )
1375 instance_id = data['uuid']
1376 nfvo.delete_instance(mydb, tenant_id,instance_id)
1377 return format_out({"result":"Verify OK"})
1378 except bottle.HTTPError:
1379 raise
1380 except (nfvo.NfvoException, db_base_Exception) as e:
1381 logger.error("http_post_scenario_action error {}: {}".format(e.http_code, str(e)))
1382 bottle.abort(e.http_code, str(e))
1383 except Exception as e:
1384 logger.error("Unexpected exception: ", exc_info=True)
1385 bottle.abort(HTTP_Internal_Server_Error, type(e).__name__ + ": " + str(e))
1386
1387
1388 @bottle.route(url_base + '/<tenant_id>/scenarios', method='GET')
1389 def http_get_scenarios(tenant_id):
1390 '''get scenarios list'''
1391 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
1392 try:
1393 #check valid tenant_id
1394 if tenant_id != "any":
1395 nfvo.check_tenant(mydb, tenant_id)
1396 #obtain data
1397 s,w,l=filter_query_string(bottle.request.query, None,
1398 ('uuid', 'name', 'osm_id', 'description', 'tenant_id', 'created_at', 'public'))
1399 if tenant_id != "any":
1400 w["OR"] = {"tenant_id": tenant_id, "public": True}
1401 scenarios = mydb.get_rows(SELECT=s, WHERE=w, LIMIT=l, FROM='scenarios')
1402 convert_datetime2str(scenarios)
1403 utils.convert_str2boolean(scenarios, ('public',) )
1404 data={'scenarios':scenarios}
1405 #print json.dumps(scenarios, indent=4)
1406 return format_out(data)
1407 except bottle.HTTPError:
1408 raise
1409 except (nfvo.NfvoException, db_base_Exception) as e:
1410 logger.error("http_get_scenarios error {}: {}".format(e.http_code, str(e)))
1411 bottle.abort(e.http_code, str(e))
1412 except Exception as e:
1413 logger.error("Unexpected exception: ", exc_info=True)
1414 bottle.abort(HTTP_Internal_Server_Error, type(e).__name__ + ": " + str(e))
1415
1416
1417 @bottle.route(url_base + '/<tenant_id>/scenarios/<scenario_id>', method='GET')
1418 def http_get_scenario_id(tenant_id, scenario_id):
1419 '''get scenario details, can use both uuid or name'''
1420 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
1421 try:
1422 #check valid tenant_id
1423 if tenant_id != "any":
1424 nfvo.check_tenant(mydb, tenant_id)
1425 #obtain data
1426 scenario = mydb.get_scenario(scenario_id, tenant_id)
1427 convert_datetime2str(scenario)
1428 data={'scenario' : scenario}
1429 return format_out(data)
1430 except bottle.HTTPError:
1431 raise
1432 except (nfvo.NfvoException, db_base_Exception) as e:
1433 logger.error("http_get_scenarios error {}: {}".format(e.http_code, str(e)))
1434 bottle.abort(e.http_code, str(e))
1435 except Exception as e:
1436 logger.error("Unexpected exception: ", exc_info=True)
1437 bottle.abort(HTTP_Internal_Server_Error, type(e).__name__ + ": " + str(e))
1438
1439
1440 @bottle.route(url_base + '/<tenant_id>/scenarios/<scenario_id>', method='DELETE')
1441 def http_delete_scenario_id(tenant_id, scenario_id):
1442 '''delete a scenario from database, can use both uuid or name'''
1443 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
1444 try:
1445 #check valid tenant_id
1446 if tenant_id != "any":
1447 nfvo.check_tenant(mydb, tenant_id)
1448 #obtain data
1449 data = mydb.delete_scenario(scenario_id, tenant_id)
1450 #print json.dumps(data, indent=4)
1451 return format_out({"result":"scenario " + data + " deleted"})
1452 except bottle.HTTPError:
1453 raise
1454 except (nfvo.NfvoException, db_base_Exception) as e:
1455 logger.error("http_delete_scenario_id error {}: {}".format(e.http_code, str(e)))
1456 bottle.abort(e.http_code, str(e))
1457 except Exception as e:
1458 logger.error("Unexpected exception: ", exc_info=True)
1459 bottle.abort(HTTP_Internal_Server_Error, type(e).__name__ + ": " + str(e))
1460
1461
1462 @bottle.route(url_base + '/<tenant_id>/scenarios/<scenario_id>', method='PUT')
1463 def http_put_scenario_id(tenant_id, scenario_id):
1464 '''edit an existing scenario id'''
1465 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
1466 http_content,_ = format_in( scenario_edit_schema )
1467 #r = utils.remove_extra_items(http_content, scenario_edit_schema)
1468 #if r is not None: print "http_put_scenario_id: Warning: remove extra items ", r
1469 #print "http_put_scenario_id input: ", http_content
1470 try:
1471 nfvo.edit_scenario(mydb, tenant_id, scenario_id, http_content)
1472 #print json.dumps(data, indent=4)
1473 #return format_out(data)
1474 return http_get_scenario_id(tenant_id, scenario_id)
1475 except bottle.HTTPError:
1476 raise
1477 except (nfvo.NfvoException, db_base_Exception) as e:
1478 logger.error("http_put_scenario_id error {}: {}".format(e.http_code, str(e)))
1479 bottle.abort(e.http_code, str(e))
1480 except Exception as e:
1481 logger.error("Unexpected exception: ", exc_info=True)
1482 bottle.abort(HTTP_Internal_Server_Error, type(e).__name__ + ": " + str(e))
1483
1484 @bottle.route(url_base + '/<tenant_id>/instances', method='POST')
1485 def http_post_instances(tenant_id):
1486 '''create an instance-scenario'''
1487 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
1488 # parse input data
1489 http_content, used_schema = format_in(instance_scenario_create_schema_v01)
1490 r = utils.remove_extra_items(http_content, used_schema)
1491 if r is not None:
1492 logger.warning("http_post_instances: Warning: remove extra items %s", str(r))
1493 try:
1494 #check valid tenant_id
1495 if tenant_id != "any":
1496 nfvo.check_tenant(mydb, tenant_id)
1497 data = nfvo.create_instance(mydb, tenant_id, http_content["instance"])
1498 return format_out(data)
1499 except bottle.HTTPError:
1500 raise
1501 except (nfvo.NfvoException, db_base_Exception) as e:
1502 logger.error("http_post_instances error {}: {}".format(e.http_code, str(e)))
1503 bottle.abort(e.http_code, str(e))
1504 except Exception as e:
1505 logger.error("Unexpected exception: ", exc_info=True)
1506 bottle.abort(HTTP_Internal_Server_Error, type(e).__name__ + ": " + str(e))
1507
1508 #
1509 # INSTANCES
1510 #
1511 @bottle.route(url_base + '/<tenant_id>/instances', method='GET')
1512 def http_get_instances(tenant_id):
1513 '''get instance list'''
1514 try:
1515 #check valid tenant_id
1516 if tenant_id != "any":
1517 nfvo.check_tenant(mydb, tenant_id)
1518 #obtain data
1519 s,w,l=filter_query_string(bottle.request.query, None, ('uuid', 'name', 'scenario_id', 'tenant_id', 'description', 'created_at'))
1520 if tenant_id != "any":
1521 w['tenant_id'] = tenant_id
1522 instances = mydb.get_rows(SELECT=s, WHERE=w, LIMIT=l, FROM='instance_scenarios')
1523 convert_datetime2str(instances)
1524 utils.convert_str2boolean(instances, ('public',) )
1525 data={'instances':instances}
1526 return format_out(data)
1527 except bottle.HTTPError:
1528 raise
1529 except (nfvo.NfvoException, db_base_Exception) as e:
1530 logger.error("http_get_instances error {}: {}".format(e.http_code, str(e)))
1531 bottle.abort(e.http_code, str(e))
1532 except Exception as e:
1533 logger.error("Unexpected exception: ", exc_info=True)
1534 bottle.abort(HTTP_Internal_Server_Error, type(e).__name__ + ": " + str(e))
1535
1536
1537 @bottle.route(url_base + '/<tenant_id>/instances/<instance_id>', method='GET')
1538 def http_get_instance_id(tenant_id, instance_id):
1539 '''get instances details, can use both uuid or name'''
1540 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
1541 try:
1542 #check valid tenant_id
1543 if tenant_id != "any":
1544 nfvo.check_tenant(mydb, tenant_id)
1545 if tenant_id == "any":
1546 tenant_id = None
1547 #obtain data (first time is only to check that the instance exists)
1548 instance_dict = mydb.get_instance_scenario(instance_id, tenant_id, verbose=True)
1549 try:
1550 nfvo.refresh_instance(mydb, tenant_id, instance_dict)
1551 except (nfvo.NfvoException, db_base_Exception) as e:
1552 logger.warn("nfvo.refresh_instance couldn't refresh the status of the instance: %s" % str(e))
1553 # obtain data with results upated
1554 instance = mydb.get_instance_scenario(instance_id, tenant_id, verbose=True)
1555 # Workaround to SO, convert vnfs:vms:interfaces:ip_address from ";" separated list to report the first value
1556 for vnf in instance.get("vnfs", ()):
1557 for vm in vnf.get("vms", ()):
1558 for iface in vm.get("interfaces", ()):
1559 if iface.get("ip_address"):
1560 index = iface["ip_address"].find(";")
1561 if index >= 0:
1562 iface["ip_address"] = iface["ip_address"][:index]
1563 convert_datetime2str(instance)
1564 # print json.dumps(instance, indent=4)
1565 return format_out(instance)
1566 except bottle.HTTPError:
1567 raise
1568 except (nfvo.NfvoException, db_base_Exception) as e:
1569 logger.error("http_get_instance_id error {}: {}".format(e.http_code, str(e)))
1570 bottle.abort(e.http_code, str(e))
1571 except Exception as e:
1572 logger.error("Unexpected exception: ", exc_info=True)
1573 bottle.abort(HTTP_Internal_Server_Error, type(e).__name__ + ": " + str(e))
1574
1575
1576 @bottle.route(url_base + '/<tenant_id>/instances/<instance_id>', method='DELETE')
1577 def http_delete_instance_id(tenant_id, instance_id):
1578 '''delete instance from VIM and from database, can use both uuid or name'''
1579 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
1580 try:
1581 #check valid tenant_id
1582 if tenant_id != "any":
1583 nfvo.check_tenant(mydb, tenant_id)
1584 if tenant_id == "any":
1585 tenant_id = None
1586 #obtain data
1587 message = nfvo.delete_instance(mydb, tenant_id,instance_id)
1588 return format_out({"result":message})
1589 except bottle.HTTPError:
1590 raise
1591 except (nfvo.NfvoException, db_base_Exception) as e:
1592 logger.error("http_delete_instance_id error {}: {}".format(e.http_code, str(e)))
1593 bottle.abort(e.http_code, str(e))
1594 except Exception as e:
1595 logger.error("Unexpected exception: ", exc_info=True)
1596 bottle.abort(HTTP_Internal_Server_Error, type(e).__name__ + ": " + str(e))
1597
1598
1599 @bottle.route(url_base + '/<tenant_id>/instances/<instance_id>/action', method='POST')
1600 def http_post_instance_scenario_action(tenant_id, instance_id):
1601 """
1602 take an action over a scenario instance
1603 :param tenant_id: tenant where user belongs to
1604 :param instance_id: instance indentity
1605 :return:
1606 """
1607 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
1608 # parse input data
1609 http_content, _ = format_in(instance_scenario_action_schema)
1610 r = utils.remove_extra_items(http_content, instance_scenario_action_schema)
1611 if r:
1612 logger.debug("Remove received extra items %s", str(r))
1613 try:
1614 #check valid tenant_id
1615 if tenant_id != "any":
1616 nfvo.check_tenant(mydb, tenant_id)
1617
1618 #print "http_post_instance_scenario_action input: ", http_content
1619 #obtain data
1620 instance = mydb.get_instance_scenario(instance_id, tenant_id)
1621 instance_id = instance["uuid"]
1622
1623 data = nfvo.instance_action(mydb, tenant_id, instance_id, http_content)
1624 return format_out(data)
1625 except bottle.HTTPError:
1626 raise
1627 except (nfvo.NfvoException, db_base_Exception) as e:
1628 logger.error("http_post_instance_scenario_action error {}: {}".format(e.http_code, str(e)))
1629 bottle.abort(e.http_code, str(e))
1630 except Exception as e:
1631 logger.error("Unexpected exception: ", exc_info=True)
1632 bottle.abort(HTTP_Internal_Server_Error, type(e).__name__ + ": " + str(e))
1633
1634
1635 @bottle.route(url_base + '/<tenant_id>/instances/<instance_id>/action', method='GET')
1636 @bottle.route(url_base + '/<tenant_id>/instances/<instance_id>/action/<action_id>', method='GET')
1637 def http_get_instance_scenario_action(tenant_id, instance_id, action_id=None):
1638 """
1639 List the actions done over an instance, or the action details
1640 :param tenant_id: tenant where user belongs to. Can be "any" to ignore
1641 :param instance_id: instance id, can be "any" to get actions of all instances
1642 :return:
1643 """
1644 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
1645 try:
1646 # check valid tenant_id
1647 if tenant_id != "any":
1648 nfvo.check_tenant(mydb, tenant_id)
1649 data = nfvo.instance_action_get(mydb, tenant_id, instance_id, action_id)
1650 return format_out(data)
1651 except bottle.HTTPError:
1652 raise
1653 except (nfvo.NfvoException, db_base_Exception) as e:
1654 logger.error("http_get_instance_scenario_action error {}: {}".format(e.http_code, str(e)))
1655 bottle.abort(e.http_code, str(e))
1656 except Exception as e:
1657 logger.error("Unexpected exception: ", exc_info=True)
1658 bottle.abort(HTTP_Internal_Server_Error, type(e).__name__ + ": " + str(e))
1659
1660 def remove_clear_passwd(data):
1661 """
1662 Removes clear passwords from the data received
1663 :param data: data with clear password
1664 :return: data without the password information
1665 """
1666
1667 passw = ['password: ', 'passwd: ']
1668
1669 for pattern in passw:
1670 init = data.find(pattern)
1671 while init != -1:
1672 end = data.find('\n', init)
1673 data = data[:init] + '{}******'.format(pattern) + data[end:]
1674 init += 1
1675 init = data.find(pattern, init)
1676 return data
1677
1678 @bottle.error(400)
1679 @bottle.error(401)
1680 @bottle.error(404)
1681 @bottle.error(403)
1682 @bottle.error(405)
1683 @bottle.error(406)
1684 @bottle.error(409)
1685 @bottle.error(503)
1686 @bottle.error(500)
1687 def error400(error):
1688 e={"error":{"code":error.status_code, "type":error.status, "description":error.body}}
1689 bottle.response.headers['Access-Control-Allow-Origin'] = '*'
1690 return format_out(e)
1691