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