Fix problem at vim_thread reload when vim_account edited
[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>/vim_accounts', method='GET')
475 @bottle.route(url_base + '/<tenant_id>/vim_accounts/<vim_account_id>', method='GET')
476 def http_get_vim_account(tenant_id, vim_account_id=None):
477 '''get vim_account list/details, '''
478 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
479 try:
480 select_ = ('uuid', 'name', 'dt.datacenter_id as vim_id', 'vim_tenant_name', 'vim_tenant_id', 'user', 'config',
481 'dt.created_at as created_at', 'passwd')
482 where_ = {'nfvo_tenant_id': tenant_id}
483 if vim_account_id:
484 where_['dt.uuid'] = vim_account_id
485 from_ = 'tenants_datacenters as td join datacenter_tenants as dt on dt.uuid=td.datacenter_tenant_id'
486 vim_accounts = mydb.get_rows(SELECT=select_, FROM=from_, WHERE=where_)
487
488 if len(vim_accounts) == 0 and vim_account_id:
489 bottle.abort(HTTP_Not_Found, "No vim_account found for tenant {} and id '{}'".format(tenant_id,
490 vim_account_id))
491 for vim_account in vim_accounts:
492 if vim_account["passwd"]:
493 vim_account["passwd"] = "******"
494 if vim_account['config'] != None:
495 try:
496 config_dict = yaml.load(vim_account['config'])
497 vim_account['config'] = config_dict
498 if vim_account['config'].get('admin_password'):
499 vim_account['config']['admin_password'] = "******"
500 if vim_account['config'].get('vcenter_password'):
501 vim_account['config']['vcenter_password'] = "******"
502 if vim_account['config'].get('nsx_password'):
503 vim_account['config']['nsx_password'] = "******"
504 except Exception as e:
505 logger.error("Exception '%s' while trying to load config information", str(e))
506 # change_keys_http2db(content, http2db_datacenter, reverse=True)
507 #convert_datetime2str(vim_account)
508 if vim_account_id:
509 return format_out({"datacenter": vim_accounts[0]})
510 else:
511 return format_out({"datacenters": vim_accounts})
512 except bottle.HTTPError:
513 raise
514 except (nfvo.NfvoException, db_base_Exception) as e:
515 logger.error("http_get_datacenter_id error {}: {}".format(e.http_code, str(e)))
516 bottle.abort(e.http_code, str(e))
517 except Exception as e:
518 logger.error("Unexpected exception: ", exc_info=True)
519 bottle.abort(HTTP_Internal_Server_Error, type(e).__name__ + ": " + str(e))
520
521
522 @bottle.route(url_base + '/<tenant_id>/datacenters/<datacenter_id>', method='GET')
523 def http_get_datacenter_id(tenant_id, datacenter_id):
524 '''get datacenter details, can use both uuid or name'''
525 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
526 try:
527 if tenant_id != 'any':
528 #check valid tenant_id
529 nfvo.check_tenant(mydb, tenant_id)
530 #obtain data
531 what = 'uuid' if utils.check_valid_uuid(datacenter_id) else 'name'
532 where_={}
533 where_[what] = datacenter_id
534 select_=['uuid', 'name','vim_url', 'vim_url_admin', 'type', 'd.config as config', 'description', 'd.created_at as created_at']
535 if tenant_id != 'any':
536 select_.append("datacenter_tenant_id")
537 where_['td.nfvo_tenant_id']= tenant_id
538 from_='datacenters as d join tenants_datacenters as td on d.uuid=td.datacenter_id'
539 else:
540 from_='datacenters as d'
541 datacenters = mydb.get_rows(
542 SELECT=select_,
543 FROM=from_,
544 WHERE=where_)
545
546 if len(datacenters)==0:
547 bottle.abort( HTTP_Not_Found, "No datacenter found for tenant with {} '{}'".format(what, datacenter_id) )
548 elif len(datacenters)>1:
549 bottle.abort( HTTP_Bad_Request, "More than one datacenter found for tenant with {} '{}'".format(what, datacenter_id) )
550 datacenter = datacenters[0]
551 if tenant_id != 'any':
552 #get vim tenant info
553 vim_tenants = mydb.get_rows(
554 SELECT=("vim_tenant_name", "vim_tenant_id", "user", "passwd", "config"),
555 FROM="datacenter_tenants",
556 WHERE={"uuid": datacenters[0]["datacenter_tenant_id"]},
557 ORDER_BY=("created", ) )
558 del datacenter["datacenter_tenant_id"]
559 datacenter["vim_tenants"] = vim_tenants
560 for vim_tenant in vim_tenants:
561 if vim_tenant["passwd"]:
562 vim_tenant["passwd"] = "******"
563 if vim_tenant['config'] != None:
564 try:
565 config_dict = yaml.load(vim_tenant['config'])
566 vim_tenant['config'] = config_dict
567 if vim_tenant['config'].get('admin_password'):
568 vim_tenant['config']['admin_password'] = "******"
569 if vim_tenant['config'].get('vcenter_password'):
570 vim_tenant['config']['vcenter_password'] = "******"
571 if vim_tenant['config'].get('nsx_password'):
572 vim_tenant['config']['nsx_password'] = "******"
573 except Exception as e:
574 logger.error("Exception '%s' while trying to load config information", str(e))
575
576 if datacenter['config'] != None:
577 try:
578 config_dict = yaml.load(datacenter['config'])
579 datacenter['config'] = config_dict
580 if datacenter['config'].get('admin_password'):
581 datacenter['config']['admin_password'] = "******"
582 if datacenter['config'].get('vcenter_password'):
583 datacenter['config']['vcenter_password'] = "******"
584 if datacenter['config'].get('nsx_password'):
585 datacenter['config']['nsx_password'] = "******"
586 except Exception as e:
587 logger.error("Exception '%s' while trying to load config information", str(e))
588 #change_keys_http2db(content, http2db_datacenter, reverse=True)
589 convert_datetime2str(datacenter)
590 data={'datacenter' : datacenter}
591 return format_out(data)
592 except bottle.HTTPError:
593 raise
594 except (nfvo.NfvoException, db_base_Exception) as e:
595 logger.error("http_get_datacenter_id error {}: {}".format(e.http_code, str(e)))
596 bottle.abort(e.http_code, str(e))
597 except Exception as e:
598 logger.error("Unexpected exception: ", exc_info=True)
599 bottle.abort(HTTP_Internal_Server_Error, type(e).__name__ + ": " + str(e))
600
601
602 @bottle.route(url_base + '/datacenters', method='POST')
603 def http_post_datacenters():
604 '''insert a datacenter into the catalogue. '''
605 #parse input data
606 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
607 http_content,_ = format_in(datacenter_schema, confidential_data=True)
608 r = utils.remove_extra_items(http_content, datacenter_schema)
609 if r:
610 logger.debug("Remove received extra items %s", str(r))
611 try:
612 data = nfvo.new_datacenter(mydb, http_content['datacenter'])
613 return http_get_datacenter_id('any', data)
614 except bottle.HTTPError:
615 raise
616 except (nfvo.NfvoException, db_base_Exception) as e:
617 logger.error("http_post_datacenters error {}: {}".format(e.http_code, str(e)))
618 bottle.abort(e.http_code, str(e))
619 except Exception as e:
620 logger.error("Unexpected exception: ", exc_info=True)
621 bottle.abort(HTTP_Internal_Server_Error, type(e).__name__ + ": " + str(e))
622
623
624 @bottle.route(url_base + '/datacenters/<datacenter_id_name>', method='PUT')
625 def http_edit_datacenter_id(datacenter_id_name):
626 '''edit datacenter details, can use both uuid or name'''
627 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
628 #parse input data
629 http_content,_ = format_in( datacenter_edit_schema )
630 r = utils.remove_extra_items(http_content, datacenter_edit_schema)
631 if r:
632 logger.debug("Remove received extra items %s", str(r))
633
634 try:
635 datacenter_id = nfvo.edit_datacenter(mydb, datacenter_id_name, http_content['datacenter'])
636 return http_get_datacenter_id('any', datacenter_id)
637 except bottle.HTTPError:
638 raise
639 except (nfvo.NfvoException, db_base_Exception) as e:
640 logger.error("http_edit_datacenter_id error {}: {}".format(e.http_code, str(e)))
641 bottle.abort(e.http_code, str(e))
642 except Exception as e:
643 logger.error("Unexpected exception: ", exc_info=True)
644 bottle.abort(HTTP_Internal_Server_Error, type(e).__name__ + ": " + str(e))
645
646 @bottle.route(url_base + '/<tenant_id>/sdn_controllers', method='POST')
647 def http_post_sdn_controller(tenant_id):
648 '''insert a sdn controller into the catalogue. '''
649 #parse input data
650 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
651 http_content,_ = format_in( sdn_controller_schema )
652 try:
653 logger.debug("tenant_id: "+tenant_id)
654 #logger.debug("content: {}".format(http_content['sdn_controller']))
655
656 data = nfvo.sdn_controller_create(mydb, tenant_id, http_content['sdn_controller'])
657 return format_out({"sdn_controller": nfvo.sdn_controller_list(mydb, tenant_id, data)})
658 except bottle.HTTPError:
659 raise
660 except (nfvo.NfvoException, db_base_Exception) as e:
661 logger.error("http_post_sdn_controller error {}: {}".format(e.http_code, str(e)))
662 bottle.abort(e.http_code, str(e))
663 except Exception as e:
664 logger.error("Unexpected exception: ", exc_info=True)
665 bottle.abort(HTTP_Internal_Server_Error, type(e).__name__ + ": " + str(e))
666
667 @bottle.route(url_base + '/<tenant_id>/sdn_controllers/<controller_id>', method='PUT')
668 def http_put_sdn_controller_update(tenant_id, controller_id):
669 '''Update sdn controller'''
670 #parse input data
671 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
672 http_content,_ = format_in( sdn_controller_edit_schema )
673 # r = utils.remove_extra_items(http_content, datacenter_schema)
674 # if r:
675 # logger.debug("Remove received extra items %s", str(r))
676 try:
677 #logger.debug("tenant_id: "+tenant_id)
678 logger.debug("content: {}".format(http_content['sdn_controller']))
679
680 data = nfvo.sdn_controller_update(mydb, tenant_id, controller_id, http_content['sdn_controller'])
681 return format_out({"sdn_controller": nfvo.sdn_controller_list(mydb, tenant_id, controller_id)})
682
683 except bottle.HTTPError:
684 raise
685 except (nfvo.NfvoException, db_base_Exception) as e:
686 logger.error("http_post_sdn_controller error {}: {}".format(e.http_code, str(e)))
687 bottle.abort(e.http_code, str(e))
688 except Exception as e:
689 logger.error("Unexpected exception: ", exc_info=True)
690 bottle.abort(HTTP_Internal_Server_Error, type(e).__name__ + ": " + str(e))
691
692 @bottle.route(url_base + '/<tenant_id>/sdn_controllers', method='GET')
693 def http_get_sdn_controller(tenant_id):
694 '''get sdn controllers list, can use both uuid or name'''
695 try:
696 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
697
698 data = {'sdn_controllers': nfvo.sdn_controller_list(mydb, tenant_id)}
699 return format_out(data)
700 except bottle.HTTPError:
701 raise
702 except (nfvo.NfvoException, db_base_Exception) as e:
703 logger.error("http_get_sdn_controller error {}: {}".format(e.http_code, str(e)))
704 bottle.abort(e.http_code, str(e))
705 except Exception as e:
706 logger.error("Unexpected exception: ", exc_info=True)
707 bottle.abort(HTTP_Internal_Server_Error, type(e).__name__ + ": " + str(e))
708
709 @bottle.route(url_base + '/<tenant_id>/sdn_controllers/<controller_id>', method='GET')
710 def http_get_sdn_controller_id(tenant_id, controller_id):
711 '''get sdn controller details, can use both uuid or name'''
712 try:
713 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
714 data = nfvo.sdn_controller_list(mydb, tenant_id, controller_id)
715 return format_out({"sdn_controllers": data})
716 except bottle.HTTPError:
717 raise
718 except (nfvo.NfvoException, db_base_Exception) as e:
719 logger.error("http_get_sdn_controller_id error {}: {}".format(e.http_code, str(e)))
720 bottle.abort(e.http_code, str(e))
721 except Exception as e:
722 logger.error("Unexpected exception: ", exc_info=True)
723 bottle.abort(HTTP_Internal_Server_Error, type(e).__name__ + ": " + str(e))
724
725 @bottle.route(url_base + '/<tenant_id>/sdn_controllers/<controller_id>', method='DELETE')
726 def http_delete_sdn_controller_id(tenant_id, controller_id):
727 '''delete sdn controller, can use both uuid or name'''
728 try:
729 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
730 data = nfvo.sdn_controller_delete(mydb, tenant_id, controller_id)
731 return format_out(data)
732 except bottle.HTTPError:
733 raise
734 except (nfvo.NfvoException, db_base_Exception) as e:
735 logger.error("http_delete_sdn_controller_id error {}: {}".format(e.http_code, str(e)))
736 bottle.abort(e.http_code, str(e))
737 except Exception as e:
738 logger.error("Unexpected exception: ", exc_info=True)
739 bottle.abort(HTTP_Internal_Server_Error, type(e).__name__ + ": " + str(e))
740
741 @bottle.route(url_base + '/<tenant_id>/datacenters/<datacenter_id>/sdn_mapping', method='POST')
742 def http_post_datacenter_sdn_port_mapping(tenant_id, datacenter_id):
743 '''Set the sdn port mapping for a datacenter. '''
744 #parse input data
745 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
746 http_content, _ = format_in(sdn_port_mapping_schema)
747 # r = utils.remove_extra_items(http_content, datacenter_schema)
748 # if r:
749 # logger.debug("Remove received extra items %s", str(r))
750 try:
751 data = nfvo.datacenter_sdn_port_mapping_set(mydb, tenant_id, datacenter_id, http_content['sdn_port_mapping'])
752 return format_out({"sdn_port_mapping": data})
753 except bottle.HTTPError:
754 raise
755 except (nfvo.NfvoException, db_base_Exception) as e:
756 logger.error("http_post_datacenter_sdn_port_mapping error {}: {}".format(e.http_code, str(e)))
757 bottle.abort(e.http_code, str(e))
758 except Exception as e:
759 logger.error("Unexpected exception: ", exc_info=True)
760 bottle.abort(HTTP_Internal_Server_Error, type(e).__name__ + ": " + str(e))
761
762 @bottle.route(url_base + '/<tenant_id>/datacenters/<datacenter_id>/sdn_mapping', method='GET')
763 def http_get_datacenter_sdn_port_mapping(tenant_id, datacenter_id):
764 '''get datacenter sdn mapping details, can use both uuid or name'''
765 try:
766 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
767
768 data = nfvo.datacenter_sdn_port_mapping_list(mydb, tenant_id, datacenter_id)
769 return format_out({"sdn_port_mapping": data})
770 except bottle.HTTPError:
771 raise
772 except (nfvo.NfvoException, db_base_Exception) as e:
773 logger.error("http_get_datacenter_sdn_port_mapping error {}: {}".format(e.http_code, str(e)))
774 bottle.abort(e.http_code, str(e))
775 except Exception as e:
776 logger.error("Unexpected exception: ", exc_info=True)
777 bottle.abort(HTTP_Internal_Server_Error, type(e).__name__ + ": " + str(e))
778
779 @bottle.route(url_base + '/<tenant_id>/datacenters/<datacenter_id>/sdn_mapping', method='DELETE')
780 def http_delete_datacenter_sdn_port_mapping(tenant_id, datacenter_id):
781 '''clean datacenter sdn mapping, can use both uuid or name'''
782 try:
783 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
784 data = nfvo.datacenter_sdn_port_mapping_delete(mydb, tenant_id, datacenter_id)
785 return format_out({"result": data})
786 except bottle.HTTPError:
787 raise
788 except (nfvo.NfvoException, db_base_Exception) as e:
789 logger.error("http_delete_datacenter_sdn_port_mapping error {}: {}".format(e.http_code, str(e)))
790 bottle.abort(e.http_code, str(e))
791 except Exception as e:
792 logger.error("Unexpected exception: ", exc_info=True)
793 bottle.abort(HTTP_Internal_Server_Error, type(e).__name__ + ": " + str(e))
794
795 @bottle.route(url_base + '/<tenant_id>/datacenters/<datacenter_id>/networks', method='GET') #deprecated
796 @bottle.route(url_base + '/<tenant_id>/datacenters/<datacenter_id>/netmaps', method='GET')
797 @bottle.route(url_base + '/<tenant_id>/datacenters/<datacenter_id>/netmaps/<netmap_id>', method='GET')
798 def http_getnetmap_datacenter_id(tenant_id, datacenter_id, netmap_id=None):
799 '''get datacenter networks, can use both uuid or name'''
800 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
801 #obtain data
802 try:
803 datacenter_dict = mydb.get_table_by_uuid_name('datacenters', datacenter_id, "datacenter")
804 where_= {"datacenter_id":datacenter_dict['uuid']}
805 if netmap_id:
806 if utils.check_valid_uuid(netmap_id):
807 where_["uuid"] = netmap_id
808 else:
809 where_["name"] = netmap_id
810 netmaps =mydb.get_rows(FROM='datacenter_nets',
811 SELECT=('name','vim_net_id as vim_id', 'uuid', 'type','multipoint','shared','description', 'created_at'),
812 WHERE=where_ )
813 convert_datetime2str(netmaps)
814 utils.convert_str2boolean(netmaps, ('shared', 'multipoint') )
815 if netmap_id and len(netmaps)==1:
816 data={'netmap' : netmaps[0]}
817 elif netmap_id and len(netmaps)==0:
818 bottle.abort(HTTP_Not_Found, "No netmap found with " + " and ".join(map(lambda x: str(x[0])+": "+str(x[1]), where_.iteritems())) )
819 return
820 else:
821 data={'netmaps' : netmaps}
822 return format_out(data)
823 except bottle.HTTPError:
824 raise
825 except (nfvo.NfvoException, db_base_Exception) as e:
826 logger.error("http_getnetwork_datacenter_id error {}: {}".format(e.http_code, str(e)))
827 bottle.abort(e.http_code, str(e))
828 except Exception as e:
829 logger.error("Unexpected exception: ", exc_info=True)
830 bottle.abort(HTTP_Internal_Server_Error, type(e).__name__ + ": " + str(e))
831
832
833 @bottle.route(url_base + '/<tenant_id>/datacenters/<datacenter_id>/netmaps', method='DELETE')
834 @bottle.route(url_base + '/<tenant_id>/datacenters/<datacenter_id>/netmaps/<netmap_id>', method='DELETE')
835 def http_delnetmap_datacenter_id(tenant_id, datacenter_id, netmap_id=None):
836 '''get datacenter networks, can use both uuid or name'''
837 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
838 #obtain data
839 try:
840 datacenter_dict = mydb.get_table_by_uuid_name('datacenters', datacenter_id, "datacenter")
841 where_= {"datacenter_id":datacenter_dict['uuid']}
842 if netmap_id:
843 if utils.check_valid_uuid(netmap_id):
844 where_["uuid"] = netmap_id
845 else:
846 where_["name"] = netmap_id
847 #change_keys_http2db(content, http2db_tenant, reverse=True)
848 deleted = mydb.delete_row(FROM='datacenter_nets', WHERE= where_)
849 if deleted == 0 and netmap_id:
850 bottle.abort(HTTP_Not_Found, "No netmap found with " + " and ".join(map(lambda x: str(x[0])+": "+str(x[1]), where_.iteritems())) )
851 if netmap_id:
852 return format_out({"result": "netmap %s deleted" % netmap_id})
853 else:
854 return format_out({"result": "%d netmap deleted" % deleted})
855 except bottle.HTTPError:
856 raise
857 except (nfvo.NfvoException, db_base_Exception) as e:
858 logger.error("http_delnetmap_datacenter_id error {}: {}".format(e.http_code, str(e)))
859 bottle.abort(e.http_code, str(e))
860 except Exception as e:
861 logger.error("Unexpected exception: ", exc_info=True)
862 bottle.abort(HTTP_Internal_Server_Error, type(e).__name__ + ": " + str(e))
863
864
865 @bottle.route(url_base + '/<tenant_id>/datacenters/<datacenter_id>/netmaps/upload', method='POST')
866 def http_uploadnetmap_datacenter_id(tenant_id, datacenter_id):
867 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
868 try:
869 netmaps = nfvo.datacenter_new_netmap(mydb, tenant_id, datacenter_id, None)
870 convert_datetime2str(netmaps)
871 utils.convert_str2boolean(netmaps, ('shared', 'multipoint') )
872 data={'netmaps' : netmaps}
873 return format_out(data)
874 except bottle.HTTPError:
875 raise
876 except (nfvo.NfvoException, db_base_Exception) as e:
877 logger.error("http_uploadnetmap_datacenter_id error {}: {}".format(e.http_code, str(e)))
878 bottle.abort(e.http_code, str(e))
879 except Exception as e:
880 logger.error("Unexpected exception: ", exc_info=True)
881 bottle.abort(HTTP_Internal_Server_Error, type(e).__name__ + ": " + str(e))
882
883
884 @bottle.route(url_base + '/<tenant_id>/datacenters/<datacenter_id>/netmaps', method='POST')
885 def http_postnetmap_datacenter_id(tenant_id, datacenter_id):
886 '''creates a new netmap'''
887 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
888 #parse input data
889 http_content,_ = format_in( netmap_new_schema )
890 r = utils.remove_extra_items(http_content, netmap_new_schema)
891 if r:
892 logger.debug("Remove received extra items %s", str(r))
893 try:
894 #obtain data, check that only one exist
895 netmaps = nfvo.datacenter_new_netmap(mydb, tenant_id, datacenter_id, http_content)
896 convert_datetime2str(netmaps)
897 utils.convert_str2boolean(netmaps, ('shared', 'multipoint') )
898 data={'netmaps' : netmaps}
899 return format_out(data)
900 except bottle.HTTPError:
901 raise
902 except (nfvo.NfvoException, db_base_Exception) as e:
903 logger.error("http_postnetmap_datacenter_id error {}: {}".format(e.http_code, str(e)))
904 bottle.abort(e.http_code, str(e))
905 except Exception as e:
906 logger.error("Unexpected exception: ", exc_info=True)
907 bottle.abort(HTTP_Internal_Server_Error, type(e).__name__ + ": " + str(e))
908
909
910 @bottle.route(url_base + '/<tenant_id>/datacenters/<datacenter_id>/netmaps/<netmap_id>', method='PUT')
911 def http_putnettmap_datacenter_id(tenant_id, datacenter_id, netmap_id):
912 '''edit a netmap'''
913 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
914 #parse input data
915 http_content,_ = format_in( netmap_edit_schema )
916 r = utils.remove_extra_items(http_content, netmap_edit_schema)
917 if r:
918 logger.debug("Remove received extra items %s", str(r))
919
920 #obtain data, check that only one exist
921 try:
922 nfvo.datacenter_edit_netmap(mydb, tenant_id, datacenter_id, netmap_id, http_content)
923 return http_getnetmap_datacenter_id(tenant_id, datacenter_id, netmap_id)
924 except bottle.HTTPError:
925 raise
926 except (nfvo.NfvoException, db_base_Exception) as e:
927 logger.error("http_putnettmap_datacenter_id error {}: {}".format(e.http_code, str(e)))
928 bottle.abort(e.http_code, str(e))
929 except Exception as e:
930 logger.error("Unexpected exception: ", exc_info=True)
931 bottle.abort(HTTP_Internal_Server_Error, type(e).__name__ + ": " + str(e))
932
933
934 @bottle.route(url_base + '/<tenant_id>/datacenters/<datacenter_id>/action', method='POST')
935 def http_action_datacenter_id(tenant_id, datacenter_id):
936 '''perform an action over datacenter, can use both uuid or name'''
937 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
938 #parse input data
939 http_content,_ = format_in( datacenter_action_schema )
940 r = utils.remove_extra_items(http_content, datacenter_action_schema)
941 if r:
942 logger.debug("Remove received extra items %s", str(r))
943 try:
944 #obtain data, check that only one exist
945 result = nfvo.datacenter_action(mydb, tenant_id, datacenter_id, http_content)
946 if 'net-update' in http_content:
947 return http_getnetmap_datacenter_id(datacenter_id)
948 else:
949 return format_out(result)
950 except bottle.HTTPError:
951 raise
952 except (nfvo.NfvoException, db_base_Exception) as e:
953 logger.error("http_action_datacenter_id 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 + '/datacenters/<datacenter_id>', method='DELETE')
961 def http_delete_datacenter_id( datacenter_id):
962 '''delete a tenant from database, can use both uuid or name'''
963
964 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
965 try:
966 data = nfvo.delete_datacenter(mydb, datacenter_id)
967 return format_out({"result":"datacenter '" + data + "' deleted"})
968 except bottle.HTTPError:
969 raise
970 except (nfvo.NfvoException, db_base_Exception) as e:
971 logger.error("http_delete_datacenter_id error {}: {}".format(e.http_code, str(e)))
972 bottle.abort(e.http_code, str(e))
973 except Exception as e:
974 logger.error("Unexpected exception: ", exc_info=True)
975 bottle.abort(HTTP_Internal_Server_Error, type(e).__name__ + ": " + str(e))
976
977
978 @bottle.route(url_base + '/<tenant_id>/datacenters/<datacenter_id>', method='POST')
979 @bottle.route(url_base + '/<tenant_id>/vim_accounts', method='POST')
980 def http_associate_datacenters(tenant_id, datacenter_id=None):
981 '''associate an existing datacenter to a this tenant. '''
982 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
983 #parse input data
984 http_content,_ = format_in(datacenter_associate_schema, confidential_data=True)
985 r = utils.remove_extra_items(http_content, datacenter_associate_schema)
986 if r:
987 logger.debug("Remove received extra items %s", str(r))
988 try:
989 vim_account_id = nfvo.create_vim_account(mydb, tenant_id, datacenter_id,
990 **http_content['datacenter'])
991 return http_get_vim_account(tenant_id, vim_account_id)
992 except bottle.HTTPError:
993 raise
994 except (nfvo.NfvoException, db_base_Exception) as e:
995 logger.error("http_associate_datacenters error {}: {}".format(e.http_code, str(e)))
996 bottle.abort(e.http_code, str(e))
997 except Exception as e:
998 logger.error("Unexpected exception: ", exc_info=True)
999 bottle.abort(HTTP_Internal_Server_Error, type(e).__name__ + ": " + str(e))
1000
1001 @bottle.route(url_base + '/<tenant_id>/vim_accounts/<vim_account_id>', method='PUT')
1002 @bottle.route(url_base + '/<tenant_id>/datacenters/<datacenter_id>', method='PUT')
1003 def http_vim_account_edit(tenant_id, vim_account_id=None, datacenter_id=None):
1004 '''associate an existing datacenter to a this tenant. '''
1005 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
1006 #parse input data
1007 http_content,_ = format_in(datacenter_associate_schema)
1008 r = utils.remove_extra_items(http_content, datacenter_associate_schema)
1009 if r:
1010 logger.debug("Remove received extra items %s", str(r))
1011 try:
1012 vim_account_id = nfvo.edit_vim_account(mydb, tenant_id, vim_account_id, datacenter_id=datacenter_id,
1013 **http_content['datacenter'])
1014 return http_get_vim_account(tenant_id, vim_account_id)
1015 except bottle.HTTPError:
1016 raise
1017 except (nfvo.NfvoException, db_base_Exception) as e:
1018 logger.error("http_vim_account_edit error {}: {}".format(e.http_code, str(e)))
1019 bottle.abort(e.http_code, str(e))
1020 except Exception as e:
1021 logger.error("Unexpected exception: ", exc_info=True)
1022 bottle.abort(HTTP_Internal_Server_Error, type(e).__name__ + ": " + str(e))
1023
1024
1025 @bottle.route(url_base + '/<tenant_id>/datacenters/<datacenter_id>', method='DELETE')
1026 @bottle.route(url_base + '/<tenant_id>/vim_accounts/<vim_account_id>', method='DELETE')
1027 def http_deassociate_datacenters(tenant_id, datacenter_id=None, vim_account_id=None):
1028 '''deassociate an existing datacenter to a this tenant. '''
1029 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
1030 try:
1031 data = nfvo.delete_vim_account(mydb, tenant_id, vim_account_id, datacenter_id)
1032 return format_out({"result": data})
1033 except bottle.HTTPError:
1034 raise
1035 except (nfvo.NfvoException, db_base_Exception) as e:
1036 logger.error("http_deassociate_datacenters error {}: {}".format(e.http_code, str(e)))
1037 bottle.abort(e.http_code, str(e))
1038 except Exception as e:
1039 logger.error("Unexpected exception: ", exc_info=True)
1040 bottle.abort(HTTP_Internal_Server_Error, type(e).__name__ + ": " + str(e))
1041
1042 @bottle.route(url_base + '/<tenant_id>/vim/<datacenter_id>/network/<network_id>/attach', method='POST')
1043 def http_post_vim_net_sdn_attach(tenant_id, datacenter_id, network_id):
1044 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
1045 http_content, _ = format_in(sdn_external_port_schema)
1046 try:
1047 data = nfvo.vim_net_sdn_attach(mydb, tenant_id, datacenter_id, network_id, http_content)
1048 return format_out(data)
1049 except bottle.HTTPError:
1050 raise
1051 except (nfvo.NfvoException, db_base_Exception) as e:
1052 logger.error("http_post_vim_net_sdn_attach error {}: {}".format(e.http_code, str(e)))
1053 bottle.abort(e.http_code, str(e))
1054 except Exception as e:
1055 logger.error("Unexpected exception: ", exc_info=True)
1056 bottle.abort(HTTP_Internal_Server_Error, type(e).__name__ + ": " + str(e))
1057
1058 @bottle.route(url_base + '/<tenant_id>/vim/<datacenter_id>/network/<network_id>/detach', method='DELETE')
1059 @bottle.route(url_base + '/<tenant_id>/vim/<datacenter_id>/network/<network_id>/detach/<port_id>', method='DELETE')
1060 def http_delete_vim_net_sdn_detach(tenant_id, datacenter_id, network_id, port_id=None):
1061 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
1062 try:
1063 data = nfvo.vim_net_sdn_detach(mydb, tenant_id, datacenter_id, network_id, port_id)
1064 return format_out(data)
1065 except bottle.HTTPError:
1066 raise
1067 except (nfvo.NfvoException, db_base_Exception) as e:
1068 logger.error("http_delete_vim_net_sdn_detach error {}: {}".format(e.http_code, str(e)))
1069 bottle.abort(e.http_code, str(e))
1070 except Exception as e:
1071 logger.error("Unexpected exception: ", exc_info=True)
1072 bottle.abort(HTTP_Internal_Server_Error, type(e).__name__ + ": " + str(e))
1073
1074 @bottle.route(url_base + '/<tenant_id>/vim/<datacenter_id>/<item>', method='GET')
1075 @bottle.route(url_base + '/<tenant_id>/vim/<datacenter_id>/<item>/<name>', method='GET')
1076 def http_get_vim_items(tenant_id, datacenter_id, item, name=None):
1077 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
1078 try:
1079 data = nfvo.vim_action_get(mydb, tenant_id, datacenter_id, item, name)
1080 return format_out(data)
1081 except bottle.HTTPError:
1082 raise
1083 except (nfvo.NfvoException, db_base_Exception) as e:
1084 logger.error("http_get_vim_items error {}: {}".format(e.http_code, str(e)))
1085 bottle.abort(e.http_code, str(e))
1086 except Exception as e:
1087 logger.error("Unexpected exception: ", exc_info=True)
1088 bottle.abort(HTTP_Internal_Server_Error, type(e).__name__ + ": " + str(e))
1089
1090
1091 @bottle.route(url_base + '/<tenant_id>/vim/<datacenter_id>/<item>/<name>', method='DELETE')
1092 def http_del_vim_items(tenant_id, datacenter_id, item, name):
1093 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
1094 try:
1095 data = nfvo.vim_action_delete(mydb, tenant_id, datacenter_id, item, name)
1096 return format_out({"result":data})
1097 except bottle.HTTPError:
1098 raise
1099 except (nfvo.NfvoException, db_base_Exception) as e:
1100 logger.error("http_del_vim_items error {}: {}".format(e.http_code, str(e)))
1101 bottle.abort(e.http_code, str(e))
1102 except Exception as e:
1103 logger.error("Unexpected exception: ", exc_info=True)
1104 bottle.abort(HTTP_Internal_Server_Error, type(e).__name__ + ": " + str(e))
1105
1106
1107 @bottle.route(url_base + '/<tenant_id>/vim/<datacenter_id>/<item>', method='POST')
1108 def http_post_vim_items(tenant_id, datacenter_id, item):
1109 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
1110 http_content,_ = format_in( object_schema )
1111 try:
1112 data = nfvo.vim_action_create(mydb, tenant_id, datacenter_id, item, http_content)
1113 return format_out(data)
1114 except bottle.HTTPError:
1115 raise
1116 except (nfvo.NfvoException, db_base_Exception) as e:
1117 logger.error("http_post_vim_items error {}: {}".format(e.http_code, str(e)))
1118 bottle.abort(e.http_code, str(e))
1119 except Exception as e:
1120 logger.error("Unexpected exception: ", exc_info=True)
1121 bottle.abort(HTTP_Internal_Server_Error, type(e).__name__ + ": " + str(e))
1122
1123
1124 @bottle.route(url_base + '/<tenant_id>/vnfs', method='GET')
1125 def http_get_vnfs(tenant_id):
1126 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
1127 try:
1128 if tenant_id != 'any':
1129 #check valid tenant_id
1130 nfvo.check_tenant(mydb, tenant_id)
1131 select_,where_,limit_ = filter_query_string(bottle.request.query, None,
1132 ('uuid', 'name', 'osm_id', 'description', 'public', "tenant_id", "created_at") )
1133 if tenant_id != "any":
1134 where_["OR"]={"tenant_id": tenant_id, "public": True}
1135 vnfs = mydb.get_rows(FROM='vnfs', SELECT=select_, WHERE=where_, LIMIT=limit_)
1136 # change_keys_http2db(content, http2db_vnf, reverse=True)
1137 utils.convert_str2boolean(vnfs, ('public',))
1138 convert_datetime2str(vnfs)
1139 data={'vnfs': vnfs}
1140 return format_out(data)
1141 except bottle.HTTPError:
1142 raise
1143 except (nfvo.NfvoException, db_base_Exception) as e:
1144 logger.error("http_get_vnfs error {}: {}".format(e.http_code, str(e)))
1145 bottle.abort(e.http_code, str(e))
1146 except Exception as e:
1147 logger.error("Unexpected exception: ", exc_info=True)
1148 bottle.abort(HTTP_Internal_Server_Error, type(e).__name__ + ": " + str(e))
1149
1150
1151 @bottle.route(url_base + '/<tenant_id>/vnfs/<vnf_id>', method='GET')
1152 def http_get_vnf_id(tenant_id,vnf_id):
1153 '''get vnf details, can use both uuid or name'''
1154 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
1155 try:
1156 vnf = nfvo.get_vnf_id(mydb,tenant_id,vnf_id)
1157 utils.convert_str2boolean(vnf, ('public',))
1158 convert_datetime2str(vnf)
1159 return format_out(vnf)
1160 except bottle.HTTPError:
1161 raise
1162 except (nfvo.NfvoException, db_base_Exception) as e:
1163 logger.error("http_get_vnf_id error {}: {}".format(e.http_code, str(e)))
1164 bottle.abort(e.http_code, str(e))
1165 except Exception as e:
1166 logger.error("Unexpected exception: ", exc_info=True)
1167 bottle.abort(HTTP_Internal_Server_Error, type(e).__name__ + ": " + str(e))
1168
1169
1170 @bottle.route(url_base + '/<tenant_id>/vnfs', method='POST')
1171 def http_post_vnfs(tenant_id):
1172 """ Insert a vnf into the catalogue. Creates the flavor and images, and fill the tables at database
1173 :param tenant_id: tenant that this vnf belongs to
1174 :return:
1175 """
1176 # print "Parsing the YAML file of the VNF"
1177 # parse input data
1178 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
1179 http_content, used_schema = format_in( vnfd_schema_v01, ("schema_version",), {"0.2": vnfd_schema_v02})
1180 r = utils.remove_extra_items(http_content, used_schema)
1181 if r:
1182 logger.debug("Remove received extra items %s", str(r))
1183 try:
1184 if used_schema == vnfd_schema_v01:
1185 vnf_id = nfvo.new_vnf(mydb,tenant_id,http_content)
1186 elif used_schema == vnfd_schema_v02:
1187 vnf_id = nfvo.new_vnf_v02(mydb,tenant_id,http_content)
1188 else:
1189 logger.warning('Unexpected schema_version: %s', http_content.get("schema_version"))
1190 bottle.abort(HTTP_Bad_Request, "Invalid schema version")
1191 return http_get_vnf_id(tenant_id, vnf_id)
1192 except bottle.HTTPError:
1193 raise
1194 except (nfvo.NfvoException, db_base_Exception) as e:
1195 logger.error("http_post_vnfs error {}: {}".format(e.http_code, str(e)))
1196 bottle.abort(e.http_code, str(e))
1197 except Exception as e:
1198 logger.error("Unexpected exception: ", exc_info=True)
1199 bottle.abort(HTTP_Internal_Server_Error, type(e).__name__ + ": " + str(e))
1200
1201
1202 @bottle.route(url_base + '/v3/<tenant_id>/vnfd', method='POST')
1203 def http_post_vnfs_v3(tenant_id):
1204 """
1205 Insert one or several VNFs in the catalog, following OSM IM
1206 :param tenant_id: tenant owner of the VNF
1207 :return: The detailed list of inserted VNFs, following the old format
1208 """
1209 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
1210 http_content, _ = format_in(None)
1211 try:
1212 vnfd_uuid_list = nfvo.new_vnfd_v3(mydb, tenant_id, http_content)
1213 vnfd_list = []
1214 for vnfd_uuid in vnfd_uuid_list:
1215 vnf = nfvo.get_vnf_id(mydb, tenant_id, vnfd_uuid)
1216 utils.convert_str2boolean(vnf, ('public',))
1217 convert_datetime2str(vnf)
1218 vnfd_list.append(vnf["vnf"])
1219 return format_out({"vnfd": vnfd_list})
1220 except bottle.HTTPError:
1221 raise
1222 except (nfvo.NfvoException, db_base_Exception) as e:
1223 logger.error("http_post_vnfs error {}: {}".format(e.http_code, str(e)))
1224 bottle.abort(e.http_code, str(e))
1225 except Exception as e:
1226 logger.error("Unexpected exception: ", exc_info=True)
1227 bottle.abort(HTTP_Internal_Server_Error, type(e).__name__ + ": " + str(e))
1228
1229 @bottle.route(url_base + '/<tenant_id>/vnfs/<vnf_id>', method='DELETE')
1230 def http_delete_vnf_id(tenant_id, vnf_id):
1231 '''delete a vnf from database, and images and flavors in VIM when appropriate, can use both uuid or name'''
1232 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
1233 #check valid tenant_id and deletes the vnf, including images,
1234 try:
1235 data = nfvo.delete_vnf(mydb,tenant_id,vnf_id)
1236 #print json.dumps(data, indent=4)
1237 return format_out({"result":"VNF " + data + " deleted"})
1238 except bottle.HTTPError:
1239 raise
1240 except (nfvo.NfvoException, db_base_Exception) as e:
1241 logger.error("http_delete_vnf_id error {}: {}".format(e.http_code, str(e)))
1242 bottle.abort(e.http_code, str(e))
1243 except Exception as e:
1244 logger.error("Unexpected exception: ", exc_info=True)
1245 bottle.abort(HTTP_Internal_Server_Error, type(e).__name__ + ": " + str(e))
1246
1247
1248 #@bottle.route(url_base + '/<tenant_id>/hosts/topology', method='GET')
1249 #@bottle.route(url_base + '/<tenant_id>/physicalview/Madrid-Alcantara', method='GET')
1250 @bottle.route(url_base + '/<tenant_id>/physicalview/<datacenter>', method='GET')
1251 def http_get_hosts(tenant_id, datacenter):
1252 '''get the tidvim host hopology from the vim.'''
1253 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
1254 #print "http_get_hosts received by tenant " + tenant_id + ' datacenter ' + datacenter
1255 try:
1256 if datacenter == 'treeview':
1257 data = nfvo.get_hosts(mydb, tenant_id)
1258 else:
1259 #openmano-gui is using a hardcoded value for the datacenter
1260 result, data = nfvo.get_hosts_info(mydb, tenant_id) #, datacenter)
1261
1262 if result < 0:
1263 #print "http_get_hosts error %d %s" % (-result, data)
1264 bottle.abort(-result, data)
1265 else:
1266 convert_datetime2str(data)
1267 #print json.dumps(data, indent=4)
1268 return format_out(data)
1269 except bottle.HTTPError:
1270 raise
1271 except (nfvo.NfvoException, db_base_Exception) as e:
1272 logger.error("http_get_hosts error {}: {}".format(e.http_code, str(e)))
1273 bottle.abort(e.http_code, str(e))
1274 except Exception as e:
1275 logger.error("Unexpected exception: ", exc_info=True)
1276 bottle.abort(HTTP_Internal_Server_Error, type(e).__name__ + ": " + str(e))
1277
1278
1279 @bottle.route(url_base + '/<path:path>', method='OPTIONS')
1280 def http_options_deploy(path):
1281 '''For some reason GUI web ask for OPTIONS that must be responded'''
1282 #TODO: check correct path, and correct headers request
1283 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
1284 bottle.response.set_header('Access-Control-Allow-Methods','POST, GET, PUT, DELETE, OPTIONS')
1285 bottle.response.set_header('Accept','application/yaml,application/json')
1286 bottle.response.set_header('Content-Type','application/yaml,application/json')
1287 bottle.response.set_header('Access-Control-Allow-Headers','content-type')
1288 bottle.response.set_header('Access-Control-Allow-Origin','*')
1289 return
1290
1291 @bottle.route(url_base + '/<tenant_id>/topology/deploy', method='POST')
1292 def http_post_deploy(tenant_id):
1293 '''post topology deploy.'''
1294 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
1295
1296 http_content, used_schema = format_in( nsd_schema_v01, ("schema_version",), {2: nsd_schema_v02})
1297 #r = utils.remove_extra_items(http_content, used_schema)
1298 #if r is not None: print "http_post_deploy: Warning: remove extra items ", r
1299 #print "http_post_deploy input: ", http_content
1300
1301 try:
1302 scenario_id = nfvo.new_scenario(mydb, tenant_id, http_content)
1303 instance = nfvo.start_scenario(mydb, tenant_id, scenario_id, http_content['name'], http_content['name'])
1304 #print json.dumps(data, indent=4)
1305 return format_out(instance)
1306 except bottle.HTTPError:
1307 raise
1308 except (nfvo.NfvoException, db_base_Exception) as e:
1309 logger.error("http_post_deploy error {}: {}".format(e.http_code, str(e)))
1310 bottle.abort(e.http_code, str(e))
1311 except Exception as e:
1312 logger.error("Unexpected exception: ", exc_info=True)
1313 bottle.abort(HTTP_Internal_Server_Error, type(e).__name__ + ": " + str(e))
1314
1315
1316 @bottle.route(url_base + '/<tenant_id>/topology/verify', method='POST')
1317 def http_post_verify(tenant_id):
1318 #TODO:
1319 # '''post topology verify'''
1320 # print "http_post_verify by tenant " + tenant_id + ' datacenter ' + datacenter
1321 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
1322 return
1323
1324 #
1325 # SCENARIOS
1326 #
1327
1328 @bottle.route(url_base + '/<tenant_id>/scenarios', method='POST')
1329 def http_post_scenarios(tenant_id):
1330 '''add a scenario into the catalogue. Creates the scenario and its internal structure in the OPENMANO DB'''
1331 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
1332 http_content, used_schema = format_in( nsd_schema_v01, ("schema_version",), {2: nsd_schema_v02, "0.3": nsd_schema_v03})
1333 #r = utils.remove_extra_items(http_content, used_schema)
1334 #if r is not None: print "http_post_scenarios: Warning: remove extra items ", r
1335 #print "http_post_scenarios input: ", http_content
1336 try:
1337 if used_schema == nsd_schema_v01:
1338 scenario_id = nfvo.new_scenario(mydb, tenant_id, http_content)
1339 elif used_schema == nsd_schema_v02:
1340 scenario_id = nfvo.new_scenario_v02(mydb, tenant_id, http_content, "0.2")
1341 elif used_schema == nsd_schema_v03:
1342 scenario_id = nfvo.new_scenario_v02(mydb, tenant_id, http_content, "0.3")
1343 else:
1344 logger.warning('Unexpected schema_version: %s', http_content.get("schema_version"))
1345 bottle.abort(HTTP_Bad_Request, "Invalid schema version")
1346 #print json.dumps(data, indent=4)
1347 #return format_out(data)
1348 return http_get_scenario_id(tenant_id, scenario_id)
1349 except bottle.HTTPError:
1350 raise
1351 except (nfvo.NfvoException, db_base_Exception) as e:
1352 logger.error("http_post_scenarios error {}: {}".format(e.http_code, str(e)))
1353 bottle.abort(e.http_code, str(e))
1354 except Exception as e:
1355 logger.error("Unexpected exception: ", exc_info=True)
1356 bottle.abort(HTTP_Internal_Server_Error, type(e).__name__ + ": " + str(e))
1357
1358 @bottle.route(url_base + '/v3/<tenant_id>/nsd', method='POST')
1359 def http_post_nsds_v3(tenant_id):
1360 """
1361 Insert one or several NSDs in the catalog, following OSM IM
1362 :param tenant_id: tenant owner of the NSD
1363 :return: The detailed list of inserted NSDs, following the old format
1364 """
1365 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
1366 http_content, _ = format_in(None)
1367 try:
1368 nsd_uuid_list = nfvo.new_nsd_v3(mydb, tenant_id, http_content)
1369 nsd_list = []
1370 for nsd_uuid in nsd_uuid_list:
1371 scenario = mydb.get_scenario(nsd_uuid, tenant_id)
1372 convert_datetime2str(scenario)
1373 nsd_list.append(scenario)
1374 data = {'nsd': nsd_list}
1375 return format_out(data)
1376 except bottle.HTTPError:
1377 raise
1378 except (nfvo.NfvoException, db_base_Exception) as e:
1379 logger.error("http_post_nsds_v3 error {}: {}".format(e.http_code, str(e)))
1380 bottle.abort(e.http_code, str(e))
1381 except Exception as e:
1382 logger.error("Unexpected exception: ", exc_info=True)
1383 bottle.abort(HTTP_Internal_Server_Error, type(e).__name__ + ": " + str(e))
1384
1385
1386 @bottle.route(url_base + '/<tenant_id>/scenarios/<scenario_id>/action', method='POST')
1387 def http_post_scenario_action(tenant_id, scenario_id):
1388 '''take an action over a scenario'''
1389 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
1390 # parse input data
1391 http_content, _ = format_in(scenario_action_schema)
1392 r = utils.remove_extra_items(http_content, scenario_action_schema)
1393 if r:
1394 logger.debug("Remove received extra items %s", str(r))
1395 try:
1396 # check valid tenant_id
1397 nfvo.check_tenant(mydb, tenant_id)
1398 if "start" in http_content:
1399 data = nfvo.start_scenario(mydb, tenant_id, scenario_id, http_content['start']['instance_name'], \
1400 http_content['start'].get('description',http_content['start']['instance_name']),
1401 http_content['start'].get('datacenter') )
1402 return format_out(data)
1403 elif "deploy" in http_content: #Equivalent to start
1404 data = nfvo.start_scenario(mydb, tenant_id, scenario_id, http_content['deploy']['instance_name'],
1405 http_content['deploy'].get('description',http_content['deploy']['instance_name']),
1406 http_content['deploy'].get('datacenter') )
1407 return format_out(data)
1408 elif "reserve" in http_content: #Reserve resources
1409 data = nfvo.start_scenario(mydb, tenant_id, scenario_id, http_content['reserve']['instance_name'],
1410 http_content['reserve'].get('description',http_content['reserve']['instance_name']),
1411 http_content['reserve'].get('datacenter'), startvms=False )
1412 return format_out(data)
1413 elif "verify" in http_content: #Equivalent to start and then delete
1414 data = nfvo.start_scenario(mydb, tenant_id, scenario_id, http_content['verify']['instance_name'],
1415 http_content['verify'].get('description',http_content['verify']['instance_name']),
1416 http_content['verify'].get('datacenter'), startvms=False )
1417 instance_id = data['uuid']
1418 nfvo.delete_instance(mydb, tenant_id,instance_id)
1419 return format_out({"result":"Verify OK"})
1420 except bottle.HTTPError:
1421 raise
1422 except (nfvo.NfvoException, db_base_Exception) as e:
1423 logger.error("http_post_scenario_action error {}: {}".format(e.http_code, str(e)))
1424 bottle.abort(e.http_code, str(e))
1425 except Exception as e:
1426 logger.error("Unexpected exception: ", exc_info=True)
1427 bottle.abort(HTTP_Internal_Server_Error, type(e).__name__ + ": " + str(e))
1428
1429
1430 @bottle.route(url_base + '/<tenant_id>/scenarios', method='GET')
1431 def http_get_scenarios(tenant_id):
1432 '''get scenarios list'''
1433 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
1434 try:
1435 #check valid tenant_id
1436 if tenant_id != "any":
1437 nfvo.check_tenant(mydb, tenant_id)
1438 #obtain data
1439 s,w,l=filter_query_string(bottle.request.query, None,
1440 ('uuid', 'name', 'osm_id', 'description', 'tenant_id', 'created_at', 'public'))
1441 if tenant_id != "any":
1442 w["OR"] = {"tenant_id": tenant_id, "public": True}
1443 scenarios = mydb.get_rows(SELECT=s, WHERE=w, LIMIT=l, FROM='scenarios')
1444 convert_datetime2str(scenarios)
1445 utils.convert_str2boolean(scenarios, ('public',) )
1446 data={'scenarios':scenarios}
1447 #print json.dumps(scenarios, indent=4)
1448 return format_out(data)
1449 except bottle.HTTPError:
1450 raise
1451 except (nfvo.NfvoException, db_base_Exception) as e:
1452 logger.error("http_get_scenarios error {}: {}".format(e.http_code, str(e)))
1453 bottle.abort(e.http_code, str(e))
1454 except Exception as e:
1455 logger.error("Unexpected exception: ", exc_info=True)
1456 bottle.abort(HTTP_Internal_Server_Error, type(e).__name__ + ": " + str(e))
1457
1458
1459 @bottle.route(url_base + '/<tenant_id>/scenarios/<scenario_id>', method='GET')
1460 def http_get_scenario_id(tenant_id, scenario_id):
1461 '''get scenario details, can use both uuid or name'''
1462 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
1463 try:
1464 #check valid tenant_id
1465 if tenant_id != "any":
1466 nfvo.check_tenant(mydb, tenant_id)
1467 #obtain data
1468 scenario = mydb.get_scenario(scenario_id, tenant_id)
1469 convert_datetime2str(scenario)
1470 data={'scenario' : scenario}
1471 return format_out(data)
1472 except bottle.HTTPError:
1473 raise
1474 except (nfvo.NfvoException, db_base_Exception) as e:
1475 logger.error("http_get_scenarios error {}: {}".format(e.http_code, str(e)))
1476 bottle.abort(e.http_code, str(e))
1477 except Exception as e:
1478 logger.error("Unexpected exception: ", exc_info=True)
1479 bottle.abort(HTTP_Internal_Server_Error, type(e).__name__ + ": " + str(e))
1480
1481
1482 @bottle.route(url_base + '/<tenant_id>/scenarios/<scenario_id>', method='DELETE')
1483 def http_delete_scenario_id(tenant_id, scenario_id):
1484 '''delete a scenario from database, can use both uuid or name'''
1485 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
1486 try:
1487 #check valid tenant_id
1488 if tenant_id != "any":
1489 nfvo.check_tenant(mydb, tenant_id)
1490 #obtain data
1491 data = mydb.delete_scenario(scenario_id, tenant_id)
1492 #print json.dumps(data, indent=4)
1493 return format_out({"result":"scenario " + data + " deleted"})
1494 except bottle.HTTPError:
1495 raise
1496 except (nfvo.NfvoException, db_base_Exception) as e:
1497 logger.error("http_delete_scenario_id error {}: {}".format(e.http_code, str(e)))
1498 bottle.abort(e.http_code, str(e))
1499 except Exception as e:
1500 logger.error("Unexpected exception: ", exc_info=True)
1501 bottle.abort(HTTP_Internal_Server_Error, type(e).__name__ + ": " + str(e))
1502
1503
1504 @bottle.route(url_base + '/<tenant_id>/scenarios/<scenario_id>', method='PUT')
1505 def http_put_scenario_id(tenant_id, scenario_id):
1506 '''edit an existing scenario id'''
1507 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
1508 http_content,_ = format_in( scenario_edit_schema )
1509 #r = utils.remove_extra_items(http_content, scenario_edit_schema)
1510 #if r is not None: print "http_put_scenario_id: Warning: remove extra items ", r
1511 #print "http_put_scenario_id input: ", http_content
1512 try:
1513 nfvo.edit_scenario(mydb, tenant_id, scenario_id, http_content)
1514 #print json.dumps(data, indent=4)
1515 #return format_out(data)
1516 return http_get_scenario_id(tenant_id, scenario_id)
1517 except bottle.HTTPError:
1518 raise
1519 except (nfvo.NfvoException, db_base_Exception) as e:
1520 logger.error("http_put_scenario_id error {}: {}".format(e.http_code, str(e)))
1521 bottle.abort(e.http_code, str(e))
1522 except Exception as e:
1523 logger.error("Unexpected exception: ", exc_info=True)
1524 bottle.abort(HTTP_Internal_Server_Error, type(e).__name__ + ": " + str(e))
1525
1526 @bottle.route(url_base + '/<tenant_id>/instances', method='POST')
1527 def http_post_instances(tenant_id):
1528 '''create an instance-scenario'''
1529 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
1530 # parse input data
1531 http_content, used_schema = format_in(instance_scenario_create_schema_v01)
1532 r = utils.remove_extra_items(http_content, used_schema)
1533 if r is not None:
1534 logger.warning("http_post_instances: Warning: remove extra items %s", str(r))
1535 try:
1536 #check valid tenant_id
1537 if tenant_id != "any":
1538 nfvo.check_tenant(mydb, tenant_id)
1539 data = nfvo.create_instance(mydb, tenant_id, http_content["instance"])
1540 return format_out(data)
1541 except bottle.HTTPError:
1542 raise
1543 except (nfvo.NfvoException, db_base_Exception) as e:
1544 logger.error("http_post_instances error {}: {}".format(e.http_code, str(e)))
1545 bottle.abort(e.http_code, str(e))
1546 except Exception as e:
1547 logger.error("Unexpected exception: ", exc_info=True)
1548 bottle.abort(HTTP_Internal_Server_Error, type(e).__name__ + ": " + str(e))
1549
1550 #
1551 # INSTANCES
1552 #
1553 @bottle.route(url_base + '/<tenant_id>/instances', method='GET')
1554 def http_get_instances(tenant_id):
1555 '''get instance list'''
1556 try:
1557 #check valid tenant_id
1558 if tenant_id != "any":
1559 nfvo.check_tenant(mydb, tenant_id)
1560 #obtain data
1561 s,w,l=filter_query_string(bottle.request.query, None, ('uuid', 'name', 'scenario_id', 'tenant_id', 'description', 'created_at'))
1562 if tenant_id != "any":
1563 w['tenant_id'] = tenant_id
1564 instances = mydb.get_rows(SELECT=s, WHERE=w, LIMIT=l, FROM='instance_scenarios')
1565 convert_datetime2str(instances)
1566 utils.convert_str2boolean(instances, ('public',) )
1567 data={'instances':instances}
1568 return format_out(data)
1569 except bottle.HTTPError:
1570 raise
1571 except (nfvo.NfvoException, db_base_Exception) as e:
1572 logger.error("http_get_instances error {}: {}".format(e.http_code, str(e)))
1573 bottle.abort(e.http_code, str(e))
1574 except Exception as e:
1575 logger.error("Unexpected exception: ", exc_info=True)
1576 bottle.abort(HTTP_Internal_Server_Error, type(e).__name__ + ": " + str(e))
1577
1578
1579 @bottle.route(url_base + '/<tenant_id>/instances/<instance_id>', method='GET')
1580 def http_get_instance_id(tenant_id, instance_id):
1581 '''get instances details, can use both uuid or name'''
1582 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
1583 try:
1584
1585 #check valid tenant_id
1586 if tenant_id != "any":
1587 nfvo.check_tenant(mydb, tenant_id)
1588 if tenant_id == "any":
1589 tenant_id = None
1590
1591 instance = nfvo.get_instance_id(mydb, tenant_id, instance_id)
1592
1593 # Workaround to SO, convert vnfs:vms:interfaces:ip_address from ";" separated list to report the first value
1594 for vnf in instance.get("vnfs", ()):
1595 for vm in vnf.get("vms", ()):
1596 for iface in vm.get("interfaces", ()):
1597 if iface.get("ip_address"):
1598 index = iface["ip_address"].find(";")
1599 if index >= 0:
1600 iface["ip_address"] = iface["ip_address"][:index]
1601 convert_datetime2str(instance)
1602 # print json.dumps(instance, indent=4)
1603 return format_out(instance)
1604 except bottle.HTTPError:
1605 raise
1606 except (nfvo.NfvoException, db_base_Exception) as e:
1607 logger.error("http_get_instance_id error {}: {}".format(e.http_code, str(e)))
1608 bottle.abort(e.http_code, str(e))
1609 except Exception as e:
1610 logger.error("Unexpected exception: ", exc_info=True)
1611 bottle.abort(HTTP_Internal_Server_Error, type(e).__name__ + ": " + str(e))
1612
1613
1614 @bottle.route(url_base + '/<tenant_id>/instances/<instance_id>', method='DELETE')
1615 def http_delete_instance_id(tenant_id, instance_id):
1616 '''delete instance from VIM and from database, can use both uuid or name'''
1617 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
1618 try:
1619 #check valid tenant_id
1620 if tenant_id != "any":
1621 nfvo.check_tenant(mydb, tenant_id)
1622 if tenant_id == "any":
1623 tenant_id = None
1624 #obtain data
1625 message = nfvo.delete_instance(mydb, tenant_id,instance_id)
1626 return format_out({"result":message})
1627 except bottle.HTTPError:
1628 raise
1629 except (nfvo.NfvoException, db_base_Exception) as e:
1630 logger.error("http_delete_instance_id error {}: {}".format(e.http_code, str(e)))
1631 bottle.abort(e.http_code, str(e))
1632 except Exception as e:
1633 logger.error("Unexpected exception: ", exc_info=True)
1634 bottle.abort(HTTP_Internal_Server_Error, type(e).__name__ + ": " + str(e))
1635
1636
1637 @bottle.route(url_base + '/<tenant_id>/instances/<instance_id>/action', method='POST')
1638 def http_post_instance_scenario_action(tenant_id, instance_id):
1639 """
1640 take an action over a scenario instance
1641 :param tenant_id: tenant where user belongs to
1642 :param instance_id: instance indentity
1643 :return:
1644 """
1645 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
1646 # parse input data
1647 http_content, _ = format_in(instance_scenario_action_schema)
1648 r = utils.remove_extra_items(http_content, instance_scenario_action_schema)
1649 if r:
1650 logger.debug("Remove received extra items %s", str(r))
1651 try:
1652 #check valid tenant_id
1653 if tenant_id != "any":
1654 nfvo.check_tenant(mydb, tenant_id)
1655
1656 #print "http_post_instance_scenario_action input: ", http_content
1657 #obtain data
1658 instance = mydb.get_instance_scenario(instance_id, tenant_id)
1659 instance_id = instance["uuid"]
1660
1661 data = nfvo.instance_action(mydb, tenant_id, instance_id, http_content)
1662 return format_out(data)
1663 except bottle.HTTPError:
1664 raise
1665 except (nfvo.NfvoException, db_base_Exception) as e:
1666 logger.error("http_post_instance_scenario_action error {}: {}".format(e.http_code, str(e)))
1667 bottle.abort(e.http_code, str(e))
1668 except Exception as e:
1669 logger.error("Unexpected exception: ", exc_info=True)
1670 bottle.abort(HTTP_Internal_Server_Error, type(e).__name__ + ": " + str(e))
1671
1672
1673 @bottle.route(url_base + '/<tenant_id>/instances/<instance_id>/action', method='GET')
1674 @bottle.route(url_base + '/<tenant_id>/instances/<instance_id>/action/<action_id>', method='GET')
1675 def http_get_instance_scenario_action(tenant_id, instance_id, action_id=None):
1676 """
1677 List the actions done over an instance, or the action details
1678 :param tenant_id: tenant where user belongs to. Can be "any" to ignore
1679 :param instance_id: instance id, can be "any" to get actions of all instances
1680 :return:
1681 """
1682 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
1683 try:
1684 # check valid tenant_id
1685 if tenant_id != "any":
1686 nfvo.check_tenant(mydb, tenant_id)
1687 data = nfvo.instance_action_get(mydb, tenant_id, instance_id, action_id)
1688 return format_out(data)
1689 except bottle.HTTPError:
1690 raise
1691 except (nfvo.NfvoException, db_base_Exception) as e:
1692 logger.error("http_get_instance_scenario_action error {}: {}".format(e.http_code, str(e)))
1693 bottle.abort(e.http_code, str(e))
1694 except Exception as e:
1695 logger.error("Unexpected exception: ", exc_info=True)
1696 bottle.abort(HTTP_Internal_Server_Error, type(e).__name__ + ": " + str(e))
1697
1698 def remove_clear_passwd(data):
1699 """
1700 Removes clear passwords from the data received
1701 :param data: data with clear password
1702 :return: data without the password information
1703 """
1704
1705 passw = ['password: ', 'passwd: ']
1706
1707 for pattern in passw:
1708 init = data.find(pattern)
1709 while init != -1:
1710 end = data.find('\n', init)
1711 data = data[:init] + '{}******'.format(pattern) + data[end:]
1712 init += 1
1713 init = data.find(pattern, init)
1714 return data
1715
1716 @bottle.error(400)
1717 @bottle.error(401)
1718 @bottle.error(404)
1719 @bottle.error(403)
1720 @bottle.error(405)
1721 @bottle.error(406)
1722 @bottle.error(409)
1723 @bottle.error(503)
1724 @bottle.error(500)
1725 def error400(error):
1726 e={"error":{"code":error.status_code, "type":error.status, "description":error.body}}
1727 bottle.response.headers['Access-Control-Allow-Origin'] = '*'
1728 return format_out(e)
1729