blob: 771ff1b024a4b57815194070bf4c3d8c9dba15db [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, \
41 nsd_schema_v01, nsd_schema_v02, 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
215 #look for the client provider version
216 error_text = "Invalid content "
217 client_version = None
218 used_schema = None
219 if version_fields != None:
220 client_version = client_data
221 for field in version_fields:
222 if field in client_version:
223 client_version = client_version[field]
224 else:
225 client_version=None
226 break
227 if client_version==None:
228 used_schema=default_schema
229 elif version_dict_schema!=None:
230 if client_version in version_dict_schema:
231 used_schema = version_dict_schema[client_version]
232 elif None in version_dict_schema:
233 used_schema = version_dict_schema[None]
234 if used_schema==None:
235 bottle.abort(HTTP_Bad_Request, "Invalid schema version or missing version field")
236
237 js_v(client_data, used_schema)
238 return client_data, used_schema
239 except (ValueError, yaml.YAMLError) as exc:
240 error_text += str(exc)
241 print error_text
242 bottle.abort(HTTP_Bad_Request, error_text)
243 except js_e.ValidationError as exc:
244 print "validate_in error, jsonschema exception ", exc.message, "at", exc.path
245 error_pos = ""
246 if len(exc.path)>0: error_pos=" at " + ":".join(map(json.dumps, exc.path))
247 bottle.abort(HTTP_Bad_Request, error_text + exc.message + error_pos)
248 #except:
249 # bottle.abort(HTTP_Bad_Request, "Content error: Failed to parse Content-Type", error_pos)
250 # raise
251
252def filter_query_string(qs, http2db, allowed):
253 '''Process query string (qs) checking that contains only valid tokens for avoiding SQL injection
254 Attributes:
255 'qs': bottle.FormsDict variable to be processed. None or empty is considered valid
256 'http2db': dictionary with change from http API naming (dictionary key) to database naming(dictionary value)
257 'allowed': list of allowed string tokens (API http naming). All the keys of 'qs' must be one of 'allowed'
258 Return: A tuple with the (select,where,limit) to be use in a database query. All of then transformed to the database naming
259 select: list of items to retrieve, filtered by query string 'field=token'. If no 'field' is present, allowed list is returned
260 where: dictionary with key, value, taken from the query string token=value. Empty if nothing is provided
261 limit: limit dictated by user with the query string 'limit'. 100 by default
262 abort if not permited, using bottel.abort
263 '''
264 where={}
265 limit=100
266 select=[]
tiernof97fd272016-07-11 14:32:37 +0200267 #if type(qs) is not bottle.FormsDict:
268 # bottle.abort(HTTP_Internal_Server_Error, '!!!!!!!!!!!!!!invalid query string not a dictionary')
269 # #bottle.abort(HTTP_Internal_Server_Error, "call programmer")
270 for k in qs:
271 if k=='field':
272 select += qs.getall(k)
273 for v in select:
274 if v not in allowed:
275 bottle.abort(HTTP_Bad_Request, "Invalid query string at 'field="+v+"'")
276 elif k=='limit':
277 try:
278 limit=int(qs[k])
279 except:
280 bottle.abort(HTTP_Bad_Request, "Invalid query string at 'limit="+qs[k]+"'")
281 else:
282 if k not in allowed:
283 bottle.abort(HTTP_Bad_Request, "Invalid query string at '"+k+"="+qs[k]+"'")
284 if qs[k]!="null": where[k]=qs[k]
285 else: where[k]=None
tierno7edb6752016-03-21 17:37:52 +0100286 if len(select)==0: select += allowed
287 #change from http api to database naming
288 for i in range(0,len(select)):
289 k=select[i]
290 if http2db and k in http2db:
291 select[i] = http2db[k]
292 if http2db:
293 change_keys_http2db(where, http2db)
tiernof97fd272016-07-11 14:32:37 +0200294 #print "filter_query_string", select,where,limit
tierno7edb6752016-03-21 17:37:52 +0100295
296 return select,where,limit
297
298@bottle.hook('after_request')
299def enable_cors():
300 '''Don't know yet if really needed. Keep it just in case'''
301 bottle.response.headers['Access-Control-Allow-Origin'] = '*'
302
303#
304# VNFs
305#
306
307@bottle.route(url_base + '/tenants', method='GET')
308def http_get_tenants():
tiernof97fd272016-07-11 14:32:37 +0200309 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
tierno7edb6752016-03-21 17:37:52 +0100310 select_,where_,limit_ = filter_query_string(bottle.request.query, None,
311 ('uuid','name','description','created_at') )
tiernof97fd272016-07-11 14:32:37 +0200312 try:
313 tenants = mydb.get_rows(FROM='nfvo_tenants', SELECT=select_,WHERE=where_,LIMIT=limit_)
tierno7edb6752016-03-21 17:37:52 +0100314 #change_keys_http2db(content, http2db_tenant, reverse=True)
tiernof97fd272016-07-11 14:32:37 +0200315 convert_datetime2str(tenants)
316 data={'tenants' : tenants}
tierno7edb6752016-03-21 17:37:52 +0100317 return format_out(data)
tiernof97fd272016-07-11 14:32:37 +0200318 except db_base_Exception as e:
319 logger.error("http_get_tenants error {}: {}".format(e.http_code, str(e)))
320 bottle.abort(e.http_code, str(e))
tierno7edb6752016-03-21 17:37:52 +0100321
322@bottle.route(url_base + '/tenants/<tenant_id>', method='GET')
323def http_get_tenant_id(tenant_id):
324 '''get tenant details, can use both uuid or name'''
325 #obtain data
tiernof97fd272016-07-11 14:32:37 +0200326 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
327 try:
328 tenant = mydb.get_table_by_uuid_name('nfvo_tenants', tenant_id, "tenant")
329 #change_keys_http2db(content, http2db_tenant, reverse=True)
330 convert_datetime2str(tenant)
331 data={'tenant' : tenant}
332 return format_out(data)
333 except db_base_Exception as e:
334 logger.error("http_get_tenant_id error {}: {}".format(e.http_code, str(e)))
335 bottle.abort(e.http_code, str(e))
tierno7edb6752016-03-21 17:37:52 +0100336
337@bottle.route(url_base + '/tenants', method='POST')
338def http_post_tenants():
339 '''insert a tenant into the catalogue. '''
340 #parse input data
tiernof97fd272016-07-11 14:32:37 +0200341 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
tierno7edb6752016-03-21 17:37:52 +0100342 http_content,_ = format_in( tenant_schema )
tierno42fcc3b2016-07-06 17:20:40 +0200343 r = utils.remove_extra_items(http_content, tenant_schema)
tierno7edb6752016-03-21 17:37:52 +0100344 if r is not None: print "http_post_tenants: Warning: remove extra items ", r
tiernof97fd272016-07-11 14:32:37 +0200345 try:
346 data = nfvo.new_tenant(mydb, http_content['tenant'])
tierno7edb6752016-03-21 17:37:52 +0100347 return http_get_tenant_id(data)
tiernof97fd272016-07-11 14:32:37 +0200348 except (nfvo.NfvoException, db_base_Exception) as e:
349 logger.error("http_post_tenants error {}: {}".format(e.http_code, str(e)))
350 bottle.abort(e.http_code, str(e))
tierno7edb6752016-03-21 17:37:52 +0100351
352@bottle.route(url_base + '/tenants/<tenant_id>', method='PUT')
353def http_edit_tenant_id(tenant_id):
354 '''edit tenant details, can use both uuid or name'''
355 #parse input data
tiernof97fd272016-07-11 14:32:37 +0200356 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
tierno7edb6752016-03-21 17:37:52 +0100357 http_content,_ = format_in( tenant_edit_schema )
tierno42fcc3b2016-07-06 17:20:40 +0200358 r = utils.remove_extra_items(http_content, tenant_edit_schema)
tierno7edb6752016-03-21 17:37:52 +0100359 if r is not None: print "http_edit_tenant_id: Warning: remove extra items ", r
360
361 #obtain data, check that only one exist
tiernof97fd272016-07-11 14:32:37 +0200362 try:
363 tenant = mydb.get_table_by_uuid_name('nfvo_tenants', tenant_id)
364 #edit data
365 tenant_id = tenant['uuid']
366 where={'uuid': tenant['uuid']}
367 mydb.update_rows('nfvo_tenants', http_content['tenant'], where)
368 return http_get_tenant_id(tenant_id)
369 except db_base_Exception as e:
370 logger.error("http_edit_tenant_id error {}: {}".format(e.http_code, str(e)))
371 bottle.abort(e.http_code, str(e))
tierno7edb6752016-03-21 17:37:52 +0100372
373@bottle.route(url_base + '/tenants/<tenant_id>', method='DELETE')
374def http_delete_tenant_id(tenant_id):
375 '''delete a tenant from database, can use both uuid or name'''
tiernof97fd272016-07-11 14:32:37 +0200376 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
377 try:
378 data = nfvo.delete_tenant(mydb, tenant_id)
tierno7edb6752016-03-21 17:37:52 +0100379 return format_out({"result":"tenant " + data + " deleted"})
tiernof97fd272016-07-11 14:32:37 +0200380 except db_base_Exception as e:
381 logger.error("http_delete_tenant_id error {}: {}".format(e.http_code, str(e)))
382 bottle.abort(e.http_code, str(e))
tierno7edb6752016-03-21 17:37:52 +0100383
384
385@bottle.route(url_base + '/<tenant_id>/datacenters', method='GET')
386def http_get_datacenters(tenant_id):
tiernof97fd272016-07-11 14:32:37 +0200387 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
388 try:
389 if tenant_id != 'any':
390 #check valid tenant_id
391 nfvo.check_tenant(mydb, tenant_id)
392 select_,where_,limit_ = filter_query_string(bottle.request.query, None,
393 ('uuid','name','vim_url','type','created_at') )
394 if tenant_id != 'any':
395 where_['nfvo_tenant_id'] = tenant_id
396 if 'created_at' in select_:
397 select_[ select_.index('created_at') ] = 'd.created_at as created_at'
398 if 'created_at' in where_:
399 where_['d.created_at'] = where_.pop('created_at')
400 datacenters = mydb.get_rows(FROM='datacenters as d join tenants_datacenters as td on d.uuid=td.datacenter_id',
401 SELECT=select_,WHERE=where_,LIMIT=limit_)
402 else:
403 datacenters = mydb.get_rows(FROM='datacenters',
404 SELECT=select_,WHERE=where_,LIMIT=limit_)
tierno7edb6752016-03-21 17:37:52 +0100405 #change_keys_http2db(content, http2db_tenant, reverse=True)
tiernof97fd272016-07-11 14:32:37 +0200406 convert_datetime2str(datacenters)
407 data={'datacenters' : datacenters}
tierno7edb6752016-03-21 17:37:52 +0100408 return format_out(data)
tiernof97fd272016-07-11 14:32:37 +0200409 except (nfvo.NfvoException, db_base_Exception) as e:
410 logger.error("http_get_datacenters error {}: {}".format(e.http_code, str(e)))
411 bottle.abort(e.http_code, str(e))
tierno7edb6752016-03-21 17:37:52 +0100412
413@bottle.route(url_base + '/<tenant_id>/datacenters/<datacenter_id>', method='GET')
414def http_get_datacenter_id(tenant_id, datacenter_id):
415 '''get datacenter details, can use both uuid or name'''
tiernof97fd272016-07-11 14:32:37 +0200416 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
417 try:
418 if tenant_id != 'any':
419 #check valid tenant_id
420 nfvo.check_tenant(mydb, tenant_id)
421 #obtain data
422 what = 'uuid' if utils.check_valid_uuid(datacenter_id) else 'name'
423 where_={}
424 where_[what] = datacenter_id
425 select_=['uuid', 'name','vim_url', 'vim_url_admin', 'type', 'config', 'description', 'd.created_at as created_at']
426 if tenant_id != 'any':
427 select_.append("datacenter_tenant_id")
428 where_['td.nfvo_tenant_id']= tenant_id
429 from_='datacenters as d join tenants_datacenters as td on d.uuid=td.datacenter_id'
430 else:
431 from_='datacenters as d'
432 datacenters = mydb.get_rows(
433 SELECT=select_,
434 FROM=from_,
435 WHERE=where_)
436
437 if len(datacenters)==0:
438 bottle.abort( HTTP_Not_Found, "No datacenter found for tenant with {} '{}'".format(what, datacenter_id) )
439 elif len(datacenters)>1:
440 bottle.abort( HTTP_Bad_Request, "More than one datacenter found for tenant with {} '{}'".format(what, datacenter_id) )
441 datacenter = datacenters[0]
442 if tenant_id != 'any':
443 #get vim tenant info
444 vim_tenants = mydb.get_rows(
445 SELECT=("vim_tenant_name", "vim_tenant_id", "user"),
446 FROM="datacenter_tenants",
447 WHERE={"uuid": datacenters[0]["datacenter_tenant_id"]},
448 ORDER_BY=("created", ) )
449 del datacenter["datacenter_tenant_id"]
450 datacenter["vim_tenants"] = vim_tenants
451
452 if datacenter['config'] != None:
453 try:
454 config_dict = yaml.load(datacenter['config'])
455 datacenter['config'] = config_dict
456 except Exception, e:
457 print "Exception '%s' while trying to load config information" % str(e)
458 #change_keys_http2db(content, http2db_datacenter, reverse=True)
459 convert_datetime2str(datacenter)
460 data={'datacenter' : datacenter}
461 return format_out(data)
462 except (nfvo.NfvoException, db_base_Exception) as e:
463 logger.error("http_get_datacenter_id error {}: {}".format(e.http_code, str(e)))
464 bottle.abort(e.http_code, str(e))
tierno7edb6752016-03-21 17:37:52 +0100465
466@bottle.route(url_base + '/datacenters', method='POST')
467def http_post_datacenters():
468 '''insert a tenant into the catalogue. '''
469 #parse input data
tiernof97fd272016-07-11 14:32:37 +0200470 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
tierno7edb6752016-03-21 17:37:52 +0100471 http_content,_ = format_in( datacenter_schema )
tierno42fcc3b2016-07-06 17:20:40 +0200472 r = utils.remove_extra_items(http_content, datacenter_schema)
tiernof97fd272016-07-11 14:32:37 +0200473 if r is not None: print "http_post_datacenters: Warning: remove extra items ", r
474 try:
475 data = nfvo.new_datacenter(mydb, http_content['datacenter'])
tierno7edb6752016-03-21 17:37:52 +0100476 return http_get_datacenter_id('any', data)
tiernof97fd272016-07-11 14:32:37 +0200477 except (nfvo.NfvoException, db_base_Exception) as e:
478 logger.error("http_post_datacenters error {}: {}".format(e.http_code, str(e)))
479 bottle.abort(e.http_code, str(e))
tierno7edb6752016-03-21 17:37:52 +0100480
481@bottle.route(url_base + '/datacenters/<datacenter_id_name>', method='PUT')
482def http_edit_datacenter_id(datacenter_id_name):
483 '''edit datacenter details, can use both uuid or name'''
tiernof97fd272016-07-11 14:32:37 +0200484 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
tierno7edb6752016-03-21 17:37:52 +0100485 #parse input data
486 http_content,_ = format_in( datacenter_edit_schema )
tierno42fcc3b2016-07-06 17:20:40 +0200487 r = utils.remove_extra_items(http_content, datacenter_edit_schema)
tierno7edb6752016-03-21 17:37:52 +0100488 if r is not None: print "http_edit_datacenter_id: Warning: remove extra items ", r
489
tiernof97fd272016-07-11 14:32:37 +0200490 try:
491 datacenter_id = nfvo.edit_datacenter(mydb, datacenter_id_name, http_content['datacenter'])
tierno7edb6752016-03-21 17:37:52 +0100492 return http_get_datacenter_id('any', datacenter_id)
tiernof97fd272016-07-11 14:32:37 +0200493 except (nfvo.NfvoException, db_base_Exception) as e:
494 logger.error("http_edit_datacenter_id error {}: {}".format(e.http_code, str(e)))
495 bottle.abort(e.http_code, str(e))
tierno7edb6752016-03-21 17:37:52 +0100496
497@bottle.route(url_base + '/<tenant_id>/datacenters/<datacenter_id>/networks', method='GET') #deprecated
498@bottle.route(url_base + '/<tenant_id>/datacenters/<datacenter_id>/netmaps', method='GET')
499@bottle.route(url_base + '/<tenant_id>/datacenters/<datacenter_id>/netmaps/<netmap_id>', method='GET')
500def http_getnetmap_datacenter_id(tenant_id, datacenter_id, netmap_id=None):
501 '''get datacenter networks, can use both uuid or name'''
tiernof97fd272016-07-11 14:32:37 +0200502 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
tierno7edb6752016-03-21 17:37:52 +0100503 #obtain data
tiernof97fd272016-07-11 14:32:37 +0200504 try:
505 datacenter_dict = mydb.get_table_by_uuid_name('datacenters', datacenter_id, "datacenter")
506 where_= {"datacenter_id":datacenter_dict['uuid']}
507 if netmap_id:
508 if utils.check_valid_uuid(netmap_id):
509 where_["uuid"] = netmap_id
510 else:
511 where_["name"] = netmap_id
512 netmaps =mydb.get_rows(FROM='datacenter_nets',
513 SELECT=('name','vim_net_id as vim_id', 'uuid', 'type','multipoint','shared','description', 'created_at'),
514 WHERE=where_ )
515 convert_datetime2str(netmaps)
516 utils.convert_str2boolean(netmaps, ('shared', 'multipoint') )
517 if netmap_id and len(netmaps)==1:
518 data={'netmap' : netmaps[0]}
519 elif netmap_id and len(netmaps)==0:
520 bottle.abort(HTTP_Not_Found, "No netmap found with " + " and ".join(map(lambda x: str(x[0])+": "+str(x[1]), where_.iteritems())) )
521 return
tierno7edb6752016-03-21 17:37:52 +0100522 else:
tiernof97fd272016-07-11 14:32:37 +0200523 data={'netmaps' : netmaps}
524 return format_out(data)
525 except (nfvo.NfvoException, db_base_Exception) as e:
526 logger.error("http_getnetwork_datacenter_id error {}: {}".format(e.http_code, str(e)))
527 bottle.abort(e.http_code, str(e))
tierno7edb6752016-03-21 17:37:52 +0100528
529@bottle.route(url_base + '/<tenant_id>/datacenters/<datacenter_id>/netmaps', method='DELETE')
530@bottle.route(url_base + '/<tenant_id>/datacenters/<datacenter_id>/netmaps/<netmap_id>', method='DELETE')
531def http_delnetmap_datacenter_id(tenant_id, datacenter_id, netmap_id=None):
532 '''get datacenter networks, can use both uuid or name'''
tiernof97fd272016-07-11 14:32:37 +0200533 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
tierno7edb6752016-03-21 17:37:52 +0100534 #obtain data
tiernof97fd272016-07-11 14:32:37 +0200535 try:
536 datacenter_dict = mydb.get_table_by_uuid_name('datacenters', datacenter_id, "datacenter")
537 where_= {"datacenter_id":datacenter_dict['uuid']}
538 if netmap_id:
539 if utils.check_valid_uuid(netmap_id):
540 where_["uuid"] = netmap_id
541 else:
542 where_["name"] = netmap_id
543 #change_keys_http2db(content, http2db_tenant, reverse=True)
544 deleted = mydb.delete_row(FROM='datacenter_nets', WHERE= where_)
545 if deleted == 0 and netmap_id :
546 bottle.abort(HTTP_Not_Found, "No netmap found with " + " and ".join(map(lambda x: str(x[0])+": "+str(x[1]), where_.iteritems())) )
547 if netmap_id:
548 return format_out({"result": "netmap %s deleted" % netmap_id})
tierno7edb6752016-03-21 17:37:52 +0100549 else:
tiernof97fd272016-07-11 14:32:37 +0200550 return format_out({"result": "%d netmap deleted" % deleted})
551 except (nfvo.NfvoException, db_base_Exception) as e:
552 logger.error("http_delnetmap_datacenter_id error {}: {}".format(e.http_code, str(e)))
553 bottle.abort(e.http_code, str(e))
tierno7edb6752016-03-21 17:37:52 +0100554
555
556@bottle.route(url_base + '/<tenant_id>/datacenters/<datacenter_id>/netmaps/upload', method='POST')
557def http_uploadnetmap_datacenter_id(tenant_id, datacenter_id):
tiernof97fd272016-07-11 14:32:37 +0200558 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
559 try:
560 netmaps = nfvo.datacenter_new_netmap(mydb, tenant_id, datacenter_id, None)
561 convert_datetime2str(netmaps)
562 utils.convert_str2boolean(netmaps, ('shared', 'multipoint') )
563 data={'netmaps' : netmaps}
564 return format_out(data)
565 except (nfvo.NfvoException, db_base_Exception) as e:
566 logger.error("http_uploadnetmap_datacenter_id error {}: {}".format(e.http_code, str(e)))
567 bottle.abort(e.http_code, str(e))
tierno7edb6752016-03-21 17:37:52 +0100568
569@bottle.route(url_base + '/<tenant_id>/datacenters/<datacenter_id>/netmaps', method='POST')
570def http_postnetmap_datacenter_id(tenant_id, datacenter_id):
571 '''creates a new netmap'''
tiernof97fd272016-07-11 14:32:37 +0200572 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
tierno7edb6752016-03-21 17:37:52 +0100573 #parse input data
574 http_content,_ = format_in( netmap_new_schema )
tierno42fcc3b2016-07-06 17:20:40 +0200575 r = utils.remove_extra_items(http_content, netmap_new_schema)
tiernof97fd272016-07-11 14:32:37 +0200576 if r is not None: print "http_postnetmap_datacenter_id: Warning: remove extra items ", r
tierno7edb6752016-03-21 17:37:52 +0100577
tiernof97fd272016-07-11 14:32:37 +0200578 try:
579 #obtain data, check that only one exist
580 netmaps = nfvo.datacenter_new_netmap(mydb, tenant_id, datacenter_id, http_content)
581 convert_datetime2str(netmaps)
582 utils.convert_str2boolean(netmaps, ('shared', 'multipoint') )
583 data={'netmaps' : netmaps}
584 return format_out(data)
585 except (nfvo.NfvoException, db_base_Exception) as e:
586 logger.error("http_postnetmap_datacenter_id error {}: {}".format(e.http_code, str(e)))
587 bottle.abort(e.http_code, str(e))
tierno7edb6752016-03-21 17:37:52 +0100588
589@bottle.route(url_base + '/<tenant_id>/datacenters/<datacenter_id>/netmaps/<netmap_id>', method='PUT')
590def http_putnettmap_datacenter_id(tenant_id, datacenter_id, netmap_id):
591 '''edit a netmap'''
tiernof97fd272016-07-11 14:32:37 +0200592 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
tierno7edb6752016-03-21 17:37:52 +0100593 #parse input data
594 http_content,_ = format_in( netmap_edit_schema )
tierno42fcc3b2016-07-06 17:20:40 +0200595 r = utils.remove_extra_items(http_content, netmap_edit_schema)
tierno7edb6752016-03-21 17:37:52 +0100596 if r is not None: print "http_putnettmap_datacenter_id: Warning: remove extra items ", r
597
598 #obtain data, check that only one exist
tiernof97fd272016-07-11 14:32:37 +0200599 try:
600 nfvo.datacenter_edit_netmap(mydb, tenant_id, datacenter_id, netmap_id, http_content)
tierno7edb6752016-03-21 17:37:52 +0100601 return http_getnetmap_datacenter_id(tenant_id, datacenter_id, netmap_id)
tiernof97fd272016-07-11 14:32:37 +0200602 except (nfvo.NfvoException, db_base_Exception) as e:
603 logger.error("http_putnettmap_datacenter_id error {}: {}".format(e.http_code, str(e)))
604 bottle.abort(e.http_code, str(e))
tierno7edb6752016-03-21 17:37:52 +0100605
606
607@bottle.route(url_base + '/<tenant_id>/datacenters/<datacenter_id>/action', method='POST')
608def http_action_datacenter_id(tenant_id, datacenter_id):
609 '''perform an action over datacenter, can use both uuid or name'''
tiernof97fd272016-07-11 14:32:37 +0200610 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
tierno7edb6752016-03-21 17:37:52 +0100611 #parse input data
612 http_content,_ = format_in( datacenter_action_schema )
tierno42fcc3b2016-07-06 17:20:40 +0200613 r = utils.remove_extra_items(http_content, datacenter_action_schema)
tierno7edb6752016-03-21 17:37:52 +0100614 if r is not None: print "http_action_datacenter_id: Warning: remove extra items ", r
615
tiernof97fd272016-07-11 14:32:37 +0200616 try:
617 #obtain data, check that only one exist
618 result = nfvo.datacenter_action(mydb, tenant_id, datacenter_id, http_content)
619 if 'net-update' in http_content:
620 return http_getnetmap_datacenter_id(datacenter_id)
621 else:
622 return format_out(result)
623 except (nfvo.NfvoException, db_base_Exception) as e:
624 logger.error("http_action_datacenter_id error {}: {}".format(e.http_code, str(e)))
625 bottle.abort(e.http_code, str(e))
tierno7edb6752016-03-21 17:37:52 +0100626
627
628@bottle.route(url_base + '/datacenters/<datacenter_id>', method='DELETE')
629def http_delete_datacenter_id( datacenter_id):
630 '''delete a tenant from database, can use both uuid or name'''
631
tiernof97fd272016-07-11 14:32:37 +0200632 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
633 try:
634 data = nfvo.delete_datacenter(mydb, datacenter_id)
635 return format_out({"result":"datacenter '" + data + "' deleted"})
636 except (nfvo.NfvoException, db_base_Exception) as e:
637 logger.error("http_delete_datacenter_id error {}: {}".format(e.http_code, str(e)))
638 bottle.abort(e.http_code, str(e))
tierno7edb6752016-03-21 17:37:52 +0100639
640@bottle.route(url_base + '/<tenant_id>/datacenters/<datacenter_id>', method='POST')
641def http_associate_datacenters(tenant_id, datacenter_id):
642 '''associate an existing datacenter to a this tenant. '''
tiernof97fd272016-07-11 14:32:37 +0200643 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
tierno7edb6752016-03-21 17:37:52 +0100644 #parse input data
645 http_content,_ = format_in( datacenter_associate_schema )
tierno42fcc3b2016-07-06 17:20:40 +0200646 r = utils.remove_extra_items(http_content, datacenter_associate_schema)
tierno7edb6752016-03-21 17:37:52 +0100647 if r != None: print "http_associate_datacenters: Warning: remove extra items ", r
tiernof97fd272016-07-11 14:32:37 +0200648 try:
649 id_ = nfvo.associate_datacenter_to_tenant(mydb, tenant_id, datacenter_id,
650 http_content['datacenter'].get('vim_tenant'),
651 http_content['datacenter'].get('vim_tenant_name'),
652 http_content['datacenter'].get('vim_username'),
653 http_content['datacenter'].get('vim_password')
654 )
655 return http_get_datacenter_id(tenant_id, id_)
656 except (nfvo.NfvoException, db_base_Exception) as e:
657 logger.error("http_associate_datacenters error {}: {}".format(e.http_code, str(e)))
658 bottle.abort(e.http_code, str(e))
tierno7edb6752016-03-21 17:37:52 +0100659
660@bottle.route(url_base + '/<tenant_id>/datacenters/<datacenter_id>', method='DELETE')
661def http_deassociate_datacenters(tenant_id, datacenter_id):
662 '''deassociate an existing datacenter to a this tenant. '''
tiernof97fd272016-07-11 14:32:37 +0200663 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
664 try:
665 data = nfvo.deassociate_datacenter_to_tenant(mydb, tenant_id, datacenter_id)
666 return format_out({"result": data})
667 except (nfvo.NfvoException, db_base_Exception) as e:
668 logger.error("http_deassociate_datacenters error {}: {}".format(e.http_code, str(e)))
669 bottle.abort(e.http_code, str(e))
tierno7edb6752016-03-21 17:37:52 +0100670
tierno7edb6752016-03-21 17:37:52 +0100671@bottle.route(url_base + '/<tenant_id>/vim/<datacenter_id>/<item>', method='GET')
672@bottle.route(url_base + '/<tenant_id>/vim/<datacenter_id>/<item>/<name>', method='GET')
673def http_get_vim_items(tenant_id, datacenter_id, item, name=None):
tiernof97fd272016-07-11 14:32:37 +0200674 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
675 try:
676 data = nfvo.vim_action_get(mydb, tenant_id, datacenter_id, item, name)
tierno7edb6752016-03-21 17:37:52 +0100677 return format_out(data)
tiernof97fd272016-07-11 14:32:37 +0200678 except (nfvo.NfvoException, db_base_Exception) as e:
679 logger.error("http_get_vim_items error {}: {}".format(e.http_code, str(e)))
680 bottle.abort(e.http_code, str(e))
tierno7edb6752016-03-21 17:37:52 +0100681
682@bottle.route(url_base + '/<tenant_id>/vim/<datacenter_id>/<item>/<name>', method='DELETE')
683def http_del_vim_items(tenant_id, datacenter_id, item, name):
tiernof97fd272016-07-11 14:32:37 +0200684 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
685 try:
686 data = nfvo.vim_action_delete(mydb, tenant_id, datacenter_id, item, name)
tierno7edb6752016-03-21 17:37:52 +0100687 return format_out({"result":data})
tiernof97fd272016-07-11 14:32:37 +0200688 except (nfvo.NfvoException, db_base_Exception) as e:
689 logger.error("http_del_vim_items error {}: {}".format(e.http_code, str(e)))
690 bottle.abort(e.http_code, str(e))
tierno7edb6752016-03-21 17:37:52 +0100691@bottle.route(url_base + '/<tenant_id>/vim/<datacenter_id>/<item>', method='POST')
692def http_post_vim_items(tenant_id, datacenter_id, item):
tiernof97fd272016-07-11 14:32:37 +0200693 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
tierno7edb6752016-03-21 17:37:52 +0100694 http_content,_ = format_in( object_schema )
tiernof97fd272016-07-11 14:32:37 +0200695 try:
696 data = nfvo.vim_action_create(mydb, tenant_id, datacenter_id, item, http_content)
tierno7edb6752016-03-21 17:37:52 +0100697 return format_out(data)
tiernof97fd272016-07-11 14:32:37 +0200698 except (nfvo.NfvoException, db_base_Exception) as e:
699 logger.error("http_post_vim_items error {}: {}".format(e.http_code, str(e)))
700 bottle.abort(e.http_code, str(e))
tierno7edb6752016-03-21 17:37:52 +0100701
702@bottle.route(url_base + '/<tenant_id>/vnfs', method='GET')
703def http_get_vnfs(tenant_id):
tiernof97fd272016-07-11 14:32:37 +0200704 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
705 try:
706 if tenant_id != 'any':
707 #check valid tenant_id
708 nfvo.check_tenant(mydb, tenant_id)
709 select_,where_,limit_ = filter_query_string(bottle.request.query, None,
710 ('uuid','name','description','public', "tenant_id", "created_at") )
711 where_or = {}
712 if tenant_id != "any":
713 where_or["tenant_id"] = tenant_id
714 where_or["public"] = True
715 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 +0100716 #change_keys_http2db(content, http2db_vnf, reverse=True)
tiernof97fd272016-07-11 14:32:37 +0200717 utils.convert_str2boolean(vnfs, ('public',))
718 convert_datetime2str(vnfs)
719 data={'vnfs' : vnfs}
tierno7edb6752016-03-21 17:37:52 +0100720 return format_out(data)
tiernof97fd272016-07-11 14:32:37 +0200721 except (nfvo.NfvoException, db_base_Exception) as e:
722 logger.error("http_get_vnfs error {}: {}".format(e.http_code, str(e)))
723 bottle.abort(e.http_code, str(e))
tierno7edb6752016-03-21 17:37:52 +0100724
725@bottle.route(url_base + '/<tenant_id>/vnfs/<vnf_id>', method='GET')
726def http_get_vnf_id(tenant_id,vnf_id):
727 '''get vnf details, can use both uuid or name'''
tiernof97fd272016-07-11 14:32:37 +0200728 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
729 try:
730 vnf = nfvo.get_vnf_id(mydb,tenant_id,vnf_id)
731 utils.convert_str2boolean(vnf, ('public',))
732 convert_datetime2str(vnf)
733 return format_out(vnf)
734 except (nfvo.NfvoException, db_base_Exception) as e:
735 logger.error("http_get_vnf_id error {}: {}".format(e.http_code, str(e)))
736 bottle.abort(e.http_code, str(e))
tierno7edb6752016-03-21 17:37:52 +0100737
738@bottle.route(url_base + '/<tenant_id>/vnfs', method='POST')
739def http_post_vnfs(tenant_id):
740 '''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 +0200741 #print "Parsing the YAML file of the VNF"
tierno7edb6752016-03-21 17:37:52 +0100742 #parse input data
tiernof97fd272016-07-11 14:32:37 +0200743 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
tierno7edb6752016-03-21 17:37:52 +0100744 http_content, used_schema = format_in( vnfd_schema_v01, ("version",), {"v0.2": vnfd_schema_v02})
tierno42fcc3b2016-07-06 17:20:40 +0200745 r = utils.remove_extra_items(http_content, used_schema)
tierno7edb6752016-03-21 17:37:52 +0100746 if r is not None: print "http_post_vnfs: Warning: remove extra items ", r
tiernof97fd272016-07-11 14:32:37 +0200747 try:
748 vnf_id = nfvo.new_vnf(mydb,tenant_id,http_content)
749 return http_get_vnf_id(tenant_id, vnf_id)
750 except (nfvo.NfvoException, db_base_Exception) as e:
751 logger.error("http_post_vnfs error {}: {}".format(e.http_code, str(e)))
752 bottle.abort(e.http_code, str(e))
tierno7edb6752016-03-21 17:37:52 +0100753
754@bottle.route(url_base + '/<tenant_id>/vnfs/<vnf_id>', method='DELETE')
755def http_delete_vnf_id(tenant_id,vnf_id):
756 '''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 +0200757 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
tierno7edb6752016-03-21 17:37:52 +0100758 #check valid tenant_id and deletes the vnf, including images,
tiernof97fd272016-07-11 14:32:37 +0200759 try:
760 data = nfvo.delete_vnf(mydb,tenant_id,vnf_id)
tierno7edb6752016-03-21 17:37:52 +0100761 #print json.dumps(data, indent=4)
762 return format_out({"result":"VNF " + data + " deleted"})
tiernof97fd272016-07-11 14:32:37 +0200763 except (nfvo.NfvoException, db_base_Exception) as e:
764 logger.error("http_delete_vnf_id error {}: {}".format(e.http_code, str(e)))
765 bottle.abort(e.http_code, str(e))
tierno7edb6752016-03-21 17:37:52 +0100766
767#@bottle.route(url_base + '/<tenant_id>/hosts/topology', method='GET')
768#@bottle.route(url_base + '/<tenant_id>/physicalview/Madrid-Alcantara', method='GET')
769@bottle.route(url_base + '/<tenant_id>/physicalview/<datacenter>', method='GET')
770def http_get_hosts(tenant_id, datacenter):
771 '''get the tidvim host hopology from the vim.'''
tiernof97fd272016-07-11 14:32:37 +0200772 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
773 #print "http_get_hosts received by tenant " + tenant_id + ' datacenter ' + datacenter
774 try:
775 if datacenter == 'treeview':
776 data = nfvo.get_hosts(mydb, tenant_id)
777 else:
778 #openmano-gui is using a hardcoded value for the datacenter
779 result, data = nfvo.get_hosts_info(mydb, tenant_id) #, datacenter)
780
781 if result < 0:
782 print "http_post_vnfs error %d %s" % (-result, data)
783 bottle.abort(-result, data)
784 else:
785 convert_datetime2str(data)
786 print json.dumps(data, indent=4)
787 return format_out(data)
788 except (nfvo.NfvoException, db_base_Exception) as e:
789 logger.error("http_post_vnfs error {}: {}".format(e.http_code, str(e)))
790 bottle.abort(e.http_code, str(e))
tierno7edb6752016-03-21 17:37:52 +0100791
792
793@bottle.route(url_base + '/<path:path>', method='OPTIONS')
794def http_options_deploy(path):
795 '''For some reason GUI web ask for OPTIONS that must be responded'''
796 #TODO: check correct path, and correct headers request
tiernof97fd272016-07-11 14:32:37 +0200797 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
tierno7edb6752016-03-21 17:37:52 +0100798 bottle.response.set_header('Access-Control-Allow-Methods','POST, GET, PUT, DELETE, OPTIONS')
799 bottle.response.set_header('Accept','application/yaml,application/json')
800 bottle.response.set_header('Content-Type','application/yaml,application/json')
801 bottle.response.set_header('Access-Control-Allow-Headers','content-type')
802 bottle.response.set_header('Access-Control-Allow-Origin','*')
803 return
804
805@bottle.route(url_base + '/<tenant_id>/topology/deploy', method='POST')
806def http_post_deploy(tenant_id):
807 '''post topology deploy.'''
tiernof97fd272016-07-11 14:32:37 +0200808 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
tierno7edb6752016-03-21 17:37:52 +0100809
tierno66aa0372016-07-06 17:31:12 +0200810 http_content, used_schema = format_in( nsd_schema_v01, ("schema_version",), {2: nsd_schema_v02})
tierno42fcc3b2016-07-06 17:20:40 +0200811 #r = utils.remove_extra_items(http_content, used_schema)
tierno7edb6752016-03-21 17:37:52 +0100812 #if r is not None: print "http_post_deploy: Warning: remove extra items ", r
tiernof97fd272016-07-11 14:32:37 +0200813 #print "http_post_deploy input: ", http_content
tierno7edb6752016-03-21 17:37:52 +0100814
tiernof97fd272016-07-11 14:32:37 +0200815 try:
816 scenario_id = nfvo.new_scenario(mydb, tenant_id, http_content)
817 instance = nfvo.start_scenario(mydb, tenant_id, scenario_id, http_content['name'], http_content['name'])
818 #print json.dumps(data, indent=4)
819 return format_out(instance)
820 except (nfvo.NfvoException, db_base_Exception) as e:
821 logger.error("http_post_deploy error {}: {}".format(e.http_code, str(e)))
822 bottle.abort(e.http_code, str(e))
tierno7edb6752016-03-21 17:37:52 +0100823
824@bottle.route(url_base + '/<tenant_id>/topology/verify', method='POST')
825def http_post_verify(tenant_id):
826 #TODO:
827# '''post topology verify'''
828# print "http_post_verify by tenant " + tenant_id + ' datacenter ' + datacenter
tiernof97fd272016-07-11 14:32:37 +0200829 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
tierno7edb6752016-03-21 17:37:52 +0100830 return
831
832#
833# SCENARIOS
834#
835
836@bottle.route(url_base + '/<tenant_id>/scenarios', method='POST')
837def http_post_scenarios(tenant_id):
838 '''add a scenario into the catalogue. Creates the scenario and its internal structure in the OPENMANO DB'''
tiernof97fd272016-07-11 14:32:37 +0200839 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
tierno66aa0372016-07-06 17:31:12 +0200840 http_content, used_schema = format_in( nsd_schema_v01, ("schema_version",), {2: nsd_schema_v02})
tierno42fcc3b2016-07-06 17:20:40 +0200841 #r = utils.remove_extra_items(http_content, used_schema)
tierno7edb6752016-03-21 17:37:52 +0100842 #if r is not None: print "http_post_scenarios: Warning: remove extra items ", r
tiernof97fd272016-07-11 14:32:37 +0200843 #print "http_post_scenarios input: ", http_content
844 try:
845 if http_content.get("schema_version") == None:
846 scenario_id = nfvo.new_scenario(mydb, tenant_id, http_content)
847 else:
848 scenario_id = nfvo.new_scenario_v02(mydb, tenant_id, http_content)
tierno7edb6752016-03-21 17:37:52 +0100849 #print json.dumps(data, indent=4)
850 #return format_out(data)
tiernof97fd272016-07-11 14:32:37 +0200851 return http_get_scenario_id(tenant_id, scenario_id)
852 except (nfvo.NfvoException, db_base_Exception) as e:
853 logger.error("http_post_scenarios error {}: {}".format(e.http_code, str(e)))
854 bottle.abort(e.http_code, str(e))
tierno7edb6752016-03-21 17:37:52 +0100855
856@bottle.route(url_base + '/<tenant_id>/scenarios/<scenario_id>/action', method='POST')
857def http_post_scenario_action(tenant_id, scenario_id):
858 '''take an action over a scenario'''
tiernof97fd272016-07-11 14:32:37 +0200859 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
tierno7edb6752016-03-21 17:37:52 +0100860 #check valid tenant_id
tiernof97fd272016-07-11 14:32:37 +0200861 try:
862 nfvo.check_tenant(mydb, tenant_id)
863 #parse input data
864 http_content,_ = format_in( scenario_action_schema )
865 r = utils.remove_extra_items(http_content, scenario_action_schema)
866 if r is not None: print "http_post_scenario_action: Warning: remove extra items ", r
867 if "start" in http_content:
868 data = nfvo.start_scenario(mydb, tenant_id, scenario_id, http_content['start']['instance_name'], \
869 http_content['start'].get('description',http_content['start']['instance_name']),
870 http_content['start'].get('datacenter') )
tierno7edb6752016-03-21 17:37:52 +0100871 return format_out(data)
tiernof97fd272016-07-11 14:32:37 +0200872 elif "deploy" in http_content: #Equivalent to start
873 data = nfvo.start_scenario(mydb, tenant_id, scenario_id, http_content['deploy']['instance_name'],
874 http_content['deploy'].get('description',http_content['deploy']['instance_name']),
875 http_content['deploy'].get('datacenter') )
tierno7edb6752016-03-21 17:37:52 +0100876 return format_out(data)
tiernof97fd272016-07-11 14:32:37 +0200877 elif "reserve" in http_content: #Reserve resources
878 data = nfvo.start_scenario(mydb, tenant_id, scenario_id, http_content['reserve']['instance_name'],
879 http_content['reserve'].get('description',http_content['reserve']['instance_name']),
880 http_content['reserve'].get('datacenter'), startvms=False )
tierno7edb6752016-03-21 17:37:52 +0100881 return format_out(data)
tiernof97fd272016-07-11 14:32:37 +0200882 elif "verify" in http_content: #Equivalent to start and then delete
883 data = nfvo.start_scenario(mydb, tenant_id, scenario_id, http_content['verify']['instance_name'],
884 http_content['verify'].get('description',http_content['verify']['instance_name']),
885 http_content['verify'].get('datacenter'), startvms=False )
886 instance_id = data['uuid']
887 nfvo.delete_instance(mydb, tenant_id,instance_id)
tierno7edb6752016-03-21 17:37:52 +0100888 return format_out({"result":"Verify OK"})
tiernof97fd272016-07-11 14:32:37 +0200889 except (nfvo.NfvoException, db_base_Exception) as e:
890 logger.error("http_post_scenario_action error {}: {}".format(e.http_code, str(e)))
891 bottle.abort(e.http_code, str(e))
tierno7edb6752016-03-21 17:37:52 +0100892
893@bottle.route(url_base + '/<tenant_id>/scenarios', method='GET')
894def http_get_scenarios(tenant_id):
895 '''get scenarios list'''
tiernof97fd272016-07-11 14:32:37 +0200896 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
897 try:
898 #check valid tenant_id
899 if tenant_id != "any":
900 nfvo.check_tenant(mydb, tenant_id)
901 #obtain data
902 s,w,l=filter_query_string(bottle.request.query, None, ('uuid', 'name', 'description', 'tenant_id', 'created_at', 'public'))
903 where_or={}
904 if tenant_id != "any":
905 where_or["tenant_id"] = tenant_id
906 where_or["public"] = True
907 scenarios = mydb.get_rows(SELECT=s, WHERE=w, WHERE_OR=where_or, WHERE_AND_OR="AND", LIMIT=l, FROM='scenarios')
908 convert_datetime2str(scenarios)
909 utils.convert_str2boolean(scenarios, ('public',) )
910 data={'scenarios':scenarios}
tierno7edb6752016-03-21 17:37:52 +0100911 #print json.dumps(scenarios, indent=4)
tiernof97fd272016-07-11 14:32:37 +0200912 return format_out(data)
913 except (nfvo.NfvoException, db_base_Exception) as e:
914 logger.error("http_get_scenarios error {}: {}".format(e.http_code, str(e)))
915 bottle.abort(e.http_code, str(e))
tierno7edb6752016-03-21 17:37:52 +0100916
917@bottle.route(url_base + '/<tenant_id>/scenarios/<scenario_id>', method='GET')
918def http_get_scenario_id(tenant_id, scenario_id):
919 '''get scenario details, can use both uuid or name'''
tiernof97fd272016-07-11 14:32:37 +0200920 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
921 try:
922 #check valid tenant_id
923 if tenant_id != "any":
924 nfvo.check_tenant(mydb, tenant_id)
925 #obtain data
926 scenario = mydb.get_scenario(scenario_id, tenant_id)
927 convert_datetime2str(scenario)
928 data={'scenario' : scenario}
tierno7edb6752016-03-21 17:37:52 +0100929 return format_out(data)
tiernof97fd272016-07-11 14:32:37 +0200930 except (nfvo.NfvoException, db_base_Exception) as e:
931 logger.error("http_get_scenarios error {}: {}".format(e.http_code, str(e)))
932 bottle.abort(e.http_code, str(e))
tierno7edb6752016-03-21 17:37:52 +0100933
934@bottle.route(url_base + '/<tenant_id>/scenarios/<scenario_id>', method='DELETE')
935def http_delete_scenario_id(tenant_id, scenario_id):
936 '''delete a scenario from database, can use both uuid or name'''
tiernof97fd272016-07-11 14:32:37 +0200937 try:
938 #check valid tenant_id
939 if tenant_id != "any":
940 nfvo.check_tenant(mydb, tenant_id)
941 #obtain data
942 data = mydb.delete_scenario(scenario_id, tenant_id)
tierno7edb6752016-03-21 17:37:52 +0100943 #print json.dumps(data, indent=4)
944 return format_out({"result":"scenario " + data + " deleted"})
tiernof97fd272016-07-11 14:32:37 +0200945 except (nfvo.NfvoException, db_base_Exception) as e:
946 logger.error("http_delete_scenario_id error {}: {}".format(e.http_code, str(e)))
947 bottle.abort(e.http_code, str(e))
tierno7edb6752016-03-21 17:37:52 +0100948
949
950@bottle.route(url_base + '/<tenant_id>/scenarios/<scenario_id>', method='PUT')
951def http_put_scenario_id(tenant_id, scenario_id):
952 '''edit an existing scenario id'''
tiernof97fd272016-07-11 14:32:37 +0200953 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
tierno7edb6752016-03-21 17:37:52 +0100954 http_content,_ = format_in( scenario_edit_schema )
tierno42fcc3b2016-07-06 17:20:40 +0200955 #r = utils.remove_extra_items(http_content, scenario_edit_schema)
tierno7edb6752016-03-21 17:37:52 +0100956 #if r is not None: print "http_put_scenario_id: Warning: remove extra items ", r
tiernof97fd272016-07-11 14:32:37 +0200957 #print "http_put_scenario_id input: ", http_content
958 try:
959 nfvo.edit_scenario(mydb, tenant_id, scenario_id, http_content)
tierno7edb6752016-03-21 17:37:52 +0100960 #print json.dumps(data, indent=4)
961 #return format_out(data)
tiernof97fd272016-07-11 14:32:37 +0200962 return http_get_scenario_id(tenant_id, scenario_id)
963 except (nfvo.NfvoException, db_base_Exception) as e:
964 logger.error("http_put_scenario_id error {}: {}".format(e.http_code, str(e)))
965 bottle.abort(e.http_code, str(e))
tierno7edb6752016-03-21 17:37:52 +0100966
967@bottle.route(url_base + '/<tenant_id>/instances', method='POST')
968def http_post_instances(tenant_id):
969 '''take an action over a scenario'''
tiernof97fd272016-07-11 14:32:37 +0200970 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
971 try:
972 #check valid tenant_id
973 if tenant_id != "any":
974 nfvo.check_tenant(mydb, tenant_id)
975 #parse input data
garciadeblas0c317ee2016-08-29 12:33:06 +0200976 http_content,used_schema = format_in( instance_scenario_create_schema_v01)
tiernof97fd272016-07-11 14:32:37 +0200977 r = utils.remove_extra_items(http_content, used_schema)
978 if r is not None: print "http_post_instances: Warning: remove extra items ", r
979 data = nfvo.create_instance(mydb, tenant_id, http_content["instance"])
tierno7edb6752016-03-21 17:37:52 +0100980 return format_out(data)
tiernof97fd272016-07-11 14:32:37 +0200981 except (nfvo.NfvoException, db_base_Exception) as e:
982 logger.error("http_post_instances error {}: {}".format(e.http_code, str(e)))
983 bottle.abort(e.http_code, str(e))
tierno7edb6752016-03-21 17:37:52 +0100984
985#
986# INSTANCES
987#
988@bottle.route(url_base + '/<tenant_id>/instances', method='GET')
989def http_get_instances(tenant_id):
990 '''get instance list'''
tiernof97fd272016-07-11 14:32:37 +0200991 try:
992 #check valid tenant_id
993 if tenant_id != "any":
994 nfvo.check_tenant(mydb, tenant_id)
995 #obtain data
996 s,w,l=filter_query_string(bottle.request.query, None, ('uuid', 'name', 'scenario_id', 'tenant_id', 'description', 'created_at'))
997 if tenant_id != "any":
998 w['tenant_id'] = tenant_id
999 instances = mydb.get_rows(SELECT=s, WHERE=w, LIMIT=l, FROM='instance_scenarios')
1000 convert_datetime2str(instances)
1001 utils.convert_str2boolean(instances, ('public',) )
1002 data={'instances':instances}
1003 return format_out(data)
1004 except (nfvo.NfvoException, db_base_Exception) as e:
1005 logger.error("http_get_instances error {}: {}".format(e.http_code, str(e)))
1006 bottle.abort(e.http_code, str(e))
tierno7edb6752016-03-21 17:37:52 +01001007
1008@bottle.route(url_base + '/<tenant_id>/instances/<instance_id>', method='GET')
1009def http_get_instance_id(tenant_id, instance_id):
1010 '''get instances details, can use both uuid or name'''
tiernof97fd272016-07-11 14:32:37 +02001011 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
1012 try:
1013 #check valid tenant_id
1014 if tenant_id != "any":
1015 nfvo.check_tenant(mydb, tenant_id)
1016 if tenant_id == "any":
1017 tenant_id = None
1018 #obtain data (first time is only to check that the instance exists)
1019 instance_dict = mydb.get_instance_scenario(instance_id, tenant_id, verbose=True)
1020 try:
1021 nfvo.refresh_instance(mydb, tenant_id, instance_dict)
1022 except (nfvo.NfvoException, db_base_Exception) as e:
1023 logger.warn("nfvo.refresh_instance couldn't refresh the status of the instance: %s" % str(e))
1024 #obtain data with results upated
1025 instance = mydb.get_instance_scenario(instance_id, tenant_id)
1026 convert_datetime2str(instance)
1027 #print json.dumps(instance, indent=4)
1028 return format_out(instance)
1029 except (nfvo.NfvoException, db_base_Exception) as e:
1030 logger.error("http_get_instance_id error {}: {}".format(e.http_code, str(e)))
1031 bottle.abort(e.http_code, str(e))
tierno7edb6752016-03-21 17:37:52 +01001032
1033@bottle.route(url_base + '/<tenant_id>/instances/<instance_id>', method='DELETE')
1034def http_delete_instance_id(tenant_id, instance_id):
1035 '''delete instance from VIM and from database, can use both uuid or name'''
tiernof97fd272016-07-11 14:32:37 +02001036 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
1037 try:
1038 #check valid tenant_id
1039 if tenant_id != "any":
1040 nfvo.check_tenant(mydb, tenant_id)
1041 if tenant_id == "any":
1042 tenant_id = None
1043 #obtain data
1044 message = nfvo.delete_instance(mydb, tenant_id,instance_id)
tierno7edb6752016-03-21 17:37:52 +01001045 return format_out({"result":message})
tiernof97fd272016-07-11 14:32:37 +02001046 except (nfvo.NfvoException, db_base_Exception) as e:
1047 logger.error("http_delete_instance_id error {}: {}".format(e.http_code, str(e)))
1048 bottle.abort(e.http_code, str(e))
tierno7edb6752016-03-21 17:37:52 +01001049
1050@bottle.route(url_base + '/<tenant_id>/instances/<instance_id>/action', method='POST')
1051def http_post_instance_scenario_action(tenant_id, instance_id):
1052 '''take an action over a scenario instance'''
tiernof97fd272016-07-11 14:32:37 +02001053 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
1054 try:
1055 #check valid tenant_id
1056 if tenant_id != "any":
1057 nfvo.check_tenant(mydb, tenant_id)
1058
1059 #parse input data
1060 http_content,_ = format_in( instance_scenario_action_schema )
1061 r = utils.remove_extra_items(http_content, instance_scenario_action_schema)
1062 if r is not None: print "http_post_instance_scenario_action: Warning: remove extra items ", r
1063 #print "http_post_instance_scenario_action input: ", http_content
1064 #obtain data
1065 instance = mydb.get_instance_scenario(instance_id, tenant_id)
1066 instance_id = instance["uuid"]
1067
1068 data = nfvo.instance_action(mydb, tenant_id, instance_id, http_content)
tierno7edb6752016-03-21 17:37:52 +01001069 return format_out(data)
tiernof97fd272016-07-11 14:32:37 +02001070 except (nfvo.NfvoException, db_base_Exception) as e:
1071 logger.error("http_post_instance_scenario_action error {}: {}".format(e.http_code, str(e)))
1072 bottle.abort(e.http_code, str(e))
tierno7edb6752016-03-21 17:37:52 +01001073
1074
1075@bottle.error(400)
1076@bottle.error(401)
1077@bottle.error(404)
1078@bottle.error(403)
1079@bottle.error(405)
1080@bottle.error(406)
1081@bottle.error(409)
1082@bottle.error(503)
1083@bottle.error(500)
1084def error400(error):
1085 e={"error":{"code":error.status_code, "type":error.status, "description":error.body}}
1086 bottle.response.headers['Access-Control-Allow-Origin'] = '*'
1087 return format_out(e)
1088