store OVS network ports at database as type instance:ovs
[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:
1473 for iface in c2:
1474 if config_dic.get("dhcp_server") and 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 #ensure compute contain the bridge for ovs networks:
1481 server_net = get_network_id(iface['net_id'])
1482 if server_net["network"].get('provider:physical', "")[:3] == 'OVS':
1483 vlan = str(server_net['network']['provider:vlan'])
1484 config_dic['host_threads'][server['host_id']].insert_task("create-ovs-bridge-port", vlan)
1485 #Start server
1486
1487 server['uuid'] = new_instance
1488 server_start = server.get('start', 'yes')
1489
1490 if server_start != 'no':
1491 server['paused'] = True if server_start == 'paused' else False
1492 server['action'] = {"start":None}
1493 server['status'] = "CREATING"
1494 #Program task
1495 r,c = config_dic['host_threads'][ server['host_id'] ].insert_task( 'instance',server )
1496 if r<0:
1497 my.db.update_rows('instances', {'status':"ERROR"}, {'uuid':server['uuid'], 'last_error':c}, log=True)
1498
1499 return http_get_server_id(tenant_id, new_instance)
1500 else:
1501 bottle.abort(HTTP_Bad_Request, content)
1502 return
1503
1504 def http_server_action(server_id, tenant_id, action):
1505 '''Perform actions over a server as resume, reboot, terminate, ...'''
1506 my = config_dic['http_threads'][ threading.current_thread().name ]
1507 server={"uuid": server_id, "action":action}
1508 where={'uuid': server_id}
1509 if tenant_id!='any':
1510 where['tenant_id']= tenant_id
1511 result, content = my.db.get_table(FROM='instances', WHERE=where)
1512 if result == 0:
1513 bottle.abort(HTTP_Not_Found, "server %s not found" % server_id)
1514 return
1515 if result < 0:
1516 print "http_post_server_action error getting data %d %s" % (result, content)
1517 bottle.abort(HTTP_Internal_Server_Error, content)
1518 return
1519 server.update(content[0])
1520 tenant_id = server["tenant_id"]
1521
1522 #TODO check a right content
1523 new_status = None
1524 if 'terminate' in action:
1525 new_status='DELETING'
1526 elif server['status'] == 'ERROR': #or server['status'] == 'CREATING':
1527 if 'terminate' not in action and 'rebuild' not in action:
1528 bottle.abort(HTTP_Method_Not_Allowed, "Server is in ERROR status, must be rebuit or deleted ")
1529 return
1530 # elif server['status'] == 'INACTIVE':
1531 # if 'start' not in action and 'createImage' not in action:
1532 # bottle.abort(HTTP_Method_Not_Allowed, "The only possible action over an instance in 'INACTIVE' status is 'start'")
1533 # return
1534 # if 'start' in action:
1535 # new_status='CREATING'
1536 # server['paused']='no'
1537 # elif server['status'] == 'PAUSED':
1538 # if 'resume' not in action:
1539 # bottle.abort(HTTP_Method_Not_Allowed, "The only possible action over an instance in 'PAUSED' status is 'resume'")
1540 # return
1541 # elif server['status'] == 'ACTIVE':
1542 # if 'pause' not in action and 'reboot'not in action and 'shutoff'not in action:
1543 # bottle.abort(HTTP_Method_Not_Allowed, "The only possible action over an instance in 'ACTIVE' status is 'pause','reboot' or 'shutoff'")
1544 # return
1545
1546 if 'start' in action or 'createImage' in action or 'rebuild' in action:
1547 #check image valid and take info
1548 image_id = server['image_id']
1549 if 'createImage' in action:
1550 if 'imageRef' in action['createImage']:
1551 image_id = action['createImage']['imageRef']
1552 elif 'disk' in action['createImage']:
1553 result, content = my.db.get_table(FROM='instance_devices',
1554 SELECT=('image_id','dev'), WHERE={'instance_id':server['uuid'],"type":"disk"})
1555 if result<=0:
1556 bottle.abort(HTTP_Not_Found, 'disk not found for server')
1557 return
1558 elif result>1:
1559 disk_id=None
1560 if action['createImage']['imageRef']['disk'] != None:
1561 for disk in content:
1562 if disk['dev'] == action['createImage']['imageRef']['disk']:
1563 disk_id = disk['image_id']
1564 break
1565 if disk_id == None:
1566 bottle.abort(HTTP_Not_Found, 'disk %s not found for server' % action['createImage']['imageRef']['disk'])
1567 return
1568 else:
1569 bottle.abort(HTTP_Not_Found, 'more than one disk found for server' )
1570 return
1571 image_id = disk_id
1572 else: #result==1
1573 image_id = content[0]['image_id']
1574
1575 result, content = my.db.get_table(FROM='tenants_images as ti join images as i on ti.image_id=i.uuid',
1576 SELECT=('path','metadata'), WHERE={'uuid':image_id, 'tenant_id':tenant_id, "status":"ACTIVE"})
1577 if result<=0:
1578 bottle.abort(HTTP_Not_Found, 'image_id %s not found or not ACTIVE' % image_id)
1579 return
1580 if content[0]['metadata'] is not None:
1581 try:
1582 metadata = json.loads(content[0]['metadata'])
1583 except:
1584 return -HTTP_Internal_Server_Error, "Can not decode image metadata"
1585 content[0]['metadata']=metadata
1586 else:
1587 content[0]['metadata'] = {}
1588 server['image']=content[0]
1589 if 'createImage' in action:
1590 action['createImage']['source'] = {'image_id': image_id, 'path': content[0]['path']}
1591 if 'createImage' in action:
1592 #Create an entry in Database for the new image
1593 new_image={'status':'BUILD', 'progress': 0 }
1594 new_image_metadata=content[0]
1595 if 'metadata' in server['image'] and server['image']['metadata'] != None:
1596 new_image_metadata.update(server['image']['metadata'])
1597 new_image_metadata = {"use_incremental":"no"}
1598 if 'metadata' in action['createImage']:
1599 new_image_metadata.update(action['createImage']['metadata'])
1600 new_image['metadata'] = json.dumps(new_image_metadata)
1601 new_image['name'] = action['createImage'].get('name', None)
1602 new_image['description'] = action['createImage'].get('description', None)
1603 new_image['uuid']=my.db.new_uuid()
1604 if 'path' in action['createImage']:
1605 new_image['path'] = action['createImage']['path']
1606 else:
1607 new_image['path']="/provisional/path/" + new_image['uuid']
1608 result, image_uuid = my.db.new_image(new_image, tenant_id)
1609 if result<=0:
1610 bottle.abort(HTTP_Bad_Request, 'Error: ' + image_uuid)
1611 return
1612 server['new_image'] = new_image
1613
1614
1615 #Program task
1616 r,c = config_dic['host_threads'][ server['host_id'] ].insert_task( 'instance',server )
1617 if r<0:
1618 print "Task queue full at host ", server['host_id']
1619 bottle.abort(HTTP_Request_Timeout, c)
1620 if 'createImage' in action and result >= 0:
1621 return http_get_image_id(tenant_id, image_uuid)
1622
1623 #Update DB only for CREATING or DELETING status
1624 data={'result' : 'in process'}
1625 if new_status != None and new_status == 'DELETING':
1626 nets=[]
1627 ports_to_free=[]
1628
1629 net_ovs_list = []
1630 #look for dhcp ip address
1631 r2, c2 = my.db.get_table(FROM="ports", SELECT=["mac", "net_id"], WHERE={"instance_id": server_id})
1632 r, c = my.db.delete_instance(server_id, tenant_id, nets, ports_to_free, net_ovs_list, "requested by http")
1633 for port in ports_to_free:
1634 r1,c1 = config_dic['host_threads'][ server['host_id'] ].insert_task( 'restore-iface',*port )
1635 if r1 < 0:
1636 print ' http_post_server_action error at server deletion ERROR resore-iface !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!' + c1
1637 data={'result' : 'deleting in process, but ifaces cannot be restored!!!!!'}
1638 for net in nets:
1639 r1,c1 = config_dic['of_thread'].insert_task("update-net", net)
1640 if r1 < 0:
1641 print ' http_post_server_action error at server deletion ERROR UPDATING NETS !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!' + c1
1642 data={'result' : 'deleting in process, but openflow rules cannot be deleted!!!!!'}
1643 #look for dhcp ip address
1644 if r2 >0 and config_dic.get("dhcp_server"):
1645 for iface in c2:
1646 if iface["net_id"] in config_dic["dhcp_nets"]:
1647 r,c = config_dic['dhcp_thread'].insert_task("del", iface["mac"])
1648 #print "dhcp insert del task"
1649 if r < 0:
1650 print ':http_post_servers ERROR UPDATING dhcp_server !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!' + c
1651 # delete ovs-port and linux bridge, contains a list of tuple (net_id,vlan)
1652 for net in net_ovs_list:
1653 vlan = str(net[1])
1654 net_id = net[0]
1655 config_dic['host_threads'][server['host_id']].insert_task('del-ovs-port', vlan, net_id)
1656 return format_out(data)
1657
1658
1659
1660 @bottle.route(url_base + '/<tenant_id>/servers/<server_id>', method='DELETE')
1661 def http_delete_server_id(tenant_id, server_id):
1662 '''delete a server'''
1663 my = config_dic['http_threads'][ threading.current_thread().name ]
1664 #check valid tenant_id
1665 result,content = check_valid_tenant(my, tenant_id)
1666 if result != 0:
1667 bottle.abort(result, content)
1668 return
1669
1670 return http_server_action(server_id, tenant_id, {"terminate":None} )
1671
1672
1673 @bottle.route(url_base + '/<tenant_id>/servers/<server_id>/action', method='POST')
1674 def http_post_server_action(tenant_id, server_id):
1675 '''take an action over a server'''
1676 my = config_dic['http_threads'][ threading.current_thread().name ]
1677 #check valid tenant_id
1678 result,content = check_valid_tenant(my, tenant_id)
1679 if result != 0:
1680 bottle.abort(result, content)
1681 return
1682 http_content = format_in( server_action_schema )
1683 #r = remove_extra_items(http_content, server_action_schema)
1684 #if r is not None: print "http_post_server_action: Warning: remove extra items ", r
1685
1686 return http_server_action(server_id, tenant_id, http_content)
1687
1688 #
1689 # NETWORKS
1690 #
1691
1692
1693 @bottle.route(url_base + '/networks', method='GET')
1694 def http_get_networks():
1695 my = config_dic['http_threads'][ threading.current_thread().name ]
1696 #obtain data
1697 select_,where_,limit_ = filter_query_string(bottle.request.query, http2db_network,
1698 ('id','name','tenant_id','type',
1699 'shared','provider:vlan','status','last_error','admin_state_up','provider:physical') )
1700 #TODO temporally remove tenant_id
1701 if "tenant_id" in where_:
1702 del where_["tenant_id"]
1703 result, content = my.db.get_table(SELECT=select_, FROM='nets', WHERE=where_, LIMIT=limit_)
1704 if result < 0:
1705 print "http_get_networks error %d %s" % (result, content)
1706 bottle.abort(-result, content)
1707 else:
1708 convert_boolean(content, ('shared', 'admin_state_up', 'enable_dhcp') )
1709 delete_nulls(content)
1710 change_keys_http2db(content, http2db_network, reverse=True)
1711 data={'networks' : content}
1712 return format_out(data)
1713
1714 @bottle.route(url_base + '/networks/<network_id>', method='GET')
1715 def http_get_network_id(network_id):
1716 data = get_network_id(network_id)
1717 return format_out(data)
1718
1719 def get_network_id(network_id):
1720 my = config_dic['http_threads'][threading.current_thread().name]
1721 # obtain data
1722 where_ = bottle.request.query
1723 where_['uuid'] = network_id
1724 result, content = my.db.get_table(FROM='nets', WHERE=where_, LIMIT=100)
1725
1726 if result < 0:
1727 print "http_get_networks_id error %d %s" % (result, content)
1728 bottle.abort(-result, content)
1729 elif result==0:
1730 print "http_get_networks_id network '%s' not found" % network_id
1731 bottle.abort(HTTP_Not_Found, 'network %s not found' % network_id)
1732 else:
1733 convert_boolean(content, ('shared', 'admin_state_up', 'enale_dhcp') )
1734 change_keys_http2db(content, http2db_network, reverse=True)
1735 #get ports
1736 result, ports = my.db.get_table(FROM='ports', SELECT=('uuid as port_id',),
1737 WHERE={'net_id': network_id}, LIMIT=100)
1738 if len(ports) > 0:
1739 content[0]['ports'] = ports
1740 delete_nulls(content[0])
1741 data={'network' : content[0]}
1742 return data
1743
1744 @bottle.route(url_base + '/networks', method='POST')
1745 def http_post_networks():
1746 '''insert a network into the database.'''
1747 my = config_dic['http_threads'][ threading.current_thread().name ]
1748 #parse input data
1749 http_content = format_in( network_new_schema )
1750 r = remove_extra_items(http_content, network_new_schema)
1751 if r is not None: print "http_post_networks: Warning: remove extra items ", r
1752 change_keys_http2db(http_content['network'], http2db_network)
1753 network=http_content['network']
1754 #check valid tenant_id
1755 tenant_id= network.get('tenant_id')
1756 if tenant_id!=None:
1757 result, _ = my.db.get_table(FROM='tenants', SELECT=('uuid',), WHERE={'uuid': tenant_id,"enabled":True})
1758 if result<=0:
1759 bottle.abort(HTTP_Not_Found, 'tenant %s not found or not enabled' % tenant_id)
1760 return
1761 bridge_net = None
1762 #check valid params
1763 net_provider = network.get('provider')
1764 net_type = network.get('type')
1765 net_vlan = network.get("vlan")
1766 net_bind_net = network.get("bind_net")
1767 net_bind_type= network.get("bind_type")
1768 name = network["name"]
1769
1770 #check if network name ends with :<vlan_tag> and network exist in order to make and automated bindning
1771 vlan_index =name.rfind(":")
1772 if net_bind_net==None and net_bind_type==None and vlan_index > 1:
1773 try:
1774 vlan_tag = int(name[vlan_index+1:])
1775 if vlan_tag >0 and vlan_tag < 4096:
1776 net_bind_net = name[:vlan_index]
1777 net_bind_type = "vlan:" + name[vlan_index+1:]
1778 except:
1779 pass
1780
1781 if net_bind_net != None:
1782 #look for a valid net
1783 if check_valid_uuid(net_bind_net):
1784 net_bind_key = "uuid"
1785 else:
1786 net_bind_key = "name"
1787 result, content = my.db.get_table(FROM='nets', WHERE={net_bind_key: net_bind_net} )
1788 if result<0:
1789 bottle.abort(HTTP_Internal_Server_Error, 'getting nets from db ' + content)
1790 return
1791 elif result==0:
1792 bottle.abort(HTTP_Bad_Request, "bind_net %s '%s'not found" % (net_bind_key, net_bind_net) )
1793 return
1794 elif result>1:
1795 bottle.abort(HTTP_Bad_Request, "more than one bind_net %s '%s' found, use uuid" % (net_bind_key, net_bind_net) )
1796 return
1797 network["bind_net"] = content[0]["uuid"]
1798 if net_bind_type != None:
1799 if net_bind_type[0:5] != "vlan:":
1800 bottle.abort(HTTP_Bad_Request, "bad format for 'bind_type', must be 'vlan:<tag>'")
1801 return
1802 if int(net_bind_type[5:]) > 4095 or int(net_bind_type[5:])<=0 :
1803 bottle.abort(HTTP_Bad_Request, "bad format for 'bind_type', must be 'vlan:<tag>' with a tag between 1 and 4095")
1804 return
1805 network["bind_type"] = net_bind_type
1806
1807 if net_provider!=None:
1808 if net_provider[:9]=="openflow:":
1809 if net_type!=None:
1810 if net_type!="ptp" and net_type!="data":
1811 bottle.abort(HTTP_Bad_Request, "Only 'ptp' or 'data' net types can be bound to 'openflow'")
1812 else:
1813 net_type='data'
1814 else:
1815 if net_type!=None:
1816 if net_type!="bridge_man" and net_type!="bridge_data":
1817 bottle.abort(HTTP_Bad_Request, "Only 'bridge_man' or 'bridge_data' net types can be bound to 'bridge', 'macvtap' or 'default")
1818 else:
1819 net_type='bridge_man'
1820
1821 if net_type==None:
1822 net_type='bridge_man'
1823
1824 if net_provider != None:
1825 if net_provider[:7] == 'bridge:':
1826 bridge_net_name = net_provider[7:]
1827 for brnet in config_dic['bridge_nets']:
1828 if brnet[0]==bridge_net_name: # free
1829 if brnet[3] != None:
1830 bottle.abort(HTTP_Conflict, "invalid 'provider:physical', bridge '%s' is already used" % bridge_net_name)
1831 return
1832 bridge_net=brnet
1833 net_vlan = brnet[1]
1834 break
1835 # if bridge_net==None:
1836 # 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)
1837 # return
1838 elif config_dic['network_type'] == 'bridge' and ( net_type =='bridge_data' or net_type == 'bridge_man' ):
1839 #look for a free precreated nets
1840 for brnet in config_dic['bridge_nets']:
1841 if brnet[3]==None: # free
1842 if bridge_net != None:
1843 if net_type=='bridge_man': #look for the smaller speed
1844 if brnet[2] < bridge_net[2]: bridge_net = brnet
1845 else: #look for the larger speed
1846 if brnet[2] > bridge_net[2]: bridge_net = brnet
1847 else:
1848 bridge_net = brnet
1849 net_vlan = brnet[1]
1850 if bridge_net==None:
1851 bottle.abort(HTTP_Bad_Request, "Max limits of bridge networks reached. Future versions of VIM will overcome this limit")
1852 return
1853 else:
1854 print "using net", bridge_net
1855 net_provider = "bridge:"+bridge_net[0]
1856 net_vlan = bridge_net[1]
1857 elif net_type == 'bridge_data' or net_type == 'bridge_man' and config_dic['network_type'] == 'ovs':
1858 net_provider = 'OVS'
1859 if not net_vlan and (net_type == "data" or net_type == "ptp" or net_provider == "OVS"):
1860 net_vlan = my.db.get_free_net_vlan()
1861 if net_vlan < 0:
1862 bottle.abort(HTTP_Internal_Server_Error, "Error getting an available vlan")
1863 return
1864 if net_provider == 'OVS':
1865 net_provider = 'OVS' + ":" + str(net_vlan)
1866
1867 network['provider'] = net_provider
1868 network['type'] = net_type
1869 network['vlan'] = net_vlan
1870 result, content = my.db.new_row('nets', network, True, True)
1871
1872 if result >= 0:
1873 if bridge_net!=None:
1874 bridge_net[3] = content
1875
1876 if config_dic.get("dhcp_server") and config_dic['network_type'] == 'bridge':
1877 if network["name"] in config_dic["dhcp_server"].get("nets", () ):
1878 config_dic["dhcp_nets"].append(content)
1879 print "dhcp_server: add new net", content
1880 elif bridge_net != None and bridge_net[0] in config_dic["dhcp_server"].get("bridge_ifaces", () ):
1881 config_dic["dhcp_nets"].append(content)
1882 print "dhcp_server: add new net", content
1883 return http_get_network_id(content)
1884 else:
1885 print "http_post_networks error %d %s" % (result, content)
1886 bottle.abort(-result, content)
1887 return
1888
1889
1890 @bottle.route(url_base + '/networks/<network_id>', method='PUT')
1891 def http_put_network_id(network_id):
1892 '''update a network_id into the database.'''
1893 my = config_dic['http_threads'][ threading.current_thread().name ]
1894 #parse input data
1895 http_content = format_in( network_update_schema )
1896 r = remove_extra_items(http_content, network_update_schema)
1897 change_keys_http2db(http_content['network'], http2db_network)
1898 network=http_content['network']
1899
1900 #Look for the previous data
1901 where_ = {'uuid': network_id}
1902 result, network_old = my.db.get_table(FROM='nets', WHERE=where_)
1903 if result < 0:
1904 print "http_put_network_id error %d %s" % (result, network_old)
1905 bottle.abort(-result, network_old)
1906 return
1907 elif result==0:
1908 print "http_put_network_id network '%s' not found" % network_id
1909 bottle.abort(HTTP_Not_Found, 'network %s not found' % network_id)
1910 return
1911 #get ports
1912 nbports, content = my.db.get_table(FROM='ports', SELECT=('uuid as port_id',),
1913 WHERE={'net_id': network_id}, LIMIT=100)
1914 if result < 0:
1915 print "http_put_network_id error %d %s" % (result, network_old)
1916 bottle.abort(-result, content)
1917 return
1918 if nbports>0:
1919 if 'type' in network and network['type'] != network_old[0]['type']:
1920 bottle.abort(HTTP_Method_Not_Allowed, "Can not change type of network while having ports attached")
1921 if 'vlan' in network and network['vlan'] != network_old[0]['vlan']:
1922 bottle.abort(HTTP_Method_Not_Allowed, "Can not change vlan of network while having ports attached")
1923
1924 #check valid params
1925 net_provider = network.get('provider', network_old[0]['provider'])
1926 net_type = network.get('type', network_old[0]['type'])
1927 net_bind_net = network.get("bind_net")
1928 net_bind_type= network.get("bind_type")
1929 if net_bind_net != None:
1930 #look for a valid net
1931 if check_valid_uuid(net_bind_net):
1932 net_bind_key = "uuid"
1933 else:
1934 net_bind_key = "name"
1935 result, content = my.db.get_table(FROM='nets', WHERE={net_bind_key: net_bind_net} )
1936 if result<0:
1937 bottle.abort(HTTP_Internal_Server_Error, 'getting nets from db ' + content)
1938 return
1939 elif result==0:
1940 bottle.abort(HTTP_Bad_Request, "bind_net %s '%s'not found" % (net_bind_key, net_bind_net) )
1941 return
1942 elif result>1:
1943 bottle.abort(HTTP_Bad_Request, "more than one bind_net %s '%s' found, use uuid" % (net_bind_key, net_bind_net) )
1944 return
1945 network["bind_net"] = content[0]["uuid"]
1946 if net_bind_type != None:
1947 if net_bind_type[0:5] != "vlan:":
1948 bottle.abort(HTTP_Bad_Request, "bad format for 'bind_type', must be 'vlan:<tag>'")
1949 return
1950 if int(net_bind_type[5:]) > 4095 or int(net_bind_type[5:])<=0 :
1951 bottle.abort(HTTP_Bad_Request, "bad format for 'bind_type', must be 'vlan:<tag>' with a tag between 1 and 4095")
1952 return
1953 if net_provider!=None:
1954 if net_provider[:9]=="openflow:":
1955 if net_type!="ptp" and net_type!="data":
1956 bottle.abort(HTTP_Bad_Request, "Only 'ptp' or 'data' net types can be bound to 'openflow'")
1957 else:
1958 if net_type!="bridge_man" and net_type!="bridge_data":
1959 bottle.abort(HTTP_Bad_Request, "Only 'bridge_man' or 'bridge_data' net types can be bound to 'bridge', 'macvtap' or 'default")
1960
1961 #insert in data base
1962 result, content = my.db.update_rows('nets', network, WHERE={'uuid': network_id}, log=True )
1963 if result >= 0:
1964 if result>0: # and nbports>0 and 'admin_state_up' in network and network['admin_state_up'] != network_old[0]['admin_state_up']:
1965 r,c = config_dic['of_thread'].insert_task("update-net", network_id)
1966 if r < 0:
1967 print "http_put_network_id error while launching openflow rules"
1968 bottle.abort(HTTP_Internal_Server_Error, c)
1969 if config_dic.get("dhcp_server"):
1970 if network_id in config_dic["dhcp_nets"]:
1971 config_dic["dhcp_nets"].remove(network_id)
1972 print "dhcp_server: delete net", network_id
1973 if network.get("name", network_old["name"]) in config_dic["dhcp_server"].get("nets", () ):
1974 config_dic["dhcp_nets"].append(network_id)
1975 print "dhcp_server: add new net", network_id
1976 else:
1977 net_bind = network.get("bind", network_old["bind"] )
1978 if net_bind and net_bind[:7]=="bridge:" and net_bind[7:] in config_dic["dhcp_server"].get("bridge_ifaces", () ):
1979 config_dic["dhcp_nets"].append(network_id)
1980 print "dhcp_server: add new net", network_id
1981 return http_get_network_id(network_id)
1982 else:
1983 bottle.abort(-result, content)
1984 return
1985
1986
1987 @bottle.route(url_base + '/networks/<network_id>', method='DELETE')
1988 def http_delete_network_id(network_id):
1989 '''delete a network_id from the database.'''
1990 my = config_dic['http_threads'][ threading.current_thread().name ]
1991
1992 #delete from the data base
1993 result, content = my.db.delete_row('nets', network_id )
1994
1995 if result == 0:
1996 bottle.abort(HTTP_Not_Found, content)
1997 elif result >0:
1998 for brnet in config_dic['bridge_nets']:
1999 if brnet[3]==network_id:
2000 brnet[3]=None
2001 break
2002 if config_dic.get("dhcp_server") and network_id in config_dic["dhcp_nets"]:
2003 config_dic["dhcp_nets"].remove(network_id)
2004 print "dhcp_server: delete net", network_id
2005 data={'result' : content}
2006 return format_out(data)
2007 else:
2008 print "http_delete_network_id error",result, content
2009 bottle.abort(-result, content)
2010 return
2011 #
2012 # OPENFLOW
2013 #
2014 @bottle.route(url_base + '/networks/<network_id>/openflow', method='GET')
2015 def http_get_openflow_id(network_id):
2016 '''To obtain the list of openflow rules of a network
2017 '''
2018 my = config_dic['http_threads'][ threading.current_thread().name ]
2019 #ignore input data
2020 if network_id=='all':
2021 where_={}
2022 else:
2023 where_={"net_id": network_id}
2024 result, content = my.db.get_table(SELECT=("name","net_id","priority","vlan_id","ingress_port","src_mac","dst_mac","actions"),
2025 WHERE=where_, FROM='of_flows')
2026 if result < 0:
2027 bottle.abort(-result, content)
2028 return
2029 data={'openflow-rules' : content}
2030 return format_out(data)
2031
2032 @bottle.route(url_base + '/networks/<network_id>/openflow', method='PUT')
2033 def http_put_openflow_id(network_id):
2034 '''To make actions over the net. The action is to reinstall the openflow rules
2035 network_id can be 'all'
2036 '''
2037 my = config_dic['http_threads'][ threading.current_thread().name ]
2038 if not my.admin:
2039 bottle.abort(HTTP_Unauthorized, "Needed admin privileges")
2040 return
2041 #ignore input data
2042 if network_id=='all':
2043 where_={}
2044 else:
2045 where_={"uuid": network_id}
2046 result, content = my.db.get_table(SELECT=("uuid","type"), WHERE=where_, FROM='nets')
2047 if result < 0:
2048 bottle.abort(-result, content)
2049 return
2050
2051 for net in content:
2052 if net["type"]!="ptp" and net["type"]!="data":
2053 result-=1
2054 continue
2055 r,c = config_dic['of_thread'].insert_task("update-net", net['uuid'])
2056 if r < 0:
2057 print "http_put_openflow_id error while launching openflow rules"
2058 bottle.abort(HTTP_Internal_Server_Error, c)
2059 data={'result' : str(result)+" nets updates"}
2060 return format_out(data)
2061
2062 @bottle.route(url_base + '/networks/openflow/clear', method='DELETE')
2063 @bottle.route(url_base + '/networks/clear/openflow', method='DELETE')
2064 def http_clear_openflow_rules():
2065 '''To make actions over the net. The action is to delete ALL openflow rules
2066 '''
2067 my = config_dic['http_threads'][ threading.current_thread().name ]
2068 if not my.admin:
2069 bottle.abort(HTTP_Unauthorized, "Needed admin privileges")
2070 return
2071 #ignore input data
2072 r,c = config_dic['of_thread'].insert_task("clear-all")
2073 if r < 0:
2074 print "http_delete_openflow_id error while launching openflow rules"
2075 bottle.abort(HTTP_Internal_Server_Error, c)
2076 return
2077
2078 data={'result' : " Clearing openflow rules in process"}
2079 return format_out(data)
2080
2081 @bottle.route(url_base + '/networks/openflow/ports', method='GET')
2082 def http_get_openflow_ports():
2083 '''Obtain switch ports names of openflow controller
2084 '''
2085 data={'ports' : config_dic['of_thread'].OF_connector.pp2ofi}
2086 return format_out(data)
2087
2088
2089 #
2090 # PORTS
2091 #
2092
2093 @bottle.route(url_base + '/ports', method='GET')
2094 def http_get_ports():
2095 #obtain data
2096 my = config_dic['http_threads'][ threading.current_thread().name ]
2097 select_,where_,limit_ = filter_query_string(bottle.request.query, http2db_port,
2098 ('id','name','tenant_id','network_id','vpci','mac_address','device_owner','device_id',
2099 'binding:switch_port','binding:vlan','bandwidth','status','admin_state_up','ip_address') )
2100 #result, content = my.db.get_ports(where_)
2101 result, content = my.db.get_table(SELECT=select_, WHERE=where_, FROM='ports',LIMIT=limit_)
2102 if result < 0:
2103 print "http_get_ports Error", result, content
2104 bottle.abort(-result, content)
2105 return
2106 else:
2107 convert_boolean(content, ('admin_state_up',) )
2108 delete_nulls(content)
2109 change_keys_http2db(content, http2db_port, reverse=True)
2110 data={'ports' : content}
2111 return format_out(data)
2112
2113 @bottle.route(url_base + '/ports/<port_id>', method='GET')
2114 def http_get_port_id(port_id):
2115 my = config_dic['http_threads'][ threading.current_thread().name ]
2116 #obtain data
2117 result, content = my.db.get_table(WHERE={'uuid': port_id}, FROM='ports')
2118 if result < 0:
2119 print "http_get_ports error", result, content
2120 bottle.abort(-result, content)
2121 elif result==0:
2122 print "http_get_ports port '%s' not found" % str(port_id)
2123 bottle.abort(HTTP_Not_Found, 'port %s not found' % port_id)
2124 else:
2125 convert_boolean(content, ('admin_state_up',) )
2126 delete_nulls(content)
2127 change_keys_http2db(content, http2db_port, reverse=True)
2128 data={'port' : content[0]}
2129 return format_out(data)
2130
2131
2132 @bottle.route(url_base + '/ports', method='POST')
2133 def http_post_ports():
2134 '''insert an external port into the database.'''
2135 my = config_dic['http_threads'][ threading.current_thread().name ]
2136 if not my.admin:
2137 bottle.abort(HTTP_Unauthorized, "Needed admin privileges")
2138 #parse input data
2139 http_content = format_in( port_new_schema )
2140 r = remove_extra_items(http_content, port_new_schema)
2141 if r is not None: print "http_post_ports: Warning: remove extra items ", r
2142 change_keys_http2db(http_content['port'], http2db_port)
2143 port=http_content['port']
2144
2145 port['type'] = 'external'
2146 if 'net_id' in port and port['net_id'] == None:
2147 del port['net_id']
2148
2149 if 'net_id' in port:
2150 #check that new net has the correct type
2151 result, new_net = my.db.check_target_net(port['net_id'], None, 'external' )
2152 if result < 0:
2153 bottle.abort(HTTP_Bad_Request, new_net)
2154 return
2155 #insert in data base
2156 result, uuid = my.db.new_row('ports', port, True, True)
2157 if result > 0:
2158 if 'net_id' in port:
2159 r,c = config_dic['of_thread'].insert_task("update-net", port['net_id'])
2160 if r < 0:
2161 print "http_post_ports error while launching openflow rules"
2162 bottle.abort(HTTP_Internal_Server_Error, c)
2163 return http_get_port_id(uuid)
2164 else:
2165 bottle.abort(-result, uuid)
2166 return
2167
2168 @bottle.route(url_base + '/ports/<port_id>', method='PUT')
2169 def http_put_port_id(port_id):
2170 '''update a port_id into the database.'''
2171
2172 my = config_dic['http_threads'][ threading.current_thread().name ]
2173 #parse input data
2174 http_content = format_in( port_update_schema )
2175 change_keys_http2db(http_content['port'], http2db_port)
2176 port_dict=http_content['port']
2177
2178 #Look for the previous port data
2179 where_ = {'uuid': port_id}
2180 result, content = my.db.get_table(FROM="ports",WHERE=where_)
2181 if result < 0:
2182 print "http_put_port_id error", result, content
2183 bottle.abort(-result, content)
2184 return
2185 elif result==0:
2186 print "http_put_port_id port '%s' not found" % port_id
2187 bottle.abort(HTTP_Not_Found, 'port %s not found' % port_id)
2188 return
2189 print port_dict
2190 for k in ('vlan','switch_port','mac_address', 'tenant_id'):
2191 if k in port_dict and not my.admin:
2192 bottle.abort(HTTP_Unauthorized, "Needed admin privileges for changing " + k)
2193 return
2194
2195 port=content[0]
2196 #change_keys_http2db(port, http2db_port, reverse=True)
2197 nets = []
2198 host_id = None
2199 result=1
2200 if 'net_id' in port_dict:
2201 #change of net.
2202 old_net = port.get('net_id', None)
2203 new_net = port_dict['net_id']
2204 if old_net != new_net:
2205
2206 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
2207 if old_net is not None: nets.append(old_net)
2208 if port['type'] == 'instance:bridge' or port['type'] == 'instance:ovs':
2209 bottle.abort(HTTP_Forbidden, "bridge interfaces cannot be attached to a different net")
2210 return
2211 elif port['type'] == 'external':
2212 if not my.admin:
2213 bottle.abort(HTTP_Unauthorized, "Needed admin privileges")
2214 return
2215 else:
2216 if new_net != None:
2217 #check that new net has the correct type
2218 result, new_net_dict = my.db.check_target_net(new_net, None, port['type'] )
2219
2220 #change VLAN for SR-IOV ports
2221 if result>=0 and port["type"]=="instance:data" and port["model"]=="VF": #TODO consider also VFnotShared
2222 if new_net == None:
2223 port_dict["vlan"] = None
2224 else:
2225 port_dict["vlan"] = new_net_dict["vlan"]
2226 #get host where this VM is allocated
2227 result, content = my.db.get_table(FROM="instances",WHERE={"uuid":port["instance_id"]})
2228 if result<0:
2229 print "http_put_port_id database error", content
2230 elif result>0:
2231 host_id = content[0]["host_id"]
2232
2233 #insert in data base
2234 if result >= 0:
2235 result, content = my.db.update_rows('ports', port_dict, WHERE={'uuid': port_id}, log=False )
2236
2237 #Insert task to complete actions
2238 if result > 0:
2239 for net_id in nets:
2240 r,v = config_dic['of_thread'].insert_task("update-net", net_id)
2241 if r<0: print "Error ********* http_put_port_id update_of_flows: ", v
2242 #TODO Do something if fails
2243 if host_id != None:
2244 config_dic['host_threads'][host_id].insert_task("edit-iface", port_id, old_net, new_net)
2245
2246 if result >= 0:
2247 return http_get_port_id(port_id)
2248 else:
2249 bottle.abort(HTTP_Bad_Request, content)
2250 return
2251
2252
2253 @bottle.route(url_base + '/ports/<port_id>', method='DELETE')
2254 def http_delete_port_id(port_id):
2255 '''delete a port_id from the database.'''
2256 my = config_dic['http_threads'][ threading.current_thread().name ]
2257 if not my.admin:
2258 bottle.abort(HTTP_Unauthorized, "Needed admin privileges")
2259 return
2260
2261 #Look for the previous port data
2262 where_ = {'uuid': port_id, "type": "external"}
2263 result, ports = my.db.get_table(WHERE=where_, FROM='ports',LIMIT=100)
2264
2265 if result<=0:
2266 print "http_delete_port_id port '%s' not found" % port_id
2267 bottle.abort(HTTP_Not_Found, 'port %s not found or device_owner is not external' % port_id)
2268 return
2269 #delete from the data base
2270 result, content = my.db.delete_row('ports', port_id )
2271
2272 if result == 0:
2273 bottle.abort(HTTP_Not_Found, content)
2274 elif result >0:
2275 network = ports[0].get('net_id', None)
2276 if network is not None:
2277 #change of net.
2278 r,c = config_dic['of_thread'].insert_task("update-net", network)
2279 if r<0: print "!!!!!! http_delete_port_id update_of_flows error", r, c
2280 data={'result' : content}
2281 return format_out(data)
2282 else:
2283 print "http_delete_port_id error",result, content
2284 bottle.abort(-result, content)
2285 return
2286