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