1 # -*- coding: utf-8 -*-
4 # Copyright 2015 Telefónica Investigación y Desarrollo, S.A.U.
5 # This file is part of openmano
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
12 # http://www.apache.org/licenses/LICENSE-2.0
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
20 # For those usages not covered by the Apache License, Version 2.0 please
21 # contact with: nfvlabs@tid.es
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.
29 __author__
="Alfonso Tierno, Gerardo Garcia"
30 __date__
="$17-sep-2014 09:07:15$"
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
46 from utils
import auxiliary_functions
as af
52 HTTP_Bad_Request
= 400
53 HTTP_Unauthorized
= 401
56 HTTP_Method_Not_Allowed
= 405
57 HTTP_Not_Acceptable
= 406
58 HTTP_Service_Unavailable
= 503
59 HTTP_Internal_Server_Error
= 500
61 def delete_nulls(var
):
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:
70 if type(k
) is dict: delete_nulls(k
)
71 if len(var
) == 0: return True
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
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:
87 convert_datetime2str(v
)
90 class httpserver(threading
.Thread
):
91 def __init__(self
, db
, admin
=False, host
='localhost', port
=9090):
95 threading
.Thread
.__init
__(self
)
97 self
.port
= port
#Port where the listen service must be started
99 self
.name
= "http_admin"
102 #self.url_preffix = 'http://' + host + ':' + str(port) + url_base
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
111 bottle
.run(host
=self
.host
, port
=self
.port
, debug
=True) #quiet=True
113 def run_bottle(db
, host_
='localhost', port_
=9090):
114 '''used for launching in main thread, so that it can be debugged'''
117 bottle
.run(host
=host_
, port
=port_
, debug
=True) #quiet=True
120 @bottle.route(url_base
+ '/', method
='GET')
123 return 'works' #TODO: to be completed
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
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:
140 change_keys_http2db(d
, http_db
, reverse
)
141 elif type(data
) is dict or type(data
) is bottle
.FormsDict
:
143 for k
,v
in http_db
.items():
144 if v
in data
: data
[k
]=data
.pop(v
)
146 for k
,v
in http_db
.items():
147 if k
in data
: data
[v
]=data
.pop(k
)
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"
160 def format_in(default_schema
, version_fields
=None, version_dict_schema
=None):
161 ''' Parse the content of HTTP request against a json_schema
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
168 user_data, used_schema: if the data is successfully decoded and matches the schema
169 launch a bottle abort if fails
171 #print "HEADERS :" + str(bottle.request.headers.items())
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.")
186 print 'Content-Type ' + str(format_type
) + ' not supported.'
187 bottle
.abort(HTTP_Not_Acceptable
, 'Content-Type ' + str(format_type
) + ' not supported.')
189 #if client_data == None:
190 # bottle.abort(HTTP_Bad_Request, "Content error, empty")
193 #look for the client provider version
194 error_text
= "Invalid content "
195 client_version
= 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
]
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")
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
)
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
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
)
227 # bottle.abort(HTTP_Bad_Request, "Content error: Failed to parse Content-Type", error_pos)
230 def filter_query_string(qs
, http2db
, allowed
):
231 '''Process query string (qs) checking that contains only valid tokens for avoiding SQL injection
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
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")
251 select
+= qs
.getall(k
)
254 bottle
.abort(HTTP_Bad_Request
, "Invalid query string at 'field="+v
+"'")
259 bottle
.abort(HTTP_Bad_Request
, "Invalid query string at 'limit="+qs
[k
]+"'")
262 bottle
.abort(HTTP_Bad_Request
, "Invalid query string at '"+k
+"="+qs
[k
]+"'")
263 if qs
[k
]!="null": where
[k
]=qs
[k
]
265 if len(select
)==0: select
+= allowed
266 #change from http api to database naming
267 for i
in range(0,len(select
)):
269 if http2db
and k
in http2db
:
270 select
[i
] = http2db
[k
]
272 change_keys_http2db(where
, http2db
)
273 print "filter_query_string", select
,where
,limit
275 return select
,where
,limit
277 @bottle.hook('after_request')
279 '''Don't know yet if really needed. Keep it just in case'''
280 bottle
.response
.headers
['Access-Control-Allow-Origin'] = '*'
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_
)
292 print "http_get_tenants Error", content
293 bottle
.abort(-result
, content
)
295 #change_keys_http2db(content, http2db_tenant, reverse=True)
296 convert_datetime2str(content
)
297 data
={'tenants' : content
}
298 return format_out(data
)
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'''
304 result
, content
= mydb
.get_table_by_uuid_name('nfvo_tenants', tenant_id
, "tenant")
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
)
311 data
={'tenant' : content
}
312 return format_out(data
)
314 @bottle.route(url_base
+ '/tenants', method
='POST')
315 def http_post_tenants():
316 '''insert a tenant into the catalogue. '''
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'])
323 print "http_post_tenants error %d %s" % (-result
, data
)
324 bottle
.abort(-result
, data
)
326 return http_get_tenant_id(data
)
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'''
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
336 #obtain data, check that only one exist
337 result
, content
= mydb
.get_table_by_uuid_name('nfvo_tenants', tenant_id
)
339 print "http_edit_tenant_id error %d %s" % (result
, content
)
340 bottle
.abort(-result
, content
)
343 tenant_id
= content
['uuid']
344 where
={'uuid': content
['uuid']}
345 result
, content
= mydb
.update_rows('nfvo_tenants', http_content
['tenant'], where
)
347 print "http_edit_tenant_id error %d %s" % (result
, content
)
348 bottle
.abort(-result
, content
)
350 return http_get_tenant_id(tenant_id
)
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'''
356 result
, data
= nfvo
.delete_tenant(mydb
, tenant_id
)
358 print "http_delete_tenant_id error %d %s" % (-result
, data
)
359 bottle
.abort(-result
, data
)
361 #print json.dumps(data, indent=4)
362 return format_out({"result":"tenant " + data
+ " deleted"})
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
)
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_
)
384 result
, content
= mydb
.get_table(FROM
='datacenters',
385 SELECT
=select_
,WHERE
=where_
,LIMIT
=limit_
)
387 print "http_get_datacenters Error", content
388 bottle
.abort(-result
, content
)
390 #change_keys_http2db(content, http2db_tenant, reverse=True)
391 convert_datetime2str(content
)
392 data
={'datacenters' : content
}
393 return format_out(data
)
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
)
405 what
= 'uuid' if af
.check_valid_uuid(datacenter_id
) else 'name'
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'
414 from_
='datacenters as d'
415 result
, content
= mydb
.get_table(
421 print "http_get_datacenter_id error %d %s" % (result
, content
)
422 bottle
.abort(-result
, content
)
424 bottle
.abort( HTTP_Not_Found
, "No datacenter found for tenant with %s '%s'" %(what
, datacenter_id
) )
426 bottle
.abort( HTTP_Bad_Request
, "More than one datacenter found for tenant with %s '%s'" %(what
, datacenter_id
) )
428 if tenant_id
!= 'any':
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"]
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
442 if content
[0]['config'] != None:
444 config_dict
= yaml
.load(content
[0]['config'])
445 content
[0]['config'] = config_dict
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
)
453 @bottle.route(url_base
+ '/datacenters', method
='POST')
454 def http_post_datacenters():
455 '''insert a tenant into the catalogue. '''
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'])
462 print "http_post_datacenters error %d %s" % (-result
, data
)
463 bottle
.abort(-result
, data
)
465 return http_get_datacenter_id('any', data
)
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'''
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
476 result
, datacenter_id
= nfvo
.edit_datacenter(mydb
, datacenter_id_name
, http_content
['datacenter'])
478 print "http_edit_datacenter_id error %d %s" % (-result
, datacenter_id
)
479 bottle
.abort(-result
, datacenter_id
)
481 return http_get_datacenter_id('any', datacenter_id
)
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'''
489 result
, datacenter_dict
= mydb
.get_table_by_uuid_name('datacenters', datacenter_id
, "datacenter")
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']}
495 if af
.check_valid_uuid(netmap_id
):
496 where_
["uuid"] = netmap_id
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'),
503 print "http_getnetwork_datacenter_id error %d %s" % (result
, content
)
504 bottle
.abort(-result
, content
)
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())) )
514 data
={'netmaps' : content
}
515 return format_out(data
)
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'''
522 result
, datacenter_dict
= mydb
.get_table_by_uuid_name('datacenters', datacenter_id
, "datacenter")
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']}
528 if af
.check_valid_uuid(netmap_id
):
529 where_
["uuid"] = netmap_id
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_
)
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())) )
540 return format_out({"result": "netmap %s deleted" % netmap_id
})
542 return format_out({"result": "%d netmap deleted" % result
})
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)
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') )
554 data
={'netmaps' : content
}
555 return format_out(data
)
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'''
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
565 #obtain data, check that only one exist
566 result
, content
= nfvo
.datacenter_new_netmap(mydb
, tenant_id
, datacenter_id
, http_content
)
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') )
573 data
={'netmaps' : content
}
574 return format_out(data
)
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
):
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
584 #obtain data, check that only one exist
585 result
, content
= nfvo
.datacenter_edit_netmap(mydb
, tenant_id
, datacenter_id
, netmap_id
, http_content
)
587 print "http_putnettmap_datacenter_id error %d %s" % (result
, content
)
588 bottle
.abort(-result
, content
)
590 return http_getnetmap_datacenter_id(tenant_id
, datacenter_id
, netmap_id
)
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'''
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
601 #obtain data, check that only one exist
602 result
, content
= nfvo
.datacenter_action(mydb
, tenant_id
, datacenter_id
, http_content
)
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
)
609 return format_out(content
)
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'''
616 result
, data
= nfvo
.delete_datacenter(mydb
, datacenter_id
)
618 print "http_delete_datacenter_id error %d %s" % (-result
, data
)
619 bottle
.abort(-result
, data
)
621 #print json.dumps(data, indent=4)
622 return format_out({"result":"datacenter " + data
+ " deleted"})
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. '''
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')
638 print "http_associate_datacenters error %d %s" % (-result
, data
)
639 bottle
.abort(-result
, data
)
641 print "http_associate_datacenters data" , data
642 return http_get_datacenter_id(tenant_id
, data
)
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
)
649 print "http_deassociate_datacenters error %d %s" % (-result
, data
)
650 bottle
.abort(-result
, data
)
652 return format_out({"result":data
})
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
)
661 print "http_get_vim_items error %d %s" % (-result
, data
)
662 bottle
.abort(-result
, data
)
664 return format_out(data
)
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
)
670 print "http_get_vim_items error %d %s" % (-result
, data
)
671 bottle
.abort(-result
, data
)
673 return format_out({"result":data
})
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
)
680 print "http_post_vim_items error %d %s" % (-result
, data
)
681 bottle
.abort(-result
, data
)
683 return format_out(data
)
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
)
692 select_
,where_
,limit_
= filter_query_string(bottle
.request
.query
, None,
693 ('uuid','name','description','public', "tenant_id", "created_at") )
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_
)
700 print "http_get_vnfs Error", content
701 bottle
.abort(-result
, content
)
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
)
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
)
714 print "http_post_vnfs error %d %s" % (-result
, data
)
715 bottle
.abort(-result
, data
)
717 af
.convert_str2boolean(data
, ('public',))
718 convert_datetime2str(data
)
719 return format_out(data
)
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"
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
)
731 print "http_post_vnfs error %d %s" % (-result
, data
)
732 bottle
.abort(-result
, data
)
734 return http_get_vnf_id(tenant_id
,data
)
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
)
742 print "http_delete_vnf_id error %d %s" % (-result
, data
)
743 bottle
.abort(-result
, data
)
745 #print json.dumps(data, indent=4)
746 return format_out({"result":"VNF " + data
+ " deleted"})
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.'''
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
)
758 #openmano-gui is using a hardcoded value for the datacenter
759 result
, data
= nfvo
.get_hosts_info(mydb
, tenant_id
) #, datacenter)
762 print "http_post_vnfs error %d %s" % (-result
, data
)
763 bottle
.abort(-result
, data
)
765 convert_datetime2str(data
)
766 print json
.dumps(data
, indent
=4)
767 return format_out(data
)
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','*')
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
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
791 result
, scenario_uuid
= nfvo
.new_scenario(mydb
, tenant_id
, http_content
)
793 print "http_post_deploy error creating the scenario %d %s" % (-result
, scenario_uuid
)
794 bottle
.abort(-result
, scenario_uuid
)
796 result
, data
= nfvo
.start_scenario(mydb
, tenant_id
, scenario_uuid
, http_content
['name'], http_content
['name'])
798 print "http_post_deploy error launching the scenario %d %s" % (-result
, data
)
799 bottle
.abort(-result
, data
)
801 print json
.dumps(data
, indent
=4)
802 return format_out(data
)
804 @bottle.route(url_base
+ '/<tenant_id>/topology/verify', method
='POST')
805 def http_post_verify(tenant_id
):
807 # '''post topology verify'''
808 # print "http_post_verify by tenant " + tenant_id + ' datacenter ' + datacenter
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
)
826 result
, data
= nfvo
.new_scenario_v02(mydb
, tenant_id
, http_content
)
828 print "http_post_scenarios error %d %s" % (-result
, data
)
829 bottle
.abort(-result
, data
)
831 #print json.dumps(data, indent=4)
832 #return format_out(data)
833 return http_get_scenario_id(tenant_id
,data
)
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
)
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') )
852 print "http_post_scenario_action start error %d: %s" % (-result
, data
)
853 bottle
.abort(-result
, data
)
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') )
861 print "http_post_scenario_action deploy error %d: %s" % (-result
, data
)
862 bottle
.abort(-result
, data
)
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 )
870 print "http_post_scenario_action reserve error %d: %s" % (-result
, data
)
871 bottle
.abort(-result
, data
)
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
)
884 print "http_post_scenario_action verify error during start delete_instance_id %d %s" % (-result
, message
)
885 bottle
.abort(-result
, message
)
887 #print json.dumps(data, indent=4)
888 return format_out({"result":"Verify OK"})
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
)
899 s
,w
,l
=filter_query_string(bottle
.request
.query
, None, ('uuid', 'name', 'description', 'tenant_id', 'created_at', 'public'))
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')
906 print "http_get_scenarios error %d %s" % (-result
, data
)
907 bottle
.abort(-result
, data
)
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
)
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
)
924 result
, content
= mydb
.get_scenario(scenario_id
, tenant_id
)
926 print "http_get_scenario_id error %d %s" % (-result
, content
)
927 bottle
.abort(-result
, content
)
929 #print json.dumps(content, indent=4)
930 convert_datetime2str(content
)
931 data
={'scenario' : content
}
932 return format_out(data
)
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
)
943 result
, data
= mydb
.delete_scenario(scenario_id
, tenant_id
)
945 print "http_delete_scenario_id error %d %s" % (-result
, data
)
946 bottle
.abort(-result
, data
)
948 #print json.dumps(data, indent=4)
949 return format_out({"result":"scenario " + data
+ " deleted"})
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
961 result
, data
= nfvo
.edit_scenario(mydb
, tenant_id
, scenario_id
, http_content
)
963 print "http_put_scenarios error %d %s" % (-result
, data
)
964 bottle
.abort(-result
, data
)
966 #print json.dumps(data, indent=4)
967 #return format_out(data)
968 return http_get_scenario_id(tenant_id
,data
)
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
)
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"])
984 print "http_post_instances start error %d: %s" % (-result
, data
)
985 bottle
.abort(-result
, data
)
987 return format_out(data
)
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
)
1001 s
,w
,l
=filter_query_string(bottle
.request
.query
, None, ('uuid', 'name', 'scenario_id', 'tenant_id', 'description', 'created_at'))
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')
1007 print "http_get_instances error %d %s" % (-result
, data
)
1008 bottle
.abort(-result
, data
)
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
)
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
)
1024 if tenant_id
== "any":
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)
1030 print "http_get_instance_id error %d %s" % (-result
, data
)
1031 bottle
.abort(-result
, data
)
1034 r
,c
= nfvo
.refresh_instance(mydb
, tenant_id
, data
)
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
)
1040 print "http_get_instance_id error %d %s" % (-result
, data
)
1041 bottle
.abort(-result
, data
)
1043 convert_datetime2str(data
)
1044 print json
.dumps(data
, indent
=4)
1045 return format_out(data
)
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
)
1055 if tenant_id
== "any":
1058 result
, message
= nfvo
.delete_instance(mydb
, tenant_id
,instance_id
)
1060 print "http_delete_instance_id error %d %s" % (-result
, message
)
1061 bottle
.abort(-result
, message
)
1063 #print json.dumps(data, indent=4)
1064 return format_out({"result":message
})
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
)
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
1080 result
, data
= mydb
.get_instance_scenario(instance_id
, tenant_id
)
1082 print "http_get_instance_id error %d %s" % (-result
, data
)
1083 bottle
.abort(-result
, data
)
1084 instance_id
= data
["uuid"]
1086 result
, data
= nfvo
.instance_action(mydb
, tenant_id
, instance_id
, http_content
)
1088 print "http_post_scenario_action error %d: %s" % (-result
, data
)
1089 bottle
.abort(-result
, data
)
1091 return format_out(data
)
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
)