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