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