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