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