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