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