blob: 0e5bf3973d41de3e82e1a26358f0448242511a33 [file] [log] [blame]
tiernof7aa8c42016-09-06 16:43:04 +02001# -*- coding: utf-8 -*-
2
3##
tiernoa62249e2018-09-17 17:57:30 +02004# Copyright 2015 Telefonica Investigacion y Desarrollo, S.A.U.
tierno9a61c6b2016-09-08 10:57:02 +02005# This file is part of openvim
tiernof7aa8c42016-09-06 16:43:04 +02006# 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'''
25This is the thread for the http server North API.
26Two thread will be launched, with normal and administrative permissions.
27'''
28
tiernoc1d1d472017-02-08 14:21:28 +010029__author__="Alfonso Tierno, Gerardo Garcia, Leonardo Mirabal"
tiernof7aa8c42016-09-06 16:43:04 +020030__date__ ="$10-jul-2014 12:07:15$"
31
32import bottle
Leonardo2a5a0c52016-11-07 14:28:35 +010033import urlparse
tiernof7aa8c42016-09-06 16:43:04 +020034import yaml
35import json
36import threading
37import datetime
garciadeblas24595392016-09-30 17:49:57 +020038import hashlib
39import os
tiernoe7bedeb2016-12-07 16:22:53 +010040import imp
tiernoc67abe22017-07-03 17:04:54 +020041import socket
Mirabale9317ff2017-01-18 16:10:58 +000042from netaddr import IPNetwork, IPAddress, all_matching_cidrs
tiernoe7bedeb2016-12-07 16:22:53 +010043#import only if needed because not needed in test mode. To allow an easier installation import RADclass
tiernof7aa8c42016-09-06 16:43:04 +020044from jsonschema import validate as js_v, exceptions as js_e
45import host_thread as ht
46from vim_schema import host_new_schema, host_edit_schema, tenant_new_schema, \
47 tenant_edit_schema, \
48 flavor_new_schema, flavor_update_schema, \
49 image_new_schema, image_update_schema, \
50 server_new_schema, server_action_schema, network_new_schema, network_update_schema, \
mirabal6045a9d2017-03-06 11:36:55 +010051 port_new_schema, port_update_schema, openflow_controller_schema, of_port_map_new_schema
tierno57f7bda2017-02-09 12:01:55 +010052import ovim
53import logging
tiernof7aa8c42016-09-06 16:43:04 +020054
55global my
56global url_base
57global config_dic
tiernoe7bedeb2016-12-07 16:22:53 +010058global RADclass_module
tiernoa6933042017-05-24 16:54:33 +020059RADclass_module=None #RADclass module is charged only if not in test mode
tiernof7aa8c42016-09-06 16:43:04 +020060
61url_base="/openvim"
62
63HTTP_Bad_Request = 400
tiernoa6933042017-05-24 16:54:33 +020064HTTP_Unauthorized = 401
65HTTP_Not_Found = 404
tiernof7aa8c42016-09-06 16:43:04 +020066HTTP_Forbidden = 403
tiernoa6933042017-05-24 16:54:33 +020067HTTP_Method_Not_Allowed = 405
tiernof7aa8c42016-09-06 16:43:04 +020068HTTP_Not_Acceptable = 406
69HTTP_Request_Timeout = 408
70HTTP_Conflict = 409
tiernoa6933042017-05-24 16:54:33 +020071HTTP_Service_Unavailable = 503
72HTTP_Internal_Server_Error= 500
tiernof7aa8c42016-09-06 16:43:04 +020073
garciadeblas24595392016-09-30 17:49:57 +020074def md5(fname):
75 hash_md5 = hashlib.md5()
76 with open(fname, "rb") as f:
77 for chunk in iter(lambda: f.read(4096), b""):
78 hash_md5.update(chunk)
79 return hash_md5.hexdigest()
tiernof7aa8c42016-09-06 16:43:04 +020080
garciadeblas922ad6f2017-01-10 13:10:30 +010081def md5_string(fname):
82 hash_md5 = hashlib.md5()
83 hash_md5.update(fname)
84 return hash_md5.hexdigest()
85
tiernof7aa8c42016-09-06 16:43:04 +020086def check_extended(extended, allow_net_attach=False):
87 '''Makes and extra checking of extended input that cannot be done using jsonschema
88 Attributes:
89 allow_net_attach: for allowing or not the uuid field at interfaces
90 that are allowed for instance, but not for flavors
91 Return: (<0, error_text) if error; (0,None) if not error '''
92 if "numas" not in extended: return 0, None
93 id_s=[]
94 numaid=0
95 for numa in extended["numas"]:
96 nb_formats = 0
97 if "cores" in numa:
98 nb_formats += 1
99 if "cores-id" in numa:
100 if len(numa["cores-id"]) != numa["cores"]:
101 return -HTTP_Bad_Request, "different number of cores-id (%d) than cores (%d) at numa %d" % (len(numa["cores-id"]), numa["cores"],numaid)
102 id_s.extend(numa["cores-id"])
103 if "threads" in numa:
104 nb_formats += 1
105 if "threads-id" in numa:
106 if len(numa["threads-id"]) != numa["threads"]:
107 return -HTTP_Bad_Request, "different number of threads-id (%d) than threads (%d) at numa %d" % (len(numa["threads-id"]), numa["threads"],numaid)
108 id_s.extend(numa["threads-id"])
109 if "paired-threads" in numa:
110 nb_formats += 1
111 if "paired-threads-id" in numa:
112 if len(numa["paired-threads-id"]) != numa["paired-threads"]:
113 return -HTTP_Bad_Request, "different number of paired-threads-id (%d) than paired-threads (%d) at numa %d" % (len(numa["paired-threads-id"]), numa["paired-threads"],numaid)
114 for pair in numa["paired-threads-id"]:
115 if len(pair) != 2:
116 return -HTTP_Bad_Request, "paired-threads-id must contain a list of two elements list at numa %d" % (numaid)
117 id_s.extend(pair)
118 if nb_formats > 1:
119 return -HTTP_Service_Unavailable, "only one of cores, threads, paired-threads are allowed in this version at numa %d" % numaid
120 #check interfaces
121 if "interfaces" in numa:
122 ifaceid=0
123 names=[]
124 vpcis=[]
125 for interface in numa["interfaces"]:
126 if "uuid" in interface and not allow_net_attach:
127 return -HTTP_Bad_Request, "uuid field is not allowed at numa %d interface %s position %d" % (numaid, interface.get("name",""), ifaceid )
128 if "mac_address" in interface and interface["dedicated"]=="yes":
129 return -HTTP_Bad_Request, "mac_address can not be set for dedicated (passthrough) at numa %d, interface %s position %d" % (numaid, interface.get("name",""), ifaceid )
130 if "name" in interface:
131 if interface["name"] in names:
132 return -HTTP_Bad_Request, "name repeated at numa %d, interface %s position %d" % (numaid, interface.get("name",""), ifaceid )
133 names.append(interface["name"])
134 if "vpci" in interface:
135 if interface["vpci"] in vpcis:
136 return -HTTP_Bad_Request, "vpci %s repeated at numa %d, interface %s position %d" % (interface["vpci"], numaid, interface.get("name",""), ifaceid )
137 vpcis.append(interface["vpci"])
138 ifaceid+=1
139 numaid+=1
140 if numaid > 1:
141 return -HTTP_Service_Unavailable, "only one numa can be defined in this version "
142 for a in range(0,len(id_s)):
143 if a not in id_s:
144 return -HTTP_Bad_Request, "core/thread identifiers must start at 0 and gaps are not alloed. Missing id number %d" % a
145
146 return 0, None
147
148#
149# dictionaries that change from HTTP API to database naming
150#
mirabal6045a9d2017-03-06 11:36:55 +0100151http2db_id={'id':'uuid'}
tiernof7aa8c42016-09-06 16:43:04 +0200152http2db_host={'id':'uuid'}
153http2db_tenant={'id':'uuid'}
mirabalcaeb2242017-05-31 10:52:22 -0500154http2db_flavor={'id':'uuid','imageRef':'image_id', 'size': 'image_size'}
tiernof7aa8c42016-09-06 16:43:04 +0200155http2db_image={'id':'uuid', 'created':'created_at', 'updated':'modified_at', 'public': 'public'}
Paolo Lungaroni423a4082018-02-14 18:05:02 +0100156http2db_server={'id':'uuid','hostId':'host_id','flavorRef':'flavor_id','osImageType':'os_image_type','imageRef':'image_id','created':'created_at'} #Unikernels extension
tiernof7aa8c42016-09-06 16:43:04 +0200157http2db_network={'id':'uuid','provider:vlan':'vlan', 'provider:physical': 'provider'}
mirabal9e194592017-02-17 11:03:25 +0100158http2db_ofc = {'id': 'uuid'}
tiernof7aa8c42016-09-06 16:43:04 +0200159http2db_port={'id':'uuid', 'network_id':'net_id', 'mac_address':'mac', 'device_owner':'type','device_id':'instance_id','binding:switch_port':'switch_port','binding:vlan':'vlan', 'bandwidth':'Mbps'}
160
mirabal6878e3f2017-06-05 09:19:26 -0500161
tiernof7aa8c42016-09-06 16:43:04 +0200162def remove_extra_items(data, schema):
mirabal6878e3f2017-06-05 09:19:26 -0500163 import re
164
tiernof7aa8c42016-09-06 16:43:04 +0200165 deleted=[]
166 if type(data) is tuple or type(data) is list:
167 for d in data:
168 a= remove_extra_items(d, schema['items'])
169 if a is not None: deleted.append(a)
170 elif type(data) is dict:
mirabal6878e3f2017-06-05 09:19:26 -0500171
tiernof7aa8c42016-09-06 16:43:04 +0200172 for k in data.keys():
mirabal6878e3f2017-06-05 09:19:26 -0500173 if 'patternProperties' in schema and k not in schema['properties'].keys():
174 reg_ex_list = schema['patternProperties'].keys()
175 for reg_ex in reg_ex_list:
176 if not re.match(reg_ex, k):
177 del data[k]
178 deleted.append(k)
179 elif 'properties' not in schema or k not in schema['properties'].keys(): # or k not in schema['patternProperties'].keys():
tiernof7aa8c42016-09-06 16:43:04 +0200180 del data[k]
181 deleted.append(k)
182 else:
183 a = remove_extra_items(data[k], schema['properties'][k])
184 if a is not None: deleted.append({k:a})
185 if len(deleted) == 0: return None
186 elif len(deleted) == 1: return deleted[0]
187 else: return deleted
mirabal6878e3f2017-06-05 09:19:26 -0500188
189
tiernof7aa8c42016-09-06 16:43:04 +0200190def delete_nulls(var):
191 if type(var) is dict:
192 for k in var.keys():
193 if var[k] is None: del var[k]
194 elif type(var[k]) is dict or type(var[k]) is list or type(var[k]) is tuple:
195 if delete_nulls(var[k]): del var[k]
196 if len(var) == 0: return True
197 elif type(var) is list or type(var) is tuple:
198 for k in var:
199 if type(k) is dict: delete_nulls(k)
200 if len(var) == 0: return True
201 return False
202
203
204class httpserver(threading.Thread):
tierno57f7bda2017-02-09 12:01:55 +0100205 def __init__(self, ovim, name="http", host='localhost', port=8080, admin=False, config_=None):
tiernof7aa8c42016-09-06 16:43:04 +0200206 '''
207 Creates a new thread to attend the http connections
208 Attributes:
209 db_conn: database connection
210 name: name of this thread
211 host: ip or name where to listen
212 port: port where to listen
213 admin: if this has privileges of administrator or not
214 config_: unless the first thread must be provided. It is a global dictionary where to allocate the self variable
215 '''
216 global url_base
217 global config_dic
218
219 #initialization
220 if config_ is not None:
221 config_dic = config_
222 if 'http_threads' not in config_dic:
223 config_dic['http_threads'] = {}
224 threading.Thread.__init__(self)
225 self.host = host
226 self.port = port
tierno57f7bda2017-02-09 12:01:55 +0100227 self.db = ovim.db #TODO OVIM remove
228 self.ovim = ovim
tiernof7aa8c42016-09-06 16:43:04 +0200229 self.admin = admin
230 if name in config_dic:
231 print "httpserver Warning!!! Onether thread with the same name", name
232 n=0
233 while name+str(n) in config_dic:
234 n +=1
235 name +=str(n)
236 self.name = name
237 self.url_preffix = 'http://' + self.host + ':' + str(self.port) + url_base
238 config_dic['http_threads'][name] = self
239
240 #Ensure that when the main program exits the thread will also exit
241 self.daemon = True
242 self.setDaemon(True)
tierno57f7bda2017-02-09 12:01:55 +0100243 self.logger = logging.getLogger("openvim.http")
tiernof7aa8c42016-09-06 16:43:04 +0200244
245 def run(self):
246 bottle.run(host=self.host, port=self.port, debug=True) #quiet=True
247
248 def gethost(self, host_id):
249 result, content = self.db.get_host(host_id)
250 if result < 0:
251 print "httpserver.gethost error %d %s" % (result, content)
252 bottle.abort(-result, content)
253 elif result==0:
254 print "httpserver.gethost host '%s' not found" % host_id
255 bottle.abort(HTTP_Not_Found, content)
256 else:
257 data={'host' : content}
258 convert_boolean(content, ('admin_state_up',) )
259 change_keys_http2db(content, http2db_host, reverse=True)
260 print data['host']
261 return format_out(data)
262
263@bottle.route(url_base + '/', method='GET')
264def http_get():
265 print
266 return 'works' #TODO: put links or redirection to /openvim???
267
268#
269# Util funcions
270#
271
272def change_keys_http2db(data, http_db, reverse=False):
273 '''Change keys of dictionary data according to the key_dict values
274 This allow change from http interface names to database names.
275 When reverse is True, the change is otherwise
276 Attributes:
277 data: can be a dictionary or a list
278 http_db: is a dictionary with hhtp names as keys and database names as value
279 reverse: by default change is done from http API to database. If True change is done otherwise
280 Return: None, but data is modified'''
281 if type(data) is tuple or type(data) is list:
282 for d in data:
283 change_keys_http2db(d, http_db, reverse)
284 elif type(data) is dict or type(data) is bottle.FormsDict:
285 if reverse:
286 for k,v in http_db.items():
287 if v in data: data[k]=data.pop(v)
288 else:
289 for k,v in http_db.items():
290 if k in data: data[v]=data.pop(k)
291
292
293
294def format_out(data):
295 '''return string of dictionary data according to requested json, yaml, xml. By default json'''
296 if 'application/yaml' in bottle.request.headers.get('Accept'):
297 bottle.response.content_type='application/yaml'
298 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='"'
299 else: #by default json
300 bottle.response.content_type='application/json'
301 #return data #json no style
302 return json.dumps(data, indent=4) + "\n"
303
304def format_in(schema):
305 try:
306 error_text = "Invalid header format "
307 format_type = bottle.request.headers.get('Content-Type', 'application/json')
308 if 'application/json' in format_type:
309 error_text = "Invalid json format "
310 #Use the json decoder instead of bottle decoder because it informs about the location of error formats with a ValueError exception
311 client_data = json.load(bottle.request.body)
312 #client_data = bottle.request.json()
313 elif 'application/yaml' in format_type:
314 error_text = "Invalid yaml format "
315 client_data = yaml.load(bottle.request.body)
316 elif format_type == 'application/xml':
317 bottle.abort(501, "Content-Type: application/xml not supported yet.")
318 else:
319 print "HTTP HEADERS: " + str(bottle.request.headers.items())
320 bottle.abort(HTTP_Not_Acceptable, 'Content-Type ' + str(format_type) + ' not supported.')
321 return
322 #if client_data == None:
323 # bottle.abort(HTTP_Bad_Request, "Content error, empty")
324 # return
325 #check needed_items
326
327 #print "HTTP input data: ", str(client_data)
328 error_text = "Invalid content "
329 js_v(client_data, schema)
330
331 return client_data
332 except (ValueError, yaml.YAMLError) as exc:
333 error_text += str(exc)
334 print error_text
335 bottle.abort(HTTP_Bad_Request, error_text)
336 except js_e.ValidationError as exc:
337 print "HTTP validate_in error, jsonschema exception ", exc.message, "at", exc.path
338 print " CONTENT: " + str(bottle.request.body.readlines())
339 error_pos = ""
340 if len(exc.path)>0: error_pos=" at '" + ":".join(map(str, exc.path)) + "'"
341 bottle.abort(HTTP_Bad_Request, error_text + error_pos+": "+exc.message)
342 #except:
343 # bottle.abort(HTTP_Bad_Request, "Content error: Failed to parse Content-Type", error_pos)
344 # raise
345
346def filter_query_string(qs, http2db, allowed):
347 '''Process query string (qs) checking that contains only valid tokens for avoiding SQL injection
348 Attributes:
349 'qs': bottle.FormsDict variable to be processed. None or empty is considered valid
350 'allowed': list of allowed string tokens (API http naming). All the keys of 'qs' must be one of 'allowed'
351 'http2db': dictionary with change from http API naming (dictionary key) to database naming(dictionary value)
352 Return: A tuple with the (select,where,limit) to be use in a database query. All of then transformed to the database naming
353 select: list of items to retrieve, filtered by query string 'field=token'. If no 'field' is present, allowed list is returned
354 where: dictionary with key, value, taken from the query string token=value. Empty if nothing is provided
355 limit: limit dictated by user with the query string 'limit'. 100 by default
356 abort if not permitted, using bottel.abort
357 '''
tierno57f7bda2017-02-09 12:01:55 +0100358 where = {}
359 limit = 100
360 select = []
tiernof7aa8c42016-09-06 16:43:04 +0200361 if type(qs) is not bottle.FormsDict:
362 print '!!!!!!!!!!!!!!invalid query string not a dictionary'
tierno57f7bda2017-02-09 12:01:55 +0100363 # bottle.abort(HTTP_Internal_Server_Error, "call programmer")
tiernof7aa8c42016-09-06 16:43:04 +0200364 else:
365 for k in qs:
tierno57f7bda2017-02-09 12:01:55 +0100366 if k == 'field':
tiernof7aa8c42016-09-06 16:43:04 +0200367 select += qs.getall(k)
368 for v in select:
369 if v not in allowed:
tierno57f7bda2017-02-09 12:01:55 +0100370 bottle.abort(HTTP_Bad_Request, "Invalid query string at 'field=" + v + "'")
371 elif k == 'limit':
tiernof7aa8c42016-09-06 16:43:04 +0200372 try:
tierno57f7bda2017-02-09 12:01:55 +0100373 limit = int(qs[k])
tiernof7aa8c42016-09-06 16:43:04 +0200374 except:
tierno57f7bda2017-02-09 12:01:55 +0100375 bottle.abort(HTTP_Bad_Request, "Invalid query string at 'limit=" + qs[k] + "'")
tiernof7aa8c42016-09-06 16:43:04 +0200376 else:
377 if k not in allowed:
tierno57f7bda2017-02-09 12:01:55 +0100378 bottle.abort(HTTP_Bad_Request, "Invalid query string at '" + k + "=" + qs[k] + "'")
379 if qs[k] != "null":
380 where[k] = qs[k]
381 else:
382 where[k] = None
383 if len(select) == 0: select += allowed
384 # change from http api to database naming
385 for i in range(0, len(select)):
386 k = select[i]
387 if k in http2db:
tiernof7aa8c42016-09-06 16:43:04 +0200388 select[i] = http2db[k]
389 change_keys_http2db(where, http2db)
tierno57f7bda2017-02-09 12:01:55 +0100390 # print "filter_query_string", select,where,limit
tiernof7aa8c42016-09-06 16:43:04 +0200391
tierno57f7bda2017-02-09 12:01:55 +0100392 return select, where, limit
tiernof7aa8c42016-09-06 16:43:04 +0200393
394def convert_bandwidth(data, reverse=False):
395 '''Check the field bandwidth recursively and when found, it removes units and convert to number
396 It assumes that bandwidth is well formed
397 Attributes:
398 'data': dictionary bottle.FormsDict variable to be checked. None or empty is considered valid
399 'reverse': by default convert form str to int (Mbps), if True it convert from number to units
400 Return:
401 None
402 '''
403 if type(data) is dict:
404 for k in data.keys():
405 if type(data[k]) is dict or type(data[k]) is tuple or type(data[k]) is list:
406 convert_bandwidth(data[k], reverse)
407 if "bandwidth" in data:
408 try:
409 value=str(data["bandwidth"])
410 if not reverse:
411 pos = value.find("bps")
412 if pos>0:
413 if value[pos-1]=="G": data["bandwidth"] = int(data["bandwidth"][:pos-1]) * 1000
414 elif value[pos-1]=="k": data["bandwidth"]= int(data["bandwidth"][:pos-1]) / 1000
415 else: data["bandwidth"]= int(data["bandwidth"][:pos-1])
416 else:
417 value = int(data["bandwidth"])
418 if value % 1000 == 0: data["bandwidth"]=str(value/1000) + " Gbps"
419 else: data["bandwidth"]=str(value) + " Mbps"
420 except:
421 print "convert_bandwidth exception for type", type(data["bandwidth"]), " data", data["bandwidth"]
422 return
423 if type(data) is tuple or type(data) is list:
424 for k in data:
425 if type(k) is dict or type(k) is tuple or type(k) is list:
426 convert_bandwidth(k, reverse)
427
tierno57f7bda2017-02-09 12:01:55 +0100428def convert_boolean(data, items): #TODO OVIM delete
tiernof7aa8c42016-09-06 16:43:04 +0200429 '''Check recursively the content of data, and if there is an key contained in items, convert value from string to boolean
430 It assumes that bandwidth is well formed
431 Attributes:
432 'data': dictionary bottle.FormsDict variable to be checked. None or empty is consideted valid
433 'items': tuple of keys to convert
434 Return:
435 None
436 '''
437 if type(data) is dict:
438 for k in data.keys():
439 if type(data[k]) is dict or type(data[k]) is tuple or type(data[k]) is list:
440 convert_boolean(data[k], items)
441 if k in items:
442 if type(data[k]) is str:
443 if data[k]=="false": data[k]=False
444 elif data[k]=="true": data[k]=True
445 if type(data) is tuple or type(data) is list:
446 for k in data:
447 if type(k) is dict or type(k) is tuple or type(k) is list:
448 convert_boolean(k, items)
449
450def convert_datetime2str(var):
451 '''Converts a datetime variable to a string with the format '%Y-%m-%dT%H:%i:%s'
452 It enters recursively in the dict var finding this kind of variables
453 '''
454 if type(var) is dict:
455 for k,v in var.items():
456 if type(v) is datetime.datetime:
457 var[k]= v.strftime('%Y-%m-%dT%H:%M:%S')
458 elif type(v) is dict or type(v) is list or type(v) is tuple:
459 convert_datetime2str(v)
460 if len(var) == 0: return True
461 elif type(var) is list or type(var) is tuple:
462 for v in var:
463 convert_datetime2str(v)
464
465def check_valid_tenant(my, tenant_id):
466 if tenant_id=='any':
467 if not my.admin:
468 return HTTP_Unauthorized, "Needed admin privileges"
469 else:
470 result, _ = my.db.get_table(FROM='tenants', SELECT=('uuid',), WHERE={'uuid': tenant_id})
471 if result<=0:
472 return HTTP_Not_Found, "tenant '%s' not found" % tenant_id
473 return 0, None
474
Leonardo2a5a0c52016-11-07 14:28:35 +0100475def is_url(url):
476 '''
477 Check if string value is a well-wormed url
478 :param url: string url
479 :return: True if is a valid url, False if is not well-formed
480 '''
481
482 parsed_url = urlparse.urlparse(url)
483 return parsed_url
484
485
tiernof7aa8c42016-09-06 16:43:04 +0200486@bottle.error(400)
487@bottle.error(401)
488@bottle.error(404)
489@bottle.error(403)
490@bottle.error(405)
491@bottle.error(406)
492@bottle.error(408)
493@bottle.error(409)
494@bottle.error(503)
495@bottle.error(500)
496def error400(error):
497 e={"error":{"code":error.status_code, "type":error.status, "description":error.body}}
498 return format_out(e)
499
500@bottle.hook('after_request')
501def enable_cors():
502 #TODO: Alf: Is it needed??
503 bottle.response.headers['Access-Control-Allow-Origin'] = '*'
504
505#
506# HOSTS
507#
508
509@bottle.route(url_base + '/hosts', method='GET')
510def http_get_hosts():
Mirabal7256d6b2016-12-15 10:51:19 +0000511 return format_out(get_hosts())
512
513
514def get_hosts():
515 select_, where_, limit_ = filter_query_string(bottle.request.query, http2db_host,
Paolo Lungaroni423a4082018-02-14 18:05:02 +0100516 ('id', 'name', 'description', 'status', 'admin_state_up', 'ip_name', 'hypervisors')) #Unikernels extension
tiernof7aa8c42016-09-06 16:43:04 +0200517
518 myself = config_dic['http_threads'][ threading.current_thread().name ]
519 result, content = myself.db.get_table(FROM='hosts', SELECT=select_, WHERE=where_, LIMIT=limit_)
520 if result < 0:
521 print "http_get_hosts Error", content
522 bottle.abort(-result, content)
523 else:
524 convert_boolean(content, ('admin_state_up',) )
525 change_keys_http2db(content, http2db_host, reverse=True)
526 for row in content:
527 row['links'] = ( {'href': myself.url_preffix + '/hosts/' + str(row['id']), 'rel': 'bookmark'}, )
528 data={'hosts' : content}
Mirabal7256d6b2016-12-15 10:51:19 +0000529 return data
tiernof7aa8c42016-09-06 16:43:04 +0200530
531@bottle.route(url_base + '/hosts/<host_id>', method='GET')
532def http_get_host_id(host_id):
533 my = config_dic['http_threads'][ threading.current_thread().name ]
534 return my.gethost(host_id)
535
536@bottle.route(url_base + '/hosts', method='POST')
537def http_post_hosts():
538 '''insert a host into the database. All resources are got and inserted'''
tiernoa6933042017-05-24 16:54:33 +0200539 global RADclass_module
tiernof7aa8c42016-09-06 16:43:04 +0200540 my = config_dic['http_threads'][ threading.current_thread().name ]
541 #check permissions
542 if not my.admin:
543 bottle.abort(HTTP_Unauthorized, "Needed admin privileges")
544
545 #parse input data
546 http_content = format_in( host_new_schema )
547 r = remove_extra_items(http_content, host_new_schema)
548 if r is not None: print "http_post_host_id: Warning: remove extra items ", r
549 change_keys_http2db(http_content['host'], http2db_host)
550
tiernoa6933042017-05-24 16:54:33 +0200551 if 'host' in http_content:
552 host = http_content['host']
553 if 'host-data' in http_content:
554 host.update(http_content['host-data'])
tiernof7aa8c42016-09-06 16:43:04 +0200555 else:
tiernoa6933042017-05-24 16:54:33 +0200556 host = http_content['host-data']
557 warning_text = ""
558 ip_name = host['ip_name']
559 user = host['user']
560 password = host.get('password')
561 if host.get('autodiscover'):
tiernoe7bedeb2016-12-07 16:22:53 +0100562 if not RADclass_module:
563 try:
564 RADclass_module = imp.find_module("RADclass")
565 except (IOError, ImportError) as e:
566 raise ImportError("Cannot import RADclass.py Openvim not properly installed" +str(e))
tiernof7aa8c42016-09-06 16:43:04 +0200567
568 #fill rad info
tiernoe7bedeb2016-12-07 16:22:53 +0100569 rad = RADclass_module.RADclass()
tiernof7aa8c42016-09-06 16:43:04 +0200570 (return_status, code) = rad.obtain_RAD(user, password, ip_name)
571
572 #return
573 if not return_status:
574 print 'http_post_hosts ERROR obtaining RAD', code
575 bottle.abort(HTTP_Bad_Request, code)
576 return
577 warning_text=code
578 rad_structure = yaml.load(rad.to_text())
579 print 'rad_structure\n---------------------'
580 print json.dumps(rad_structure, indent=4)
581 print '---------------------'
582 #return
583 WHERE_={"family":rad_structure['processor']['family'], 'manufacturer':rad_structure['processor']['manufacturer'], 'version':rad_structure['processor']['version']}
584 result, content = my.db.get_table(FROM='host_ranking',
585 SELECT=('ranking',),
586 WHERE=WHERE_)
587 if result > 0:
588 host['ranking'] = content[0]['ranking']
589 else:
590 #error_text= "Host " + str(WHERE_)+ " not found in ranking table. Not valid for VIM management"
591 #bottle.abort(HTTP_Bad_Request, error_text)
592 #return
593 warning_text += "Host " + str(WHERE_)+ " not found in ranking table. Assuming lowest value 100\n"
594 host['ranking'] = 100 #TODO: as not used in this version, set the lowest value
595
596 features = rad_structure['processor'].get('features', ())
597 host['features'] = ",".join(features)
598 host['numas'] = []
599
600 for node in (rad_structure['resource topology']['nodes'] or {}).itervalues():
601 interfaces= []
602 cores = []
603 eligible_cores=[]
604 count = 0
605 for core in node['cpu']['eligible_cores']:
606 eligible_cores.extend(core)
607 for core in node['cpu']['cores']:
608 for thread_id in core:
609 c={'core_id': count, 'thread_id': thread_id}
610 if thread_id not in eligible_cores: c['status'] = 'noteligible'
611 cores.append(c)
612 count = count+1
613
614 if 'nics' in node:
615 for port_k, port_v in node['nics']['nic 0']['ports'].iteritems():
616 if port_v['virtual']:
617 continue
618 else:
619 sriovs = []
620 for port_k2, port_v2 in node['nics']['nic 0']['ports'].iteritems():
621 if port_v2['virtual'] and port_v2['PF_pci_id']==port_k:
622 sriovs.append({'pci':port_k2, 'mac':port_v2['mac'], 'source_name':port_v2['source_name']})
623 if len(sriovs)>0:
624 #sort sriov according to pci and rename them to the vf number
625 new_sriovs = sorted(sriovs, key=lambda k: k['pci'])
626 index=0
627 for sriov in new_sriovs:
628 sriov['source_name'] = index
629 index += 1
630 interfaces.append ({'pci':str(port_k), 'Mbps': port_v['speed']/1000000, 'sriovs': new_sriovs, 'mac':port_v['mac'], 'source_name':port_v['source_name']})
tiernof7aa8c42016-09-06 16:43:04 +0200631 memory=node['memory']['node_size'] / (1024*1024*1024)
632 #memory=get_next_2pow(node['memory']['hugepage_nr'])
633 host['numas'].append( {'numa_socket': node['id'], 'hugepages': node['memory']['hugepage_nr'], 'memory':memory, 'interfaces': interfaces, 'cores': cores } )
tiernoa6933042017-05-24 16:54:33 +0200634 # print json.dumps(host, indent=4)
635 # insert in data base
636 if "created_at" in host:
637 del host["created_at"]
638 for numa in host.get("numas", ()):
639 if "hugepages_consumed" in numa:
640 del numa["hugepages_consumed"]
tiernoc67abe22017-07-03 17:04:54 +0200641 for core in numa.get("cores", ()):
642 if "instance_id" in core:
643 del core["instance_id"]
644 if "v_thread_id" in core:
645 del core["v_thread_id"]
tiernof7aa8c42016-09-06 16:43:04 +0200646 result, content = my.db.new_host(host)
647 if result >= 0:
648 if content['admin_state_up']:
649 #create thread
650 host_test_mode = True if config_dic['mode']=='test' or config_dic['mode']=="OF only" else False
651 host_develop_mode = True if config_dic['mode']=='development' else False
652 host_develop_bridge_iface = config_dic.get('development_bridge', None)
tiernoa6933042017-05-24 16:54:33 +0200653 thread = ht.host_thread(name=host.get('name',ip_name), user=user, host=ip_name,
654 password=host.get('password'),
655 keyfile=host.get('keyfile', config_dic["host_ssh_keyfile"]),
tiernoa62249e2018-09-17 17:57:30 +0200656 db=config_dic['db'],
tiernoa6933042017-05-24 16:54:33 +0200657 test=host_test_mode, image_path=config_dic['host_image_path'],
658 version=config_dic['version'], host_id=content['uuid'],
Paolo Lungaroni423a4082018-02-14 18:05:02 +0100659 develop_mode=host_develop_mode, develop_bridge_iface=host_develop_bridge_iface,
660 hypervisors=host.get('hypervisors', None)) #Unikernels extension
mirabal42ca0092017-06-14 05:42:30 -0500661
tiernof7aa8c42016-09-06 16:43:04 +0200662 thread.start()
mirabal42ca0092017-06-14 05:42:30 -0500663 config_dic['host_threads'][content['uuid']] = thread
tiernof7aa8c42016-09-06 16:43:04 +0200664
Mirabal7256d6b2016-12-15 10:51:19 +0000665 if config_dic['network_type'] == 'ovs':
666 # create bridge
Mirabale9317ff2017-01-18 16:10:58 +0000667 create_dhcp_ovs_bridge()
Mirabal7256d6b2016-12-15 10:51:19 +0000668 config_dic['host_threads'][content['uuid']].insert_task("new-ovsbridge")
mirabal6878e3f2017-06-05 09:19:26 -0500669 # create vlxan bwt OVS controller and computes
tiernoc67abe22017-07-03 17:04:54 +0200670 create_vxlan_mesh(content['uuid'], my.logger)
Mirabal7256d6b2016-12-15 10:51:19 +0000671
mirabal42ca0092017-06-14 05:42:30 -0500672 # return host data
tiernof7aa8c42016-09-06 16:43:04 +0200673 change_keys_http2db(content, http2db_host, reverse=True)
674 if len(warning_text)>0:
675 content["warning"]= warning_text
676 data={'host' : content}
677 return format_out(data)
678 else:
679 bottle.abort(HTTP_Bad_Request, content)
680 return
681
Mirabal7256d6b2016-12-15 10:51:19 +0000682
Mirabale9317ff2017-01-18 16:10:58 +0000683def delete_dhcp_ovs_bridge(vlan, net_uuid):
684 """
685 Delete bridges and port created during dhcp launching at openvim controller
686 :param vlan: net vlan id
687 :param net_uuid: network identifier
688 :return:
689 """
690 dhcp_path = config_dic['ovs_controller_file_path']
691
mirabalb716ac52017-02-10 14:47:53 +0100692 http_controller = config_dic['http_threads'][threading.current_thread().name]
693 dhcp_controller = http_controller.ovim.get_dhcp_controller()
694
mirabalb716ac52017-02-10 14:47:53 +0100695 dhcp_controller.delete_dhcp_server(vlan, net_uuid, dhcp_path)
mirabale16a6362017-07-10 05:45:56 -0500696 dhcp_controller.delete_dhcp_port(vlan, net_uuid, dhcp_path)
Mirabale9317ff2017-01-18 16:10:58 +0000697
698
699def create_dhcp_ovs_bridge():
700 """
701 Initialize bridge to allocate the dhcp server at openvim controller
702 :return:
703 """
mirabalb716ac52017-02-10 14:47:53 +0100704 http_controller = config_dic['http_threads'][threading.current_thread().name]
705 dhcp_controller = http_controller.ovim.get_dhcp_controller()
706
707 dhcp_controller.create_ovs_bridge()
Mirabale9317ff2017-01-18 16:10:58 +0000708
709
710def set_mac_dhcp(vm_ip, vlan, first_ip, last_ip, cidr, mac):
711 """"
712 Launch a dhcpserver base on dnsmasq attached to the net base on vlan id across the the openvim computes
713 :param vm_ip: IP address asigned to a VM
714 :param vlan: Segmentation id
715 :param first_ip: First dhcp range ip
716 :param last_ip: Last dhcp range ip
717 :param cidr: net cidr
718 :param mac: VM vnic mac to be macthed with the IP received
719 """
720 if not vm_ip:
721 return
722 ip_tools = IPNetwork(cidr)
723 cidr_len = ip_tools.prefixlen
724 dhcp_netmask = str(ip_tools.netmask)
725 dhcp_path = config_dic['ovs_controller_file_path']
726
727 new_cidr = [first_ip + '/' + str(cidr_len)]
728 if not len(all_matching_cidrs(vm_ip, new_cidr)):
729 vm_ip = None
730
mirabalb716ac52017-02-10 14:47:53 +0100731 http_controller = config_dic['http_threads'][threading.current_thread().name]
732 dhcp_controller = http_controller.ovim.get_dhcp_controller()
733
mirabal6878e3f2017-06-05 09:19:26 -0500734 dhcp_controller.set_mac_dhcp_server(vm_ip, mac, vlan, dhcp_netmask, first_ip, dhcp_path)
Mirabale9317ff2017-01-18 16:10:58 +0000735
736
737def delete_mac_dhcp(vm_ip, vlan, mac):
738 """
739 Delete into dhcp conf file the ip assigned to a specific MAC address
740 :param vm_ip: IP address asigned to a VM
741 :param vlan: Segmentation id
742 :param mac: VM vnic mac to be macthed with the IP received
743 :return:
744 """
745
746 dhcp_path = config_dic['ovs_controller_file_path']
747
mirabalb716ac52017-02-10 14:47:53 +0100748 http_controller = config_dic['http_threads'][threading.current_thread().name]
749 dhcp_controller = http_controller.ovim.get_dhcp_controller()
Mirabale9317ff2017-01-18 16:10:58 +0000750
mirabalb716ac52017-02-10 14:47:53 +0100751 dhcp_controller.delete_mac_dhcp_server(vm_ip, mac, vlan, dhcp_path)
Mirabale9317ff2017-01-18 16:10:58 +0000752
753
tiernoc67abe22017-07-03 17:04:54 +0200754def create_vxlan_mesh(host_id, logger=None):
Mirabale9317ff2017-01-18 16:10:58 +0000755 """
756 Create vxlan mesh across all openvimc controller and computes.
tiernoc67abe22017-07-03 17:04:54 +0200757 :param host_id: Added compute node id. Anyway vlan is created by all compute nodes
758 :param logger: To log errors
759 :return: None
Mirabale9317ff2017-01-18 16:10:58 +0000760 """
761 dhcp_compute_name = get_vxlan_interface("dhcp")
Mirabal7256d6b2016-12-15 10:51:19 +0000762 existing_hosts = get_hosts()
Mirabale9317ff2017-01-18 16:10:58 +0000763 if len(existing_hosts['hosts']) > 0:
764 # vlxan mesh creation between openvim controller and computes
Mirabal7256d6b2016-12-15 10:51:19 +0000765 computes_available = existing_hosts['hosts']
mirabalb716ac52017-02-10 14:47:53 +0100766
767 http_controller = config_dic['http_threads'][threading.current_thread().name]
768 dhcp_controller = http_controller.ovim.get_dhcp_controller()
769
Mirabale9317ff2017-01-18 16:10:58 +0000770 for compute in computes_available:
tiernoc67abe22017-07-03 17:04:54 +0200771 try:
772 if compute['ip_name'] != 'localhost':
773 remote_ip = socket.gethostbyname(compute['ip_name'])
774 else:
775 remote_ip = 'localhost'
776 except socket.error as e:
777 if logger:
778 logger.error("Cannot get compute node remote ip from '{}'. Skipping: {}".format(
779 compute['ip_name'], e))
780 continue
781 # vxlan ovs_controller <=> compute node
Mirabale9317ff2017-01-18 16:10:58 +0000782 vxlan_interface_name = get_vxlan_interface(compute['id'][:8])
mirabalb716ac52017-02-10 14:47:53 +0100783 config_dic['host_threads'][compute['id']].insert_task("new-vxlan", dhcp_compute_name, dhcp_controller.host)
tiernoc67abe22017-07-03 17:04:54 +0200784 dhcp_controller.create_ovs_vxlan_tunnel(vxlan_interface_name, remote_ip)
785 # vxlan from others compute node to cthis ompute node
786 for compute_src in computes_available:
787 if compute_src['id'] == compute['id']:
788 continue
789 config_dic['host_threads'][compute_src['id']].insert_task("new-vxlan",
790 vxlan_interface_name,
791 remote_ip)
Mirabal7256d6b2016-12-15 10:51:19 +0000792
793def delete_vxlan_mesh(host_id):
794 """
795 Create a task for remove a specific compute of the vlxan mesh
796 :param host_id: host id to be deleted.
797 """
798 existing_hosts = get_hosts()
799 computes_available = existing_hosts['hosts']
Mirabale9317ff2017-01-18 16:10:58 +0000800 #
Mirabal7256d6b2016-12-15 10:51:19 +0000801 vxlan_interface_name = get_vxlan_interface(host_id[:8])
mirabalb716ac52017-02-10 14:47:53 +0100802
803 http_controller = config_dic['http_threads'][threading.current_thread().name]
804 dhcp_host = http_controller.ovim.get_dhcp_controller()
805
806 dhcp_host.delete_ovs_vxlan_tunnel(vxlan_interface_name)
Mirabale9317ff2017-01-18 16:10:58 +0000807 # remove bridge from openvim controller if no more computes exist
808 if len(existing_hosts):
mirabalb716ac52017-02-10 14:47:53 +0100809 dhcp_host.delete_ovs_bridge()
Mirabale9317ff2017-01-18 16:10:58 +0000810 # Remove vxlan mesh
Mirabal7256d6b2016-12-15 10:51:19 +0000811 for compute in computes_available:
812 if host_id == compute['id']:
813 pass
814 else:
mirabalb716ac52017-02-10 14:47:53 +0100815 dhcp_host.delete_ovs_vxlan_tunnel(vxlan_interface_name)
Mirabale9317ff2017-01-18 16:10:58 +0000816 config_dic['host_threads'][compute['id']].insert_task("del-vxlan", vxlan_interface_name)
Mirabal7256d6b2016-12-15 10:51:19 +0000817
818
819def get_vxlan_interface(local_uuid):
820 """
821 Genearte a vxlan interface name
822 :param local_uuid: host id
823 :return: vlxan-8digits
824 """
825 return 'vxlan-' + local_uuid[:8]
826
827
tiernof7aa8c42016-09-06 16:43:04 +0200828@bottle.route(url_base + '/hosts/<host_id>', method='PUT')
829def http_put_host_id(host_id):
830 '''modify a host into the database. All resources are got and inserted'''
831 my = config_dic['http_threads'][ threading.current_thread().name ]
832 #check permissions
833 if not my.admin:
834 bottle.abort(HTTP_Unauthorized, "Needed admin privileges")
835
836 #parse input data
837 http_content = format_in( host_edit_schema )
838 r = remove_extra_items(http_content, host_edit_schema)
839 if r is not None: print "http_post_host_id: Warning: remove extra items ", r
840 change_keys_http2db(http_content['host'], http2db_host)
841
842 #insert in data base
843 result, content = my.db.edit_host(host_id, http_content['host'])
844 if result >= 0:
845 convert_boolean(content, ('admin_state_up',) )
846 change_keys_http2db(content, http2db_host, reverse=True)
847 data={'host' : content}
848
Mirabal7256d6b2016-12-15 10:51:19 +0000849 if config_dic['network_type'] == 'ovs':
850 delete_vxlan_mesh(host_id)
851 config_dic['host_threads'][host_id].insert_task("del-ovsbridge")
852
tiernof7aa8c42016-09-06 16:43:04 +0200853 #reload thread
854 config_dic['host_threads'][host_id].name = content.get('name',content['ip_name'])
855 config_dic['host_threads'][host_id].user = content['user']
856 config_dic['host_threads'][host_id].host = content['ip_name']
857 config_dic['host_threads'][host_id].insert_task("reload")
858
Mirabal7256d6b2016-12-15 10:51:19 +0000859 if config_dic['network_type'] == 'ovs':
860 # create mesh with new host data
861 config_dic['host_threads'][host_id].insert_task("new-ovsbridge")
tiernoc67abe22017-07-03 17:04:54 +0200862 create_vxlan_mesh(host_id, my.logger)
Mirabal7256d6b2016-12-15 10:51:19 +0000863
tiernof7aa8c42016-09-06 16:43:04 +0200864 #print data
865 return format_out(data)
866 else:
867 bottle.abort(HTTP_Bad_Request, content)
868 return
869
870
871
872@bottle.route(url_base + '/hosts/<host_id>', method='DELETE')
873def http_delete_host_id(host_id):
874 my = config_dic['http_threads'][ threading.current_thread().name ]
875 #check permissions
876 if not my.admin:
877 bottle.abort(HTTP_Unauthorized, "Needed admin privileges")
878 result, content = my.db.delete_row('hosts', host_id)
879 if result == 0:
880 bottle.abort(HTTP_Not_Found, content)
Mirabal7256d6b2016-12-15 10:51:19 +0000881 elif result > 0:
882 if config_dic['network_type'] == 'ovs':
883 delete_vxlan_mesh(host_id)
884 # terminate thread
tiernof7aa8c42016-09-06 16:43:04 +0200885 if host_id in config_dic['host_threads']:
Mirabal7256d6b2016-12-15 10:51:19 +0000886 if config_dic['network_type'] == 'ovs':
887 config_dic['host_threads'][host_id].insert_task("del-ovsbridge")
tiernof7aa8c42016-09-06 16:43:04 +0200888 config_dic['host_threads'][host_id].insert_task("exit")
889 #return data
890 data={'result' : content}
891 return format_out(data)
892 else:
893 print "http_delete_host_id error",result, content
894 bottle.abort(-result, content)
895 return
tiernof7aa8c42016-09-06 16:43:04 +0200896#
897# TENANTS
898#
899
mirabalfbfb7972017-02-27 17:36:17 +0100900
tiernof7aa8c42016-09-06 16:43:04 +0200901@bottle.route(url_base + '/tenants', method='GET')
902def http_get_tenants():
mirabalfbfb7972017-02-27 17:36:17 +0100903 """
904 Retreive tenant list from DB
905 :return:
906 """
907 my = config_dic['http_threads'][threading.current_thread().name]
908
909 try:
910 select_, where_, limit_ = filter_query_string(bottle.request.query, http2db_tenant,
911 ('id', 'name', 'description', 'enabled'))
912 tenants = my.ovim.get_tenants(select_, where_)
913 delete_nulls(tenants)
914 change_keys_http2db(tenants, http2db_tenant, reverse=True)
915 data = {'tenants': tenants}
tiernof7aa8c42016-09-06 16:43:04 +0200916 return format_out(data)
mirabalfbfb7972017-02-27 17:36:17 +0100917 except ovim.ovimException as e:
918 my.logger.error(str(e), exc_info=True)
919 bottle.abort(e.http_code, str(e))
920 except Exception as e:
921 my.logger.error(str(e), exc_info=True)
922 bottle.abort(HTTP_Bad_Request, str(e))
923
tiernof7aa8c42016-09-06 16:43:04 +0200924
925@bottle.route(url_base + '/tenants/<tenant_id>', method='GET')
926def http_get_tenant_id(tenant_id):
mirabalfbfb7972017-02-27 17:36:17 +0100927 """
928 Get tenant from DB by id
929 :param tenant_id: tenant id
930 :return:
931 """
932 my = config_dic['http_threads'][threading.current_thread().name]
933
934 try:
935 tenant = my.ovim.show_tenant_id(tenant_id)
936 delete_nulls(tenant)
937 change_keys_http2db(tenant, http2db_tenant, reverse=True)
938 data = {'tenant': tenant}
tiernof7aa8c42016-09-06 16:43:04 +0200939 return format_out(data)
mirabalfbfb7972017-02-27 17:36:17 +0100940 except ovim.ovimException as e:
941 my.logger.error(str(e), exc_info=True)
942 bottle.abort(e.http_code, str(e))
943 except Exception as e:
944 my.logger.error(str(e), exc_info=True)
945 bottle.abort(HTTP_Bad_Request, str(e))
tiernof7aa8c42016-09-06 16:43:04 +0200946
947
948@bottle.route(url_base + '/tenants', method='POST')
949def http_post_tenants():
mirabalfbfb7972017-02-27 17:36:17 +0100950 """
951 Insert a tenant into the database.
952 :return:
953 """
954 my = config_dic['http_threads'][threading.current_thread().name]
tiernof7aa8c42016-09-06 16:43:04 +0200955
mirabalfbfb7972017-02-27 17:36:17 +0100956 try:
957 http_content = format_in(tenant_new_schema)
958 r = remove_extra_items(http_content, tenant_new_schema)
959 if r is not None:
960 my.logger.error("http_post_tenants: Warning: remove extra items " + str(r), exc_info=True)
961 # insert in data base
962 tenant_id = my.ovim.new_tentant(http_content['tenant'])
963 tenant = my.ovim.show_tenant_id(tenant_id)
964 change_keys_http2db(tenant, http2db_tenant, reverse=True)
965 delete_nulls(tenant)
966 data = {'tenant': tenant}
967 return format_out(data)
968 except ovim.ovimException as e:
969 my.logger.error(str(e), exc_info=True)
970 bottle.abort(e.http_code, str(e))
971 except Exception as e:
972 my.logger.error(str(e), exc_info=True)
973 bottle.abort(HTTP_Bad_Request, str(e))
974
tiernof7aa8c42016-09-06 16:43:04 +0200975
976@bottle.route(url_base + '/tenants/<tenant_id>', method='PUT')
977def http_put_tenant_id(tenant_id):
mirabalfbfb7972017-02-27 17:36:17 +0100978 """
979 Update a tenantinto DB.
980 :param tenant_id: tentant id
981 :return:
982 """
tiernof7aa8c42016-09-06 16:43:04 +0200983
mirabalfbfb7972017-02-27 17:36:17 +0100984 my = config_dic['http_threads'][threading.current_thread().name]
985 try:
986 # parse input data
987 http_content = format_in(tenant_edit_schema)
988 r = remove_extra_items(http_content, tenant_edit_schema)
989 if r is not None:
990 print "http_put_tenant_id: Warning: remove extra items ", r
991 change_keys_http2db(http_content['tenant'], http2db_tenant)
992 # insert in data base
993 my.ovim.edit_tenant(tenant_id, http_content['tenant'])
994 tenant = my.ovim.show_tenant_id(tenant_id)
995 change_keys_http2db(tenant, http2db_tenant, reverse=True)
996 data = {'tenant': tenant}
997 return format_out(data)
998 except ovim.ovimException as e:
999 my.logger.error(str(e), exc_info=True)
1000 bottle.abort(e.http_code, str(e))
1001 except Exception as e:
1002 my.logger.error(str(e), exc_info=True)
1003 bottle.abort(HTTP_Bad_Request, str(e))
1004
tiernof7aa8c42016-09-06 16:43:04 +02001005
1006@bottle.route(url_base + '/tenants/<tenant_id>', method='DELETE')
1007def http_delete_tenant_id(tenant_id):
mirabalfbfb7972017-02-27 17:36:17 +01001008 """
1009 Delete a tenant from the database.
1010 :param tenant_id: tenant id
1011 :return:
1012 """
1013 my = config_dic['http_threads'][threading.current_thread().name]
tiernof7aa8c42016-09-06 16:43:04 +02001014
mirabalfbfb7972017-02-27 17:36:17 +01001015 try:
1016 content = my.ovim.delete_tentant(tenant_id)
1017 data = {'result': content}
1018 return format_out(data)
1019 except ovim.ovimException as e:
1020 my.logger.error(str(e), exc_info=True)
1021 bottle.abort(e.http_code, str(e))
1022 except Exception as e:
1023 my.logger.error(str(e), exc_info=True)
1024 bottle.abort(HTTP_Bad_Request, str(e))
tiernof7aa8c42016-09-06 16:43:04 +02001025#
1026# FLAVORS
1027#
1028
mirabalfbfb7972017-02-27 17:36:17 +01001029
tiernof7aa8c42016-09-06 16:43:04 +02001030@bottle.route(url_base + '/<tenant_id>/flavors', method='GET')
1031def http_get_flavors(tenant_id):
1032 my = config_dic['http_threads'][ threading.current_thread().name ]
1033 #check valid tenant_id
1034 result,content = check_valid_tenant(my, tenant_id)
1035 if result != 0:
1036 bottle.abort(result, content)
1037 #obtain data
1038 select_,where_,limit_ = filter_query_string(bottle.request.query, http2db_flavor,
1039 ('id','name','description','public') )
1040 if tenant_id=='any':
1041 from_ ='flavors'
1042 else:
1043 from_ ='tenants_flavors inner join flavors on tenants_flavors.flavor_id=flavors.uuid'
1044 where_['tenant_id'] = tenant_id
1045 result, content = my.db.get_table(FROM=from_, SELECT=select_, WHERE=where_, LIMIT=limit_)
1046 if result < 0:
1047 print "http_get_flavors Error", content
1048 bottle.abort(-result, content)
1049 else:
1050 change_keys_http2db(content, http2db_flavor, reverse=True)
1051 for row in content:
1052 row['links']=[ {'href': "/".join( (my.url_preffix, tenant_id, 'flavors', str(row['id']) ) ), 'rel':'bookmark' } ]
1053 data={'flavors' : content}
1054 return format_out(data)
1055
1056@bottle.route(url_base + '/<tenant_id>/flavors/<flavor_id>', method='GET')
1057def http_get_flavor_id(tenant_id, flavor_id):
1058 my = config_dic['http_threads'][ threading.current_thread().name ]
1059 #check valid tenant_id
1060 result,content = check_valid_tenant(my, tenant_id)
1061 if result != 0:
1062 bottle.abort(result, content)
1063 #obtain data
1064 select_,where_,limit_ = filter_query_string(bottle.request.query, http2db_flavor,
1065 ('id','name','description','ram', 'vcpus', 'extended', 'disk', 'public') )
1066 if tenant_id=='any':
1067 from_ ='flavors'
1068 else:
1069 from_ ='tenants_flavors as tf inner join flavors as f on tf.flavor_id=f.uuid'
1070 where_['tenant_id'] = tenant_id
1071 where_['uuid'] = flavor_id
1072 result, content = my.db.get_table(SELECT=select_, FROM=from_, WHERE=where_, LIMIT=limit_)
1073
1074 if result < 0:
1075 print "http_get_flavor_id error %d %s" % (result, content)
1076 bottle.abort(-result, content)
1077 elif result==0:
1078 print "http_get_flavors_id flavor '%s' not found" % str(flavor_id)
1079 bottle.abort(HTTP_Not_Found, 'flavor %s not found' % flavor_id)
1080 else:
1081 change_keys_http2db(content, http2db_flavor, reverse=True)
1082 if 'extended' in content[0] and content[0]['extended'] is not None:
1083 extended = json.loads(content[0]['extended'])
1084 if 'devices' in extended:
1085 change_keys_http2db(extended['devices'], http2db_flavor, reverse=True)
1086 content[0]['extended']=extended
1087 convert_bandwidth(content[0], reverse=True)
1088 content[0]['links']=[ {'href': "/".join( (my.url_preffix, tenant_id, 'flavors', str(content[0]['id']) ) ), 'rel':'bookmark' } ]
1089 data={'flavor' : content[0]}
1090 #data['tenants_links'] = dict([('tenant', row['id']) for row in content])
1091 return format_out(data)
1092
1093
1094@bottle.route(url_base + '/<tenant_id>/flavors', method='POST')
1095def http_post_flavors(tenant_id):
1096 '''insert a flavor into the database, and attach to tenant.'''
1097 my = config_dic['http_threads'][ threading.current_thread().name ]
1098 #check valid tenant_id
1099 result,content = check_valid_tenant(my, tenant_id)
1100 if result != 0:
1101 bottle.abort(result, content)
1102 http_content = format_in( flavor_new_schema )
1103 r = remove_extra_items(http_content, flavor_new_schema)
1104 if r is not None: print "http_post_flavors: Warning: remove extra items ", r
1105 change_keys_http2db(http_content['flavor'], http2db_flavor)
1106 extended_dict = http_content['flavor'].pop('extended', None)
1107 if extended_dict is not None:
1108 result, content = check_extended(extended_dict)
1109 if result<0:
1110 print "http_post_flavors wrong input extended error %d %s" % (result, content)
1111 bottle.abort(-result, content)
1112 return
1113 convert_bandwidth(extended_dict)
1114 if 'devices' in extended_dict: change_keys_http2db(extended_dict['devices'], http2db_flavor)
1115 http_content['flavor']['extended'] = json.dumps(extended_dict)
1116 #insert in data base
1117 result, content = my.db.new_flavor(http_content['flavor'], tenant_id)
1118 if result >= 0:
1119 return http_get_flavor_id(tenant_id, content)
1120 else:
1121 print "http_psot_flavors error %d %s" % (result, content)
1122 bottle.abort(-result, content)
1123 return
1124
1125@bottle.route(url_base + '/<tenant_id>/flavors/<flavor_id>', method='DELETE')
1126def http_delete_flavor_id(tenant_id, flavor_id):
1127 '''Deletes the flavor_id of a tenant. IT removes from tenants_flavors table.'''
1128 my = config_dic['http_threads'][ threading.current_thread().name ]
1129 #check valid tenant_id
1130 result,content = check_valid_tenant(my, tenant_id)
1131 if result != 0:
1132 bottle.abort(result, content)
1133 return
1134 result, content = my.db.delete_image_flavor('flavor', flavor_id, tenant_id)
1135 if result == 0:
1136 bottle.abort(HTTP_Not_Found, content)
1137 elif result >0:
1138 data={'result' : content}
1139 return format_out(data)
1140 else:
1141 print "http_delete_flavor_id error",result, content
1142 bottle.abort(-result, content)
1143 return
1144
1145@bottle.route(url_base + '/<tenant_id>/flavors/<flavor_id>/<action>', method='POST')
1146def http_attach_detach_flavors(tenant_id, flavor_id, action):
1147 '''attach/detach an existing flavor in this tenant. That is insert/remove at tenants_flavors table.'''
1148 #TODO alf: not tested at all!!!
1149 my = config_dic['http_threads'][ threading.current_thread().name ]
1150 #check valid tenant_id
1151 result,content = check_valid_tenant(my, tenant_id)
1152 if result != 0:
1153 bottle.abort(result, content)
1154 if tenant_id=='any':
1155 bottle.abort(HTTP_Bad_Request, "Invalid tenant 'any' with this command")
1156 #check valid action
1157 if action!='attach' and action != 'detach':
1158 bottle.abort(HTTP_Method_Not_Allowed, "actions can be attach or detach")
1159 return
1160
1161 #Ensure that flavor exist
1162 from_ ='tenants_flavors as tf right join flavors as f on tf.flavor_id=f.uuid'
1163 where_={'uuid': flavor_id}
1164 result, content = my.db.get_table(SELECT=('public','tenant_id'), FROM=from_, WHERE=where_)
1165 if result==0:
1166 if action=='attach':
1167 text_error="Flavor '%s' not found" % flavor_id
1168 else:
1169 text_error="Flavor '%s' not found for tenant '%s'" % (flavor_id, tenant_id)
1170 bottle.abort(HTTP_Not_Found, text_error)
1171 return
1172 elif result>0:
1173 flavor=content[0]
1174 if action=='attach':
1175 if flavor['tenant_id']!=None:
1176 bottle.abort(HTTP_Conflict, "Flavor '%s' already attached to tenant '%s'" % (flavor_id, tenant_id))
1177 if flavor['public']=='no' and not my.admin:
1178 #allow only attaching public flavors
1179 bottle.abort(HTTP_Unauthorized, "Needed admin rights to attach a private flavor")
1180 return
1181 #insert in data base
1182 result, content = my.db.new_row('tenants_flavors', {'flavor_id':flavor_id, 'tenant_id': tenant_id})
1183 if result >= 0:
1184 return http_get_flavor_id(tenant_id, flavor_id)
1185 else: #detach
1186 if flavor['tenant_id']==None:
1187 bottle.abort(HTTP_Not_Found, "Flavor '%s' not attached to tenant '%s'" % (flavor_id, tenant_id))
1188 result, content = my.db.delete_row_by_dict(FROM='tenants_flavors', WHERE={'flavor_id':flavor_id, 'tenant_id':tenant_id})
1189 if result>=0:
1190 if flavor['public']=='no':
1191 #try to delete the flavor completely to avoid orphan flavors, IGNORE error
1192 my.db.delete_row_by_dict(FROM='flavors', WHERE={'uuid':flavor_id})
1193 data={'result' : "flavor detached"}
1194 return format_out(data)
1195
1196 #if get here is because an error
1197 print "http_attach_detach_flavors error %d %s" % (result, content)
1198 bottle.abort(-result, content)
1199 return
1200
1201@bottle.route(url_base + '/<tenant_id>/flavors/<flavor_id>', method='PUT')
1202def http_put_flavor_id(tenant_id, flavor_id):
1203 '''update a flavor_id into the database.'''
1204 my = config_dic['http_threads'][ threading.current_thread().name ]
1205 #check valid tenant_id
1206 result,content = check_valid_tenant(my, tenant_id)
1207 if result != 0:
1208 bottle.abort(result, content)
1209 #parse input data
1210 http_content = format_in( flavor_update_schema )
1211 r = remove_extra_items(http_content, flavor_update_schema)
1212 if r is not None: print "http_put_flavor_id: Warning: remove extra items ", r
1213 change_keys_http2db(http_content['flavor'], http2db_flavor)
1214 extended_dict = http_content['flavor'].pop('extended', None)
1215 if extended_dict is not None:
1216 result, content = check_extended(extended_dict)
1217 if result<0:
1218 print "http_put_flavor_id wrong input extended error %d %s" % (result, content)
1219 bottle.abort(-result, content)
1220 return
1221 convert_bandwidth(extended_dict)
1222 if 'devices' in extended_dict: change_keys_http2db(extended_dict['devices'], http2db_flavor)
1223 http_content['flavor']['extended'] = json.dumps(extended_dict)
1224 #Ensure that flavor exist
1225 where_={'uuid': flavor_id}
1226 if tenant_id=='any':
1227 from_ ='flavors'
1228 else:
1229 from_ ='tenants_flavors as ti inner join flavors as i on ti.flavor_id=i.uuid'
1230 where_['tenant_id'] = tenant_id
1231 result, content = my.db.get_table(SELECT=('public',), FROM=from_, WHERE=where_)
1232 if result==0:
1233 text_error="Flavor '%s' not found" % flavor_id
1234 if tenant_id!='any':
1235 text_error +=" for tenant '%s'" % flavor_id
1236 bottle.abort(HTTP_Not_Found, text_error)
1237 return
1238 elif result>0:
1239 if content[0]['public']=='yes' and not my.admin:
1240 #allow only modifications over private flavors
1241 bottle.abort(HTTP_Unauthorized, "Needed admin rights to edit a public flavor")
1242 return
1243 #insert in data base
1244 result, content = my.db.update_rows('flavors', http_content['flavor'], {'uuid': flavor_id})
1245
1246 if result < 0:
1247 print "http_put_flavor_id error %d %s" % (result, content)
1248 bottle.abort(-result, content)
1249 return
1250 else:
1251 return http_get_flavor_id(tenant_id, flavor_id)
1252
1253
1254
1255#
1256# IMAGES
1257#
1258
1259@bottle.route(url_base + '/<tenant_id>/images', method='GET')
1260def http_get_images(tenant_id):
1261 my = config_dic['http_threads'][ threading.current_thread().name ]
1262 #check valid tenant_id
1263 result,content = check_valid_tenant(my, tenant_id)
1264 if result != 0:
1265 bottle.abort(result, content)
1266 #obtain data
1267 select_,where_,limit_ = filter_query_string(bottle.request.query, http2db_image,
garciadeblas922ad6f2017-01-10 13:10:30 +01001268 ('id','name','checksum','description','path','public') )
tiernof7aa8c42016-09-06 16:43:04 +02001269 if tenant_id=='any':
1270 from_ ='images'
tierno69a57392017-01-07 01:06:21 +01001271 where_or_ = None
tiernof7aa8c42016-09-06 16:43:04 +02001272 else:
tierno69a57392017-01-07 01:06:21 +01001273 from_ ='tenants_images right join images on tenants_images.image_id=images.uuid'
1274 where_or_ = {'tenant_id': tenant_id, 'public': 'yes'}
tierno12cb90b2017-01-09 12:46:17 +01001275 result, content = my.db.get_table(SELECT=select_, DISTINCT=True, FROM=from_, WHERE=where_, WHERE_OR=where_or_, WHERE_AND_OR="AND", LIMIT=limit_)
tiernof7aa8c42016-09-06 16:43:04 +02001276 if result < 0:
1277 print "http_get_images Error", content
1278 bottle.abort(-result, content)
1279 else:
1280 change_keys_http2db(content, http2db_image, reverse=True)
1281 #for row in content: row['links']=[ {'href': "/".join( (my.url_preffix, tenant_id, 'images', str(row['id']) ) ), 'rel':'bookmark' } ]
1282 data={'images' : content}
1283 return format_out(data)
1284
1285@bottle.route(url_base + '/<tenant_id>/images/<image_id>', method='GET')
1286def http_get_image_id(tenant_id, image_id):
1287 my = config_dic['http_threads'][ threading.current_thread().name ]
1288 #check valid tenant_id
1289 result,content = check_valid_tenant(my, tenant_id)
1290 if result != 0:
1291 bottle.abort(result, content)
1292 #obtain data
1293 select_,where_,limit_ = filter_query_string(bottle.request.query, http2db_image,
garciadeblas922ad6f2017-01-10 13:10:30 +01001294 ('id','name','checksum','description','progress', 'status','path', 'created', 'updated','public') )
tiernof7aa8c42016-09-06 16:43:04 +02001295 if tenant_id=='any':
1296 from_ ='images'
tierno69a57392017-01-07 01:06:21 +01001297 where_or_ = None
tiernof7aa8c42016-09-06 16:43:04 +02001298 else:
tierno69a57392017-01-07 01:06:21 +01001299 from_ ='tenants_images as ti right join images as i on ti.image_id=i.uuid'
1300 where_or_ = {'tenant_id': tenant_id, 'public': "yes"}
tiernof7aa8c42016-09-06 16:43:04 +02001301 where_['uuid'] = image_id
tierno12cb90b2017-01-09 12:46:17 +01001302 result, content = my.db.get_table(SELECT=select_, DISTINCT=True, FROM=from_, WHERE=where_, WHERE_OR=where_or_, WHERE_AND_OR="AND", LIMIT=limit_)
tiernof7aa8c42016-09-06 16:43:04 +02001303
1304 if result < 0:
1305 print "http_get_images error %d %s" % (result, content)
1306 bottle.abort(-result, content)
1307 elif result==0:
1308 print "http_get_images image '%s' not found" % str(image_id)
1309 bottle.abort(HTTP_Not_Found, 'image %s not found' % image_id)
1310 else:
1311 convert_datetime2str(content)
1312 change_keys_http2db(content, http2db_image, reverse=True)
1313 if 'metadata' in content[0] and content[0]['metadata'] is not None:
1314 metadata = json.loads(content[0]['metadata'])
1315 content[0]['metadata']=metadata
1316 content[0]['links']=[ {'href': "/".join( (my.url_preffix, tenant_id, 'images', str(content[0]['id']) ) ), 'rel':'bookmark' } ]
1317 data={'image' : content[0]}
1318 #data['tenants_links'] = dict([('tenant', row['id']) for row in content])
1319 return format_out(data)
1320
1321@bottle.route(url_base + '/<tenant_id>/images', method='POST')
1322def http_post_images(tenant_id):
1323 '''insert a image into the database, and attach to tenant.'''
1324 my = config_dic['http_threads'][ threading.current_thread().name ]
1325 #check valid tenant_id
1326 result,content = check_valid_tenant(my, tenant_id)
1327 if result != 0:
1328 bottle.abort(result, content)
1329 http_content = format_in(image_new_schema)
1330 r = remove_extra_items(http_content, image_new_schema)
1331 if r is not None: print "http_post_images: Warning: remove extra items ", r
1332 change_keys_http2db(http_content['image'], http2db_image)
1333 metadata_dict = http_content['image'].pop('metadata', None)
1334 if metadata_dict is not None:
1335 http_content['image']['metadata'] = json.dumps(metadata_dict)
garciadeblas24595392016-09-30 17:49:57 +02001336 #calculate checksum
garciadeblas24595392016-09-30 17:49:57 +02001337 try:
1338 image_file = http_content['image'].get('path',None)
garciadeblas922ad6f2017-01-10 13:10:30 +01001339 parsed_url = urlparse.urlparse(image_file)
1340 if parsed_url.scheme == "" and parsed_url.netloc == "":
1341 # The path is a local file
1342 if os.path.exists(image_file):
1343 http_content['image']['checksum'] = md5(image_file)
garciadeblas24595392016-09-30 17:49:57 +02001344 else:
garciadeblas922ad6f2017-01-10 13:10:30 +01001345 # The path is a URL. Code should be added to download the image and calculate the checksum
1346 #http_content['image']['checksum'] = md5(downloaded_image)
1347 pass
1348 # Finally, only if we are in test mode and checksum has not been calculated, we calculate it from the path
1349 host_test_mode = True if config_dic['mode']=='test' or config_dic['mode']=="OF only" else False
1350 if host_test_mode:
1351 if 'checksum' not in http_content['image']:
1352 http_content['image']['checksum'] = md5_string(image_file)
1353 else:
1354 # At this point, if the path is a local file and no chechsum has been obtained yet, an error is sent back.
1355 # If it is a URL, no error is sent. Checksum will be an empty string
1356 if parsed_url.scheme == "" and parsed_url.netloc == "" and 'checksum' not in http_content['image']:
garciadeblas24595392016-09-30 17:49:57 +02001357 content = "Image file not found"
1358 print "http_post_images error: %d %s" % (HTTP_Bad_Request, content)
1359 bottle.abort(HTTP_Bad_Request, content)
1360 except Exception as e:
1361 print "ERROR. Unexpected exception: %s" % (str(e))
1362 bottle.abort(HTTP_Internal_Server_Error, type(e).__name__ + ": " + str(e))
tiernof7aa8c42016-09-06 16:43:04 +02001363 #insert in data base
1364 result, content = my.db.new_image(http_content['image'], tenant_id)
1365 if result >= 0:
1366 return http_get_image_id(tenant_id, content)
1367 else:
1368 print "http_post_images error %d %s" % (result, content)
1369 bottle.abort(-result, content)
1370 return
1371
1372@bottle.route(url_base + '/<tenant_id>/images/<image_id>', method='DELETE')
1373def http_delete_image_id(tenant_id, image_id):
1374 '''Deletes the image_id of a tenant. IT removes from tenants_images table.'''
1375 my = config_dic['http_threads'][ threading.current_thread().name ]
1376 #check valid tenant_id
1377 result,content = check_valid_tenant(my, tenant_id)
1378 if result != 0:
1379 bottle.abort(result, content)
1380 result, content = my.db.delete_image_flavor('image', image_id, tenant_id)
1381 if result == 0:
1382 bottle.abort(HTTP_Not_Found, content)
1383 elif result >0:
1384 data={'result' : content}
1385 return format_out(data)
1386 else:
1387 print "http_delete_image_id error",result, content
1388 bottle.abort(-result, content)
1389 return
1390
1391@bottle.route(url_base + '/<tenant_id>/images/<image_id>/<action>', method='POST')
1392def http_attach_detach_images(tenant_id, image_id, action):
1393 '''attach/detach an existing image in this tenant. That is insert/remove at tenants_images table.'''
1394 #TODO alf: not tested at all!!!
1395 my = config_dic['http_threads'][ threading.current_thread().name ]
1396 #check valid tenant_id
1397 result,content = check_valid_tenant(my, tenant_id)
1398 if result != 0:
1399 bottle.abort(result, content)
1400 if tenant_id=='any':
1401 bottle.abort(HTTP_Bad_Request, "Invalid tenant 'any' with this command")
1402 #check valid action
1403 if action!='attach' and action != 'detach':
1404 bottle.abort(HTTP_Method_Not_Allowed, "actions can be attach or detach")
1405 return
1406
1407 #Ensure that image exist
1408 from_ ='tenants_images as ti right join images as i on ti.image_id=i.uuid'
1409 where_={'uuid': image_id}
1410 result, content = my.db.get_table(SELECT=('public','tenant_id'), FROM=from_, WHERE=where_)
1411 if result==0:
1412 if action=='attach':
1413 text_error="Image '%s' not found" % image_id
1414 else:
1415 text_error="Image '%s' not found for tenant '%s'" % (image_id, tenant_id)
1416 bottle.abort(HTTP_Not_Found, text_error)
1417 return
1418 elif result>0:
1419 image=content[0]
1420 if action=='attach':
1421 if image['tenant_id']!=None:
1422 bottle.abort(HTTP_Conflict, "Image '%s' already attached to tenant '%s'" % (image_id, tenant_id))
1423 if image['public']=='no' and not my.admin:
1424 #allow only attaching public images
1425 bottle.abort(HTTP_Unauthorized, "Needed admin rights to attach a private image")
1426 return
1427 #insert in data base
1428 result, content = my.db.new_row('tenants_images', {'image_id':image_id, 'tenant_id': tenant_id})
1429 if result >= 0:
1430 return http_get_image_id(tenant_id, image_id)
1431 else: #detach
1432 if image['tenant_id']==None:
1433 bottle.abort(HTTP_Not_Found, "Image '%s' not attached to tenant '%s'" % (image_id, tenant_id))
1434 result, content = my.db.delete_row_by_dict(FROM='tenants_images', WHERE={'image_id':image_id, 'tenant_id':tenant_id})
1435 if result>=0:
1436 if image['public']=='no':
1437 #try to delete the image completely to avoid orphan images, IGNORE error
1438 my.db.delete_row_by_dict(FROM='images', WHERE={'uuid':image_id})
1439 data={'result' : "image detached"}
1440 return format_out(data)
1441
1442 #if get here is because an error
1443 print "http_attach_detach_images error %d %s" % (result, content)
1444 bottle.abort(-result, content)
1445 return
1446
1447@bottle.route(url_base + '/<tenant_id>/images/<image_id>', method='PUT')
1448def http_put_image_id(tenant_id, image_id):
1449 '''update a image_id into the database.'''
1450 my = config_dic['http_threads'][ threading.current_thread().name ]
1451 #check valid tenant_id
1452 result,content = check_valid_tenant(my, tenant_id)
1453 if result != 0:
1454 bottle.abort(result, content)
1455 #parse input data
1456 http_content = format_in( image_update_schema )
1457 r = remove_extra_items(http_content, image_update_schema)
1458 if r is not None: print "http_put_image_id: Warning: remove extra items ", r
1459 change_keys_http2db(http_content['image'], http2db_image)
1460 metadata_dict = http_content['image'].pop('metadata', None)
1461 if metadata_dict is not None:
1462 http_content['image']['metadata'] = json.dumps(metadata_dict)
1463 #Ensure that image exist
1464 where_={'uuid': image_id}
1465 if tenant_id=='any':
1466 from_ ='images'
tiernodf24c742017-01-09 14:38:50 +00001467 where_or_ = None
tiernof7aa8c42016-09-06 16:43:04 +02001468 else:
tiernodf24c742017-01-09 14:38:50 +00001469 from_ ='tenants_images as ti right join images as i on ti.image_id=i.uuid'
1470 where_or_ = {'tenant_id': tenant_id, 'public': 'yes'}
1471 result, content = my.db.get_table(SELECT=('public',), DISTINCT=True, FROM=from_, WHERE=where_, WHERE_OR=where_or_, WHERE_AND_OR="AND")
tiernof7aa8c42016-09-06 16:43:04 +02001472 if result==0:
1473 text_error="Image '%s' not found" % image_id
1474 if tenant_id!='any':
1475 text_error +=" for tenant '%s'" % image_id
1476 bottle.abort(HTTP_Not_Found, text_error)
1477 return
1478 elif result>0:
1479 if content[0]['public']=='yes' and not my.admin:
1480 #allow only modifications over private images
1481 bottle.abort(HTTP_Unauthorized, "Needed admin rights to edit a public image")
1482 return
1483 #insert in data base
1484 result, content = my.db.update_rows('images', http_content['image'], {'uuid': image_id})
1485
1486 if result < 0:
1487 print "http_put_image_id error %d %s" % (result, content)
1488 bottle.abort(-result, content)
1489 return
1490 else:
1491 return http_get_image_id(tenant_id, image_id)
1492
1493
1494#
1495# SERVERS
1496#
1497
1498@bottle.route(url_base + '/<tenant_id>/servers', method='GET')
1499def http_get_servers(tenant_id):
1500 my = config_dic['http_threads'][ threading.current_thread().name ]
1501 result,content = check_valid_tenant(my, tenant_id)
1502 if result != 0:
1503 bottle.abort(result, content)
1504 return
1505 #obtain data
1506 select_,where_,limit_ = filter_query_string(bottle.request.query, http2db_server,
1507 ('id','name','description','hostId','imageRef','flavorRef','status', 'tenant_id') )
1508 if tenant_id!='any':
1509 where_['tenant_id'] = tenant_id
1510 result, content = my.db.get_table(SELECT=select_, FROM='instances', WHERE=where_, LIMIT=limit_)
1511 if result < 0:
1512 print "http_get_servers Error", content
1513 bottle.abort(-result, content)
1514 else:
1515 change_keys_http2db(content, http2db_server, reverse=True)
1516 for row in content:
1517 tenant_id = row.pop('tenant_id')
1518 row['links']=[ {'href': "/".join( (my.url_preffix, tenant_id, 'servers', str(row['id']) ) ), 'rel':'bookmark' } ]
1519 data={'servers' : content}
1520 return format_out(data)
1521
1522@bottle.route(url_base + '/<tenant_id>/servers/<server_id>', method='GET')
1523def http_get_server_id(tenant_id, server_id):
1524 my = config_dic['http_threads'][ threading.current_thread().name ]
1525 #check valid tenant_id
1526 result,content = check_valid_tenant(my, tenant_id)
1527 if result != 0:
1528 bottle.abort(result, content)
1529 return
1530 #obtain data
1531 result, content = my.db.get_instance(server_id)
1532 if result == 0:
1533 bottle.abort(HTTP_Not_Found, content)
1534 elif result >0:
1535 #change image/flavor-id to id and link
1536 convert_bandwidth(content, reverse=True)
1537 convert_datetime2str(content)
1538 if content["ram"]==0 : del content["ram"]
1539 if content["vcpus"]==0 : del content["vcpus"]
1540 if 'flavor_id' in content:
1541 if content['flavor_id'] is not None:
1542 content['flavor'] = {'id':content['flavor_id'],
1543 'links':[{'href': "/".join( (my.url_preffix, content['tenant_id'], 'flavors', str(content['flavor_id']) ) ), 'rel':'bookmark'}]
1544 }
1545 del content['flavor_id']
1546 if 'image_id' in content:
1547 if content['image_id'] is not None:
1548 content['image'] = {'id':content['image_id'],
1549 'links':[{'href': "/".join( (my.url_preffix, content['tenant_id'], 'images', str(content['image_id']) ) ), 'rel':'bookmark'}]
1550 }
1551 del content['image_id']
1552 change_keys_http2db(content, http2db_server, reverse=True)
1553 if 'extended' in content:
1554 if 'devices' in content['extended']: change_keys_http2db(content['extended']['devices'], http2db_server, reverse=True)
1555
1556 data={'server' : content}
1557 return format_out(data)
1558 else:
1559 bottle.abort(-result, content)
1560 return
1561
1562@bottle.route(url_base + '/<tenant_id>/servers', method='POST')
1563def http_post_server_id(tenant_id):
1564 '''deploys a new server'''
1565 my = config_dic['http_threads'][ threading.current_thread().name ]
1566 #check valid tenant_id
1567 result,content = check_valid_tenant(my, tenant_id)
1568 if result != 0:
1569 bottle.abort(result, content)
1570 return
1571 if tenant_id=='any':
1572 bottle.abort(HTTP_Bad_Request, "Invalid tenant 'any' with this command")
1573 #chek input
1574 http_content = format_in( server_new_schema )
1575 r = remove_extra_items(http_content, server_new_schema)
1576 if r is not None: print "http_post_serves: Warning: remove extra items ", r
1577 change_keys_http2db(http_content['server'], http2db_server)
1578 extended_dict = http_content['server'].get('extended', None)
1579 if extended_dict is not None:
1580 result, content = check_extended(extended_dict, True)
1581 if result<0:
1582 print "http_post_servers wrong input extended error %d %s" % (result, content)
1583 bottle.abort(-result, content)
1584 return
1585 convert_bandwidth(extended_dict)
1586 if 'devices' in extended_dict: change_keys_http2db(extended_dict['devices'], http2db_server)
1587
1588 server = http_content['server']
1589 server_start = server.get('start', 'yes')
1590 server['tenant_id'] = tenant_id
1591 #check flavor valid and take info
1592 result, content = my.db.get_table(FROM='tenants_flavors as tf join flavors as f on tf.flavor_id=f.uuid',
1593 SELECT=('ram','vcpus','extended'), WHERE={'uuid':server['flavor_id'], 'tenant_id':tenant_id})
1594 if result<=0:
1595 bottle.abort(HTTP_Not_Found, 'flavor_id %s not found' % server['flavor_id'])
1596 return
1597 server['flavor']=content[0]
1598 #check image valid and take info
tiernodf24c742017-01-09 14:38:50 +00001599 result, content = my.db.get_table(FROM='tenants_images as ti right join images as i on ti.image_id=i.uuid',
tiernoe8283ab2017-01-17 17:35:27 +00001600 SELECT=('path', 'metadata', 'image_id'),
1601 WHERE={'uuid':server['image_id'], "status":"ACTIVE"},
1602 WHERE_OR={'tenant_id':tenant_id, 'public': 'yes'},
1603 WHERE_AND_OR="AND",
1604 DISTINCT=True)
tiernof7aa8c42016-09-06 16:43:04 +02001605 if result<=0:
1606 bottle.abort(HTTP_Not_Found, 'image_id %s not found or not ACTIVE' % server['image_id'])
1607 return
tiernoe8283ab2017-01-17 17:35:27 +00001608 for image_dict in content:
1609 if image_dict.get("image_id"):
1610 break
1611 else:
1612 # insert in data base tenants_images
1613 r2, c2 = my.db.new_row('tenants_images', {'image_id': server['image_id'], 'tenant_id': tenant_id})
1614 if r2<=0:
1615 bottle.abort(HTTP_Not_Found, 'image_id %s cannot be used. Error %s' % (server['image_id'], c2))
1616 return
1617 server['image']={"path": content[0]["path"], "metadata": content[0]["metadata"]}
tiernof7aa8c42016-09-06 16:43:04 +02001618 if "hosts_id" in server:
1619 result, content = my.db.get_table(FROM='hosts', SELECT=('uuid',), WHERE={'uuid': server['host_id']})
1620 if result<=0:
1621 bottle.abort(HTTP_Not_Found, 'hostId %s not found' % server['host_id'])
1622 return
1623 #print json.dumps(server, indent=4)
1624
tiernoa62249e2018-09-17 17:57:30 +02001625 result, content = ht.create_server(server, config_dic['db'], config_dic['mode']=='normal')
tiernof7aa8c42016-09-06 16:43:04 +02001626
1627 if result >= 0:
1628 #Insert instance to database
1629 nets=[]
1630 print
1631 print "inserting at DB"
1632 print
1633 if server_start == 'no':
1634 content['status'] = 'INACTIVE'
Mirabale9317ff2017-01-18 16:10:58 +00001635 dhcp_nets_id = []
1636 for net in http_content['server']['networks']:
1637 if net['type'] == 'instance:ovs':
1638 dhcp_nets_id.append(get_network_id(net['net_id']))
1639
mirabal6878e3f2017-06-05 09:19:26 -05001640 ports_to_free = []
tiernof7aa8c42016-09-06 16:43:04 +02001641 new_instance_result, new_instance = my.db.new_instance(content, nets, ports_to_free)
1642 if new_instance_result < 0:
1643 print "Error http_post_servers() :", new_instance_result, new_instance
1644 bottle.abort(-new_instance_result, new_instance)
1645 return
1646 print
1647 print "inserted at DB"
1648 print
mirabal6878e3f2017-06-05 09:19:26 -05001649
1650
tiernof7aa8c42016-09-06 16:43:04 +02001651 for port in ports_to_free:
1652 r,c = config_dic['host_threads'][ server['host_id'] ].insert_task( 'restore-iface',*port )
1653 if r < 0:
1654 print ' http_post_servers ERROR RESTORE IFACE!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!' + c
mirabal7bbf50e2017-03-13 15:15:18 +01001655 # update nets
1656 for net_id in nets:
1657 try:
1658 my.ovim.net_update_ofc_thread(net_id)
1659 except ovim.ovimException as e:
tierno82232582017-03-15 18:09:16 +01001660 my.logger.error("http_post_servers, Error updating network with id '{}', '{}'".format(net_id, str(e)))
tiernof7aa8c42016-09-06 16:43:04 +02001661
mirabal7bbf50e2017-03-13 15:15:18 +01001662 # look for dhcp ip address
Mirabale9317ff2017-01-18 16:10:58 +00001663 r2, c2 = my.db.get_table(FROM="ports", SELECT=["mac", "ip_address", "net_id"], WHERE={"instance_id": new_instance})
tierno4a8f0df2017-01-19 18:59:59 +01001664 if r2 >0:
tiernof7aa8c42016-09-06 16:43:04 +02001665 for iface in c2:
tierno4a8f0df2017-01-19 18:59:59 +01001666 if config_dic.get("dhcp_server") and iface["net_id"] in config_dic["dhcp_nets"]:
tiernof7aa8c42016-09-06 16:43:04 +02001667 #print "dhcp insert add task"
1668 r,c = config_dic['dhcp_thread'].insert_task("add", iface["mac"])
1669 if r < 0:
tierno4a8f0df2017-01-19 18:59:59 +01001670 print ':http_post_servers ERROR UPDATING dhcp_server !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!' + c
1671
1672 #ensure compute contain the bridge for ovs networks:
mirabal8afc8a22017-05-19 12:11:27 +02001673 if iface.get("net_id"):
1674 server_net = get_network_id(iface['net_id'])
1675 if server_net["network"].get('provider:physical', "")[:3] == 'OVS':
1676 vlan = str(server_net['network']['provider:vlan'])
1677 dhcp_enable = bool(server_net['network']['enable_dhcp'])
mirabalefc4d972017-05-26 11:50:27 +02001678 vm_dhcp_ip = c2[0]["ip_address"]
1679 config_dic['host_threads'][server['host_id']].insert_task("create-ovs-bridge-port", vlan)
mirabale16a6362017-07-10 05:45:56 -05001680 dns = server_net['network'].get("dns")
1681 if dns:
1682 dns = yaml.safe_load(server_net['network'].get("dns"))
1683 routes = server_net['network'].get("routes")
1684 if routes:
1685 routes = yaml.safe_load(server_net['network'].get("routes"))
1686 links = server_net['network'].get("links")
1687 if links:
1688 links = yaml.safe_load(server_net['network'].get("links"))
mirabal8afc8a22017-05-19 12:11:27 +02001689 if dhcp_enable:
1690 dhcp_firt_ip = str(server_net['network']['dhcp_first_ip'])
1691 dhcp_last_ip = str(server_net['network']['dhcp_last_ip'])
1692 dhcp_cidr = str(server_net['network']['cidr'])
1693 gateway = str(server_net['network']['gateway_ip'])
mirabal6878e3f2017-06-05 09:19:26 -05001694
mirabal8afc8a22017-05-19 12:11:27 +02001695 http_controller = config_dic['http_threads'][threading.current_thread().name]
mirabal6878e3f2017-06-05 09:19:26 -05001696 http_controller.ovim.launch_dhcp_server(vlan, dhcp_firt_ip, dhcp_last_ip,
1697 dhcp_cidr, gateway, dns, routes)
1698 set_mac_dhcp(vm_dhcp_ip, vlan, dhcp_firt_ip, dhcp_last_ip, dhcp_cidr, c2[0]['mac'])
1699
1700 if links:
1701 http_controller.ovim.launch_link_bridge_to_ovs(vlan, gateway, dhcp_cidr, links, routes)
1702
Mirabale9317ff2017-01-18 16:10:58 +00001703
tierno4a8f0df2017-01-19 18:59:59 +01001704 #Start server
tiernof7aa8c42016-09-06 16:43:04 +02001705 server['uuid'] = new_instance
tierno4a8f0df2017-01-19 18:59:59 +01001706 server_start = server.get('start', 'yes')
1707
tiernof7aa8c42016-09-06 16:43:04 +02001708 if server_start != 'no':
Mirabale9317ff2017-01-18 16:10:58 +00001709 server['paused'] = True if server_start == 'paused' else False
tiernof7aa8c42016-09-06 16:43:04 +02001710 server['action'] = {"start":None}
1711 server['status'] = "CREATING"
1712 #Program task
1713 r,c = config_dic['host_threads'][ server['host_id'] ].insert_task( 'instance',server )
1714 if r<0:
1715 my.db.update_rows('instances', {'status':"ERROR"}, {'uuid':server['uuid'], 'last_error':c}, log=True)
1716
1717 return http_get_server_id(tenant_id, new_instance)
1718 else:
1719 bottle.abort(HTTP_Bad_Request, content)
1720 return
1721
mirabal6878e3f2017-06-05 09:19:26 -05001722
tiernof7aa8c42016-09-06 16:43:04 +02001723def http_server_action(server_id, tenant_id, action):
1724 '''Perform actions over a server as resume, reboot, terminate, ...'''
1725 my = config_dic['http_threads'][ threading.current_thread().name ]
1726 server={"uuid": server_id, "action":action}
1727 where={'uuid': server_id}
1728 if tenant_id!='any':
1729 where['tenant_id']= tenant_id
1730 result, content = my.db.get_table(FROM='instances', WHERE=where)
1731 if result == 0:
1732 bottle.abort(HTTP_Not_Found, "server %s not found" % server_id)
1733 return
1734 if result < 0:
1735 print "http_post_server_action error getting data %d %s" % (result, content)
1736 bottle.abort(HTTP_Internal_Server_Error, content)
1737 return
1738 server.update(content[0])
1739 tenant_id = server["tenant_id"]
1740
1741 #TODO check a right content
1742 new_status = None
1743 if 'terminate' in action:
1744 new_status='DELETING'
1745 elif server['status'] == 'ERROR': #or server['status'] == 'CREATING':
1746 if 'terminate' not in action and 'rebuild' not in action:
1747 bottle.abort(HTTP_Method_Not_Allowed, "Server is in ERROR status, must be rebuit or deleted ")
1748 return
1749# elif server['status'] == 'INACTIVE':
1750# if 'start' not in action and 'createImage' not in action:
1751# bottle.abort(HTTP_Method_Not_Allowed, "The only possible action over an instance in 'INACTIVE' status is 'start'")
1752# return
1753# if 'start' in action:
1754# new_status='CREATING'
1755# server['paused']='no'
1756# elif server['status'] == 'PAUSED':
1757# if 'resume' not in action:
1758# bottle.abort(HTTP_Method_Not_Allowed, "The only possible action over an instance in 'PAUSED' status is 'resume'")
1759# return
1760# elif server['status'] == 'ACTIVE':
1761# if 'pause' not in action and 'reboot'not in action and 'shutoff'not in action:
1762# bottle.abort(HTTP_Method_Not_Allowed, "The only possible action over an instance in 'ACTIVE' status is 'pause','reboot' or 'shutoff'")
1763# return
1764
1765 if 'start' in action or 'createImage' in action or 'rebuild' in action:
1766 #check image valid and take info
1767 image_id = server['image_id']
1768 if 'createImage' in action:
1769 if 'imageRef' in action['createImage']:
1770 image_id = action['createImage']['imageRef']
1771 elif 'disk' in action['createImage']:
1772 result, content = my.db.get_table(FROM='instance_devices',
1773 SELECT=('image_id','dev'), WHERE={'instance_id':server['uuid'],"type":"disk"})
1774 if result<=0:
1775 bottle.abort(HTTP_Not_Found, 'disk not found for server')
1776 return
1777 elif result>1:
1778 disk_id=None
1779 if action['createImage']['imageRef']['disk'] != None:
1780 for disk in content:
1781 if disk['dev'] == action['createImage']['imageRef']['disk']:
1782 disk_id = disk['image_id']
1783 break
1784 if disk_id == None:
1785 bottle.abort(HTTP_Not_Found, 'disk %s not found for server' % action['createImage']['imageRef']['disk'])
1786 return
1787 else:
1788 bottle.abort(HTTP_Not_Found, 'more than one disk found for server' )
1789 return
1790 image_id = disk_id
1791 else: #result==1
1792 image_id = content[0]['image_id']
1793
tiernodf24c742017-01-09 14:38:50 +00001794 result, content = my.db.get_table(FROM='tenants_images as ti right join images as i on ti.image_id=i.uuid',
1795 SELECT=('path','metadata'), WHERE={'uuid':image_id, "status":"ACTIVE"},
1796 WHERE_OR={'tenant_id':tenant_id, 'public': 'yes'}, WHERE_AND_OR="AND", DISTINCT=True)
tiernof7aa8c42016-09-06 16:43:04 +02001797 if result<=0:
1798 bottle.abort(HTTP_Not_Found, 'image_id %s not found or not ACTIVE' % image_id)
1799 return
1800 if content[0]['metadata'] is not None:
1801 try:
1802 metadata = json.loads(content[0]['metadata'])
1803 except:
1804 return -HTTP_Internal_Server_Error, "Can not decode image metadata"
1805 content[0]['metadata']=metadata
1806 else:
1807 content[0]['metadata'] = {}
1808 server['image']=content[0]
1809 if 'createImage' in action:
1810 action['createImage']['source'] = {'image_id': image_id, 'path': content[0]['path']}
1811 if 'createImage' in action:
1812 #Create an entry in Database for the new image
1813 new_image={'status':'BUILD', 'progress': 0 }
1814 new_image_metadata=content[0]
1815 if 'metadata' in server['image'] and server['image']['metadata'] != None:
1816 new_image_metadata.update(server['image']['metadata'])
1817 new_image_metadata = {"use_incremental":"no"}
1818 if 'metadata' in action['createImage']:
1819 new_image_metadata.update(action['createImage']['metadata'])
1820 new_image['metadata'] = json.dumps(new_image_metadata)
1821 new_image['name'] = action['createImage'].get('name', None)
1822 new_image['description'] = action['createImage'].get('description', None)
1823 new_image['uuid']=my.db.new_uuid()
1824 if 'path' in action['createImage']:
1825 new_image['path'] = action['createImage']['path']
1826 else:
1827 new_image['path']="/provisional/path/" + new_image['uuid']
1828 result, image_uuid = my.db.new_image(new_image, tenant_id)
1829 if result<=0:
1830 bottle.abort(HTTP_Bad_Request, 'Error: ' + image_uuid)
1831 return
1832 server['new_image'] = new_image
1833
1834
1835 #Program task
1836 r,c = config_dic['host_threads'][ server['host_id'] ].insert_task( 'instance',server )
1837 if r<0:
1838 print "Task queue full at host ", server['host_id']
1839 bottle.abort(HTTP_Request_Timeout, c)
1840 if 'createImage' in action and result >= 0:
1841 return http_get_image_id(tenant_id, image_uuid)
1842
1843 #Update DB only for CREATING or DELETING status
tierno82232582017-03-15 18:09:16 +01001844 data={'result' : 'deleting in process'}
1845 warn_text=""
tiernof7aa8c42016-09-06 16:43:04 +02001846 if new_status != None and new_status == 'DELETING':
1847 nets=[]
1848 ports_to_free=[]
Mirabal7256d6b2016-12-15 10:51:19 +00001849
1850 net_ovs_list = []
1851 #look for dhcp ip address
tiernof7aa8c42016-09-06 16:43:04 +02001852 r2, c2 = my.db.get_table(FROM="ports", SELECT=["mac", "net_id"], WHERE={"instance_id": server_id})
Mirabal7256d6b2016-12-15 10:51:19 +00001853 r, c = my.db.delete_instance(server_id, tenant_id, nets, ports_to_free, net_ovs_list, "requested by http")
tiernof7aa8c42016-09-06 16:43:04 +02001854 for port in ports_to_free:
1855 r1,c1 = config_dic['host_threads'][ server['host_id'] ].insert_task( 'restore-iface',*port )
1856 if r1 < 0:
tierno82232582017-03-15 18:09:16 +01001857 my.logger.error("http_post_server_action server deletion ERROR at resore-iface!!!! " + c1)
1858 warn_text += "; Error iface '{}' cannot be restored '{}'".format(str(port), str(e))
mirabal7bbf50e2017-03-13 15:15:18 +01001859 for net_id in nets:
1860 try:
1861 my.ovim.net_update_ofc_thread(net_id)
1862 except ovim.ovimException as e:
tierno82232582017-03-15 18:09:16 +01001863 my.logger.error("http_server_action, Error updating network with id '{}', '{}'".format(net_id, str(e)))
1864 warn_text += "; Error openflow rules of network '{}' cannot be restore '{}'".format(net_id, str (e))
mirabal7bbf50e2017-03-13 15:15:18 +01001865
1866 # look for dhcp ip address
tiernof7aa8c42016-09-06 16:43:04 +02001867 if r2 >0 and config_dic.get("dhcp_server"):
1868 for iface in c2:
1869 if iface["net_id"] in config_dic["dhcp_nets"]:
1870 r,c = config_dic['dhcp_thread'].insert_task("del", iface["mac"])
1871 #print "dhcp insert del task"
1872 if r < 0:
1873 print ':http_post_servers ERROR UPDATING dhcp_server !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!' + c
mirabal6878e3f2017-06-05 09:19:26 -05001874 # delete ovs-port and linux bridge, contains a list of tuple (net_id,vlan, vm_ip, mac)
1875
tierno181f1752017-02-01 16:28:11 +01001876 for net in net_ovs_list:
Mirabale9317ff2017-01-18 16:10:58 +00001877 mac = str(net[3])
1878 vm_ip = str(net[2])
tierno181f1752017-02-01 16:28:11 +01001879 vlan = str(net[1])
1880 net_id = net[0]
mirabal6878e3f2017-06-05 09:19:26 -05001881
Mirabale9317ff2017-01-18 16:10:58 +00001882 delete_dhcp_ovs_bridge(vlan, net_id)
1883 delete_mac_dhcp(vm_ip, vlan, mac)
mirabal6878e3f2017-06-05 09:19:26 -05001884
1885 net_data = my.ovim.show_network(net_id)
1886 if net_data.get('links'):
1887 links = yaml.load(net_data.get('links'))
1888 my.ovim.delete_link_bridge_to_ovs(vlan, links)
1889
tierno181f1752017-02-01 16:28:11 +01001890 config_dic['host_threads'][server['host_id']].insert_task('del-ovs-port', vlan, net_id)
tiernoe0c28c12017-05-04 18:44:40 +02001891 if warn_text:
1892 data["result"] += warn_text
1893 return format_out(data)
tiernof7aa8c42016-09-06 16:43:04 +02001894
1895
1896
1897@bottle.route(url_base + '/<tenant_id>/servers/<server_id>', method='DELETE')
1898def http_delete_server_id(tenant_id, server_id):
1899 '''delete a server'''
1900 my = config_dic['http_threads'][ threading.current_thread().name ]
1901 #check valid tenant_id
1902 result,content = check_valid_tenant(my, tenant_id)
1903 if result != 0:
1904 bottle.abort(result, content)
1905 return
1906
1907 return http_server_action(server_id, tenant_id, {"terminate":None} )
1908
1909
1910@bottle.route(url_base + '/<tenant_id>/servers/<server_id>/action', method='POST')
1911def http_post_server_action(tenant_id, server_id):
1912 '''take an action over a server'''
1913 my = config_dic['http_threads'][ threading.current_thread().name ]
1914 #check valid tenant_id
1915 result,content = check_valid_tenant(my, tenant_id)
1916 if result != 0:
1917 bottle.abort(result, content)
1918 return
1919 http_content = format_in( server_action_schema )
1920 #r = remove_extra_items(http_content, server_action_schema)
1921 #if r is not None: print "http_post_server_action: Warning: remove extra items ", r
1922
1923 return http_server_action(server_id, tenant_id, http_content)
1924
1925#
1926# NETWORKS
1927#
1928
1929
1930@bottle.route(url_base + '/networks', method='GET')
1931def http_get_networks():
mirabale9f6f1a2017-02-16 17:57:35 +01001932 """
1933 Get all networks available
1934 :return:
1935 """
1936 my = config_dic['http_threads'][threading.current_thread().name]
1937
mirabal65ba8f82017-02-15 12:36:33 +01001938 try:
mirabal65ba8f82017-02-15 12:36:33 +01001939 # obtain data
1940 select_, where_, limit_ = filter_query_string(bottle.request.query, http2db_network,
1941 ('id', 'name', 'tenant_id', 'type',
1942 'shared', 'provider:vlan', 'status', 'last_error',
1943 'admin_state_up', 'provider:physical'))
1944 if "tenant_id" in where_:
1945 del where_["tenant_id"]
mirabale9f6f1a2017-02-16 17:57:35 +01001946
mirabal65ba8f82017-02-15 12:36:33 +01001947 content = my.ovim.get_networks(select_, where_, limit_)
1948
1949 delete_nulls(content)
1950 change_keys_http2db(content, http2db_network, reverse=True)
1951 data = {'networks': content}
tiernof7aa8c42016-09-06 16:43:04 +02001952 return format_out(data)
1953
mirabal65ba8f82017-02-15 12:36:33 +01001954 except ovim.ovimException as e:
1955 my.logger.error(str(e), exc_info=True)
1956 bottle.abort(e.http_code, str(e))
1957 except Exception as e:
1958 my.logger.error(str(e), exc_info=True)
1959 bottle.abort(HTTP_Bad_Request, str(e))
1960
1961
tiernof7aa8c42016-09-06 16:43:04 +02001962@bottle.route(url_base + '/networks/<network_id>', method='GET')
1963def http_get_network_id(network_id):
mirabale9f6f1a2017-02-16 17:57:35 +01001964 """
1965 Get a network data by id
1966 :param network_id:
1967 :return:
1968 """
1969 data = get_network_id(network_id)
1970 return format_out(data)
Mirabal7256d6b2016-12-15 10:51:19 +00001971
tiernof7aa8c42016-09-06 16:43:04 +02001972
mirabal65ba8f82017-02-15 12:36:33 +01001973def get_network_id(network_id):
mirabale9f6f1a2017-02-16 17:57:35 +01001974 """
1975 Get network from DB by id
1976 :param network_id: network Id
1977 :return:
1978 """
1979 my = config_dic['http_threads'][threading.current_thread().name]
1980
mirabal65ba8f82017-02-15 12:36:33 +01001981 try:
mirabal65ba8f82017-02-15 12:36:33 +01001982 # obtain data
1983 where_ = bottle.request.query
mirabale9f6f1a2017-02-16 17:57:35 +01001984 content = my.ovim.show_network(network_id, where_)
mirabal65ba8f82017-02-15 12:36:33 +01001985
mirabal65ba8f82017-02-15 12:36:33 +01001986 change_keys_http2db(content, http2db_network, reverse=True)
mirabale9f6f1a2017-02-16 17:57:35 +01001987 delete_nulls(content)
1988 data = {'network': content}
Mirabal7256d6b2016-12-15 10:51:19 +00001989 return data
mirabal65ba8f82017-02-15 12:36:33 +01001990 except ovim.ovimException as e:
1991 my.logger.error(str(e), exc_info=True)
1992 bottle.abort(e.http_code, str(e))
1993 except Exception as e:
1994 my.logger.error(str(e), exc_info=True)
1995 bottle.abort(HTTP_Bad_Request, str(e))
1996
tiernof7aa8c42016-09-06 16:43:04 +02001997
1998@bottle.route(url_base + '/networks', method='POST')
1999def http_post_networks():
Mirabale9317ff2017-01-18 16:10:58 +00002000 """
mirabal65ba8f82017-02-15 12:36:33 +01002001 Insert a network into the database.
Mirabale9317ff2017-01-18 16:10:58 +00002002 :return:
2003 """
mirabale9f6f1a2017-02-16 17:57:35 +01002004 my = config_dic['http_threads'][threading.current_thread().name]
2005
mirabal65ba8f82017-02-15 12:36:33 +01002006 try:
mirabal65ba8f82017-02-15 12:36:33 +01002007 # parse input data
mirabal6878e3f2017-06-05 09:19:26 -05002008 http_content = format_in(network_new_schema)
mirabal65ba8f82017-02-15 12:36:33 +01002009 r = remove_extra_items(http_content, network_new_schema)
2010 if r is not None:
2011 print "http_post_networks: Warning: remove extra items ", r
2012 change_keys_http2db(http_content['network'], http2db_network)
2013 network = http_content['network']
mirabale9f6f1a2017-02-16 17:57:35 +01002014 content = my.ovim.new_network(network)
2015 return format_out(get_network_id(content))
mirabal65ba8f82017-02-15 12:36:33 +01002016 except ovim.ovimException as e:
2017 my.logger.error(str(e), exc_info=True)
2018 bottle.abort(e.http_code, str(e))
2019 except Exception as e:
2020 my.logger.error(str(e), exc_info=True)
2021 bottle.abort(HTTP_Bad_Request, str(e))
mirabalb716ac52017-02-10 14:47:53 +01002022
Mirabale9317ff2017-01-18 16:10:58 +00002023
tiernof7aa8c42016-09-06 16:43:04 +02002024@bottle.route(url_base + '/networks/<network_id>', method='PUT')
2025def http_put_network_id(network_id):
mirabale9f6f1a2017-02-16 17:57:35 +01002026 """
2027 Update a network_id into DB.
2028 :param network_id: network id
2029 :return:
2030 """
2031 my = config_dic['http_threads'][threading.current_thread().name]
2032
2033 try:
2034 # parse input data
2035 http_content = format_in(network_update_schema)
2036 change_keys_http2db(http_content['network'], http2db_network)
2037 network = http_content['network']
mirabal7bbf50e2017-03-13 15:15:18 +01002038 return format_out(my.ovim.edit_network(network_id, network))
tiernof7aa8c42016-09-06 16:43:04 +02002039
mirabale9f6f1a2017-02-16 17:57:35 +01002040 except ovim.ovimException as e:
2041 my.logger.error(str(e), exc_info=True)
2042 bottle.abort(e.http_code, str(e))
2043 except Exception as e:
2044 my.logger.error(str(e), exc_info=True)
2045 bottle.abort(HTTP_Bad_Request, str(e))
tiernof7aa8c42016-09-06 16:43:04 +02002046
tiernof7aa8c42016-09-06 16:43:04 +02002047
tiernof7aa8c42016-09-06 16:43:04 +02002048@bottle.route(url_base + '/networks/<network_id>', method='DELETE')
2049def http_delete_network_id(network_id):
mirabale9f6f1a2017-02-16 17:57:35 +01002050 """
2051 Delete a network_id from the database.
2052 :param network_id: Network id
2053 :return:
2054 """
2055 my = config_dic['http_threads'][threading.current_thread().name]
tiernof7aa8c42016-09-06 16:43:04 +02002056
mirabale9f6f1a2017-02-16 17:57:35 +01002057 try:
2058 # delete from the data base
2059 content = my.ovim.delete_network(network_id)
2060 data = {'result': content}
tiernof7aa8c42016-09-06 16:43:04 +02002061 return format_out(data)
mirabale9f6f1a2017-02-16 17:57:35 +01002062
2063 except ovim.ovimException as e:
2064 my.logger.error(str(e), exc_info=True)
2065 bottle.abort(e.http_code, str(e))
2066 except Exception as e:
2067 my.logger.error(str(e), exc_info=True)
2068 bottle.abort(HTTP_Bad_Request, str(e))
2069
tiernof7aa8c42016-09-06 16:43:04 +02002070#
2071# OPENFLOW
2072#
mirabal65ba8f82017-02-15 12:36:33 +01002073
2074
mirabal9e194592017-02-17 11:03:25 +01002075@bottle.route(url_base + '/openflow/controller', method='GET')
2076def http_get_openflow_controller():
2077 """
2078 Retrieve a openflow controllers list from DB.
2079 :return:
2080 """
2081 # TODO check if show a proper list
2082 my = config_dic['http_threads'][threading.current_thread().name]
2083
2084 try:
2085 select_, where_, limit_ = filter_query_string(bottle.request.query, http2db_ofc,
2086 ('id', 'name', 'dpid', 'ip', 'port', 'type',
2087 'version', 'user', 'password'))
2088
2089 content = my.ovim.get_of_controllers(select_, where_)
2090 delete_nulls(content)
2091 change_keys_http2db(content, http2db_ofc, reverse=True)
2092 data = {'ofcs': content}
2093 return format_out(data)
2094 except ovim.ovimException as e:
2095 my.logger.error(str(e), exc_info=True)
2096 bottle.abort(e.http_code, str(e))
2097 except Exception as e:
2098 my.logger.error(str(e), exc_info=True)
2099 bottle.abort(HTTP_Bad_Request, str(e))
2100
2101
2102@bottle.route(url_base + '/openflow/controller/<uuid>', method='GET')
2103def http_get_openflow_controller_id(uuid):
2104 """
2105 Get an openflow controller by dpid from DB.get_of_controllers
2106 """
2107 my = config_dic['http_threads'][threading.current_thread().name]
2108
2109 try:
2110
2111 content = my.ovim.show_of_controller(uuid)
2112 delete_nulls(content)
2113 change_keys_http2db(content, http2db_ofc, reverse=True)
2114 data = {'ofc': content}
2115 return format_out(data)
2116 except ovim.ovimException as e:
2117 my.logger.error(str(e), exc_info=True)
2118 bottle.abort(e.http_code, str(e))
2119 except Exception as e:
2120 my.logger.error(str(e), exc_info=True)
2121 bottle.abort(HTTP_Bad_Request, str(e))
2122
2123
2124@bottle.route(url_base + '/openflow/controller/', method='POST')
2125def http_post_openflow_controller():
2126 """
2127 Create a new openflow controller into DB
2128 :return:
2129 """
2130 my = config_dic['http_threads'][threading.current_thread().name]
2131
2132 try:
2133 http_content = format_in(openflow_controller_schema)
2134 of_c = http_content['ofc']
2135 uuid = my.ovim.new_of_controller(of_c)
2136 content = my.ovim.show_of_controller(uuid)
2137 delete_nulls(content)
2138 change_keys_http2db(content, http2db_ofc, reverse=True)
2139 data = {'ofc': content}
2140 return format_out(data)
2141 except ovim.ovimException as e:
2142 my.logger.error(str(e), exc_info=True)
2143 bottle.abort(e.http_code, str(e))
2144 except Exception as e:
2145 my.logger.error(str(e), exc_info=True)
2146 bottle.abort(HTTP_Bad_Request, str(e))
2147
2148
2149@bottle.route(url_base + '/openflow/controller/<of_controller_id>', method='PUT')
2150def http_put_openflow_controller_by_id(of_controller_id):
2151 """
2152 Create an openflow controller into DB
2153 :param of_controller_id: openflow controller dpid
2154 :return:
2155 """
2156 my = config_dic['http_threads'][threading.current_thread().name]
2157
2158 try:
2159 http_content = format_in(openflow_controller_schema)
2160 of_c = http_content['ofc']
2161
2162 content = my.ovim.edit_of_controller(of_controller_id, of_c)
2163 delete_nulls(content)
2164 change_keys_http2db(content, http2db_ofc, reverse=True)
2165 data = {'ofc': content}
2166 return format_out(data)
2167 except ovim.ovimException as e:
2168 my.logger.error(str(e), exc_info=True)
2169 bottle.abort(e.http_code, str(e))
2170 except Exception as e:
2171 my.logger.error(str(e), exc_info=True)
2172 bottle.abort(HTTP_Bad_Request, str(e))
2173
2174
2175@bottle.route(url_base + '/openflow/controller/<of_controller_id>', method='DELETE')
2176def http_delete_openflow_controller(of_controller_id):
2177 """
2178 Delete an openflow controller from DB.
2179 :param of_controller_id: openflow controller dpid
2180 :return:
2181 """
2182 my = config_dic['http_threads'][threading.current_thread().name]
2183
2184 try:
2185 content = my.ovim.delete_of_controller(of_controller_id)
2186 data = {'result': content}
2187 return format_out(data)
2188 except ovim.ovimException as e:
2189 my.logger.error(str(e), exc_info=True)
2190 bottle.abort(e.http_code, str(e))
2191 except Exception as e:
2192 my.logger.error(str(e), exc_info=True)
2193 bottle.abort(HTTP_Bad_Request, str(e))
2194
2195
tiernof7aa8c42016-09-06 16:43:04 +02002196@bottle.route(url_base + '/networks/<network_id>/openflow', method='GET')
2197def http_get_openflow_id(network_id):
mirabal65ba8f82017-02-15 12:36:33 +01002198 """
2199 To obtain the list of openflow rules of a network
2200 :param network_id: network id
2201 :return:
2202 """
mirabale9f6f1a2017-02-16 17:57:35 +01002203 my = config_dic['http_threads'][threading.current_thread().name]
mirabal65ba8f82017-02-15 12:36:33 +01002204
2205 # ignore input data
2206 if network_id == 'all':
2207 network_id = None
2208 try:
mirabal65ba8f82017-02-15 12:36:33 +01002209 content = my.ovim.get_openflow_rules(network_id)
2210 data = {'openflow-rules': content}
2211 except ovim.ovimException as e:
2212 my.logger.error(str(e), exc_info=True)
2213 bottle.abort(e.http_code, str(e))
2214 except Exception as e:
2215 my.logger.error(str(e), exc_info=True)
2216 bottle.abort(HTTP_Bad_Request, str(e))
2217
tiernof7aa8c42016-09-06 16:43:04 +02002218 return format_out(data)
2219
mirabal65ba8f82017-02-15 12:36:33 +01002220
tiernof7aa8c42016-09-06 16:43:04 +02002221@bottle.route(url_base + '/networks/<network_id>/openflow', method='PUT')
2222def http_put_openflow_id(network_id):
mirabal65ba8f82017-02-15 12:36:33 +01002223 """
2224 To make actions over the net. The action is to reinstall the openflow rules
tiernof7aa8c42016-09-06 16:43:04 +02002225 network_id can be 'all'
mirabal65ba8f82017-02-15 12:36:33 +01002226 :param network_id: network id
2227 :return:
2228 """
2229 my = config_dic['http_threads'][threading.current_thread().name]
2230
tiernof7aa8c42016-09-06 16:43:04 +02002231 if not my.admin:
2232 bottle.abort(HTTP_Unauthorized, "Needed admin privileges")
mirabal65ba8f82017-02-15 12:36:33 +01002233
2234 if network_id == 'all':
2235 network_id = None
2236
2237 try:
mirabale9f6f1a2017-02-16 17:57:35 +01002238 result = my.ovim.edit_openflow_rules(network_id)
mirabal65ba8f82017-02-15 12:36:33 +01002239 except ovim.ovimException as e:
2240 my.logger.error(str(e), exc_info=True)
2241 bottle.abort(e.http_code, str(e))
2242 except Exception as e:
2243 my.logger.error(str(e), exc_info=True)
2244 bottle.abort(HTTP_Bad_Request, str(e))
2245
2246 data = {'result': str(result) + " nets updates"}
tiernof7aa8c42016-09-06 16:43:04 +02002247 return format_out(data)
2248
mirabalf9a1a8d2017-03-15 12:42:27 +01002249@bottle.route(url_base + '/networks/clear/openflow/<ofc_id>', method='DELETE')
tiernof7aa8c42016-09-06 16:43:04 +02002250@bottle.route(url_base + '/networks/clear/openflow', method='DELETE')
mirabalf9a1a8d2017-03-15 12:42:27 +01002251def http_clear_openflow_rules(ofc_id=None):
mirabal65ba8f82017-02-15 12:36:33 +01002252 """
2253 To make actions over the net. The action is to delete ALL openflow rules
2254 :return:
2255 """
2256 my = config_dic['http_threads'][ threading.current_thread().name]
2257
tiernof7aa8c42016-09-06 16:43:04 +02002258 if not my.admin:
2259 bottle.abort(HTTP_Unauthorized, "Needed admin privileges")
mirabal65ba8f82017-02-15 12:36:33 +01002260 try:
mirabalf9a1a8d2017-03-15 12:42:27 +01002261 my.ovim.delete_openflow_rules(ofc_id)
mirabal65ba8f82017-02-15 12:36:33 +01002262 except ovim.ovimException as e:
2263 my.logger.error(str(e), exc_info=True)
2264 bottle.abort(e.http_code, str(e))
2265 except Exception as e:
2266 my.logger.error(str(e), exc_info=True)
2267 bottle.abort(HTTP_Bad_Request, str(e))
tiernof7aa8c42016-09-06 16:43:04 +02002268
mirabal65ba8f82017-02-15 12:36:33 +01002269 data = {'result': " Clearing openflow rules in process"}
tiernof7aa8c42016-09-06 16:43:04 +02002270 return format_out(data)
2271
mirabalf9a1a8d2017-03-15 12:42:27 +01002272@bottle.route(url_base + '/networks/openflow/ports/<ofc_id>', method='GET')
tiernof7aa8c42016-09-06 16:43:04 +02002273@bottle.route(url_base + '/networks/openflow/ports', method='GET')
mirabalf9a1a8d2017-03-15 12:42:27 +01002274def http_get_openflow_ports(ofc_id=None):
mirabal65ba8f82017-02-15 12:36:33 +01002275 """
2276 Obtain switch ports names of openflow controller
2277 :return:
2278 """
2279 my = config_dic['http_threads'][threading.current_thread().name]
mirabalf9a1a8d2017-03-15 12:42:27 +01002280
2281 try:
2282 ports = my.ovim.get_openflow_ports(ofc_id)
2283 data = {'ports': ports}
2284 except ovim.ovimException as e:
2285 my.logger.error(str(e), exc_info=True)
2286 bottle.abort(e.http_code, str(e))
2287 except Exception as e:
2288 my.logger.error(str(e), exc_info=True)
2289 bottle.abort(HTTP_Bad_Request, str(e))
2290
tiernof7aa8c42016-09-06 16:43:04 +02002291 return format_out(data)
tiernof7aa8c42016-09-06 16:43:04 +02002292#
2293# PORTS
2294#
2295
mirabal65ba8f82017-02-15 12:36:33 +01002296
tiernof7aa8c42016-09-06 16:43:04 +02002297@bottle.route(url_base + '/ports', method='GET')
2298def http_get_ports():
2299 #obtain data
2300 my = config_dic['http_threads'][ threading.current_thread().name ]
2301 select_,where_,limit_ = filter_query_string(bottle.request.query, http2db_port,
2302 ('id','name','tenant_id','network_id','vpci','mac_address','device_owner','device_id',
2303 'binding:switch_port','binding:vlan','bandwidth','status','admin_state_up','ip_address') )
tierno57f7bda2017-02-09 12:01:55 +01002304 try:
2305 ports = my.ovim.get_ports(columns=select_, filter=where_, limit=limit_)
2306 delete_nulls(ports)
2307 change_keys_http2db(ports, http2db_port, reverse=True)
2308 data={'ports' : ports}
tiernof7aa8c42016-09-06 16:43:04 +02002309 return format_out(data)
tierno57f7bda2017-02-09 12:01:55 +01002310 except ovim.ovimException as e:
2311 my.logger.error(str(e), exc_info=True)
2312 bottle.abort(e.http_code, str(e))
2313 except Exception as e:
2314 my.logger.error(str(e), exc_info=True)
2315 bottle.abort(HTTP_Bad_Request, str(e))
tiernof7aa8c42016-09-06 16:43:04 +02002316
2317@bottle.route(url_base + '/ports/<port_id>', method='GET')
2318def http_get_port_id(port_id):
2319 my = config_dic['http_threads'][ threading.current_thread().name ]
tierno57f7bda2017-02-09 12:01:55 +01002320 try:
2321 ports = my.ovim.get_ports(filter={"uuid": port_id})
2322 if not ports:
2323 bottle.abort(HTTP_Not_Found, 'port %s not found' % port_id)
2324 return
2325 delete_nulls(ports)
2326 change_keys_http2db(ports, http2db_port, reverse=True)
2327 data = {'port': ports[0]}
tiernof7aa8c42016-09-06 16:43:04 +02002328 return format_out(data)
tierno57f7bda2017-02-09 12:01:55 +01002329 except ovim.ovimException as e:
2330 my.logger.error(str(e), exc_info=True)
2331 bottle.abort(e.http_code, str(e))
2332 except Exception as e:
2333 my.logger.error(str(e), exc_info=True)
2334 bottle.abort(HTTP_Bad_Request, str(e))
tiernof7aa8c42016-09-06 16:43:04 +02002335
2336@bottle.route(url_base + '/ports', method='POST')
2337def http_post_ports():
2338 '''insert an external port into the database.'''
2339 my = config_dic['http_threads'][ threading.current_thread().name ]
2340 if not my.admin:
2341 bottle.abort(HTTP_Unauthorized, "Needed admin privileges")
2342 #parse input data
2343 http_content = format_in( port_new_schema )
2344 r = remove_extra_items(http_content, port_new_schema)
2345 if r is not None: print "http_post_ports: Warning: remove extra items ", r
2346 change_keys_http2db(http_content['port'], http2db_port)
2347 port=http_content['port']
tierno57f7bda2017-02-09 12:01:55 +01002348 try:
2349 port_id = my.ovim.new_port(port)
2350 ports = my.ovim.get_ports(filter={"uuid": port_id})
2351 if not ports:
2352 bottle.abort(HTTP_Internal_Server_Error, "port '{}' inserted but not found at database".format(port_id))
tiernof7aa8c42016-09-06 16:43:04 +02002353 return
tierno57f7bda2017-02-09 12:01:55 +01002354 delete_nulls(ports)
2355 change_keys_http2db(ports, http2db_port, reverse=True)
2356 data = {'port': ports[0]}
2357 return format_out(data)
2358 except ovim.ovimException as e:
2359 my.logger.error(str(e), exc_info=True)
2360 bottle.abort(e.http_code, str(e))
2361 except Exception as e:
2362 my.logger.error(str(e), exc_info=True)
2363 bottle.abort(HTTP_Bad_Request, str(e))
2364
tiernof7aa8c42016-09-06 16:43:04 +02002365@bottle.route(url_base + '/ports/<port_id>', method='PUT')
2366def http_put_port_id(port_id):
2367 '''update a port_id into the database.'''
tiernof7aa8c42016-09-06 16:43:04 +02002368 my = config_dic['http_threads'][ threading.current_thread().name ]
2369 #parse input data
2370 http_content = format_in( port_update_schema )
2371 change_keys_http2db(http_content['port'], http2db_port)
2372 port_dict=http_content['port']
2373
tierno57f7bda2017-02-09 12:01:55 +01002374 for k in ('vlan', 'switch_port', 'mac_address', 'tenant_id'):
tiernof7aa8c42016-09-06 16:43:04 +02002375 if k in port_dict and not my.admin:
2376 bottle.abort(HTTP_Unauthorized, "Needed admin privileges for changing " + k)
2377 return
tierno57f7bda2017-02-09 12:01:55 +01002378 try:
2379 port_id = my.ovim.edit_port(port_id, port_dict, my.admin)
2380 ports = my.ovim.get_ports(filter={"uuid": port_id})
2381 if not ports:
2382 bottle.abort(HTTP_Internal_Server_Error, "port '{}' edited but not found at database".format(port_id))
2383 return
2384 delete_nulls(ports)
2385 change_keys_http2db(ports, http2db_port, reverse=True)
2386 data = {'port': ports[0]}
2387 return format_out(data)
2388 except ovim.ovimException as e:
2389 my.logger.error(str(e), exc_info=True)
2390 bottle.abort(e.http_code, str(e))
2391 except Exception as e:
2392 my.logger.error(str(e), exc_info=True)
2393 bottle.abort(HTTP_Bad_Request, str(e))
tiernof7aa8c42016-09-06 16:43:04 +02002394
tierno57f7bda2017-02-09 12:01:55 +01002395
tiernof7aa8c42016-09-06 16:43:04 +02002396@bottle.route(url_base + '/ports/<port_id>', method='DELETE')
2397def http_delete_port_id(port_id):
2398 '''delete a port_id from the database.'''
2399 my = config_dic['http_threads'][ threading.current_thread().name ]
2400 if not my.admin:
2401 bottle.abort(HTTP_Unauthorized, "Needed admin privileges")
2402 return
tierno57f7bda2017-02-09 12:01:55 +01002403 try:
2404 result = my.ovim.delete_port(port_id)
2405 data = {'result': result}
tiernof7aa8c42016-09-06 16:43:04 +02002406 return format_out(data)
tierno57f7bda2017-02-09 12:01:55 +01002407 except ovim.ovimException as e:
2408 my.logger.error(str(e), exc_info=True)
2409 bottle.abort(e.http_code, str(e))
2410 except Exception as e:
2411 my.logger.error(str(e), exc_info=True)
2412 bottle.abort(HTTP_Bad_Request, str(e))
2413
2414
mirabal6045a9d2017-03-06 11:36:55 +01002415@bottle.route(url_base + '/openflow/mapping', method='POST')
2416def http_of_port_mapping():
2417 """
mirabal38cf6072017-03-16 11:19:19 +01002418 Create new compute port mapping entry
mirabal6045a9d2017-03-06 11:36:55 +01002419 :return:
2420 """
2421 my = config_dic['http_threads'][threading.current_thread().name]
2422
2423 try:
2424 http_content = format_in(of_port_map_new_schema)
2425 r = remove_extra_items(http_content, of_port_map_new_schema)
2426 if r is not None:
2427 my.logger.error("http_of_port_mapping: Warning: remove extra items " + str(r), exc_info=True)
2428
2429 # insert in data base
2430 port_mapping = my.ovim.set_of_port_mapping(http_content['of_port_mapings'])
2431 change_keys_http2db(port_mapping, http2db_id, reverse=True)
2432 delete_nulls(port_mapping)
2433 data = {'of_port_mappings': port_mapping}
2434 return format_out(data)
2435 except ovim.ovimException as e:
2436 my.logger.error(str(e), exc_info=True)
2437 bottle.abort(e.http_code, str(e))
2438 except Exception as e:
2439 my.logger.error(str(e), exc_info=True)
2440 bottle.abort(HTTP_Bad_Request, str(e))
2441
2442
2443@bottle.route(url_base + '/openflow/mapping', method='GET')
2444def get_of_port_mapping():
2445 """
mirabal38cf6072017-03-16 11:19:19 +01002446 Get compute port mapping
mirabal6045a9d2017-03-06 11:36:55 +01002447 :return:
2448 """
2449 my = config_dic['http_threads'][threading.current_thread().name]
2450
2451 try:
2452 select_, where_, limit_ = filter_query_string(bottle.request.query, http2db_id,
2453 ('id', 'ofc_id', 'region', 'compute_node', 'pci',
2454 'switch_dpid', 'switch_port', 'switch_mac'))
2455 # insert in data base
2456 port_mapping = my.ovim.get_of_port_mappings(select_, where_)
2457 change_keys_http2db(port_mapping, http2db_id, reverse=True)
2458 delete_nulls(port_mapping)
2459 data = {'of_port_mappings': port_mapping}
2460 return format_out(data)
2461 except ovim.ovimException as e:
2462 my.logger.error(str(e), exc_info=True)
2463 bottle.abort(e.http_code, str(e))
2464 except Exception as e:
2465 my.logger.error(str(e), exc_info=True)
2466 bottle.abort(HTTP_Bad_Request, str(e))
2467
2468
2469@bottle.route(url_base + '/openflow/mapping/<region>', method='DELETE')
2470def delete_of_port_mapping(region):
2471 """
2472 Insert a tenant into the database.
2473 :return:
2474 """
2475 my = config_dic['http_threads'][threading.current_thread().name]
2476
2477 try:
2478 # insert in data base
2479 db_filter = {'region': region}
2480 result = my.ovim.clear_of_port_mapping(db_filter)
2481 data = {'result': result}
2482 return format_out(data)
2483 except ovim.ovimException as e:
2484 my.logger.error(str(e), exc_info=True)
2485 bottle.abort(e.http_code, str(e))
2486 except Exception as e:
2487 my.logger.error(str(e), exc_info=True)
2488 bottle.abort(HTTP_Bad_Request, str(e))
mirabal37829452017-03-09 14:41:21 +01002489