blob: 046584e364d0063178b7757d8aa83efe356227cd [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():
tiernoefd80c92016-09-16 14:17:46 +0200146 #print
tierno7edb6752016-03-21 17:37:52 +0100147 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'''
tiernoefd80c92016-09-16 14:17:46 +0200175 logger.debug("OUT: " + 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:
tiernoefd80c92016-09-16 14:17:46 +0200210 logger.warning('Content-Type ' + str(format_type) + ' not supported.')
tierno7edb6752016-03-21 17:37:52 +0100211 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
tiernoefd80c92016-09-16 14:17:46 +0200217 logger.debug('IN: %s', yaml.safe_dump(client_data, explicit_start=True, indent=4, default_flow_style=False, tags=False, encoding='utf-8', allow_unicode=True) )
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)
tiernoefd80c92016-09-16 14:17:46 +0200244 logger.error(error_text)
tierno7edb6752016-03-21 17:37:52 +0100245 bottle.abort(HTTP_Bad_Request, error_text)
246 except js_e.ValidationError as exc:
tiernoefd80c92016-09-16 14:17:46 +0200247 logger.error("validate_in error, jsonschema exception at '%s' '%s' ", str(exc.path), str(exc.message))
tierno7edb6752016-03-21 17:37:52 +0100248 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)
tiernoefd80c92016-09-16 14:17:46 +0200347 if r:
348 logger.debug("Remove received extra items %s", str(r))
tiernof97fd272016-07-11 14:32:37 +0200349 try:
350 data = nfvo.new_tenant(mydb, http_content['tenant'])
tierno7edb6752016-03-21 17:37:52 +0100351 return http_get_tenant_id(data)
tiernof97fd272016-07-11 14:32:37 +0200352 except (nfvo.NfvoException, db_base_Exception) as e:
353 logger.error("http_post_tenants error {}: {}".format(e.http_code, str(e)))
354 bottle.abort(e.http_code, str(e))
tierno7edb6752016-03-21 17:37:52 +0100355
356@bottle.route(url_base + '/tenants/<tenant_id>', method='PUT')
357def http_edit_tenant_id(tenant_id):
358 '''edit tenant details, can use both uuid or name'''
359 #parse input data
tiernof97fd272016-07-11 14:32:37 +0200360 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
tierno7edb6752016-03-21 17:37:52 +0100361 http_content,_ = format_in( tenant_edit_schema )
tierno42fcc3b2016-07-06 17:20:40 +0200362 r = utils.remove_extra_items(http_content, tenant_edit_schema)
tiernoefd80c92016-09-16 14:17:46 +0200363 if r:
364 logger.debug("Remove received extra items %s", str(r))
tierno7edb6752016-03-21 17:37:52 +0100365
366 #obtain data, check that only one exist
tiernof97fd272016-07-11 14:32:37 +0200367 try:
368 tenant = mydb.get_table_by_uuid_name('nfvo_tenants', tenant_id)
369 #edit data
370 tenant_id = tenant['uuid']
371 where={'uuid': tenant['uuid']}
372 mydb.update_rows('nfvo_tenants', http_content['tenant'], where)
373 return http_get_tenant_id(tenant_id)
374 except db_base_Exception as e:
375 logger.error("http_edit_tenant_id error {}: {}".format(e.http_code, str(e)))
376 bottle.abort(e.http_code, str(e))
tierno7edb6752016-03-21 17:37:52 +0100377
378@bottle.route(url_base + '/tenants/<tenant_id>', method='DELETE')
379def http_delete_tenant_id(tenant_id):
380 '''delete a tenant from database, can use both uuid or name'''
tiernof97fd272016-07-11 14:32:37 +0200381 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
382 try:
383 data = nfvo.delete_tenant(mydb, tenant_id)
tierno7edb6752016-03-21 17:37:52 +0100384 return format_out({"result":"tenant " + data + " deleted"})
tiernof97fd272016-07-11 14:32:37 +0200385 except db_base_Exception as e:
386 logger.error("http_delete_tenant_id error {}: {}".format(e.http_code, str(e)))
387 bottle.abort(e.http_code, str(e))
tierno7edb6752016-03-21 17:37:52 +0100388
389
390@bottle.route(url_base + '/<tenant_id>/datacenters', method='GET')
391def http_get_datacenters(tenant_id):
tiernof97fd272016-07-11 14:32:37 +0200392 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
393 try:
394 if tenant_id != 'any':
395 #check valid tenant_id
396 nfvo.check_tenant(mydb, tenant_id)
397 select_,where_,limit_ = filter_query_string(bottle.request.query, None,
398 ('uuid','name','vim_url','type','created_at') )
399 if tenant_id != 'any':
400 where_['nfvo_tenant_id'] = tenant_id
401 if 'created_at' in select_:
402 select_[ select_.index('created_at') ] = 'd.created_at as created_at'
403 if 'created_at' in where_:
404 where_['d.created_at'] = where_.pop('created_at')
405 datacenters = mydb.get_rows(FROM='datacenters as d join tenants_datacenters as td on d.uuid=td.datacenter_id',
406 SELECT=select_,WHERE=where_,LIMIT=limit_)
407 else:
408 datacenters = mydb.get_rows(FROM='datacenters',
409 SELECT=select_,WHERE=where_,LIMIT=limit_)
tierno7edb6752016-03-21 17:37:52 +0100410 #change_keys_http2db(content, http2db_tenant, reverse=True)
tiernof97fd272016-07-11 14:32:37 +0200411 convert_datetime2str(datacenters)
412 data={'datacenters' : datacenters}
tierno7edb6752016-03-21 17:37:52 +0100413 return format_out(data)
tiernof97fd272016-07-11 14:32:37 +0200414 except (nfvo.NfvoException, db_base_Exception) as e:
415 logger.error("http_get_datacenters error {}: {}".format(e.http_code, str(e)))
416 bottle.abort(e.http_code, str(e))
tierno7edb6752016-03-21 17:37:52 +0100417
418@bottle.route(url_base + '/<tenant_id>/datacenters/<datacenter_id>', method='GET')
419def http_get_datacenter_id(tenant_id, datacenter_id):
420 '''get datacenter details, can use both uuid or name'''
tiernof97fd272016-07-11 14:32:37 +0200421 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
422 try:
423 if tenant_id != 'any':
424 #check valid tenant_id
425 nfvo.check_tenant(mydb, tenant_id)
426 #obtain data
427 what = 'uuid' if utils.check_valid_uuid(datacenter_id) else 'name'
428 where_={}
429 where_[what] = datacenter_id
430 select_=['uuid', 'name','vim_url', 'vim_url_admin', 'type', 'config', 'description', 'd.created_at as created_at']
431 if tenant_id != 'any':
432 select_.append("datacenter_tenant_id")
433 where_['td.nfvo_tenant_id']= tenant_id
434 from_='datacenters as d join tenants_datacenters as td on d.uuid=td.datacenter_id'
435 else:
436 from_='datacenters as d'
437 datacenters = mydb.get_rows(
438 SELECT=select_,
439 FROM=from_,
440 WHERE=where_)
441
442 if len(datacenters)==0:
443 bottle.abort( HTTP_Not_Found, "No datacenter found for tenant with {} '{}'".format(what, datacenter_id) )
444 elif len(datacenters)>1:
445 bottle.abort( HTTP_Bad_Request, "More than one datacenter found for tenant with {} '{}'".format(what, datacenter_id) )
446 datacenter = datacenters[0]
447 if tenant_id != 'any':
448 #get vim tenant info
449 vim_tenants = mydb.get_rows(
450 SELECT=("vim_tenant_name", "vim_tenant_id", "user"),
451 FROM="datacenter_tenants",
452 WHERE={"uuid": datacenters[0]["datacenter_tenant_id"]},
453 ORDER_BY=("created", ) )
454 del datacenter["datacenter_tenant_id"]
455 datacenter["vim_tenants"] = vim_tenants
456
457 if datacenter['config'] != None:
458 try:
459 config_dict = yaml.load(datacenter['config'])
460 datacenter['config'] = config_dict
461 except Exception, e:
tiernoefd80c92016-09-16 14:17:46 +0200462 logger.error("Exception '%s' while trying to load config information", str(e))
tiernof97fd272016-07-11 14:32:37 +0200463 #change_keys_http2db(content, http2db_datacenter, reverse=True)
464 convert_datetime2str(datacenter)
465 data={'datacenter' : datacenter}
466 return format_out(data)
467 except (nfvo.NfvoException, db_base_Exception) as e:
468 logger.error("http_get_datacenter_id error {}: {}".format(e.http_code, str(e)))
469 bottle.abort(e.http_code, str(e))
tierno7edb6752016-03-21 17:37:52 +0100470
471@bottle.route(url_base + '/datacenters', method='POST')
472def http_post_datacenters():
473 '''insert a tenant into the catalogue. '''
474 #parse input data
tiernof97fd272016-07-11 14:32:37 +0200475 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
tierno7edb6752016-03-21 17:37:52 +0100476 http_content,_ = format_in( datacenter_schema )
tierno42fcc3b2016-07-06 17:20:40 +0200477 r = utils.remove_extra_items(http_content, datacenter_schema)
tiernoefd80c92016-09-16 14:17:46 +0200478 if r:
479 logger.debug("Remove received extra items %s", str(r))
tiernof97fd272016-07-11 14:32:37 +0200480 try:
481 data = nfvo.new_datacenter(mydb, http_content['datacenter'])
tierno7edb6752016-03-21 17:37:52 +0100482 return http_get_datacenter_id('any', data)
tiernof97fd272016-07-11 14:32:37 +0200483 except (nfvo.NfvoException, db_base_Exception) as e:
484 logger.error("http_post_datacenters error {}: {}".format(e.http_code, str(e)))
485 bottle.abort(e.http_code, str(e))
tierno7edb6752016-03-21 17:37:52 +0100486
487@bottle.route(url_base + '/datacenters/<datacenter_id_name>', method='PUT')
488def http_edit_datacenter_id(datacenter_id_name):
489 '''edit datacenter details, can use both uuid or name'''
tiernof97fd272016-07-11 14:32:37 +0200490 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
tierno7edb6752016-03-21 17:37:52 +0100491 #parse input data
492 http_content,_ = format_in( datacenter_edit_schema )
tierno42fcc3b2016-07-06 17:20:40 +0200493 r = utils.remove_extra_items(http_content, datacenter_edit_schema)
tiernoefd80c92016-09-16 14:17:46 +0200494 if r:
495 logger.debug("Remove received extra items %s", str(r))
tierno7edb6752016-03-21 17:37:52 +0100496
tiernof97fd272016-07-11 14:32:37 +0200497 try:
498 datacenter_id = nfvo.edit_datacenter(mydb, datacenter_id_name, http_content['datacenter'])
tierno7edb6752016-03-21 17:37:52 +0100499 return http_get_datacenter_id('any', datacenter_id)
tiernof97fd272016-07-11 14:32:37 +0200500 except (nfvo.NfvoException, db_base_Exception) as e:
501 logger.error("http_edit_datacenter_id error {}: {}".format(e.http_code, str(e)))
502 bottle.abort(e.http_code, str(e))
tierno7edb6752016-03-21 17:37:52 +0100503
504@bottle.route(url_base + '/<tenant_id>/datacenters/<datacenter_id>/networks', method='GET') #deprecated
505@bottle.route(url_base + '/<tenant_id>/datacenters/<datacenter_id>/netmaps', method='GET')
506@bottle.route(url_base + '/<tenant_id>/datacenters/<datacenter_id>/netmaps/<netmap_id>', method='GET')
507def http_getnetmap_datacenter_id(tenant_id, datacenter_id, netmap_id=None):
508 '''get datacenter networks, can use both uuid or name'''
tiernof97fd272016-07-11 14:32:37 +0200509 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
tierno7edb6752016-03-21 17:37:52 +0100510 #obtain data
tiernof97fd272016-07-11 14:32:37 +0200511 try:
512 datacenter_dict = mydb.get_table_by_uuid_name('datacenters', datacenter_id, "datacenter")
513 where_= {"datacenter_id":datacenter_dict['uuid']}
514 if netmap_id:
515 if utils.check_valid_uuid(netmap_id):
516 where_["uuid"] = netmap_id
517 else:
518 where_["name"] = netmap_id
519 netmaps =mydb.get_rows(FROM='datacenter_nets',
520 SELECT=('name','vim_net_id as vim_id', 'uuid', 'type','multipoint','shared','description', 'created_at'),
521 WHERE=where_ )
522 convert_datetime2str(netmaps)
523 utils.convert_str2boolean(netmaps, ('shared', 'multipoint') )
524 if netmap_id and len(netmaps)==1:
525 data={'netmap' : netmaps[0]}
526 elif netmap_id and len(netmaps)==0:
527 bottle.abort(HTTP_Not_Found, "No netmap found with " + " and ".join(map(lambda x: str(x[0])+": "+str(x[1]), where_.iteritems())) )
528 return
tierno7edb6752016-03-21 17:37:52 +0100529 else:
tiernof97fd272016-07-11 14:32:37 +0200530 data={'netmaps' : netmaps}
531 return format_out(data)
532 except (nfvo.NfvoException, db_base_Exception) as e:
533 logger.error("http_getnetwork_datacenter_id error {}: {}".format(e.http_code, str(e)))
534 bottle.abort(e.http_code, str(e))
tierno7edb6752016-03-21 17:37:52 +0100535
536@bottle.route(url_base + '/<tenant_id>/datacenters/<datacenter_id>/netmaps', method='DELETE')
537@bottle.route(url_base + '/<tenant_id>/datacenters/<datacenter_id>/netmaps/<netmap_id>', method='DELETE')
538def http_delnetmap_datacenter_id(tenant_id, datacenter_id, netmap_id=None):
539 '''get datacenter networks, can use both uuid or name'''
tiernof97fd272016-07-11 14:32:37 +0200540 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
tierno7edb6752016-03-21 17:37:52 +0100541 #obtain data
tiernof97fd272016-07-11 14:32:37 +0200542 try:
543 datacenter_dict = mydb.get_table_by_uuid_name('datacenters', datacenter_id, "datacenter")
544 where_= {"datacenter_id":datacenter_dict['uuid']}
545 if netmap_id:
546 if utils.check_valid_uuid(netmap_id):
547 where_["uuid"] = netmap_id
548 else:
549 where_["name"] = netmap_id
550 #change_keys_http2db(content, http2db_tenant, reverse=True)
551 deleted = mydb.delete_row(FROM='datacenter_nets', WHERE= where_)
552 if deleted == 0 and netmap_id :
553 bottle.abort(HTTP_Not_Found, "No netmap found with " + " and ".join(map(lambda x: str(x[0])+": "+str(x[1]), where_.iteritems())) )
554 if netmap_id:
555 return format_out({"result": "netmap %s deleted" % netmap_id})
tierno7edb6752016-03-21 17:37:52 +0100556 else:
tiernof97fd272016-07-11 14:32:37 +0200557 return format_out({"result": "%d netmap deleted" % deleted})
558 except (nfvo.NfvoException, db_base_Exception) as e:
559 logger.error("http_delnetmap_datacenter_id error {}: {}".format(e.http_code, str(e)))
560 bottle.abort(e.http_code, str(e))
tierno7edb6752016-03-21 17:37:52 +0100561
562
563@bottle.route(url_base + '/<tenant_id>/datacenters/<datacenter_id>/netmaps/upload', method='POST')
564def http_uploadnetmap_datacenter_id(tenant_id, datacenter_id):
tiernof97fd272016-07-11 14:32:37 +0200565 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
566 try:
567 netmaps = nfvo.datacenter_new_netmap(mydb, tenant_id, datacenter_id, None)
568 convert_datetime2str(netmaps)
569 utils.convert_str2boolean(netmaps, ('shared', 'multipoint') )
570 data={'netmaps' : netmaps}
571 return format_out(data)
572 except (nfvo.NfvoException, db_base_Exception) as e:
573 logger.error("http_uploadnetmap_datacenter_id error {}: {}".format(e.http_code, str(e)))
574 bottle.abort(e.http_code, str(e))
tierno7edb6752016-03-21 17:37:52 +0100575
576@bottle.route(url_base + '/<tenant_id>/datacenters/<datacenter_id>/netmaps', method='POST')
577def http_postnetmap_datacenter_id(tenant_id, datacenter_id):
578 '''creates a new netmap'''
tiernof97fd272016-07-11 14:32:37 +0200579 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
tierno7edb6752016-03-21 17:37:52 +0100580 #parse input data
581 http_content,_ = format_in( netmap_new_schema )
tierno42fcc3b2016-07-06 17:20:40 +0200582 r = utils.remove_extra_items(http_content, netmap_new_schema)
tiernoefd80c92016-09-16 14:17:46 +0200583 if r:
584 logger.debug("Remove received extra items %s", str(r))
tiernof97fd272016-07-11 14:32:37 +0200585 try:
586 #obtain data, check that only one exist
587 netmaps = nfvo.datacenter_new_netmap(mydb, tenant_id, datacenter_id, http_content)
588 convert_datetime2str(netmaps)
589 utils.convert_str2boolean(netmaps, ('shared', 'multipoint') )
590 data={'netmaps' : netmaps}
591 return format_out(data)
592 except (nfvo.NfvoException, db_base_Exception) as e:
593 logger.error("http_postnetmap_datacenter_id error {}: {}".format(e.http_code, str(e)))
594 bottle.abort(e.http_code, str(e))
tierno7edb6752016-03-21 17:37:52 +0100595
596@bottle.route(url_base + '/<tenant_id>/datacenters/<datacenter_id>/netmaps/<netmap_id>', method='PUT')
597def http_putnettmap_datacenter_id(tenant_id, datacenter_id, netmap_id):
598 '''edit a netmap'''
tiernof97fd272016-07-11 14:32:37 +0200599 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
tierno7edb6752016-03-21 17:37:52 +0100600 #parse input data
601 http_content,_ = format_in( netmap_edit_schema )
tierno42fcc3b2016-07-06 17:20:40 +0200602 r = utils.remove_extra_items(http_content, netmap_edit_schema)
tiernoefd80c92016-09-16 14:17:46 +0200603 if r:
604 logger.debug("Remove received extra items %s", str(r))
tierno7edb6752016-03-21 17:37:52 +0100605
606 #obtain data, check that only one exist
tiernof97fd272016-07-11 14:32:37 +0200607 try:
608 nfvo.datacenter_edit_netmap(mydb, tenant_id, datacenter_id, netmap_id, http_content)
tierno7edb6752016-03-21 17:37:52 +0100609 return http_getnetmap_datacenter_id(tenant_id, datacenter_id, netmap_id)
tiernof97fd272016-07-11 14:32:37 +0200610 except (nfvo.NfvoException, db_base_Exception) as e:
611 logger.error("http_putnettmap_datacenter_id error {}: {}".format(e.http_code, str(e)))
612 bottle.abort(e.http_code, str(e))
tierno7edb6752016-03-21 17:37:52 +0100613
614
615@bottle.route(url_base + '/<tenant_id>/datacenters/<datacenter_id>/action', method='POST')
616def http_action_datacenter_id(tenant_id, datacenter_id):
617 '''perform an action over datacenter, can use both uuid or name'''
tiernof97fd272016-07-11 14:32:37 +0200618 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
tierno7edb6752016-03-21 17:37:52 +0100619 #parse input data
620 http_content,_ = format_in( datacenter_action_schema )
tierno42fcc3b2016-07-06 17:20:40 +0200621 r = utils.remove_extra_items(http_content, datacenter_action_schema)
tiernoefd80c92016-09-16 14:17:46 +0200622 if r:
623 logger.debug("Remove received extra items %s", str(r))
tiernof97fd272016-07-11 14:32:37 +0200624 try:
625 #obtain data, check that only one exist
626 result = nfvo.datacenter_action(mydb, tenant_id, datacenter_id, http_content)
627 if 'net-update' in http_content:
628 return http_getnetmap_datacenter_id(datacenter_id)
629 else:
630 return format_out(result)
631 except (nfvo.NfvoException, db_base_Exception) as e:
632 logger.error("http_action_datacenter_id error {}: {}".format(e.http_code, str(e)))
633 bottle.abort(e.http_code, str(e))
tierno7edb6752016-03-21 17:37:52 +0100634
635
636@bottle.route(url_base + '/datacenters/<datacenter_id>', method='DELETE')
637def http_delete_datacenter_id( datacenter_id):
638 '''delete a tenant from database, can use both uuid or name'''
639
tiernof97fd272016-07-11 14:32:37 +0200640 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
641 try:
642 data = nfvo.delete_datacenter(mydb, datacenter_id)
643 return format_out({"result":"datacenter '" + data + "' deleted"})
644 except (nfvo.NfvoException, db_base_Exception) as e:
645 logger.error("http_delete_datacenter_id error {}: {}".format(e.http_code, str(e)))
646 bottle.abort(e.http_code, str(e))
tierno7edb6752016-03-21 17:37:52 +0100647
648@bottle.route(url_base + '/<tenant_id>/datacenters/<datacenter_id>', method='POST')
649def http_associate_datacenters(tenant_id, datacenter_id):
650 '''associate an existing datacenter to a this tenant. '''
tiernof97fd272016-07-11 14:32:37 +0200651 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
tierno7edb6752016-03-21 17:37:52 +0100652 #parse input data
653 http_content,_ = format_in( datacenter_associate_schema )
tierno42fcc3b2016-07-06 17:20:40 +0200654 r = utils.remove_extra_items(http_content, datacenter_associate_schema)
tiernoefd80c92016-09-16 14:17:46 +0200655 if r:
656 logger.debug("Remove received extra items %s", str(r))
tiernof97fd272016-07-11 14:32:37 +0200657 try:
658 id_ = nfvo.associate_datacenter_to_tenant(mydb, tenant_id, datacenter_id,
659 http_content['datacenter'].get('vim_tenant'),
660 http_content['datacenter'].get('vim_tenant_name'),
661 http_content['datacenter'].get('vim_username'),
662 http_content['datacenter'].get('vim_password')
663 )
664 return http_get_datacenter_id(tenant_id, id_)
665 except (nfvo.NfvoException, db_base_Exception) as e:
666 logger.error("http_associate_datacenters error {}: {}".format(e.http_code, str(e)))
667 bottle.abort(e.http_code, str(e))
tierno7edb6752016-03-21 17:37:52 +0100668
669@bottle.route(url_base + '/<tenant_id>/datacenters/<datacenter_id>', method='DELETE')
670def http_deassociate_datacenters(tenant_id, datacenter_id):
671 '''deassociate an existing datacenter to a this tenant. '''
tiernof97fd272016-07-11 14:32:37 +0200672 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
673 try:
674 data = nfvo.deassociate_datacenter_to_tenant(mydb, tenant_id, datacenter_id)
675 return format_out({"result": data})
676 except (nfvo.NfvoException, db_base_Exception) as e:
677 logger.error("http_deassociate_datacenters error {}: {}".format(e.http_code, str(e)))
678 bottle.abort(e.http_code, str(e))
tierno7edb6752016-03-21 17:37:52 +0100679
tierno7edb6752016-03-21 17:37:52 +0100680@bottle.route(url_base + '/<tenant_id>/vim/<datacenter_id>/<item>', method='GET')
681@bottle.route(url_base + '/<tenant_id>/vim/<datacenter_id>/<item>/<name>', method='GET')
682def http_get_vim_items(tenant_id, datacenter_id, item, name=None):
tiernof97fd272016-07-11 14:32:37 +0200683 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
684 try:
685 data = nfvo.vim_action_get(mydb, tenant_id, datacenter_id, item, name)
tierno7edb6752016-03-21 17:37:52 +0100686 return format_out(data)
tiernof97fd272016-07-11 14:32:37 +0200687 except (nfvo.NfvoException, db_base_Exception) as e:
688 logger.error("http_get_vim_items error {}: {}".format(e.http_code, str(e)))
689 bottle.abort(e.http_code, str(e))
tierno7edb6752016-03-21 17:37:52 +0100690
691@bottle.route(url_base + '/<tenant_id>/vim/<datacenter_id>/<item>/<name>', method='DELETE')
692def http_del_vim_items(tenant_id, datacenter_id, item, name):
tiernof97fd272016-07-11 14:32:37 +0200693 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
694 try:
695 data = nfvo.vim_action_delete(mydb, tenant_id, datacenter_id, item, name)
tierno7edb6752016-03-21 17:37:52 +0100696 return format_out({"result":data})
tiernof97fd272016-07-11 14:32:37 +0200697 except (nfvo.NfvoException, db_base_Exception) as e:
698 logger.error("http_del_vim_items error {}: {}".format(e.http_code, str(e)))
699 bottle.abort(e.http_code, str(e))
tierno7edb6752016-03-21 17:37:52 +0100700@bottle.route(url_base + '/<tenant_id>/vim/<datacenter_id>/<item>', method='POST')
701def http_post_vim_items(tenant_id, datacenter_id, item):
tiernof97fd272016-07-11 14:32:37 +0200702 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
tierno7edb6752016-03-21 17:37:52 +0100703 http_content,_ = format_in( object_schema )
tiernof97fd272016-07-11 14:32:37 +0200704 try:
705 data = nfvo.vim_action_create(mydb, tenant_id, datacenter_id, item, http_content)
tierno7edb6752016-03-21 17:37:52 +0100706 return format_out(data)
tiernof97fd272016-07-11 14:32:37 +0200707 except (nfvo.NfvoException, db_base_Exception) as e:
708 logger.error("http_post_vim_items error {}: {}".format(e.http_code, str(e)))
709 bottle.abort(e.http_code, str(e))
tierno7edb6752016-03-21 17:37:52 +0100710
711@bottle.route(url_base + '/<tenant_id>/vnfs', method='GET')
712def http_get_vnfs(tenant_id):
tiernof97fd272016-07-11 14:32:37 +0200713 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
714 try:
715 if tenant_id != 'any':
716 #check valid tenant_id
717 nfvo.check_tenant(mydb, tenant_id)
718 select_,where_,limit_ = filter_query_string(bottle.request.query, None,
719 ('uuid','name','description','public', "tenant_id", "created_at") )
720 where_or = {}
721 if tenant_id != "any":
722 where_or["tenant_id"] = tenant_id
723 where_or["public"] = True
724 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 +0100725 #change_keys_http2db(content, http2db_vnf, reverse=True)
tiernof97fd272016-07-11 14:32:37 +0200726 utils.convert_str2boolean(vnfs, ('public',))
727 convert_datetime2str(vnfs)
728 data={'vnfs' : vnfs}
tierno7edb6752016-03-21 17:37:52 +0100729 return format_out(data)
tiernof97fd272016-07-11 14:32:37 +0200730 except (nfvo.NfvoException, db_base_Exception) as e:
731 logger.error("http_get_vnfs error {}: {}".format(e.http_code, str(e)))
732 bottle.abort(e.http_code, str(e))
tierno7edb6752016-03-21 17:37:52 +0100733
734@bottle.route(url_base + '/<tenant_id>/vnfs/<vnf_id>', method='GET')
735def http_get_vnf_id(tenant_id,vnf_id):
736 '''get vnf details, can use both uuid or name'''
tiernof97fd272016-07-11 14:32:37 +0200737 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
738 try:
739 vnf = nfvo.get_vnf_id(mydb,tenant_id,vnf_id)
740 utils.convert_str2boolean(vnf, ('public',))
741 convert_datetime2str(vnf)
742 return format_out(vnf)
743 except (nfvo.NfvoException, db_base_Exception) as e:
744 logger.error("http_get_vnf_id error {}: {}".format(e.http_code, str(e)))
745 bottle.abort(e.http_code, str(e))
tierno7edb6752016-03-21 17:37:52 +0100746
747@bottle.route(url_base + '/<tenant_id>/vnfs', method='POST')
748def http_post_vnfs(tenant_id):
749 '''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 +0200750 #print "Parsing the YAML file of the VNF"
tierno7edb6752016-03-21 17:37:52 +0100751 #parse input data
tiernof97fd272016-07-11 14:32:37 +0200752 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
garciadeblas9f8456e2016-09-05 05:02:59 +0200753 http_content, used_schema = format_in( vnfd_schema_v01, ("schema_version",), {"0.2": vnfd_schema_v02})
tierno42fcc3b2016-07-06 17:20:40 +0200754 r = utils.remove_extra_items(http_content, used_schema)
tiernoefd80c92016-09-16 14:17:46 +0200755 if r:
756 logger.debug("Remove received extra items %s", str(r))
tiernof97fd272016-07-11 14:32:37 +0200757 try:
tierno4319dad2016-09-05 12:11:11 +0200758 if used_schema == vnfd_schema_v01:
garciadeblas9f8456e2016-09-05 05:02:59 +0200759 vnf_id = nfvo.new_vnf(mydb,tenant_id,http_content)
tierno4319dad2016-09-05 12:11:11 +0200760 elif used_schema == vnfd_schema_v02:
garciadeblas9f8456e2016-09-05 05:02:59 +0200761 vnf_id = nfvo.new_vnf_v02(mydb,tenant_id,http_content)
762 else:
763 logger.warning('Unexpected schema_version: %s', http_content.get("schema_version"))
764 bottle.abort(HTTP_Bad_Request, "Invalid schema version")
tiernof97fd272016-07-11 14:32:37 +0200765 return http_get_vnf_id(tenant_id, vnf_id)
766 except (nfvo.NfvoException, db_base_Exception) as e:
767 logger.error("http_post_vnfs error {}: {}".format(e.http_code, str(e)))
768 bottle.abort(e.http_code, str(e))
tierno7edb6752016-03-21 17:37:52 +0100769
770@bottle.route(url_base + '/<tenant_id>/vnfs/<vnf_id>', method='DELETE')
771def http_delete_vnf_id(tenant_id,vnf_id):
772 '''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 +0200773 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
tierno7edb6752016-03-21 17:37:52 +0100774 #check valid tenant_id and deletes the vnf, including images,
tiernof97fd272016-07-11 14:32:37 +0200775 try:
776 data = nfvo.delete_vnf(mydb,tenant_id,vnf_id)
tierno7edb6752016-03-21 17:37:52 +0100777 #print json.dumps(data, indent=4)
778 return format_out({"result":"VNF " + data + " deleted"})
tiernof97fd272016-07-11 14:32:37 +0200779 except (nfvo.NfvoException, db_base_Exception) as e:
780 logger.error("http_delete_vnf_id error {}: {}".format(e.http_code, str(e)))
781 bottle.abort(e.http_code, str(e))
tierno7edb6752016-03-21 17:37:52 +0100782
783#@bottle.route(url_base + '/<tenant_id>/hosts/topology', method='GET')
784#@bottle.route(url_base + '/<tenant_id>/physicalview/Madrid-Alcantara', method='GET')
785@bottle.route(url_base + '/<tenant_id>/physicalview/<datacenter>', method='GET')
786def http_get_hosts(tenant_id, datacenter):
787 '''get the tidvim host hopology from the vim.'''
tiernof97fd272016-07-11 14:32:37 +0200788 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
789 #print "http_get_hosts received by tenant " + tenant_id + ' datacenter ' + datacenter
790 try:
791 if datacenter == 'treeview':
792 data = nfvo.get_hosts(mydb, tenant_id)
793 else:
794 #openmano-gui is using a hardcoded value for the datacenter
795 result, data = nfvo.get_hosts_info(mydb, tenant_id) #, datacenter)
796
797 if result < 0:
tiernoefd80c92016-09-16 14:17:46 +0200798 #print "http_get_hosts error %d %s" % (-result, data)
tiernof97fd272016-07-11 14:32:37 +0200799 bottle.abort(-result, data)
800 else:
801 convert_datetime2str(data)
tiernoefd80c92016-09-16 14:17:46 +0200802 #print json.dumps(data, indent=4)
tiernof97fd272016-07-11 14:32:37 +0200803 return format_out(data)
804 except (nfvo.NfvoException, db_base_Exception) as e:
garciadeblas9f8456e2016-09-05 05:02:59 +0200805 logger.error("http_get_hosts error {}: {}".format(e.http_code, str(e)))
tiernof97fd272016-07-11 14:32:37 +0200806 bottle.abort(e.http_code, str(e))
tierno7edb6752016-03-21 17:37:52 +0100807
808
809@bottle.route(url_base + '/<path:path>', method='OPTIONS')
810def http_options_deploy(path):
811 '''For some reason GUI web ask for OPTIONS that must be responded'''
812 #TODO: check correct path, and correct headers request
tiernof97fd272016-07-11 14:32:37 +0200813 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
tierno7edb6752016-03-21 17:37:52 +0100814 bottle.response.set_header('Access-Control-Allow-Methods','POST, GET, PUT, DELETE, OPTIONS')
815 bottle.response.set_header('Accept','application/yaml,application/json')
816 bottle.response.set_header('Content-Type','application/yaml,application/json')
817 bottle.response.set_header('Access-Control-Allow-Headers','content-type')
818 bottle.response.set_header('Access-Control-Allow-Origin','*')
819 return
820
821@bottle.route(url_base + '/<tenant_id>/topology/deploy', method='POST')
822def http_post_deploy(tenant_id):
823 '''post topology deploy.'''
tiernof97fd272016-07-11 14:32:37 +0200824 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
tierno7edb6752016-03-21 17:37:52 +0100825
tierno66aa0372016-07-06 17:31:12 +0200826 http_content, used_schema = format_in( nsd_schema_v01, ("schema_version",), {2: nsd_schema_v02})
tierno42fcc3b2016-07-06 17:20:40 +0200827 #r = utils.remove_extra_items(http_content, used_schema)
tierno7edb6752016-03-21 17:37:52 +0100828 #if r is not None: print "http_post_deploy: Warning: remove extra items ", r
tiernof97fd272016-07-11 14:32:37 +0200829 #print "http_post_deploy input: ", http_content
tierno7edb6752016-03-21 17:37:52 +0100830
tiernof97fd272016-07-11 14:32:37 +0200831 try:
832 scenario_id = nfvo.new_scenario(mydb, tenant_id, http_content)
833 instance = nfvo.start_scenario(mydb, tenant_id, scenario_id, http_content['name'], http_content['name'])
834 #print json.dumps(data, indent=4)
835 return format_out(instance)
836 except (nfvo.NfvoException, db_base_Exception) as e:
837 logger.error("http_post_deploy error {}: {}".format(e.http_code, str(e)))
838 bottle.abort(e.http_code, str(e))
tierno7edb6752016-03-21 17:37:52 +0100839
840@bottle.route(url_base + '/<tenant_id>/topology/verify', method='POST')
841def http_post_verify(tenant_id):
842 #TODO:
843# '''post topology verify'''
844# print "http_post_verify by tenant " + tenant_id + ' datacenter ' + datacenter
tiernof97fd272016-07-11 14:32:37 +0200845 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
tierno7edb6752016-03-21 17:37:52 +0100846 return
847
848#
849# SCENARIOS
850#
851
852@bottle.route(url_base + '/<tenant_id>/scenarios', method='POST')
853def http_post_scenarios(tenant_id):
854 '''add a scenario into the catalogue. Creates the scenario and its internal structure in the OPENMANO DB'''
tiernof97fd272016-07-11 14:32:37 +0200855 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
garciadeblas9f8456e2016-09-05 05:02:59 +0200856 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 +0200857 #r = utils.remove_extra_items(http_content, used_schema)
tierno7edb6752016-03-21 17:37:52 +0100858 #if r is not None: print "http_post_scenarios: Warning: remove extra items ", r
tiernof97fd272016-07-11 14:32:37 +0200859 #print "http_post_scenarios input: ", http_content
860 try:
tierno4319dad2016-09-05 12:11:11 +0200861 if used_schema == nsd_schema_v01:
tiernof97fd272016-07-11 14:32:37 +0200862 scenario_id = nfvo.new_scenario(mydb, tenant_id, http_content)
tierno4319dad2016-09-05 12:11:11 +0200863 elif used_schema == nsd_schema_v02:
tiernof97fd272016-07-11 14:32:37 +0200864 scenario_id = nfvo.new_scenario_v02(mydb, tenant_id, http_content)
tierno4319dad2016-09-05 12:11:11 +0200865 elif used_schema == nsd_schema_v03:
garciadeblas9f8456e2016-09-05 05:02:59 +0200866 scenario_id = nfvo.new_scenario_v03(mydb, tenant_id, http_content)
867 else:
868 logger.warning('Unexpected schema_version: %s', http_content.get("schema_version"))
869 bottle.abort(HTTP_Bad_Request, "Invalid schema version")
tierno7edb6752016-03-21 17:37:52 +0100870 #print json.dumps(data, indent=4)
871 #return format_out(data)
tiernof97fd272016-07-11 14:32:37 +0200872 return http_get_scenario_id(tenant_id, scenario_id)
873 except (nfvo.NfvoException, db_base_Exception) as e:
874 logger.error("http_post_scenarios error {}: {}".format(e.http_code, str(e)))
875 bottle.abort(e.http_code, str(e))
tierno7edb6752016-03-21 17:37:52 +0100876
877@bottle.route(url_base + '/<tenant_id>/scenarios/<scenario_id>/action', method='POST')
878def http_post_scenario_action(tenant_id, scenario_id):
879 '''take an action over a scenario'''
tiernof97fd272016-07-11 14:32:37 +0200880 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
tierno7edb6752016-03-21 17:37:52 +0100881 #check valid tenant_id
tiernof97fd272016-07-11 14:32:37 +0200882 try:
883 nfvo.check_tenant(mydb, tenant_id)
884 #parse input data
885 http_content,_ = format_in( scenario_action_schema )
886 r = utils.remove_extra_items(http_content, scenario_action_schema)
tiernoefd80c92016-09-16 14:17:46 +0200887 if r:
888 logger.debug("Remove received extra items %s", str(r))
tiernof97fd272016-07-11 14:32:37 +0200889 if "start" in http_content:
890 data = nfvo.start_scenario(mydb, tenant_id, scenario_id, http_content['start']['instance_name'], \
891 http_content['start'].get('description',http_content['start']['instance_name']),
892 http_content['start'].get('datacenter') )
tierno7edb6752016-03-21 17:37:52 +0100893 return format_out(data)
tiernof97fd272016-07-11 14:32:37 +0200894 elif "deploy" in http_content: #Equivalent to start
895 data = nfvo.start_scenario(mydb, tenant_id, scenario_id, http_content['deploy']['instance_name'],
896 http_content['deploy'].get('description',http_content['deploy']['instance_name']),
897 http_content['deploy'].get('datacenter') )
tierno7edb6752016-03-21 17:37:52 +0100898 return format_out(data)
tiernof97fd272016-07-11 14:32:37 +0200899 elif "reserve" in http_content: #Reserve resources
900 data = nfvo.start_scenario(mydb, tenant_id, scenario_id, http_content['reserve']['instance_name'],
901 http_content['reserve'].get('description',http_content['reserve']['instance_name']),
902 http_content['reserve'].get('datacenter'), startvms=False )
tierno7edb6752016-03-21 17:37:52 +0100903 return format_out(data)
tiernof97fd272016-07-11 14:32:37 +0200904 elif "verify" in http_content: #Equivalent to start and then delete
905 data = nfvo.start_scenario(mydb, tenant_id, scenario_id, http_content['verify']['instance_name'],
906 http_content['verify'].get('description',http_content['verify']['instance_name']),
907 http_content['verify'].get('datacenter'), startvms=False )
908 instance_id = data['uuid']
909 nfvo.delete_instance(mydb, tenant_id,instance_id)
tierno7edb6752016-03-21 17:37:52 +0100910 return format_out({"result":"Verify OK"})
tiernof97fd272016-07-11 14:32:37 +0200911 except (nfvo.NfvoException, db_base_Exception) as e:
912 logger.error("http_post_scenario_action error {}: {}".format(e.http_code, str(e)))
913 bottle.abort(e.http_code, str(e))
tierno7edb6752016-03-21 17:37:52 +0100914
915@bottle.route(url_base + '/<tenant_id>/scenarios', method='GET')
916def http_get_scenarios(tenant_id):
917 '''get scenarios list'''
tiernof97fd272016-07-11 14:32:37 +0200918 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
919 try:
920 #check valid tenant_id
921 if tenant_id != "any":
922 nfvo.check_tenant(mydb, tenant_id)
923 #obtain data
924 s,w,l=filter_query_string(bottle.request.query, None, ('uuid', 'name', 'description', 'tenant_id', 'created_at', 'public'))
925 where_or={}
926 if tenant_id != "any":
927 where_or["tenant_id"] = tenant_id
928 where_or["public"] = True
929 scenarios = mydb.get_rows(SELECT=s, WHERE=w, WHERE_OR=where_or, WHERE_AND_OR="AND", LIMIT=l, FROM='scenarios')
930 convert_datetime2str(scenarios)
931 utils.convert_str2boolean(scenarios, ('public',) )
932 data={'scenarios':scenarios}
tierno7edb6752016-03-21 17:37:52 +0100933 #print json.dumps(scenarios, indent=4)
tiernof97fd272016-07-11 14:32:37 +0200934 return format_out(data)
935 except (nfvo.NfvoException, db_base_Exception) as e:
936 logger.error("http_get_scenarios error {}: {}".format(e.http_code, str(e)))
937 bottle.abort(e.http_code, str(e))
tierno7edb6752016-03-21 17:37:52 +0100938
939@bottle.route(url_base + '/<tenant_id>/scenarios/<scenario_id>', method='GET')
940def http_get_scenario_id(tenant_id, scenario_id):
941 '''get scenario details, can use both uuid or name'''
tiernof97fd272016-07-11 14:32:37 +0200942 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
943 try:
944 #check valid tenant_id
945 if tenant_id != "any":
946 nfvo.check_tenant(mydb, tenant_id)
947 #obtain data
948 scenario = mydb.get_scenario(scenario_id, tenant_id)
949 convert_datetime2str(scenario)
950 data={'scenario' : scenario}
tierno7edb6752016-03-21 17:37:52 +0100951 return format_out(data)
tiernof97fd272016-07-11 14:32:37 +0200952 except (nfvo.NfvoException, db_base_Exception) as e:
953 logger.error("http_get_scenarios error {}: {}".format(e.http_code, str(e)))
954 bottle.abort(e.http_code, str(e))
tierno7edb6752016-03-21 17:37:52 +0100955
956@bottle.route(url_base + '/<tenant_id>/scenarios/<scenario_id>', method='DELETE')
957def http_delete_scenario_id(tenant_id, scenario_id):
958 '''delete a scenario from database, can use both uuid or name'''
tiernof97fd272016-07-11 14:32:37 +0200959 try:
960 #check valid tenant_id
961 if tenant_id != "any":
962 nfvo.check_tenant(mydb, tenant_id)
963 #obtain data
964 data = mydb.delete_scenario(scenario_id, tenant_id)
tierno7edb6752016-03-21 17:37:52 +0100965 #print json.dumps(data, indent=4)
966 return format_out({"result":"scenario " + data + " deleted"})
tiernof97fd272016-07-11 14:32:37 +0200967 except (nfvo.NfvoException, db_base_Exception) as e:
968 logger.error("http_delete_scenario_id error {}: {}".format(e.http_code, str(e)))
969 bottle.abort(e.http_code, str(e))
tierno7edb6752016-03-21 17:37:52 +0100970
971
972@bottle.route(url_base + '/<tenant_id>/scenarios/<scenario_id>', method='PUT')
973def http_put_scenario_id(tenant_id, scenario_id):
974 '''edit an existing scenario id'''
tiernof97fd272016-07-11 14:32:37 +0200975 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
tierno7edb6752016-03-21 17:37:52 +0100976 http_content,_ = format_in( scenario_edit_schema )
tierno42fcc3b2016-07-06 17:20:40 +0200977 #r = utils.remove_extra_items(http_content, scenario_edit_schema)
tierno7edb6752016-03-21 17:37:52 +0100978 #if r is not None: print "http_put_scenario_id: Warning: remove extra items ", r
tiernof97fd272016-07-11 14:32:37 +0200979 #print "http_put_scenario_id input: ", http_content
980 try:
981 nfvo.edit_scenario(mydb, tenant_id, scenario_id, http_content)
tierno7edb6752016-03-21 17:37:52 +0100982 #print json.dumps(data, indent=4)
983 #return format_out(data)
tiernof97fd272016-07-11 14:32:37 +0200984 return http_get_scenario_id(tenant_id, scenario_id)
985 except (nfvo.NfvoException, db_base_Exception) as e:
986 logger.error("http_put_scenario_id error {}: {}".format(e.http_code, str(e)))
987 bottle.abort(e.http_code, str(e))
tierno7edb6752016-03-21 17:37:52 +0100988
989@bottle.route(url_base + '/<tenant_id>/instances', method='POST')
990def http_post_instances(tenant_id):
991 '''take an action over a scenario'''
tiernof97fd272016-07-11 14:32:37 +0200992 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
993 try:
994 #check valid tenant_id
995 if tenant_id != "any":
996 nfvo.check_tenant(mydb, tenant_id)
997 #parse input data
garciadeblas0c317ee2016-08-29 12:33:06 +0200998 http_content,used_schema = format_in( instance_scenario_create_schema_v01)
tiernof97fd272016-07-11 14:32:37 +0200999 r = utils.remove_extra_items(http_content, used_schema)
tiernoa4e1a6e2016-08-31 14:19:40 +02001000 if r is not None:
1001 logger.warning("http_post_instances: Warning: remove extra items %s", str(r))
tiernof97fd272016-07-11 14:32:37 +02001002 data = nfvo.create_instance(mydb, tenant_id, http_content["instance"])
tierno7edb6752016-03-21 17:37:52 +01001003 return format_out(data)
tiernof97fd272016-07-11 14:32:37 +02001004 except (nfvo.NfvoException, db_base_Exception) as e:
1005 logger.error("http_post_instances error {}: {}".format(e.http_code, str(e)))
1006 bottle.abort(e.http_code, str(e))
tierno7edb6752016-03-21 17:37:52 +01001007
1008#
1009# INSTANCES
1010#
1011@bottle.route(url_base + '/<tenant_id>/instances', method='GET')
1012def http_get_instances(tenant_id):
1013 '''get instance list'''
tiernof97fd272016-07-11 14:32:37 +02001014 try:
1015 #check valid tenant_id
1016 if tenant_id != "any":
1017 nfvo.check_tenant(mydb, tenant_id)
1018 #obtain data
1019 s,w,l=filter_query_string(bottle.request.query, None, ('uuid', 'name', 'scenario_id', 'tenant_id', 'description', 'created_at'))
1020 if tenant_id != "any":
1021 w['tenant_id'] = tenant_id
1022 instances = mydb.get_rows(SELECT=s, WHERE=w, LIMIT=l, FROM='instance_scenarios')
1023 convert_datetime2str(instances)
1024 utils.convert_str2boolean(instances, ('public',) )
1025 data={'instances':instances}
1026 return format_out(data)
1027 except (nfvo.NfvoException, db_base_Exception) as e:
1028 logger.error("http_get_instances error {}: {}".format(e.http_code, str(e)))
1029 bottle.abort(e.http_code, str(e))
tierno7edb6752016-03-21 17:37:52 +01001030
1031@bottle.route(url_base + '/<tenant_id>/instances/<instance_id>', method='GET')
1032def http_get_instance_id(tenant_id, instance_id):
1033 '''get instances details, can use both uuid or name'''
tiernof97fd272016-07-11 14:32:37 +02001034 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
1035 try:
1036 #check valid tenant_id
1037 if tenant_id != "any":
1038 nfvo.check_tenant(mydb, tenant_id)
1039 if tenant_id == "any":
1040 tenant_id = None
1041 #obtain data (first time is only to check that the instance exists)
1042 instance_dict = mydb.get_instance_scenario(instance_id, tenant_id, verbose=True)
1043 try:
1044 nfvo.refresh_instance(mydb, tenant_id, instance_dict)
1045 except (nfvo.NfvoException, db_base_Exception) as e:
1046 logger.warn("nfvo.refresh_instance couldn't refresh the status of the instance: %s" % str(e))
1047 #obtain data with results upated
1048 instance = mydb.get_instance_scenario(instance_id, tenant_id)
1049 convert_datetime2str(instance)
1050 #print json.dumps(instance, indent=4)
1051 return format_out(instance)
1052 except (nfvo.NfvoException, db_base_Exception) as e:
1053 logger.error("http_get_instance_id error {}: {}".format(e.http_code, str(e)))
1054 bottle.abort(e.http_code, str(e))
tierno8e995ce2016-09-22 08:13:00 +00001055 except Exception as e:
1056 logger.error("http_get_instance_id error {}".format(str(e)))
1057 bottle.abort(HTTP_Internal_Server_Error, str(e))
tierno7edb6752016-03-21 17:37:52 +01001058
1059@bottle.route(url_base + '/<tenant_id>/instances/<instance_id>', method='DELETE')
1060def http_delete_instance_id(tenant_id, instance_id):
1061 '''delete instance from VIM and from database, can use both uuid or name'''
tiernof97fd272016-07-11 14:32:37 +02001062 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
1063 try:
1064 #check valid tenant_id
1065 if tenant_id != "any":
1066 nfvo.check_tenant(mydb, tenant_id)
1067 if tenant_id == "any":
1068 tenant_id = None
1069 #obtain data
1070 message = nfvo.delete_instance(mydb, tenant_id,instance_id)
tierno7edb6752016-03-21 17:37:52 +01001071 return format_out({"result":message})
tiernof97fd272016-07-11 14:32:37 +02001072 except (nfvo.NfvoException, db_base_Exception) as e:
1073 logger.error("http_delete_instance_id error {}: {}".format(e.http_code, str(e)))
1074 bottle.abort(e.http_code, str(e))
tierno7edb6752016-03-21 17:37:52 +01001075
1076@bottle.route(url_base + '/<tenant_id>/instances/<instance_id>/action', method='POST')
1077def http_post_instance_scenario_action(tenant_id, instance_id):
1078 '''take an action over a scenario instance'''
tiernof97fd272016-07-11 14:32:37 +02001079 logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url)
1080 try:
1081 #check valid tenant_id
1082 if tenant_id != "any":
1083 nfvo.check_tenant(mydb, tenant_id)
1084
1085 #parse input data
1086 http_content,_ = format_in( instance_scenario_action_schema )
1087 r = utils.remove_extra_items(http_content, instance_scenario_action_schema)
tiernoefd80c92016-09-16 14:17:46 +02001088 if r:
1089 logger.debug("Remove received extra items %s", str(r))
tiernof97fd272016-07-11 14:32:37 +02001090 #print "http_post_instance_scenario_action input: ", http_content
1091 #obtain data
1092 instance = mydb.get_instance_scenario(instance_id, tenant_id)
1093 instance_id = instance["uuid"]
1094
1095 data = nfvo.instance_action(mydb, tenant_id, instance_id, http_content)
tierno7edb6752016-03-21 17:37:52 +01001096 return format_out(data)
tiernof97fd272016-07-11 14:32:37 +02001097 except (nfvo.NfvoException, db_base_Exception) as e:
1098 logger.error("http_post_instance_scenario_action error {}: {}".format(e.http_code, str(e)))
1099 bottle.abort(e.http_code, str(e))
tierno7edb6752016-03-21 17:37:52 +01001100
1101
1102@bottle.error(400)
1103@bottle.error(401)
1104@bottle.error(404)
1105@bottle.error(403)
1106@bottle.error(405)
1107@bottle.error(406)
1108@bottle.error(409)
1109@bottle.error(503)
1110@bottle.error(500)
1111def error400(error):
1112 e={"error":{"code":error.status_code, "type":error.status, "description":error.body}}
1113 bottle.response.headers['Access-Control-Allow-Origin'] = '*'
1114 return format_out(e)
1115