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