v0.4.52 fixes logging name mismatching. Add individual log files per module
[osm/RO.git] / 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
46 import nfvo
47 import utils
48 from db_base import db_base_Exception
49 from functools import wraps
50
51 global mydb
52 global url_base
53 global logger
54 url_base="/openmano"
55 logger = None
56
57 HTTP_Bad_Request = 400
58 HTTP_Unauthorized = 401
59 HTTP_Not_Found = 404
60 HTTP_Forbidden = 403
61 HTTP_Method_Not_Allowed = 405
62 HTTP_Not_Acceptable = 406
63 HTTP_Service_Unavailable = 503
64 HTTP_Internal_Server_Error= 500
65
66 def delete_nulls(var):
67 if type(var) is dict:
68 for k in var.keys():
69 if var[k] is None: del var[k]
70 elif type(var[k]) is dict or type(var[k]) is list or type(var[k]) is tuple:
71 if delete_nulls(var[k]): del var[k]
72 if len(var) == 0: return True
73 elif type(var) is list or type(var) is tuple:
74 for k in var:
75 if type(k) is dict: delete_nulls(k)
76 if len(var) == 0: return True
77 return False
78
79 def convert_datetime2str(var):
80 '''Converts a datetime variable to a string with the format '%Y-%m-%dT%H:%i:%s'
81 It enters recursively in the dict var finding this kind of variables
82 '''
83 if type(var) is dict:
84 for k,v in var.items():
85 if type(v) is float and k in ("created_at", "modified_at"):
86 var[k] = time.strftime("%Y-%m-%dT%H:%M:%S", time.localtime(v) )
87 elif type(v) is dict or type(v) is list or type(v) is tuple:
88 convert_datetime2str(v)
89 if len(var) == 0: return True
90 elif type(var) is list or type(var) is tuple:
91 for v in var:
92 convert_datetime2str(v)
93
94 def log_to_logger(fn):
95 '''
96 Wrap a Bottle request so that a log line is emitted after it's handled.
97 (This decorator can be extended to take the desired logger as a param.)
98 '''
99 @wraps(fn)
100 def _log_to_logger(*args, **kwargs):
101 actual_response = fn(*args, **kwargs)
102 # modify this to log exactly what you need:
103 logger.info('FROM %s %s %s %s' % (bottle.request.remote_addr,
104 bottle.request.method,
105 bottle.request.url,
106 bottle.response.status))
107 return actual_response
108 return _log_to_logger
109
110 class httpserver(threading.Thread):
111 def __init__(self, db, admin=False, host='localhost', port=9090):
112 #global url_base
113 global mydb
114 global logger
115 #initialization
116 if not logger:
117 logger = logging.getLogger('openmano.http')
118 threading.Thread.__init__(self)
119 self.host = host
120 self.port = port #Port where the listen service must be started
121 if admin==True:
122 self.name = "http_admin"
123 else:
124 self.name = "http"
125 #self.url_preffix = 'http://' + host + ':' + str(port) + url_base
126 mydb = db
127 #self.first_usable_connection_index = 10
128 #self.next_connection_index = self.first_usable_connection_index #The next connection index to be used
129 #Ensure that when the main program exits the thread will also exit
130 self.daemon = True
131 self.setDaemon(True)
132
133 def run(self):
134 bottle.install(log_to_logger)
135 bottle.run(host=self.host, port=self.port, debug=False, quiet=True)
136
137 def run_bottle(db, host_='localhost', port_=9090):
138 '''used for launching in main thread, so that it can be debugged'''
139 global mydb
140 mydb = db
141 bottle.run(host=host_, port=port_, debug=True) #quiet=True
142
143
144 @bottle.route(url_base + '/', method='GET')
145 def http_get():
146 print
147 return 'works' #TODO: to be completed
148
149 #
150 # Util functions
151 #
152
153 def change_keys_http2db(data, http_db, reverse=False):
154 '''Change keys of dictionary data acording to the key_dict values
155 This allow change from http interface names to database names.
156 When reverse is True, the change is otherwise
157 Attributes:
158 data: can be a dictionary or a list
159 http_db: is a dictionary with hhtp names as keys and database names as value
160 reverse: by default change is done from http api to database. If True change is done otherwise
161 Return: None, but data is modified'''
162 if type(data) is tuple or type(data) is list:
163 for d in data:
164 change_keys_http2db(d, http_db, reverse)
165 elif type(data) is dict or type(data) is bottle.FormsDict:
166 if reverse:
167 for k,v in http_db.items():
168 if v in data: data[k]=data.pop(v)
169 else:
170 for k,v in http_db.items():
171 if k in data: data[v]=data.pop(k)
172
173 def format_out(data):
174 '''return string of dictionary data according to requested json, yaml, xml. By default json'''
175 logger.debug(yaml.safe_dump(data, explicit_start=True, indent=4, default_flow_style=False, tags=False, encoding='utf-8', allow_unicode=True) )
176 if 'application/yaml' in bottle.request.headers.get('Accept'):
177 bottle.response.content_type='application/yaml'
178 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='"'
179 else: #by default json
180 bottle.response.content_type='application/json'
181 #return data #json no style
182 return json.dumps(data, indent=4) + "\n"
183
184 def format_in(default_schema, version_fields=None, version_dict_schema=None):
185 ''' Parse the content of HTTP request against a json_schema
186 Parameters
187 default_schema: The schema to be parsed by default if no version field is found in the client data
188 version_fields: If provided it contains a tuple or list with the fields to iterate across the client data to obtain the version
189 version_dict_schema: It contains a dictionary with the version as key, and json schema to apply as value
190 It can contain a None as key, and this is apply if the client data version does not match any key
191 Return:
192 user_data, used_schema: if the data is successfully decoded and matches the schema
193 launch a bottle abort if fails
194 '''
195 #print "HEADERS :" + str(bottle.request.headers.items())
196 try:
197 error_text = "Invalid header format "
198 format_type = bottle.request.headers.get('Content-Type', 'application/json')
199 if 'application/json' in format_type:
200 error_text = "Invalid json format "
201 #Use the json decoder instead of bottle decoder because it informs about the location of error formats with a ValueError exception
202 client_data = json.load(bottle.request.body)
203 #client_data = bottle.request.json()
204 elif 'application/yaml' in format_type:
205 error_text = "Invalid yaml format "
206 client_data = yaml.load(bottle.request.body)
207 elif 'application/xml' in format_type:
208 bottle.abort(501, "Content-Type: application/xml not supported yet.")
209 else:
210 print 'Content-Type ' + str(format_type) + ' not supported.'
211 bottle.abort(HTTP_Not_Acceptable, 'Content-Type ' + str(format_type) + ' not supported.')
212 return
213 #if client_data == None:
214 # bottle.abort(HTTP_Bad_Request, "Content error, empty")
215 # return
216
217 #logger.debug('client-data: %s', client_data)
218 #look for the client provider version
219 error_text = "Invalid content "
220 client_version = None
221 used_schema = None
222 if version_fields != None:
223 client_version = client_data
224 for field in version_fields:
225 if field in client_version:
226 client_version = client_version[field]
227 else:
228 client_version=None
229 break
230 if client_version==None:
231 used_schema=default_schema
232 elif version_dict_schema!=None:
233 if client_version in version_dict_schema:
234 used_schema = version_dict_schema[client_version]
235 elif None in version_dict_schema:
236 used_schema = version_dict_schema[None]
237 if used_schema==None:
238 bottle.abort(HTTP_Bad_Request, "Invalid schema version or missing version field")
239
240 js_v(client_data, used_schema)
241 return client_data, used_schema
242 except (ValueError, yaml.YAMLError) as exc:
243 error_text += str(exc)
244 print error_text
245 bottle.abort(HTTP_Bad_Request, error_text)
246 except js_e.ValidationError as exc:
247 print "validate_in error, jsonschema exception ", exc.message, "at", exc.path
248 error_pos = ""
249 if len(exc.path)>0: error_pos=" at " + ":".join(map(json.dumps, exc.path))
250 bottle.abort(HTTP_Bad_Request, error_text + exc.message + error_pos)
251 #except:
252 # bottle.abort(HTTP_Bad_Request, "Content error: Failed to parse Content-Type", error_pos)
253 # raise
254
255 def filter_query_string(qs, http2db, allowed):
256 '''Process query string (qs) checking that contains only valid tokens for avoiding SQL injection
257 Attributes:
258 'qs': bottle.FormsDict variable to be processed. None or empty is considered valid
259 'http2db': dictionary with change from http API naming (dictionary key) to database naming(dictionary value)
260 'allowed': list of allowed string tokens (API http naming). All the keys of 'qs' must be one of 'allowed'
261 Return: A tuple with the (select,where,limit) to be use in a database query. All of then transformed to the database naming
262 select: list of items to retrieve, filtered by query string 'field=token'. If no 'field' is present, allowed list is returned
263 where: dictionary with key, value, taken from the query string token=value. Empty if nothing is provided
264 limit: limit dictated by user with the query string 'limit'. 100 by default
265 abort if not permited, using bottel.abort
266 '''
267 where={}
268 limit=100
269 select=[]
270 #if type(qs) is not bottle.FormsDict:
271 # bottle.abort(HTTP_Internal_Server_Error, '!!!!!!!!!!!!!!invalid query string not a dictionary')
272 # #bottle.abort(HTTP_Internal_Server_Error, "call programmer")
273 for k in qs:
274 if k=='field':
275 select += qs.getall(k)
276 for v in select:
277 if v not in allowed:
278 bottle.abort(HTTP_Bad_Request, "Invalid query string at 'field="+v+"'")
279 elif k=='limit':
280 try:
281 limit=int(qs[k])
282 except:
283 bottle.abort(HTTP_Bad_Request, "Invalid query string at 'limit="+qs[k]+"'")
284 else:
285 if k not in allowed:
286 bottle.abort(HTTP_Bad_Request, "Invalid query string at '"+k+"="+qs[k]+"'")
287 if qs[k]!="null": where[k]=qs[k]
288 else: where[k]=None
289 if len(select)==0: select += allowed
290 #change from http api to database naming
291 for i in range(0,len(select)):
292 k=select[i]
293 if http2db and k in http2db:
294 select[i] = http2db[k]
295 if http2db:
296 change_keys_http2db(where, http2db)
297 #print "filter_query_string", select,where,limit
298
299 return select,where,limit
300
301 @bottle.hook('after_request')
302 def enable_cors():
303 '''Don't know yet if really needed. Keep it just in case'''
304 bottle.response.headers['Access-Control-Allow-Origin'] = '*'
305
306 #
307 # VNFs
308 #
309
310 @bottle.route(url_base + '/tenants', method='GET')
311 def http_get_tenants():
312 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
313 select_,where_,limit_ = filter_query_string(bottle.request.query, None,
314 ('uuid','name','description','created_at') )
315 try:
316 tenants = mydb.get_rows(FROM='nfvo_tenants', SELECT=select_,WHERE=where_,LIMIT=limit_)
317 #change_keys_http2db(content, http2db_tenant, reverse=True)
318 convert_datetime2str(tenants)
319 data={'tenants' : tenants}
320 return format_out(data)
321 except db_base_Exception as e:
322 logger.error("http_get_tenants error {}: {}".format(e.http_code, str(e)))
323 bottle.abort(e.http_code, str(e))
324
325 @bottle.route(url_base + '/tenants/<tenant_id>', method='GET')
326 def http_get_tenant_id(tenant_id):
327 '''get tenant details, can use both uuid or name'''
328 #obtain data
329 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
330 try:
331 tenant = mydb.get_table_by_uuid_name('nfvo_tenants', tenant_id, "tenant")
332 #change_keys_http2db(content, http2db_tenant, reverse=True)
333 convert_datetime2str(tenant)
334 data={'tenant' : tenant}
335 return format_out(data)
336 except db_base_Exception as e:
337 logger.error("http_get_tenant_id error {}: {}".format(e.http_code, str(e)))
338 bottle.abort(e.http_code, str(e))
339
340 @bottle.route(url_base + '/tenants', method='POST')
341 def http_post_tenants():
342 '''insert a tenant into the catalogue. '''
343 #parse input data
344 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
345 http_content,_ = format_in( tenant_schema )
346 r = utils.remove_extra_items(http_content, tenant_schema)
347 if r is not None: print "http_post_tenants: Warning: remove extra items ", r
348 try:
349 data = nfvo.new_tenant(mydb, http_content['tenant'])
350 return http_get_tenant_id(data)
351 except (nfvo.NfvoException, db_base_Exception) as e:
352 logger.error("http_post_tenants error {}: {}".format(e.http_code, str(e)))
353 bottle.abort(e.http_code, str(e))
354
355 @bottle.route(url_base + '/tenants/<tenant_id>', method='PUT')
356 def http_edit_tenant_id(tenant_id):
357 '''edit tenant details, can use both uuid or name'''
358 #parse input data
359 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
360 http_content,_ = format_in( tenant_edit_schema )
361 r = utils.remove_extra_items(http_content, tenant_edit_schema)
362 if r is not None: print "http_edit_tenant_id: Warning: remove extra items ", r
363
364 #obtain data, check that only one exist
365 try:
366 tenant = mydb.get_table_by_uuid_name('nfvo_tenants', tenant_id)
367 #edit data
368 tenant_id = tenant['uuid']
369 where={'uuid': tenant['uuid']}
370 mydb.update_rows('nfvo_tenants', http_content['tenant'], where)
371 return http_get_tenant_id(tenant_id)
372 except db_base_Exception as e:
373 logger.error("http_edit_tenant_id error {}: {}".format(e.http_code, str(e)))
374 bottle.abort(e.http_code, str(e))
375
376 @bottle.route(url_base + '/tenants/<tenant_id>', method='DELETE')
377 def http_delete_tenant_id(tenant_id):
378 '''delete a tenant from database, can use both uuid or name'''
379 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
380 try:
381 data = nfvo.delete_tenant(mydb, tenant_id)
382 return format_out({"result":"tenant " + data + " deleted"})
383 except db_base_Exception as e:
384 logger.error("http_delete_tenant_id error {}: {}".format(e.http_code, str(e)))
385 bottle.abort(e.http_code, str(e))
386
387
388 @bottle.route(url_base + '/<tenant_id>/datacenters', method='GET')
389 def http_get_datacenters(tenant_id):
390 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
391 try:
392 if tenant_id != 'any':
393 #check valid tenant_id
394 nfvo.check_tenant(mydb, tenant_id)
395 select_,where_,limit_ = filter_query_string(bottle.request.query, None,
396 ('uuid','name','vim_url','type','created_at') )
397 if tenant_id != 'any':
398 where_['nfvo_tenant_id'] = tenant_id
399 if 'created_at' in select_:
400 select_[ select_.index('created_at') ] = 'd.created_at as created_at'
401 if 'created_at' in where_:
402 where_['d.created_at'] = where_.pop('created_at')
403 datacenters = mydb.get_rows(FROM='datacenters as d join tenants_datacenters as td on d.uuid=td.datacenter_id',
404 SELECT=select_,WHERE=where_,LIMIT=limit_)
405 else:
406 datacenters = mydb.get_rows(FROM='datacenters',
407 SELECT=select_,WHERE=where_,LIMIT=limit_)
408 #change_keys_http2db(content, http2db_tenant, reverse=True)
409 convert_datetime2str(datacenters)
410 data={'datacenters' : datacenters}
411 return format_out(data)
412 except (nfvo.NfvoException, db_base_Exception) as e:
413 logger.error("http_get_datacenters error {}: {}".format(e.http_code, str(e)))
414 bottle.abort(e.http_code, str(e))
415
416 @bottle.route(url_base + '/<tenant_id>/datacenters/<datacenter_id>', method='GET')
417 def http_get_datacenter_id(tenant_id, datacenter_id):
418 '''get datacenter details, can use both uuid or name'''
419 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
420 try:
421 if tenant_id != 'any':
422 #check valid tenant_id
423 nfvo.check_tenant(mydb, tenant_id)
424 #obtain data
425 what = 'uuid' if utils.check_valid_uuid(datacenter_id) else 'name'
426 where_={}
427 where_[what] = datacenter_id
428 select_=['uuid', 'name','vim_url', 'vim_url_admin', 'type', 'config', 'description', 'd.created_at as created_at']
429 if tenant_id != 'any':
430 select_.append("datacenter_tenant_id")
431 where_['td.nfvo_tenant_id']= tenant_id
432 from_='datacenters as d join tenants_datacenters as td on d.uuid=td.datacenter_id'
433 else:
434 from_='datacenters as d'
435 datacenters = mydb.get_rows(
436 SELECT=select_,
437 FROM=from_,
438 WHERE=where_)
439
440 if len(datacenters)==0:
441 bottle.abort( HTTP_Not_Found, "No datacenter found for tenant with {} '{}'".format(what, datacenter_id) )
442 elif len(datacenters)>1:
443 bottle.abort( HTTP_Bad_Request, "More than one datacenter found for tenant with {} '{}'".format(what, datacenter_id) )
444 datacenter = datacenters[0]
445 if tenant_id != 'any':
446 #get vim tenant info
447 vim_tenants = mydb.get_rows(
448 SELECT=("vim_tenant_name", "vim_tenant_id", "user"),
449 FROM="datacenter_tenants",
450 WHERE={"uuid": datacenters[0]["datacenter_tenant_id"]},
451 ORDER_BY=("created", ) )
452 del datacenter["datacenter_tenant_id"]
453 datacenter["vim_tenants"] = vim_tenants
454
455 if datacenter['config'] != None:
456 try:
457 config_dict = yaml.load(datacenter['config'])
458 datacenter['config'] = config_dict
459 except Exception, e:
460 print "Exception '%s' while trying to load config information" % str(e)
461 #change_keys_http2db(content, http2db_datacenter, reverse=True)
462 convert_datetime2str(datacenter)
463 data={'datacenter' : datacenter}
464 return format_out(data)
465 except (nfvo.NfvoException, db_base_Exception) as e:
466 logger.error("http_get_datacenter_id error {}: {}".format(e.http_code, str(e)))
467 bottle.abort(e.http_code, str(e))
468
469 @bottle.route(url_base + '/datacenters', method='POST')
470 def http_post_datacenters():
471 '''insert a tenant into the catalogue. '''
472 #parse input data
473 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
474 http_content,_ = format_in( datacenter_schema )
475 r = utils.remove_extra_items(http_content, datacenter_schema)
476 if r is not None: print "http_post_datacenters: Warning: remove extra items ", r
477 try:
478 data = nfvo.new_datacenter(mydb, http_content['datacenter'])
479 return http_get_datacenter_id('any', data)
480 except (nfvo.NfvoException, db_base_Exception) as e:
481 logger.error("http_post_datacenters error {}: {}".format(e.http_code, str(e)))
482 bottle.abort(e.http_code, str(e))
483
484 @bottle.route(url_base + '/datacenters/<datacenter_id_name>', method='PUT')
485 def http_edit_datacenter_id(datacenter_id_name):
486 '''edit datacenter details, can use both uuid or name'''
487 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
488 #parse input data
489 http_content,_ = format_in( datacenter_edit_schema )
490 r = utils.remove_extra_items(http_content, datacenter_edit_schema)
491 if r is not None: print "http_edit_datacenter_id: Warning: remove extra items ", r
492
493 try:
494 datacenter_id = nfvo.edit_datacenter(mydb, datacenter_id_name, http_content['datacenter'])
495 return http_get_datacenter_id('any', datacenter_id)
496 except (nfvo.NfvoException, db_base_Exception) as e:
497 logger.error("http_edit_datacenter_id error {}: {}".format(e.http_code, str(e)))
498 bottle.abort(e.http_code, str(e))
499
500 @bottle.route(url_base + '/<tenant_id>/datacenters/<datacenter_id>/networks', method='GET') #deprecated
501 @bottle.route(url_base + '/<tenant_id>/datacenters/<datacenter_id>/netmaps', method='GET')
502 @bottle.route(url_base + '/<tenant_id>/datacenters/<datacenter_id>/netmaps/<netmap_id>', method='GET')
503 def http_getnetmap_datacenter_id(tenant_id, datacenter_id, netmap_id=None):
504 '''get datacenter networks, can use both uuid or name'''
505 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
506 #obtain data
507 try:
508 datacenter_dict = mydb.get_table_by_uuid_name('datacenters', datacenter_id, "datacenter")
509 where_= {"datacenter_id":datacenter_dict['uuid']}
510 if netmap_id:
511 if utils.check_valid_uuid(netmap_id):
512 where_["uuid"] = netmap_id
513 else:
514 where_["name"] = netmap_id
515 netmaps =mydb.get_rows(FROM='datacenter_nets',
516 SELECT=('name','vim_net_id as vim_id', 'uuid', 'type','multipoint','shared','description', 'created_at'),
517 WHERE=where_ )
518 convert_datetime2str(netmaps)
519 utils.convert_str2boolean(netmaps, ('shared', 'multipoint') )
520 if netmap_id and len(netmaps)==1:
521 data={'netmap' : netmaps[0]}
522 elif netmap_id and len(netmaps)==0:
523 bottle.abort(HTTP_Not_Found, "No netmap found with " + " and ".join(map(lambda x: str(x[0])+": "+str(x[1]), where_.iteritems())) )
524 return
525 else:
526 data={'netmaps' : netmaps}
527 return format_out(data)
528 except (nfvo.NfvoException, db_base_Exception) as e:
529 logger.error("http_getnetwork_datacenter_id error {}: {}".format(e.http_code, str(e)))
530 bottle.abort(e.http_code, str(e))
531
532 @bottle.route(url_base + '/<tenant_id>/datacenters/<datacenter_id>/netmaps', method='DELETE')
533 @bottle.route(url_base + '/<tenant_id>/datacenters/<datacenter_id>/netmaps/<netmap_id>', method='DELETE')
534 def http_delnetmap_datacenter_id(tenant_id, datacenter_id, netmap_id=None):
535 '''get datacenter networks, can use both uuid or name'''
536 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
537 #obtain data
538 try:
539 datacenter_dict = mydb.get_table_by_uuid_name('datacenters', datacenter_id, "datacenter")
540 where_= {"datacenter_id":datacenter_dict['uuid']}
541 if netmap_id:
542 if utils.check_valid_uuid(netmap_id):
543 where_["uuid"] = netmap_id
544 else:
545 where_["name"] = netmap_id
546 #change_keys_http2db(content, http2db_tenant, reverse=True)
547 deleted = mydb.delete_row(FROM='datacenter_nets', WHERE= where_)
548 if deleted == 0 and netmap_id :
549 bottle.abort(HTTP_Not_Found, "No netmap found with " + " and ".join(map(lambda x: str(x[0])+": "+str(x[1]), where_.iteritems())) )
550 if netmap_id:
551 return format_out({"result": "netmap %s deleted" % netmap_id})
552 else:
553 return format_out({"result": "%d netmap deleted" % deleted})
554 except (nfvo.NfvoException, db_base_Exception) as e:
555 logger.error("http_delnetmap_datacenter_id error {}: {}".format(e.http_code, str(e)))
556 bottle.abort(e.http_code, str(e))
557
558
559 @bottle.route(url_base + '/<tenant_id>/datacenters/<datacenter_id>/netmaps/upload', method='POST')
560 def http_uploadnetmap_datacenter_id(tenant_id, datacenter_id):
561 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
562 try:
563 netmaps = nfvo.datacenter_new_netmap(mydb, tenant_id, datacenter_id, None)
564 convert_datetime2str(netmaps)
565 utils.convert_str2boolean(netmaps, ('shared', 'multipoint') )
566 data={'netmaps' : netmaps}
567 return format_out(data)
568 except (nfvo.NfvoException, db_base_Exception) as e:
569 logger.error("http_uploadnetmap_datacenter_id error {}: {}".format(e.http_code, str(e)))
570 bottle.abort(e.http_code, str(e))
571
572 @bottle.route(url_base + '/<tenant_id>/datacenters/<datacenter_id>/netmaps', method='POST')
573 def http_postnetmap_datacenter_id(tenant_id, datacenter_id):
574 '''creates a new netmap'''
575 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
576 #parse input data
577 http_content,_ = format_in( netmap_new_schema )
578 r = utils.remove_extra_items(http_content, netmap_new_schema)
579 if r is not None: print "http_postnetmap_datacenter_id: Warning: remove extra items ", r
580
581 try:
582 #obtain data, check that only one exist
583 netmaps = nfvo.datacenter_new_netmap(mydb, tenant_id, datacenter_id, http_content)
584 convert_datetime2str(netmaps)
585 utils.convert_str2boolean(netmaps, ('shared', 'multipoint') )
586 data={'netmaps' : netmaps}
587 return format_out(data)
588 except (nfvo.NfvoException, db_base_Exception) as e:
589 logger.error("http_postnetmap_datacenter_id error {}: {}".format(e.http_code, str(e)))
590 bottle.abort(e.http_code, str(e))
591
592 @bottle.route(url_base + '/<tenant_id>/datacenters/<datacenter_id>/netmaps/<netmap_id>', method='PUT')
593 def http_putnettmap_datacenter_id(tenant_id, datacenter_id, netmap_id):
594 '''edit a netmap'''
595 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
596 #parse input data
597 http_content,_ = format_in( netmap_edit_schema )
598 r = utils.remove_extra_items(http_content, netmap_edit_schema)
599 if r is not None: print "http_putnettmap_datacenter_id: Warning: remove extra items ", r
600
601 #obtain data, check that only one exist
602 try:
603 nfvo.datacenter_edit_netmap(mydb, tenant_id, datacenter_id, netmap_id, http_content)
604 return http_getnetmap_datacenter_id(tenant_id, datacenter_id, netmap_id)
605 except (nfvo.NfvoException, db_base_Exception) as e:
606 logger.error("http_putnettmap_datacenter_id error {}: {}".format(e.http_code, str(e)))
607 bottle.abort(e.http_code, str(e))
608
609
610 @bottle.route(url_base + '/<tenant_id>/datacenters/<datacenter_id>/action', method='POST')
611 def http_action_datacenter_id(tenant_id, datacenter_id):
612 '''perform an action over datacenter, can use both uuid or name'''
613 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
614 #parse input data
615 http_content,_ = format_in( datacenter_action_schema )
616 r = utils.remove_extra_items(http_content, datacenter_action_schema)
617 if r is not None: print "http_action_datacenter_id: Warning: remove extra items ", r
618
619 try:
620 #obtain data, check that only one exist
621 result = nfvo.datacenter_action(mydb, tenant_id, datacenter_id, http_content)
622 if 'net-update' in http_content:
623 return http_getnetmap_datacenter_id(datacenter_id)
624 else:
625 return format_out(result)
626 except (nfvo.NfvoException, db_base_Exception) as e:
627 logger.error("http_action_datacenter_id error {}: {}".format(e.http_code, str(e)))
628 bottle.abort(e.http_code, str(e))
629
630
631 @bottle.route(url_base + '/datacenters/<datacenter_id>', method='DELETE')
632 def http_delete_datacenter_id( datacenter_id):
633 '''delete a tenant from database, can use both uuid or name'''
634
635 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
636 try:
637 data = nfvo.delete_datacenter(mydb, datacenter_id)
638 return format_out({"result":"datacenter '" + data + "' deleted"})
639 except (nfvo.NfvoException, db_base_Exception) as e:
640 logger.error("http_delete_datacenter_id error {}: {}".format(e.http_code, str(e)))
641 bottle.abort(e.http_code, str(e))
642
643 @bottle.route(url_base + '/<tenant_id>/datacenters/<datacenter_id>', method='POST')
644 def http_associate_datacenters(tenant_id, datacenter_id):
645 '''associate an existing datacenter to a this tenant. '''
646 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
647 #parse input data
648 http_content,_ = format_in( datacenter_associate_schema )
649 r = utils.remove_extra_items(http_content, datacenter_associate_schema)
650 if r != None: print "http_associate_datacenters: Warning: remove extra items ", r
651 try:
652 id_ = nfvo.associate_datacenter_to_tenant(mydb, tenant_id, datacenter_id,
653 http_content['datacenter'].get('vim_tenant'),
654 http_content['datacenter'].get('vim_tenant_name'),
655 http_content['datacenter'].get('vim_username'),
656 http_content['datacenter'].get('vim_password')
657 )
658 return http_get_datacenter_id(tenant_id, id_)
659 except (nfvo.NfvoException, db_base_Exception) as e:
660 logger.error("http_associate_datacenters error {}: {}".format(e.http_code, str(e)))
661 bottle.abort(e.http_code, str(e))
662
663 @bottle.route(url_base + '/<tenant_id>/datacenters/<datacenter_id>', method='DELETE')
664 def http_deassociate_datacenters(tenant_id, datacenter_id):
665 '''deassociate an existing datacenter to a this tenant. '''
666 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
667 try:
668 data = nfvo.deassociate_datacenter_to_tenant(mydb, tenant_id, datacenter_id)
669 return format_out({"result": data})
670 except (nfvo.NfvoException, db_base_Exception) as e:
671 logger.error("http_deassociate_datacenters error {}: {}".format(e.http_code, str(e)))
672 bottle.abort(e.http_code, str(e))
673
674 @bottle.route(url_base + '/<tenant_id>/vim/<datacenter_id>/<item>', method='GET')
675 @bottle.route(url_base + '/<tenant_id>/vim/<datacenter_id>/<item>/<name>', method='GET')
676 def http_get_vim_items(tenant_id, datacenter_id, item, name=None):
677 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
678 try:
679 data = nfvo.vim_action_get(mydb, tenant_id, datacenter_id, item, name)
680 return format_out(data)
681 except (nfvo.NfvoException, db_base_Exception) as e:
682 logger.error("http_get_vim_items error {}: {}".format(e.http_code, str(e)))
683 bottle.abort(e.http_code, str(e))
684
685 @bottle.route(url_base + '/<tenant_id>/vim/<datacenter_id>/<item>/<name>', method='DELETE')
686 def http_del_vim_items(tenant_id, datacenter_id, item, name):
687 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
688 try:
689 data = nfvo.vim_action_delete(mydb, tenant_id, datacenter_id, item, name)
690 return format_out({"result":data})
691 except (nfvo.NfvoException, db_base_Exception) as e:
692 logger.error("http_del_vim_items error {}: {}".format(e.http_code, str(e)))
693 bottle.abort(e.http_code, str(e))
694 @bottle.route(url_base + '/<tenant_id>/vim/<datacenter_id>/<item>', method='POST')
695 def http_post_vim_items(tenant_id, datacenter_id, item):
696 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
697 http_content,_ = format_in( object_schema )
698 try:
699 data = nfvo.vim_action_create(mydb, tenant_id, datacenter_id, item, http_content)
700 return format_out(data)
701 except (nfvo.NfvoException, db_base_Exception) as e:
702 logger.error("http_post_vim_items error {}: {}".format(e.http_code, str(e)))
703 bottle.abort(e.http_code, str(e))
704
705 @bottle.route(url_base + '/<tenant_id>/vnfs', method='GET')
706 def http_get_vnfs(tenant_id):
707 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
708 try:
709 if tenant_id != 'any':
710 #check valid tenant_id
711 nfvo.check_tenant(mydb, tenant_id)
712 select_,where_,limit_ = filter_query_string(bottle.request.query, None,
713 ('uuid','name','description','public', "tenant_id", "created_at") )
714 where_or = {}
715 if tenant_id != "any":
716 where_or["tenant_id"] = tenant_id
717 where_or["public"] = True
718 vnfs = mydb.get_rows(FROM='vnfs', SELECT=select_,WHERE=where_,WHERE_OR=where_or, WHERE_AND_OR="AND",LIMIT=limit_)
719 #change_keys_http2db(content, http2db_vnf, reverse=True)
720 utils.convert_str2boolean(vnfs, ('public',))
721 convert_datetime2str(vnfs)
722 data={'vnfs' : vnfs}
723 return format_out(data)
724 except (nfvo.NfvoException, db_base_Exception) as e:
725 logger.error("http_get_vnfs error {}: {}".format(e.http_code, str(e)))
726 bottle.abort(e.http_code, str(e))
727
728 @bottle.route(url_base + '/<tenant_id>/vnfs/<vnf_id>', method='GET')
729 def http_get_vnf_id(tenant_id,vnf_id):
730 '''get vnf details, can use both uuid or name'''
731 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
732 try:
733 vnf = nfvo.get_vnf_id(mydb,tenant_id,vnf_id)
734 utils.convert_str2boolean(vnf, ('public',))
735 convert_datetime2str(vnf)
736 return format_out(vnf)
737 except (nfvo.NfvoException, db_base_Exception) as e:
738 logger.error("http_get_vnf_id error {}: {}".format(e.http_code, str(e)))
739 bottle.abort(e.http_code, str(e))
740
741 @bottle.route(url_base + '/<tenant_id>/vnfs', method='POST')
742 def http_post_vnfs(tenant_id):
743 '''insert a vnf into the catalogue. Creates the flavor and images in the VIM, and creates the VNF and its internal structure in the OPENMANO DB'''
744 #print "Parsing the YAML file of the VNF"
745 #parse input data
746 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
747 http_content, used_schema = format_in( vnfd_schema_v01, ("schema_version",), {"0.2": vnfd_schema_v02})
748 r = utils.remove_extra_items(http_content, used_schema)
749 if r is not None: print "http_post_vnfs: Warning: remove extra items ", r
750 try:
751 if used_schema == vnfd_schema_v01:
752 vnf_id = nfvo.new_vnf(mydb,tenant_id,http_content)
753 elif used_schema == vnfd_schema_v02:
754 vnf_id = nfvo.new_vnf_v02(mydb,tenant_id,http_content)
755 else:
756 logger.warning('Unexpected schema_version: %s', http_content.get("schema_version"))
757 bottle.abort(HTTP_Bad_Request, "Invalid schema version")
758 return http_get_vnf_id(tenant_id, vnf_id)
759 except (nfvo.NfvoException, db_base_Exception) as e:
760 logger.error("http_post_vnfs error {}: {}".format(e.http_code, str(e)))
761 bottle.abort(e.http_code, str(e))
762
763 @bottle.route(url_base + '/<tenant_id>/vnfs/<vnf_id>', method='DELETE')
764 def http_delete_vnf_id(tenant_id,vnf_id):
765 '''delete a vnf from database, and images and flavors in VIM when appropriate, can use both uuid or name'''
766 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
767 #check valid tenant_id and deletes the vnf, including images,
768 try:
769 data = nfvo.delete_vnf(mydb,tenant_id,vnf_id)
770 #print json.dumps(data, indent=4)
771 return format_out({"result":"VNF " + data + " deleted"})
772 except (nfvo.NfvoException, db_base_Exception) as e:
773 logger.error("http_delete_vnf_id error {}: {}".format(e.http_code, str(e)))
774 bottle.abort(e.http_code, str(e))
775
776 #@bottle.route(url_base + '/<tenant_id>/hosts/topology', method='GET')
777 #@bottle.route(url_base + '/<tenant_id>/physicalview/Madrid-Alcantara', method='GET')
778 @bottle.route(url_base + '/<tenant_id>/physicalview/<datacenter>', method='GET')
779 def http_get_hosts(tenant_id, datacenter):
780 '''get the tidvim host hopology from the vim.'''
781 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
782 #print "http_get_hosts received by tenant " + tenant_id + ' datacenter ' + datacenter
783 try:
784 if datacenter == 'treeview':
785 data = nfvo.get_hosts(mydb, tenant_id)
786 else:
787 #openmano-gui is using a hardcoded value for the datacenter
788 result, data = nfvo.get_hosts_info(mydb, tenant_id) #, datacenter)
789
790 if result < 0:
791 print "http_get_hosts error %d %s" % (-result, data)
792 bottle.abort(-result, data)
793 else:
794 convert_datetime2str(data)
795 print json.dumps(data, indent=4)
796 return format_out(data)
797 except (nfvo.NfvoException, db_base_Exception) as e:
798 logger.error("http_get_hosts error {}: {}".format(e.http_code, str(e)))
799 bottle.abort(e.http_code, str(e))
800
801
802 @bottle.route(url_base + '/<path:path>', method='OPTIONS')
803 def http_options_deploy(path):
804 '''For some reason GUI web ask for OPTIONS that must be responded'''
805 #TODO: check correct path, and correct headers request
806 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
807 bottle.response.set_header('Access-Control-Allow-Methods','POST, GET, PUT, DELETE, OPTIONS')
808 bottle.response.set_header('Accept','application/yaml,application/json')
809 bottle.response.set_header('Content-Type','application/yaml,application/json')
810 bottle.response.set_header('Access-Control-Allow-Headers','content-type')
811 bottle.response.set_header('Access-Control-Allow-Origin','*')
812 return
813
814 @bottle.route(url_base + '/<tenant_id>/topology/deploy', method='POST')
815 def http_post_deploy(tenant_id):
816 '''post topology deploy.'''
817 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
818
819 http_content, used_schema = format_in( nsd_schema_v01, ("schema_version",), {2: nsd_schema_v02})
820 #r = utils.remove_extra_items(http_content, used_schema)
821 #if r is not None: print "http_post_deploy: Warning: remove extra items ", r
822 #print "http_post_deploy input: ", http_content
823
824 try:
825 scenario_id = nfvo.new_scenario(mydb, tenant_id, http_content)
826 instance = nfvo.start_scenario(mydb, tenant_id, scenario_id, http_content['name'], http_content['name'])
827 #print json.dumps(data, indent=4)
828 return format_out(instance)
829 except (nfvo.NfvoException, db_base_Exception) as e:
830 logger.error("http_post_deploy error {}: {}".format(e.http_code, str(e)))
831 bottle.abort(e.http_code, str(e))
832
833 @bottle.route(url_base + '/<tenant_id>/topology/verify', method='POST')
834 def http_post_verify(tenant_id):
835 #TODO:
836 # '''post topology verify'''
837 # print "http_post_verify by tenant " + tenant_id + ' datacenter ' + datacenter
838 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
839 return
840
841 #
842 # SCENARIOS
843 #
844
845 @bottle.route(url_base + '/<tenant_id>/scenarios', method='POST')
846 def http_post_scenarios(tenant_id):
847 '''add a scenario into the catalogue. Creates the scenario and its internal structure in the OPENMANO DB'''
848 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
849 http_content, used_schema = format_in( nsd_schema_v01, ("schema_version",), {2: nsd_schema_v02, "0.3": nsd_schema_v03})
850 #r = utils.remove_extra_items(http_content, used_schema)
851 #if r is not None: print "http_post_scenarios: Warning: remove extra items ", r
852 #print "http_post_scenarios input: ", http_content
853 try:
854 if used_schema == nsd_schema_v01:
855 scenario_id = nfvo.new_scenario(mydb, tenant_id, http_content)
856 elif used_schema == nsd_schema_v02:
857 scenario_id = nfvo.new_scenario_v02(mydb, tenant_id, http_content)
858 elif used_schema == nsd_schema_v03:
859 scenario_id = nfvo.new_scenario_v03(mydb, tenant_id, http_content)
860 else:
861 logger.warning('Unexpected schema_version: %s', http_content.get("schema_version"))
862 bottle.abort(HTTP_Bad_Request, "Invalid schema version")
863 #print json.dumps(data, indent=4)
864 #return format_out(data)
865 return http_get_scenario_id(tenant_id, scenario_id)
866 except (nfvo.NfvoException, db_base_Exception) as e:
867 logger.error("http_post_scenarios error {}: {}".format(e.http_code, str(e)))
868 bottle.abort(e.http_code, str(e))
869
870 @bottle.route(url_base + '/<tenant_id>/scenarios/<scenario_id>/action', method='POST')
871 def http_post_scenario_action(tenant_id, scenario_id):
872 '''take an action over a scenario'''
873 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
874 #check valid tenant_id
875 try:
876 nfvo.check_tenant(mydb, tenant_id)
877 #parse input data
878 http_content,_ = format_in( scenario_action_schema )
879 r = utils.remove_extra_items(http_content, scenario_action_schema)
880 if r is not None: print "http_post_scenario_action: Warning: remove extra items ", r
881 if "start" in http_content:
882 data = nfvo.start_scenario(mydb, tenant_id, scenario_id, http_content['start']['instance_name'], \
883 http_content['start'].get('description',http_content['start']['instance_name']),
884 http_content['start'].get('datacenter') )
885 return format_out(data)
886 elif "deploy" in http_content: #Equivalent to start
887 data = nfvo.start_scenario(mydb, tenant_id, scenario_id, http_content['deploy']['instance_name'],
888 http_content['deploy'].get('description',http_content['deploy']['instance_name']),
889 http_content['deploy'].get('datacenter') )
890 return format_out(data)
891 elif "reserve" in http_content: #Reserve resources
892 data = nfvo.start_scenario(mydb, tenant_id, scenario_id, http_content['reserve']['instance_name'],
893 http_content['reserve'].get('description',http_content['reserve']['instance_name']),
894 http_content['reserve'].get('datacenter'), startvms=False )
895 return format_out(data)
896 elif "verify" in http_content: #Equivalent to start and then delete
897 data = nfvo.start_scenario(mydb, tenant_id, scenario_id, http_content['verify']['instance_name'],
898 http_content['verify'].get('description',http_content['verify']['instance_name']),
899 http_content['verify'].get('datacenter'), startvms=False )
900 instance_id = data['uuid']
901 nfvo.delete_instance(mydb, tenant_id,instance_id)
902 return format_out({"result":"Verify OK"})
903 except (nfvo.NfvoException, db_base_Exception) as e:
904 logger.error("http_post_scenario_action error {}: {}".format(e.http_code, str(e)))
905 bottle.abort(e.http_code, str(e))
906
907 @bottle.route(url_base + '/<tenant_id>/scenarios', method='GET')
908 def http_get_scenarios(tenant_id):
909 '''get scenarios list'''
910 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
911 try:
912 #check valid tenant_id
913 if tenant_id != "any":
914 nfvo.check_tenant(mydb, tenant_id)
915 #obtain data
916 s,w,l=filter_query_string(bottle.request.query, None, ('uuid', 'name', 'description', 'tenant_id', 'created_at', 'public'))
917 where_or={}
918 if tenant_id != "any":
919 where_or["tenant_id"] = tenant_id
920 where_or["public"] = True
921 scenarios = mydb.get_rows(SELECT=s, WHERE=w, WHERE_OR=where_or, WHERE_AND_OR="AND", LIMIT=l, FROM='scenarios')
922 convert_datetime2str(scenarios)
923 utils.convert_str2boolean(scenarios, ('public',) )
924 data={'scenarios':scenarios}
925 #print json.dumps(scenarios, indent=4)
926 return format_out(data)
927 except (nfvo.NfvoException, db_base_Exception) as e:
928 logger.error("http_get_scenarios error {}: {}".format(e.http_code, str(e)))
929 bottle.abort(e.http_code, str(e))
930
931 @bottle.route(url_base + '/<tenant_id>/scenarios/<scenario_id>', method='GET')
932 def http_get_scenario_id(tenant_id, scenario_id):
933 '''get scenario details, can use both uuid or name'''
934 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
935 try:
936 #check valid tenant_id
937 if tenant_id != "any":
938 nfvo.check_tenant(mydb, tenant_id)
939 #obtain data
940 scenario = mydb.get_scenario(scenario_id, tenant_id)
941 convert_datetime2str(scenario)
942 data={'scenario' : scenario}
943 return format_out(data)
944 except (nfvo.NfvoException, db_base_Exception) as e:
945 logger.error("http_get_scenarios error {}: {}".format(e.http_code, str(e)))
946 bottle.abort(e.http_code, str(e))
947
948 @bottle.route(url_base + '/<tenant_id>/scenarios/<scenario_id>', method='DELETE')
949 def http_delete_scenario_id(tenant_id, scenario_id):
950 '''delete a scenario from database, can use both uuid or name'''
951 try:
952 #check valid tenant_id
953 if tenant_id != "any":
954 nfvo.check_tenant(mydb, tenant_id)
955 #obtain data
956 data = mydb.delete_scenario(scenario_id, tenant_id)
957 #print json.dumps(data, indent=4)
958 return format_out({"result":"scenario " + data + " deleted"})
959 except (nfvo.NfvoException, db_base_Exception) as e:
960 logger.error("http_delete_scenario_id error {}: {}".format(e.http_code, str(e)))
961 bottle.abort(e.http_code, str(e))
962
963
964 @bottle.route(url_base + '/<tenant_id>/scenarios/<scenario_id>', method='PUT')
965 def http_put_scenario_id(tenant_id, scenario_id):
966 '''edit an existing scenario id'''
967 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
968 http_content,_ = format_in( scenario_edit_schema )
969 #r = utils.remove_extra_items(http_content, scenario_edit_schema)
970 #if r is not None: print "http_put_scenario_id: Warning: remove extra items ", r
971 #print "http_put_scenario_id input: ", http_content
972 try:
973 nfvo.edit_scenario(mydb, tenant_id, scenario_id, http_content)
974 #print json.dumps(data, indent=4)
975 #return format_out(data)
976 return http_get_scenario_id(tenant_id, scenario_id)
977 except (nfvo.NfvoException, db_base_Exception) as e:
978 logger.error("http_put_scenario_id error {}: {}".format(e.http_code, str(e)))
979 bottle.abort(e.http_code, str(e))
980
981 @bottle.route(url_base + '/<tenant_id>/instances', method='POST')
982 def http_post_instances(tenant_id):
983 '''take an action over a scenario'''
984 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
985 try:
986 #check valid tenant_id
987 if tenant_id != "any":
988 nfvo.check_tenant(mydb, tenant_id)
989 #parse input data
990 http_content,used_schema = format_in( instance_scenario_create_schema_v01)
991 r = utils.remove_extra_items(http_content, used_schema)
992 if r is not None:
993 logger.warning("http_post_instances: Warning: remove extra items %s", str(r))
994 data = nfvo.create_instance(mydb, tenant_id, http_content["instance"])
995 return format_out(data)
996 except (nfvo.NfvoException, db_base_Exception) as e:
997 logger.error("http_post_instances error {}: {}".format(e.http_code, str(e)))
998 bottle.abort(e.http_code, str(e))
999
1000 #
1001 # INSTANCES
1002 #
1003 @bottle.route(url_base + '/<tenant_id>/instances', method='GET')
1004 def http_get_instances(tenant_id):
1005 '''get instance list'''
1006 try:
1007 #check valid tenant_id
1008 if tenant_id != "any":
1009 nfvo.check_tenant(mydb, tenant_id)
1010 #obtain data
1011 s,w,l=filter_query_string(bottle.request.query, None, ('uuid', 'name', 'scenario_id', 'tenant_id', 'description', 'created_at'))
1012 if tenant_id != "any":
1013 w['tenant_id'] = tenant_id
1014 instances = mydb.get_rows(SELECT=s, WHERE=w, LIMIT=l, FROM='instance_scenarios')
1015 convert_datetime2str(instances)
1016 utils.convert_str2boolean(instances, ('public',) )
1017 data={'instances':instances}
1018 return format_out(data)
1019 except (nfvo.NfvoException, db_base_Exception) as e:
1020 logger.error("http_get_instances error {}: {}".format(e.http_code, str(e)))
1021 bottle.abort(e.http_code, str(e))
1022
1023 @bottle.route(url_base + '/<tenant_id>/instances/<instance_id>', method='GET')
1024 def http_get_instance_id(tenant_id, instance_id):
1025 '''get instances details, can use both uuid or name'''
1026 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
1027 try:
1028 #check valid tenant_id
1029 if tenant_id != "any":
1030 nfvo.check_tenant(mydb, tenant_id)
1031 if tenant_id == "any":
1032 tenant_id = None
1033 #obtain data (first time is only to check that the instance exists)
1034 instance_dict = mydb.get_instance_scenario(instance_id, tenant_id, verbose=True)
1035 try:
1036 nfvo.refresh_instance(mydb, tenant_id, instance_dict)
1037 except (nfvo.NfvoException, db_base_Exception) as e:
1038 logger.warn("nfvo.refresh_instance couldn't refresh the status of the instance: %s" % str(e))
1039 #obtain data with results upated
1040 instance = mydb.get_instance_scenario(instance_id, tenant_id)
1041 convert_datetime2str(instance)
1042 #print json.dumps(instance, indent=4)
1043 return format_out(instance)
1044 except (nfvo.NfvoException, db_base_Exception) as e:
1045 logger.error("http_get_instance_id error {}: {}".format(e.http_code, str(e)))
1046 bottle.abort(e.http_code, str(e))
1047
1048 @bottle.route(url_base + '/<tenant_id>/instances/<instance_id>', method='DELETE')
1049 def http_delete_instance_id(tenant_id, instance_id):
1050 '''delete instance from VIM and from database, can use both uuid or name'''
1051 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
1052 try:
1053 #check valid tenant_id
1054 if tenant_id != "any":
1055 nfvo.check_tenant(mydb, tenant_id)
1056 if tenant_id == "any":
1057 tenant_id = None
1058 #obtain data
1059 message = nfvo.delete_instance(mydb, tenant_id,instance_id)
1060 return format_out({"result":message})
1061 except (nfvo.NfvoException, db_base_Exception) as e:
1062 logger.error("http_delete_instance_id error {}: {}".format(e.http_code, str(e)))
1063 bottle.abort(e.http_code, str(e))
1064
1065 @bottle.route(url_base + '/<tenant_id>/instances/<instance_id>/action', method='POST')
1066 def http_post_instance_scenario_action(tenant_id, instance_id):
1067 '''take an action over a scenario instance'''
1068 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
1069 try:
1070 #check valid tenant_id
1071 if tenant_id != "any":
1072 nfvo.check_tenant(mydb, tenant_id)
1073
1074 #parse input data
1075 http_content,_ = format_in( instance_scenario_action_schema )
1076 r = utils.remove_extra_items(http_content, instance_scenario_action_schema)
1077 if r is not None: print "http_post_instance_scenario_action: Warning: remove extra items ", r
1078 #print "http_post_instance_scenario_action input: ", http_content
1079 #obtain data
1080 instance = mydb.get_instance_scenario(instance_id, tenant_id)
1081 instance_id = instance["uuid"]
1082
1083 data = nfvo.instance_action(mydb, tenant_id, instance_id, http_content)
1084 return format_out(data)
1085 except (nfvo.NfvoException, db_base_Exception) as e:
1086 logger.error("http_post_instance_scenario_action error {}: {}".format(e.http_code, str(e)))
1087 bottle.abort(e.http_code, str(e))
1088
1089
1090 @bottle.error(400)
1091 @bottle.error(401)
1092 @bottle.error(404)
1093 @bottle.error(403)
1094 @bottle.error(405)
1095 @bottle.error(406)
1096 @bottle.error(409)
1097 @bottle.error(503)
1098 @bottle.error(500)
1099 def error400(error):
1100 e={"error":{"code":error.status_code, "type":error.status, "description":error.body}}
1101 bottle.response.headers['Access-Control-Allow-Origin'] = '*'
1102 return format_out(e)
1103