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', 'd.config as 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", "passwd", "config"),
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
479 for vim_tenant
in vim_tenants
:
480 if vim_tenant
["passwd"]:
481 vim_tenant
["passwd"] = "******"
482 if vim_tenant
['config'] != None:
484 config_dict
= yaml
.load(vim_tenant
['config'])
485 vim_tenant
['config'] = config_dict
486 except Exception as e
:
487 logger
.error("Exception '%s' while trying to load config information", str(e
))
489 if datacenter
['config'] != None:
491 config_dict
= yaml
.load(datacenter
['config'])
492 datacenter
['config'] = config_dict
493 except Exception as e
:
494 logger
.error("Exception '%s' while trying to load config information", str(e
))
495 #change_keys_http2db(content, http2db_datacenter, reverse=True)
496 convert_datetime2str(datacenter
)
497 data
={'datacenter' : datacenter
}
498 return format_out(data
)
499 except (nfvo
.NfvoException
, db_base_Exception
) as e
:
500 logger
.error("http_get_datacenter_id error {}: {}".format(e
.http_code
, str(e
)))
501 bottle
.abort(e
.http_code
, str(e
))
502 except Exception as e
:
503 logger
.error("Unexpected exception: ", exc_info
=True)
504 bottle
.abort(HTTP_Internal_Server_Error
, type(e
).__name
__ + ": " + str(e
))
507 @bottle.route(url_base
+ '/datacenters', method
='POST')
508 def http_post_datacenters():
509 '''insert a tenant into the catalogue. '''
511 logger
.debug('FROM %s %s %s', bottle
.request
.remote_addr
, bottle
.request
.method
, bottle
.request
.url
)
512 http_content
,_
= format_in( datacenter_schema
)
513 r
= utils
.remove_extra_items(http_content
, datacenter_schema
)
515 logger
.debug("Remove received extra items %s", str(r
))
517 data
= nfvo
.new_datacenter(mydb
, http_content
['datacenter'])
518 return http_get_datacenter_id('any', data
)
519 except (nfvo
.NfvoException
, db_base_Exception
) as e
:
520 logger
.error("http_post_datacenters error {}: {}".format(e
.http_code
, str(e
)))
521 bottle
.abort(e
.http_code
, str(e
))
522 except Exception as e
:
523 logger
.error("Unexpected exception: ", exc_info
=True)
524 bottle
.abort(HTTP_Internal_Server_Error
, type(e
).__name
__ + ": " + str(e
))
527 @bottle.route(url_base
+ '/datacenters/<datacenter_id_name>', method
='PUT')
528 def http_edit_datacenter_id(datacenter_id_name
):
529 '''edit datacenter details, can use both uuid or name'''
530 logger
.debug('FROM %s %s %s', bottle
.request
.remote_addr
, bottle
.request
.method
, bottle
.request
.url
)
532 http_content
,_
= format_in( datacenter_edit_schema
)
533 r
= utils
.remove_extra_items(http_content
, datacenter_edit_schema
)
535 logger
.debug("Remove received extra items %s", str(r
))
538 datacenter_id
= nfvo
.edit_datacenter(mydb
, datacenter_id_name
, http_content
['datacenter'])
539 return http_get_datacenter_id('any', datacenter_id
)
540 except (nfvo
.NfvoException
, db_base_Exception
) as e
:
541 logger
.error("http_edit_datacenter_id error {}: {}".format(e
.http_code
, str(e
)))
542 bottle
.abort(e
.http_code
, str(e
))
543 except Exception as e
:
544 logger
.error("Unexpected exception: ", exc_info
=True)
545 bottle
.abort(HTTP_Internal_Server_Error
, type(e
).__name
__ + ": " + str(e
))
548 @bottle.route(url_base
+ '/<tenant_id>/datacenters/<datacenter_id>/networks', method
='GET') #deprecated
549 @bottle.route(url_base
+ '/<tenant_id>/datacenters/<datacenter_id>/netmaps', method
='GET')
550 @bottle.route(url_base
+ '/<tenant_id>/datacenters/<datacenter_id>/netmaps/<netmap_id>', method
='GET')
551 def http_getnetmap_datacenter_id(tenant_id
, datacenter_id
, netmap_id
=None):
552 '''get datacenter networks, can use both uuid or name'''
553 logger
.debug('FROM %s %s %s', bottle
.request
.remote_addr
, bottle
.request
.method
, bottle
.request
.url
)
556 datacenter_dict
= mydb
.get_table_by_uuid_name('datacenters', datacenter_id
, "datacenter")
557 where_
= {"datacenter_id":datacenter_dict
['uuid']}
559 if utils
.check_valid_uuid(netmap_id
):
560 where_
["uuid"] = netmap_id
562 where_
["name"] = netmap_id
563 netmaps
=mydb
.get_rows(FROM
='datacenter_nets',
564 SELECT
=('name','vim_net_id as vim_id', 'uuid', 'type','multipoint','shared','description', 'created_at'),
566 convert_datetime2str(netmaps
)
567 utils
.convert_str2boolean(netmaps
, ('shared', 'multipoint') )
568 if netmap_id
and len(netmaps
)==1:
569 data
={'netmap' : netmaps
[0]}
570 elif netmap_id
and len(netmaps
)==0:
571 bottle
.abort(HTTP_Not_Found
, "No netmap found with " + " and ".join(map(lambda x
: str(x
[0])+": "+str(x
[1]), where_
.iteritems())) )
574 data
={'netmaps' : netmaps
}
575 return format_out(data
)
576 except (nfvo
.NfvoException
, db_base_Exception
) as e
:
577 logger
.error("http_getnetwork_datacenter_id error {}: {}".format(e
.http_code
, str(e
)))
578 bottle
.abort(e
.http_code
, str(e
))
579 except Exception as e
:
580 logger
.error("Unexpected exception: ", exc_info
=True)
581 bottle
.abort(HTTP_Internal_Server_Error
, type(e
).__name
__ + ": " + str(e
))
584 @bottle.route(url_base
+ '/<tenant_id>/datacenters/<datacenter_id>/netmaps', method
='DELETE')
585 @bottle.route(url_base
+ '/<tenant_id>/datacenters/<datacenter_id>/netmaps/<netmap_id>', method
='DELETE')
586 def http_delnetmap_datacenter_id(tenant_id
, datacenter_id
, netmap_id
=None):
587 '''get datacenter networks, can use both uuid or name'''
588 logger
.debug('FROM %s %s %s', bottle
.request
.remote_addr
, bottle
.request
.method
, bottle
.request
.url
)
591 datacenter_dict
= mydb
.get_table_by_uuid_name('datacenters', datacenter_id
, "datacenter")
592 where_
= {"datacenter_id":datacenter_dict
['uuid']}
594 if utils
.check_valid_uuid(netmap_id
):
595 where_
["uuid"] = netmap_id
597 where_
["name"] = netmap_id
598 #change_keys_http2db(content, http2db_tenant, reverse=True)
599 deleted
= mydb
.delete_row(FROM
='datacenter_nets', WHERE
= where_
)
600 if deleted
== 0 and netmap_id
:
601 bottle
.abort(HTTP_Not_Found
, "No netmap found with " + " and ".join(map(lambda x
: str(x
[0])+": "+str(x
[1]), where_
.iteritems())) )
603 return format_out({"result": "netmap %s deleted" % netmap_id
})
605 return format_out({"result": "%d netmap deleted" % deleted
})
606 except (nfvo
.NfvoException
, db_base_Exception
) as e
:
607 logger
.error("http_delnetmap_datacenter_id error {}: {}".format(e
.http_code
, str(e
)))
608 bottle
.abort(e
.http_code
, str(e
))
609 except Exception as e
:
610 logger
.error("Unexpected exception: ", exc_info
=True)
611 bottle
.abort(HTTP_Internal_Server_Error
, type(e
).__name
__ + ": " + str(e
))
614 @bottle.route(url_base
+ '/<tenant_id>/datacenters/<datacenter_id>/netmaps/upload', method
='POST')
615 def http_uploadnetmap_datacenter_id(tenant_id
, datacenter_id
):
616 logger
.debug('FROM %s %s %s', bottle
.request
.remote_addr
, bottle
.request
.method
, bottle
.request
.url
)
618 netmaps
= nfvo
.datacenter_new_netmap(mydb
, tenant_id
, datacenter_id
, None)
619 convert_datetime2str(netmaps
)
620 utils
.convert_str2boolean(netmaps
, ('shared', 'multipoint') )
621 data
={'netmaps' : netmaps
}
622 return format_out(data
)
623 except (nfvo
.NfvoException
, db_base_Exception
) as e
:
624 logger
.error("http_uploadnetmap_datacenter_id error {}: {}".format(e
.http_code
, str(e
)))
625 bottle
.abort(e
.http_code
, str(e
))
626 except Exception as e
:
627 logger
.error("Unexpected exception: ", exc_info
=True)
628 bottle
.abort(HTTP_Internal_Server_Error
, type(e
).__name
__ + ": " + str(e
))
631 @bottle.route(url_base
+ '/<tenant_id>/datacenters/<datacenter_id>/netmaps', method
='POST')
632 def http_postnetmap_datacenter_id(tenant_id
, datacenter_id
):
633 '''creates a new netmap'''
634 logger
.debug('FROM %s %s %s', bottle
.request
.remote_addr
, bottle
.request
.method
, bottle
.request
.url
)
636 http_content
,_
= format_in( netmap_new_schema
)
637 r
= utils
.remove_extra_items(http_content
, netmap_new_schema
)
639 logger
.debug("Remove received extra items %s", str(r
))
641 #obtain data, check that only one exist
642 netmaps
= nfvo
.datacenter_new_netmap(mydb
, tenant_id
, datacenter_id
, http_content
)
643 convert_datetime2str(netmaps
)
644 utils
.convert_str2boolean(netmaps
, ('shared', 'multipoint') )
645 data
={'netmaps' : netmaps
}
646 return format_out(data
)
647 except (nfvo
.NfvoException
, db_base_Exception
) as e
:
648 logger
.error("http_postnetmap_datacenter_id error {}: {}".format(e
.http_code
, str(e
)))
649 bottle
.abort(e
.http_code
, str(e
))
650 except Exception as e
:
651 logger
.error("Unexpected exception: ", exc_info
=True)
652 bottle
.abort(HTTP_Internal_Server_Error
, type(e
).__name
__ + ": " + str(e
))
655 @bottle.route(url_base
+ '/<tenant_id>/datacenters/<datacenter_id>/netmaps/<netmap_id>', method
='PUT')
656 def http_putnettmap_datacenter_id(tenant_id
, datacenter_id
, netmap_id
):
658 logger
.debug('FROM %s %s %s', bottle
.request
.remote_addr
, bottle
.request
.method
, bottle
.request
.url
)
660 http_content
,_
= format_in( netmap_edit_schema
)
661 r
= utils
.remove_extra_items(http_content
, netmap_edit_schema
)
663 logger
.debug("Remove received extra items %s", str(r
))
665 #obtain data, check that only one exist
667 nfvo
.datacenter_edit_netmap(mydb
, tenant_id
, datacenter_id
, netmap_id
, http_content
)
668 return http_getnetmap_datacenter_id(tenant_id
, datacenter_id
, netmap_id
)
669 except (nfvo
.NfvoException
, db_base_Exception
) as e
:
670 logger
.error("http_putnettmap_datacenter_id error {}: {}".format(e
.http_code
, str(e
)))
671 bottle
.abort(e
.http_code
, str(e
))
672 except Exception as e
:
673 logger
.error("Unexpected exception: ", exc_info
=True)
674 bottle
.abort(HTTP_Internal_Server_Error
, type(e
).__name
__ + ": " + str(e
))
677 @bottle.route(url_base
+ '/<tenant_id>/datacenters/<datacenter_id>/action', method
='POST')
678 def http_action_datacenter_id(tenant_id
, datacenter_id
):
679 '''perform an action over datacenter, can use both uuid or name'''
680 logger
.debug('FROM %s %s %s', bottle
.request
.remote_addr
, bottle
.request
.method
, bottle
.request
.url
)
682 http_content
,_
= format_in( datacenter_action_schema
)
683 r
= utils
.remove_extra_items(http_content
, datacenter_action_schema
)
685 logger
.debug("Remove received extra items %s", str(r
))
687 #obtain data, check that only one exist
688 result
= nfvo
.datacenter_action(mydb
, tenant_id
, datacenter_id
, http_content
)
689 if 'net-update' in http_content
:
690 return http_getnetmap_datacenter_id(datacenter_id
)
692 return format_out(result
)
693 except (nfvo
.NfvoException
, db_base_Exception
) as e
:
694 logger
.error("http_action_datacenter_id error {}: {}".format(e
.http_code
, str(e
)))
695 bottle
.abort(e
.http_code
, str(e
))
696 except Exception as e
:
697 logger
.error("Unexpected exception: ", exc_info
=True)
698 bottle
.abort(HTTP_Internal_Server_Error
, type(e
).__name
__ + ": " + str(e
))
701 @bottle.route(url_base
+ '/datacenters/<datacenter_id>', method
='DELETE')
702 def http_delete_datacenter_id( datacenter_id
):
703 '''delete a tenant from database, can use both uuid or name'''
705 logger
.debug('FROM %s %s %s', bottle
.request
.remote_addr
, bottle
.request
.method
, bottle
.request
.url
)
707 data
= nfvo
.delete_datacenter(mydb
, datacenter_id
)
708 return format_out({"result":"datacenter '" + data
+ "' deleted"})
709 except (nfvo
.NfvoException
, db_base_Exception
) as e
:
710 logger
.error("http_delete_datacenter_id error {}: {}".format(e
.http_code
, str(e
)))
711 bottle
.abort(e
.http_code
, str(e
))
712 except Exception as e
:
713 logger
.error("Unexpected exception: ", exc_info
=True)
714 bottle
.abort(HTTP_Internal_Server_Error
, type(e
).__name
__ + ": " + str(e
))
717 @bottle.route(url_base
+ '/<tenant_id>/datacenters/<datacenter_id>', method
='POST')
718 def http_associate_datacenters(tenant_id
, datacenter_id
):
719 '''associate an existing datacenter to a this tenant. '''
720 logger
.debug('FROM %s %s %s', bottle
.request
.remote_addr
, bottle
.request
.method
, bottle
.request
.url
)
722 http_content
,_
= format_in( datacenter_associate_schema
)
723 r
= utils
.remove_extra_items(http_content
, datacenter_associate_schema
)
725 logger
.debug("Remove received extra items %s", str(r
))
727 id_
= nfvo
.associate_datacenter_to_tenant(mydb
, tenant_id
, datacenter_id
,
728 http_content
['datacenter'].get('vim_tenant'),
729 http_content
['datacenter'].get('vim_tenant_name'),
730 http_content
['datacenter'].get('vim_username'),
731 http_content
['datacenter'].get('vim_password'),
732 http_content
['datacenter'].get('config')
734 return http_get_datacenter_id(tenant_id
, id_
)
735 except (nfvo
.NfvoException
, db_base_Exception
) as e
:
736 logger
.error("http_associate_datacenters error {}: {}".format(e
.http_code
, str(e
)))
737 bottle
.abort(e
.http_code
, str(e
))
738 except Exception as e
:
739 logger
.error("Unexpected exception: ", exc_info
=True)
740 bottle
.abort(HTTP_Internal_Server_Error
, type(e
).__name
__ + ": " + str(e
))
743 @bottle.route(url_base
+ '/<tenant_id>/datacenters/<datacenter_id>', method
='DELETE')
744 def http_deassociate_datacenters(tenant_id
, datacenter_id
):
745 '''deassociate an existing datacenter to a this tenant. '''
746 logger
.debug('FROM %s %s %s', bottle
.request
.remote_addr
, bottle
.request
.method
, bottle
.request
.url
)
748 data
= nfvo
.deassociate_datacenter_to_tenant(mydb
, tenant_id
, datacenter_id
)
749 return format_out({"result": data
})
750 except (nfvo
.NfvoException
, db_base_Exception
) as e
:
751 logger
.error("http_deassociate_datacenters error {}: {}".format(e
.http_code
, str(e
)))
752 bottle
.abort(e
.http_code
, str(e
))
753 except Exception as e
:
754 logger
.error("Unexpected exception: ", exc_info
=True)
755 bottle
.abort(HTTP_Internal_Server_Error
, type(e
).__name
__ + ": " + str(e
))
758 @bottle.route(url_base
+ '/<tenant_id>/vim/<datacenter_id>/<item>', method
='GET')
759 @bottle.route(url_base
+ '/<tenant_id>/vim/<datacenter_id>/<item>/<name>', method
='GET')
760 def http_get_vim_items(tenant_id
, datacenter_id
, item
, name
=None):
761 logger
.debug('FROM %s %s %s', bottle
.request
.remote_addr
, bottle
.request
.method
, bottle
.request
.url
)
763 data
= nfvo
.vim_action_get(mydb
, tenant_id
, datacenter_id
, item
, name
)
764 return format_out(data
)
765 except (nfvo
.NfvoException
, db_base_Exception
) as e
:
766 logger
.error("http_get_vim_items error {}: {}".format(e
.http_code
, str(e
)))
767 bottle
.abort(e
.http_code
, str(e
))
768 except Exception as e
:
769 logger
.error("Unexpected exception: ", exc_info
=True)
770 bottle
.abort(HTTP_Internal_Server_Error
, type(e
).__name
__ + ": " + str(e
))
773 @bottle.route(url_base
+ '/<tenant_id>/vim/<datacenter_id>/<item>/<name>', method
='DELETE')
774 def http_del_vim_items(tenant_id
, datacenter_id
, item
, name
):
775 logger
.debug('FROM %s %s %s', bottle
.request
.remote_addr
, bottle
.request
.method
, bottle
.request
.url
)
777 data
= nfvo
.vim_action_delete(mydb
, tenant_id
, datacenter_id
, item
, name
)
778 return format_out({"result":data
})
779 except (nfvo
.NfvoException
, db_base_Exception
) as e
:
780 logger
.error("http_del_vim_items error {}: {}".format(e
.http_code
, str(e
)))
781 bottle
.abort(e
.http_code
, str(e
))
782 except Exception as e
:
783 logger
.error("Unexpected exception: ", exc_info
=True)
784 bottle
.abort(HTTP_Internal_Server_Error
, type(e
).__name
__ + ": " + str(e
))
787 @bottle.route(url_base
+ '/<tenant_id>/vim/<datacenter_id>/<item>', method
='POST')
788 def http_post_vim_items(tenant_id
, datacenter_id
, item
):
789 logger
.debug('FROM %s %s %s', bottle
.request
.remote_addr
, bottle
.request
.method
, bottle
.request
.url
)
790 http_content
,_
= format_in( object_schema
)
792 data
= nfvo
.vim_action_create(mydb
, tenant_id
, datacenter_id
, item
, http_content
)
793 return format_out(data
)
794 except (nfvo
.NfvoException
, db_base_Exception
) as e
:
795 logger
.error("http_post_vim_items error {}: {}".format(e
.http_code
, str(e
)))
796 bottle
.abort(e
.http_code
, str(e
))
797 except Exception as e
:
798 logger
.error("Unexpected exception: ", exc_info
=True)
799 bottle
.abort(HTTP_Internal_Server_Error
, type(e
).__name
__ + ": " + str(e
))
802 @bottle.route(url_base
+ '/<tenant_id>/vnfs', method
='GET')
803 def http_get_vnfs(tenant_id
):
804 logger
.debug('FROM %s %s %s', bottle
.request
.remote_addr
, bottle
.request
.method
, bottle
.request
.url
)
806 if tenant_id
!= 'any':
807 #check valid tenant_id
808 nfvo
.check_tenant(mydb
, tenant_id
)
809 select_
,where_
,limit_
= filter_query_string(bottle
.request
.query
, None,
810 ('uuid','name','description','public', "tenant_id", "created_at") )
812 if tenant_id
!= "any":
813 where_or
["tenant_id"] = tenant_id
814 where_or
["public"] = True
815 vnfs
= mydb
.get_rows(FROM
='vnfs', SELECT
=select_
,WHERE
=where_
,WHERE_OR
=where_or
, WHERE_AND_OR
="AND",LIMIT
=limit_
)
816 #change_keys_http2db(content, http2db_vnf, reverse=True)
817 utils
.convert_str2boolean(vnfs
, ('public',))
818 convert_datetime2str(vnfs
)
820 return format_out(data
)
821 except (nfvo
.NfvoException
, db_base_Exception
) as e
:
822 logger
.error("http_get_vnfs error {}: {}".format(e
.http_code
, str(e
)))
823 bottle
.abort(e
.http_code
, str(e
))
824 except Exception as e
:
825 logger
.error("Unexpected exception: ", exc_info
=True)
826 bottle
.abort(HTTP_Internal_Server_Error
, type(e
).__name
__ + ": " + str(e
))
829 @bottle.route(url_base
+ '/<tenant_id>/vnfs/<vnf_id>', method
='GET')
830 def http_get_vnf_id(tenant_id
,vnf_id
):
831 '''get vnf details, can use both uuid or name'''
832 logger
.debug('FROM %s %s %s', bottle
.request
.remote_addr
, bottle
.request
.method
, bottle
.request
.url
)
834 vnf
= nfvo
.get_vnf_id(mydb
,tenant_id
,vnf_id
)
835 utils
.convert_str2boolean(vnf
, ('public',))
836 convert_datetime2str(vnf
)
837 return format_out(vnf
)
838 except (nfvo
.NfvoException
, db_base_Exception
) as e
:
839 logger
.error("http_get_vnf_id error {}: {}".format(e
.http_code
, str(e
)))
840 bottle
.abort(e
.http_code
, str(e
))
841 except Exception as e
:
842 logger
.error("Unexpected exception: ", exc_info
=True)
843 bottle
.abort(HTTP_Internal_Server_Error
, type(e
).__name
__ + ": " + str(e
))
846 @bottle.route(url_base
+ '/<tenant_id>/vnfs', method
='POST')
847 def http_post_vnfs(tenant_id
):
848 '''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'''
849 #print "Parsing the YAML file of the VNF"
851 logger
.debug('FROM %s %s %s', bottle
.request
.remote_addr
, bottle
.request
.method
, bottle
.request
.url
)
852 http_content
, used_schema
= format_in( vnfd_schema_v01
, ("schema_version",), {"0.2": vnfd_schema_v02
})
853 r
= utils
.remove_extra_items(http_content
, used_schema
)
855 logger
.debug("Remove received extra items %s", str(r
))
857 if used_schema
== vnfd_schema_v01
:
858 vnf_id
= nfvo
.new_vnf(mydb
,tenant_id
,http_content
)
859 elif used_schema
== vnfd_schema_v02
:
860 vnf_id
= nfvo
.new_vnf_v02(mydb
,tenant_id
,http_content
)
862 logger
.warning('Unexpected schema_version: %s', http_content
.get("schema_version"))
863 bottle
.abort(HTTP_Bad_Request
, "Invalid schema version")
864 return http_get_vnf_id(tenant_id
, vnf_id
)
865 except (nfvo
.NfvoException
, db_base_Exception
) as e
:
866 logger
.error("http_post_vnfs error {}: {}".format(e
.http_code
, str(e
)))
867 bottle
.abort(e
.http_code
, str(e
))
868 except Exception as e
:
869 logger
.error("Unexpected exception: ", exc_info
=True)
870 bottle
.abort(HTTP_Internal_Server_Error
, type(e
).__name
__ + ": " + str(e
))
873 @bottle.route(url_base
+ '/<tenant_id>/vnfs/<vnf_id>', method
='DELETE')
874 def http_delete_vnf_id(tenant_id
,vnf_id
):
875 '''delete a vnf from database, and images and flavors in VIM when appropriate, can use both uuid or name'''
876 logger
.debug('FROM %s %s %s', bottle
.request
.remote_addr
, bottle
.request
.method
, bottle
.request
.url
)
877 #check valid tenant_id and deletes the vnf, including images,
879 data
= nfvo
.delete_vnf(mydb
,tenant_id
,vnf_id
)
880 #print json.dumps(data, indent=4)
881 return format_out({"result":"VNF " + data
+ " deleted"})
882 except (nfvo
.NfvoException
, db_base_Exception
) as e
:
883 logger
.error("http_delete_vnf_id error {}: {}".format(e
.http_code
, str(e
)))
884 bottle
.abort(e
.http_code
, str(e
))
885 except Exception as e
:
886 logger
.error("Unexpected exception: ", exc_info
=True)
887 bottle
.abort(HTTP_Internal_Server_Error
, type(e
).__name
__ + ": " + str(e
))
890 #@bottle.route(url_base + '/<tenant_id>/hosts/topology', method='GET')
891 #@bottle.route(url_base + '/<tenant_id>/physicalview/Madrid-Alcantara', method='GET')
892 @bottle.route(url_base
+ '/<tenant_id>/physicalview/<datacenter>', method
='GET')
893 def http_get_hosts(tenant_id
, datacenter
):
894 '''get the tidvim host hopology from the vim.'''
895 logger
.debug('FROM %s %s %s', bottle
.request
.remote_addr
, bottle
.request
.method
, bottle
.request
.url
)
896 #print "http_get_hosts received by tenant " + tenant_id + ' datacenter ' + datacenter
898 if datacenter
== 'treeview':
899 data
= nfvo
.get_hosts(mydb
, tenant_id
)
901 #openmano-gui is using a hardcoded value for the datacenter
902 result
, data
= nfvo
.get_hosts_info(mydb
, tenant_id
) #, datacenter)
905 #print "http_get_hosts error %d %s" % (-result, data)
906 bottle
.abort(-result
, data
)
908 convert_datetime2str(data
)
909 #print json.dumps(data, indent=4)
910 return format_out(data
)
911 except (nfvo
.NfvoException
, db_base_Exception
) as e
:
912 logger
.error("http_get_hosts error {}: {}".format(e
.http_code
, str(e
)))
913 bottle
.abort(e
.http_code
, str(e
))
914 except Exception as e
:
915 logger
.error("Unexpected exception: ", exc_info
=True)
916 bottle
.abort(HTTP_Internal_Server_Error
, type(e
).__name
__ + ": " + str(e
))
919 @bottle.route(url_base
+ '/<path:path>', method
='OPTIONS')
920 def http_options_deploy(path
):
921 '''For some reason GUI web ask for OPTIONS that must be responded'''
922 #TODO: check correct path, and correct headers request
923 logger
.debug('FROM %s %s %s', bottle
.request
.remote_addr
, bottle
.request
.method
, bottle
.request
.url
)
924 bottle
.response
.set_header('Access-Control-Allow-Methods','POST, GET, PUT, DELETE, OPTIONS')
925 bottle
.response
.set_header('Accept','application/yaml,application/json')
926 bottle
.response
.set_header('Content-Type','application/yaml,application/json')
927 bottle
.response
.set_header('Access-Control-Allow-Headers','content-type')
928 bottle
.response
.set_header('Access-Control-Allow-Origin','*')
931 @bottle.route(url_base
+ '/<tenant_id>/topology/deploy', method
='POST')
932 def http_post_deploy(tenant_id
):
933 '''post topology deploy.'''
934 logger
.debug('FROM %s %s %s', bottle
.request
.remote_addr
, bottle
.request
.method
, bottle
.request
.url
)
936 http_content
, used_schema
= format_in( nsd_schema_v01
, ("schema_version",), {2: nsd_schema_v02
})
937 #r = utils.remove_extra_items(http_content, used_schema)
938 #if r is not None: print "http_post_deploy: Warning: remove extra items ", r
939 #print "http_post_deploy input: ", http_content
942 scenario_id
= nfvo
.new_scenario(mydb
, tenant_id
, http_content
)
943 instance
= nfvo
.start_scenario(mydb
, tenant_id
, scenario_id
, http_content
['name'], http_content
['name'])
944 #print json.dumps(data, indent=4)
945 return format_out(instance
)
946 except (nfvo
.NfvoException
, db_base_Exception
) as e
:
947 logger
.error("http_post_deploy error {}: {}".format(e
.http_code
, str(e
)))
948 bottle
.abort(e
.http_code
, str(e
))
949 except Exception as e
:
950 logger
.error("Unexpected exception: ", exc_info
=True)
951 bottle
.abort(HTTP_Internal_Server_Error
, type(e
).__name
__ + ": " + str(e
))
954 @bottle.route(url_base
+ '/<tenant_id>/topology/verify', method
='POST')
955 def http_post_verify(tenant_id
):
957 # '''post topology verify'''
958 # print "http_post_verify by tenant " + tenant_id + ' datacenter ' + datacenter
959 logger
.debug('FROM %s %s %s', bottle
.request
.remote_addr
, bottle
.request
.method
, bottle
.request
.url
)
966 @bottle.route(url_base
+ '/<tenant_id>/scenarios', method
='POST')
967 def http_post_scenarios(tenant_id
):
968 '''add a scenario into the catalogue. Creates the scenario and its internal structure in the OPENMANO DB'''
969 logger
.debug('FROM %s %s %s', bottle
.request
.remote_addr
, bottle
.request
.method
, bottle
.request
.url
)
970 http_content
, used_schema
= format_in( nsd_schema_v01
, ("schema_version",), {2: nsd_schema_v02
, "0.3": nsd_schema_v03
})
971 #r = utils.remove_extra_items(http_content, used_schema)
972 #if r is not None: print "http_post_scenarios: Warning: remove extra items ", r
973 #print "http_post_scenarios input: ", http_content
975 if used_schema
== nsd_schema_v01
:
976 scenario_id
= nfvo
.new_scenario(mydb
, tenant_id
, http_content
)
977 elif used_schema
== nsd_schema_v02
:
978 scenario_id
= nfvo
.new_scenario_v02(mydb
, tenant_id
, http_content
, "0.2")
979 elif used_schema
== nsd_schema_v03
:
980 scenario_id
= nfvo
.new_scenario_v02(mydb
, tenant_id
, http_content
, "0.3")
982 logger
.warning('Unexpected schema_version: %s', http_content
.get("schema_version"))
983 bottle
.abort(HTTP_Bad_Request
, "Invalid schema version")
984 #print json.dumps(data, indent=4)
985 #return format_out(data)
986 return http_get_scenario_id(tenant_id
, scenario_id
)
987 except (nfvo
.NfvoException
, db_base_Exception
) as e
:
988 logger
.error("http_post_scenarios error {}: {}".format(e
.http_code
, str(e
)))
989 bottle
.abort(e
.http_code
, str(e
))
990 except Exception as e
:
991 logger
.error("Unexpected exception: ", exc_info
=True)
992 bottle
.abort(HTTP_Internal_Server_Error
, type(e
).__name
__ + ": " + str(e
))
995 @bottle.route(url_base
+ '/<tenant_id>/scenarios/<scenario_id>/action', method
='POST')
996 def http_post_scenario_action(tenant_id
, scenario_id
):
997 '''take an action over a scenario'''
998 logger
.debug('FROM %s %s %s', bottle
.request
.remote_addr
, bottle
.request
.method
, bottle
.request
.url
)
1000 http_content
, _
= format_in(scenario_action_schema
)
1001 r
= utils
.remove_extra_items(http_content
, scenario_action_schema
)
1003 logger
.debug("Remove received extra items %s", str(r
))
1005 # check valid tenant_id
1006 nfvo
.check_tenant(mydb
, tenant_id
)
1007 if "start" in http_content
:
1008 data
= nfvo
.start_scenario(mydb
, tenant_id
, scenario_id
, http_content
['start']['instance_name'], \
1009 http_content
['start'].get('description',http_content
['start']['instance_name']),
1010 http_content
['start'].get('datacenter') )
1011 return format_out(data
)
1012 elif "deploy" in http_content
: #Equivalent to start
1013 data
= nfvo
.start_scenario(mydb
, tenant_id
, scenario_id
, http_content
['deploy']['instance_name'],
1014 http_content
['deploy'].get('description',http_content
['deploy']['instance_name']),
1015 http_content
['deploy'].get('datacenter') )
1016 return format_out(data
)
1017 elif "reserve" in http_content
: #Reserve resources
1018 data
= nfvo
.start_scenario(mydb
, tenant_id
, scenario_id
, http_content
['reserve']['instance_name'],
1019 http_content
['reserve'].get('description',http_content
['reserve']['instance_name']),
1020 http_content
['reserve'].get('datacenter'), startvms
=False )
1021 return format_out(data
)
1022 elif "verify" in http_content
: #Equivalent to start and then delete
1023 data
= nfvo
.start_scenario(mydb
, tenant_id
, scenario_id
, http_content
['verify']['instance_name'],
1024 http_content
['verify'].get('description',http_content
['verify']['instance_name']),
1025 http_content
['verify'].get('datacenter'), startvms
=False )
1026 instance_id
= data
['uuid']
1027 nfvo
.delete_instance(mydb
, tenant_id
,instance_id
)
1028 return format_out({"result":"Verify OK"})
1029 except (nfvo
.NfvoException
, db_base_Exception
) as e
:
1030 logger
.error("http_post_scenario_action error {}: {}".format(e
.http_code
, str(e
)))
1031 bottle
.abort(e
.http_code
, str(e
))
1032 except Exception as e
:
1033 logger
.error("Unexpected exception: ", exc_info
=True)
1034 bottle
.abort(HTTP_Internal_Server_Error
, type(e
).__name
__ + ": " + str(e
))
1037 @bottle.route(url_base
+ '/<tenant_id>/scenarios', method
='GET')
1038 def http_get_scenarios(tenant_id
):
1039 '''get scenarios list'''
1040 logger
.debug('FROM %s %s %s', bottle
.request
.remote_addr
, bottle
.request
.method
, bottle
.request
.url
)
1042 #check valid tenant_id
1043 if tenant_id
!= "any":
1044 nfvo
.check_tenant(mydb
, tenant_id
)
1046 s
,w
,l
=filter_query_string(bottle
.request
.query
, None, ('uuid', 'name', 'description', 'tenant_id', 'created_at', 'public'))
1048 if tenant_id
!= "any":
1049 where_or
["tenant_id"] = tenant_id
1050 where_or
["public"] = True
1051 scenarios
= mydb
.get_rows(SELECT
=s
, WHERE
=w
, WHERE_OR
=where_or
, WHERE_AND_OR
="AND", LIMIT
=l
, FROM
='scenarios')
1052 convert_datetime2str(scenarios
)
1053 utils
.convert_str2boolean(scenarios
, ('public',) )
1054 data
={'scenarios':scenarios
}
1055 #print json.dumps(scenarios, indent=4)
1056 return format_out(data
)
1057 except (nfvo
.NfvoException
, db_base_Exception
) as e
:
1058 logger
.error("http_get_scenarios error {}: {}".format(e
.http_code
, str(e
)))
1059 bottle
.abort(e
.http_code
, str(e
))
1060 except Exception as e
:
1061 logger
.error("Unexpected exception: ", exc_info
=True)
1062 bottle
.abort(HTTP_Internal_Server_Error
, type(e
).__name
__ + ": " + str(e
))
1065 @bottle.route(url_base
+ '/<tenant_id>/scenarios/<scenario_id>', method
='GET')
1066 def http_get_scenario_id(tenant_id
, scenario_id
):
1067 '''get scenario details, can use both uuid or name'''
1068 logger
.debug('FROM %s %s %s', bottle
.request
.remote_addr
, bottle
.request
.method
, bottle
.request
.url
)
1070 #check valid tenant_id
1071 if tenant_id
!= "any":
1072 nfvo
.check_tenant(mydb
, tenant_id
)
1074 scenario
= mydb
.get_scenario(scenario_id
, tenant_id
)
1075 convert_datetime2str(scenario
)
1076 data
={'scenario' : scenario
}
1077 return format_out(data
)
1078 except (nfvo
.NfvoException
, db_base_Exception
) as e
:
1079 logger
.error("http_get_scenarios error {}: {}".format(e
.http_code
, str(e
)))
1080 bottle
.abort(e
.http_code
, str(e
))
1081 except Exception as e
:
1082 logger
.error("Unexpected exception: ", exc_info
=True)
1083 bottle
.abort(HTTP_Internal_Server_Error
, type(e
).__name
__ + ": " + str(e
))
1086 @bottle.route(url_base
+ '/<tenant_id>/scenarios/<scenario_id>', method
='DELETE')
1087 def http_delete_scenario_id(tenant_id
, scenario_id
):
1088 '''delete a scenario from database, can use both uuid or name'''
1089 logger
.debug('FROM %s %s %s', bottle
.request
.remote_addr
, bottle
.request
.method
, bottle
.request
.url
)
1091 #check valid tenant_id
1092 if tenant_id
!= "any":
1093 nfvo
.check_tenant(mydb
, tenant_id
)
1095 data
= mydb
.delete_scenario(scenario_id
, tenant_id
)
1096 #print json.dumps(data, indent=4)
1097 return format_out({"result":"scenario " + data
+ " deleted"})
1098 except (nfvo
.NfvoException
, db_base_Exception
) as e
:
1099 logger
.error("http_delete_scenario_id error {}: {}".format(e
.http_code
, str(e
)))
1100 bottle
.abort(e
.http_code
, str(e
))
1101 except Exception as e
:
1102 logger
.error("Unexpected exception: ", exc_info
=True)
1103 bottle
.abort(HTTP_Internal_Server_Error
, type(e
).__name
__ + ": " + str(e
))
1106 @bottle.route(url_base
+ '/<tenant_id>/scenarios/<scenario_id>', method
='PUT')
1107 def http_put_scenario_id(tenant_id
, scenario_id
):
1108 '''edit an existing scenario id'''
1109 logger
.debug('FROM %s %s %s', bottle
.request
.remote_addr
, bottle
.request
.method
, bottle
.request
.url
)
1110 http_content
,_
= format_in( scenario_edit_schema
)
1111 #r = utils.remove_extra_items(http_content, scenario_edit_schema)
1112 #if r is not None: print "http_put_scenario_id: Warning: remove extra items ", r
1113 #print "http_put_scenario_id input: ", http_content
1115 nfvo
.edit_scenario(mydb
, tenant_id
, scenario_id
, http_content
)
1116 #print json.dumps(data, indent=4)
1117 #return format_out(data)
1118 return http_get_scenario_id(tenant_id
, scenario_id
)
1119 except (nfvo
.NfvoException
, db_base_Exception
) as e
:
1120 logger
.error("http_put_scenario_id error {}: {}".format(e
.http_code
, str(e
)))
1121 bottle
.abort(e
.http_code
, str(e
))
1122 except Exception as e
:
1123 logger
.error("Unexpected exception: ", exc_info
=True)
1124 bottle
.abort(HTTP_Internal_Server_Error
, type(e
).__name
__ + ": " + str(e
))
1126 @bottle.route(url_base
+ '/<tenant_id>/instances', method
='POST')
1127 def http_post_instances(tenant_id
):
1128 '''create an instance-scenario'''
1129 logger
.debug('FROM %s %s %s', bottle
.request
.remote_addr
, bottle
.request
.method
, bottle
.request
.url
)
1131 http_content
, used_schema
= format_in(instance_scenario_create_schema_v01
)
1132 r
= utils
.remove_extra_items(http_content
, used_schema
)
1134 logger
.warning("http_post_instances: Warning: remove extra items %s", str(r
))
1136 #check valid tenant_id
1137 if tenant_id
!= "any":
1138 nfvo
.check_tenant(mydb
, tenant_id
)
1139 data
= nfvo
.create_instance(mydb
, tenant_id
, http_content
["instance"])
1140 return format_out(data
)
1141 except (nfvo
.NfvoException
, db_base_Exception
) as e
:
1142 logger
.error("http_post_instances error {}: {}".format(e
.http_code
, str(e
)))
1143 bottle
.abort(e
.http_code
, str(e
))
1144 except Exception as e
:
1145 logger
.error("Unexpected exception: ", exc_info
=True)
1146 bottle
.abort(HTTP_Internal_Server_Error
, type(e
).__name
__ + ": " + str(e
))
1151 @bottle.route(url_base
+ '/<tenant_id>/instances', method
='GET')
1152 def http_get_instances(tenant_id
):
1153 '''get instance list'''
1155 #check valid tenant_id
1156 if tenant_id
!= "any":
1157 nfvo
.check_tenant(mydb
, tenant_id
)
1159 s
,w
,l
=filter_query_string(bottle
.request
.query
, None, ('uuid', 'name', 'scenario_id', 'tenant_id', 'description', 'created_at'))
1160 if tenant_id
!= "any":
1161 w
['tenant_id'] = tenant_id
1162 instances
= mydb
.get_rows(SELECT
=s
, WHERE
=w
, LIMIT
=l
, FROM
='instance_scenarios')
1163 convert_datetime2str(instances
)
1164 utils
.convert_str2boolean(instances
, ('public',) )
1165 data
={'instances':instances
}
1166 return format_out(data
)
1167 except (nfvo
.NfvoException
, db_base_Exception
) as e
:
1168 logger
.error("http_get_instances error {}: {}".format(e
.http_code
, str(e
)))
1169 bottle
.abort(e
.http_code
, str(e
))
1170 except Exception as e
:
1171 logger
.error("Unexpected exception: ", exc_info
=True)
1172 bottle
.abort(HTTP_Internal_Server_Error
, type(e
).__name
__ + ": " + str(e
))
1175 @bottle.route(url_base
+ '/<tenant_id>/instances/<instance_id>', method
='GET')
1176 def http_get_instance_id(tenant_id
, instance_id
):
1177 '''get instances details, can use both uuid or name'''
1178 logger
.debug('FROM %s %s %s', bottle
.request
.remote_addr
, bottle
.request
.method
, bottle
.request
.url
)
1180 #check valid tenant_id
1181 if tenant_id
!= "any":
1182 nfvo
.check_tenant(mydb
, tenant_id
)
1183 if tenant_id
== "any":
1185 #obtain data (first time is only to check that the instance exists)
1186 instance_dict
= mydb
.get_instance_scenario(instance_id
, tenant_id
, verbose
=True)
1188 nfvo
.refresh_instance(mydb
, tenant_id
, instance_dict
)
1189 except (nfvo
.NfvoException
, db_base_Exception
) as e
:
1190 logger
.warn("nfvo.refresh_instance couldn't refresh the status of the instance: %s" % str(e
))
1191 #obtain data with results upated
1192 instance
= mydb
.get_instance_scenario(instance_id
, tenant_id
)
1193 convert_datetime2str(instance
)
1194 #print json.dumps(instance, indent=4)
1195 return format_out(instance
)
1196 except (nfvo
.NfvoException
, db_base_Exception
) as e
:
1197 logger
.error("http_get_instance_id error {}: {}".format(e
.http_code
, str(e
)))
1198 bottle
.abort(e
.http_code
, str(e
))
1199 except Exception as e
:
1200 logger
.error("Unexpected exception: ", exc_info
=True)
1201 bottle
.abort(HTTP_Internal_Server_Error
, type(e
).__name
__ + ": " + str(e
))
1204 @bottle.route(url_base
+ '/<tenant_id>/instances/<instance_id>', method
='DELETE')
1205 def http_delete_instance_id(tenant_id
, instance_id
):
1206 '''delete instance from VIM and from database, can use both uuid or name'''
1207 logger
.debug('FROM %s %s %s', bottle
.request
.remote_addr
, bottle
.request
.method
, bottle
.request
.url
)
1209 #check valid tenant_id
1210 if tenant_id
!= "any":
1211 nfvo
.check_tenant(mydb
, tenant_id
)
1212 if tenant_id
== "any":
1215 message
= nfvo
.delete_instance(mydb
, tenant_id
,instance_id
)
1216 return format_out({"result":message
})
1217 except (nfvo
.NfvoException
, db_base_Exception
) as e
:
1218 logger
.error("http_delete_instance_id error {}: {}".format(e
.http_code
, str(e
)))
1219 bottle
.abort(e
.http_code
, str(e
))
1220 except Exception as e
:
1221 logger
.error("Unexpected exception: ", exc_info
=True)
1222 bottle
.abort(HTTP_Internal_Server_Error
, type(e
).__name
__ + ": " + str(e
))
1225 @bottle.route(url_base
+ '/<tenant_id>/instances/<instance_id>/action', method
='POST')
1226 def http_post_instance_scenario_action(tenant_id
, instance_id
):
1227 '''take an action over a scenario instance'''
1228 logger
.debug('FROM %s %s %s', bottle
.request
.remote_addr
, bottle
.request
.method
, bottle
.request
.url
)
1230 http_content
, _
= format_in(instance_scenario_action_schema
)
1231 r
= utils
.remove_extra_items(http_content
, instance_scenario_action_schema
)
1233 logger
.debug("Remove received extra items %s", str(r
))
1235 #check valid tenant_id
1236 if tenant_id
!= "any":
1237 nfvo
.check_tenant(mydb
, tenant_id
)
1239 #print "http_post_instance_scenario_action input: ", http_content
1241 instance
= mydb
.get_instance_scenario(instance_id
, tenant_id
)
1242 instance_id
= instance
["uuid"]
1244 data
= nfvo
.instance_action(mydb
, tenant_id
, instance_id
, http_content
)
1245 return format_out(data
)
1246 except (nfvo
.NfvoException
, db_base_Exception
) as e
:
1247 logger
.error("http_post_instance_scenario_action error {}: {}".format(e
.http_code
, str(e
)))
1248 bottle
.abort(e
.http_code
, str(e
))
1249 except Exception as e
:
1250 logger
.error("Unexpected exception: ", exc_info
=True)
1251 bottle
.abort(HTTP_Internal_Server_Error
, type(e
).__name
__ + ": " + str(e
))
1263 def error400(error
):
1264 e
={"error":{"code":error
.status_code
, "type":error
.status
, "description":error
.body
}}
1265 bottle
.response
.headers
['Access-Control-Allow-Origin'] = '*'
1266 return format_out(e
)