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