blob: a4e7fb9bf2bbb9fd19d2b9d852369859d98c8475 [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"
tierno73ad9e42016-09-12 18:11:11 +020055logger = None
tierno7edb6752016-03-21 17:37:52 +010056
57HTTP_Bad_Request = 400
58HTTP_Unauthorized = 401
59HTTP_Not_Found = 404
60HTTP_Forbidden = 403
61HTTP_Method_Not_Allowed = 405
62HTTP_Not_Acceptable = 406
63HTTP_Service_Unavailable = 503
64HTTP_Internal_Server_Error= 500
65
66def delete_nulls(var):
67 if type(var) is dict:
68 for k in var.keys():
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:
74 for k in var:
75 if type(k) is dict: delete_nulls(k)
76 if len(var) == 0: return True
77 return False
78
79def 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
82 '''
83 if type(var) is dict:
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:
91 for v in var:
92 convert_datetime2str(v)
93
tiernof97fd272016-07-11 14:32:37 +020094def log_to_logger(fn):
95 '''
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.)
98 '''
99 @wraps(fn)
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,
105 bottle.request.url,
106 bottle.response.status))
107 return actual_response
108 return _log_to_logger
tierno7edb6752016-03-21 17:37:52 +0100109
110class httpserver(threading.Thread):
111 def __init__(self, db, admin=False, host='localhost', port=9090):
112 #global url_base
113 global mydb
tiernof97fd272016-07-11 14:32:37 +0200114 global logger
tierno7edb6752016-03-21 17:37:52 +0100115 #initialization
tierno73ad9e42016-09-12 18:11:11 +0200116 if not logger:
117 logger = logging.getLogger('openmano.http')
tierno7edb6752016-03-21 17:37:52 +0100118 threading.Thread.__init__(self)
119 self.host = host
120 self.port = port #Port where the listen service must be started
121 if admin==True:
122 self.name = "http_admin"
123 else:
124 self.name = "http"
125 #self.url_preffix = 'http://' + host + ':' + str(port) + url_base
126 mydb = db
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
130 self.daemon = True
131 self.setDaemon(True)
132
133 def run(self):
tiernof97fd272016-07-11 14:32:37 +0200134 bottle.install(log_to_logger)
135 bottle.run(host=self.host, port=self.port, debug=False, quiet=True)
tierno7edb6752016-03-21 17:37:52 +0100136
137def run_bottle(db, host_='localhost', port_=9090):
138 '''used for launching in main thread, so that it can be debugged'''
139 global mydb
140 mydb = db
141 bottle.run(host=host_, port=port_, debug=True) #quiet=True
142
143
144@bottle.route(url_base + '/', method='GET')
145def http_get():
146 print
147 return 'works' #TODO: to be completed
148
149#
150# Util functions
151#
152
153def 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
157 Attributes:
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:
163 for d in data:
164 change_keys_http2db(d, http_db, reverse)
165 elif type(data) is dict or type(data) is bottle.FormsDict:
166 if reverse:
167 for k,v in http_db.items():
168 if v in data: data[k]=data.pop(v)
169 else:
170 for k,v in http_db.items():
171 if k in data: data[v]=data.pop(k)
172
173def format_out(data):
174 '''return string of dictionary data according to requested json, yaml, xml. By default json'''
tiernof97fd272016-07-11 14:32:37 +0200175 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 +0100176 if 'application/yaml' in bottle.request.headers.get('Accept'):
177 bottle.response.content_type='application/yaml'
tierno7edb6752016-03-21 17:37:52 +0100178 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"
183
184def format_in(default_schema, version_fields=None, version_dict_schema=None):
185 ''' Parse the content of HTTP request against a json_schema
186 Parameters
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
191 Return:
192 user_data, used_schema: if the data is successfully decoded and matches the schema
193 launch a bottle abort if fails
194 '''
195 #print "HEADERS :" + str(bottle.request.headers.items())
196 try:
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.")
209 else:
210 print 'Content-Type ' + str(format_type) + ' not supported.'
211 bottle.abort(HTTP_Not_Acceptable, 'Content-Type ' + str(format_type) + ' not supported.')
212 return
213 #if client_data == None:
214 # bottle.abort(HTTP_Bad_Request, "Content error, empty")
215 # return
216
garciadeblas9f8456e2016-09-05 05:02:59 +0200217 #logger.debug('client-data: %s', client_data)
tierno7edb6752016-03-21 17:37:52 +0100218 #look for the client provider version
219 error_text = "Invalid content "
220 client_version = None
221 used_schema = 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]
227 else:
228 client_version=None
229 break
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")
239
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 print error_text
245 bottle.abort(HTTP_Bad_Request, error_text)
246 except js_e.ValidationError as exc:
247 print "validate_in error, jsonschema exception ", exc.message, "at", exc.path
248 error_pos = ""
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)
251 #except:
252 # bottle.abort(HTTP_Bad_Request, "Content error: Failed to parse Content-Type", error_pos)
253 # raise
254
255def filter_query_string(qs, http2db, allowed):
256 '''Process query string (qs) checking that contains only valid tokens for avoiding SQL injection
257 Attributes:
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
266 '''
267 where={}
268 limit=100
269 select=[]
tiernof97fd272016-07-11 14:32:37 +0200270 #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")
273 for k in qs:
274 if k=='field':
275 select += qs.getall(k)
276 for v in select:
277 if v not in allowed:
278 bottle.abort(HTTP_Bad_Request, "Invalid query string at 'field="+v+"'")
279 elif k=='limit':
280 try:
281 limit=int(qs[k])
282 except:
283 bottle.abort(HTTP_Bad_Request, "Invalid query string at 'limit="+qs[k]+"'")
284 else:
285 if k not in allowed:
286 bottle.abort(HTTP_Bad_Request, "Invalid query string at '"+k+"="+qs[k]+"'")
287 if qs[k]!="null": where[k]=qs[k]
288 else: where[k]=None
tierno7edb6752016-03-21 17:37:52 +0100289 if len(select)==0: select += allowed
290 #change from http api to database naming
291 for i in range(0,len(select)):
292 k=select[i]
293 if http2db and k in http2db:
294 select[i] = http2db[k]
295 if http2db:
296 change_keys_http2db(where, http2db)
tiernof97fd272016-07-11 14:32:37 +0200297 #print "filter_query_string", select,where,limit
tierno7edb6752016-03-21 17:37:52 +0100298
299 return select,where,limit
300
301@bottle.hook('after_request')
302def enable_cors():
303 '''Don't know yet if really needed. Keep it just in case'''
304 bottle.response.headers['Access-Control-Allow-Origin'] = '*'
305
306#
307# VNFs
308#
309
310@bottle.route(url_base + '/tenants', method='GET')
311def http_get_tenants():
tiernof97fd272016-07-11 14:32:37 +0200312 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
tierno7edb6752016-03-21 17:37:52 +0100313 select_,where_,limit_ = filter_query_string(bottle.request.query, None,
314 ('uuid','name','description','created_at') )
tiernof97fd272016-07-11 14:32:37 +0200315 try:
316 tenants = mydb.get_rows(FROM='nfvo_tenants', SELECT=select_,WHERE=where_,LIMIT=limit_)
tierno7edb6752016-03-21 17:37:52 +0100317 #change_keys_http2db(content, http2db_tenant, reverse=True)
tiernof97fd272016-07-11 14:32:37 +0200318 convert_datetime2str(tenants)
319 data={'tenants' : tenants}
tierno7edb6752016-03-21 17:37:52 +0100320 return format_out(data)
tiernof97fd272016-07-11 14:32:37 +0200321 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))
tierno7edb6752016-03-21 17:37:52 +0100324
325@bottle.route(url_base + '/tenants/<tenant_id>', method='GET')
326def http_get_tenant_id(tenant_id):
327 '''get tenant details, can use both uuid or name'''
328 #obtain data
tiernof97fd272016-07-11 14:32:37 +0200329 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
330 try:
331 tenant = mydb.get_table_by_uuid_name('nfvo_tenants', tenant_id, "tenant")
332 #change_keys_http2db(content, http2db_tenant, reverse=True)
333 convert_datetime2str(tenant)
334 data={'tenant' : tenant}
335 return format_out(data)
336 except db_base_Exception as e:
337 logger.error("http_get_tenant_id error {}: {}".format(e.http_code, str(e)))
338 bottle.abort(e.http_code, str(e))
tierno7edb6752016-03-21 17:37:52 +0100339
340@bottle.route(url_base + '/tenants', method='POST')
341def http_post_tenants():
342 '''insert a tenant into the catalogue. '''
343 #parse input data
tiernof97fd272016-07-11 14:32:37 +0200344 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
tierno7edb6752016-03-21 17:37:52 +0100345 http_content,_ = format_in( tenant_schema )
tierno42fcc3b2016-07-06 17:20:40 +0200346 r = utils.remove_extra_items(http_content, tenant_schema)
tierno7edb6752016-03-21 17:37:52 +0100347 if r is not None: print "http_post_tenants: Warning: remove extra items ", r
tiernof97fd272016-07-11 14:32:37 +0200348 try:
349 data = nfvo.new_tenant(mydb, http_content['tenant'])
tierno7edb6752016-03-21 17:37:52 +0100350 return http_get_tenant_id(data)
tiernof97fd272016-07-11 14:32:37 +0200351 except (nfvo.NfvoException, db_base_Exception) as e:
352 logger.error("http_post_tenants error {}: {}".format(e.http_code, str(e)))
353 bottle.abort(e.http_code, str(e))
tierno7edb6752016-03-21 17:37:52 +0100354
355@bottle.route(url_base + '/tenants/<tenant_id>', method='PUT')
356def http_edit_tenant_id(tenant_id):
357 '''edit tenant details, can use both uuid or name'''
358 #parse input data
tiernof97fd272016-07-11 14:32:37 +0200359 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
tierno7edb6752016-03-21 17:37:52 +0100360 http_content,_ = format_in( tenant_edit_schema )
tierno42fcc3b2016-07-06 17:20:40 +0200361 r = utils.remove_extra_items(http_content, tenant_edit_schema)
tierno7edb6752016-03-21 17:37:52 +0100362 if r is not None: print "http_edit_tenant_id: Warning: remove extra items ", r
363
364 #obtain data, check that only one exist
tiernof97fd272016-07-11 14:32:37 +0200365 try:
366 tenant = mydb.get_table_by_uuid_name('nfvo_tenants', tenant_id)
367 #edit data
368 tenant_id = tenant['uuid']
369 where={'uuid': tenant['uuid']}
370 mydb.update_rows('nfvo_tenants', http_content['tenant'], where)
371 return http_get_tenant_id(tenant_id)
372 except db_base_Exception as e:
373 logger.error("http_edit_tenant_id error {}: {}".format(e.http_code, str(e)))
374 bottle.abort(e.http_code, str(e))
tierno7edb6752016-03-21 17:37:52 +0100375
376@bottle.route(url_base + '/tenants/<tenant_id>', method='DELETE')
377def http_delete_tenant_id(tenant_id):
378 '''delete a tenant from database, can use both uuid or name'''
tiernof97fd272016-07-11 14:32:37 +0200379 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
380 try:
381 data = nfvo.delete_tenant(mydb, tenant_id)
tierno7edb6752016-03-21 17:37:52 +0100382 return format_out({"result":"tenant " + data + " deleted"})
tiernof97fd272016-07-11 14:32:37 +0200383 except db_base_Exception as e:
384 logger.error("http_delete_tenant_id error {}: {}".format(e.http_code, str(e)))
385 bottle.abort(e.http_code, str(e))
tierno7edb6752016-03-21 17:37:52 +0100386
387
388@bottle.route(url_base + '/<tenant_id>/datacenters', method='GET')
389def http_get_datacenters(tenant_id):
tiernof97fd272016-07-11 14:32:37 +0200390 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
391 try:
392 if tenant_id != 'any':
393 #check valid tenant_id
394 nfvo.check_tenant(mydb, tenant_id)
395 select_,where_,limit_ = filter_query_string(bottle.request.query, None,
396 ('uuid','name','vim_url','type','created_at') )
397 if tenant_id != 'any':
398 where_['nfvo_tenant_id'] = tenant_id
399 if 'created_at' in select_:
400 select_[ select_.index('created_at') ] = 'd.created_at as created_at'
401 if 'created_at' in where_:
402 where_['d.created_at'] = where_.pop('created_at')
403 datacenters = mydb.get_rows(FROM='datacenters as d join tenants_datacenters as td on d.uuid=td.datacenter_id',
404 SELECT=select_,WHERE=where_,LIMIT=limit_)
405 else:
406 datacenters = mydb.get_rows(FROM='datacenters',
407 SELECT=select_,WHERE=where_,LIMIT=limit_)
tierno7edb6752016-03-21 17:37:52 +0100408 #change_keys_http2db(content, http2db_tenant, reverse=True)
tiernof97fd272016-07-11 14:32:37 +0200409 convert_datetime2str(datacenters)
410 data={'datacenters' : datacenters}
tierno7edb6752016-03-21 17:37:52 +0100411 return format_out(data)
tiernof97fd272016-07-11 14:32:37 +0200412 except (nfvo.NfvoException, db_base_Exception) as e:
413 logger.error("http_get_datacenters error {}: {}".format(e.http_code, str(e)))
414 bottle.abort(e.http_code, str(e))
tierno7edb6752016-03-21 17:37:52 +0100415
416@bottle.route(url_base + '/<tenant_id>/datacenters/<datacenter_id>', method='GET')
417def http_get_datacenter_id(tenant_id, datacenter_id):
418 '''get datacenter details, can use both uuid or name'''
tiernof97fd272016-07-11 14:32:37 +0200419 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
420 try:
421 if tenant_id != 'any':
422 #check valid tenant_id
423 nfvo.check_tenant(mydb, tenant_id)
424 #obtain data
425 what = 'uuid' if utils.check_valid_uuid(datacenter_id) else 'name'
426 where_={}
427 where_[what] = datacenter_id
428 select_=['uuid', 'name','vim_url', 'vim_url_admin', 'type', 'config', 'description', 'd.created_at as created_at']
429 if tenant_id != 'any':
430 select_.append("datacenter_tenant_id")
431 where_['td.nfvo_tenant_id']= tenant_id
432 from_='datacenters as d join tenants_datacenters as td on d.uuid=td.datacenter_id'
433 else:
434 from_='datacenters as d'
435 datacenters = mydb.get_rows(
436 SELECT=select_,
437 FROM=from_,
438 WHERE=where_)
439
440 if len(datacenters)==0:
441 bottle.abort( HTTP_Not_Found, "No datacenter found for tenant with {} '{}'".format(what, datacenter_id) )
442 elif len(datacenters)>1:
443 bottle.abort( HTTP_Bad_Request, "More than one datacenter found for tenant with {} '{}'".format(what, datacenter_id) )
444 datacenter = datacenters[0]
445 if tenant_id != 'any':
446 #get vim tenant info
447 vim_tenants = mydb.get_rows(
448 SELECT=("vim_tenant_name", "vim_tenant_id", "user"),
449 FROM="datacenter_tenants",
450 WHERE={"uuid": datacenters[0]["datacenter_tenant_id"]},
451 ORDER_BY=("created", ) )
452 del datacenter["datacenter_tenant_id"]
453 datacenter["vim_tenants"] = vim_tenants
454
455 if datacenter['config'] != None:
456 try:
457 config_dict = yaml.load(datacenter['config'])
458 datacenter['config'] = config_dict
459 except Exception, e:
460 print "Exception '%s' while trying to load config information" % str(e)
461 #change_keys_http2db(content, http2db_datacenter, reverse=True)
462 convert_datetime2str(datacenter)
463 data={'datacenter' : datacenter}
464 return format_out(data)
465 except (nfvo.NfvoException, db_base_Exception) as e:
466 logger.error("http_get_datacenter_id error {}: {}".format(e.http_code, str(e)))
467 bottle.abort(e.http_code, str(e))
tierno7edb6752016-03-21 17:37:52 +0100468
469@bottle.route(url_base + '/datacenters', method='POST')
470def http_post_datacenters():
471 '''insert a tenant into the catalogue. '''
472 #parse input data
tiernof97fd272016-07-11 14:32:37 +0200473 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
tierno7edb6752016-03-21 17:37:52 +0100474 http_content,_ = format_in( datacenter_schema )
tierno42fcc3b2016-07-06 17:20:40 +0200475 r = utils.remove_extra_items(http_content, datacenter_schema)
tiernof97fd272016-07-11 14:32:37 +0200476 if r is not None: print "http_post_datacenters: Warning: remove extra items ", r
477 try:
478 data = nfvo.new_datacenter(mydb, http_content['datacenter'])
tierno7edb6752016-03-21 17:37:52 +0100479 return http_get_datacenter_id('any', data)
tiernof97fd272016-07-11 14:32:37 +0200480 except (nfvo.NfvoException, db_base_Exception) as e:
481 logger.error("http_post_datacenters error {}: {}".format(e.http_code, str(e)))
482 bottle.abort(e.http_code, str(e))
tierno7edb6752016-03-21 17:37:52 +0100483
484@bottle.route(url_base + '/datacenters/<datacenter_id_name>', method='PUT')
485def http_edit_datacenter_id(datacenter_id_name):
486 '''edit datacenter details, can use both uuid or name'''
tiernof97fd272016-07-11 14:32:37 +0200487 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
tierno7edb6752016-03-21 17:37:52 +0100488 #parse input data
489 http_content,_ = format_in( datacenter_edit_schema )
tierno42fcc3b2016-07-06 17:20:40 +0200490 r = utils.remove_extra_items(http_content, datacenter_edit_schema)
tierno7edb6752016-03-21 17:37:52 +0100491 if r is not None: print "http_edit_datacenter_id: Warning: remove extra items ", r
492
tiernof97fd272016-07-11 14:32:37 +0200493 try:
494 datacenter_id = nfvo.edit_datacenter(mydb, datacenter_id_name, http_content['datacenter'])
tierno7edb6752016-03-21 17:37:52 +0100495 return http_get_datacenter_id('any', datacenter_id)
tiernof97fd272016-07-11 14:32:37 +0200496 except (nfvo.NfvoException, db_base_Exception) as e:
497 logger.error("http_edit_datacenter_id error {}: {}".format(e.http_code, str(e)))
498 bottle.abort(e.http_code, str(e))
tierno7edb6752016-03-21 17:37:52 +0100499
500@bottle.route(url_base + '/<tenant_id>/datacenters/<datacenter_id>/networks', method='GET') #deprecated
501@bottle.route(url_base + '/<tenant_id>/datacenters/<datacenter_id>/netmaps', method='GET')
502@bottle.route(url_base + '/<tenant_id>/datacenters/<datacenter_id>/netmaps/<netmap_id>', method='GET')
503def http_getnetmap_datacenter_id(tenant_id, datacenter_id, netmap_id=None):
504 '''get datacenter networks, can use both uuid or name'''
tiernof97fd272016-07-11 14:32:37 +0200505 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
tierno7edb6752016-03-21 17:37:52 +0100506 #obtain data
tiernof97fd272016-07-11 14:32:37 +0200507 try:
508 datacenter_dict = mydb.get_table_by_uuid_name('datacenters', datacenter_id, "datacenter")
509 where_= {"datacenter_id":datacenter_dict['uuid']}
510 if netmap_id:
511 if utils.check_valid_uuid(netmap_id):
512 where_["uuid"] = netmap_id
513 else:
514 where_["name"] = netmap_id
515 netmaps =mydb.get_rows(FROM='datacenter_nets',
516 SELECT=('name','vim_net_id as vim_id', 'uuid', 'type','multipoint','shared','description', 'created_at'),
517 WHERE=where_ )
518 convert_datetime2str(netmaps)
519 utils.convert_str2boolean(netmaps, ('shared', 'multipoint') )
520 if netmap_id and len(netmaps)==1:
521 data={'netmap' : netmaps[0]}
522 elif netmap_id and len(netmaps)==0:
523 bottle.abort(HTTP_Not_Found, "No netmap found with " + " and ".join(map(lambda x: str(x[0])+": "+str(x[1]), where_.iteritems())) )
524 return
tierno7edb6752016-03-21 17:37:52 +0100525 else:
tiernof97fd272016-07-11 14:32:37 +0200526 data={'netmaps' : netmaps}
527 return format_out(data)
528 except (nfvo.NfvoException, db_base_Exception) as e:
529 logger.error("http_getnetwork_datacenter_id error {}: {}".format(e.http_code, str(e)))
530 bottle.abort(e.http_code, str(e))
tierno7edb6752016-03-21 17:37:52 +0100531
532@bottle.route(url_base + '/<tenant_id>/datacenters/<datacenter_id>/netmaps', method='DELETE')
533@bottle.route(url_base + '/<tenant_id>/datacenters/<datacenter_id>/netmaps/<netmap_id>', method='DELETE')
534def http_delnetmap_datacenter_id(tenant_id, datacenter_id, netmap_id=None):
535 '''get datacenter networks, can use both uuid or name'''
tiernof97fd272016-07-11 14:32:37 +0200536 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
tierno7edb6752016-03-21 17:37:52 +0100537 #obtain data
tiernof97fd272016-07-11 14:32:37 +0200538 try:
539 datacenter_dict = mydb.get_table_by_uuid_name('datacenters', datacenter_id, "datacenter")
540 where_= {"datacenter_id":datacenter_dict['uuid']}
541 if netmap_id:
542 if utils.check_valid_uuid(netmap_id):
543 where_["uuid"] = netmap_id
544 else:
545 where_["name"] = netmap_id
546 #change_keys_http2db(content, http2db_tenant, reverse=True)
547 deleted = mydb.delete_row(FROM='datacenter_nets', WHERE= where_)
548 if deleted == 0 and netmap_id :
549 bottle.abort(HTTP_Not_Found, "No netmap found with " + " and ".join(map(lambda x: str(x[0])+": "+str(x[1]), where_.iteritems())) )
550 if netmap_id:
551 return format_out({"result": "netmap %s deleted" % netmap_id})
tierno7edb6752016-03-21 17:37:52 +0100552 else:
tiernof97fd272016-07-11 14:32:37 +0200553 return format_out({"result": "%d netmap deleted" % deleted})
554 except (nfvo.NfvoException, db_base_Exception) as e:
555 logger.error("http_delnetmap_datacenter_id error {}: {}".format(e.http_code, str(e)))
556 bottle.abort(e.http_code, str(e))
tierno7edb6752016-03-21 17:37:52 +0100557
558
559@bottle.route(url_base + '/<tenant_id>/datacenters/<datacenter_id>/netmaps/upload', method='POST')
560def http_uploadnetmap_datacenter_id(tenant_id, datacenter_id):
tiernof97fd272016-07-11 14:32:37 +0200561 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
562 try:
563 netmaps = nfvo.datacenter_new_netmap(mydb, tenant_id, datacenter_id, None)
564 convert_datetime2str(netmaps)
565 utils.convert_str2boolean(netmaps, ('shared', 'multipoint') )
566 data={'netmaps' : netmaps}
567 return format_out(data)
568 except (nfvo.NfvoException, db_base_Exception) as e:
569 logger.error("http_uploadnetmap_datacenter_id error {}: {}".format(e.http_code, str(e)))
570 bottle.abort(e.http_code, str(e))
tierno7edb6752016-03-21 17:37:52 +0100571
572@bottle.route(url_base + '/<tenant_id>/datacenters/<datacenter_id>/netmaps', method='POST')
573def http_postnetmap_datacenter_id(tenant_id, datacenter_id):
574 '''creates a new netmap'''
tiernof97fd272016-07-11 14:32:37 +0200575 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
tierno7edb6752016-03-21 17:37:52 +0100576 #parse input data
577 http_content,_ = format_in( netmap_new_schema )
tierno42fcc3b2016-07-06 17:20:40 +0200578 r = utils.remove_extra_items(http_content, netmap_new_schema)
tiernof97fd272016-07-11 14:32:37 +0200579 if r is not None: print "http_postnetmap_datacenter_id: Warning: remove extra items ", r
tierno7edb6752016-03-21 17:37:52 +0100580
tiernof97fd272016-07-11 14:32:37 +0200581 try:
582 #obtain data, check that only one exist
583 netmaps = nfvo.datacenter_new_netmap(mydb, tenant_id, datacenter_id, http_content)
584 convert_datetime2str(netmaps)
585 utils.convert_str2boolean(netmaps, ('shared', 'multipoint') )
586 data={'netmaps' : netmaps}
587 return format_out(data)
588 except (nfvo.NfvoException, db_base_Exception) as e:
589 logger.error("http_postnetmap_datacenter_id error {}: {}".format(e.http_code, str(e)))
590 bottle.abort(e.http_code, str(e))
tierno7edb6752016-03-21 17:37:52 +0100591
592@bottle.route(url_base + '/<tenant_id>/datacenters/<datacenter_id>/netmaps/<netmap_id>', method='PUT')
593def http_putnettmap_datacenter_id(tenant_id, datacenter_id, netmap_id):
594 '''edit a netmap'''
tiernof97fd272016-07-11 14:32:37 +0200595 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
tierno7edb6752016-03-21 17:37:52 +0100596 #parse input data
597 http_content,_ = format_in( netmap_edit_schema )
tierno42fcc3b2016-07-06 17:20:40 +0200598 r = utils.remove_extra_items(http_content, netmap_edit_schema)
tierno7edb6752016-03-21 17:37:52 +0100599 if r is not None: print "http_putnettmap_datacenter_id: Warning: remove extra items ", r
600
601 #obtain data, check that only one exist
tiernof97fd272016-07-11 14:32:37 +0200602 try:
603 nfvo.datacenter_edit_netmap(mydb, tenant_id, datacenter_id, netmap_id, http_content)
tierno7edb6752016-03-21 17:37:52 +0100604 return http_getnetmap_datacenter_id(tenant_id, datacenter_id, netmap_id)
tiernof97fd272016-07-11 14:32:37 +0200605 except (nfvo.NfvoException, db_base_Exception) as e:
606 logger.error("http_putnettmap_datacenter_id error {}: {}".format(e.http_code, str(e)))
607 bottle.abort(e.http_code, str(e))
tierno7edb6752016-03-21 17:37:52 +0100608
609
610@bottle.route(url_base + '/<tenant_id>/datacenters/<datacenter_id>/action', method='POST')
611def http_action_datacenter_id(tenant_id, datacenter_id):
612 '''perform an action over datacenter, can use both uuid or name'''
tiernof97fd272016-07-11 14:32:37 +0200613 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
tierno7edb6752016-03-21 17:37:52 +0100614 #parse input data
615 http_content,_ = format_in( datacenter_action_schema )
tierno42fcc3b2016-07-06 17:20:40 +0200616 r = utils.remove_extra_items(http_content, datacenter_action_schema)
tierno7edb6752016-03-21 17:37:52 +0100617 if r is not None: print "http_action_datacenter_id: Warning: remove extra items ", r
618
tiernof97fd272016-07-11 14:32:37 +0200619 try:
620 #obtain data, check that only one exist
621 result = nfvo.datacenter_action(mydb, tenant_id, datacenter_id, http_content)
622 if 'net-update' in http_content:
623 return http_getnetmap_datacenter_id(datacenter_id)
624 else:
625 return format_out(result)
626 except (nfvo.NfvoException, db_base_Exception) as e:
627 logger.error("http_action_datacenter_id error {}: {}".format(e.http_code, str(e)))
628 bottle.abort(e.http_code, str(e))
tierno7edb6752016-03-21 17:37:52 +0100629
630
631@bottle.route(url_base + '/datacenters/<datacenter_id>', method='DELETE')
632def http_delete_datacenter_id( datacenter_id):
633 '''delete a tenant from database, can use both uuid or name'''
634
tiernof97fd272016-07-11 14:32:37 +0200635 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
636 try:
637 data = nfvo.delete_datacenter(mydb, datacenter_id)
638 return format_out({"result":"datacenter '" + data + "' deleted"})
639 except (nfvo.NfvoException, db_base_Exception) as e:
640 logger.error("http_delete_datacenter_id error {}: {}".format(e.http_code, str(e)))
641 bottle.abort(e.http_code, str(e))
tierno7edb6752016-03-21 17:37:52 +0100642
643@bottle.route(url_base + '/<tenant_id>/datacenters/<datacenter_id>', method='POST')
644def http_associate_datacenters(tenant_id, datacenter_id):
645 '''associate an existing datacenter to a this tenant. '''
tiernof97fd272016-07-11 14:32:37 +0200646 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
tierno7edb6752016-03-21 17:37:52 +0100647 #parse input data
648 http_content,_ = format_in( datacenter_associate_schema )
tierno42fcc3b2016-07-06 17:20:40 +0200649 r = utils.remove_extra_items(http_content, datacenter_associate_schema)
tierno7edb6752016-03-21 17:37:52 +0100650 if r != None: print "http_associate_datacenters: Warning: remove extra items ", r
tiernof97fd272016-07-11 14:32:37 +0200651 try:
652 id_ = nfvo.associate_datacenter_to_tenant(mydb, tenant_id, datacenter_id,
653 http_content['datacenter'].get('vim_tenant'),
654 http_content['datacenter'].get('vim_tenant_name'),
655 http_content['datacenter'].get('vim_username'),
656 http_content['datacenter'].get('vim_password')
657 )
658 return http_get_datacenter_id(tenant_id, id_)
659 except (nfvo.NfvoException, db_base_Exception) as e:
660 logger.error("http_associate_datacenters error {}: {}".format(e.http_code, str(e)))
661 bottle.abort(e.http_code, str(e))
tierno7edb6752016-03-21 17:37:52 +0100662
663@bottle.route(url_base + '/<tenant_id>/datacenters/<datacenter_id>', method='DELETE')
664def http_deassociate_datacenters(tenant_id, datacenter_id):
665 '''deassociate an existing datacenter to a this tenant. '''
tiernof97fd272016-07-11 14:32:37 +0200666 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
667 try:
668 data = nfvo.deassociate_datacenter_to_tenant(mydb, tenant_id, datacenter_id)
669 return format_out({"result": data})
670 except (nfvo.NfvoException, db_base_Exception) as e:
671 logger.error("http_deassociate_datacenters error {}: {}".format(e.http_code, str(e)))
672 bottle.abort(e.http_code, str(e))
tierno7edb6752016-03-21 17:37:52 +0100673
tierno7edb6752016-03-21 17:37:52 +0100674@bottle.route(url_base + '/<tenant_id>/vim/<datacenter_id>/<item>', method='GET')
675@bottle.route(url_base + '/<tenant_id>/vim/<datacenter_id>/<item>/<name>', method='GET')
676def http_get_vim_items(tenant_id, datacenter_id, item, name=None):
tiernof97fd272016-07-11 14:32:37 +0200677 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
678 try:
679 data = nfvo.vim_action_get(mydb, tenant_id, datacenter_id, item, name)
tierno7edb6752016-03-21 17:37:52 +0100680 return format_out(data)
tiernof97fd272016-07-11 14:32:37 +0200681 except (nfvo.NfvoException, db_base_Exception) as e:
682 logger.error("http_get_vim_items error {}: {}".format(e.http_code, str(e)))
683 bottle.abort(e.http_code, str(e))
tierno7edb6752016-03-21 17:37:52 +0100684
685@bottle.route(url_base + '/<tenant_id>/vim/<datacenter_id>/<item>/<name>', method='DELETE')
686def http_del_vim_items(tenant_id, datacenter_id, item, name):
tiernof97fd272016-07-11 14:32:37 +0200687 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
688 try:
689 data = nfvo.vim_action_delete(mydb, tenant_id, datacenter_id, item, name)
tierno7edb6752016-03-21 17:37:52 +0100690 return format_out({"result":data})
tiernof97fd272016-07-11 14:32:37 +0200691 except (nfvo.NfvoException, db_base_Exception) as e:
692 logger.error("http_del_vim_items error {}: {}".format(e.http_code, str(e)))
693 bottle.abort(e.http_code, str(e))
tierno7edb6752016-03-21 17:37:52 +0100694@bottle.route(url_base + '/<tenant_id>/vim/<datacenter_id>/<item>', method='POST')
695def http_post_vim_items(tenant_id, datacenter_id, item):
tiernof97fd272016-07-11 14:32:37 +0200696 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
tierno7edb6752016-03-21 17:37:52 +0100697 http_content,_ = format_in( object_schema )
tiernof97fd272016-07-11 14:32:37 +0200698 try:
699 data = nfvo.vim_action_create(mydb, tenant_id, datacenter_id, item, http_content)
tierno7edb6752016-03-21 17:37:52 +0100700 return format_out(data)
tiernof97fd272016-07-11 14:32:37 +0200701 except (nfvo.NfvoException, db_base_Exception) as e:
702 logger.error("http_post_vim_items error {}: {}".format(e.http_code, str(e)))
703 bottle.abort(e.http_code, str(e))
tierno7edb6752016-03-21 17:37:52 +0100704
705@bottle.route(url_base + '/<tenant_id>/vnfs', method='GET')
706def http_get_vnfs(tenant_id):
tiernof97fd272016-07-11 14:32:37 +0200707 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
708 try:
709 if tenant_id != 'any':
710 #check valid tenant_id
711 nfvo.check_tenant(mydb, tenant_id)
712 select_,where_,limit_ = filter_query_string(bottle.request.query, None,
713 ('uuid','name','description','public', "tenant_id", "created_at") )
714 where_or = {}
715 if tenant_id != "any":
716 where_or["tenant_id"] = tenant_id
717 where_or["public"] = True
718 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 +0100719 #change_keys_http2db(content, http2db_vnf, reverse=True)
tiernof97fd272016-07-11 14:32:37 +0200720 utils.convert_str2boolean(vnfs, ('public',))
721 convert_datetime2str(vnfs)
722 data={'vnfs' : vnfs}
tierno7edb6752016-03-21 17:37:52 +0100723 return format_out(data)
tiernof97fd272016-07-11 14:32:37 +0200724 except (nfvo.NfvoException, db_base_Exception) as e:
725 logger.error("http_get_vnfs error {}: {}".format(e.http_code, str(e)))
726 bottle.abort(e.http_code, str(e))
tierno7edb6752016-03-21 17:37:52 +0100727
728@bottle.route(url_base + '/<tenant_id>/vnfs/<vnf_id>', method='GET')
729def http_get_vnf_id(tenant_id,vnf_id):
730 '''get vnf details, can use both uuid or name'''
tiernof97fd272016-07-11 14:32:37 +0200731 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
732 try:
733 vnf = nfvo.get_vnf_id(mydb,tenant_id,vnf_id)
734 utils.convert_str2boolean(vnf, ('public',))
735 convert_datetime2str(vnf)
736 return format_out(vnf)
737 except (nfvo.NfvoException, db_base_Exception) as e:
738 logger.error("http_get_vnf_id error {}: {}".format(e.http_code, str(e)))
739 bottle.abort(e.http_code, str(e))
tierno7edb6752016-03-21 17:37:52 +0100740
741@bottle.route(url_base + '/<tenant_id>/vnfs', method='POST')
742def http_post_vnfs(tenant_id):
743 '''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 +0200744 #print "Parsing the YAML file of the VNF"
tierno7edb6752016-03-21 17:37:52 +0100745 #parse input data
tiernof97fd272016-07-11 14:32:37 +0200746 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
garciadeblas9f8456e2016-09-05 05:02:59 +0200747 http_content, used_schema = format_in( vnfd_schema_v01, ("schema_version",), {"0.2": vnfd_schema_v02})
tierno42fcc3b2016-07-06 17:20:40 +0200748 r = utils.remove_extra_items(http_content, used_schema)
tierno7edb6752016-03-21 17:37:52 +0100749 if r is not None: print "http_post_vnfs: Warning: remove extra items ", r
tiernof97fd272016-07-11 14:32:37 +0200750 try:
tierno4319dad2016-09-05 12:11:11 +0200751 if used_schema == vnfd_schema_v01:
garciadeblas9f8456e2016-09-05 05:02:59 +0200752 vnf_id = nfvo.new_vnf(mydb,tenant_id,http_content)
tierno4319dad2016-09-05 12:11:11 +0200753 elif used_schema == vnfd_schema_v02:
garciadeblas9f8456e2016-09-05 05:02:59 +0200754 vnf_id = nfvo.new_vnf_v02(mydb,tenant_id,http_content)
755 else:
756 logger.warning('Unexpected schema_version: %s', http_content.get("schema_version"))
757 bottle.abort(HTTP_Bad_Request, "Invalid schema version")
tiernof97fd272016-07-11 14:32:37 +0200758 return http_get_vnf_id(tenant_id, vnf_id)
759 except (nfvo.NfvoException, db_base_Exception) as e:
760 logger.error("http_post_vnfs error {}: {}".format(e.http_code, str(e)))
761 bottle.abort(e.http_code, str(e))
tierno7edb6752016-03-21 17:37:52 +0100762
763@bottle.route(url_base + '/<tenant_id>/vnfs/<vnf_id>', method='DELETE')
764def http_delete_vnf_id(tenant_id,vnf_id):
765 '''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 +0200766 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
tierno7edb6752016-03-21 17:37:52 +0100767 #check valid tenant_id and deletes the vnf, including images,
tiernof97fd272016-07-11 14:32:37 +0200768 try:
769 data = nfvo.delete_vnf(mydb,tenant_id,vnf_id)
tierno7edb6752016-03-21 17:37:52 +0100770 #print json.dumps(data, indent=4)
771 return format_out({"result":"VNF " + data + " deleted"})
tiernof97fd272016-07-11 14:32:37 +0200772 except (nfvo.NfvoException, db_base_Exception) as e:
773 logger.error("http_delete_vnf_id error {}: {}".format(e.http_code, str(e)))
774 bottle.abort(e.http_code, str(e))
tierno7edb6752016-03-21 17:37:52 +0100775
776#@bottle.route(url_base + '/<tenant_id>/hosts/topology', method='GET')
777#@bottle.route(url_base + '/<tenant_id>/physicalview/Madrid-Alcantara', method='GET')
778@bottle.route(url_base + '/<tenant_id>/physicalview/<datacenter>', method='GET')
779def http_get_hosts(tenant_id, datacenter):
780 '''get the tidvim host hopology from the vim.'''
tiernof97fd272016-07-11 14:32:37 +0200781 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
782 #print "http_get_hosts received by tenant " + tenant_id + ' datacenter ' + datacenter
783 try:
784 if datacenter == 'treeview':
785 data = nfvo.get_hosts(mydb, tenant_id)
786 else:
787 #openmano-gui is using a hardcoded value for the datacenter
788 result, data = nfvo.get_hosts_info(mydb, tenant_id) #, datacenter)
789
790 if result < 0:
garciadeblas9f8456e2016-09-05 05:02:59 +0200791 print "http_get_hosts error %d %s" % (-result, data)
tiernof97fd272016-07-11 14:32:37 +0200792 bottle.abort(-result, data)
793 else:
794 convert_datetime2str(data)
795 print json.dumps(data, indent=4)
796 return format_out(data)
797 except (nfvo.NfvoException, db_base_Exception) as e:
garciadeblas9f8456e2016-09-05 05:02:59 +0200798 logger.error("http_get_hosts error {}: {}".format(e.http_code, str(e)))
tiernof97fd272016-07-11 14:32:37 +0200799 bottle.abort(e.http_code, str(e))
tierno7edb6752016-03-21 17:37:52 +0100800
801
802@bottle.route(url_base + '/<path:path>', method='OPTIONS')
803def http_options_deploy(path):
804 '''For some reason GUI web ask for OPTIONS that must be responded'''
805 #TODO: check correct path, and correct headers request
tiernof97fd272016-07-11 14:32:37 +0200806 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
tierno7edb6752016-03-21 17:37:52 +0100807 bottle.response.set_header('Access-Control-Allow-Methods','POST, GET, PUT, DELETE, OPTIONS')
808 bottle.response.set_header('Accept','application/yaml,application/json')
809 bottle.response.set_header('Content-Type','application/yaml,application/json')
810 bottle.response.set_header('Access-Control-Allow-Headers','content-type')
811 bottle.response.set_header('Access-Control-Allow-Origin','*')
812 return
813
814@bottle.route(url_base + '/<tenant_id>/topology/deploy', method='POST')
815def http_post_deploy(tenant_id):
816 '''post topology deploy.'''
tiernof97fd272016-07-11 14:32:37 +0200817 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
tierno7edb6752016-03-21 17:37:52 +0100818
tierno66aa0372016-07-06 17:31:12 +0200819 http_content, used_schema = format_in( nsd_schema_v01, ("schema_version",), {2: nsd_schema_v02})
tierno42fcc3b2016-07-06 17:20:40 +0200820 #r = utils.remove_extra_items(http_content, used_schema)
tierno7edb6752016-03-21 17:37:52 +0100821 #if r is not None: print "http_post_deploy: Warning: remove extra items ", r
tiernof97fd272016-07-11 14:32:37 +0200822 #print "http_post_deploy input: ", http_content
tierno7edb6752016-03-21 17:37:52 +0100823
tiernof97fd272016-07-11 14:32:37 +0200824 try:
825 scenario_id = nfvo.new_scenario(mydb, tenant_id, http_content)
826 instance = nfvo.start_scenario(mydb, tenant_id, scenario_id, http_content['name'], http_content['name'])
827 #print json.dumps(data, indent=4)
828 return format_out(instance)
829 except (nfvo.NfvoException, db_base_Exception) as e:
830 logger.error("http_post_deploy error {}: {}".format(e.http_code, str(e)))
831 bottle.abort(e.http_code, str(e))
tierno7edb6752016-03-21 17:37:52 +0100832
833@bottle.route(url_base + '/<tenant_id>/topology/verify', method='POST')
834def http_post_verify(tenant_id):
835 #TODO:
836# '''post topology verify'''
837# print "http_post_verify by tenant " + tenant_id + ' datacenter ' + datacenter
tiernof97fd272016-07-11 14:32:37 +0200838 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
tierno7edb6752016-03-21 17:37:52 +0100839 return
840
841#
842# SCENARIOS
843#
844
845@bottle.route(url_base + '/<tenant_id>/scenarios', method='POST')
846def http_post_scenarios(tenant_id):
847 '''add a scenario into the catalogue. Creates the scenario and its internal structure in the OPENMANO DB'''
tiernof97fd272016-07-11 14:32:37 +0200848 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
garciadeblas9f8456e2016-09-05 05:02:59 +0200849 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 +0200850 #r = utils.remove_extra_items(http_content, used_schema)
tierno7edb6752016-03-21 17:37:52 +0100851 #if r is not None: print "http_post_scenarios: Warning: remove extra items ", r
tiernof97fd272016-07-11 14:32:37 +0200852 #print "http_post_scenarios input: ", http_content
853 try:
tierno4319dad2016-09-05 12:11:11 +0200854 if used_schema == nsd_schema_v01:
tiernof97fd272016-07-11 14:32:37 +0200855 scenario_id = nfvo.new_scenario(mydb, tenant_id, http_content)
tierno4319dad2016-09-05 12:11:11 +0200856 elif used_schema == nsd_schema_v02:
tiernof97fd272016-07-11 14:32:37 +0200857 scenario_id = nfvo.new_scenario_v02(mydb, tenant_id, http_content)
tierno4319dad2016-09-05 12:11:11 +0200858 elif used_schema == nsd_schema_v03:
garciadeblas9f8456e2016-09-05 05:02:59 +0200859 scenario_id = nfvo.new_scenario_v03(mydb, tenant_id, http_content)
860 else:
861 logger.warning('Unexpected schema_version: %s', http_content.get("schema_version"))
862 bottle.abort(HTTP_Bad_Request, "Invalid schema version")
tierno7edb6752016-03-21 17:37:52 +0100863 #print json.dumps(data, indent=4)
864 #return format_out(data)
tiernof97fd272016-07-11 14:32:37 +0200865 return http_get_scenario_id(tenant_id, scenario_id)
866 except (nfvo.NfvoException, db_base_Exception) as e:
867 logger.error("http_post_scenarios error {}: {}".format(e.http_code, str(e)))
868 bottle.abort(e.http_code, str(e))
tierno7edb6752016-03-21 17:37:52 +0100869
870@bottle.route(url_base + '/<tenant_id>/scenarios/<scenario_id>/action', method='POST')
871def http_post_scenario_action(tenant_id, scenario_id):
872 '''take an action over a scenario'''
tiernof97fd272016-07-11 14:32:37 +0200873 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
tierno7edb6752016-03-21 17:37:52 +0100874 #check valid tenant_id
tiernof97fd272016-07-11 14:32:37 +0200875 try:
876 nfvo.check_tenant(mydb, tenant_id)
877 #parse input data
878 http_content,_ = format_in( scenario_action_schema )
879 r = utils.remove_extra_items(http_content, scenario_action_schema)
880 if r is not None: print "http_post_scenario_action: Warning: remove extra items ", r
881 if "start" in http_content:
882 data = nfvo.start_scenario(mydb, tenant_id, scenario_id, http_content['start']['instance_name'], \
883 http_content['start'].get('description',http_content['start']['instance_name']),
884 http_content['start'].get('datacenter') )
tierno7edb6752016-03-21 17:37:52 +0100885 return format_out(data)
tiernof97fd272016-07-11 14:32:37 +0200886 elif "deploy" in http_content: #Equivalent to start
887 data = nfvo.start_scenario(mydb, tenant_id, scenario_id, http_content['deploy']['instance_name'],
888 http_content['deploy'].get('description',http_content['deploy']['instance_name']),
889 http_content['deploy'].get('datacenter') )
tierno7edb6752016-03-21 17:37:52 +0100890 return format_out(data)
tiernof97fd272016-07-11 14:32:37 +0200891 elif "reserve" in http_content: #Reserve resources
892 data = nfvo.start_scenario(mydb, tenant_id, scenario_id, http_content['reserve']['instance_name'],
893 http_content['reserve'].get('description',http_content['reserve']['instance_name']),
894 http_content['reserve'].get('datacenter'), startvms=False )
tierno7edb6752016-03-21 17:37:52 +0100895 return format_out(data)
tiernof97fd272016-07-11 14:32:37 +0200896 elif "verify" in http_content: #Equivalent to start and then delete
897 data = nfvo.start_scenario(mydb, tenant_id, scenario_id, http_content['verify']['instance_name'],
898 http_content['verify'].get('description',http_content['verify']['instance_name']),
899 http_content['verify'].get('datacenter'), startvms=False )
900 instance_id = data['uuid']
901 nfvo.delete_instance(mydb, tenant_id,instance_id)
tierno7edb6752016-03-21 17:37:52 +0100902 return format_out({"result":"Verify OK"})
tiernof97fd272016-07-11 14:32:37 +0200903 except (nfvo.NfvoException, db_base_Exception) as e:
904 logger.error("http_post_scenario_action error {}: {}".format(e.http_code, str(e)))
905 bottle.abort(e.http_code, str(e))
tierno7edb6752016-03-21 17:37:52 +0100906
907@bottle.route(url_base + '/<tenant_id>/scenarios', method='GET')
908def http_get_scenarios(tenant_id):
909 '''get scenarios list'''
tiernof97fd272016-07-11 14:32:37 +0200910 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
911 try:
912 #check valid tenant_id
913 if tenant_id != "any":
914 nfvo.check_tenant(mydb, tenant_id)
915 #obtain data
916 s,w,l=filter_query_string(bottle.request.query, None, ('uuid', 'name', 'description', 'tenant_id', 'created_at', 'public'))
917 where_or={}
918 if tenant_id != "any":
919 where_or["tenant_id"] = tenant_id
920 where_or["public"] = True
921 scenarios = mydb.get_rows(SELECT=s, WHERE=w, WHERE_OR=where_or, WHERE_AND_OR="AND", LIMIT=l, FROM='scenarios')
922 convert_datetime2str(scenarios)
923 utils.convert_str2boolean(scenarios, ('public',) )
924 data={'scenarios':scenarios}
tierno7edb6752016-03-21 17:37:52 +0100925 #print json.dumps(scenarios, indent=4)
tiernof97fd272016-07-11 14:32:37 +0200926 return format_out(data)
927 except (nfvo.NfvoException, db_base_Exception) as e:
928 logger.error("http_get_scenarios error {}: {}".format(e.http_code, str(e)))
929 bottle.abort(e.http_code, str(e))
tierno7edb6752016-03-21 17:37:52 +0100930
931@bottle.route(url_base + '/<tenant_id>/scenarios/<scenario_id>', method='GET')
932def http_get_scenario_id(tenant_id, scenario_id):
933 '''get scenario details, can use both uuid or name'''
tiernof97fd272016-07-11 14:32:37 +0200934 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
935 try:
936 #check valid tenant_id
937 if tenant_id != "any":
938 nfvo.check_tenant(mydb, tenant_id)
939 #obtain data
940 scenario = mydb.get_scenario(scenario_id, tenant_id)
941 convert_datetime2str(scenario)
942 data={'scenario' : scenario}
tierno7edb6752016-03-21 17:37:52 +0100943 return format_out(data)
tiernof97fd272016-07-11 14:32:37 +0200944 except (nfvo.NfvoException, db_base_Exception) as e:
945 logger.error("http_get_scenarios error {}: {}".format(e.http_code, str(e)))
946 bottle.abort(e.http_code, str(e))
tierno7edb6752016-03-21 17:37:52 +0100947
948@bottle.route(url_base + '/<tenant_id>/scenarios/<scenario_id>', method='DELETE')
949def http_delete_scenario_id(tenant_id, scenario_id):
950 '''delete a scenario from database, can use both uuid or name'''
tiernof97fd272016-07-11 14:32:37 +0200951 try:
952 #check valid tenant_id
953 if tenant_id != "any":
954 nfvo.check_tenant(mydb, tenant_id)
955 #obtain data
956 data = mydb.delete_scenario(scenario_id, tenant_id)
tierno7edb6752016-03-21 17:37:52 +0100957 #print json.dumps(data, indent=4)
958 return format_out({"result":"scenario " + data + " deleted"})
tiernof97fd272016-07-11 14:32:37 +0200959 except (nfvo.NfvoException, db_base_Exception) as e:
960 logger.error("http_delete_scenario_id error {}: {}".format(e.http_code, str(e)))
961 bottle.abort(e.http_code, str(e))
tierno7edb6752016-03-21 17:37:52 +0100962
963
964@bottle.route(url_base + '/<tenant_id>/scenarios/<scenario_id>', method='PUT')
965def http_put_scenario_id(tenant_id, scenario_id):
966 '''edit an existing scenario id'''
tiernof97fd272016-07-11 14:32:37 +0200967 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
tierno7edb6752016-03-21 17:37:52 +0100968 http_content,_ = format_in( scenario_edit_schema )
tierno42fcc3b2016-07-06 17:20:40 +0200969 #r = utils.remove_extra_items(http_content, scenario_edit_schema)
tierno7edb6752016-03-21 17:37:52 +0100970 #if r is not None: print "http_put_scenario_id: Warning: remove extra items ", r
tiernof97fd272016-07-11 14:32:37 +0200971 #print "http_put_scenario_id input: ", http_content
972 try:
973 nfvo.edit_scenario(mydb, tenant_id, scenario_id, http_content)
tierno7edb6752016-03-21 17:37:52 +0100974 #print json.dumps(data, indent=4)
975 #return format_out(data)
tiernof97fd272016-07-11 14:32:37 +0200976 return http_get_scenario_id(tenant_id, scenario_id)
977 except (nfvo.NfvoException, db_base_Exception) as e:
978 logger.error("http_put_scenario_id error {}: {}".format(e.http_code, str(e)))
979 bottle.abort(e.http_code, str(e))
tierno7edb6752016-03-21 17:37:52 +0100980
981@bottle.route(url_base + '/<tenant_id>/instances', method='POST')
982def http_post_instances(tenant_id):
983 '''take an action over a scenario'''
tiernof97fd272016-07-11 14:32:37 +0200984 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
985 try:
986 #check valid tenant_id
987 if tenant_id != "any":
988 nfvo.check_tenant(mydb, tenant_id)
989 #parse input data
garciadeblas0c317ee2016-08-29 12:33:06 +0200990 http_content,used_schema = format_in( instance_scenario_create_schema_v01)
tiernof97fd272016-07-11 14:32:37 +0200991 r = utils.remove_extra_items(http_content, used_schema)
tiernoa4e1a6e2016-08-31 14:19:40 +0200992 if r is not None:
993 logger.warning("http_post_instances: Warning: remove extra items %s", str(r))
tiernof97fd272016-07-11 14:32:37 +0200994 data = nfvo.create_instance(mydb, tenant_id, http_content["instance"])
tierno7edb6752016-03-21 17:37:52 +0100995 return format_out(data)
tiernof97fd272016-07-11 14:32:37 +0200996 except (nfvo.NfvoException, db_base_Exception) as e:
997 logger.error("http_post_instances error {}: {}".format(e.http_code, str(e)))
998 bottle.abort(e.http_code, str(e))
tierno7edb6752016-03-21 17:37:52 +0100999
1000#
1001# INSTANCES
1002#
1003@bottle.route(url_base + '/<tenant_id>/instances', method='GET')
1004def http_get_instances(tenant_id):
1005 '''get instance list'''
tiernof97fd272016-07-11 14:32:37 +02001006 try:
1007 #check valid tenant_id
1008 if tenant_id != "any":
1009 nfvo.check_tenant(mydb, tenant_id)
1010 #obtain data
1011 s,w,l=filter_query_string(bottle.request.query, None, ('uuid', 'name', 'scenario_id', 'tenant_id', 'description', 'created_at'))
1012 if tenant_id != "any":
1013 w['tenant_id'] = tenant_id
1014 instances = mydb.get_rows(SELECT=s, WHERE=w, LIMIT=l, FROM='instance_scenarios')
1015 convert_datetime2str(instances)
1016 utils.convert_str2boolean(instances, ('public',) )
1017 data={'instances':instances}
1018 return format_out(data)
1019 except (nfvo.NfvoException, db_base_Exception) as e:
1020 logger.error("http_get_instances error {}: {}".format(e.http_code, str(e)))
1021 bottle.abort(e.http_code, str(e))
tierno7edb6752016-03-21 17:37:52 +01001022
1023@bottle.route(url_base + '/<tenant_id>/instances/<instance_id>', method='GET')
1024def http_get_instance_id(tenant_id, instance_id):
1025 '''get instances details, can use both uuid or name'''
tiernof97fd272016-07-11 14:32:37 +02001026 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
1027 try:
1028 #check valid tenant_id
1029 if tenant_id != "any":
1030 nfvo.check_tenant(mydb, tenant_id)
1031 if tenant_id == "any":
1032 tenant_id = None
1033 #obtain data (first time is only to check that the instance exists)
1034 instance_dict = mydb.get_instance_scenario(instance_id, tenant_id, verbose=True)
1035 try:
1036 nfvo.refresh_instance(mydb, tenant_id, instance_dict)
1037 except (nfvo.NfvoException, db_base_Exception) as e:
1038 logger.warn("nfvo.refresh_instance couldn't refresh the status of the instance: %s" % str(e))
1039 #obtain data with results upated
1040 instance = mydb.get_instance_scenario(instance_id, tenant_id)
1041 convert_datetime2str(instance)
1042 #print json.dumps(instance, indent=4)
1043 return format_out(instance)
1044 except (nfvo.NfvoException, db_base_Exception) as e:
1045 logger.error("http_get_instance_id error {}: {}".format(e.http_code, str(e)))
1046 bottle.abort(e.http_code, str(e))
tierno7edb6752016-03-21 17:37:52 +01001047
1048@bottle.route(url_base + '/<tenant_id>/instances/<instance_id>', method='DELETE')
1049def http_delete_instance_id(tenant_id, instance_id):
1050 '''delete instance from VIM and from database, can use both uuid or name'''
tiernof97fd272016-07-11 14:32:37 +02001051 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
1052 try:
1053 #check valid tenant_id
1054 if tenant_id != "any":
1055 nfvo.check_tenant(mydb, tenant_id)
1056 if tenant_id == "any":
1057 tenant_id = None
1058 #obtain data
1059 message = nfvo.delete_instance(mydb, tenant_id,instance_id)
tierno7edb6752016-03-21 17:37:52 +01001060 return format_out({"result":message})
tiernof97fd272016-07-11 14:32:37 +02001061 except (nfvo.NfvoException, db_base_Exception) as e:
1062 logger.error("http_delete_instance_id error {}: {}".format(e.http_code, str(e)))
1063 bottle.abort(e.http_code, str(e))
tierno7edb6752016-03-21 17:37:52 +01001064
1065@bottle.route(url_base + '/<tenant_id>/instances/<instance_id>/action', method='POST')
1066def http_post_instance_scenario_action(tenant_id, instance_id):
1067 '''take an action over a scenario instance'''
tiernof97fd272016-07-11 14:32:37 +02001068 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
1069 try:
1070 #check valid tenant_id
1071 if tenant_id != "any":
1072 nfvo.check_tenant(mydb, tenant_id)
1073
1074 #parse input data
1075 http_content,_ = format_in( instance_scenario_action_schema )
1076 r = utils.remove_extra_items(http_content, instance_scenario_action_schema)
1077 if r is not None: print "http_post_instance_scenario_action: Warning: remove extra items ", r
1078 #print "http_post_instance_scenario_action input: ", http_content
1079 #obtain data
1080 instance = mydb.get_instance_scenario(instance_id, tenant_id)
1081 instance_id = instance["uuid"]
1082
1083 data = nfvo.instance_action(mydb, tenant_id, instance_id, http_content)
tierno7edb6752016-03-21 17:37:52 +01001084 return format_out(data)
tiernof97fd272016-07-11 14:32:37 +02001085 except (nfvo.NfvoException, db_base_Exception) as e:
1086 logger.error("http_post_instance_scenario_action error {}: {}".format(e.http_code, str(e)))
1087 bottle.abort(e.http_code, str(e))
tierno7edb6752016-03-21 17:37:52 +01001088
1089
1090@bottle.error(400)
1091@bottle.error(401)
1092@bottle.error(404)
1093@bottle.error(403)
1094@bottle.error(405)
1095@bottle.error(406)
1096@bottle.error(409)
1097@bottle.error(503)
1098@bottle.error(500)
1099def error400(error):
1100 e={"error":{"code":error.status_code, "type":error.status, "description":error.body}}
1101 bottle.response.headers['Access-Control-Allow-Origin'] = '*'
1102 return format_out(e)
1103