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