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$"
39 from jsonschema
import validate
as js_v
, exceptions
as js_e
40 from openmano_schemas
import vnfd_schema_v01
, vnfd_schema_v02
, \
41 nsd_schema_v01
, nsd_schema_v02
, nsd_schema_v03
, scenario_edit_schema
, \
42 scenario_action_schema
, instance_scenario_action_schema
, instance_scenario_create_schema_v01
, \
43 tenant_schema
, tenant_edit_schema
,\
44 datacenter_schema
, datacenter_edit_schema
, datacenter_action_schema
, datacenter_associate_schema
,\
45 object_schema
, netmap_new_schema
, netmap_edit_schema
48 from db_base
import db_base_Exception
49 from functools
import wraps
57 HTTP_Bad_Request
= 400
58 HTTP_Unauthorized
= 401
61 HTTP_Method_Not_Allowed
= 405
62 HTTP_Not_Acceptable
= 406
63 HTTP_Service_Unavailable
= 503
64 HTTP_Internal_Server_Error
= 500
66 def delete_nulls(var
):
69 if var
[k
] is None: del var
[k
]
70 elif type(var
[k
]) is dict or type(var
[k
]) is list or type(var
[k
]) is tuple:
71 if delete_nulls(var
[k
]): del var
[k
]
72 if len(var
) == 0: return True
73 elif type(var
) is list or type(var
) is tuple:
75 if type(k
) is dict: delete_nulls(k
)
76 if len(var
) == 0: return True
79 def convert_datetime2str(var
):
80 '''Converts a datetime variable to a string with the format '%Y-%m-%dT%H:%i:%s'
81 It enters recursively in the dict var finding this kind of variables
84 for k
,v
in var
.items():
85 if type(v
) is float and k
in ("created_at", "modified_at"):
86 var
[k
] = time
.strftime("%Y-%m-%dT%H:%M:%S", time
.localtime(v
) )
87 elif type(v
) is dict or type(v
) is list or type(v
) is tuple:
88 convert_datetime2str(v
)
89 if len(var
) == 0: return True
90 elif type(var
) is list or type(var
) is tuple:
92 convert_datetime2str(v
)
94 def log_to_logger(fn
):
96 Wrap a Bottle request so that a log line is emitted after it's handled.
97 (This decorator can be extended to take the desired logger as a param.)
100 def _log_to_logger(*args
, **kwargs
):
101 actual_response
= fn(*args
, **kwargs
)
102 # modify this to log exactly what you need:
103 logger
.info('FROM %s %s %s %s' % (bottle
.request
.remote_addr
,
104 bottle
.request
.method
,
106 bottle
.response
.status
))
107 return actual_response
108 return _log_to_logger
110 class httpserver(threading
.Thread
):
111 def __init__(self
, db
, admin
=False, host
='localhost', port
=9090):
117 logger
= logging
.getLogger('openmano.http')
118 threading
.Thread
.__init
__(self
)
120 self
.port
= port
#Port where the listen service must be started
122 self
.name
= "http_admin"
125 #self.url_preffix = 'http://' + host + ':' + str(port) + url_base
127 #self.first_usable_connection_index = 10
128 #self.next_connection_index = self.first_usable_connection_index #The next connection index to be used
129 #Ensure that when the main program exits the thread will also exit
134 bottle
.install(log_to_logger
)
135 bottle
.run(host
=self
.host
, port
=self
.port
, debug
=False, quiet
=True)
137 def run_bottle(db
, host_
='localhost', port_
=9090):
138 '''used for launching in main thread, so that it can be debugged'''
141 bottle
.run(host
=host_
, port
=port_
, debug
=True) #quiet=True
144 @bottle.route(url_base
+ '/', method
='GET')
147 return 'works' #TODO: to be completed
153 def change_keys_http2db(data
, http_db
, reverse
=False):
154 '''Change keys of dictionary data acording to the key_dict values
155 This allow change from http interface names to database names.
156 When reverse is True, the change is otherwise
158 data: can be a dictionary or a list
159 http_db: is a dictionary with hhtp names as keys and database names as value
160 reverse: by default change is done from http api to database. If True change is done otherwise
161 Return: None, but data is modified'''
162 if type(data
) is tuple or type(data
) is list:
164 change_keys_http2db(d
, http_db
, reverse
)
165 elif type(data
) is dict or type(data
) is bottle
.FormsDict
:
167 for k
,v
in http_db
.items():
168 if v
in data
: data
[k
]=data
.pop(v
)
170 for k
,v
in http_db
.items():
171 if k
in data
: data
[v
]=data
.pop(k
)
173 def format_out(data
):
174 '''return string of dictionary data according to requested json, yaml, xml. By default json'''
175 logger
.debug("OUT: " + yaml
.safe_dump(data
, explicit_start
=True, indent
=4, default_flow_style
=False, tags
=False, encoding
='utf-8', allow_unicode
=True) )
176 if 'application/yaml' in bottle
.request
.headers
.get('Accept'):
177 bottle
.response
.content_type
='application/yaml'
178 return yaml
.safe_dump(data
, explicit_start
=True, indent
=4, default_flow_style
=False, tags
=False, encoding
='utf-8', allow_unicode
=True) #, canonical=True, default_style='"'
179 else: #by default json
180 bottle
.response
.content_type
='application/json'
181 #return data #json no style
182 return json
.dumps(data
, indent
=4) + "\n"
184 def format_in(default_schema
, version_fields
=None, version_dict_schema
=None):
185 ''' Parse the content of HTTP request against a json_schema
187 default_schema: The schema to be parsed by default if no version field is found in the client data
188 version_fields: If provided it contains a tuple or list with the fields to iterate across the client data to obtain the version
189 version_dict_schema: It contains a dictionary with the version as key, and json schema to apply as value
190 It can contain a None as key, and this is apply if the client data version does not match any key
192 user_data, used_schema: if the data is successfully decoded and matches the schema
193 launch a bottle abort if fails
195 #print "HEADERS :" + str(bottle.request.headers.items())
197 error_text
= "Invalid header format "
198 format_type
= bottle
.request
.headers
.get('Content-Type', 'application/json')
199 if 'application/json' in format_type
:
200 error_text
= "Invalid json format "
201 #Use the json decoder instead of bottle decoder because it informs about the location of error formats with a ValueError exception
202 client_data
= json
.load(bottle
.request
.body
)
203 #client_data = bottle.request.json()
204 elif 'application/yaml' in format_type
:
205 error_text
= "Invalid yaml format "
206 client_data
= yaml
.load(bottle
.request
.body
)
207 elif 'application/xml' in format_type
:
208 bottle
.abort(501, "Content-Type: application/xml not supported yet.")
210 logger
.warning('Content-Type ' + str(format_type
) + ' not supported.')
211 bottle
.abort(HTTP_Not_Acceptable
, 'Content-Type ' + str(format_type
) + ' not supported.')
213 #if client_data == None:
214 # bottle.abort(HTTP_Bad_Request, "Content error, empty")
217 logger
.debug('IN: %s', yaml
.safe_dump(client_data
, explicit_start
=True, indent
=4, default_flow_style
=False, tags
=False, encoding
='utf-8', allow_unicode
=True) )
218 #look for the client provider version
219 error_text
= "Invalid content "
220 client_version
= None
222 if version_fields
!= None:
223 client_version
= client_data
224 for field
in version_fields
:
225 if field
in client_version
:
226 client_version
= client_version
[field
]
230 if client_version
==None:
231 used_schema
=default_schema
232 elif version_dict_schema
!=None:
233 if client_version
in version_dict_schema
:
234 used_schema
= version_dict_schema
[client_version
]
235 elif None in version_dict_schema
:
236 used_schema
= version_dict_schema
[None]
237 if used_schema
==None:
238 bottle
.abort(HTTP_Bad_Request
, "Invalid schema version or missing version field")
240 js_v(client_data
, used_schema
)
241 return client_data
, used_schema
242 except (ValueError, yaml
.YAMLError
) as exc
:
243 error_text
+= str(exc
)
244 logger
.error(error_text
)
245 bottle
.abort(HTTP_Bad_Request
, error_text
)
246 except js_e
.ValidationError
as exc
:
247 logger
.error("validate_in error, jsonschema exception at '%s' '%s' ", str(exc
.path
), str(exc
.message
))
249 if len(exc
.path
)>0: error_pos
=" at " + ":".join(map(json
.dumps
, exc
.path
))
250 bottle
.abort(HTTP_Bad_Request
, error_text
+ exc
.message
+ error_pos
)
252 # bottle.abort(HTTP_Bad_Request, "Content error: Failed to parse Content-Type", error_pos)
255 def filter_query_string(qs
, http2db
, allowed
):
256 '''Process query string (qs) checking that contains only valid tokens for avoiding SQL injection
258 'qs': bottle.FormsDict variable to be processed. None or empty is considered valid
259 'http2db': dictionary with change from http API naming (dictionary key) to database naming(dictionary value)
260 'allowed': list of allowed string tokens (API http naming). All the keys of 'qs' must be one of 'allowed'
261 Return: A tuple with the (select,where,limit) to be use in a database query. All of then transformed to the database naming
262 select: list of items to retrieve, filtered by query string 'field=token'. If no 'field' is present, allowed list is returned
263 where: dictionary with key, value, taken from the query string token=value. Empty if nothing is provided
264 limit: limit dictated by user with the query string 'limit'. 100 by default
265 abort if not permited, using bottel.abort
270 #if type(qs) is not bottle.FormsDict:
271 # bottle.abort(HTTP_Internal_Server_Error, '!!!!!!!!!!!!!!invalid query string not a dictionary')
272 # #bottle.abort(HTTP_Internal_Server_Error, "call programmer")
275 select
+= qs
.getall(k
)
278 bottle
.abort(HTTP_Bad_Request
, "Invalid query string at 'field="+v
+"'")
283 bottle
.abort(HTTP_Bad_Request
, "Invalid query string at 'limit="+qs
[k
]+"'")
286 bottle
.abort(HTTP_Bad_Request
, "Invalid query string at '"+k
+"="+qs
[k
]+"'")
287 if qs
[k
]!="null": where
[k
]=qs
[k
]
289 if len(select
)==0: select
+= allowed
290 #change from http api to database naming
291 for i
in range(0,len(select
)):
293 if http2db
and k
in http2db
:
294 select
[i
] = http2db
[k
]
296 change_keys_http2db(where
, http2db
)
297 #print "filter_query_string", select,where,limit
299 return select
,where
,limit
301 @bottle.hook('after_request')
303 '''Don't know yet if really needed. Keep it just in case'''
304 bottle
.response
.headers
['Access-Control-Allow-Origin'] = '*'
310 @bottle.route(url_base
+ '/tenants', method
='GET')
311 def http_get_tenants():
312 logger
.debug('FROM %s %s %s', bottle
.request
.remote_addr
, bottle
.request
.method
, bottle
.request
.url
)
313 select_
,where_
,limit_
= filter_query_string(bottle
.request
.query
, None,
314 ('uuid','name','description','created_at') )
316 tenants
= mydb
.get_rows(FROM
='nfvo_tenants', SELECT
=select_
,WHERE
=where_
,LIMIT
=limit_
)
317 #change_keys_http2db(content, http2db_tenant, reverse=True)
318 convert_datetime2str(tenants
)
319 data
={'tenants' : tenants
}
320 return format_out(data
)
321 except db_base_Exception
as e
:
322 logger
.error("http_get_tenants error {}: {}".format(e
.http_code
, str(e
)))
323 bottle
.abort(e
.http_code
, str(e
))
324 except Exception as e
:
325 logger
.error("Unexpected exception: ", exc_info
=True)
326 bottle
.abort(HTTP_Internal_Server_Error
, type(e
).__name
__ + ": " + str(e
))
329 @bottle.route(url_base
+ '/tenants/<tenant_id>', method
='GET')
330 def http_get_tenant_id(tenant_id
):
331 '''get tenant details, can use both uuid or name'''
333 logger
.debug('FROM %s %s %s', bottle
.request
.remote_addr
, bottle
.request
.method
, bottle
.request
.url
)
335 tenant
= mydb
.get_table_by_uuid_name('nfvo_tenants', tenant_id
, "tenant")
336 #change_keys_http2db(content, http2db_tenant, reverse=True)
337 convert_datetime2str(tenant
)
338 data
={'tenant' : tenant
}
339 return format_out(data
)
340 except db_base_Exception
as e
:
341 logger
.error("http_get_tenant_id error {}: {}".format(e
.http_code
, str(e
)))
342 bottle
.abort(e
.http_code
, str(e
))
343 except Exception as e
:
344 logger
.error("Unexpected exception: ", exc_info
=True)
345 bottle
.abort(HTTP_Internal_Server_Error
, type(e
).__name
__ + ": " + str(e
))
348 @bottle.route(url_base
+ '/tenants', method
='POST')
349 def http_post_tenants():
350 '''insert a tenant into the catalogue. '''
352 logger
.debug('FROM %s %s %s', bottle
.request
.remote_addr
, bottle
.request
.method
, bottle
.request
.url
)
353 http_content
,_
= format_in( tenant_schema
)
354 r
= utils
.remove_extra_items(http_content
, tenant_schema
)
356 logger
.debug("Remove received extra items %s", str(r
))
358 data
= nfvo
.new_tenant(mydb
, http_content
['tenant'])
359 return http_get_tenant_id(data
)
360 except (nfvo
.NfvoException
, db_base_Exception
) as e
:
361 logger
.error("http_post_tenants error {}: {}".format(e
.http_code
, str(e
)))
362 bottle
.abort(e
.http_code
, str(e
))
363 except Exception as e
:
364 logger
.error("Unexpected exception: ", exc_info
=True)
365 bottle
.abort(HTTP_Internal_Server_Error
, type(e
).__name
__ + ": " + str(e
))
368 @bottle.route(url_base
+ '/tenants/<tenant_id>', method
='PUT')
369 def http_edit_tenant_id(tenant_id
):
370 '''edit tenant details, can use both uuid or name'''
372 logger
.debug('FROM %s %s %s', bottle
.request
.remote_addr
, bottle
.request
.method
, bottle
.request
.url
)
373 http_content
,_
= format_in( tenant_edit_schema
)
374 r
= utils
.remove_extra_items(http_content
, tenant_edit_schema
)
376 logger
.debug("Remove received extra items %s", str(r
))
378 #obtain data, check that only one exist
380 tenant
= mydb
.get_table_by_uuid_name('nfvo_tenants', tenant_id
)
382 tenant_id
= tenant
['uuid']
383 where
={'uuid': tenant
['uuid']}
384 mydb
.update_rows('nfvo_tenants', http_content
['tenant'], where
)
385 return http_get_tenant_id(tenant_id
)
386 except db_base_Exception
as e
:
387 logger
.error("http_edit_tenant_id error {}: {}".format(e
.http_code
, str(e
)))
388 bottle
.abort(e
.http_code
, str(e
))
389 except Exception as e
:
390 logger
.error("Unexpected exception: ", exc_info
=True)
391 bottle
.abort(HTTP_Internal_Server_Error
, type(e
).__name
__ + ": " + str(e
))
394 @bottle.route(url_base
+ '/tenants/<tenant_id>', method
='DELETE')
395 def http_delete_tenant_id(tenant_id
):
396 '''delete a tenant from database, can use both uuid or name'''
397 logger
.debug('FROM %s %s %s', bottle
.request
.remote_addr
, bottle
.request
.method
, bottle
.request
.url
)
399 data
= nfvo
.delete_tenant(mydb
, tenant_id
)
400 return format_out({"result":"tenant " + data
+ " deleted"})
401 except db_base_Exception
as e
:
402 logger
.error("http_delete_tenant_id error {}: {}".format(e
.http_code
, str(e
)))
403 bottle
.abort(e
.http_code
, str(e
))
404 except Exception as e
:
405 logger
.error("Unexpected exception: ", exc_info
=True)
406 bottle
.abort(HTTP_Internal_Server_Error
, type(e
).__name
__ + ": " + str(e
))
409 @bottle.route(url_base
+ '/<tenant_id>/datacenters', method
='GET')
410 def http_get_datacenters(tenant_id
):
411 logger
.debug('FROM %s %s %s', bottle
.request
.remote_addr
, bottle
.request
.method
, bottle
.request
.url
)
413 if tenant_id
!= 'any':
414 #check valid tenant_id
415 nfvo
.check_tenant(mydb
, tenant_id
)
416 select_
,where_
,limit_
= filter_query_string(bottle
.request
.query
, None,
417 ('uuid','name','vim_url','type','created_at') )
418 if tenant_id
!= 'any':
419 where_
['nfvo_tenant_id'] = tenant_id
420 if 'created_at' in select_
:
421 select_
[ select_
.index('created_at') ] = 'd.created_at as created_at'
422 if 'created_at' in where_
:
423 where_
['d.created_at'] = where_
.pop('created_at')
424 datacenters
= mydb
.get_rows(FROM
='datacenters as d join tenants_datacenters as td on d.uuid=td.datacenter_id',
425 SELECT
=select_
,WHERE
=where_
,LIMIT
=limit_
)
427 datacenters
= mydb
.get_rows(FROM
='datacenters',
428 SELECT
=select_
,WHERE
=where_
,LIMIT
=limit_
)
429 #change_keys_http2db(content, http2db_tenant, reverse=True)
430 convert_datetime2str(datacenters
)
431 data
={'datacenters' : datacenters
}
432 return format_out(data
)
433 except (nfvo
.NfvoException
, db_base_Exception
) as e
:
434 logger
.error("http_get_datacenters error {}: {}".format(e
.http_code
, str(e
)))
435 bottle
.abort(e
.http_code
, str(e
))
436 except Exception as e
:
437 logger
.error("Unexpected exception: ", exc_info
=True)
438 bottle
.abort(HTTP_Internal_Server_Error
, type(e
).__name
__ + ": " + str(e
))
441 @bottle.route(url_base
+ '/<tenant_id>/datacenters/<datacenter_id>', method
='GET')
442 def http_get_datacenter_id(tenant_id
, datacenter_id
):
443 '''get datacenter details, can use both uuid or name'''
444 logger
.debug('FROM %s %s %s', bottle
.request
.remote_addr
, bottle
.request
.method
, bottle
.request
.url
)
446 if tenant_id
!= 'any':
447 #check valid tenant_id
448 nfvo
.check_tenant(mydb
, tenant_id
)
450 what
= 'uuid' if utils
.check_valid_uuid(datacenter_id
) else 'name'
452 where_
[what
] = datacenter_id
453 select_
=['uuid', 'name','vim_url', 'vim_url_admin', 'type', 'config', 'description', 'd.created_at as created_at']
454 if tenant_id
!= 'any':
455 select_
.append("datacenter_tenant_id")
456 where_
['td.nfvo_tenant_id']= tenant_id
457 from_
='datacenters as d join tenants_datacenters as td on d.uuid=td.datacenter_id'
459 from_
='datacenters as d'
460 datacenters
= mydb
.get_rows(
465 if len(datacenters
)==0:
466 bottle
.abort( HTTP_Not_Found
, "No datacenter found for tenant with {} '{}'".format(what
, datacenter_id
) )
467 elif len(datacenters
)>1:
468 bottle
.abort( HTTP_Bad_Request
, "More than one datacenter found for tenant with {} '{}'".format(what
, datacenter_id
) )
469 datacenter
= datacenters
[0]
470 if tenant_id
!= 'any':
472 vim_tenants
= mydb
.get_rows(
473 SELECT
=("vim_tenant_name", "vim_tenant_id", "user"),
474 FROM
="datacenter_tenants",
475 WHERE
={"uuid": datacenters
[0]["datacenter_tenant_id"]},
476 ORDER_BY
=("created", ) )
477 del datacenter
["datacenter_tenant_id"]
478 datacenter
["vim_tenants"] = vim_tenants
480 if datacenter
['config'] != None:
482 config_dict
= yaml
.load(datacenter
['config'])
483 datacenter
['config'] = config_dict
485 logger
.error("Exception '%s' while trying to load config information", str(e
))
486 #change_keys_http2db(content, http2db_datacenter, reverse=True)
487 convert_datetime2str(datacenter
)
488 data
={'datacenter' : datacenter
}
489 return format_out(data
)
490 except (nfvo
.NfvoException
, db_base_Exception
) as e
:
491 logger
.error("http_get_datacenter_id error {}: {}".format(e
.http_code
, str(e
)))
492 bottle
.abort(e
.http_code
, str(e
))
493 except Exception as e
:
494 logger
.error("Unexpected exception: ", exc_info
=True)
495 bottle
.abort(HTTP_Internal_Server_Error
, type(e
).__name
__ + ": " + str(e
))
498 @bottle.route(url_base
+ '/datacenters', method
='POST')
499 def http_post_datacenters():
500 '''insert a tenant into the catalogue. '''
502 logger
.debug('FROM %s %s %s', bottle
.request
.remote_addr
, bottle
.request
.method
, bottle
.request
.url
)
503 http_content
,_
= format_in( datacenter_schema
)
504 r
= utils
.remove_extra_items(http_content
, datacenter_schema
)
506 logger
.debug("Remove received extra items %s", str(r
))
508 data
= nfvo
.new_datacenter(mydb
, http_content
['datacenter'])
509 return http_get_datacenter_id('any', data
)
510 except (nfvo
.NfvoException
, db_base_Exception
) as e
:
511 logger
.error("http_post_datacenters error {}: {}".format(e
.http_code
, str(e
)))
512 bottle
.abort(e
.http_code
, str(e
))
513 except Exception as e
:
514 logger
.error("Unexpected exception: ", exc_info
=True)
515 bottle
.abort(HTTP_Internal_Server_Error
, type(e
).__name
__ + ": " + str(e
))
518 @bottle.route(url_base
+ '/datacenters/<datacenter_id_name>', method
='PUT')
519 def http_edit_datacenter_id(datacenter_id_name
):
520 '''edit datacenter details, can use both uuid or name'''
521 logger
.debug('FROM %s %s %s', bottle
.request
.remote_addr
, bottle
.request
.method
, bottle
.request
.url
)
523 http_content
,_
= format_in( datacenter_edit_schema
)
524 r
= utils
.remove_extra_items(http_content
, datacenter_edit_schema
)
526 logger
.debug("Remove received extra items %s", str(r
))
529 datacenter_id
= nfvo
.edit_datacenter(mydb
, datacenter_id_name
, http_content
['datacenter'])
530 return http_get_datacenter_id('any', datacenter_id
)
531 except (nfvo
.NfvoException
, db_base_Exception
) as e
:
532 logger
.error("http_edit_datacenter_id error {}: {}".format(e
.http_code
, str(e
)))
533 bottle
.abort(e
.http_code
, str(e
))
534 except Exception as e
:
535 logger
.error("Unexpected exception: ", exc_info
=True)
536 bottle
.abort(HTTP_Internal_Server_Error
, type(e
).__name
__ + ": " + str(e
))
539 @bottle.route(url_base
+ '/<tenant_id>/datacenters/<datacenter_id>/networks', method
='GET') #deprecated
540 @bottle.route(url_base
+ '/<tenant_id>/datacenters/<datacenter_id>/netmaps', method
='GET')
541 @bottle.route(url_base
+ '/<tenant_id>/datacenters/<datacenter_id>/netmaps/<netmap_id>', method
='GET')
542 def http_getnetmap_datacenter_id(tenant_id
, datacenter_id
, netmap_id
=None):
543 '''get datacenter networks, can use both uuid or name'''
544 logger
.debug('FROM %s %s %s', bottle
.request
.remote_addr
, bottle
.request
.method
, bottle
.request
.url
)
547 datacenter_dict
= mydb
.get_table_by_uuid_name('datacenters', datacenter_id
, "datacenter")
548 where_
= {"datacenter_id":datacenter_dict
['uuid']}
550 if utils
.check_valid_uuid(netmap_id
):
551 where_
["uuid"] = netmap_id
553 where_
["name"] = netmap_id
554 netmaps
=mydb
.get_rows(FROM
='datacenter_nets',
555 SELECT
=('name','vim_net_id as vim_id', 'uuid', 'type','multipoint','shared','description', 'created_at'),
557 convert_datetime2str(netmaps
)
558 utils
.convert_str2boolean(netmaps
, ('shared', 'multipoint') )
559 if netmap_id
and len(netmaps
)==1:
560 data
={'netmap' : netmaps
[0]}
561 elif netmap_id
and len(netmaps
)==0:
562 bottle
.abort(HTTP_Not_Found
, "No netmap found with " + " and ".join(map(lambda x
: str(x
[0])+": "+str(x
[1]), where_
.iteritems())) )
565 data
={'netmaps' : netmaps
}
566 return format_out(data
)
567 except (nfvo
.NfvoException
, db_base_Exception
) as e
:
568 logger
.error("http_getnetwork_datacenter_id error {}: {}".format(e
.http_code
, str(e
)))
569 bottle
.abort(e
.http_code
, str(e
))
570 except Exception as e
:
571 logger
.error("Unexpected exception: ", exc_info
=True)
572 bottle
.abort(HTTP_Internal_Server_Error
, type(e
).__name
__ + ": " + str(e
))
575 @bottle.route(url_base
+ '/<tenant_id>/datacenters/<datacenter_id>/netmaps', method
='DELETE')
576 @bottle.route(url_base
+ '/<tenant_id>/datacenters/<datacenter_id>/netmaps/<netmap_id>', method
='DELETE')
577 def http_delnetmap_datacenter_id(tenant_id
, datacenter_id
, netmap_id
=None):
578 '''get datacenter networks, can use both uuid or name'''
579 logger
.debug('FROM %s %s %s', bottle
.request
.remote_addr
, bottle
.request
.method
, bottle
.request
.url
)
582 datacenter_dict
= mydb
.get_table_by_uuid_name('datacenters', datacenter_id
, "datacenter")
583 where_
= {"datacenter_id":datacenter_dict
['uuid']}
585 if utils
.check_valid_uuid(netmap_id
):
586 where_
["uuid"] = netmap_id
588 where_
["name"] = netmap_id
589 #change_keys_http2db(content, http2db_tenant, reverse=True)
590 deleted
= mydb
.delete_row(FROM
='datacenter_nets', WHERE
= where_
)
591 if deleted
== 0 and netmap_id
:
592 bottle
.abort(HTTP_Not_Found
, "No netmap found with " + " and ".join(map(lambda x
: str(x
[0])+": "+str(x
[1]), where_
.iteritems())) )
594 return format_out({"result": "netmap %s deleted" % netmap_id
})
596 return format_out({"result": "%d netmap deleted" % deleted
})
597 except (nfvo
.NfvoException
, db_base_Exception
) as e
:
598 logger
.error("http_delnetmap_datacenter_id error {}: {}".format(e
.http_code
, str(e
)))
599 bottle
.abort(e
.http_code
, str(e
))
600 except Exception as e
:
601 logger
.error("Unexpected exception: ", exc_info
=True)
602 bottle
.abort(HTTP_Internal_Server_Error
, type(e
).__name
__ + ": " + str(e
))
605 @bottle.route(url_base
+ '/<tenant_id>/datacenters/<datacenter_id>/netmaps/upload', method
='POST')
606 def http_uploadnetmap_datacenter_id(tenant_id
, datacenter_id
):
607 logger
.debug('FROM %s %s %s', bottle
.request
.remote_addr
, bottle
.request
.method
, bottle
.request
.url
)
609 netmaps
= nfvo
.datacenter_new_netmap(mydb
, tenant_id
, datacenter_id
, None)
610 convert_datetime2str(netmaps
)
611 utils
.convert_str2boolean(netmaps
, ('shared', 'multipoint') )
612 data
={'netmaps' : netmaps
}
613 return format_out(data
)
614 except (nfvo
.NfvoException
, db_base_Exception
) as e
:
615 logger
.error("http_uploadnetmap_datacenter_id error {}: {}".format(e
.http_code
, str(e
)))
616 bottle
.abort(e
.http_code
, str(e
))
617 except Exception as e
:
618 logger
.error("Unexpected exception: ", exc_info
=True)
619 bottle
.abort(HTTP_Internal_Server_Error
, type(e
).__name
__ + ": " + str(e
))
622 @bottle.route(url_base
+ '/<tenant_id>/datacenters/<datacenter_id>/netmaps', method
='POST')
623 def http_postnetmap_datacenter_id(tenant_id
, datacenter_id
):
624 '''creates a new netmap'''
625 logger
.debug('FROM %s %s %s', bottle
.request
.remote_addr
, bottle
.request
.method
, bottle
.request
.url
)
627 http_content
,_
= format_in( netmap_new_schema
)
628 r
= utils
.remove_extra_items(http_content
, netmap_new_schema
)
630 logger
.debug("Remove received extra items %s", str(r
))
632 #obtain data, check that only one exist
633 netmaps
= nfvo
.datacenter_new_netmap(mydb
, tenant_id
, datacenter_id
, http_content
)
634 convert_datetime2str(netmaps
)
635 utils
.convert_str2boolean(netmaps
, ('shared', 'multipoint') )
636 data
={'netmaps' : netmaps
}
637 return format_out(data
)
638 except (nfvo
.NfvoException
, db_base_Exception
) as e
:
639 logger
.error("http_postnetmap_datacenter_id error {}: {}".format(e
.http_code
, str(e
)))
640 bottle
.abort(e
.http_code
, str(e
))
641 except Exception as e
:
642 logger
.error("Unexpected exception: ", exc_info
=True)
643 bottle
.abort(HTTP_Internal_Server_Error
, type(e
).__name
__ + ": " + str(e
))
646 @bottle.route(url_base
+ '/<tenant_id>/datacenters/<datacenter_id>/netmaps/<netmap_id>', method
='PUT')
647 def http_putnettmap_datacenter_id(tenant_id
, datacenter_id
, netmap_id
):
649 logger
.debug('FROM %s %s %s', bottle
.request
.remote_addr
, bottle
.request
.method
, bottle
.request
.url
)
651 http_content
,_
= format_in( netmap_edit_schema
)
652 r
= utils
.remove_extra_items(http_content
, netmap_edit_schema
)
654 logger
.debug("Remove received extra items %s", str(r
))
656 #obtain data, check that only one exist
658 nfvo
.datacenter_edit_netmap(mydb
, tenant_id
, datacenter_id
, netmap_id
, http_content
)
659 return http_getnetmap_datacenter_id(tenant_id
, datacenter_id
, netmap_id
)
660 except (nfvo
.NfvoException
, db_base_Exception
) as e
:
661 logger
.error("http_putnettmap_datacenter_id error {}: {}".format(e
.http_code
, str(e
)))
662 bottle
.abort(e
.http_code
, str(e
))
663 except Exception as e
:
664 logger
.error("Unexpected exception: ", exc_info
=True)
665 bottle
.abort(HTTP_Internal_Server_Error
, type(e
).__name
__ + ": " + str(e
))
668 @bottle.route(url_base
+ '/<tenant_id>/datacenters/<datacenter_id>/action', method
='POST')
669 def http_action_datacenter_id(tenant_id
, datacenter_id
):
670 '''perform an action over datacenter, can use both uuid or name'''
671 logger
.debug('FROM %s %s %s', bottle
.request
.remote_addr
, bottle
.request
.method
, bottle
.request
.url
)
673 http_content
,_
= format_in( datacenter_action_schema
)
674 r
= utils
.remove_extra_items(http_content
, datacenter_action_schema
)
676 logger
.debug("Remove received extra items %s", str(r
))
678 #obtain data, check that only one exist
679 result
= nfvo
.datacenter_action(mydb
, tenant_id
, datacenter_id
, http_content
)
680 if 'net-update' in http_content
:
681 return http_getnetmap_datacenter_id(datacenter_id
)
683 return format_out(result
)
684 except (nfvo
.NfvoException
, db_base_Exception
) as e
:
685 logger
.error("http_action_datacenter_id error {}: {}".format(e
.http_code
, str(e
)))
686 bottle
.abort(e
.http_code
, str(e
))
687 except Exception as e
:
688 logger
.error("Unexpected exception: ", exc_info
=True)
689 bottle
.abort(HTTP_Internal_Server_Error
, type(e
).__name
__ + ": " + str(e
))
692 @bottle.route(url_base
+ '/datacenters/<datacenter_id>', method
='DELETE')
693 def http_delete_datacenter_id( datacenter_id
):
694 '''delete a tenant from database, can use both uuid or name'''
696 logger
.debug('FROM %s %s %s', bottle
.request
.remote_addr
, bottle
.request
.method
, bottle
.request
.url
)
698 data
= nfvo
.delete_datacenter(mydb
, datacenter_id
)
699 return format_out({"result":"datacenter '" + data
+ "' deleted"})
700 except (nfvo
.NfvoException
, db_base_Exception
) as e
:
701 logger
.error("http_delete_datacenter_id error {}: {}".format(e
.http_code
, str(e
)))
702 bottle
.abort(e
.http_code
, str(e
))
703 except Exception as e
:
704 logger
.error("Unexpected exception: ", exc_info
=True)
705 bottle
.abort(HTTP_Internal_Server_Error
, type(e
).__name
__ + ": " + str(e
))
708 @bottle.route(url_base
+ '/<tenant_id>/datacenters/<datacenter_id>', method
='POST')
709 def http_associate_datacenters(tenant_id
, datacenter_id
):
710 '''associate an existing datacenter to a this tenant. '''
711 logger
.debug('FROM %s %s %s', bottle
.request
.remote_addr
, bottle
.request
.method
, bottle
.request
.url
)
713 http_content
,_
= format_in( datacenter_associate_schema
)
714 r
= utils
.remove_extra_items(http_content
, datacenter_associate_schema
)
716 logger
.debug("Remove received extra items %s", str(r
))
718 id_
= nfvo
.associate_datacenter_to_tenant(mydb
, tenant_id
, datacenter_id
,
719 http_content
['datacenter'].get('vim_tenant'),
720 http_content
['datacenter'].get('vim_tenant_name'),
721 http_content
['datacenter'].get('vim_username'),
722 http_content
['datacenter'].get('vim_password')
724 return http_get_datacenter_id(tenant_id
, id_
)
725 except (nfvo
.NfvoException
, db_base_Exception
) as e
:
726 logger
.error("http_associate_datacenters error {}: {}".format(e
.http_code
, str(e
)))
727 bottle
.abort(e
.http_code
, str(e
))
728 except Exception as e
:
729 logger
.error("Unexpected exception: ", exc_info
=True)
730 bottle
.abort(HTTP_Internal_Server_Error
, type(e
).__name
__ + ": " + str(e
))
733 @bottle.route(url_base
+ '/<tenant_id>/datacenters/<datacenter_id>', method
='DELETE')
734 def http_deassociate_datacenters(tenant_id
, datacenter_id
):
735 '''deassociate an existing datacenter to a this tenant. '''
736 logger
.debug('FROM %s %s %s', bottle
.request
.remote_addr
, bottle
.request
.method
, bottle
.request
.url
)
738 data
= nfvo
.deassociate_datacenter_to_tenant(mydb
, tenant_id
, datacenter_id
)
739 return format_out({"result": data
})
740 except (nfvo
.NfvoException
, db_base_Exception
) as e
:
741 logger
.error("http_deassociate_datacenters error {}: {}".format(e
.http_code
, str(e
)))
742 bottle
.abort(e
.http_code
, str(e
))
743 except Exception as e
:
744 logger
.error("Unexpected exception: ", exc_info
=True)
745 bottle
.abort(HTTP_Internal_Server_Error
, type(e
).__name
__ + ": " + str(e
))
748 @bottle.route(url_base
+ '/<tenant_id>/vim/<datacenter_id>/<item>', method
='GET')
749 @bottle.route(url_base
+ '/<tenant_id>/vim/<datacenter_id>/<item>/<name>', method
='GET')
750 def http_get_vim_items(tenant_id
, datacenter_id
, item
, name
=None):
751 logger
.debug('FROM %s %s %s', bottle
.request
.remote_addr
, bottle
.request
.method
, bottle
.request
.url
)
753 data
= nfvo
.vim_action_get(mydb
, tenant_id
, datacenter_id
, item
, name
)
754 return format_out(data
)
755 except (nfvo
.NfvoException
, db_base_Exception
) as e
:
756 logger
.error("http_get_vim_items error {}: {}".format(e
.http_code
, str(e
)))
757 bottle
.abort(e
.http_code
, str(e
))
758 except Exception as e
:
759 logger
.error("Unexpected exception: ", exc_info
=True)
760 bottle
.abort(HTTP_Internal_Server_Error
, type(e
).__name
__ + ": " + str(e
))
763 @bottle.route(url_base
+ '/<tenant_id>/vim/<datacenter_id>/<item>/<name>', method
='DELETE')
764 def http_del_vim_items(tenant_id
, datacenter_id
, item
, name
):
765 logger
.debug('FROM %s %s %s', bottle
.request
.remote_addr
, bottle
.request
.method
, bottle
.request
.url
)
767 data
= nfvo
.vim_action_delete(mydb
, tenant_id
, datacenter_id
, item
, name
)
768 return format_out({"result":data
})
769 except (nfvo
.NfvoException
, db_base_Exception
) as e
:
770 logger
.error("http_del_vim_items error {}: {}".format(e
.http_code
, str(e
)))
771 bottle
.abort(e
.http_code
, str(e
))
772 except Exception as e
:
773 logger
.error("Unexpected exception: ", exc_info
=True)
774 bottle
.abort(HTTP_Internal_Server_Error
, type(e
).__name
__ + ": " + str(e
))
777 @bottle.route(url_base
+ '/<tenant_id>/vim/<datacenter_id>/<item>', method
='POST')
778 def http_post_vim_items(tenant_id
, datacenter_id
, item
):
779 logger
.debug('FROM %s %s %s', bottle
.request
.remote_addr
, bottle
.request
.method
, bottle
.request
.url
)
780 http_content
,_
= format_in( object_schema
)
782 data
= nfvo
.vim_action_create(mydb
, tenant_id
, datacenter_id
, item
, http_content
)
783 return format_out(data
)
784 except (nfvo
.NfvoException
, db_base_Exception
) as e
:
785 logger
.error("http_post_vim_items error {}: {}".format(e
.http_code
, str(e
)))
786 bottle
.abort(e
.http_code
, str(e
))
787 except Exception as e
:
788 logger
.error("Unexpected exception: ", exc_info
=True)
789 bottle
.abort(HTTP_Internal_Server_Error
, type(e
).__name
__ + ": " + str(e
))
792 @bottle.route(url_base
+ '/<tenant_id>/vnfs', method
='GET')
793 def http_get_vnfs(tenant_id
):
794 logger
.debug('FROM %s %s %s', bottle
.request
.remote_addr
, bottle
.request
.method
, bottle
.request
.url
)
796 if tenant_id
!= 'any':
797 #check valid tenant_id
798 nfvo
.check_tenant(mydb
, tenant_id
)
799 select_
,where_
,limit_
= filter_query_string(bottle
.request
.query
, None,
800 ('uuid','name','description','public', "tenant_id", "created_at") )
802 if tenant_id
!= "any":
803 where_or
["tenant_id"] = tenant_id
804 where_or
["public"] = True
805 vnfs
= mydb
.get_rows(FROM
='vnfs', SELECT
=select_
,WHERE
=where_
,WHERE_OR
=where_or
, WHERE_AND_OR
="AND",LIMIT
=limit_
)
806 #change_keys_http2db(content, http2db_vnf, reverse=True)
807 utils
.convert_str2boolean(vnfs
, ('public',))
808 convert_datetime2str(vnfs
)
810 return format_out(data
)
811 except (nfvo
.NfvoException
, db_base_Exception
) as e
:
812 logger
.error("http_get_vnfs error {}: {}".format(e
.http_code
, str(e
)))
813 bottle
.abort(e
.http_code
, str(e
))
814 except Exception as e
:
815 logger
.error("Unexpected exception: ", exc_info
=True)
816 bottle
.abort(HTTP_Internal_Server_Error
, type(e
).__name
__ + ": " + str(e
))
819 @bottle.route(url_base
+ '/<tenant_id>/vnfs/<vnf_id>', method
='GET')
820 def http_get_vnf_id(tenant_id
,vnf_id
):
821 '''get vnf details, can use both uuid or name'''
822 logger
.debug('FROM %s %s %s', bottle
.request
.remote_addr
, bottle
.request
.method
, bottle
.request
.url
)
824 vnf
= nfvo
.get_vnf_id(mydb
,tenant_id
,vnf_id
)
825 utils
.convert_str2boolean(vnf
, ('public',))
826 convert_datetime2str(vnf
)
827 return format_out(vnf
)
828 except (nfvo
.NfvoException
, db_base_Exception
) as e
:
829 logger
.error("http_get_vnf_id error {}: {}".format(e
.http_code
, str(e
)))
830 bottle
.abort(e
.http_code
, str(e
))
831 except Exception as e
:
832 logger
.error("Unexpected exception: ", exc_info
=True)
833 bottle
.abort(HTTP_Internal_Server_Error
, type(e
).__name
__ + ": " + str(e
))
836 @bottle.route(url_base
+ '/<tenant_id>/vnfs', method
='POST')
837 def http_post_vnfs(tenant_id
):
838 '''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'''
839 #print "Parsing the YAML file of the VNF"
841 logger
.debug('FROM %s %s %s', bottle
.request
.remote_addr
, bottle
.request
.method
, bottle
.request
.url
)
842 http_content
, used_schema
= format_in( vnfd_schema_v01
, ("schema_version",), {"0.2": vnfd_schema_v02
})
843 r
= utils
.remove_extra_items(http_content
, used_schema
)
845 logger
.debug("Remove received extra items %s", str(r
))
847 if used_schema
== vnfd_schema_v01
:
848 vnf_id
= nfvo
.new_vnf(mydb
,tenant_id
,http_content
)
849 elif used_schema
== vnfd_schema_v02
:
850 vnf_id
= nfvo
.new_vnf_v02(mydb
,tenant_id
,http_content
)
852 logger
.warning('Unexpected schema_version: %s', http_content
.get("schema_version"))
853 bottle
.abort(HTTP_Bad_Request
, "Invalid schema version")
854 return http_get_vnf_id(tenant_id
, vnf_id
)
855 except (nfvo
.NfvoException
, db_base_Exception
) as e
:
856 logger
.error("http_post_vnfs error {}: {}".format(e
.http_code
, str(e
)))
857 bottle
.abort(e
.http_code
, str(e
))
858 except Exception as e
:
859 logger
.error("Unexpected exception: ", exc_info
=True)
860 bottle
.abort(HTTP_Internal_Server_Error
, type(e
).__name
__ + ": " + str(e
))
863 @bottle.route(url_base
+ '/<tenant_id>/vnfs/<vnf_id>', method
='DELETE')
864 def http_delete_vnf_id(tenant_id
,vnf_id
):
865 '''delete a vnf from database, and images and flavors in VIM when appropriate, can use both uuid or name'''
866 logger
.debug('FROM %s %s %s', bottle
.request
.remote_addr
, bottle
.request
.method
, bottle
.request
.url
)
867 #check valid tenant_id and deletes the vnf, including images,
869 data
= nfvo
.delete_vnf(mydb
,tenant_id
,vnf_id
)
870 #print json.dumps(data, indent=4)
871 return format_out({"result":"VNF " + data
+ " deleted"})
872 except (nfvo
.NfvoException
, db_base_Exception
) as e
:
873 logger
.error("http_delete_vnf_id error {}: {}".format(e
.http_code
, str(e
)))
874 bottle
.abort(e
.http_code
, str(e
))
875 except Exception as e
:
876 logger
.error("Unexpected exception: ", exc_info
=True)
877 bottle
.abort(HTTP_Internal_Server_Error
, type(e
).__name
__ + ": " + str(e
))
880 #@bottle.route(url_base + '/<tenant_id>/hosts/topology', method='GET')
881 #@bottle.route(url_base + '/<tenant_id>/physicalview/Madrid-Alcantara', method='GET')
882 @bottle.route(url_base
+ '/<tenant_id>/physicalview/<datacenter>', method
='GET')
883 def http_get_hosts(tenant_id
, datacenter
):
884 '''get the tidvim host hopology from the vim.'''
885 logger
.debug('FROM %s %s %s', bottle
.request
.remote_addr
, bottle
.request
.method
, bottle
.request
.url
)
886 #print "http_get_hosts received by tenant " + tenant_id + ' datacenter ' + datacenter
888 if datacenter
== 'treeview':
889 data
= nfvo
.get_hosts(mydb
, tenant_id
)
891 #openmano-gui is using a hardcoded value for the datacenter
892 result
, data
= nfvo
.get_hosts_info(mydb
, tenant_id
) #, datacenter)
895 #print "http_get_hosts error %d %s" % (-result, data)
896 bottle
.abort(-result
, data
)
898 convert_datetime2str(data
)
899 #print json.dumps(data, indent=4)
900 return format_out(data
)
901 except (nfvo
.NfvoException
, db_base_Exception
) as e
:
902 logger
.error("http_get_hosts error {}: {}".format(e
.http_code
, str(e
)))
903 bottle
.abort(e
.http_code
, str(e
))
904 except Exception as e
:
905 logger
.error("Unexpected exception: ", exc_info
=True)
906 bottle
.abort(HTTP_Internal_Server_Error
, type(e
).__name
__ + ": " + str(e
))
909 @bottle.route(url_base
+ '/<path:path>', method
='OPTIONS')
910 def http_options_deploy(path
):
911 '''For some reason GUI web ask for OPTIONS that must be responded'''
912 #TODO: check correct path, and correct headers request
913 logger
.debug('FROM %s %s %s', bottle
.request
.remote_addr
, bottle
.request
.method
, bottle
.request
.url
)
914 bottle
.response
.set_header('Access-Control-Allow-Methods','POST, GET, PUT, DELETE, OPTIONS')
915 bottle
.response
.set_header('Accept','application/yaml,application/json')
916 bottle
.response
.set_header('Content-Type','application/yaml,application/json')
917 bottle
.response
.set_header('Access-Control-Allow-Headers','content-type')
918 bottle
.response
.set_header('Access-Control-Allow-Origin','*')
921 @bottle.route(url_base
+ '/<tenant_id>/topology/deploy', method
='POST')
922 def http_post_deploy(tenant_id
):
923 '''post topology deploy.'''
924 logger
.debug('FROM %s %s %s', bottle
.request
.remote_addr
, bottle
.request
.method
, bottle
.request
.url
)
926 http_content
, used_schema
= format_in( nsd_schema_v01
, ("schema_version",), {2: nsd_schema_v02
})
927 #r = utils.remove_extra_items(http_content, used_schema)
928 #if r is not None: print "http_post_deploy: Warning: remove extra items ", r
929 #print "http_post_deploy input: ", http_content
932 scenario_id
= nfvo
.new_scenario(mydb
, tenant_id
, http_content
)
933 instance
= nfvo
.start_scenario(mydb
, tenant_id
, scenario_id
, http_content
['name'], http_content
['name'])
934 #print json.dumps(data, indent=4)
935 return format_out(instance
)
936 except (nfvo
.NfvoException
, db_base_Exception
) as e
:
937 logger
.error("http_post_deploy error {}: {}".format(e
.http_code
, str(e
)))
938 bottle
.abort(e
.http_code
, str(e
))
939 except Exception as e
:
940 logger
.error("Unexpected exception: ", exc_info
=True)
941 bottle
.abort(HTTP_Internal_Server_Error
, type(e
).__name
__ + ": " + str(e
))
944 @bottle.route(url_base
+ '/<tenant_id>/topology/verify', method
='POST')
945 def http_post_verify(tenant_id
):
947 # '''post topology verify'''
948 # print "http_post_verify by tenant " + tenant_id + ' datacenter ' + datacenter
949 logger
.debug('FROM %s %s %s', bottle
.request
.remote_addr
, bottle
.request
.method
, bottle
.request
.url
)
956 @bottle.route(url_base
+ '/<tenant_id>/scenarios', method
='POST')
957 def http_post_scenarios(tenant_id
):
958 '''add a scenario into the catalogue. Creates the scenario and its internal structure in the OPENMANO DB'''
959 logger
.debug('FROM %s %s %s', bottle
.request
.remote_addr
, bottle
.request
.method
, bottle
.request
.url
)
960 http_content
, used_schema
= format_in( nsd_schema_v01
, ("schema_version",), {2: nsd_schema_v02
, "0.3": nsd_schema_v03
})
961 #r = utils.remove_extra_items(http_content, used_schema)
962 #if r is not None: print "http_post_scenarios: Warning: remove extra items ", r
963 #print "http_post_scenarios input: ", http_content
965 if used_schema
== nsd_schema_v01
:
966 scenario_id
= nfvo
.new_scenario(mydb
, tenant_id
, http_content
)
967 elif used_schema
== nsd_schema_v02
:
968 scenario_id
= nfvo
.new_scenario_v02(mydb
, tenant_id
, http_content
)
969 elif used_schema
== nsd_schema_v03
:
970 scenario_id
= nfvo
.new_scenario_v03(mydb
, tenant_id
, http_content
)
972 logger
.warning('Unexpected schema_version: %s', http_content
.get("schema_version"))
973 bottle
.abort(HTTP_Bad_Request
, "Invalid schema version")
974 #print json.dumps(data, indent=4)
975 #return format_out(data)
976 return http_get_scenario_id(tenant_id
, scenario_id
)
977 except (nfvo
.NfvoException
, db_base_Exception
) as e
:
978 logger
.error("http_post_scenarios error {}: {}".format(e
.http_code
, str(e
)))
979 bottle
.abort(e
.http_code
, str(e
))
980 except Exception as e
:
981 logger
.error("Unexpected exception: ", exc_info
=True)
982 bottle
.abort(HTTP_Internal_Server_Error
, type(e
).__name
__ + ": " + str(e
))
985 @bottle.route(url_base
+ '/<tenant_id>/scenarios/<scenario_id>/action', method
='POST')
986 def http_post_scenario_action(tenant_id
, scenario_id
):
987 '''take an action over a scenario'''
988 logger
.debug('FROM %s %s %s', bottle
.request
.remote_addr
, bottle
.request
.method
, bottle
.request
.url
)
989 #check valid tenant_id
991 nfvo
.check_tenant(mydb
, tenant_id
)
993 http_content
,_
= format_in( scenario_action_schema
)
994 r
= utils
.remove_extra_items(http_content
, scenario_action_schema
)
996 logger
.debug("Remove received extra items %s", str(r
))
997 if "start" in http_content
:
998 data
= nfvo
.start_scenario(mydb
, tenant_id
, scenario_id
, http_content
['start']['instance_name'], \
999 http_content
['start'].get('description',http_content
['start']['instance_name']),
1000 http_content
['start'].get('datacenter') )
1001 return format_out(data
)
1002 elif "deploy" in http_content
: #Equivalent to start
1003 data
= nfvo
.start_scenario(mydb
, tenant_id
, scenario_id
, http_content
['deploy']['instance_name'],
1004 http_content
['deploy'].get('description',http_content
['deploy']['instance_name']),
1005 http_content
['deploy'].get('datacenter') )
1006 return format_out(data
)
1007 elif "reserve" in http_content
: #Reserve resources
1008 data
= nfvo
.start_scenario(mydb
, tenant_id
, scenario_id
, http_content
['reserve']['instance_name'],
1009 http_content
['reserve'].get('description',http_content
['reserve']['instance_name']),
1010 http_content
['reserve'].get('datacenter'), startvms
=False )
1011 return format_out(data
)
1012 elif "verify" in http_content
: #Equivalent to start and then delete
1013 data
= nfvo
.start_scenario(mydb
, tenant_id
, scenario_id
, http_content
['verify']['instance_name'],
1014 http_content
['verify'].get('description',http_content
['verify']['instance_name']),
1015 http_content
['verify'].get('datacenter'), startvms
=False )
1016 instance_id
= data
['uuid']
1017 nfvo
.delete_instance(mydb
, tenant_id
,instance_id
)
1018 return format_out({"result":"Verify OK"})
1019 except (nfvo
.NfvoException
, db_base_Exception
) as e
:
1020 logger
.error("http_post_scenario_action error {}: {}".format(e
.http_code
, str(e
)))
1021 bottle
.abort(e
.http_code
, str(e
))
1022 except Exception as e
:
1023 logger
.error("Unexpected exception: ", exc_info
=True)
1024 bottle
.abort(HTTP_Internal_Server_Error
, type(e
).__name
__ + ": " + str(e
))
1027 @bottle.route(url_base
+ '/<tenant_id>/scenarios', method
='GET')
1028 def http_get_scenarios(tenant_id
):
1029 '''get scenarios list'''
1030 logger
.debug('FROM %s %s %s', bottle
.request
.remote_addr
, bottle
.request
.method
, bottle
.request
.url
)
1032 #check valid tenant_id
1033 if tenant_id
!= "any":
1034 nfvo
.check_tenant(mydb
, tenant_id
)
1036 s
,w
,l
=filter_query_string(bottle
.request
.query
, None, ('uuid', 'name', 'description', 'tenant_id', 'created_at', 'public'))
1038 if tenant_id
!= "any":
1039 where_or
["tenant_id"] = tenant_id
1040 where_or
["public"] = True
1041 scenarios
= mydb
.get_rows(SELECT
=s
, WHERE
=w
, WHERE_OR
=where_or
, WHERE_AND_OR
="AND", LIMIT
=l
, FROM
='scenarios')
1042 convert_datetime2str(scenarios
)
1043 utils
.convert_str2boolean(scenarios
, ('public',) )
1044 data
={'scenarios':scenarios
}
1045 #print json.dumps(scenarios, indent=4)
1046 return format_out(data
)
1047 except (nfvo
.NfvoException
, db_base_Exception
) as e
:
1048 logger
.error("http_get_scenarios error {}: {}".format(e
.http_code
, str(e
)))
1049 bottle
.abort(e
.http_code
, str(e
))
1050 except Exception as e
:
1051 logger
.error("Unexpected exception: ", exc_info
=True)
1052 bottle
.abort(HTTP_Internal_Server_Error
, type(e
).__name
__ + ": " + str(e
))
1055 @bottle.route(url_base
+ '/<tenant_id>/scenarios/<scenario_id>', method
='GET')
1056 def http_get_scenario_id(tenant_id
, scenario_id
):
1057 '''get scenario details, can use both uuid or name'''
1058 logger
.debug('FROM %s %s %s', bottle
.request
.remote_addr
, bottle
.request
.method
, bottle
.request
.url
)
1060 #check valid tenant_id
1061 if tenant_id
!= "any":
1062 nfvo
.check_tenant(mydb
, tenant_id
)
1064 scenario
= mydb
.get_scenario(scenario_id
, tenant_id
)
1065 convert_datetime2str(scenario
)
1066 data
={'scenario' : scenario
}
1067 return format_out(data
)
1068 except (nfvo
.NfvoException
, db_base_Exception
) as e
:
1069 logger
.error("http_get_scenarios error {}: {}".format(e
.http_code
, str(e
)))
1070 bottle
.abort(e
.http_code
, str(e
))
1071 except Exception as e
:
1072 logger
.error("Unexpected exception: ", exc_info
=True)
1073 bottle
.abort(HTTP_Internal_Server_Error
, type(e
).__name
__ + ": " + str(e
))
1076 @bottle.route(url_base
+ '/<tenant_id>/scenarios/<scenario_id>', method
='DELETE')
1077 def http_delete_scenario_id(tenant_id
, scenario_id
):
1078 '''delete a scenario from database, can use both uuid or name'''
1080 #check valid tenant_id
1081 if tenant_id
!= "any":
1082 nfvo
.check_tenant(mydb
, tenant_id
)
1084 data
= mydb
.delete_scenario(scenario_id
, tenant_id
)
1085 #print json.dumps(data, indent=4)
1086 return format_out({"result":"scenario " + data
+ " deleted"})
1087 except (nfvo
.NfvoException
, db_base_Exception
) as e
:
1088 logger
.error("http_delete_scenario_id error {}: {}".format(e
.http_code
, str(e
)))
1089 bottle
.abort(e
.http_code
, str(e
))
1090 except Exception as e
:
1091 logger
.error("Unexpected exception: ", exc_info
=True)
1092 bottle
.abort(HTTP_Internal_Server_Error
, type(e
).__name
__ + ": " + str(e
))
1095 @bottle.route(url_base
+ '/<tenant_id>/scenarios/<scenario_id>', method
='PUT')
1096 def http_put_scenario_id(tenant_id
, scenario_id
):
1097 '''edit an existing scenario id'''
1098 logger
.debug('FROM %s %s %s', bottle
.request
.remote_addr
, bottle
.request
.method
, bottle
.request
.url
)
1099 http_content
,_
= format_in( scenario_edit_schema
)
1100 #r = utils.remove_extra_items(http_content, scenario_edit_schema)
1101 #if r is not None: print "http_put_scenario_id: Warning: remove extra items ", r
1102 #print "http_put_scenario_id input: ", http_content
1104 nfvo
.edit_scenario(mydb
, tenant_id
, scenario_id
, http_content
)
1105 #print json.dumps(data, indent=4)
1106 #return format_out(data)
1107 return http_get_scenario_id(tenant_id
, scenario_id
)
1108 except (nfvo
.NfvoException
, db_base_Exception
) as e
:
1109 logger
.error("http_put_scenario_id error {}: {}".format(e
.http_code
, str(e
)))
1110 bottle
.abort(e
.http_code
, str(e
))
1111 except Exception as e
:
1112 logger
.error("Unexpected exception: ", exc_info
=True)
1113 bottle
.abort(HTTP_Internal_Server_Error
, type(e
).__name
__ + ": " + str(e
))
1115 @bottle.route(url_base
+ '/<tenant_id>/instances', method
='POST')
1116 def http_post_instances(tenant_id
):
1117 '''create an instance-scenario'''
1118 logger
.debug('FROM %s %s %s', bottle
.request
.remote_addr
, bottle
.request
.method
, bottle
.request
.url
)
1120 #check valid tenant_id
1121 if tenant_id
!= "any":
1122 nfvo
.check_tenant(mydb
, tenant_id
)
1124 http_content
,used_schema
= format_in( instance_scenario_create_schema_v01
)
1125 r
= utils
.remove_extra_items(http_content
, used_schema
)
1127 logger
.warning("http_post_instances: Warning: remove extra items %s", str(r
))
1128 data
= nfvo
.create_instance(mydb
, tenant_id
, http_content
["instance"])
1129 return format_out(data
)
1130 except (nfvo
.NfvoException
, db_base_Exception
) as e
:
1131 logger
.error("http_post_instances error {}: {}".format(e
.http_code
, str(e
)))
1132 bottle
.abort(e
.http_code
, str(e
))
1133 except Exception as e
:
1134 logger
.error("Unexpected exception: ", exc_info
=True)
1135 bottle
.abort(HTTP_Internal_Server_Error
, type(e
).__name
__ + ": " + str(e
))
1140 @bottle.route(url_base
+ '/<tenant_id>/instances', method
='GET')
1141 def http_get_instances(tenant_id
):
1142 '''get instance list'''
1144 #check valid tenant_id
1145 if tenant_id
!= "any":
1146 nfvo
.check_tenant(mydb
, tenant_id
)
1148 s
,w
,l
=filter_query_string(bottle
.request
.query
, None, ('uuid', 'name', 'scenario_id', 'tenant_id', 'description', 'created_at'))
1149 if tenant_id
!= "any":
1150 w
['tenant_id'] = tenant_id
1151 instances
= mydb
.get_rows(SELECT
=s
, WHERE
=w
, LIMIT
=l
, FROM
='instance_scenarios')
1152 convert_datetime2str(instances
)
1153 utils
.convert_str2boolean(instances
, ('public',) )
1154 data
={'instances':instances
}
1155 return format_out(data
)
1156 except (nfvo
.NfvoException
, db_base_Exception
) as e
:
1157 logger
.error("http_get_instances error {}: {}".format(e
.http_code
, str(e
)))
1158 bottle
.abort(e
.http_code
, str(e
))
1159 except Exception as e
:
1160 logger
.error("Unexpected exception: ", exc_info
=True)
1161 bottle
.abort(HTTP_Internal_Server_Error
, type(e
).__name
__ + ": " + str(e
))
1164 @bottle.route(url_base
+ '/<tenant_id>/instances/<instance_id>', method
='GET')
1165 def http_get_instance_id(tenant_id
, instance_id
):
1166 '''get instances details, can use both uuid or name'''
1167 logger
.debug('FROM %s %s %s', bottle
.request
.remote_addr
, bottle
.request
.method
, bottle
.request
.url
)
1169 #check valid tenant_id
1170 if tenant_id
!= "any":
1171 nfvo
.check_tenant(mydb
, tenant_id
)
1172 if tenant_id
== "any":
1174 #obtain data (first time is only to check that the instance exists)
1175 instance_dict
= mydb
.get_instance_scenario(instance_id
, tenant_id
, verbose
=True)
1177 nfvo
.refresh_instance(mydb
, tenant_id
, instance_dict
)
1178 except (nfvo
.NfvoException
, db_base_Exception
) as e
:
1179 logger
.warn("nfvo.refresh_instance couldn't refresh the status of the instance: %s" % str(e
))
1180 #obtain data with results upated
1181 instance
= mydb
.get_instance_scenario(instance_id
, tenant_id
)
1182 convert_datetime2str(instance
)
1183 #print json.dumps(instance, indent=4)
1184 return format_out(instance
)
1185 except (nfvo
.NfvoException
, db_base_Exception
) as e
:
1186 logger
.error("http_get_instance_id error {}: {}".format(e
.http_code
, str(e
)))
1187 bottle
.abort(e
.http_code
, str(e
))
1188 except Exception as e
:
1189 logger
.error("Unexpected exception: ", exc_info
=True)
1190 bottle
.abort(HTTP_Internal_Server_Error
, type(e
).__name
__ + ": " + str(e
))
1193 @bottle.route(url_base
+ '/<tenant_id>/instances/<instance_id>', method
='DELETE')
1194 def http_delete_instance_id(tenant_id
, instance_id
):
1195 '''delete instance from VIM and from database, can use both uuid or name'''
1196 logger
.debug('FROM %s %s %s', bottle
.request
.remote_addr
, bottle
.request
.method
, bottle
.request
.url
)
1198 #check valid tenant_id
1199 if tenant_id
!= "any":
1200 nfvo
.check_tenant(mydb
, tenant_id
)
1201 if tenant_id
== "any":
1204 message
= nfvo
.delete_instance(mydb
, tenant_id
,instance_id
)
1205 return format_out({"result":message
})
1206 except (nfvo
.NfvoException
, db_base_Exception
) as e
:
1207 logger
.error("http_delete_instance_id error {}: {}".format(e
.http_code
, str(e
)))
1208 bottle
.abort(e
.http_code
, str(e
))
1209 except Exception as e
:
1210 logger
.error("Unexpected exception: ", exc_info
=True)
1211 bottle
.abort(HTTP_Internal_Server_Error
, type(e
).__name
__ + ": " + str(e
))
1214 @bottle.route(url_base
+ '/<tenant_id>/instances/<instance_id>/action', method
='POST')
1215 def http_post_instance_scenario_action(tenant_id
, instance_id
):
1216 '''take an action over a scenario instance'''
1217 logger
.debug('FROM %s %s %s', bottle
.request
.remote_addr
, bottle
.request
.method
, bottle
.request
.url
)
1219 #check valid tenant_id
1220 if tenant_id
!= "any":
1221 nfvo
.check_tenant(mydb
, tenant_id
)
1224 http_content
,_
= format_in( instance_scenario_action_schema
)
1225 r
= utils
.remove_extra_items(http_content
, instance_scenario_action_schema
)
1227 logger
.debug("Remove received extra items %s", str(r
))
1228 #print "http_post_instance_scenario_action input: ", http_content
1230 instance
= mydb
.get_instance_scenario(instance_id
, tenant_id
)
1231 instance_id
= instance
["uuid"]
1233 data
= nfvo
.instance_action(mydb
, tenant_id
, instance_id
, http_content
)
1234 return format_out(data
)
1235 except (nfvo
.NfvoException
, db_base_Exception
) as e
:
1236 logger
.error("http_post_instance_scenario_action error {}: {}".format(e
.http_code
, str(e
)))
1237 bottle
.abort(e
.http_code
, str(e
))
1238 except Exception as e
:
1239 logger
.error("Unexpected exception: ", exc_info
=True)
1240 bottle
.abort(HTTP_Internal_Server_Error
, type(e
).__name
__ + ": " + str(e
))
1252 def error400(error
):
1253 e
={"error":{"code":error
.status_code
, "type":error
.status
, "description":error
.body
}}
1254 bottle
.response
.headers
['Access-Control-Allow-Origin'] = '*'
1255 return format_out(e
)