Move openflow logic to ovim.py from httpserver.py
[osm/openvim.git] / httpserver.py
1 # -*- coding: utf-8 -*-
2
3 ##
4 # Copyright 2015 Telefónica Investigación y Desarrollo, S.A.U.
5 # This file is part of openvim
6 # All Rights Reserved.
7 #
8 # Licensed under the Apache License, Version 2.0 (the "License"); you may
9 # not use this file except in compliance with the License. You may obtain
10 # a copy of the License at
11 #
12 # http://www.apache.org/licenses/LICENSE-2.0
13 #
14 # Unless required by applicable law or agreed to in writing, software
15 # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
16 # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
17 # License for the specific language governing permissions and limitations
18 # under the License.
19 #
20 # For those usages not covered by the Apache License, Version 2.0 please
21 # contact with: nfvlabs@tid.es
22 ##
23
24 '''
25 This is the thread for the http server North API.
26 Two thread will be launched, with normal and administrative permissions.
27 '''
28
29 __author__="Alfonso Tierno, Gerardo Garcia, Leonardo Mirabal"
30 __date__ ="$10-jul-2014 12:07:15$"
31
32 import bottle
33 import urlparse
34 import yaml
35 import json
36 import threading
37 import datetime
38 import hashlib
39 import os
40 import imp
41 from netaddr import IPNetwork, IPAddress, all_matching_cidrs
42 #import only if needed because not needed in test mode. To allow an easier installation import RADclass
43 from jsonschema import validate as js_v, exceptions as js_e
44 import host_thread as ht
45 from vim_schema import host_new_schema, host_edit_schema, tenant_new_schema, \
46 tenant_edit_schema, \
47 flavor_new_schema, flavor_update_schema, \
48 image_new_schema, image_update_schema, \
49 server_new_schema, server_action_schema, network_new_schema, network_update_schema, \
50 port_new_schema, port_update_schema
51 import ovim
52 import logging
53
54 global my
55 global url_base
56 global config_dic
57 global RADclass_module
58 RADclass=None #RADclass module is charged only if not in test mode
59
60 url_base="/openvim"
61
62 HTTP_Bad_Request = 400
63 HTTP_Unauthorized = 401
64 HTTP_Not_Found = 404
65 HTTP_Forbidden = 403
66 HTTP_Method_Not_Allowed = 405
67 HTTP_Not_Acceptable = 406
68 HTTP_Request_Timeout = 408
69 HTTP_Conflict = 409
70 HTTP_Service_Unavailable = 503
71 HTTP_Internal_Server_Error= 500
72
73 def md5(fname):
74 hash_md5 = hashlib.md5()
75 with open(fname, "rb") as f:
76 for chunk in iter(lambda: f.read(4096), b""):
77 hash_md5.update(chunk)
78 return hash_md5.hexdigest()
79
80 def md5_string(fname):
81 hash_md5 = hashlib.md5()
82 hash_md5.update(fname)
83 return hash_md5.hexdigest()
84
85 def check_extended(extended, allow_net_attach=False):
86 '''Makes and extra checking of extended input that cannot be done using jsonschema
87 Attributes:
88 allow_net_attach: for allowing or not the uuid field at interfaces
89 that are allowed for instance, but not for flavors
90 Return: (<0, error_text) if error; (0,None) if not error '''
91 if "numas" not in extended: return 0, None
92 id_s=[]
93 numaid=0
94 for numa in extended["numas"]:
95 nb_formats = 0
96 if "cores" in numa:
97 nb_formats += 1
98 if "cores-id" in numa:
99 if len(numa["cores-id"]) != numa["cores"]:
100 return -HTTP_Bad_Request, "different number of cores-id (%d) than cores (%d) at numa %d" % (len(numa["cores-id"]), numa["cores"],numaid)
101 id_s.extend(numa["cores-id"])
102 if "threads" in numa:
103 nb_formats += 1
104 if "threads-id" in numa:
105 if len(numa["threads-id"]) != numa["threads"]:
106 return -HTTP_Bad_Request, "different number of threads-id (%d) than threads (%d) at numa %d" % (len(numa["threads-id"]), numa["threads"],numaid)
107 id_s.extend(numa["threads-id"])
108 if "paired-threads" in numa:
109 nb_formats += 1
110 if "paired-threads-id" in numa:
111 if len(numa["paired-threads-id"]) != numa["paired-threads"]:
112 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)
113 for pair in numa["paired-threads-id"]:
114 if len(pair) != 2:
115 return -HTTP_Bad_Request, "paired-threads-id must contain a list of two elements list at numa %d" % (numaid)
116 id_s.extend(pair)
117 if nb_formats > 1:
118 return -HTTP_Service_Unavailable, "only one of cores, threads, paired-threads are allowed in this version at numa %d" % numaid
119 #check interfaces
120 if "interfaces" in numa:
121 ifaceid=0
122 names=[]
123 vpcis=[]
124 for interface in numa["interfaces"]:
125 if "uuid" in interface and not allow_net_attach:
126 return -HTTP_Bad_Request, "uuid field is not allowed at numa %d interface %s position %d" % (numaid, interface.get("name",""), ifaceid )
127 if "mac_address" in interface and interface["dedicated"]=="yes":
128 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 )
129 if "name" in interface:
130 if interface["name"] in names:
131 return -HTTP_Bad_Request, "name repeated at numa %d, interface %s position %d" % (numaid, interface.get("name",""), ifaceid )
132 names.append(interface["name"])
133 if "vpci" in interface:
134 if interface["vpci"] in vpcis:
135 return -HTTP_Bad_Request, "vpci %s repeated at numa %d, interface %s position %d" % (interface["vpci"], numaid, interface.get("name",""), ifaceid )
136 vpcis.append(interface["vpci"])
137 ifaceid+=1
138 numaid+=1
139 if numaid > 1:
140 return -HTTP_Service_Unavailable, "only one numa can be defined in this version "
141 for a in range(0,len(id_s)):
142 if a not in id_s:
143 return -HTTP_Bad_Request, "core/thread identifiers must start at 0 and gaps are not alloed. Missing id number %d" % a
144
145 return 0, None
146
147 #
148 # dictionaries that change from HTTP API to database naming
149 #
150 http2db_host={'id':'uuid'}
151 http2db_tenant={'id':'uuid'}
152 http2db_flavor={'id':'uuid','imageRef':'image_id'}
153 http2db_image={'id':'uuid', 'created':'created_at', 'updated':'modified_at', 'public': 'public'}
154 http2db_server={'id':'uuid','hostId':'host_id','flavorRef':'flavor_id','imageRef':'image_id','created':'created_at'}
155 http2db_network={'id':'uuid','provider:vlan':'vlan', 'provider:physical': 'provider'}
156 http2db_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'}
157
158 def remove_extra_items(data, schema):
159 deleted=[]
160 if type(data) is tuple or type(data) is list:
161 for d in data:
162 a= remove_extra_items(d, schema['items'])
163 if a is not None: deleted.append(a)
164 elif type(data) is dict:
165 for k in data.keys():
166 if 'properties' not in schema or k not in schema['properties'].keys():
167 del data[k]
168 deleted.append(k)
169 else:
170 a = remove_extra_items(data[k], schema['properties'][k])
171 if a is not None: deleted.append({k:a})
172 if len(deleted) == 0: return None
173 elif len(deleted) == 1: return deleted[0]
174 else: return deleted
175
176 def delete_nulls(var):
177 if type(var) is dict:
178 for k in var.keys():
179 if var[k] is None: del var[k]
180 elif type(var[k]) is dict or type(var[k]) is list or type(var[k]) is tuple:
181 if delete_nulls(var[k]): del var[k]
182 if len(var) == 0: return True
183 elif type(var) is list or type(var) is tuple:
184 for k in var:
185 if type(k) is dict: delete_nulls(k)
186 if len(var) == 0: return True
187 return False
188
189
190 class httpserver(threading.Thread):
191 def __init__(self, ovim, name="http", host='localhost', port=8080, admin=False, config_=None):
192 '''
193 Creates a new thread to attend the http connections
194 Attributes:
195 db_conn: database connection
196 name: name of this thread
197 host: ip or name where to listen
198 port: port where to listen
199 admin: if this has privileges of administrator or not
200 config_: unless the first thread must be provided. It is a global dictionary where to allocate the self variable
201 '''
202 global url_base
203 global config_dic
204
205 #initialization
206 if config_ is not None:
207 config_dic = config_
208 if 'http_threads' not in config_dic:
209 config_dic['http_threads'] = {}
210 threading.Thread.__init__(self)
211 self.host = host
212 self.port = port
213 self.db = ovim.db #TODO OVIM remove
214 self.ovim = ovim
215 self.admin = admin
216 if name in config_dic:
217 print "httpserver Warning!!! Onether thread with the same name", name
218 n=0
219 while name+str(n) in config_dic:
220 n +=1
221 name +=str(n)
222 self.name = name
223 self.url_preffix = 'http://' + self.host + ':' + str(self.port) + url_base
224 config_dic['http_threads'][name] = self
225
226 #Ensure that when the main program exits the thread will also exit
227 self.daemon = True
228 self.setDaemon(True)
229 self.logger = logging.getLogger("openvim.http")
230
231 def run(self):
232 bottle.run(host=self.host, port=self.port, debug=True) #quiet=True
233
234 def gethost(self, host_id):
235 result, content = self.db.get_host(host_id)
236 if result < 0:
237 print "httpserver.gethost error %d %s" % (result, content)
238 bottle.abort(-result, content)
239 elif result==0:
240 print "httpserver.gethost host '%s' not found" % host_id
241 bottle.abort(HTTP_Not_Found, content)
242 else:
243 data={'host' : content}
244 convert_boolean(content, ('admin_state_up',) )
245 change_keys_http2db(content, http2db_host, reverse=True)
246 print data['host']
247 return format_out(data)
248
249 @bottle.route(url_base + '/', method='GET')
250 def http_get():
251 print
252 return 'works' #TODO: put links or redirection to /openvim???
253
254 #
255 # Util funcions
256 #
257
258 def change_keys_http2db(data, http_db, reverse=False):
259 '''Change keys of dictionary data according to the key_dict values
260 This allow change from http interface names to database names.
261 When reverse is True, the change is otherwise
262 Attributes:
263 data: can be a dictionary or a list
264 http_db: is a dictionary with hhtp names as keys and database names as value
265 reverse: by default change is done from http API to database. If True change is done otherwise
266 Return: None, but data is modified'''
267 if type(data) is tuple or type(data) is list:
268 for d in data:
269 change_keys_http2db(d, http_db, reverse)
270 elif type(data) is dict or type(data) is bottle.FormsDict:
271 if reverse:
272 for k,v in http_db.items():
273 if v in data: data[k]=data.pop(v)
274 else:
275 for k,v in http_db.items():
276 if k in data: data[v]=data.pop(k)
277
278
279
280 def format_out(data):
281 '''return string of dictionary data according to requested json, yaml, xml. By default json'''
282 if 'application/yaml' in bottle.request.headers.get('Accept'):
283 bottle.response.content_type='application/yaml'
284 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='"'
285 else: #by default json
286 bottle.response.content_type='application/json'
287 #return data #json no style
288 return json.dumps(data, indent=4) + "\n"
289
290 def format_in(schema):
291 try:
292 error_text = "Invalid header format "
293 format_type = bottle.request.headers.get('Content-Type', 'application/json')
294 if 'application/json' in format_type:
295 error_text = "Invalid json format "
296 #Use the json decoder instead of bottle decoder because it informs about the location of error formats with a ValueError exception
297 client_data = json.load(bottle.request.body)
298 #client_data = bottle.request.json()
299 elif 'application/yaml' in format_type:
300 error_text = "Invalid yaml format "
301 client_data = yaml.load(bottle.request.body)
302 elif format_type == 'application/xml':
303 bottle.abort(501, "Content-Type: application/xml not supported yet.")
304 else:
305 print "HTTP HEADERS: " + str(bottle.request.headers.items())
306 bottle.abort(HTTP_Not_Acceptable, 'Content-Type ' + str(format_type) + ' not supported.')
307 return
308 #if client_data == None:
309 # bottle.abort(HTTP_Bad_Request, "Content error, empty")
310 # return
311 #check needed_items
312
313 #print "HTTP input data: ", str(client_data)
314 error_text = "Invalid content "
315 js_v(client_data, schema)
316
317 return client_data
318 except (ValueError, yaml.YAMLError) as exc:
319 error_text += str(exc)
320 print error_text
321 bottle.abort(HTTP_Bad_Request, error_text)
322 except js_e.ValidationError as exc:
323 print "HTTP validate_in error, jsonschema exception ", exc.message, "at", exc.path
324 print " CONTENT: " + str(bottle.request.body.readlines())
325 error_pos = ""
326 if len(exc.path)>0: error_pos=" at '" + ":".join(map(str, exc.path)) + "'"
327 bottle.abort(HTTP_Bad_Request, error_text + error_pos+": "+exc.message)
328 #except:
329 # bottle.abort(HTTP_Bad_Request, "Content error: Failed to parse Content-Type", error_pos)
330 # raise
331
332 def filter_query_string(qs, http2db, allowed):
333 '''Process query string (qs) checking that contains only valid tokens for avoiding SQL injection
334 Attributes:
335 'qs': bottle.FormsDict variable to be processed. None or empty is considered valid
336 'allowed': list of allowed string tokens (API http naming). All the keys of 'qs' must be one of 'allowed'
337 'http2db': dictionary with change from http API naming (dictionary key) to database naming(dictionary value)
338 Return: A tuple with the (select,where,limit) to be use in a database query. All of then transformed to the database naming
339 select: list of items to retrieve, filtered by query string 'field=token'. If no 'field' is present, allowed list is returned
340 where: dictionary with key, value, taken from the query string token=value. Empty if nothing is provided
341 limit: limit dictated by user with the query string 'limit'. 100 by default
342 abort if not permitted, using bottel.abort
343 '''
344 where = {}
345 limit = 100
346 select = []
347 if type(qs) is not bottle.FormsDict:
348 print '!!!!!!!!!!!!!!invalid query string not a dictionary'
349 # bottle.abort(HTTP_Internal_Server_Error, "call programmer")
350 else:
351 for k in qs:
352 if k == 'field':
353 select += qs.getall(k)
354 for v in select:
355 if v not in allowed:
356 bottle.abort(HTTP_Bad_Request, "Invalid query string at 'field=" + v + "'")
357 elif k == 'limit':
358 try:
359 limit = int(qs[k])
360 except:
361 bottle.abort(HTTP_Bad_Request, "Invalid query string at 'limit=" + qs[k] + "'")
362 else:
363 if k not in allowed:
364 bottle.abort(HTTP_Bad_Request, "Invalid query string at '" + k + "=" + qs[k] + "'")
365 if qs[k] != "null":
366 where[k] = qs[k]
367 else:
368 where[k] = None
369 if len(select) == 0: select += allowed
370 # change from http api to database naming
371 for i in range(0, len(select)):
372 k = select[i]
373 if k in http2db:
374 select[i] = http2db[k]
375 change_keys_http2db(where, http2db)
376 # print "filter_query_string", select,where,limit
377
378 return select, where, limit
379
380 def convert_bandwidth(data, reverse=False):
381 '''Check the field bandwidth recursively and when found, it removes units and convert to number
382 It assumes that bandwidth is well formed
383 Attributes:
384 'data': dictionary bottle.FormsDict variable to be checked. None or empty is considered valid
385 'reverse': by default convert form str to int (Mbps), if True it convert from number to units
386 Return:
387 None
388 '''
389 if type(data) is dict:
390 for k in data.keys():
391 if type(data[k]) is dict or type(data[k]) is tuple or type(data[k]) is list:
392 convert_bandwidth(data[k], reverse)
393 if "bandwidth" in data:
394 try:
395 value=str(data["bandwidth"])
396 if not reverse:
397 pos = value.find("bps")
398 if pos>0:
399 if value[pos-1]=="G": data["bandwidth"] = int(data["bandwidth"][:pos-1]) * 1000
400 elif value[pos-1]=="k": data["bandwidth"]= int(data["bandwidth"][:pos-1]) / 1000
401 else: data["bandwidth"]= int(data["bandwidth"][:pos-1])
402 else:
403 value = int(data["bandwidth"])
404 if value % 1000 == 0: data["bandwidth"]=str(value/1000) + " Gbps"
405 else: data["bandwidth"]=str(value) + " Mbps"
406 except:
407 print "convert_bandwidth exception for type", type(data["bandwidth"]), " data", data["bandwidth"]
408 return
409 if type(data) is tuple or type(data) is list:
410 for k in data:
411 if type(k) is dict or type(k) is tuple or type(k) is list:
412 convert_bandwidth(k, reverse)
413
414 def convert_boolean(data, items): #TODO OVIM delete
415 '''Check recursively the content of data, and if there is an key contained in items, convert value from string to boolean
416 It assumes that bandwidth is well formed
417 Attributes:
418 'data': dictionary bottle.FormsDict variable to be checked. None or empty is consideted valid
419 'items': tuple of keys to convert
420 Return:
421 None
422 '''
423 if type(data) is dict:
424 for k in data.keys():
425 if type(data[k]) is dict or type(data[k]) is tuple or type(data[k]) is list:
426 convert_boolean(data[k], items)
427 if k in items:
428 if type(data[k]) is str:
429 if data[k]=="false": data[k]=False
430 elif data[k]=="true": data[k]=True
431 if type(data) is tuple or type(data) is list:
432 for k in data:
433 if type(k) is dict or type(k) is tuple or type(k) is list:
434 convert_boolean(k, items)
435
436 def convert_datetime2str(var):
437 '''Converts a datetime variable to a string with the format '%Y-%m-%dT%H:%i:%s'
438 It enters recursively in the dict var finding this kind of variables
439 '''
440 if type(var) is dict:
441 for k,v in var.items():
442 if type(v) is datetime.datetime:
443 var[k]= v.strftime('%Y-%m-%dT%H:%M:%S')
444 elif type(v) is dict or type(v) is list or type(v) is tuple:
445 convert_datetime2str(v)
446 if len(var) == 0: return True
447 elif type(var) is list or type(var) is tuple:
448 for v in var:
449 convert_datetime2str(v)
450
451 def check_valid_tenant(my, tenant_id):
452 if tenant_id=='any':
453 if not my.admin:
454 return HTTP_Unauthorized, "Needed admin privileges"
455 else:
456 result, _ = my.db.get_table(FROM='tenants', SELECT=('uuid',), WHERE={'uuid': tenant_id})
457 if result<=0:
458 return HTTP_Not_Found, "tenant '%s' not found" % tenant_id
459 return 0, None
460
461 def is_url(url):
462 '''
463 Check if string value is a well-wormed url
464 :param url: string url
465 :return: True if is a valid url, False if is not well-formed
466 '''
467
468 parsed_url = urlparse.urlparse(url)
469 return parsed_url
470
471
472 @bottle.error(400)
473 @bottle.error(401)
474 @bottle.error(404)
475 @bottle.error(403)
476 @bottle.error(405)
477 @bottle.error(406)
478 @bottle.error(408)
479 @bottle.error(409)
480 @bottle.error(503)
481 @bottle.error(500)
482 def error400(error):
483 e={"error":{"code":error.status_code, "type":error.status, "description":error.body}}
484 return format_out(e)
485
486 @bottle.hook('after_request')
487 def enable_cors():
488 #TODO: Alf: Is it needed??
489 bottle.response.headers['Access-Control-Allow-Origin'] = '*'
490
491 #
492 # HOSTS
493 #
494
495 @bottle.route(url_base + '/hosts', method='GET')
496 def http_get_hosts():
497 return format_out(get_hosts())
498
499
500 def get_hosts():
501 select_, where_, limit_ = filter_query_string(bottle.request.query, http2db_host,
502 ('id', 'name', 'description', 'status', 'admin_state_up', 'ip_name'))
503
504 myself = config_dic['http_threads'][ threading.current_thread().name ]
505 result, content = myself.db.get_table(FROM='hosts', SELECT=select_, WHERE=where_, LIMIT=limit_)
506 if result < 0:
507 print "http_get_hosts Error", content
508 bottle.abort(-result, content)
509 else:
510 convert_boolean(content, ('admin_state_up',) )
511 change_keys_http2db(content, http2db_host, reverse=True)
512 for row in content:
513 row['links'] = ( {'href': myself.url_preffix + '/hosts/' + str(row['id']), 'rel': 'bookmark'}, )
514 data={'hosts' : content}
515 return data
516
517 @bottle.route(url_base + '/hosts/<host_id>', method='GET')
518 def http_get_host_id(host_id):
519 my = config_dic['http_threads'][ threading.current_thread().name ]
520 return my.gethost(host_id)
521
522 @bottle.route(url_base + '/hosts', method='POST')
523 def http_post_hosts():
524 '''insert a host into the database. All resources are got and inserted'''
525 my = config_dic['http_threads'][ threading.current_thread().name ]
526 #check permissions
527 if not my.admin:
528 bottle.abort(HTTP_Unauthorized, "Needed admin privileges")
529
530 #parse input data
531 http_content = format_in( host_new_schema )
532 r = remove_extra_items(http_content, host_new_schema)
533 if r is not None: print "http_post_host_id: Warning: remove extra items ", r
534 change_keys_http2db(http_content['host'], http2db_host)
535
536 host = http_content['host']
537 warning_text=""
538 if 'host-data' in http_content:
539 host.update(http_content['host-data'])
540 ip_name=http_content['host-data']['ip_name']
541 user=http_content['host-data']['user']
542 password=http_content['host-data'].get('password', None)
543 else:
544 ip_name=host['ip_name']
545 user=host['user']
546 password=host.get('password', None)
547 if not RADclass_module:
548 try:
549 RADclass_module = imp.find_module("RADclass")
550 except (IOError, ImportError) as e:
551 raise ImportError("Cannot import RADclass.py Openvim not properly installed" +str(e))
552
553 #fill rad info
554 rad = RADclass_module.RADclass()
555 (return_status, code) = rad.obtain_RAD(user, password, ip_name)
556
557 #return
558 if not return_status:
559 print 'http_post_hosts ERROR obtaining RAD', code
560 bottle.abort(HTTP_Bad_Request, code)
561 return
562 warning_text=code
563 rad_structure = yaml.load(rad.to_text())
564 print 'rad_structure\n---------------------'
565 print json.dumps(rad_structure, indent=4)
566 print '---------------------'
567 #return
568 WHERE_={"family":rad_structure['processor']['family'], 'manufacturer':rad_structure['processor']['manufacturer'], 'version':rad_structure['processor']['version']}
569 result, content = my.db.get_table(FROM='host_ranking',
570 SELECT=('ranking',),
571 WHERE=WHERE_)
572 if result > 0:
573 host['ranking'] = content[0]['ranking']
574 else:
575 #error_text= "Host " + str(WHERE_)+ " not found in ranking table. Not valid for VIM management"
576 #bottle.abort(HTTP_Bad_Request, error_text)
577 #return
578 warning_text += "Host " + str(WHERE_)+ " not found in ranking table. Assuming lowest value 100\n"
579 host['ranking'] = 100 #TODO: as not used in this version, set the lowest value
580
581 features = rad_structure['processor'].get('features', ())
582 host['features'] = ",".join(features)
583 host['numas'] = []
584
585 for node in (rad_structure['resource topology']['nodes'] or {}).itervalues():
586 interfaces= []
587 cores = []
588 eligible_cores=[]
589 count = 0
590 for core in node['cpu']['eligible_cores']:
591 eligible_cores.extend(core)
592 for core in node['cpu']['cores']:
593 for thread_id in core:
594 c={'core_id': count, 'thread_id': thread_id}
595 if thread_id not in eligible_cores: c['status'] = 'noteligible'
596 cores.append(c)
597 count = count+1
598
599 if 'nics' in node:
600 for port_k, port_v in node['nics']['nic 0']['ports'].iteritems():
601 if port_v['virtual']:
602 continue
603 else:
604 sriovs = []
605 for port_k2, port_v2 in node['nics']['nic 0']['ports'].iteritems():
606 if port_v2['virtual'] and port_v2['PF_pci_id']==port_k:
607 sriovs.append({'pci':port_k2, 'mac':port_v2['mac'], 'source_name':port_v2['source_name']})
608 if len(sriovs)>0:
609 #sort sriov according to pci and rename them to the vf number
610 new_sriovs = sorted(sriovs, key=lambda k: k['pci'])
611 index=0
612 for sriov in new_sriovs:
613 sriov['source_name'] = index
614 index += 1
615 interfaces.append ({'pci':str(port_k), 'Mbps': port_v['speed']/1000000, 'sriovs': new_sriovs, 'mac':port_v['mac'], 'source_name':port_v['source_name']})
616 memory=node['memory']['node_size'] / (1024*1024*1024)
617 #memory=get_next_2pow(node['memory']['hugepage_nr'])
618 host['numas'].append( {'numa_socket': node['id'], 'hugepages': node['memory']['hugepage_nr'], 'memory':memory, 'interfaces': interfaces, 'cores': cores } )
619 print json.dumps(host, indent=4)
620 #return
621 #
622 #insert in data base
623 result, content = my.db.new_host(host)
624 if result >= 0:
625 if content['admin_state_up']:
626 #create thread
627 host_test_mode = True if config_dic['mode']=='test' or config_dic['mode']=="OF only" else False
628 host_develop_mode = True if config_dic['mode']=='development' else False
629 host_develop_bridge_iface = config_dic.get('development_bridge', None)
630 thread = ht.host_thread(name=host.get('name',ip_name), user=user, host=ip_name, db=config_dic['db'], db_lock=config_dic['db_lock'],
631 test=host_test_mode, image_path=config_dic['image_path'],
632 version=config_dic['version'], host_id=content['uuid'],
633 develop_mode=host_develop_mode, develop_bridge_iface=host_develop_bridge_iface )
634 thread.start()
635 config_dic['host_threads'][ content['uuid'] ] = thread
636
637 if config_dic['network_type'] == 'ovs':
638 # create bridge
639 create_dhcp_ovs_bridge()
640 config_dic['host_threads'][content['uuid']].insert_task("new-ovsbridge")
641 # check if more host exist
642 create_vxlan_mesh(content['uuid'])
643
644 #return host data
645 change_keys_http2db(content, http2db_host, reverse=True)
646 if len(warning_text)>0:
647 content["warning"]= warning_text
648 data={'host' : content}
649 return format_out(data)
650 else:
651 bottle.abort(HTTP_Bad_Request, content)
652 return
653
654
655 def delete_dhcp_ovs_bridge(vlan, net_uuid):
656 """
657 Delete bridges and port created during dhcp launching at openvim controller
658 :param vlan: net vlan id
659 :param net_uuid: network identifier
660 :return:
661 """
662 dhcp_path = config_dic['ovs_controller_file_path']
663
664 http_controller = config_dic['http_threads'][threading.current_thread().name]
665 dhcp_controller = http_controller.ovim.get_dhcp_controller()
666
667 dhcp_controller.delete_dhcp_port(vlan, net_uuid)
668 dhcp_controller.delete_dhcp_server(vlan, net_uuid, dhcp_path)
669
670
671 def create_dhcp_ovs_bridge():
672 """
673 Initialize bridge to allocate the dhcp server at openvim controller
674 :return:
675 """
676 http_controller = config_dic['http_threads'][threading.current_thread().name]
677 dhcp_controller = http_controller.ovim.get_dhcp_controller()
678
679 dhcp_controller.create_ovs_bridge()
680
681
682 def set_mac_dhcp(vm_ip, vlan, first_ip, last_ip, cidr, mac):
683 """"
684 Launch a dhcpserver base on dnsmasq attached to the net base on vlan id across the the openvim computes
685 :param vm_ip: IP address asigned to a VM
686 :param vlan: Segmentation id
687 :param first_ip: First dhcp range ip
688 :param last_ip: Last dhcp range ip
689 :param cidr: net cidr
690 :param mac: VM vnic mac to be macthed with the IP received
691 """
692 if not vm_ip:
693 return
694 ip_tools = IPNetwork(cidr)
695 cidr_len = ip_tools.prefixlen
696 dhcp_netmask = str(ip_tools.netmask)
697 dhcp_path = config_dic['ovs_controller_file_path']
698
699 new_cidr = [first_ip + '/' + str(cidr_len)]
700 if not len(all_matching_cidrs(vm_ip, new_cidr)):
701 vm_ip = None
702
703 http_controller = config_dic['http_threads'][threading.current_thread().name]
704 dhcp_controller = http_controller.ovim.get_dhcp_controller()
705
706 dhcp_controller.set_mac_dhcp_server(vm_ip, mac, vlan, dhcp_netmask, dhcp_path)
707
708
709 def delete_mac_dhcp(vm_ip, vlan, mac):
710 """
711 Delete into dhcp conf file the ip assigned to a specific MAC address
712 :param vm_ip: IP address asigned to a VM
713 :param vlan: Segmentation id
714 :param mac: VM vnic mac to be macthed with the IP received
715 :return:
716 """
717
718 dhcp_path = config_dic['ovs_controller_file_path']
719
720 http_controller = config_dic['http_threads'][threading.current_thread().name]
721 dhcp_controller = http_controller.ovim.get_dhcp_controller()
722
723 dhcp_controller.delete_mac_dhcp_server(vm_ip, mac, vlan, dhcp_path)
724
725
726 def create_vxlan_mesh(host_id):
727 """
728 Create vxlan mesh across all openvimc controller and computes.
729 :param host_id: host identifier
730 :param host_id: host identifier
731 :return:
732 """
733 dhcp_compute_name = get_vxlan_interface("dhcp")
734 existing_hosts = get_hosts()
735 if len(existing_hosts['hosts']) > 0:
736 # vlxan mesh creation between openvim controller and computes
737 computes_available = existing_hosts['hosts']
738
739 http_controller = config_dic['http_threads'][threading.current_thread().name]
740 dhcp_controller = http_controller.ovim.get_dhcp_controller()
741
742 for compute in computes_available:
743 vxlan_interface_name = get_vxlan_interface(compute['id'][:8])
744 config_dic['host_threads'][compute['id']].insert_task("new-vxlan", dhcp_compute_name, dhcp_controller.host)
745 dhcp_controller.create_ovs_vxlan_tunnel(vxlan_interface_name, compute['ip_name'])
746
747 # vlxan mesh creation between openvim computes
748 for count, compute_owner in enumerate(computes_available):
749 for compute in computes_available:
750 if compute_owner['id'] == compute['id']:
751 pass
752 else:
753 vxlan_interface_name = get_vxlan_interface(compute_owner['id'][:8])
754 dhcp_controller.create_ovs_vxlan_tunnel(vxlan_interface_name, compute_owner['ip_name'])
755 config_dic['host_threads'][compute['id']].insert_task("new-vxlan",
756 vxlan_interface_name,
757 compute_owner['ip_name'])
758
759
760 def delete_vxlan_mesh(host_id):
761 """
762 Create a task for remove a specific compute of the vlxan mesh
763 :param host_id: host id to be deleted.
764 """
765 existing_hosts = get_hosts()
766 computes_available = existing_hosts['hosts']
767 #
768 vxlan_interface_name = get_vxlan_interface(host_id[:8])
769
770 http_controller = config_dic['http_threads'][threading.current_thread().name]
771 dhcp_host = http_controller.ovim.get_dhcp_controller()
772
773 dhcp_host.delete_ovs_vxlan_tunnel(vxlan_interface_name)
774 # remove bridge from openvim controller if no more computes exist
775 if len(existing_hosts):
776 dhcp_host.delete_ovs_bridge()
777 # Remove vxlan mesh
778 for compute in computes_available:
779 if host_id == compute['id']:
780 pass
781 else:
782 dhcp_host.delete_ovs_vxlan_tunnel(vxlan_interface_name)
783 config_dic['host_threads'][compute['id']].insert_task("del-vxlan", vxlan_interface_name)
784
785
786 def get_vxlan_interface(local_uuid):
787 """
788 Genearte a vxlan interface name
789 :param local_uuid: host id
790 :return: vlxan-8digits
791 """
792 return 'vxlan-' + local_uuid[:8]
793
794
795 @bottle.route(url_base + '/hosts/<host_id>', method='PUT')
796 def http_put_host_id(host_id):
797 '''modify a host into the database. All resources are got and inserted'''
798 my = config_dic['http_threads'][ threading.current_thread().name ]
799 #check permissions
800 if not my.admin:
801 bottle.abort(HTTP_Unauthorized, "Needed admin privileges")
802
803 #parse input data
804 http_content = format_in( host_edit_schema )
805 r = remove_extra_items(http_content, host_edit_schema)
806 if r is not None: print "http_post_host_id: Warning: remove extra items ", r
807 change_keys_http2db(http_content['host'], http2db_host)
808
809 #insert in data base
810 result, content = my.db.edit_host(host_id, http_content['host'])
811 if result >= 0:
812 convert_boolean(content, ('admin_state_up',) )
813 change_keys_http2db(content, http2db_host, reverse=True)
814 data={'host' : content}
815
816 if config_dic['network_type'] == 'ovs':
817 delete_vxlan_mesh(host_id)
818 config_dic['host_threads'][host_id].insert_task("del-ovsbridge")
819
820 #reload thread
821 config_dic['host_threads'][host_id].name = content.get('name',content['ip_name'])
822 config_dic['host_threads'][host_id].user = content['user']
823 config_dic['host_threads'][host_id].host = content['ip_name']
824 config_dic['host_threads'][host_id].insert_task("reload")
825
826 if config_dic['network_type'] == 'ovs':
827 # create mesh with new host data
828 config_dic['host_threads'][host_id].insert_task("new-ovsbridge")
829 create_vxlan_mesh(host_id)
830
831 #print data
832 return format_out(data)
833 else:
834 bottle.abort(HTTP_Bad_Request, content)
835 return
836
837
838
839 @bottle.route(url_base + '/hosts/<host_id>', method='DELETE')
840 def http_delete_host_id(host_id):
841 my = config_dic['http_threads'][ threading.current_thread().name ]
842 #check permissions
843 if not my.admin:
844 bottle.abort(HTTP_Unauthorized, "Needed admin privileges")
845 result, content = my.db.delete_row('hosts', host_id)
846 if result == 0:
847 bottle.abort(HTTP_Not_Found, content)
848 elif result > 0:
849 if config_dic['network_type'] == 'ovs':
850 delete_vxlan_mesh(host_id)
851 # terminate thread
852 if host_id in config_dic['host_threads']:
853 if config_dic['network_type'] == 'ovs':
854 config_dic['host_threads'][host_id].insert_task("del-ovsbridge")
855 config_dic['host_threads'][host_id].insert_task("exit")
856 #return data
857 data={'result' : content}
858 return format_out(data)
859 else:
860 print "http_delete_host_id error",result, content
861 bottle.abort(-result, content)
862 return
863
864
865
866 #
867 # TENANTS
868 #
869
870 @bottle.route(url_base + '/tenants', method='GET')
871 def http_get_tenants():
872 my = config_dic['http_threads'][ threading.current_thread().name ]
873 select_,where_,limit_ = filter_query_string(bottle.request.query, http2db_tenant,
874 ('id','name','description','enabled') )
875 result, content = my.db.get_table(FROM='tenants', SELECT=select_,WHERE=where_,LIMIT=limit_)
876 if result < 0:
877 print "http_get_tenants Error", content
878 bottle.abort(-result, content)
879 else:
880 change_keys_http2db(content, http2db_tenant, reverse=True)
881 convert_boolean(content, ('enabled',))
882 data={'tenants' : content}
883 #data['tenants_links'] = dict([('tenant', row['id']) for row in content])
884 return format_out(data)
885
886 @bottle.route(url_base + '/tenants/<tenant_id>', method='GET')
887 def http_get_tenant_id(tenant_id):
888 my = config_dic['http_threads'][ threading.current_thread().name ]
889 result, content = my.db.get_table(FROM='tenants', SELECT=('uuid','name','description', 'enabled'),WHERE={'uuid': tenant_id} )
890 if result < 0:
891 print "http_get_tenant_id error %d %s" % (result, content)
892 bottle.abort(-result, content)
893 elif result==0:
894 print "http_get_tenant_id tenant '%s' not found" % tenant_id
895 bottle.abort(HTTP_Not_Found, "tenant %s not found" % tenant_id)
896 else:
897 change_keys_http2db(content, http2db_tenant, reverse=True)
898 convert_boolean(content, ('enabled',))
899 data={'tenant' : content[0]}
900 #data['tenants_links'] = dict([('tenant', row['id']) for row in content])
901 return format_out(data)
902
903
904 @bottle.route(url_base + '/tenants', method='POST')
905 def http_post_tenants():
906 '''insert a tenant into the database.'''
907 my = config_dic['http_threads'][ threading.current_thread().name ]
908 #parse input data
909 http_content = format_in( tenant_new_schema )
910 r = remove_extra_items(http_content, tenant_new_schema)
911 if r is not None: print "http_post_tenants: Warning: remove extra items ", r
912 change_keys_http2db(http_content['tenant'], http2db_tenant)
913
914 #insert in data base
915 result, content = my.db.new_tenant(http_content['tenant'])
916
917 if result >= 0:
918 return http_get_tenant_id(content)
919 else:
920 bottle.abort(-result, content)
921 return
922
923 @bottle.route(url_base + '/tenants/<tenant_id>', method='PUT')
924 def http_put_tenant_id(tenant_id):
925 '''update a tenant into the database.'''
926 my = config_dic['http_threads'][ threading.current_thread().name ]
927 #parse input data
928 http_content = format_in( tenant_edit_schema )
929 r = remove_extra_items(http_content, tenant_edit_schema)
930 if r is not None: print "http_put_tenant_id: Warning: remove extra items ", r
931 change_keys_http2db(http_content['tenant'], http2db_tenant)
932
933 #insert in data base
934 result, content = my.db.update_rows('tenants', http_content['tenant'], WHERE={'uuid': tenant_id}, log=True )
935 if result >= 0:
936 return http_get_tenant_id(tenant_id)
937 else:
938 bottle.abort(-result, content)
939 return
940
941 @bottle.route(url_base + '/tenants/<tenant_id>', method='DELETE')
942 def http_delete_tenant_id(tenant_id):
943 my = config_dic['http_threads'][ threading.current_thread().name ]
944 #check permissions
945 r, tenants_flavors = my.db.get_table(FROM='tenants_flavors', SELECT=('flavor_id','tenant_id'), WHERE={'tenant_id': tenant_id})
946 if r<=0:
947 tenants_flavors=()
948 r, tenants_images = my.db.get_table(FROM='tenants_images', SELECT=('image_id','tenant_id'), WHERE={'tenant_id': tenant_id})
949 if r<=0:
950 tenants_images=()
951 result, content = my.db.delete_row('tenants', tenant_id)
952 if result == 0:
953 bottle.abort(HTTP_Not_Found, content)
954 elif result >0:
955 print "alf", tenants_flavors, tenants_images
956 for flavor in tenants_flavors:
957 my.db.delete_row_by_key("flavors", "uuid", flavor['flavor_id'])
958 for image in tenants_images:
959 my.db.delete_row_by_key("images", "uuid", image['image_id'])
960 data={'result' : content}
961 return format_out(data)
962 else:
963 print "http_delete_tenant_id error",result, content
964 bottle.abort(-result, content)
965 return
966
967 #
968 # FLAVORS
969 #
970
971 @bottle.route(url_base + '/<tenant_id>/flavors', method='GET')
972 def http_get_flavors(tenant_id):
973 my = config_dic['http_threads'][ threading.current_thread().name ]
974 #check valid tenant_id
975 result,content = check_valid_tenant(my, tenant_id)
976 if result != 0:
977 bottle.abort(result, content)
978 #obtain data
979 select_,where_,limit_ = filter_query_string(bottle.request.query, http2db_flavor,
980 ('id','name','description','public') )
981 if tenant_id=='any':
982 from_ ='flavors'
983 else:
984 from_ ='tenants_flavors inner join flavors on tenants_flavors.flavor_id=flavors.uuid'
985 where_['tenant_id'] = tenant_id
986 result, content = my.db.get_table(FROM=from_, SELECT=select_, WHERE=where_, LIMIT=limit_)
987 if result < 0:
988 print "http_get_flavors Error", content
989 bottle.abort(-result, content)
990 else:
991 change_keys_http2db(content, http2db_flavor, reverse=True)
992 for row in content:
993 row['links']=[ {'href': "/".join( (my.url_preffix, tenant_id, 'flavors', str(row['id']) ) ), 'rel':'bookmark' } ]
994 data={'flavors' : content}
995 return format_out(data)
996
997 @bottle.route(url_base + '/<tenant_id>/flavors/<flavor_id>', method='GET')
998 def http_get_flavor_id(tenant_id, flavor_id):
999 my = config_dic['http_threads'][ threading.current_thread().name ]
1000 #check valid tenant_id
1001 result,content = check_valid_tenant(my, tenant_id)
1002 if result != 0:
1003 bottle.abort(result, content)
1004 #obtain data
1005 select_,where_,limit_ = filter_query_string(bottle.request.query, http2db_flavor,
1006 ('id','name','description','ram', 'vcpus', 'extended', 'disk', 'public') )
1007 if tenant_id=='any':
1008 from_ ='flavors'
1009 else:
1010 from_ ='tenants_flavors as tf inner join flavors as f on tf.flavor_id=f.uuid'
1011 where_['tenant_id'] = tenant_id
1012 where_['uuid'] = flavor_id
1013 result, content = my.db.get_table(SELECT=select_, FROM=from_, WHERE=where_, LIMIT=limit_)
1014
1015 if result < 0:
1016 print "http_get_flavor_id error %d %s" % (result, content)
1017 bottle.abort(-result, content)
1018 elif result==0:
1019 print "http_get_flavors_id flavor '%s' not found" % str(flavor_id)
1020 bottle.abort(HTTP_Not_Found, 'flavor %s not found' % flavor_id)
1021 else:
1022 change_keys_http2db(content, http2db_flavor, reverse=True)
1023 if 'extended' in content[0] and content[0]['extended'] is not None:
1024 extended = json.loads(content[0]['extended'])
1025 if 'devices' in extended:
1026 change_keys_http2db(extended['devices'], http2db_flavor, reverse=True)
1027 content[0]['extended']=extended
1028 convert_bandwidth(content[0], reverse=True)
1029 content[0]['links']=[ {'href': "/".join( (my.url_preffix, tenant_id, 'flavors', str(content[0]['id']) ) ), 'rel':'bookmark' } ]
1030 data={'flavor' : content[0]}
1031 #data['tenants_links'] = dict([('tenant', row['id']) for row in content])
1032 return format_out(data)
1033
1034
1035 @bottle.route(url_base + '/<tenant_id>/flavors', method='POST')
1036 def http_post_flavors(tenant_id):
1037 '''insert a flavor into the database, and attach to tenant.'''
1038 my = config_dic['http_threads'][ threading.current_thread().name ]
1039 #check valid tenant_id
1040 result,content = check_valid_tenant(my, tenant_id)
1041 if result != 0:
1042 bottle.abort(result, content)
1043 http_content = format_in( flavor_new_schema )
1044 r = remove_extra_items(http_content, flavor_new_schema)
1045 if r is not None: print "http_post_flavors: Warning: remove extra items ", r
1046 change_keys_http2db(http_content['flavor'], http2db_flavor)
1047 extended_dict = http_content['flavor'].pop('extended', None)
1048 if extended_dict is not None:
1049 result, content = check_extended(extended_dict)
1050 if result<0:
1051 print "http_post_flavors wrong input extended error %d %s" % (result, content)
1052 bottle.abort(-result, content)
1053 return
1054 convert_bandwidth(extended_dict)
1055 if 'devices' in extended_dict: change_keys_http2db(extended_dict['devices'], http2db_flavor)
1056 http_content['flavor']['extended'] = json.dumps(extended_dict)
1057 #insert in data base
1058 result, content = my.db.new_flavor(http_content['flavor'], tenant_id)
1059 if result >= 0:
1060 return http_get_flavor_id(tenant_id, content)
1061 else:
1062 print "http_psot_flavors error %d %s" % (result, content)
1063 bottle.abort(-result, content)
1064 return
1065
1066 @bottle.route(url_base + '/<tenant_id>/flavors/<flavor_id>', method='DELETE')
1067 def http_delete_flavor_id(tenant_id, flavor_id):
1068 '''Deletes the flavor_id of a tenant. IT removes from tenants_flavors table.'''
1069 my = config_dic['http_threads'][ threading.current_thread().name ]
1070 #check valid tenant_id
1071 result,content = check_valid_tenant(my, tenant_id)
1072 if result != 0:
1073 bottle.abort(result, content)
1074 return
1075 result, content = my.db.delete_image_flavor('flavor', flavor_id, tenant_id)
1076 if result == 0:
1077 bottle.abort(HTTP_Not_Found, content)
1078 elif result >0:
1079 data={'result' : content}
1080 return format_out(data)
1081 else:
1082 print "http_delete_flavor_id error",result, content
1083 bottle.abort(-result, content)
1084 return
1085
1086 @bottle.route(url_base + '/<tenant_id>/flavors/<flavor_id>/<action>', method='POST')
1087 def http_attach_detach_flavors(tenant_id, flavor_id, action):
1088 '''attach/detach an existing flavor in this tenant. That is insert/remove at tenants_flavors table.'''
1089 #TODO alf: not tested at all!!!
1090 my = config_dic['http_threads'][ threading.current_thread().name ]
1091 #check valid tenant_id
1092 result,content = check_valid_tenant(my, tenant_id)
1093 if result != 0:
1094 bottle.abort(result, content)
1095 if tenant_id=='any':
1096 bottle.abort(HTTP_Bad_Request, "Invalid tenant 'any' with this command")
1097 #check valid action
1098 if action!='attach' and action != 'detach':
1099 bottle.abort(HTTP_Method_Not_Allowed, "actions can be attach or detach")
1100 return
1101
1102 #Ensure that flavor exist
1103 from_ ='tenants_flavors as tf right join flavors as f on tf.flavor_id=f.uuid'
1104 where_={'uuid': flavor_id}
1105 result, content = my.db.get_table(SELECT=('public','tenant_id'), FROM=from_, WHERE=where_)
1106 if result==0:
1107 if action=='attach':
1108 text_error="Flavor '%s' not found" % flavor_id
1109 else:
1110 text_error="Flavor '%s' not found for tenant '%s'" % (flavor_id, tenant_id)
1111 bottle.abort(HTTP_Not_Found, text_error)
1112 return
1113 elif result>0:
1114 flavor=content[0]
1115 if action=='attach':
1116 if flavor['tenant_id']!=None:
1117 bottle.abort(HTTP_Conflict, "Flavor '%s' already attached to tenant '%s'" % (flavor_id, tenant_id))
1118 if flavor['public']=='no' and not my.admin:
1119 #allow only attaching public flavors
1120 bottle.abort(HTTP_Unauthorized, "Needed admin rights to attach a private flavor")
1121 return
1122 #insert in data base
1123 result, content = my.db.new_row('tenants_flavors', {'flavor_id':flavor_id, 'tenant_id': tenant_id})
1124 if result >= 0:
1125 return http_get_flavor_id(tenant_id, flavor_id)
1126 else: #detach
1127 if flavor['tenant_id']==None:
1128 bottle.abort(HTTP_Not_Found, "Flavor '%s' not attached to tenant '%s'" % (flavor_id, tenant_id))
1129 result, content = my.db.delete_row_by_dict(FROM='tenants_flavors', WHERE={'flavor_id':flavor_id, 'tenant_id':tenant_id})
1130 if result>=0:
1131 if flavor['public']=='no':
1132 #try to delete the flavor completely to avoid orphan flavors, IGNORE error
1133 my.db.delete_row_by_dict(FROM='flavors', WHERE={'uuid':flavor_id})
1134 data={'result' : "flavor detached"}
1135 return format_out(data)
1136
1137 #if get here is because an error
1138 print "http_attach_detach_flavors error %d %s" % (result, content)
1139 bottle.abort(-result, content)
1140 return
1141
1142 @bottle.route(url_base + '/<tenant_id>/flavors/<flavor_id>', method='PUT')
1143 def http_put_flavor_id(tenant_id, flavor_id):
1144 '''update a flavor_id into the database.'''
1145 my = config_dic['http_threads'][ threading.current_thread().name ]
1146 #check valid tenant_id
1147 result,content = check_valid_tenant(my, tenant_id)
1148 if result != 0:
1149 bottle.abort(result, content)
1150 #parse input data
1151 http_content = format_in( flavor_update_schema )
1152 r = remove_extra_items(http_content, flavor_update_schema)
1153 if r is not None: print "http_put_flavor_id: Warning: remove extra items ", r
1154 change_keys_http2db(http_content['flavor'], http2db_flavor)
1155 extended_dict = http_content['flavor'].pop('extended', None)
1156 if extended_dict is not None:
1157 result, content = check_extended(extended_dict)
1158 if result<0:
1159 print "http_put_flavor_id wrong input extended error %d %s" % (result, content)
1160 bottle.abort(-result, content)
1161 return
1162 convert_bandwidth(extended_dict)
1163 if 'devices' in extended_dict: change_keys_http2db(extended_dict['devices'], http2db_flavor)
1164 http_content['flavor']['extended'] = json.dumps(extended_dict)
1165 #Ensure that flavor exist
1166 where_={'uuid': flavor_id}
1167 if tenant_id=='any':
1168 from_ ='flavors'
1169 else:
1170 from_ ='tenants_flavors as ti inner join flavors as i on ti.flavor_id=i.uuid'
1171 where_['tenant_id'] = tenant_id
1172 result, content = my.db.get_table(SELECT=('public',), FROM=from_, WHERE=where_)
1173 if result==0:
1174 text_error="Flavor '%s' not found" % flavor_id
1175 if tenant_id!='any':
1176 text_error +=" for tenant '%s'" % flavor_id
1177 bottle.abort(HTTP_Not_Found, text_error)
1178 return
1179 elif result>0:
1180 if content[0]['public']=='yes' and not my.admin:
1181 #allow only modifications over private flavors
1182 bottle.abort(HTTP_Unauthorized, "Needed admin rights to edit a public flavor")
1183 return
1184 #insert in data base
1185 result, content = my.db.update_rows('flavors', http_content['flavor'], {'uuid': flavor_id})
1186
1187 if result < 0:
1188 print "http_put_flavor_id error %d %s" % (result, content)
1189 bottle.abort(-result, content)
1190 return
1191 else:
1192 return http_get_flavor_id(tenant_id, flavor_id)
1193
1194
1195
1196 #
1197 # IMAGES
1198 #
1199
1200 @bottle.route(url_base + '/<tenant_id>/images', method='GET')
1201 def http_get_images(tenant_id):
1202 my = config_dic['http_threads'][ threading.current_thread().name ]
1203 #check valid tenant_id
1204 result,content = check_valid_tenant(my, tenant_id)
1205 if result != 0:
1206 bottle.abort(result, content)
1207 #obtain data
1208 select_,where_,limit_ = filter_query_string(bottle.request.query, http2db_image,
1209 ('id','name','checksum','description','path','public') )
1210 if tenant_id=='any':
1211 from_ ='images'
1212 where_or_ = None
1213 else:
1214 from_ ='tenants_images right join images on tenants_images.image_id=images.uuid'
1215 where_or_ = {'tenant_id': tenant_id, 'public': 'yes'}
1216 result, content = my.db.get_table(SELECT=select_, DISTINCT=True, FROM=from_, WHERE=where_, WHERE_OR=where_or_, WHERE_AND_OR="AND", LIMIT=limit_)
1217 if result < 0:
1218 print "http_get_images Error", content
1219 bottle.abort(-result, content)
1220 else:
1221 change_keys_http2db(content, http2db_image, reverse=True)
1222 #for row in content: row['links']=[ {'href': "/".join( (my.url_preffix, tenant_id, 'images', str(row['id']) ) ), 'rel':'bookmark' } ]
1223 data={'images' : content}
1224 return format_out(data)
1225
1226 @bottle.route(url_base + '/<tenant_id>/images/<image_id>', method='GET')
1227 def http_get_image_id(tenant_id, image_id):
1228 my = config_dic['http_threads'][ threading.current_thread().name ]
1229 #check valid tenant_id
1230 result,content = check_valid_tenant(my, tenant_id)
1231 if result != 0:
1232 bottle.abort(result, content)
1233 #obtain data
1234 select_,where_,limit_ = filter_query_string(bottle.request.query, http2db_image,
1235 ('id','name','checksum','description','progress', 'status','path', 'created', 'updated','public') )
1236 if tenant_id=='any':
1237 from_ ='images'
1238 where_or_ = None
1239 else:
1240 from_ ='tenants_images as ti right join images as i on ti.image_id=i.uuid'
1241 where_or_ = {'tenant_id': tenant_id, 'public': "yes"}
1242 where_['uuid'] = image_id
1243 result, content = my.db.get_table(SELECT=select_, DISTINCT=True, FROM=from_, WHERE=where_, WHERE_OR=where_or_, WHERE_AND_OR="AND", LIMIT=limit_)
1244
1245 if result < 0:
1246 print "http_get_images error %d %s" % (result, content)
1247 bottle.abort(-result, content)
1248 elif result==0:
1249 print "http_get_images image '%s' not found" % str(image_id)
1250 bottle.abort(HTTP_Not_Found, 'image %s not found' % image_id)
1251 else:
1252 convert_datetime2str(content)
1253 change_keys_http2db(content, http2db_image, reverse=True)
1254 if 'metadata' in content[0] and content[0]['metadata'] is not None:
1255 metadata = json.loads(content[0]['metadata'])
1256 content[0]['metadata']=metadata
1257 content[0]['links']=[ {'href': "/".join( (my.url_preffix, tenant_id, 'images', str(content[0]['id']) ) ), 'rel':'bookmark' } ]
1258 data={'image' : content[0]}
1259 #data['tenants_links'] = dict([('tenant', row['id']) for row in content])
1260 return format_out(data)
1261
1262 @bottle.route(url_base + '/<tenant_id>/images', method='POST')
1263 def http_post_images(tenant_id):
1264 '''insert a image into the database, and attach to tenant.'''
1265 my = config_dic['http_threads'][ threading.current_thread().name ]
1266 #check valid tenant_id
1267 result,content = check_valid_tenant(my, tenant_id)
1268 if result != 0:
1269 bottle.abort(result, content)
1270 http_content = format_in(image_new_schema)
1271 r = remove_extra_items(http_content, image_new_schema)
1272 if r is not None: print "http_post_images: Warning: remove extra items ", r
1273 change_keys_http2db(http_content['image'], http2db_image)
1274 metadata_dict = http_content['image'].pop('metadata', None)
1275 if metadata_dict is not None:
1276 http_content['image']['metadata'] = json.dumps(metadata_dict)
1277 #calculate checksum
1278 try:
1279 image_file = http_content['image'].get('path',None)
1280 parsed_url = urlparse.urlparse(image_file)
1281 if parsed_url.scheme == "" and parsed_url.netloc == "":
1282 # The path is a local file
1283 if os.path.exists(image_file):
1284 http_content['image']['checksum'] = md5(image_file)
1285 else:
1286 # The path is a URL. Code should be added to download the image and calculate the checksum
1287 #http_content['image']['checksum'] = md5(downloaded_image)
1288 pass
1289 # Finally, only if we are in test mode and checksum has not been calculated, we calculate it from the path
1290 host_test_mode = True if config_dic['mode']=='test' or config_dic['mode']=="OF only" else False
1291 if host_test_mode:
1292 if 'checksum' not in http_content['image']:
1293 http_content['image']['checksum'] = md5_string(image_file)
1294 else:
1295 # At this point, if the path is a local file and no chechsum has been obtained yet, an error is sent back.
1296 # If it is a URL, no error is sent. Checksum will be an empty string
1297 if parsed_url.scheme == "" and parsed_url.netloc == "" and 'checksum' not in http_content['image']:
1298 content = "Image file not found"
1299 print "http_post_images error: %d %s" % (HTTP_Bad_Request, content)
1300 bottle.abort(HTTP_Bad_Request, content)
1301 except Exception as e:
1302 print "ERROR. Unexpected exception: %s" % (str(e))
1303 bottle.abort(HTTP_Internal_Server_Error, type(e).__name__ + ": " + str(e))
1304 #insert in data base
1305 result, content = my.db.new_image(http_content['image'], tenant_id)
1306 if result >= 0:
1307 return http_get_image_id(tenant_id, content)
1308 else:
1309 print "http_post_images error %d %s" % (result, content)
1310 bottle.abort(-result, content)
1311 return
1312
1313 @bottle.route(url_base + '/<tenant_id>/images/<image_id>', method='DELETE')
1314 def http_delete_image_id(tenant_id, image_id):
1315 '''Deletes the image_id of a tenant. IT removes from tenants_images table.'''
1316 my = config_dic['http_threads'][ threading.current_thread().name ]
1317 #check valid tenant_id
1318 result,content = check_valid_tenant(my, tenant_id)
1319 if result != 0:
1320 bottle.abort(result, content)
1321 result, content = my.db.delete_image_flavor('image', image_id, tenant_id)
1322 if result == 0:
1323 bottle.abort(HTTP_Not_Found, content)
1324 elif result >0:
1325 data={'result' : content}
1326 return format_out(data)
1327 else:
1328 print "http_delete_image_id error",result, content
1329 bottle.abort(-result, content)
1330 return
1331
1332 @bottle.route(url_base + '/<tenant_id>/images/<image_id>/<action>', method='POST')
1333 def http_attach_detach_images(tenant_id, image_id, action):
1334 '''attach/detach an existing image in this tenant. That is insert/remove at tenants_images table.'''
1335 #TODO alf: not tested at all!!!
1336 my = config_dic['http_threads'][ threading.current_thread().name ]
1337 #check valid tenant_id
1338 result,content = check_valid_tenant(my, tenant_id)
1339 if result != 0:
1340 bottle.abort(result, content)
1341 if tenant_id=='any':
1342 bottle.abort(HTTP_Bad_Request, "Invalid tenant 'any' with this command")
1343 #check valid action
1344 if action!='attach' and action != 'detach':
1345 bottle.abort(HTTP_Method_Not_Allowed, "actions can be attach or detach")
1346 return
1347
1348 #Ensure that image exist
1349 from_ ='tenants_images as ti right join images as i on ti.image_id=i.uuid'
1350 where_={'uuid': image_id}
1351 result, content = my.db.get_table(SELECT=('public','tenant_id'), FROM=from_, WHERE=where_)
1352 if result==0:
1353 if action=='attach':
1354 text_error="Image '%s' not found" % image_id
1355 else:
1356 text_error="Image '%s' not found for tenant '%s'" % (image_id, tenant_id)
1357 bottle.abort(HTTP_Not_Found, text_error)
1358 return
1359 elif result>0:
1360 image=content[0]
1361 if action=='attach':
1362 if image['tenant_id']!=None:
1363 bottle.abort(HTTP_Conflict, "Image '%s' already attached to tenant '%s'" % (image_id, tenant_id))
1364 if image['public']=='no' and not my.admin:
1365 #allow only attaching public images
1366 bottle.abort(HTTP_Unauthorized, "Needed admin rights to attach a private image")
1367 return
1368 #insert in data base
1369 result, content = my.db.new_row('tenants_images', {'image_id':image_id, 'tenant_id': tenant_id})
1370 if result >= 0:
1371 return http_get_image_id(tenant_id, image_id)
1372 else: #detach
1373 if image['tenant_id']==None:
1374 bottle.abort(HTTP_Not_Found, "Image '%s' not attached to tenant '%s'" % (image_id, tenant_id))
1375 result, content = my.db.delete_row_by_dict(FROM='tenants_images', WHERE={'image_id':image_id, 'tenant_id':tenant_id})
1376 if result>=0:
1377 if image['public']=='no':
1378 #try to delete the image completely to avoid orphan images, IGNORE error
1379 my.db.delete_row_by_dict(FROM='images', WHERE={'uuid':image_id})
1380 data={'result' : "image detached"}
1381 return format_out(data)
1382
1383 #if get here is because an error
1384 print "http_attach_detach_images error %d %s" % (result, content)
1385 bottle.abort(-result, content)
1386 return
1387
1388 @bottle.route(url_base + '/<tenant_id>/images/<image_id>', method='PUT')
1389 def http_put_image_id(tenant_id, image_id):
1390 '''update a image_id into the database.'''
1391 my = config_dic['http_threads'][ threading.current_thread().name ]
1392 #check valid tenant_id
1393 result,content = check_valid_tenant(my, tenant_id)
1394 if result != 0:
1395 bottle.abort(result, content)
1396 #parse input data
1397 http_content = format_in( image_update_schema )
1398 r = remove_extra_items(http_content, image_update_schema)
1399 if r is not None: print "http_put_image_id: Warning: remove extra items ", r
1400 change_keys_http2db(http_content['image'], http2db_image)
1401 metadata_dict = http_content['image'].pop('metadata', None)
1402 if metadata_dict is not None:
1403 http_content['image']['metadata'] = json.dumps(metadata_dict)
1404 #Ensure that image exist
1405 where_={'uuid': image_id}
1406 if tenant_id=='any':
1407 from_ ='images'
1408 where_or_ = None
1409 else:
1410 from_ ='tenants_images as ti right join images as i on ti.image_id=i.uuid'
1411 where_or_ = {'tenant_id': tenant_id, 'public': 'yes'}
1412 result, content = my.db.get_table(SELECT=('public',), DISTINCT=True, FROM=from_, WHERE=where_, WHERE_OR=where_or_, WHERE_AND_OR="AND")
1413 if result==0:
1414 text_error="Image '%s' not found" % image_id
1415 if tenant_id!='any':
1416 text_error +=" for tenant '%s'" % image_id
1417 bottle.abort(HTTP_Not_Found, text_error)
1418 return
1419 elif result>0:
1420 if content[0]['public']=='yes' and not my.admin:
1421 #allow only modifications over private images
1422 bottle.abort(HTTP_Unauthorized, "Needed admin rights to edit a public image")
1423 return
1424 #insert in data base
1425 result, content = my.db.update_rows('images', http_content['image'], {'uuid': image_id})
1426
1427 if result < 0:
1428 print "http_put_image_id error %d %s" % (result, content)
1429 bottle.abort(-result, content)
1430 return
1431 else:
1432 return http_get_image_id(tenant_id, image_id)
1433
1434
1435 #
1436 # SERVERS
1437 #
1438
1439 @bottle.route(url_base + '/<tenant_id>/servers', method='GET')
1440 def http_get_servers(tenant_id):
1441 my = config_dic['http_threads'][ threading.current_thread().name ]
1442 result,content = check_valid_tenant(my, tenant_id)
1443 if result != 0:
1444 bottle.abort(result, content)
1445 return
1446 #obtain data
1447 select_,where_,limit_ = filter_query_string(bottle.request.query, http2db_server,
1448 ('id','name','description','hostId','imageRef','flavorRef','status', 'tenant_id') )
1449 if tenant_id!='any':
1450 where_['tenant_id'] = tenant_id
1451 result, content = my.db.get_table(SELECT=select_, FROM='instances', WHERE=where_, LIMIT=limit_)
1452 if result < 0:
1453 print "http_get_servers Error", content
1454 bottle.abort(-result, content)
1455 else:
1456 change_keys_http2db(content, http2db_server, reverse=True)
1457 for row in content:
1458 tenant_id = row.pop('tenant_id')
1459 row['links']=[ {'href': "/".join( (my.url_preffix, tenant_id, 'servers', str(row['id']) ) ), 'rel':'bookmark' } ]
1460 data={'servers' : content}
1461 return format_out(data)
1462
1463 @bottle.route(url_base + '/<tenant_id>/servers/<server_id>', method='GET')
1464 def http_get_server_id(tenant_id, server_id):
1465 my = config_dic['http_threads'][ threading.current_thread().name ]
1466 #check valid tenant_id
1467 result,content = check_valid_tenant(my, tenant_id)
1468 if result != 0:
1469 bottle.abort(result, content)
1470 return
1471 #obtain data
1472 result, content = my.db.get_instance(server_id)
1473 if result == 0:
1474 bottle.abort(HTTP_Not_Found, content)
1475 elif result >0:
1476 #change image/flavor-id to id and link
1477 convert_bandwidth(content, reverse=True)
1478 convert_datetime2str(content)
1479 if content["ram"]==0 : del content["ram"]
1480 if content["vcpus"]==0 : del content["vcpus"]
1481 if 'flavor_id' in content:
1482 if content['flavor_id'] is not None:
1483 content['flavor'] = {'id':content['flavor_id'],
1484 'links':[{'href': "/".join( (my.url_preffix, content['tenant_id'], 'flavors', str(content['flavor_id']) ) ), 'rel':'bookmark'}]
1485 }
1486 del content['flavor_id']
1487 if 'image_id' in content:
1488 if content['image_id'] is not None:
1489 content['image'] = {'id':content['image_id'],
1490 'links':[{'href': "/".join( (my.url_preffix, content['tenant_id'], 'images', str(content['image_id']) ) ), 'rel':'bookmark'}]
1491 }
1492 del content['image_id']
1493 change_keys_http2db(content, http2db_server, reverse=True)
1494 if 'extended' in content:
1495 if 'devices' in content['extended']: change_keys_http2db(content['extended']['devices'], http2db_server, reverse=True)
1496
1497 data={'server' : content}
1498 return format_out(data)
1499 else:
1500 bottle.abort(-result, content)
1501 return
1502
1503 @bottle.route(url_base + '/<tenant_id>/servers', method='POST')
1504 def http_post_server_id(tenant_id):
1505 '''deploys a new server'''
1506 my = config_dic['http_threads'][ threading.current_thread().name ]
1507 #check valid tenant_id
1508 result,content = check_valid_tenant(my, tenant_id)
1509 if result != 0:
1510 bottle.abort(result, content)
1511 return
1512 if tenant_id=='any':
1513 bottle.abort(HTTP_Bad_Request, "Invalid tenant 'any' with this command")
1514 #chek input
1515 http_content = format_in( server_new_schema )
1516 r = remove_extra_items(http_content, server_new_schema)
1517 if r is not None: print "http_post_serves: Warning: remove extra items ", r
1518 change_keys_http2db(http_content['server'], http2db_server)
1519 extended_dict = http_content['server'].get('extended', None)
1520 if extended_dict is not None:
1521 result, content = check_extended(extended_dict, True)
1522 if result<0:
1523 print "http_post_servers wrong input extended error %d %s" % (result, content)
1524 bottle.abort(-result, content)
1525 return
1526 convert_bandwidth(extended_dict)
1527 if 'devices' in extended_dict: change_keys_http2db(extended_dict['devices'], http2db_server)
1528
1529 server = http_content['server']
1530 server_start = server.get('start', 'yes')
1531 server['tenant_id'] = tenant_id
1532 #check flavor valid and take info
1533 result, content = my.db.get_table(FROM='tenants_flavors as tf join flavors as f on tf.flavor_id=f.uuid',
1534 SELECT=('ram','vcpus','extended'), WHERE={'uuid':server['flavor_id'], 'tenant_id':tenant_id})
1535 if result<=0:
1536 bottle.abort(HTTP_Not_Found, 'flavor_id %s not found' % server['flavor_id'])
1537 return
1538 server['flavor']=content[0]
1539 #check image valid and take info
1540 result, content = my.db.get_table(FROM='tenants_images as ti right join images as i on ti.image_id=i.uuid',
1541 SELECT=('path', 'metadata', 'image_id'),
1542 WHERE={'uuid':server['image_id'], "status":"ACTIVE"},
1543 WHERE_OR={'tenant_id':tenant_id, 'public': 'yes'},
1544 WHERE_AND_OR="AND",
1545 DISTINCT=True)
1546 if result<=0:
1547 bottle.abort(HTTP_Not_Found, 'image_id %s not found or not ACTIVE' % server['image_id'])
1548 return
1549 for image_dict in content:
1550 if image_dict.get("image_id"):
1551 break
1552 else:
1553 # insert in data base tenants_images
1554 r2, c2 = my.db.new_row('tenants_images', {'image_id': server['image_id'], 'tenant_id': tenant_id})
1555 if r2<=0:
1556 bottle.abort(HTTP_Not_Found, 'image_id %s cannot be used. Error %s' % (server['image_id'], c2))
1557 return
1558 server['image']={"path": content[0]["path"], "metadata": content[0]["metadata"]}
1559 if "hosts_id" in server:
1560 result, content = my.db.get_table(FROM='hosts', SELECT=('uuid',), WHERE={'uuid': server['host_id']})
1561 if result<=0:
1562 bottle.abort(HTTP_Not_Found, 'hostId %s not found' % server['host_id'])
1563 return
1564 #print json.dumps(server, indent=4)
1565
1566 result, content = ht.create_server(server, config_dic['db'], config_dic['db_lock'], config_dic['mode']=='normal')
1567
1568 if result >= 0:
1569 #Insert instance to database
1570 nets=[]
1571 print
1572 print "inserting at DB"
1573 print
1574 if server_start == 'no':
1575 content['status'] = 'INACTIVE'
1576 dhcp_nets_id = []
1577 for net in http_content['server']['networks']:
1578 if net['type'] == 'instance:ovs':
1579 dhcp_nets_id.append(get_network_id(net['net_id']))
1580
1581 ports_to_free=[]
1582 new_instance_result, new_instance = my.db.new_instance(content, nets, ports_to_free)
1583 if new_instance_result < 0:
1584 print "Error http_post_servers() :", new_instance_result, new_instance
1585 bottle.abort(-new_instance_result, new_instance)
1586 return
1587 print
1588 print "inserted at DB"
1589 print
1590 for port in ports_to_free:
1591 r,c = config_dic['host_threads'][ server['host_id'] ].insert_task( 'restore-iface',*port )
1592 if r < 0:
1593 print ' http_post_servers ERROR RESTORE IFACE!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!' + c
1594 #updata nets
1595 for net in nets:
1596 r,c = config_dic['of_thread'].insert_task("update-net", net)
1597 if r < 0:
1598 print ':http_post_servers ERROR UPDATING NETS !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!' + c
1599
1600
1601 #look for dhcp ip address
1602 r2, c2 = my.db.get_table(FROM="ports", SELECT=["mac", "ip_address", "net_id"], WHERE={"instance_id": new_instance})
1603 if r2 >0:
1604 for iface in c2:
1605 if config_dic.get("dhcp_server") and iface["net_id"] in config_dic["dhcp_nets"]:
1606 #print "dhcp insert add task"
1607 r,c = config_dic['dhcp_thread'].insert_task("add", iface["mac"])
1608 if r < 0:
1609 print ':http_post_servers ERROR UPDATING dhcp_server !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!' + c
1610
1611 #ensure compute contain the bridge for ovs networks:
1612 server_net = get_network_id(iface['net_id'])
1613 if server_net["network"].get('provider:physical', "")[:3] == 'OVS':
1614 vlan = str(server_net['network']['provider:vlan'])
1615 dhcp_enable = bool(server_net['network']['enable_dhcp'])
1616 if dhcp_enable:
1617 dhcp_firt_ip = str(server_net['network']['dhcp_first_ip'])
1618 dhcp_last_ip = str(server_net['network']['dhcp_last_ip'])
1619 dhcp_cidr = str(server_net['network']['cidr'])
1620 gateway = str(server_net['network']['gateway'])
1621 vm_dhcp_ip = c2[0]["ip_address"]
1622 config_dic['host_threads'][server['host_id']].insert_task("create-ovs-bridge-port", vlan)
1623
1624 set_mac_dhcp(vm_dhcp_ip, vlan, dhcp_firt_ip, dhcp_last_ip, dhcp_cidr, c2[0]['mac'])
1625 http_controller = config_dic['http_threads'][threading.current_thread().name]
1626 http_controller.ovim.launch_dhcp_server(vlan, dhcp_firt_ip, dhcp_last_ip, dhcp_cidr, gateway)
1627
1628 #Start server
1629 server['uuid'] = new_instance
1630 server_start = server.get('start', 'yes')
1631
1632 if server_start != 'no':
1633 server['paused'] = True if server_start == 'paused' else False
1634 server['action'] = {"start":None}
1635 server['status'] = "CREATING"
1636 #Program task
1637 r,c = config_dic['host_threads'][ server['host_id'] ].insert_task( 'instance',server )
1638 if r<0:
1639 my.db.update_rows('instances', {'status':"ERROR"}, {'uuid':server['uuid'], 'last_error':c}, log=True)
1640
1641 return http_get_server_id(tenant_id, new_instance)
1642 else:
1643 bottle.abort(HTTP_Bad_Request, content)
1644 return
1645
1646 def http_server_action(server_id, tenant_id, action):
1647 '''Perform actions over a server as resume, reboot, terminate, ...'''
1648 my = config_dic['http_threads'][ threading.current_thread().name ]
1649 server={"uuid": server_id, "action":action}
1650 where={'uuid': server_id}
1651 if tenant_id!='any':
1652 where['tenant_id']= tenant_id
1653 result, content = my.db.get_table(FROM='instances', WHERE=where)
1654 if result == 0:
1655 bottle.abort(HTTP_Not_Found, "server %s not found" % server_id)
1656 return
1657 if result < 0:
1658 print "http_post_server_action error getting data %d %s" % (result, content)
1659 bottle.abort(HTTP_Internal_Server_Error, content)
1660 return
1661 server.update(content[0])
1662 tenant_id = server["tenant_id"]
1663
1664 #TODO check a right content
1665 new_status = None
1666 if 'terminate' in action:
1667 new_status='DELETING'
1668 elif server['status'] == 'ERROR': #or server['status'] == 'CREATING':
1669 if 'terminate' not in action and 'rebuild' not in action:
1670 bottle.abort(HTTP_Method_Not_Allowed, "Server is in ERROR status, must be rebuit or deleted ")
1671 return
1672 # elif server['status'] == 'INACTIVE':
1673 # if 'start' not in action and 'createImage' not in action:
1674 # bottle.abort(HTTP_Method_Not_Allowed, "The only possible action over an instance in 'INACTIVE' status is 'start'")
1675 # return
1676 # if 'start' in action:
1677 # new_status='CREATING'
1678 # server['paused']='no'
1679 # elif server['status'] == 'PAUSED':
1680 # if 'resume' not in action:
1681 # bottle.abort(HTTP_Method_Not_Allowed, "The only possible action over an instance in 'PAUSED' status is 'resume'")
1682 # return
1683 # elif server['status'] == 'ACTIVE':
1684 # if 'pause' not in action and 'reboot'not in action and 'shutoff'not in action:
1685 # bottle.abort(HTTP_Method_Not_Allowed, "The only possible action over an instance in 'ACTIVE' status is 'pause','reboot' or 'shutoff'")
1686 # return
1687
1688 if 'start' in action or 'createImage' in action or 'rebuild' in action:
1689 #check image valid and take info
1690 image_id = server['image_id']
1691 if 'createImage' in action:
1692 if 'imageRef' in action['createImage']:
1693 image_id = action['createImage']['imageRef']
1694 elif 'disk' in action['createImage']:
1695 result, content = my.db.get_table(FROM='instance_devices',
1696 SELECT=('image_id','dev'), WHERE={'instance_id':server['uuid'],"type":"disk"})
1697 if result<=0:
1698 bottle.abort(HTTP_Not_Found, 'disk not found for server')
1699 return
1700 elif result>1:
1701 disk_id=None
1702 if action['createImage']['imageRef']['disk'] != None:
1703 for disk in content:
1704 if disk['dev'] == action['createImage']['imageRef']['disk']:
1705 disk_id = disk['image_id']
1706 break
1707 if disk_id == None:
1708 bottle.abort(HTTP_Not_Found, 'disk %s not found for server' % action['createImage']['imageRef']['disk'])
1709 return
1710 else:
1711 bottle.abort(HTTP_Not_Found, 'more than one disk found for server' )
1712 return
1713 image_id = disk_id
1714 else: #result==1
1715 image_id = content[0]['image_id']
1716
1717 result, content = my.db.get_table(FROM='tenants_images as ti right join images as i on ti.image_id=i.uuid',
1718 SELECT=('path','metadata'), WHERE={'uuid':image_id, "status":"ACTIVE"},
1719 WHERE_OR={'tenant_id':tenant_id, 'public': 'yes'}, WHERE_AND_OR="AND", DISTINCT=True)
1720 if result<=0:
1721 bottle.abort(HTTP_Not_Found, 'image_id %s not found or not ACTIVE' % image_id)
1722 return
1723 if content[0]['metadata'] is not None:
1724 try:
1725 metadata = json.loads(content[0]['metadata'])
1726 except:
1727 return -HTTP_Internal_Server_Error, "Can not decode image metadata"
1728 content[0]['metadata']=metadata
1729 else:
1730 content[0]['metadata'] = {}
1731 server['image']=content[0]
1732 if 'createImage' in action:
1733 action['createImage']['source'] = {'image_id': image_id, 'path': content[0]['path']}
1734 if 'createImage' in action:
1735 #Create an entry in Database for the new image
1736 new_image={'status':'BUILD', 'progress': 0 }
1737 new_image_metadata=content[0]
1738 if 'metadata' in server['image'] and server['image']['metadata'] != None:
1739 new_image_metadata.update(server['image']['metadata'])
1740 new_image_metadata = {"use_incremental":"no"}
1741 if 'metadata' in action['createImage']:
1742 new_image_metadata.update(action['createImage']['metadata'])
1743 new_image['metadata'] = json.dumps(new_image_metadata)
1744 new_image['name'] = action['createImage'].get('name', None)
1745 new_image['description'] = action['createImage'].get('description', None)
1746 new_image['uuid']=my.db.new_uuid()
1747 if 'path' in action['createImage']:
1748 new_image['path'] = action['createImage']['path']
1749 else:
1750 new_image['path']="/provisional/path/" + new_image['uuid']
1751 result, image_uuid = my.db.new_image(new_image, tenant_id)
1752 if result<=0:
1753 bottle.abort(HTTP_Bad_Request, 'Error: ' + image_uuid)
1754 return
1755 server['new_image'] = new_image
1756
1757
1758 #Program task
1759 r,c = config_dic['host_threads'][ server['host_id'] ].insert_task( 'instance',server )
1760 if r<0:
1761 print "Task queue full at host ", server['host_id']
1762 bottle.abort(HTTP_Request_Timeout, c)
1763 if 'createImage' in action and result >= 0:
1764 return http_get_image_id(tenant_id, image_uuid)
1765
1766 #Update DB only for CREATING or DELETING status
1767 data={'result' : 'in process'}
1768 if new_status != None and new_status == 'DELETING':
1769 nets=[]
1770 ports_to_free=[]
1771
1772 net_ovs_list = []
1773 #look for dhcp ip address
1774 r2, c2 = my.db.get_table(FROM="ports", SELECT=["mac", "net_id"], WHERE={"instance_id": server_id})
1775 r, c = my.db.delete_instance(server_id, tenant_id, nets, ports_to_free, net_ovs_list, "requested by http")
1776 for port in ports_to_free:
1777 r1,c1 = config_dic['host_threads'][ server['host_id'] ].insert_task( 'restore-iface',*port )
1778 if r1 < 0:
1779 print ' http_post_server_action error at server deletion ERROR resore-iface !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!' + c1
1780 data={'result' : 'deleting in process, but ifaces cannot be restored!!!!!'}
1781 for net in nets:
1782 r1,c1 = config_dic['of_thread'].insert_task("update-net", net)
1783 if r1 < 0:
1784 print ' http_post_server_action error at server deletion ERROR UPDATING NETS !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!' + c1
1785 data={'result' : 'deleting in process, but openflow rules cannot be deleted!!!!!'}
1786 #look for dhcp ip address
1787 if r2 >0 and config_dic.get("dhcp_server"):
1788 for iface in c2:
1789 if iface["net_id"] in config_dic["dhcp_nets"]:
1790 r,c = config_dic['dhcp_thread'].insert_task("del", iface["mac"])
1791 #print "dhcp insert del task"
1792 if r < 0:
1793 print ':http_post_servers ERROR UPDATING dhcp_server !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!' + c
1794 # delete ovs-port and linux bridge, contains a list of tuple (net_id,vlan)
1795 for net in net_ovs_list:
1796 mac = str(net[3])
1797 vm_ip = str(net[2])
1798 vlan = str(net[1])
1799 net_id = net[0]
1800 delete_dhcp_ovs_bridge(vlan, net_id)
1801 delete_mac_dhcp(vm_ip, vlan, mac)
1802 config_dic['host_threads'][server['host_id']].insert_task('del-ovs-port', vlan, net_id)
1803 return format_out(data)
1804
1805
1806
1807 @bottle.route(url_base + '/<tenant_id>/servers/<server_id>', method='DELETE')
1808 def http_delete_server_id(tenant_id, server_id):
1809 '''delete a server'''
1810 my = config_dic['http_threads'][ threading.current_thread().name ]
1811 #check valid tenant_id
1812 result,content = check_valid_tenant(my, tenant_id)
1813 if result != 0:
1814 bottle.abort(result, content)
1815 return
1816
1817 return http_server_action(server_id, tenant_id, {"terminate":None} )
1818
1819
1820 @bottle.route(url_base + '/<tenant_id>/servers/<server_id>/action', method='POST')
1821 def http_post_server_action(tenant_id, server_id):
1822 '''take an action over a server'''
1823 my = config_dic['http_threads'][ threading.current_thread().name ]
1824 #check valid tenant_id
1825 result,content = check_valid_tenant(my, tenant_id)
1826 if result != 0:
1827 bottle.abort(result, content)
1828 return
1829 http_content = format_in( server_action_schema )
1830 #r = remove_extra_items(http_content, server_action_schema)
1831 #if r is not None: print "http_post_server_action: Warning: remove extra items ", r
1832
1833 return http_server_action(server_id, tenant_id, http_content)
1834
1835 #
1836 # NETWORKS
1837 #
1838
1839
1840 @bottle.route(url_base + '/networks', method='GET')
1841 def http_get_networks():
1842 try:
1843 my = config_dic['http_threads'][threading.current_thread().name]
1844 # obtain data
1845 select_, where_, limit_ = filter_query_string(bottle.request.query, http2db_network,
1846 ('id', 'name', 'tenant_id', 'type',
1847 'shared', 'provider:vlan', 'status', 'last_error',
1848 'admin_state_up', 'provider:physical'))
1849 if "tenant_id" in where_:
1850 del where_["tenant_id"]
1851 content = my.ovim.get_networks(select_, where_, limit_)
1852
1853 delete_nulls(content)
1854 change_keys_http2db(content, http2db_network, reverse=True)
1855 data = {'networks': content}
1856 return format_out(data)
1857
1858 except ovim.ovimException as e:
1859 my.logger.error(str(e), exc_info=True)
1860 bottle.abort(e.http_code, str(e))
1861 except Exception as e:
1862 my.logger.error(str(e), exc_info=True)
1863 bottle.abort(HTTP_Bad_Request, str(e))
1864
1865
1866 @bottle.route(url_base + '/networks/<network_id>', method='GET')
1867 def http_get_network_id(network_id):
1868 data = get_network_id(network_id)
1869 return format_out(data)
1870
1871
1872 def get_network_id(network_id):
1873 try:
1874 my = config_dic['http_threads'][threading.current_thread().name]
1875 # obtain data
1876 where_ = bottle.request.query
1877 where_['uuid'] = network_id
1878 content = my.ovim.get_network_by_id(where_)
1879
1880 convert_boolean(content, ('shared', 'admin_state_up', 'enable_dhcp'))
1881 change_keys_http2db(content, http2db_network, reverse=True)
1882
1883 # content[0]['ports'] = delete_nulls(content[0]['ports'])
1884 delete_nulls(content[0])
1885 data = {'network': content[0]}
1886 return data
1887 except ovim.ovimException as e:
1888 my.logger.error(str(e), exc_info=True)
1889 bottle.abort(e.http_code, str(e))
1890 except Exception as e:
1891 my.logger.error(str(e), exc_info=True)
1892 bottle.abort(HTTP_Bad_Request, str(e))
1893
1894
1895 @bottle.route(url_base + '/networks', method='POST')
1896 def http_post_networks():
1897 """
1898 Insert a network into the database.
1899 :return:
1900 """
1901 try:
1902 my = config_dic['http_threads'][threading.current_thread().name]
1903 # parse input data
1904 http_content = format_in(network_new_schema )
1905 r = remove_extra_items(http_content, network_new_schema)
1906 if r is not None:
1907 print "http_post_networks: Warning: remove extra items ", r
1908 change_keys_http2db(http_content['network'], http2db_network)
1909 network = http_content['network']
1910 content = my.ovim.set_network(network)
1911 return http_get_network_id(content)
1912 except ovim.ovimException as e:
1913 my.logger.error(str(e), exc_info=True)
1914 bottle.abort(e.http_code, str(e))
1915 except Exception as e:
1916 my.logger.error(str(e), exc_info=True)
1917 bottle.abort(HTTP_Bad_Request, str(e))
1918
1919
1920 @bottle.route(url_base + '/networks/<network_id>', method='PUT')
1921 def http_put_network_id(network_id):
1922 '''update a network_id into the database.'''
1923 my = config_dic['http_threads'][ threading.current_thread().name ]
1924 #parse input data
1925 http_content = format_in( network_update_schema )
1926 r = remove_extra_items(http_content, network_update_schema)
1927 change_keys_http2db(http_content['network'], http2db_network)
1928 network=http_content['network']
1929
1930 #Look for the previous data
1931 where_ = {'uuid': network_id}
1932 result, network_old = my.db.get_table(FROM='nets', WHERE=where_)
1933 if result < 0:
1934 print "http_put_network_id error %d %s" % (result, network_old)
1935 bottle.abort(-result, network_old)
1936 return
1937 elif result==0:
1938 print "http_put_network_id network '%s' not found" % network_id
1939 bottle.abort(HTTP_Not_Found, 'network %s not found' % network_id)
1940 return
1941 #get ports
1942 nbports, content = my.db.get_table(FROM='ports', SELECT=('uuid as port_id',),
1943 WHERE={'net_id': network_id}, LIMIT=100)
1944 if result < 0:
1945 print "http_put_network_id error %d %s" % (result, network_old)
1946 bottle.abort(-result, content)
1947 return
1948 if nbports>0:
1949 if 'type' in network and network['type'] != network_old[0]['type']:
1950 bottle.abort(HTTP_Method_Not_Allowed, "Can not change type of network while having ports attached")
1951 if 'vlan' in network and network['vlan'] != network_old[0]['vlan']:
1952 bottle.abort(HTTP_Method_Not_Allowed, "Can not change vlan of network while having ports attached")
1953
1954 #check valid params
1955 net_provider = network.get('provider', network_old[0]['provider'])
1956 net_type = network.get('type', network_old[0]['type'])
1957 net_bind_net = network.get("bind_net")
1958 net_bind_type= network.get("bind_type")
1959 if net_bind_net != None:
1960 #look for a valid net
1961 if check_valid_uuid(net_bind_net):
1962 net_bind_key = "uuid"
1963 else:
1964 net_bind_key = "name"
1965 result, content = my.db.get_table(FROM='nets', WHERE={net_bind_key: net_bind_net} )
1966 if result<0:
1967 bottle.abort(HTTP_Internal_Server_Error, 'getting nets from db ' + content)
1968 return
1969 elif result==0:
1970 bottle.abort(HTTP_Bad_Request, "bind_net %s '%s'not found" % (net_bind_key, net_bind_net) )
1971 return
1972 elif result>1:
1973 bottle.abort(HTTP_Bad_Request, "more than one bind_net %s '%s' found, use uuid" % (net_bind_key, net_bind_net) )
1974 return
1975 network["bind_net"] = content[0]["uuid"]
1976 if net_bind_type != None:
1977 if net_bind_type[0:5] != "vlan:":
1978 bottle.abort(HTTP_Bad_Request, "bad format for 'bind_type', must be 'vlan:<tag>'")
1979 return
1980 if int(net_bind_type[5:]) > 4095 or int(net_bind_type[5:])<=0 :
1981 bottle.abort(HTTP_Bad_Request, "bad format for 'bind_type', must be 'vlan:<tag>' with a tag between 1 and 4095")
1982 return
1983 if net_provider!=None:
1984 if net_provider[:9]=="openflow:":
1985 if net_type!="ptp" and net_type!="data":
1986 bottle.abort(HTTP_Bad_Request, "Only 'ptp' or 'data' net types can be bound to 'openflow'")
1987 else:
1988 if net_type!="bridge_man" and net_type!="bridge_data":
1989 bottle.abort(HTTP_Bad_Request, "Only 'bridge_man' or 'bridge_data' net types can be bound to 'bridge', 'macvtap' or 'default")
1990
1991 #insert in data base
1992 result, content = my.db.update_rows('nets', network, WHERE={'uuid': network_id}, log=True )
1993 if result >= 0:
1994 if result>0: # and nbports>0 and 'admin_state_up' in network and network['admin_state_up'] != network_old[0]['admin_state_up']:
1995 r,c = config_dic['of_thread'].insert_task("update-net", network_id)
1996 if r < 0:
1997 print "http_put_network_id error while launching openflow rules"
1998 bottle.abort(HTTP_Internal_Server_Error, c)
1999 if config_dic.get("dhcp_server"):
2000 if network_id in config_dic["dhcp_nets"]:
2001 config_dic["dhcp_nets"].remove(network_id)
2002 print "dhcp_server: delete net", network_id
2003 if network.get("name", network_old["name"]) in config_dic["dhcp_server"].get("nets", () ):
2004 config_dic["dhcp_nets"].append(network_id)
2005 print "dhcp_server: add new net", network_id
2006 else:
2007 net_bind = network.get("bind", network_old["bind"] )
2008 if net_bind and net_bind[:7]=="bridge:" and net_bind[7:] in config_dic["dhcp_server"].get("bridge_ifaces", () ):
2009 config_dic["dhcp_nets"].append(network_id)
2010 print "dhcp_server: add new net", network_id
2011 return http_get_network_id(network_id)
2012 else:
2013 bottle.abort(-result, content)
2014 return
2015
2016
2017 @bottle.route(url_base + '/networks/<network_id>', method='DELETE')
2018 def http_delete_network_id(network_id):
2019 '''delete a network_id from the database.'''
2020 my = config_dic['http_threads'][ threading.current_thread().name ]
2021
2022 #delete from the data base
2023 result, content = my.db.delete_row('nets', network_id )
2024
2025 if result == 0:
2026 bottle.abort(HTTP_Not_Found, content)
2027 elif result >0:
2028 for brnet in config_dic['bridge_nets']:
2029 if brnet[3]==network_id:
2030 brnet[3]=None
2031 break
2032 if config_dic.get("dhcp_server") and network_id in config_dic["dhcp_nets"]:
2033 config_dic["dhcp_nets"].remove(network_id)
2034 print "dhcp_server: delete net", network_id
2035 data={'result' : content}
2036 return format_out(data)
2037 else:
2038 print "http_delete_network_id error",result, content
2039 bottle.abort(-result, content)
2040 return
2041 #
2042 # OPENFLOW
2043 #
2044
2045
2046 @bottle.route(url_base + '/networks/<network_id>/openflow', method='GET')
2047 def http_get_openflow_id(network_id):
2048 """
2049 To obtain the list of openflow rules of a network
2050 :param network_id: network id
2051 :return:
2052 """
2053
2054 # ignore input data
2055 if network_id == 'all':
2056 network_id = None
2057 try:
2058 my = config_dic['http_threads'][threading.current_thread().name]
2059 content = my.ovim.get_openflow_rules(network_id)
2060 data = {'openflow-rules': content}
2061 except ovim.ovimException as e:
2062 my.logger.error(str(e), exc_info=True)
2063 bottle.abort(e.http_code, str(e))
2064 except Exception as e:
2065 my.logger.error(str(e), exc_info=True)
2066 bottle.abort(HTTP_Bad_Request, str(e))
2067
2068 return format_out(data)
2069
2070
2071 @bottle.route(url_base + '/networks/<network_id>/openflow', method='PUT')
2072 def http_put_openflow_id(network_id):
2073 """
2074 To make actions over the net. The action is to reinstall the openflow rules
2075 network_id can be 'all'
2076 :param network_id: network id
2077 :return:
2078 """
2079 my = config_dic['http_threads'][threading.current_thread().name]
2080
2081 if not my.admin:
2082 bottle.abort(HTTP_Unauthorized, "Needed admin privileges")
2083
2084 if network_id == 'all':
2085 network_id = None
2086
2087 try:
2088 result = my.ovim.update_openflow_rules(network_id)
2089 except ovim.ovimException as e:
2090 my.logger.error(str(e), exc_info=True)
2091 bottle.abort(e.http_code, str(e))
2092 except Exception as e:
2093 my.logger.error(str(e), exc_info=True)
2094 bottle.abort(HTTP_Bad_Request, str(e))
2095
2096 data = {'result': str(result) + " nets updates"}
2097 return format_out(data)
2098
2099
2100 @bottle.route(url_base + '/networks/clear/openflow', method='DELETE')
2101 def http_clear_openflow_rules():
2102 """
2103 To make actions over the net. The action is to delete ALL openflow rules
2104 :return:
2105 """
2106 my = config_dic['http_threads'][ threading.current_thread().name]
2107
2108 if not my.admin:
2109 bottle.abort(HTTP_Unauthorized, "Needed admin privileges")
2110 try:
2111 my.ovim.clear_openflow_rules()
2112 except ovim.ovimException as e:
2113 my.logger.error(str(e), exc_info=True)
2114 bottle.abort(e.http_code, str(e))
2115 except Exception as e:
2116 my.logger.error(str(e), exc_info=True)
2117 bottle.abort(HTTP_Bad_Request, str(e))
2118
2119 data = {'result': " Clearing openflow rules in process"}
2120 return format_out(data)
2121
2122
2123 @bottle.route(url_base + '/networks/openflow/ports', method='GET')
2124 def http_get_openflow_ports():
2125 """
2126 Obtain switch ports names of openflow controller
2127 :return:
2128 """
2129 my = config_dic['http_threads'][threading.current_thread().name]
2130 ports = my.ovim.get_openflow_ports()
2131 data = {'ports': ports}
2132 return format_out(data)
2133 #
2134 # PORTS
2135 #
2136
2137
2138 @bottle.route(url_base + '/ports', method='GET')
2139 def http_get_ports():
2140 #obtain data
2141 my = config_dic['http_threads'][ threading.current_thread().name ]
2142 select_,where_,limit_ = filter_query_string(bottle.request.query, http2db_port,
2143 ('id','name','tenant_id','network_id','vpci','mac_address','device_owner','device_id',
2144 'binding:switch_port','binding:vlan','bandwidth','status','admin_state_up','ip_address') )
2145 try:
2146 ports = my.ovim.get_ports(columns=select_, filter=where_, limit=limit_)
2147 delete_nulls(ports)
2148 change_keys_http2db(ports, http2db_port, reverse=True)
2149 data={'ports' : ports}
2150 return format_out(data)
2151 except ovim.ovimException as e:
2152 my.logger.error(str(e), exc_info=True)
2153 bottle.abort(e.http_code, str(e))
2154 except Exception as e:
2155 my.logger.error(str(e), exc_info=True)
2156 bottle.abort(HTTP_Bad_Request, str(e))
2157
2158 @bottle.route(url_base + '/ports/<port_id>', method='GET')
2159 def http_get_port_id(port_id):
2160 my = config_dic['http_threads'][ threading.current_thread().name ]
2161 try:
2162 ports = my.ovim.get_ports(filter={"uuid": port_id})
2163 if not ports:
2164 bottle.abort(HTTP_Not_Found, 'port %s not found' % port_id)
2165 return
2166 delete_nulls(ports)
2167 change_keys_http2db(ports, http2db_port, reverse=True)
2168 data = {'port': ports[0]}
2169 return format_out(data)
2170 except ovim.ovimException as e:
2171 my.logger.error(str(e), exc_info=True)
2172 bottle.abort(e.http_code, str(e))
2173 except Exception as e:
2174 my.logger.error(str(e), exc_info=True)
2175 bottle.abort(HTTP_Bad_Request, str(e))
2176
2177 @bottle.route(url_base + '/ports', method='POST')
2178 def http_post_ports():
2179 '''insert an external port into the database.'''
2180 my = config_dic['http_threads'][ threading.current_thread().name ]
2181 if not my.admin:
2182 bottle.abort(HTTP_Unauthorized, "Needed admin privileges")
2183 #parse input data
2184 http_content = format_in( port_new_schema )
2185 r = remove_extra_items(http_content, port_new_schema)
2186 if r is not None: print "http_post_ports: Warning: remove extra items ", r
2187 change_keys_http2db(http_content['port'], http2db_port)
2188 port=http_content['port']
2189 try:
2190 port_id = my.ovim.new_port(port)
2191 ports = my.ovim.get_ports(filter={"uuid": port_id})
2192 if not ports:
2193 bottle.abort(HTTP_Internal_Server_Error, "port '{}' inserted but not found at database".format(port_id))
2194 return
2195 delete_nulls(ports)
2196 change_keys_http2db(ports, http2db_port, reverse=True)
2197 data = {'port': ports[0]}
2198 return format_out(data)
2199 except ovim.ovimException as e:
2200 my.logger.error(str(e), exc_info=True)
2201 bottle.abort(e.http_code, str(e))
2202 except Exception as e:
2203 my.logger.error(str(e), exc_info=True)
2204 bottle.abort(HTTP_Bad_Request, str(e))
2205
2206 @bottle.route(url_base + '/ports/<port_id>', method='PUT')
2207 def http_put_port_id(port_id):
2208 '''update a port_id into the database.'''
2209 my = config_dic['http_threads'][ threading.current_thread().name ]
2210 #parse input data
2211 http_content = format_in( port_update_schema )
2212 change_keys_http2db(http_content['port'], http2db_port)
2213 port_dict=http_content['port']
2214
2215 for k in ('vlan', 'switch_port', 'mac_address', 'tenant_id'):
2216 if k in port_dict and not my.admin:
2217 bottle.abort(HTTP_Unauthorized, "Needed admin privileges for changing " + k)
2218 return
2219 try:
2220 port_id = my.ovim.edit_port(port_id, port_dict, my.admin)
2221 ports = my.ovim.get_ports(filter={"uuid": port_id})
2222 if not ports:
2223 bottle.abort(HTTP_Internal_Server_Error, "port '{}' edited but not found at database".format(port_id))
2224 return
2225 delete_nulls(ports)
2226 change_keys_http2db(ports, http2db_port, reverse=True)
2227 data = {'port': ports[0]}
2228 return format_out(data)
2229 except ovim.ovimException as e:
2230 my.logger.error(str(e), exc_info=True)
2231 bottle.abort(e.http_code, str(e))
2232 except Exception as e:
2233 my.logger.error(str(e), exc_info=True)
2234 bottle.abort(HTTP_Bad_Request, str(e))
2235
2236
2237 @bottle.route(url_base + '/ports/<port_id>', method='DELETE')
2238 def http_delete_port_id(port_id):
2239 '''delete a port_id from the database.'''
2240 my = config_dic['http_threads'][ threading.current_thread().name ]
2241 if not my.admin:
2242 bottle.abort(HTTP_Unauthorized, "Needed admin privileges")
2243 return
2244 try:
2245 result = my.ovim.delete_port(port_id)
2246 data = {'result': result}
2247 return format_out(data)
2248 except ovim.ovimException as e:
2249 my.logger.error(str(e), exc_info=True)
2250 bottle.abort(e.http_code, str(e))
2251 except Exception as e:
2252 my.logger.error(str(e), exc_info=True)
2253 bottle.abort(HTTP_Bad_Request, str(e))
2254
2255