blob: 9d1f2047e08443d1427772934fbd0d72f8db89f8 [file] [log] [blame]
tierno7edb6752016-03-21 17:37:52 +01001# -*- coding: utf-8 -*-
2
3##
4# Copyright 2015 Telefónica Investigación y Desarrollo, S.A.U.
5# This file is part of openmano
6# All Rights Reserved.
7#
8# Licensed under the Apache License, Version 2.0 (the "License"); you may
9# not use this file except in compliance with the License. You may obtain
10# a copy of the License at
11#
12# http://www.apache.org/licenses/LICENSE-2.0
13#
14# Unless required by applicable law or agreed to in writing, software
15# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
16# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
17# License for the specific language governing permissions and limitations
18# under the License.
19#
20# For those usages not covered by the Apache License, Version 2.0 please
21# contact with: nfvlabs@tid.es
22##
23
24'''
25HTTP server implementing the openmano API. It will answer to POST, PUT, GET methods in the appropriate URLs
26and will use the nfvo.py module to run the appropriate method.
27Every YAML/JSON file is checked against a schema in openmano_schemas.py module.
28'''
29__author__="Alfonso Tierno, Gerardo Garcia"
30__date__ ="$17-sep-2014 09:07:15$"
31
32import bottle
33import yaml
34import json
35import threading
36import time
tiernof97fd272016-07-11 14:32:37 +020037import logging
tierno7edb6752016-03-21 17:37:52 +010038
39from jsonschema import validate as js_v, exceptions as js_e
40from openmano_schemas import vnfd_schema_v01, vnfd_schema_v02, \
garciadeblas9f8456e2016-09-05 05:02:59 +020041 nsd_schema_v01, nsd_schema_v02, nsd_schema_v03, scenario_edit_schema, \
garciadeblas0c317ee2016-08-29 12:33:06 +020042 scenario_action_schema, instance_scenario_action_schema, instance_scenario_create_schema_v01, \
tierno7edb6752016-03-21 17:37:52 +010043 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
46import nfvo
tierno42fcc3b2016-07-06 17:20:40 +020047import utils
tiernof97fd272016-07-11 14:32:37 +020048from db_base import db_base_Exception
49from functools import wraps
tierno7edb6752016-03-21 17:37:52 +010050
51global mydb
52global url_base
tiernof97fd272016-07-11 14:32:37 +020053global logger
tierno7edb6752016-03-21 17:37:52 +010054url_base="/openmano"
55
56HTTP_Bad_Request = 400
57HTTP_Unauthorized = 401
58HTTP_Not_Found = 404
59HTTP_Forbidden = 403
60HTTP_Method_Not_Allowed = 405
61HTTP_Not_Acceptable = 406
62HTTP_Service_Unavailable = 503
63HTTP_Internal_Server_Error= 500
64
65def delete_nulls(var):
66 if type(var) is dict:
67 for k in var.keys():
68 if var[k] is None: del var[k]
69 elif type(var[k]) is dict or type(var[k]) is list or type(var[k]) is tuple:
70 if delete_nulls(var[k]): del var[k]
71 if len(var) == 0: return True
72 elif type(var) is list or type(var) is tuple:
73 for k in var:
74 if type(k) is dict: delete_nulls(k)
75 if len(var) == 0: return True
76 return False
77
78def convert_datetime2str(var):
79 '''Converts a datetime variable to a string with the format '%Y-%m-%dT%H:%i:%s'
80 It enters recursively in the dict var finding this kind of variables
81 '''
82 if type(var) is dict:
83 for k,v in var.items():
84 if type(v) is float and k in ("created_at", "modified_at"):
85 var[k] = time.strftime("%Y-%m-%dT%H:%M:%S", time.localtime(v) )
86 elif type(v) is dict or type(v) is list or type(v) is tuple:
87 convert_datetime2str(v)
88 if len(var) == 0: return True
89 elif type(var) is list or type(var) is tuple:
90 for v in var:
91 convert_datetime2str(v)
92
tiernof97fd272016-07-11 14:32:37 +020093def log_to_logger(fn):
94 '''
95 Wrap a Bottle request so that a log line is emitted after it's handled.
96 (This decorator can be extended to take the desired logger as a param.)
97 '''
98 @wraps(fn)
99 def _log_to_logger(*args, **kwargs):
100 actual_response = fn(*args, **kwargs)
101 # modify this to log exactly what you need:
102 logger.info('FROM %s %s %s %s' % (bottle.request.remote_addr,
103 bottle.request.method,
104 bottle.request.url,
105 bottle.response.status))
106 return actual_response
107 return _log_to_logger
tierno7edb6752016-03-21 17:37:52 +0100108
109class httpserver(threading.Thread):
110 def __init__(self, db, admin=False, host='localhost', port=9090):
111 #global url_base
112 global mydb
tiernof97fd272016-07-11 14:32:37 +0200113 global logger
tierno7edb6752016-03-21 17:37:52 +0100114 #initialization
tiernof97fd272016-07-11 14:32:37 +0200115 logger = logging.getLogger('openmano.http')
tierno7edb6752016-03-21 17:37:52 +0100116 threading.Thread.__init__(self)
117 self.host = host
118 self.port = port #Port where the listen service must be started
119 if admin==True:
120 self.name = "http_admin"
121 else:
122 self.name = "http"
123 #self.url_preffix = 'http://' + host + ':' + str(port) + url_base
124 mydb = db
125 #self.first_usable_connection_index = 10
126 #self.next_connection_index = self.first_usable_connection_index #The next connection index to be used
127 #Ensure that when the main program exits the thread will also exit
128 self.daemon = True
129 self.setDaemon(True)
130
131 def run(self):
tiernof97fd272016-07-11 14:32:37 +0200132 bottle.install(log_to_logger)
133 bottle.run(host=self.host, port=self.port, debug=False, quiet=True)
tierno7edb6752016-03-21 17:37:52 +0100134
135def run_bottle(db, host_='localhost', port_=9090):
136 '''used for launching in main thread, so that it can be debugged'''
137 global mydb
138 mydb = db
139 bottle.run(host=host_, port=port_, debug=True) #quiet=True
140
141
142@bottle.route(url_base + '/', method='GET')
143def http_get():
144 print
145 return 'works' #TODO: to be completed
146
147#
148# Util functions
149#
150
151def change_keys_http2db(data, http_db, reverse=False):
152 '''Change keys of dictionary data acording to the key_dict values
153 This allow change from http interface names to database names.
154 When reverse is True, the change is otherwise
155 Attributes:
156 data: can be a dictionary or a list
157 http_db: is a dictionary with hhtp names as keys and database names as value
158 reverse: by default change is done from http api to database. If True change is done otherwise
159 Return: None, but data is modified'''
160 if type(data) is tuple or type(data) is list:
161 for d in data:
162 change_keys_http2db(d, http_db, reverse)
163 elif type(data) is dict or type(data) is bottle.FormsDict:
164 if reverse:
165 for k,v in http_db.items():
166 if v in data: data[k]=data.pop(v)
167 else:
168 for k,v in http_db.items():
169 if k in data: data[v]=data.pop(k)
170
171def format_out(data):
172 '''return string of dictionary data according to requested json, yaml, xml. By default json'''
tiernof97fd272016-07-11 14:32:37 +0200173 logger.debug(yaml.safe_dump(data, explicit_start=True, indent=4, default_flow_style=False, tags=False, encoding='utf-8', allow_unicode=True) )
tierno7edb6752016-03-21 17:37:52 +0100174 if 'application/yaml' in bottle.request.headers.get('Accept'):
175 bottle.response.content_type='application/yaml'
tierno7edb6752016-03-21 17:37:52 +0100176 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='"'
177 else: #by default json
178 bottle.response.content_type='application/json'
179 #return data #json no style
180 return json.dumps(data, indent=4) + "\n"
181
182def format_in(default_schema, version_fields=None, version_dict_schema=None):
183 ''' Parse the content of HTTP request against a json_schema
184 Parameters
185 default_schema: The schema to be parsed by default if no version field is found in the client data
186 version_fields: If provided it contains a tuple or list with the fields to iterate across the client data to obtain the version
187 version_dict_schema: It contains a dictionary with the version as key, and json schema to apply as value
188 It can contain a None as key, and this is apply if the client data version does not match any key
189 Return:
190 user_data, used_schema: if the data is successfully decoded and matches the schema
191 launch a bottle abort if fails
192 '''
193 #print "HEADERS :" + str(bottle.request.headers.items())
194 try:
195 error_text = "Invalid header format "
196 format_type = bottle.request.headers.get('Content-Type', 'application/json')
197 if 'application/json' in format_type:
198 error_text = "Invalid json format "
199 #Use the json decoder instead of bottle decoder because it informs about the location of error formats with a ValueError exception
200 client_data = json.load(bottle.request.body)
201 #client_data = bottle.request.json()
202 elif 'application/yaml' in format_type:
203 error_text = "Invalid yaml format "
204 client_data = yaml.load(bottle.request.body)
205 elif 'application/xml' in format_type:
206 bottle.abort(501, "Content-Type: application/xml not supported yet.")
207 else:
208 print 'Content-Type ' + str(format_type) + ' not supported.'
209 bottle.abort(HTTP_Not_Acceptable, 'Content-Type ' + str(format_type) + ' not supported.')
210 return
211 #if client_data == None:
212 # bottle.abort(HTTP_Bad_Request, "Content error, empty")
213 # return
214
garciadeblas9f8456e2016-09-05 05:02:59 +0200215 #logger.debug('client-data: %s', client_data)
tierno7edb6752016-03-21 17:37:52 +0100216 #look for the client provider version
217 error_text = "Invalid content "
218 client_version = None
219 used_schema = None
220 if version_fields != None:
221 client_version = client_data
222 for field in version_fields:
223 if field in client_version:
224 client_version = client_version[field]
225 else:
226 client_version=None
227 break
228 if client_version==None:
229 used_schema=default_schema
230 elif version_dict_schema!=None:
231 if client_version in version_dict_schema:
232 used_schema = version_dict_schema[client_version]
233 elif None in version_dict_schema:
234 used_schema = version_dict_schema[None]
235 if used_schema==None:
236 bottle.abort(HTTP_Bad_Request, "Invalid schema version or missing version field")
237
238 js_v(client_data, used_schema)
239 return client_data, used_schema
240 except (ValueError, yaml.YAMLError) as exc:
241 error_text += str(exc)
242 print error_text
243 bottle.abort(HTTP_Bad_Request, error_text)
244 except js_e.ValidationError as exc:
245 print "validate_in error, jsonschema exception ", exc.message, "at", exc.path
246 error_pos = ""
247 if len(exc.path)>0: error_pos=" at " + ":".join(map(json.dumps, exc.path))
248 bottle.abort(HTTP_Bad_Request, error_text + exc.message + error_pos)
249 #except:
250 # bottle.abort(HTTP_Bad_Request, "Content error: Failed to parse Content-Type", error_pos)
251 # raise
252
253def filter_query_string(qs, http2db, allowed):
254 '''Process query string (qs) checking that contains only valid tokens for avoiding SQL injection
255 Attributes:
256 'qs': bottle.FormsDict variable to be processed. None or empty is considered valid
257 'http2db': dictionary with change from http API naming (dictionary key) to database naming(dictionary value)
258 'allowed': list of allowed string tokens (API http naming). All the keys of 'qs' must be one of 'allowed'
259 Return: A tuple with the (select,where,limit) to be use in a database query. All of then transformed to the database naming
260 select: list of items to retrieve, filtered by query string 'field=token'. If no 'field' is present, allowed list is returned
261 where: dictionary with key, value, taken from the query string token=value. Empty if nothing is provided
262 limit: limit dictated by user with the query string 'limit'. 100 by default
263 abort if not permited, using bottel.abort
264 '''
265 where={}
266 limit=100
267 select=[]
tiernof97fd272016-07-11 14:32:37 +0200268 #if type(qs) is not bottle.FormsDict:
269 # bottle.abort(HTTP_Internal_Server_Error, '!!!!!!!!!!!!!!invalid query string not a dictionary')
270 # #bottle.abort(HTTP_Internal_Server_Error, "call programmer")
271 for k in qs:
272 if k=='field':
273 select += qs.getall(k)
274 for v in select:
275 if v not in allowed:
276 bottle.abort(HTTP_Bad_Request, "Invalid query string at 'field="+v+"'")
277 elif k=='limit':
278 try:
279 limit=int(qs[k])
280 except:
281 bottle.abort(HTTP_Bad_Request, "Invalid query string at 'limit="+qs[k]+"'")
282 else:
283 if k not in allowed:
284 bottle.abort(HTTP_Bad_Request, "Invalid query string at '"+k+"="+qs[k]+"'")
285 if qs[k]!="null": where[k]=qs[k]
286 else: where[k]=None
tierno7edb6752016-03-21 17:37:52 +0100287 if len(select)==0: select += allowed
288 #change from http api to database naming
289 for i in range(0,len(select)):
290 k=select[i]
291 if http2db and k in http2db:
292 select[i] = http2db[k]
293 if http2db:
294 change_keys_http2db(where, http2db)
tiernof97fd272016-07-11 14:32:37 +0200295 #print "filter_query_string", select,where,limit
tierno7edb6752016-03-21 17:37:52 +0100296
297 return select,where,limit
298
299@bottle.hook('after_request')
300def enable_cors():
301 '''Don't know yet if really needed. Keep it just in case'''
302 bottle.response.headers['Access-Control-Allow-Origin'] = '*'
303
304#
305# VNFs
306#
307
308@bottle.route(url_base + '/tenants', method='GET')
309def http_get_tenants():
tiernof97fd272016-07-11 14:32:37 +0200310 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
tierno7edb6752016-03-21 17:37:52 +0100311 select_,where_,limit_ = filter_query_string(bottle.request.query, None,
312 ('uuid','name','description','created_at') )
tiernof97fd272016-07-11 14:32:37 +0200313 try:
314 tenants = mydb.get_rows(FROM='nfvo_tenants', SELECT=select_,WHERE=where_,LIMIT=limit_)
tierno7edb6752016-03-21 17:37:52 +0100315 #change_keys_http2db(content, http2db_tenant, reverse=True)
tiernof97fd272016-07-11 14:32:37 +0200316 convert_datetime2str(tenants)
317 data={'tenants' : tenants}
tierno7edb6752016-03-21 17:37:52 +0100318 return format_out(data)
tiernof97fd272016-07-11 14:32:37 +0200319 except db_base_Exception as e:
320 logger.error("http_get_tenants error {}: {}".format(e.http_code, str(e)))
321 bottle.abort(e.http_code, str(e))
tierno7edb6752016-03-21 17:37:52 +0100322
323@bottle.route(url_base + '/tenants/<tenant_id>', method='GET')
324def http_get_tenant_id(tenant_id):
325 '''get tenant details, can use both uuid or name'''
326 #obtain data
tiernof97fd272016-07-11 14:32:37 +0200327 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
328 try:
329 tenant = mydb.get_table_by_uuid_name('nfvo_tenants', tenant_id, "tenant")
330 #change_keys_http2db(content, http2db_tenant, reverse=True)
331 convert_datetime2str(tenant)
332 data={'tenant' : tenant}
333 return format_out(data)
334 except db_base_Exception as e:
335 logger.error("http_get_tenant_id error {}: {}".format(e.http_code, str(e)))
336 bottle.abort(e.http_code, str(e))
tierno7edb6752016-03-21 17:37:52 +0100337
338@bottle.route(url_base + '/tenants', method='POST')
339def http_post_tenants():
340 '''insert a tenant into the catalogue. '''
341 #parse input data
tiernof97fd272016-07-11 14:32:37 +0200342 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
tierno7edb6752016-03-21 17:37:52 +0100343 http_content,_ = format_in( tenant_schema )
tierno42fcc3b2016-07-06 17:20:40 +0200344 r = utils.remove_extra_items(http_content, tenant_schema)
tierno7edb6752016-03-21 17:37:52 +0100345 if r is not None: print "http_post_tenants: Warning: remove extra items ", r
tiernof97fd272016-07-11 14:32:37 +0200346 try:
347 data = nfvo.new_tenant(mydb, http_content['tenant'])
tierno7edb6752016-03-21 17:37:52 +0100348 return http_get_tenant_id(data)
tiernof97fd272016-07-11 14:32:37 +0200349 except (nfvo.NfvoException, db_base_Exception) as e:
350 logger.error("http_post_tenants error {}: {}".format(e.http_code, str(e)))
351 bottle.abort(e.http_code, str(e))
tierno7edb6752016-03-21 17:37:52 +0100352
353@bottle.route(url_base + '/tenants/<tenant_id>', method='PUT')
354def http_edit_tenant_id(tenant_id):
355 '''edit tenant details, can use both uuid or name'''
356 #parse input data
tiernof97fd272016-07-11 14:32:37 +0200357 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
tierno7edb6752016-03-21 17:37:52 +0100358 http_content,_ = format_in( tenant_edit_schema )
tierno42fcc3b2016-07-06 17:20:40 +0200359 r = utils.remove_extra_items(http_content, tenant_edit_schema)
tierno7edb6752016-03-21 17:37:52 +0100360 if r is not None: print "http_edit_tenant_id: Warning: remove extra items ", r
361
362 #obtain data, check that only one exist
tiernof97fd272016-07-11 14:32:37 +0200363 try:
364 tenant = mydb.get_table_by_uuid_name('nfvo_tenants', tenant_id)
365 #edit data
366 tenant_id = tenant['uuid']
367 where={'uuid': tenant['uuid']}
368 mydb.update_rows('nfvo_tenants', http_content['tenant'], where)
369 return http_get_tenant_id(tenant_id)
370 except db_base_Exception as e:
371 logger.error("http_edit_tenant_id error {}: {}".format(e.http_code, str(e)))
372 bottle.abort(e.http_code, str(e))
tierno7edb6752016-03-21 17:37:52 +0100373
374@bottle.route(url_base + '/tenants/<tenant_id>', method='DELETE')
375def http_delete_tenant_id(tenant_id):
376 '''delete a tenant from database, can use both uuid or name'''
tiernof97fd272016-07-11 14:32:37 +0200377 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
378 try:
379 data = nfvo.delete_tenant(mydb, tenant_id)
tierno7edb6752016-03-21 17:37:52 +0100380 return format_out({"result":"tenant " + data + " deleted"})
tiernof97fd272016-07-11 14:32:37 +0200381 except db_base_Exception as e:
382 logger.error("http_delete_tenant_id error {}: {}".format(e.http_code, str(e)))
383 bottle.abort(e.http_code, str(e))
tierno7edb6752016-03-21 17:37:52 +0100384
385
386@bottle.route(url_base + '/<tenant_id>/datacenters', method='GET')
387def http_get_datacenters(tenant_id):
tiernof97fd272016-07-11 14:32:37 +0200388 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
389 try:
390 if tenant_id != 'any':
391 #check valid tenant_id
392 nfvo.check_tenant(mydb, tenant_id)
393 select_,where_,limit_ = filter_query_string(bottle.request.query, None,
394 ('uuid','name','vim_url','type','created_at') )
395 if tenant_id != 'any':
396 where_['nfvo_tenant_id'] = tenant_id
397 if 'created_at' in select_:
398 select_[ select_.index('created_at') ] = 'd.created_at as created_at'
399 if 'created_at' in where_:
400 where_['d.created_at'] = where_.pop('created_at')
401 datacenters = mydb.get_rows(FROM='datacenters as d join tenants_datacenters as td on d.uuid=td.datacenter_id',
402 SELECT=select_,WHERE=where_,LIMIT=limit_)
403 else:
404 datacenters = mydb.get_rows(FROM='datacenters',
405 SELECT=select_,WHERE=where_,LIMIT=limit_)
tierno7edb6752016-03-21 17:37:52 +0100406 #change_keys_http2db(content, http2db_tenant, reverse=True)
tiernof97fd272016-07-11 14:32:37 +0200407 convert_datetime2str(datacenters)
408 data={'datacenters' : datacenters}
tierno7edb6752016-03-21 17:37:52 +0100409 return format_out(data)
tiernof97fd272016-07-11 14:32:37 +0200410 except (nfvo.NfvoException, db_base_Exception) as e:
411 logger.error("http_get_datacenters error {}: {}".format(e.http_code, str(e)))
412 bottle.abort(e.http_code, str(e))
tierno7edb6752016-03-21 17:37:52 +0100413
414@bottle.route(url_base + '/<tenant_id>/datacenters/<datacenter_id>', method='GET')
415def http_get_datacenter_id(tenant_id, datacenter_id):
416 '''get datacenter details, can use both uuid or name'''
tiernof97fd272016-07-11 14:32:37 +0200417 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
418 try:
419 if tenant_id != 'any':
420 #check valid tenant_id
421 nfvo.check_tenant(mydb, tenant_id)
422 #obtain data
423 what = 'uuid' if utils.check_valid_uuid(datacenter_id) else 'name'
424 where_={}
425 where_[what] = datacenter_id
426 select_=['uuid', 'name','vim_url', 'vim_url_admin', 'type', 'config', 'description', 'd.created_at as created_at']
427 if tenant_id != 'any':
428 select_.append("datacenter_tenant_id")
429 where_['td.nfvo_tenant_id']= tenant_id
430 from_='datacenters as d join tenants_datacenters as td on d.uuid=td.datacenter_id'
431 else:
432 from_='datacenters as d'
433 datacenters = mydb.get_rows(
434 SELECT=select_,
435 FROM=from_,
436 WHERE=where_)
437
438 if len(datacenters)==0:
439 bottle.abort( HTTP_Not_Found, "No datacenter found for tenant with {} '{}'".format(what, datacenter_id) )
440 elif len(datacenters)>1:
441 bottle.abort( HTTP_Bad_Request, "More than one datacenter found for tenant with {} '{}'".format(what, datacenter_id) )
442 datacenter = datacenters[0]
443 if tenant_id != 'any':
444 #get vim tenant info
445 vim_tenants = mydb.get_rows(
446 SELECT=("vim_tenant_name", "vim_tenant_id", "user"),
447 FROM="datacenter_tenants",
448 WHERE={"uuid": datacenters[0]["datacenter_tenant_id"]},
449 ORDER_BY=("created", ) )
450 del datacenter["datacenter_tenant_id"]
451 datacenter["vim_tenants"] = vim_tenants
452
453 if datacenter['config'] != None:
454 try:
455 config_dict = yaml.load(datacenter['config'])
456 datacenter['config'] = config_dict
457 except Exception, e:
458 print "Exception '%s' while trying to load config information" % str(e)
459 #change_keys_http2db(content, http2db_datacenter, reverse=True)
460 convert_datetime2str(datacenter)
461 data={'datacenter' : datacenter}
462 return format_out(data)
463 except (nfvo.NfvoException, db_base_Exception) as e:
464 logger.error("http_get_datacenter_id error {}: {}".format(e.http_code, str(e)))
465 bottle.abort(e.http_code, str(e))
tierno7edb6752016-03-21 17:37:52 +0100466
467@bottle.route(url_base + '/datacenters', method='POST')
468def http_post_datacenters():
469 '''insert a tenant into the catalogue. '''
470 #parse input data
tiernof97fd272016-07-11 14:32:37 +0200471 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
tierno7edb6752016-03-21 17:37:52 +0100472 http_content,_ = format_in( datacenter_schema )
tierno42fcc3b2016-07-06 17:20:40 +0200473 r = utils.remove_extra_items(http_content, datacenter_schema)
tiernof97fd272016-07-11 14:32:37 +0200474 if r is not None: print "http_post_datacenters: Warning: remove extra items ", r
475 try:
476 data = nfvo.new_datacenter(mydb, http_content['datacenter'])
tierno7edb6752016-03-21 17:37:52 +0100477 return http_get_datacenter_id('any', data)
tiernof97fd272016-07-11 14:32:37 +0200478 except (nfvo.NfvoException, db_base_Exception) as e:
479 logger.error("http_post_datacenters error {}: {}".format(e.http_code, str(e)))
480 bottle.abort(e.http_code, str(e))
tierno7edb6752016-03-21 17:37:52 +0100481
482@bottle.route(url_base + '/datacenters/<datacenter_id_name>', method='PUT')
483def http_edit_datacenter_id(datacenter_id_name):
484 '''edit datacenter details, can use both uuid or name'''
tiernof97fd272016-07-11 14:32:37 +0200485 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
tierno7edb6752016-03-21 17:37:52 +0100486 #parse input data
487 http_content,_ = format_in( datacenter_edit_schema )
tierno42fcc3b2016-07-06 17:20:40 +0200488 r = utils.remove_extra_items(http_content, datacenter_edit_schema)
tierno7edb6752016-03-21 17:37:52 +0100489 if r is not None: print "http_edit_datacenter_id: Warning: remove extra items ", r
490
tiernof97fd272016-07-11 14:32:37 +0200491 try:
492 datacenter_id = nfvo.edit_datacenter(mydb, datacenter_id_name, http_content['datacenter'])
tierno7edb6752016-03-21 17:37:52 +0100493 return http_get_datacenter_id('any', datacenter_id)
tiernof97fd272016-07-11 14:32:37 +0200494 except (nfvo.NfvoException, db_base_Exception) as e:
495 logger.error("http_edit_datacenter_id error {}: {}".format(e.http_code, str(e)))
496 bottle.abort(e.http_code, str(e))
tierno7edb6752016-03-21 17:37:52 +0100497
498@bottle.route(url_base + '/<tenant_id>/datacenters/<datacenter_id>/networks', method='GET') #deprecated
499@bottle.route(url_base + '/<tenant_id>/datacenters/<datacenter_id>/netmaps', method='GET')
500@bottle.route(url_base + '/<tenant_id>/datacenters/<datacenter_id>/netmaps/<netmap_id>', method='GET')
501def http_getnetmap_datacenter_id(tenant_id, datacenter_id, netmap_id=None):
502 '''get datacenter networks, can use both uuid or name'''
tiernof97fd272016-07-11 14:32:37 +0200503 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
tierno7edb6752016-03-21 17:37:52 +0100504 #obtain data
tiernof97fd272016-07-11 14:32:37 +0200505 try:
506 datacenter_dict = mydb.get_table_by_uuid_name('datacenters', datacenter_id, "datacenter")
507 where_= {"datacenter_id":datacenter_dict['uuid']}
508 if netmap_id:
509 if utils.check_valid_uuid(netmap_id):
510 where_["uuid"] = netmap_id
511 else:
512 where_["name"] = netmap_id
513 netmaps =mydb.get_rows(FROM='datacenter_nets',
514 SELECT=('name','vim_net_id as vim_id', 'uuid', 'type','multipoint','shared','description', 'created_at'),
515 WHERE=where_ )
516 convert_datetime2str(netmaps)
517 utils.convert_str2boolean(netmaps, ('shared', 'multipoint') )
518 if netmap_id and len(netmaps)==1:
519 data={'netmap' : netmaps[0]}
520 elif netmap_id and len(netmaps)==0:
521 bottle.abort(HTTP_Not_Found, "No netmap found with " + " and ".join(map(lambda x: str(x[0])+": "+str(x[1]), where_.iteritems())) )
522 return
tierno7edb6752016-03-21 17:37:52 +0100523 else:
tiernof97fd272016-07-11 14:32:37 +0200524 data={'netmaps' : netmaps}
525 return format_out(data)
526 except (nfvo.NfvoException, db_base_Exception) as e:
527 logger.error("http_getnetwork_datacenter_id error {}: {}".format(e.http_code, str(e)))
528 bottle.abort(e.http_code, str(e))
tierno7edb6752016-03-21 17:37:52 +0100529
530@bottle.route(url_base + '/<tenant_id>/datacenters/<datacenter_id>/netmaps', method='DELETE')
531@bottle.route(url_base + '/<tenant_id>/datacenters/<datacenter_id>/netmaps/<netmap_id>', method='DELETE')
532def http_delnetmap_datacenter_id(tenant_id, datacenter_id, netmap_id=None):
533 '''get datacenter networks, can use both uuid or name'''
tiernof97fd272016-07-11 14:32:37 +0200534 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
tierno7edb6752016-03-21 17:37:52 +0100535 #obtain data
tiernof97fd272016-07-11 14:32:37 +0200536 try:
537 datacenter_dict = mydb.get_table_by_uuid_name('datacenters', datacenter_id, "datacenter")
538 where_= {"datacenter_id":datacenter_dict['uuid']}
539 if netmap_id:
540 if utils.check_valid_uuid(netmap_id):
541 where_["uuid"] = netmap_id
542 else:
543 where_["name"] = netmap_id
544 #change_keys_http2db(content, http2db_tenant, reverse=True)
545 deleted = mydb.delete_row(FROM='datacenter_nets', WHERE= where_)
546 if deleted == 0 and netmap_id :
547 bottle.abort(HTTP_Not_Found, "No netmap found with " + " and ".join(map(lambda x: str(x[0])+": "+str(x[1]), where_.iteritems())) )
548 if netmap_id:
549 return format_out({"result": "netmap %s deleted" % netmap_id})
tierno7edb6752016-03-21 17:37:52 +0100550 else:
tiernof97fd272016-07-11 14:32:37 +0200551 return format_out({"result": "%d netmap deleted" % deleted})
552 except (nfvo.NfvoException, db_base_Exception) as e:
553 logger.error("http_delnetmap_datacenter_id error {}: {}".format(e.http_code, str(e)))
554 bottle.abort(e.http_code, str(e))
tierno7edb6752016-03-21 17:37:52 +0100555
556
557@bottle.route(url_base + '/<tenant_id>/datacenters/<datacenter_id>/netmaps/upload', method='POST')
558def http_uploadnetmap_datacenter_id(tenant_id, datacenter_id):
tiernof97fd272016-07-11 14:32:37 +0200559 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
560 try:
561 netmaps = nfvo.datacenter_new_netmap(mydb, tenant_id, datacenter_id, None)
562 convert_datetime2str(netmaps)
563 utils.convert_str2boolean(netmaps, ('shared', 'multipoint') )
564 data={'netmaps' : netmaps}
565 return format_out(data)
566 except (nfvo.NfvoException, db_base_Exception) as e:
567 logger.error("http_uploadnetmap_datacenter_id error {}: {}".format(e.http_code, str(e)))
568 bottle.abort(e.http_code, str(e))
tierno7edb6752016-03-21 17:37:52 +0100569
570@bottle.route(url_base + '/<tenant_id>/datacenters/<datacenter_id>/netmaps', method='POST')
571def http_postnetmap_datacenter_id(tenant_id, datacenter_id):
572 '''creates a new netmap'''
tiernof97fd272016-07-11 14:32:37 +0200573 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
tierno7edb6752016-03-21 17:37:52 +0100574 #parse input data
575 http_content,_ = format_in( netmap_new_schema )
tierno42fcc3b2016-07-06 17:20:40 +0200576 r = utils.remove_extra_items(http_content, netmap_new_schema)
tiernof97fd272016-07-11 14:32:37 +0200577 if r is not None: print "http_postnetmap_datacenter_id: Warning: remove extra items ", r
tierno7edb6752016-03-21 17:37:52 +0100578
tiernof97fd272016-07-11 14:32:37 +0200579 try:
580 #obtain data, check that only one exist
581 netmaps = nfvo.datacenter_new_netmap(mydb, tenant_id, datacenter_id, http_content)
582 convert_datetime2str(netmaps)
583 utils.convert_str2boolean(netmaps, ('shared', 'multipoint') )
584 data={'netmaps' : netmaps}
585 return format_out(data)
586 except (nfvo.NfvoException, db_base_Exception) as e:
587 logger.error("http_postnetmap_datacenter_id error {}: {}".format(e.http_code, str(e)))
588 bottle.abort(e.http_code, str(e))
tierno7edb6752016-03-21 17:37:52 +0100589
590@bottle.route(url_base + '/<tenant_id>/datacenters/<datacenter_id>/netmaps/<netmap_id>', method='PUT')
591def http_putnettmap_datacenter_id(tenant_id, datacenter_id, netmap_id):
592 '''edit a netmap'''
tiernof97fd272016-07-11 14:32:37 +0200593 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
tierno7edb6752016-03-21 17:37:52 +0100594 #parse input data
595 http_content,_ = format_in( netmap_edit_schema )
tierno42fcc3b2016-07-06 17:20:40 +0200596 r = utils.remove_extra_items(http_content, netmap_edit_schema)
tierno7edb6752016-03-21 17:37:52 +0100597 if r is not None: print "http_putnettmap_datacenter_id: Warning: remove extra items ", r
598
599 #obtain data, check that only one exist
tiernof97fd272016-07-11 14:32:37 +0200600 try:
601 nfvo.datacenter_edit_netmap(mydb, tenant_id, datacenter_id, netmap_id, http_content)
tierno7edb6752016-03-21 17:37:52 +0100602 return http_getnetmap_datacenter_id(tenant_id, datacenter_id, netmap_id)
tiernof97fd272016-07-11 14:32:37 +0200603 except (nfvo.NfvoException, db_base_Exception) as e:
604 logger.error("http_putnettmap_datacenter_id error {}: {}".format(e.http_code, str(e)))
605 bottle.abort(e.http_code, str(e))
tierno7edb6752016-03-21 17:37:52 +0100606
607
608@bottle.route(url_base + '/<tenant_id>/datacenters/<datacenter_id>/action', method='POST')
609def http_action_datacenter_id(tenant_id, datacenter_id):
610 '''perform an action over datacenter, can use both uuid or name'''
tiernof97fd272016-07-11 14:32:37 +0200611 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
tierno7edb6752016-03-21 17:37:52 +0100612 #parse input data
613 http_content,_ = format_in( datacenter_action_schema )
tierno42fcc3b2016-07-06 17:20:40 +0200614 r = utils.remove_extra_items(http_content, datacenter_action_schema)
tierno7edb6752016-03-21 17:37:52 +0100615 if r is not None: print "http_action_datacenter_id: Warning: remove extra items ", r
616
tiernof97fd272016-07-11 14:32:37 +0200617 try:
618 #obtain data, check that only one exist
619 result = nfvo.datacenter_action(mydb, tenant_id, datacenter_id, http_content)
620 if 'net-update' in http_content:
621 return http_getnetmap_datacenter_id(datacenter_id)
622 else:
623 return format_out(result)
624 except (nfvo.NfvoException, db_base_Exception) as e:
625 logger.error("http_action_datacenter_id error {}: {}".format(e.http_code, str(e)))
626 bottle.abort(e.http_code, str(e))
tierno7edb6752016-03-21 17:37:52 +0100627
628
629@bottle.route(url_base + '/datacenters/<datacenter_id>', method='DELETE')
630def http_delete_datacenter_id( datacenter_id):
631 '''delete a tenant from database, can use both uuid or name'''
632
tiernof97fd272016-07-11 14:32:37 +0200633 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
634 try:
635 data = nfvo.delete_datacenter(mydb, datacenter_id)
636 return format_out({"result":"datacenter '" + data + "' deleted"})
637 except (nfvo.NfvoException, db_base_Exception) as e:
638 logger.error("http_delete_datacenter_id error {}: {}".format(e.http_code, str(e)))
639 bottle.abort(e.http_code, str(e))
tierno7edb6752016-03-21 17:37:52 +0100640
641@bottle.route(url_base + '/<tenant_id>/datacenters/<datacenter_id>', method='POST')
642def http_associate_datacenters(tenant_id, datacenter_id):
643 '''associate an existing datacenter to a this tenant. '''
tiernof97fd272016-07-11 14:32:37 +0200644 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
tierno7edb6752016-03-21 17:37:52 +0100645 #parse input data
646 http_content,_ = format_in( datacenter_associate_schema )
tierno42fcc3b2016-07-06 17:20:40 +0200647 r = utils.remove_extra_items(http_content, datacenter_associate_schema)
tierno7edb6752016-03-21 17:37:52 +0100648 if r != None: print "http_associate_datacenters: Warning: remove extra items ", r
tiernof97fd272016-07-11 14:32:37 +0200649 try:
650 id_ = nfvo.associate_datacenter_to_tenant(mydb, tenant_id, datacenter_id,
651 http_content['datacenter'].get('vim_tenant'),
652 http_content['datacenter'].get('vim_tenant_name'),
653 http_content['datacenter'].get('vim_username'),
654 http_content['datacenter'].get('vim_password')
655 )
656 return http_get_datacenter_id(tenant_id, id_)
657 except (nfvo.NfvoException, db_base_Exception) as e:
658 logger.error("http_associate_datacenters error {}: {}".format(e.http_code, str(e)))
659 bottle.abort(e.http_code, str(e))
tierno7edb6752016-03-21 17:37:52 +0100660
661@bottle.route(url_base + '/<tenant_id>/datacenters/<datacenter_id>', method='DELETE')
662def http_deassociate_datacenters(tenant_id, datacenter_id):
663 '''deassociate an existing datacenter to a this tenant. '''
tiernof97fd272016-07-11 14:32:37 +0200664 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
665 try:
666 data = nfvo.deassociate_datacenter_to_tenant(mydb, tenant_id, datacenter_id)
667 return format_out({"result": data})
668 except (nfvo.NfvoException, db_base_Exception) as e:
669 logger.error("http_deassociate_datacenters error {}: {}".format(e.http_code, str(e)))
670 bottle.abort(e.http_code, str(e))
tierno7edb6752016-03-21 17:37:52 +0100671
tierno7edb6752016-03-21 17:37:52 +0100672@bottle.route(url_base + '/<tenant_id>/vim/<datacenter_id>/<item>', method='GET')
673@bottle.route(url_base + '/<tenant_id>/vim/<datacenter_id>/<item>/<name>', method='GET')
674def http_get_vim_items(tenant_id, datacenter_id, item, name=None):
tiernof97fd272016-07-11 14:32:37 +0200675 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
676 try:
677 data = nfvo.vim_action_get(mydb, tenant_id, datacenter_id, item, name)
tierno7edb6752016-03-21 17:37:52 +0100678 return format_out(data)
tiernof97fd272016-07-11 14:32:37 +0200679 except (nfvo.NfvoException, db_base_Exception) as e:
680 logger.error("http_get_vim_items error {}: {}".format(e.http_code, str(e)))
681 bottle.abort(e.http_code, str(e))
tierno7edb6752016-03-21 17:37:52 +0100682
683@bottle.route(url_base + '/<tenant_id>/vim/<datacenter_id>/<item>/<name>', method='DELETE')
684def http_del_vim_items(tenant_id, datacenter_id, item, name):
tiernof97fd272016-07-11 14:32:37 +0200685 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
686 try:
687 data = nfvo.vim_action_delete(mydb, tenant_id, datacenter_id, item, name)
tierno7edb6752016-03-21 17:37:52 +0100688 return format_out({"result":data})
tiernof97fd272016-07-11 14:32:37 +0200689 except (nfvo.NfvoException, db_base_Exception) as e:
690 logger.error("http_del_vim_items error {}: {}".format(e.http_code, str(e)))
691 bottle.abort(e.http_code, str(e))
tierno7edb6752016-03-21 17:37:52 +0100692@bottle.route(url_base + '/<tenant_id>/vim/<datacenter_id>/<item>', method='POST')
693def http_post_vim_items(tenant_id, datacenter_id, item):
tiernof97fd272016-07-11 14:32:37 +0200694 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
tierno7edb6752016-03-21 17:37:52 +0100695 http_content,_ = format_in( object_schema )
tiernof97fd272016-07-11 14:32:37 +0200696 try:
697 data = nfvo.vim_action_create(mydb, tenant_id, datacenter_id, item, http_content)
tierno7edb6752016-03-21 17:37:52 +0100698 return format_out(data)
tiernof97fd272016-07-11 14:32:37 +0200699 except (nfvo.NfvoException, db_base_Exception) as e:
700 logger.error("http_post_vim_items error {}: {}".format(e.http_code, str(e)))
701 bottle.abort(e.http_code, str(e))
tierno7edb6752016-03-21 17:37:52 +0100702
703@bottle.route(url_base + '/<tenant_id>/vnfs', method='GET')
704def http_get_vnfs(tenant_id):
tiernof97fd272016-07-11 14:32:37 +0200705 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
706 try:
707 if tenant_id != 'any':
708 #check valid tenant_id
709 nfvo.check_tenant(mydb, tenant_id)
710 select_,where_,limit_ = filter_query_string(bottle.request.query, None,
711 ('uuid','name','description','public', "tenant_id", "created_at") )
712 where_or = {}
713 if tenant_id != "any":
714 where_or["tenant_id"] = tenant_id
715 where_or["public"] = True
716 vnfs = mydb.get_rows(FROM='vnfs', SELECT=select_,WHERE=where_,WHERE_OR=where_or, WHERE_AND_OR="AND",LIMIT=limit_)
tierno7edb6752016-03-21 17:37:52 +0100717 #change_keys_http2db(content, http2db_vnf, reverse=True)
tiernof97fd272016-07-11 14:32:37 +0200718 utils.convert_str2boolean(vnfs, ('public',))
719 convert_datetime2str(vnfs)
720 data={'vnfs' : vnfs}
tierno7edb6752016-03-21 17:37:52 +0100721 return format_out(data)
tiernof97fd272016-07-11 14:32:37 +0200722 except (nfvo.NfvoException, db_base_Exception) as e:
723 logger.error("http_get_vnfs error {}: {}".format(e.http_code, str(e)))
724 bottle.abort(e.http_code, str(e))
tierno7edb6752016-03-21 17:37:52 +0100725
726@bottle.route(url_base + '/<tenant_id>/vnfs/<vnf_id>', method='GET')
727def http_get_vnf_id(tenant_id,vnf_id):
728 '''get vnf details, can use both uuid or name'''
tiernof97fd272016-07-11 14:32:37 +0200729 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
730 try:
731 vnf = nfvo.get_vnf_id(mydb,tenant_id,vnf_id)
732 utils.convert_str2boolean(vnf, ('public',))
733 convert_datetime2str(vnf)
734 return format_out(vnf)
735 except (nfvo.NfvoException, db_base_Exception) as e:
736 logger.error("http_get_vnf_id error {}: {}".format(e.http_code, str(e)))
737 bottle.abort(e.http_code, str(e))
tierno7edb6752016-03-21 17:37:52 +0100738
739@bottle.route(url_base + '/<tenant_id>/vnfs', method='POST')
740def http_post_vnfs(tenant_id):
741 '''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'''
tiernof97fd272016-07-11 14:32:37 +0200742 #print "Parsing the YAML file of the VNF"
tierno7edb6752016-03-21 17:37:52 +0100743 #parse input data
tiernof97fd272016-07-11 14:32:37 +0200744 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
garciadeblas9f8456e2016-09-05 05:02:59 +0200745 http_content, used_schema = format_in( vnfd_schema_v01, ("schema_version",), {"0.2": vnfd_schema_v02})
tierno42fcc3b2016-07-06 17:20:40 +0200746 r = utils.remove_extra_items(http_content, used_schema)
tierno7edb6752016-03-21 17:37:52 +0100747 if r is not None: print "http_post_vnfs: Warning: remove extra items ", r
tiernof97fd272016-07-11 14:32:37 +0200748 try:
garciadeblas9f8456e2016-09-05 05:02:59 +0200749 if http_content.get("schema_version") == None:
750 vnf_id = nfvo.new_vnf(mydb,tenant_id,http_content)
751 elif http_content.get("schema_version") == "0.2":
752 vnf_id = nfvo.new_vnf_v02(mydb,tenant_id,http_content)
753 else:
754 logger.warning('Unexpected schema_version: %s', http_content.get("schema_version"))
755 bottle.abort(HTTP_Bad_Request, "Invalid schema version")
tiernof97fd272016-07-11 14:32:37 +0200756 return http_get_vnf_id(tenant_id, vnf_id)
757 except (nfvo.NfvoException, db_base_Exception) as e:
758 logger.error("http_post_vnfs error {}: {}".format(e.http_code, str(e)))
759 bottle.abort(e.http_code, str(e))
tierno7edb6752016-03-21 17:37:52 +0100760
761@bottle.route(url_base + '/<tenant_id>/vnfs/<vnf_id>', method='DELETE')
762def http_delete_vnf_id(tenant_id,vnf_id):
763 '''delete a vnf from database, and images and flavors in VIM when appropriate, can use both uuid or name'''
tiernof97fd272016-07-11 14:32:37 +0200764 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
tierno7edb6752016-03-21 17:37:52 +0100765 #check valid tenant_id and deletes the vnf, including images,
tiernof97fd272016-07-11 14:32:37 +0200766 try:
767 data = nfvo.delete_vnf(mydb,tenant_id,vnf_id)
tierno7edb6752016-03-21 17:37:52 +0100768 #print json.dumps(data, indent=4)
769 return format_out({"result":"VNF " + data + " deleted"})
tiernof97fd272016-07-11 14:32:37 +0200770 except (nfvo.NfvoException, db_base_Exception) as e:
771 logger.error("http_delete_vnf_id error {}: {}".format(e.http_code, str(e)))
772 bottle.abort(e.http_code, str(e))
tierno7edb6752016-03-21 17:37:52 +0100773
774#@bottle.route(url_base + '/<tenant_id>/hosts/topology', method='GET')
775#@bottle.route(url_base + '/<tenant_id>/physicalview/Madrid-Alcantara', method='GET')
776@bottle.route(url_base + '/<tenant_id>/physicalview/<datacenter>', method='GET')
777def http_get_hosts(tenant_id, datacenter):
778 '''get the tidvim host hopology from the vim.'''
tiernof97fd272016-07-11 14:32:37 +0200779 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
780 #print "http_get_hosts received by tenant " + tenant_id + ' datacenter ' + datacenter
781 try:
782 if datacenter == 'treeview':
783 data = nfvo.get_hosts(mydb, tenant_id)
784 else:
785 #openmano-gui is using a hardcoded value for the datacenter
786 result, data = nfvo.get_hosts_info(mydb, tenant_id) #, datacenter)
787
788 if result < 0:
garciadeblas9f8456e2016-09-05 05:02:59 +0200789 print "http_get_hosts error %d %s" % (-result, data)
tiernof97fd272016-07-11 14:32:37 +0200790 bottle.abort(-result, data)
791 else:
792 convert_datetime2str(data)
793 print json.dumps(data, indent=4)
794 return format_out(data)
795 except (nfvo.NfvoException, db_base_Exception) as e:
garciadeblas9f8456e2016-09-05 05:02:59 +0200796 logger.error("http_get_hosts error {}: {}".format(e.http_code, str(e)))
tiernof97fd272016-07-11 14:32:37 +0200797 bottle.abort(e.http_code, str(e))
tierno7edb6752016-03-21 17:37:52 +0100798
799
800@bottle.route(url_base + '/<path:path>', method='OPTIONS')
801def http_options_deploy(path):
802 '''For some reason GUI web ask for OPTIONS that must be responded'''
803 #TODO: check correct path, and correct headers request
tiernof97fd272016-07-11 14:32:37 +0200804 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
tierno7edb6752016-03-21 17:37:52 +0100805 bottle.response.set_header('Access-Control-Allow-Methods','POST, GET, PUT, DELETE, OPTIONS')
806 bottle.response.set_header('Accept','application/yaml,application/json')
807 bottle.response.set_header('Content-Type','application/yaml,application/json')
808 bottle.response.set_header('Access-Control-Allow-Headers','content-type')
809 bottle.response.set_header('Access-Control-Allow-Origin','*')
810 return
811
812@bottle.route(url_base + '/<tenant_id>/topology/deploy', method='POST')
813def http_post_deploy(tenant_id):
814 '''post topology deploy.'''
tiernof97fd272016-07-11 14:32:37 +0200815 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
tierno7edb6752016-03-21 17:37:52 +0100816
tierno66aa0372016-07-06 17:31:12 +0200817 http_content, used_schema = format_in( nsd_schema_v01, ("schema_version",), {2: nsd_schema_v02})
tierno42fcc3b2016-07-06 17:20:40 +0200818 #r = utils.remove_extra_items(http_content, used_schema)
tierno7edb6752016-03-21 17:37:52 +0100819 #if r is not None: print "http_post_deploy: Warning: remove extra items ", r
tiernof97fd272016-07-11 14:32:37 +0200820 #print "http_post_deploy input: ", http_content
tierno7edb6752016-03-21 17:37:52 +0100821
tiernof97fd272016-07-11 14:32:37 +0200822 try:
823 scenario_id = nfvo.new_scenario(mydb, tenant_id, http_content)
824 instance = nfvo.start_scenario(mydb, tenant_id, scenario_id, http_content['name'], http_content['name'])
825 #print json.dumps(data, indent=4)
826 return format_out(instance)
827 except (nfvo.NfvoException, db_base_Exception) as e:
828 logger.error("http_post_deploy error {}: {}".format(e.http_code, str(e)))
829 bottle.abort(e.http_code, str(e))
tierno7edb6752016-03-21 17:37:52 +0100830
831@bottle.route(url_base + '/<tenant_id>/topology/verify', method='POST')
832def http_post_verify(tenant_id):
833 #TODO:
834# '''post topology verify'''
835# print "http_post_verify by tenant " + tenant_id + ' datacenter ' + datacenter
tiernof97fd272016-07-11 14:32:37 +0200836 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
tierno7edb6752016-03-21 17:37:52 +0100837 return
838
839#
840# SCENARIOS
841#
842
843@bottle.route(url_base + '/<tenant_id>/scenarios', method='POST')
844def http_post_scenarios(tenant_id):
845 '''add a scenario into the catalogue. Creates the scenario and its internal structure in the OPENMANO DB'''
tiernof97fd272016-07-11 14:32:37 +0200846 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
garciadeblas9f8456e2016-09-05 05:02:59 +0200847 http_content, used_schema = format_in( nsd_schema_v01, ("schema_version",), {2: nsd_schema_v02, "0.3": nsd_schema_v03})
tierno42fcc3b2016-07-06 17:20:40 +0200848 #r = utils.remove_extra_items(http_content, used_schema)
tierno7edb6752016-03-21 17:37:52 +0100849 #if r is not None: print "http_post_scenarios: Warning: remove extra items ", r
tiernof97fd272016-07-11 14:32:37 +0200850 #print "http_post_scenarios input: ", http_content
851 try:
852 if http_content.get("schema_version") == None:
853 scenario_id = nfvo.new_scenario(mydb, tenant_id, http_content)
garciadeblas9f8456e2016-09-05 05:02:59 +0200854 elif str(http_content.get("schema_version")) == "2":
tiernof97fd272016-07-11 14:32:37 +0200855 scenario_id = nfvo.new_scenario_v02(mydb, tenant_id, http_content)
garciadeblas9f8456e2016-09-05 05:02:59 +0200856 elif http_content.get("schema_version") == "0.3":
857 scenario_id = nfvo.new_scenario_v03(mydb, tenant_id, http_content)
858 else:
859 logger.warning('Unexpected schema_version: %s', http_content.get("schema_version"))
860 bottle.abort(HTTP_Bad_Request, "Invalid schema version")
tierno7edb6752016-03-21 17:37:52 +0100861 #print json.dumps(data, indent=4)
862 #return format_out(data)
tiernof97fd272016-07-11 14:32:37 +0200863 return http_get_scenario_id(tenant_id, scenario_id)
864 except (nfvo.NfvoException, db_base_Exception) as e:
865 logger.error("http_post_scenarios error {}: {}".format(e.http_code, str(e)))
866 bottle.abort(e.http_code, str(e))
tierno7edb6752016-03-21 17:37:52 +0100867
868@bottle.route(url_base + '/<tenant_id>/scenarios/<scenario_id>/action', method='POST')
869def http_post_scenario_action(tenant_id, scenario_id):
870 '''take an action over a scenario'''
tiernof97fd272016-07-11 14:32:37 +0200871 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
tierno7edb6752016-03-21 17:37:52 +0100872 #check valid tenant_id
tiernof97fd272016-07-11 14:32:37 +0200873 try:
874 nfvo.check_tenant(mydb, tenant_id)
875 #parse input data
876 http_content,_ = format_in( scenario_action_schema )
877 r = utils.remove_extra_items(http_content, scenario_action_schema)
878 if r is not None: print "http_post_scenario_action: Warning: remove extra items ", r
879 if "start" in http_content:
880 data = nfvo.start_scenario(mydb, tenant_id, scenario_id, http_content['start']['instance_name'], \
881 http_content['start'].get('description',http_content['start']['instance_name']),
882 http_content['start'].get('datacenter') )
tierno7edb6752016-03-21 17:37:52 +0100883 return format_out(data)
tiernof97fd272016-07-11 14:32:37 +0200884 elif "deploy" in http_content: #Equivalent to start
885 data = nfvo.start_scenario(mydb, tenant_id, scenario_id, http_content['deploy']['instance_name'],
886 http_content['deploy'].get('description',http_content['deploy']['instance_name']),
887 http_content['deploy'].get('datacenter') )
tierno7edb6752016-03-21 17:37:52 +0100888 return format_out(data)
tiernof97fd272016-07-11 14:32:37 +0200889 elif "reserve" in http_content: #Reserve resources
890 data = nfvo.start_scenario(mydb, tenant_id, scenario_id, http_content['reserve']['instance_name'],
891 http_content['reserve'].get('description',http_content['reserve']['instance_name']),
892 http_content['reserve'].get('datacenter'), startvms=False )
tierno7edb6752016-03-21 17:37:52 +0100893 return format_out(data)
tiernof97fd272016-07-11 14:32:37 +0200894 elif "verify" in http_content: #Equivalent to start and then delete
895 data = nfvo.start_scenario(mydb, tenant_id, scenario_id, http_content['verify']['instance_name'],
896 http_content['verify'].get('description',http_content['verify']['instance_name']),
897 http_content['verify'].get('datacenter'), startvms=False )
898 instance_id = data['uuid']
899 nfvo.delete_instance(mydb, tenant_id,instance_id)
tierno7edb6752016-03-21 17:37:52 +0100900 return format_out({"result":"Verify OK"})
tiernof97fd272016-07-11 14:32:37 +0200901 except (nfvo.NfvoException, db_base_Exception) as e:
902 logger.error("http_post_scenario_action error {}: {}".format(e.http_code, str(e)))
903 bottle.abort(e.http_code, str(e))
tierno7edb6752016-03-21 17:37:52 +0100904
905@bottle.route(url_base + '/<tenant_id>/scenarios', method='GET')
906def http_get_scenarios(tenant_id):
907 '''get scenarios list'''
tiernof97fd272016-07-11 14:32:37 +0200908 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
909 try:
910 #check valid tenant_id
911 if tenant_id != "any":
912 nfvo.check_tenant(mydb, tenant_id)
913 #obtain data
914 s,w,l=filter_query_string(bottle.request.query, None, ('uuid', 'name', 'description', 'tenant_id', 'created_at', 'public'))
915 where_or={}
916 if tenant_id != "any":
917 where_or["tenant_id"] = tenant_id
918 where_or["public"] = True
919 scenarios = mydb.get_rows(SELECT=s, WHERE=w, WHERE_OR=where_or, WHERE_AND_OR="AND", LIMIT=l, FROM='scenarios')
920 convert_datetime2str(scenarios)
921 utils.convert_str2boolean(scenarios, ('public',) )
922 data={'scenarios':scenarios}
tierno7edb6752016-03-21 17:37:52 +0100923 #print json.dumps(scenarios, indent=4)
tiernof97fd272016-07-11 14:32:37 +0200924 return format_out(data)
925 except (nfvo.NfvoException, db_base_Exception) as e:
926 logger.error("http_get_scenarios error {}: {}".format(e.http_code, str(e)))
927 bottle.abort(e.http_code, str(e))
tierno7edb6752016-03-21 17:37:52 +0100928
929@bottle.route(url_base + '/<tenant_id>/scenarios/<scenario_id>', method='GET')
930def http_get_scenario_id(tenant_id, scenario_id):
931 '''get scenario details, can use both uuid or name'''
tiernof97fd272016-07-11 14:32:37 +0200932 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
933 try:
934 #check valid tenant_id
935 if tenant_id != "any":
936 nfvo.check_tenant(mydb, tenant_id)
937 #obtain data
938 scenario = mydb.get_scenario(scenario_id, tenant_id)
939 convert_datetime2str(scenario)
940 data={'scenario' : scenario}
tierno7edb6752016-03-21 17:37:52 +0100941 return format_out(data)
tiernof97fd272016-07-11 14:32:37 +0200942 except (nfvo.NfvoException, db_base_Exception) as e:
943 logger.error("http_get_scenarios error {}: {}".format(e.http_code, str(e)))
944 bottle.abort(e.http_code, str(e))
tierno7edb6752016-03-21 17:37:52 +0100945
946@bottle.route(url_base + '/<tenant_id>/scenarios/<scenario_id>', method='DELETE')
947def http_delete_scenario_id(tenant_id, scenario_id):
948 '''delete a scenario from database, can use both uuid or name'''
tiernof97fd272016-07-11 14:32:37 +0200949 try:
950 #check valid tenant_id
951 if tenant_id != "any":
952 nfvo.check_tenant(mydb, tenant_id)
953 #obtain data
954 data = mydb.delete_scenario(scenario_id, tenant_id)
tierno7edb6752016-03-21 17:37:52 +0100955 #print json.dumps(data, indent=4)
956 return format_out({"result":"scenario " + data + " deleted"})
tiernof97fd272016-07-11 14:32:37 +0200957 except (nfvo.NfvoException, db_base_Exception) as e:
958 logger.error("http_delete_scenario_id error {}: {}".format(e.http_code, str(e)))
959 bottle.abort(e.http_code, str(e))
tierno7edb6752016-03-21 17:37:52 +0100960
961
962@bottle.route(url_base + '/<tenant_id>/scenarios/<scenario_id>', method='PUT')
963def http_put_scenario_id(tenant_id, scenario_id):
964 '''edit an existing scenario id'''
tiernof97fd272016-07-11 14:32:37 +0200965 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
tierno7edb6752016-03-21 17:37:52 +0100966 http_content,_ = format_in( scenario_edit_schema )
tierno42fcc3b2016-07-06 17:20:40 +0200967 #r = utils.remove_extra_items(http_content, scenario_edit_schema)
tierno7edb6752016-03-21 17:37:52 +0100968 #if r is not None: print "http_put_scenario_id: Warning: remove extra items ", r
tiernof97fd272016-07-11 14:32:37 +0200969 #print "http_put_scenario_id input: ", http_content
970 try:
971 nfvo.edit_scenario(mydb, tenant_id, scenario_id, http_content)
tierno7edb6752016-03-21 17:37:52 +0100972 #print json.dumps(data, indent=4)
973 #return format_out(data)
tiernof97fd272016-07-11 14:32:37 +0200974 return http_get_scenario_id(tenant_id, scenario_id)
975 except (nfvo.NfvoException, db_base_Exception) as e:
976 logger.error("http_put_scenario_id error {}: {}".format(e.http_code, str(e)))
977 bottle.abort(e.http_code, str(e))
tierno7edb6752016-03-21 17:37:52 +0100978
979@bottle.route(url_base + '/<tenant_id>/instances', method='POST')
980def http_post_instances(tenant_id):
981 '''take an action over a scenario'''
tiernof97fd272016-07-11 14:32:37 +0200982 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
983 try:
984 #check valid tenant_id
985 if tenant_id != "any":
986 nfvo.check_tenant(mydb, tenant_id)
987 #parse input data
garciadeblas0c317ee2016-08-29 12:33:06 +0200988 http_content,used_schema = format_in( instance_scenario_create_schema_v01)
tiernof97fd272016-07-11 14:32:37 +0200989 r = utils.remove_extra_items(http_content, used_schema)
tiernoa4e1a6e2016-08-31 14:19:40 +0200990 if r is not None:
991 logger.warning("http_post_instances: Warning: remove extra items %s", str(r))
tiernof97fd272016-07-11 14:32:37 +0200992 data = nfvo.create_instance(mydb, tenant_id, http_content["instance"])
tierno7edb6752016-03-21 17:37:52 +0100993 return format_out(data)
tiernof97fd272016-07-11 14:32:37 +0200994 except (nfvo.NfvoException, db_base_Exception) as e:
995 logger.error("http_post_instances error {}: {}".format(e.http_code, str(e)))
996 bottle.abort(e.http_code, str(e))
tierno7edb6752016-03-21 17:37:52 +0100997
998#
999# INSTANCES
1000#
1001@bottle.route(url_base + '/<tenant_id>/instances', method='GET')
1002def http_get_instances(tenant_id):
1003 '''get instance list'''
tiernof97fd272016-07-11 14:32:37 +02001004 try:
1005 #check valid tenant_id
1006 if tenant_id != "any":
1007 nfvo.check_tenant(mydb, tenant_id)
1008 #obtain data
1009 s,w,l=filter_query_string(bottle.request.query, None, ('uuid', 'name', 'scenario_id', 'tenant_id', 'description', 'created_at'))
1010 if tenant_id != "any":
1011 w['tenant_id'] = tenant_id
1012 instances = mydb.get_rows(SELECT=s, WHERE=w, LIMIT=l, FROM='instance_scenarios')
1013 convert_datetime2str(instances)
1014 utils.convert_str2boolean(instances, ('public',) )
1015 data={'instances':instances}
1016 return format_out(data)
1017 except (nfvo.NfvoException, db_base_Exception) as e:
1018 logger.error("http_get_instances error {}: {}".format(e.http_code, str(e)))
1019 bottle.abort(e.http_code, str(e))
tierno7edb6752016-03-21 17:37:52 +01001020
1021@bottle.route(url_base + '/<tenant_id>/instances/<instance_id>', method='GET')
1022def http_get_instance_id(tenant_id, instance_id):
1023 '''get instances details, can use both uuid or name'''
tiernof97fd272016-07-11 14:32:37 +02001024 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
1025 try:
1026 #check valid tenant_id
1027 if tenant_id != "any":
1028 nfvo.check_tenant(mydb, tenant_id)
1029 if tenant_id == "any":
1030 tenant_id = None
1031 #obtain data (first time is only to check that the instance exists)
1032 instance_dict = mydb.get_instance_scenario(instance_id, tenant_id, verbose=True)
1033 try:
1034 nfvo.refresh_instance(mydb, tenant_id, instance_dict)
1035 except (nfvo.NfvoException, db_base_Exception) as e:
1036 logger.warn("nfvo.refresh_instance couldn't refresh the status of the instance: %s" % str(e))
1037 #obtain data with results upated
1038 instance = mydb.get_instance_scenario(instance_id, tenant_id)
1039 convert_datetime2str(instance)
1040 #print json.dumps(instance, indent=4)
1041 return format_out(instance)
1042 except (nfvo.NfvoException, db_base_Exception) as e:
1043 logger.error("http_get_instance_id error {}: {}".format(e.http_code, str(e)))
1044 bottle.abort(e.http_code, str(e))
tierno7edb6752016-03-21 17:37:52 +01001045
1046@bottle.route(url_base + '/<tenant_id>/instances/<instance_id>', method='DELETE')
1047def http_delete_instance_id(tenant_id, instance_id):
1048 '''delete instance from VIM and from database, can use both uuid or name'''
tiernof97fd272016-07-11 14:32:37 +02001049 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
1050 try:
1051 #check valid tenant_id
1052 if tenant_id != "any":
1053 nfvo.check_tenant(mydb, tenant_id)
1054 if tenant_id == "any":
1055 tenant_id = None
1056 #obtain data
1057 message = nfvo.delete_instance(mydb, tenant_id,instance_id)
tierno7edb6752016-03-21 17:37:52 +01001058 return format_out({"result":message})
tiernof97fd272016-07-11 14:32:37 +02001059 except (nfvo.NfvoException, db_base_Exception) as e:
1060 logger.error("http_delete_instance_id error {}: {}".format(e.http_code, str(e)))
1061 bottle.abort(e.http_code, str(e))
tierno7edb6752016-03-21 17:37:52 +01001062
1063@bottle.route(url_base + '/<tenant_id>/instances/<instance_id>/action', method='POST')
1064def http_post_instance_scenario_action(tenant_id, instance_id):
1065 '''take an action over a scenario instance'''
tiernof97fd272016-07-11 14:32:37 +02001066 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
1067 try:
1068 #check valid tenant_id
1069 if tenant_id != "any":
1070 nfvo.check_tenant(mydb, tenant_id)
1071
1072 #parse input data
1073 http_content,_ = format_in( instance_scenario_action_schema )
1074 r = utils.remove_extra_items(http_content, instance_scenario_action_schema)
1075 if r is not None: print "http_post_instance_scenario_action: Warning: remove extra items ", r
1076 #print "http_post_instance_scenario_action input: ", http_content
1077 #obtain data
1078 instance = mydb.get_instance_scenario(instance_id, tenant_id)
1079 instance_id = instance["uuid"]
1080
1081 data = nfvo.instance_action(mydb, tenant_id, instance_id, http_content)
tierno7edb6752016-03-21 17:37:52 +01001082 return format_out(data)
tiernof97fd272016-07-11 14:32:37 +02001083 except (nfvo.NfvoException, db_base_Exception) as e:
1084 logger.error("http_post_instance_scenario_action error {}: {}".format(e.http_code, str(e)))
1085 bottle.abort(e.http_code, str(e))
tierno7edb6752016-03-21 17:37:52 +01001086
1087
1088@bottle.error(400)
1089@bottle.error(401)
1090@bottle.error(404)
1091@bottle.error(403)
1092@bottle.error(405)
1093@bottle.error(406)
1094@bottle.error(409)
1095@bottle.error(503)
1096@bottle.error(500)
1097def error400(error):
1098 e={"error":{"code":error.status_code, "type":error.status, "description":error.body}}
1099 bottle.response.headers['Access-Control-Allow-Origin'] = '*'
1100 return format_out(e)
1101