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