Class OVIM to perform the logic done by http receptor
[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 dhcp_host.ssh_connect()
688 return dhcp_host
689
690
691 def delete_dhcp_ovs_bridge(vlan, net_uuid):
692 """
693 Delete bridges and port created during dhcp launching at openvim controller
694 :param vlan: net vlan id
695 :param net_uuid: network identifier
696 :return:
697 """
698 dhcp_path = config_dic['ovs_controller_file_path']
699
700 controller_host = get_dhcp_controller()
701 controller_host.delete_dhcp_port(vlan, net_uuid)
702 controller_host.delete_dhcp_server(vlan, net_uuid, dhcp_path)
703
704
705 def create_dhcp_ovs_bridge():
706 """
707 Initialize bridge to allocate the dhcp server at openvim controller
708 :return:
709 """
710 controller_host = get_dhcp_controller()
711 controller_host.create_ovs_bridge()
712
713
714 def set_mac_dhcp(vm_ip, vlan, first_ip, last_ip, cidr, mac):
715 """"
716 Launch a dhcpserver base on dnsmasq attached to the net base on vlan id across the the openvim computes
717 :param vm_ip: IP address asigned to a VM
718 :param vlan: Segmentation id
719 :param first_ip: First dhcp range ip
720 :param last_ip: Last dhcp range ip
721 :param cidr: net cidr
722 :param mac: VM vnic mac to be macthed with the IP received
723 """
724 if not vm_ip:
725 return
726 ip_tools = IPNetwork(cidr)
727 cidr_len = ip_tools.prefixlen
728 dhcp_netmask = str(ip_tools.netmask)
729 dhcp_path = config_dic['ovs_controller_file_path']
730
731 new_cidr = [first_ip + '/' + str(cidr_len)]
732 if not len(all_matching_cidrs(vm_ip, new_cidr)):
733 vm_ip = None
734
735 controller_host = get_dhcp_controller()
736 controller_host.set_mac_dhcp_server(vm_ip, mac, vlan, dhcp_netmask, dhcp_path)
737
738
739 def delete_mac_dhcp(vm_ip, vlan, mac):
740 """
741 Delete into dhcp conf file the ip assigned to a specific MAC address
742 :param vm_ip: IP address asigned to a VM
743 :param vlan: Segmentation id
744 :param mac: VM vnic mac to be macthed with the IP received
745 :return:
746 """
747
748 dhcp_path = config_dic['ovs_controller_file_path']
749
750 controller_host = get_dhcp_controller()
751 controller_host.delete_mac_dhcp_server(vm_ip, mac, vlan, dhcp_path)
752
753
754 def launch_dhcp_server(vlan, first_ip, last_ip, cidr):
755 """
756 Launch a dhcpserver base on dnsmasq attached to the net base on vlan id across the the openvim computes
757 :param vlan: vlan identifier
758 :param first_ip: First dhcp range ip
759 :param last_ip: Last dhcp range ip
760 :param cidr: net cidr
761 :return:
762 """
763 ip_tools = IPNetwork(cidr)
764 dhcp_netmask = str(ip_tools.netmask)
765 ip_range = [first_ip, last_ip]
766 dhcp_path = config_dic['ovs_controller_file_path']
767
768 controller_host = get_dhcp_controller()
769 controller_host.create_linux_bridge(vlan)
770 controller_host.create_dhcp_interfaces(vlan, first_ip, dhcp_netmask)
771 controller_host.launch_dhcp_server(vlan, ip_range, dhcp_netmask, dhcp_path)
772
773
774 def create_vxlan_mesh(host_id):
775 """
776 Create vxlan mesh across all openvimc controller and computes.
777 :param host_id: host identifier
778 :param host_id: host identifier
779 :return:
780 """
781 dhcp_compute_name = get_vxlan_interface("dhcp")
782 existing_hosts = get_hosts()
783 if len(existing_hosts['hosts']) > 0:
784 # vlxan mesh creation between openvim controller and computes
785 computes_available = existing_hosts['hosts']
786 controller_host = get_dhcp_controller()
787 for compute in computes_available:
788 vxlan_interface_name = get_vxlan_interface(compute['id'][:8])
789 config_dic['host_threads'][compute['id']].insert_task("new-vxlan", dhcp_compute_name, controller_host.host)
790 controller_host.create_ovs_vxlan_tunnel(vxlan_interface_name, compute['ip_name'])
791
792 # vlxan mesh creation between openvim computes
793 for count, compute_owner in enumerate(computes_available):
794 for compute in computes_available:
795 if compute_owner['id'] == compute['id']:
796 pass
797 else:
798 vxlan_interface_name = get_vxlan_interface(compute_owner['id'][:8])
799 controller_host.create_ovs_vxlan_tunnel(vxlan_interface_name, compute_owner['ip_name'])
800 config_dic['host_threads'][compute['id']].insert_task("new-vxlan",
801 vxlan_interface_name,
802 compute_owner['ip_name'])
803
804
805 def delete_vxlan_mesh(host_id):
806 """
807 Create a task for remove a specific compute of the vlxan mesh
808 :param host_id: host id to be deleted.
809 """
810 existing_hosts = get_hosts()
811 computes_available = existing_hosts['hosts']
812 #
813 vxlan_interface_name = get_vxlan_interface(host_id[:8])
814 controller_host = get_dhcp_controller()
815 controller_host.delete_ovs_vxlan_tunnel(vxlan_interface_name)
816 # remove bridge from openvim controller if no more computes exist
817 if len(existing_hosts):
818 controller_host.delete_ovs_bridge()
819 # Remove vxlan mesh
820 for compute in computes_available:
821 if host_id == compute['id']:
822 pass
823 else:
824 controller_host.delete_ovs_vxlan_tunnel(vxlan_interface_name)
825 config_dic['host_threads'][compute['id']].insert_task("del-vxlan", vxlan_interface_name)
826
827
828 def get_vxlan_interface(local_uuid):
829 """
830 Genearte a vxlan interface name
831 :param local_uuid: host id
832 :return: vlxan-8digits
833 """
834 return 'vxlan-' + local_uuid[:8]
835
836
837 @bottle.route(url_base + '/hosts/<host_id>', method='PUT')
838 def http_put_host_id(host_id):
839 '''modify a host into the database. All resources are got and inserted'''
840 my = config_dic['http_threads'][ threading.current_thread().name ]
841 #check permissions
842 if not my.admin:
843 bottle.abort(HTTP_Unauthorized, "Needed admin privileges")
844
845 #parse input data
846 http_content = format_in( host_edit_schema )
847 r = remove_extra_items(http_content, host_edit_schema)
848 if r is not None: print "http_post_host_id: Warning: remove extra items ", r
849 change_keys_http2db(http_content['host'], http2db_host)
850
851 #insert in data base
852 result, content = my.db.edit_host(host_id, http_content['host'])
853 if result >= 0:
854 convert_boolean(content, ('admin_state_up',) )
855 change_keys_http2db(content, http2db_host, reverse=True)
856 data={'host' : content}
857
858 if config_dic['network_type'] == 'ovs':
859 delete_vxlan_mesh(host_id)
860 config_dic['host_threads'][host_id].insert_task("del-ovsbridge")
861
862 #reload thread
863 config_dic['host_threads'][host_id].name = content.get('name',content['ip_name'])
864 config_dic['host_threads'][host_id].user = content['user']
865 config_dic['host_threads'][host_id].host = content['ip_name']
866 config_dic['host_threads'][host_id].insert_task("reload")
867
868 if config_dic['network_type'] == 'ovs':
869 # create mesh with new host data
870 config_dic['host_threads'][host_id].insert_task("new-ovsbridge")
871 create_vxlan_mesh(host_id)
872
873 #print data
874 return format_out(data)
875 else:
876 bottle.abort(HTTP_Bad_Request, content)
877 return
878
879
880
881 @bottle.route(url_base + '/hosts/<host_id>', method='DELETE')
882 def http_delete_host_id(host_id):
883 my = config_dic['http_threads'][ threading.current_thread().name ]
884 #check permissions
885 if not my.admin:
886 bottle.abort(HTTP_Unauthorized, "Needed admin privileges")
887 result, content = my.db.delete_row('hosts', host_id)
888 if result == 0:
889 bottle.abort(HTTP_Not_Found, content)
890 elif result > 0:
891 if config_dic['network_type'] == 'ovs':
892 delete_vxlan_mesh(host_id)
893 # terminate thread
894 if host_id in config_dic['host_threads']:
895 if config_dic['network_type'] == 'ovs':
896 config_dic['host_threads'][host_id].insert_task("del-ovsbridge")
897 config_dic['host_threads'][host_id].insert_task("exit")
898 #return data
899 data={'result' : content}
900 return format_out(data)
901 else:
902 print "http_delete_host_id error",result, content
903 bottle.abort(-result, content)
904 return
905
906
907
908 #
909 # TENANTS
910 #
911
912 @bottle.route(url_base + '/tenants', method='GET')
913 def http_get_tenants():
914 my = config_dic['http_threads'][ threading.current_thread().name ]
915 select_,where_,limit_ = filter_query_string(bottle.request.query, http2db_tenant,
916 ('id','name','description','enabled') )
917 result, content = my.db.get_table(FROM='tenants', SELECT=select_,WHERE=where_,LIMIT=limit_)
918 if result < 0:
919 print "http_get_tenants Error", content
920 bottle.abort(-result, content)
921 else:
922 change_keys_http2db(content, http2db_tenant, reverse=True)
923 convert_boolean(content, ('enabled',))
924 data={'tenants' : content}
925 #data['tenants_links'] = dict([('tenant', row['id']) for row in content])
926 return format_out(data)
927
928 @bottle.route(url_base + '/tenants/<tenant_id>', method='GET')
929 def http_get_tenant_id(tenant_id):
930 my = config_dic['http_threads'][ threading.current_thread().name ]
931 result, content = my.db.get_table(FROM='tenants', SELECT=('uuid','name','description', 'enabled'),WHERE={'uuid': tenant_id} )
932 if result < 0:
933 print "http_get_tenant_id error %d %s" % (result, content)
934 bottle.abort(-result, content)
935 elif result==0:
936 print "http_get_tenant_id tenant '%s' not found" % tenant_id
937 bottle.abort(HTTP_Not_Found, "tenant %s not found" % tenant_id)
938 else:
939 change_keys_http2db(content, http2db_tenant, reverse=True)
940 convert_boolean(content, ('enabled',))
941 data={'tenant' : content[0]}
942 #data['tenants_links'] = dict([('tenant', row['id']) for row in content])
943 return format_out(data)
944
945
946 @bottle.route(url_base + '/tenants', method='POST')
947 def http_post_tenants():
948 '''insert a tenant into the database.'''
949 my = config_dic['http_threads'][ threading.current_thread().name ]
950 #parse input data
951 http_content = format_in( tenant_new_schema )
952 r = remove_extra_items(http_content, tenant_new_schema)
953 if r is not None: print "http_post_tenants: Warning: remove extra items ", r
954 change_keys_http2db(http_content['tenant'], http2db_tenant)
955
956 #insert in data base
957 result, content = my.db.new_tenant(http_content['tenant'])
958
959 if result >= 0:
960 return http_get_tenant_id(content)
961 else:
962 bottle.abort(-result, content)
963 return
964
965 @bottle.route(url_base + '/tenants/<tenant_id>', method='PUT')
966 def http_put_tenant_id(tenant_id):
967 '''update a tenant into the database.'''
968 my = config_dic['http_threads'][ threading.current_thread().name ]
969 #parse input data
970 http_content = format_in( tenant_edit_schema )
971 r = remove_extra_items(http_content, tenant_edit_schema)
972 if r is not None: print "http_put_tenant_id: Warning: remove extra items ", r
973 change_keys_http2db(http_content['tenant'], http2db_tenant)
974
975 #insert in data base
976 result, content = my.db.update_rows('tenants', http_content['tenant'], WHERE={'uuid': tenant_id}, log=True )
977 if result >= 0:
978 return http_get_tenant_id(tenant_id)
979 else:
980 bottle.abort(-result, content)
981 return
982
983 @bottle.route(url_base + '/tenants/<tenant_id>', method='DELETE')
984 def http_delete_tenant_id(tenant_id):
985 my = config_dic['http_threads'][ threading.current_thread().name ]
986 #check permissions
987 r, tenants_flavors = my.db.get_table(FROM='tenants_flavors', SELECT=('flavor_id','tenant_id'), WHERE={'tenant_id': tenant_id})
988 if r<=0:
989 tenants_flavors=()
990 r, tenants_images = my.db.get_table(FROM='tenants_images', SELECT=('image_id','tenant_id'), WHERE={'tenant_id': tenant_id})
991 if r<=0:
992 tenants_images=()
993 result, content = my.db.delete_row('tenants', tenant_id)
994 if result == 0:
995 bottle.abort(HTTP_Not_Found, content)
996 elif result >0:
997 print "alf", tenants_flavors, tenants_images
998 for flavor in tenants_flavors:
999 my.db.delete_row_by_key("flavors", "uuid", flavor['flavor_id'])
1000 for image in tenants_images:
1001 my.db.delete_row_by_key("images", "uuid", image['image_id'])
1002 data={'result' : content}
1003 return format_out(data)
1004 else:
1005 print "http_delete_tenant_id error",result, content
1006 bottle.abort(-result, content)
1007 return
1008
1009 #
1010 # FLAVORS
1011 #
1012
1013 @bottle.route(url_base + '/<tenant_id>/flavors', method='GET')
1014 def http_get_flavors(tenant_id):
1015 my = config_dic['http_threads'][ threading.current_thread().name ]
1016 #check valid tenant_id
1017 result,content = check_valid_tenant(my, tenant_id)
1018 if result != 0:
1019 bottle.abort(result, content)
1020 #obtain data
1021 select_,where_,limit_ = filter_query_string(bottle.request.query, http2db_flavor,
1022 ('id','name','description','public') )
1023 if tenant_id=='any':
1024 from_ ='flavors'
1025 else:
1026 from_ ='tenants_flavors inner join flavors on tenants_flavors.flavor_id=flavors.uuid'
1027 where_['tenant_id'] = tenant_id
1028 result, content = my.db.get_table(FROM=from_, SELECT=select_, WHERE=where_, LIMIT=limit_)
1029 if result < 0:
1030 print "http_get_flavors Error", content
1031 bottle.abort(-result, content)
1032 else:
1033 change_keys_http2db(content, http2db_flavor, reverse=True)
1034 for row in content:
1035 row['links']=[ {'href': "/".join( (my.url_preffix, tenant_id, 'flavors', str(row['id']) ) ), 'rel':'bookmark' } ]
1036 data={'flavors' : content}
1037 return format_out(data)
1038
1039 @bottle.route(url_base + '/<tenant_id>/flavors/<flavor_id>', method='GET')
1040 def http_get_flavor_id(tenant_id, flavor_id):
1041 my = config_dic['http_threads'][ threading.current_thread().name ]
1042 #check valid tenant_id
1043 result,content = check_valid_tenant(my, tenant_id)
1044 if result != 0:
1045 bottle.abort(result, content)
1046 #obtain data
1047 select_,where_,limit_ = filter_query_string(bottle.request.query, http2db_flavor,
1048 ('id','name','description','ram', 'vcpus', 'extended', 'disk', 'public') )
1049 if tenant_id=='any':
1050 from_ ='flavors'
1051 else:
1052 from_ ='tenants_flavors as tf inner join flavors as f on tf.flavor_id=f.uuid'
1053 where_['tenant_id'] = tenant_id
1054 where_['uuid'] = flavor_id
1055 result, content = my.db.get_table(SELECT=select_, FROM=from_, WHERE=where_, LIMIT=limit_)
1056
1057 if result < 0:
1058 print "http_get_flavor_id error %d %s" % (result, content)
1059 bottle.abort(-result, content)
1060 elif result==0:
1061 print "http_get_flavors_id flavor '%s' not found" % str(flavor_id)
1062 bottle.abort(HTTP_Not_Found, 'flavor %s not found' % flavor_id)
1063 else:
1064 change_keys_http2db(content, http2db_flavor, reverse=True)
1065 if 'extended' in content[0] and content[0]['extended'] is not None:
1066 extended = json.loads(content[0]['extended'])
1067 if 'devices' in extended:
1068 change_keys_http2db(extended['devices'], http2db_flavor, reverse=True)
1069 content[0]['extended']=extended
1070 convert_bandwidth(content[0], reverse=True)
1071 content[0]['links']=[ {'href': "/".join( (my.url_preffix, tenant_id, 'flavors', str(content[0]['id']) ) ), 'rel':'bookmark' } ]
1072 data={'flavor' : content[0]}
1073 #data['tenants_links'] = dict([('tenant', row['id']) for row in content])
1074 return format_out(data)
1075
1076
1077 @bottle.route(url_base + '/<tenant_id>/flavors', method='POST')
1078 def http_post_flavors(tenant_id):
1079 '''insert a flavor into the database, and attach to tenant.'''
1080 my = config_dic['http_threads'][ threading.current_thread().name ]
1081 #check valid tenant_id
1082 result,content = check_valid_tenant(my, tenant_id)
1083 if result != 0:
1084 bottle.abort(result, content)
1085 http_content = format_in( flavor_new_schema )
1086 r = remove_extra_items(http_content, flavor_new_schema)
1087 if r is not None: print "http_post_flavors: Warning: remove extra items ", r
1088 change_keys_http2db(http_content['flavor'], http2db_flavor)
1089 extended_dict = http_content['flavor'].pop('extended', None)
1090 if extended_dict is not None:
1091 result, content = check_extended(extended_dict)
1092 if result<0:
1093 print "http_post_flavors wrong input extended error %d %s" % (result, content)
1094 bottle.abort(-result, content)
1095 return
1096 convert_bandwidth(extended_dict)
1097 if 'devices' in extended_dict: change_keys_http2db(extended_dict['devices'], http2db_flavor)
1098 http_content['flavor']['extended'] = json.dumps(extended_dict)
1099 #insert in data base
1100 result, content = my.db.new_flavor(http_content['flavor'], tenant_id)
1101 if result >= 0:
1102 return http_get_flavor_id(tenant_id, content)
1103 else:
1104 print "http_psot_flavors error %d %s" % (result, content)
1105 bottle.abort(-result, content)
1106 return
1107
1108 @bottle.route(url_base + '/<tenant_id>/flavors/<flavor_id>', method='DELETE')
1109 def http_delete_flavor_id(tenant_id, flavor_id):
1110 '''Deletes the flavor_id of a tenant. IT removes from tenants_flavors table.'''
1111 my = config_dic['http_threads'][ threading.current_thread().name ]
1112 #check valid tenant_id
1113 result,content = check_valid_tenant(my, tenant_id)
1114 if result != 0:
1115 bottle.abort(result, content)
1116 return
1117 result, content = my.db.delete_image_flavor('flavor', flavor_id, tenant_id)
1118 if result == 0:
1119 bottle.abort(HTTP_Not_Found, content)
1120 elif result >0:
1121 data={'result' : content}
1122 return format_out(data)
1123 else:
1124 print "http_delete_flavor_id error",result, content
1125 bottle.abort(-result, content)
1126 return
1127
1128 @bottle.route(url_base + '/<tenant_id>/flavors/<flavor_id>/<action>', method='POST')
1129 def http_attach_detach_flavors(tenant_id, flavor_id, action):
1130 '''attach/detach an existing flavor in this tenant. That is insert/remove at tenants_flavors table.'''
1131 #TODO alf: not tested at all!!!
1132 my = config_dic['http_threads'][ threading.current_thread().name ]
1133 #check valid tenant_id
1134 result,content = check_valid_tenant(my, tenant_id)
1135 if result != 0:
1136 bottle.abort(result, content)
1137 if tenant_id=='any':
1138 bottle.abort(HTTP_Bad_Request, "Invalid tenant 'any' with this command")
1139 #check valid action
1140 if action!='attach' and action != 'detach':
1141 bottle.abort(HTTP_Method_Not_Allowed, "actions can be attach or detach")
1142 return
1143
1144 #Ensure that flavor exist
1145 from_ ='tenants_flavors as tf right join flavors as f on tf.flavor_id=f.uuid'
1146 where_={'uuid': flavor_id}
1147 result, content = my.db.get_table(SELECT=('public','tenant_id'), FROM=from_, WHERE=where_)
1148 if result==0:
1149 if action=='attach':
1150 text_error="Flavor '%s' not found" % flavor_id
1151 else:
1152 text_error="Flavor '%s' not found for tenant '%s'" % (flavor_id, tenant_id)
1153 bottle.abort(HTTP_Not_Found, text_error)
1154 return
1155 elif result>0:
1156 flavor=content[0]
1157 if action=='attach':
1158 if flavor['tenant_id']!=None:
1159 bottle.abort(HTTP_Conflict, "Flavor '%s' already attached to tenant '%s'" % (flavor_id, tenant_id))
1160 if flavor['public']=='no' and not my.admin:
1161 #allow only attaching public flavors
1162 bottle.abort(HTTP_Unauthorized, "Needed admin rights to attach a private flavor")
1163 return
1164 #insert in data base
1165 result, content = my.db.new_row('tenants_flavors', {'flavor_id':flavor_id, 'tenant_id': tenant_id})
1166 if result >= 0:
1167 return http_get_flavor_id(tenant_id, flavor_id)
1168 else: #detach
1169 if flavor['tenant_id']==None:
1170 bottle.abort(HTTP_Not_Found, "Flavor '%s' not attached to tenant '%s'" % (flavor_id, tenant_id))
1171 result, content = my.db.delete_row_by_dict(FROM='tenants_flavors', WHERE={'flavor_id':flavor_id, 'tenant_id':tenant_id})
1172 if result>=0:
1173 if flavor['public']=='no':
1174 #try to delete the flavor completely to avoid orphan flavors, IGNORE error
1175 my.db.delete_row_by_dict(FROM='flavors', WHERE={'uuid':flavor_id})
1176 data={'result' : "flavor detached"}
1177 return format_out(data)
1178
1179 #if get here is because an error
1180 print "http_attach_detach_flavors error %d %s" % (result, content)
1181 bottle.abort(-result, content)
1182 return
1183
1184 @bottle.route(url_base + '/<tenant_id>/flavors/<flavor_id>', method='PUT')
1185 def http_put_flavor_id(tenant_id, flavor_id):
1186 '''update a flavor_id into the database.'''
1187 my = config_dic['http_threads'][ threading.current_thread().name ]
1188 #check valid tenant_id
1189 result,content = check_valid_tenant(my, tenant_id)
1190 if result != 0:
1191 bottle.abort(result, content)
1192 #parse input data
1193 http_content = format_in( flavor_update_schema )
1194 r = remove_extra_items(http_content, flavor_update_schema)
1195 if r is not None: print "http_put_flavor_id: Warning: remove extra items ", r
1196 change_keys_http2db(http_content['flavor'], http2db_flavor)
1197 extended_dict = http_content['flavor'].pop('extended', None)
1198 if extended_dict is not None:
1199 result, content = check_extended(extended_dict)
1200 if result<0:
1201 print "http_put_flavor_id wrong input extended error %d %s" % (result, content)
1202 bottle.abort(-result, content)
1203 return
1204 convert_bandwidth(extended_dict)
1205 if 'devices' in extended_dict: change_keys_http2db(extended_dict['devices'], http2db_flavor)
1206 http_content['flavor']['extended'] = json.dumps(extended_dict)
1207 #Ensure that flavor exist
1208 where_={'uuid': flavor_id}
1209 if tenant_id=='any':
1210 from_ ='flavors'
1211 else:
1212 from_ ='tenants_flavors as ti inner join flavors as i on ti.flavor_id=i.uuid'
1213 where_['tenant_id'] = tenant_id
1214 result, content = my.db.get_table(SELECT=('public',), FROM=from_, WHERE=where_)
1215 if result==0:
1216 text_error="Flavor '%s' not found" % flavor_id
1217 if tenant_id!='any':
1218 text_error +=" for tenant '%s'" % flavor_id
1219 bottle.abort(HTTP_Not_Found, text_error)
1220 return
1221 elif result>0:
1222 if content[0]['public']=='yes' and not my.admin:
1223 #allow only modifications over private flavors
1224 bottle.abort(HTTP_Unauthorized, "Needed admin rights to edit a public flavor")
1225 return
1226 #insert in data base
1227 result, content = my.db.update_rows('flavors', http_content['flavor'], {'uuid': flavor_id})
1228
1229 if result < 0:
1230 print "http_put_flavor_id error %d %s" % (result, content)
1231 bottle.abort(-result, content)
1232 return
1233 else:
1234 return http_get_flavor_id(tenant_id, flavor_id)
1235
1236
1237
1238 #
1239 # IMAGES
1240 #
1241
1242 @bottle.route(url_base + '/<tenant_id>/images', method='GET')
1243 def http_get_images(tenant_id):
1244 my = config_dic['http_threads'][ threading.current_thread().name ]
1245 #check valid tenant_id
1246 result,content = check_valid_tenant(my, tenant_id)
1247 if result != 0:
1248 bottle.abort(result, content)
1249 #obtain data
1250 select_,where_,limit_ = filter_query_string(bottle.request.query, http2db_image,
1251 ('id','name','checksum','description','path','public') )
1252 if tenant_id=='any':
1253 from_ ='images'
1254 where_or_ = None
1255 else:
1256 from_ ='tenants_images right join images on tenants_images.image_id=images.uuid'
1257 where_or_ = {'tenant_id': tenant_id, 'public': 'yes'}
1258 result, content = my.db.get_table(SELECT=select_, DISTINCT=True, FROM=from_, WHERE=where_, WHERE_OR=where_or_, WHERE_AND_OR="AND", LIMIT=limit_)
1259 if result < 0:
1260 print "http_get_images Error", content
1261 bottle.abort(-result, content)
1262 else:
1263 change_keys_http2db(content, http2db_image, reverse=True)
1264 #for row in content: row['links']=[ {'href': "/".join( (my.url_preffix, tenant_id, 'images', str(row['id']) ) ), 'rel':'bookmark' } ]
1265 data={'images' : content}
1266 return format_out(data)
1267
1268 @bottle.route(url_base + '/<tenant_id>/images/<image_id>', method='GET')
1269 def http_get_image_id(tenant_id, image_id):
1270 my = config_dic['http_threads'][ threading.current_thread().name ]
1271 #check valid tenant_id
1272 result,content = check_valid_tenant(my, tenant_id)
1273 if result != 0:
1274 bottle.abort(result, content)
1275 #obtain data
1276 select_,where_,limit_ = filter_query_string(bottle.request.query, http2db_image,
1277 ('id','name','checksum','description','progress', 'status','path', 'created', 'updated','public') )
1278 if tenant_id=='any':
1279 from_ ='images'
1280 where_or_ = None
1281 else:
1282 from_ ='tenants_images as ti right join images as i on ti.image_id=i.uuid'
1283 where_or_ = {'tenant_id': tenant_id, 'public': "yes"}
1284 where_['uuid'] = image_id
1285 result, content = my.db.get_table(SELECT=select_, DISTINCT=True, FROM=from_, WHERE=where_, WHERE_OR=where_or_, WHERE_AND_OR="AND", LIMIT=limit_)
1286
1287 if result < 0:
1288 print "http_get_images error %d %s" % (result, content)
1289 bottle.abort(-result, content)
1290 elif result==0:
1291 print "http_get_images image '%s' not found" % str(image_id)
1292 bottle.abort(HTTP_Not_Found, 'image %s not found' % image_id)
1293 else:
1294 convert_datetime2str(content)
1295 change_keys_http2db(content, http2db_image, reverse=True)
1296 if 'metadata' in content[0] and content[0]['metadata'] is not None:
1297 metadata = json.loads(content[0]['metadata'])
1298 content[0]['metadata']=metadata
1299 content[0]['links']=[ {'href': "/".join( (my.url_preffix, tenant_id, 'images', str(content[0]['id']) ) ), 'rel':'bookmark' } ]
1300 data={'image' : content[0]}
1301 #data['tenants_links'] = dict([('tenant', row['id']) for row in content])
1302 return format_out(data)
1303
1304 @bottle.route(url_base + '/<tenant_id>/images', method='POST')
1305 def http_post_images(tenant_id):
1306 '''insert a image into the database, and attach to tenant.'''
1307 my = config_dic['http_threads'][ threading.current_thread().name ]
1308 #check valid tenant_id
1309 result,content = check_valid_tenant(my, tenant_id)
1310 if result != 0:
1311 bottle.abort(result, content)
1312 http_content = format_in(image_new_schema)
1313 r = remove_extra_items(http_content, image_new_schema)
1314 if r is not None: print "http_post_images: Warning: remove extra items ", r
1315 change_keys_http2db(http_content['image'], http2db_image)
1316 metadata_dict = http_content['image'].pop('metadata', None)
1317 if metadata_dict is not None:
1318 http_content['image']['metadata'] = json.dumps(metadata_dict)
1319 #calculate checksum
1320 try:
1321 image_file = http_content['image'].get('path',None)
1322 parsed_url = urlparse.urlparse(image_file)
1323 if parsed_url.scheme == "" and parsed_url.netloc == "":
1324 # The path is a local file
1325 if os.path.exists(image_file):
1326 http_content['image']['checksum'] = md5(image_file)
1327 else:
1328 # The path is a URL. Code should be added to download the image and calculate the checksum
1329 #http_content['image']['checksum'] = md5(downloaded_image)
1330 pass
1331 # Finally, only if we are in test mode and checksum has not been calculated, we calculate it from the path
1332 host_test_mode = True if config_dic['mode']=='test' or config_dic['mode']=="OF only" else False
1333 if host_test_mode:
1334 if 'checksum' not in http_content['image']:
1335 http_content['image']['checksum'] = md5_string(image_file)
1336 else:
1337 # At this point, if the path is a local file and no chechsum has been obtained yet, an error is sent back.
1338 # If it is a URL, no error is sent. Checksum will be an empty string
1339 if parsed_url.scheme == "" and parsed_url.netloc == "" and 'checksum' not in http_content['image']:
1340 content = "Image file not found"
1341 print "http_post_images error: %d %s" % (HTTP_Bad_Request, content)
1342 bottle.abort(HTTP_Bad_Request, content)
1343 except Exception as e:
1344 print "ERROR. Unexpected exception: %s" % (str(e))
1345 bottle.abort(HTTP_Internal_Server_Error, type(e).__name__ + ": " + str(e))
1346 #insert in data base
1347 result, content = my.db.new_image(http_content['image'], tenant_id)
1348 if result >= 0:
1349 return http_get_image_id(tenant_id, content)
1350 else:
1351 print "http_post_images error %d %s" % (result, content)
1352 bottle.abort(-result, content)
1353 return
1354
1355 @bottle.route(url_base + '/<tenant_id>/images/<image_id>', method='DELETE')
1356 def http_delete_image_id(tenant_id, image_id):
1357 '''Deletes the image_id of a tenant. IT removes from tenants_images table.'''
1358 my = config_dic['http_threads'][ threading.current_thread().name ]
1359 #check valid tenant_id
1360 result,content = check_valid_tenant(my, tenant_id)
1361 if result != 0:
1362 bottle.abort(result, content)
1363 result, content = my.db.delete_image_flavor('image', image_id, tenant_id)
1364 if result == 0:
1365 bottle.abort(HTTP_Not_Found, content)
1366 elif result >0:
1367 data={'result' : content}
1368 return format_out(data)
1369 else:
1370 print "http_delete_image_id error",result, content
1371 bottle.abort(-result, content)
1372 return
1373
1374 @bottle.route(url_base + '/<tenant_id>/images/<image_id>/<action>', method='POST')
1375 def http_attach_detach_images(tenant_id, image_id, action):
1376 '''attach/detach an existing image in this tenant. That is insert/remove at tenants_images table.'''
1377 #TODO alf: not tested at all!!!
1378 my = config_dic['http_threads'][ threading.current_thread().name ]
1379 #check valid tenant_id
1380 result,content = check_valid_tenant(my, tenant_id)
1381 if result != 0:
1382 bottle.abort(result, content)
1383 if tenant_id=='any':
1384 bottle.abort(HTTP_Bad_Request, "Invalid tenant 'any' with this command")
1385 #check valid action
1386 if action!='attach' and action != 'detach':
1387 bottle.abort(HTTP_Method_Not_Allowed, "actions can be attach or detach")
1388 return
1389
1390 #Ensure that image exist
1391 from_ ='tenants_images as ti right join images as i on ti.image_id=i.uuid'
1392 where_={'uuid': image_id}
1393 result, content = my.db.get_table(SELECT=('public','tenant_id'), FROM=from_, WHERE=where_)
1394 if result==0:
1395 if action=='attach':
1396 text_error="Image '%s' not found" % image_id
1397 else:
1398 text_error="Image '%s' not found for tenant '%s'" % (image_id, tenant_id)
1399 bottle.abort(HTTP_Not_Found, text_error)
1400 return
1401 elif result>0:
1402 image=content[0]
1403 if action=='attach':
1404 if image['tenant_id']!=None:
1405 bottle.abort(HTTP_Conflict, "Image '%s' already attached to tenant '%s'" % (image_id, tenant_id))
1406 if image['public']=='no' and not my.admin:
1407 #allow only attaching public images
1408 bottle.abort(HTTP_Unauthorized, "Needed admin rights to attach a private image")
1409 return
1410 #insert in data base
1411 result, content = my.db.new_row('tenants_images', {'image_id':image_id, 'tenant_id': tenant_id})
1412 if result >= 0:
1413 return http_get_image_id(tenant_id, image_id)
1414 else: #detach
1415 if image['tenant_id']==None:
1416 bottle.abort(HTTP_Not_Found, "Image '%s' not attached to tenant '%s'" % (image_id, tenant_id))
1417 result, content = my.db.delete_row_by_dict(FROM='tenants_images', WHERE={'image_id':image_id, 'tenant_id':tenant_id})
1418 if result>=0:
1419 if image['public']=='no':
1420 #try to delete the image completely to avoid orphan images, IGNORE error
1421 my.db.delete_row_by_dict(FROM='images', WHERE={'uuid':image_id})
1422 data={'result' : "image detached"}
1423 return format_out(data)
1424
1425 #if get here is because an error
1426 print "http_attach_detach_images error %d %s" % (result, content)
1427 bottle.abort(-result, content)
1428 return
1429
1430 @bottle.route(url_base + '/<tenant_id>/images/<image_id>', method='PUT')
1431 def http_put_image_id(tenant_id, image_id):
1432 '''update a image_id into the database.'''
1433 my = config_dic['http_threads'][ threading.current_thread().name ]
1434 #check valid tenant_id
1435 result,content = check_valid_tenant(my, tenant_id)
1436 if result != 0:
1437 bottle.abort(result, content)
1438 #parse input data
1439 http_content = format_in( image_update_schema )
1440 r = remove_extra_items(http_content, image_update_schema)
1441 if r is not None: print "http_put_image_id: Warning: remove extra items ", r
1442 change_keys_http2db(http_content['image'], http2db_image)
1443 metadata_dict = http_content['image'].pop('metadata', None)
1444 if metadata_dict is not None:
1445 http_content['image']['metadata'] = json.dumps(metadata_dict)
1446 #Ensure that image exist
1447 where_={'uuid': image_id}
1448 if tenant_id=='any':
1449 from_ ='images'
1450 where_or_ = None
1451 else:
1452 from_ ='tenants_images as ti right join images as i on ti.image_id=i.uuid'
1453 where_or_ = {'tenant_id': tenant_id, 'public': 'yes'}
1454 result, content = my.db.get_table(SELECT=('public',), DISTINCT=True, FROM=from_, WHERE=where_, WHERE_OR=where_or_, WHERE_AND_OR="AND")
1455 if result==0:
1456 text_error="Image '%s' not found" % image_id
1457 if tenant_id!='any':
1458 text_error +=" for tenant '%s'" % image_id
1459 bottle.abort(HTTP_Not_Found, text_error)
1460 return
1461 elif result>0:
1462 if content[0]['public']=='yes' and not my.admin:
1463 #allow only modifications over private images
1464 bottle.abort(HTTP_Unauthorized, "Needed admin rights to edit a public image")
1465 return
1466 #insert in data base
1467 result, content = my.db.update_rows('images', http_content['image'], {'uuid': image_id})
1468
1469 if result < 0:
1470 print "http_put_image_id error %d %s" % (result, content)
1471 bottle.abort(-result, content)
1472 return
1473 else:
1474 return http_get_image_id(tenant_id, image_id)
1475
1476
1477 #
1478 # SERVERS
1479 #
1480
1481 @bottle.route(url_base + '/<tenant_id>/servers', method='GET')
1482 def http_get_servers(tenant_id):
1483 my = config_dic['http_threads'][ threading.current_thread().name ]
1484 result,content = check_valid_tenant(my, tenant_id)
1485 if result != 0:
1486 bottle.abort(result, content)
1487 return
1488 #obtain data
1489 select_,where_,limit_ = filter_query_string(bottle.request.query, http2db_server,
1490 ('id','name','description','hostId','imageRef','flavorRef','status', 'tenant_id') )
1491 if tenant_id!='any':
1492 where_['tenant_id'] = tenant_id
1493 result, content = my.db.get_table(SELECT=select_, FROM='instances', WHERE=where_, LIMIT=limit_)
1494 if result < 0:
1495 print "http_get_servers Error", content
1496 bottle.abort(-result, content)
1497 else:
1498 change_keys_http2db(content, http2db_server, reverse=True)
1499 for row in content:
1500 tenant_id = row.pop('tenant_id')
1501 row['links']=[ {'href': "/".join( (my.url_preffix, tenant_id, 'servers', str(row['id']) ) ), 'rel':'bookmark' } ]
1502 data={'servers' : content}
1503 return format_out(data)
1504
1505 @bottle.route(url_base + '/<tenant_id>/servers/<server_id>', method='GET')
1506 def http_get_server_id(tenant_id, server_id):
1507 my = config_dic['http_threads'][ threading.current_thread().name ]
1508 #check valid tenant_id
1509 result,content = check_valid_tenant(my, tenant_id)
1510 if result != 0:
1511 bottle.abort(result, content)
1512 return
1513 #obtain data
1514 result, content = my.db.get_instance(server_id)
1515 if result == 0:
1516 bottle.abort(HTTP_Not_Found, content)
1517 elif result >0:
1518 #change image/flavor-id to id and link
1519 convert_bandwidth(content, reverse=True)
1520 convert_datetime2str(content)
1521 if content["ram"]==0 : del content["ram"]
1522 if content["vcpus"]==0 : del content["vcpus"]
1523 if 'flavor_id' in content:
1524 if content['flavor_id'] is not None:
1525 content['flavor'] = {'id':content['flavor_id'],
1526 'links':[{'href': "/".join( (my.url_preffix, content['tenant_id'], 'flavors', str(content['flavor_id']) ) ), 'rel':'bookmark'}]
1527 }
1528 del content['flavor_id']
1529 if 'image_id' in content:
1530 if content['image_id'] is not None:
1531 content['image'] = {'id':content['image_id'],
1532 'links':[{'href': "/".join( (my.url_preffix, content['tenant_id'], 'images', str(content['image_id']) ) ), 'rel':'bookmark'}]
1533 }
1534 del content['image_id']
1535 change_keys_http2db(content, http2db_server, reverse=True)
1536 if 'extended' in content:
1537 if 'devices' in content['extended']: change_keys_http2db(content['extended']['devices'], http2db_server, reverse=True)
1538
1539 data={'server' : content}
1540 return format_out(data)
1541 else:
1542 bottle.abort(-result, content)
1543 return
1544
1545 @bottle.route(url_base + '/<tenant_id>/servers', method='POST')
1546 def http_post_server_id(tenant_id):
1547 '''deploys a new server'''
1548 my = config_dic['http_threads'][ threading.current_thread().name ]
1549 #check valid tenant_id
1550 result,content = check_valid_tenant(my, tenant_id)
1551 if result != 0:
1552 bottle.abort(result, content)
1553 return
1554 if tenant_id=='any':
1555 bottle.abort(HTTP_Bad_Request, "Invalid tenant 'any' with this command")
1556 #chek input
1557 http_content = format_in( server_new_schema )
1558 r = remove_extra_items(http_content, server_new_schema)
1559 if r is not None: print "http_post_serves: Warning: remove extra items ", r
1560 change_keys_http2db(http_content['server'], http2db_server)
1561 extended_dict = http_content['server'].get('extended', None)
1562 if extended_dict is not None:
1563 result, content = check_extended(extended_dict, True)
1564 if result<0:
1565 print "http_post_servers wrong input extended error %d %s" % (result, content)
1566 bottle.abort(-result, content)
1567 return
1568 convert_bandwidth(extended_dict)
1569 if 'devices' in extended_dict: change_keys_http2db(extended_dict['devices'], http2db_server)
1570
1571 server = http_content['server']
1572 server_start = server.get('start', 'yes')
1573 server['tenant_id'] = tenant_id
1574 #check flavor valid and take info
1575 result, content = my.db.get_table(FROM='tenants_flavors as tf join flavors as f on tf.flavor_id=f.uuid',
1576 SELECT=('ram','vcpus','extended'), WHERE={'uuid':server['flavor_id'], 'tenant_id':tenant_id})
1577 if result<=0:
1578 bottle.abort(HTTP_Not_Found, 'flavor_id %s not found' % server['flavor_id'])
1579 return
1580 server['flavor']=content[0]
1581 #check image valid and take info
1582 result, content = my.db.get_table(FROM='tenants_images as ti right join images as i on ti.image_id=i.uuid',
1583 SELECT=('path', 'metadata', 'image_id'),
1584 WHERE={'uuid':server['image_id'], "status":"ACTIVE"},
1585 WHERE_OR={'tenant_id':tenant_id, 'public': 'yes'},
1586 WHERE_AND_OR="AND",
1587 DISTINCT=True)
1588 if result<=0:
1589 bottle.abort(HTTP_Not_Found, 'image_id %s not found or not ACTIVE' % server['image_id'])
1590 return
1591 for image_dict in content:
1592 if image_dict.get("image_id"):
1593 break
1594 else:
1595 # insert in data base tenants_images
1596 r2, c2 = my.db.new_row('tenants_images', {'image_id': server['image_id'], 'tenant_id': tenant_id})
1597 if r2<=0:
1598 bottle.abort(HTTP_Not_Found, 'image_id %s cannot be used. Error %s' % (server['image_id'], c2))
1599 return
1600 server['image']={"path": content[0]["path"], "metadata": content[0]["metadata"]}
1601 if "hosts_id" in server:
1602 result, content = my.db.get_table(FROM='hosts', SELECT=('uuid',), WHERE={'uuid': server['host_id']})
1603 if result<=0:
1604 bottle.abort(HTTP_Not_Found, 'hostId %s not found' % server['host_id'])
1605 return
1606 #print json.dumps(server, indent=4)
1607
1608 result, content = ht.create_server(server, config_dic['db'], config_dic['db_lock'], config_dic['mode']=='normal')
1609
1610 if result >= 0:
1611 #Insert instance to database
1612 nets=[]
1613 print
1614 print "inserting at DB"
1615 print
1616 if server_start == 'no':
1617 content['status'] = 'INACTIVE'
1618 dhcp_nets_id = []
1619 for net in http_content['server']['networks']:
1620 if net['type'] == 'instance:ovs':
1621 dhcp_nets_id.append(get_network_id(net['net_id']))
1622
1623 ports_to_free=[]
1624 new_instance_result, new_instance = my.db.new_instance(content, nets, ports_to_free)
1625 if new_instance_result < 0:
1626 print "Error http_post_servers() :", new_instance_result, new_instance
1627 bottle.abort(-new_instance_result, new_instance)
1628 return
1629 print
1630 print "inserted at DB"
1631 print
1632 for port in ports_to_free:
1633 r,c = config_dic['host_threads'][ server['host_id'] ].insert_task( 'restore-iface',*port )
1634 if r < 0:
1635 print ' http_post_servers ERROR RESTORE IFACE!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!' + c
1636 #updata nets
1637 for net in nets:
1638 r,c = config_dic['of_thread'].insert_task("update-net", net)
1639 if r < 0:
1640 print ':http_post_servers ERROR UPDATING NETS !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!' + c
1641
1642
1643 #look for dhcp ip address
1644 r2, c2 = my.db.get_table(FROM="ports", SELECT=["mac", "ip_address", "net_id"], WHERE={"instance_id": new_instance})
1645 if r2 >0:
1646 for iface in c2:
1647 if config_dic.get("dhcp_server") and iface["net_id"] in config_dic["dhcp_nets"]:
1648 #print "dhcp insert add task"
1649 r,c = config_dic['dhcp_thread'].insert_task("add", iface["mac"])
1650 if r < 0:
1651 print ':http_post_servers ERROR UPDATING dhcp_server !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!' + c
1652
1653 #ensure compute contain the bridge for ovs networks:
1654 server_net = get_network_id(iface['net_id'])
1655 if server_net["network"].get('provider:physical', "")[:3] == 'OVS':
1656 vlan = str(server_net['network']['provider:vlan'])
1657 dhcp_enable = bool(server_net['network']['enable_dhcp'])
1658 if dhcp_enable:
1659 dhcp_firt_ip = str(server_net['network']['dhcp_first_ip'])
1660 dhcp_last_ip = str(server_net['network']['dhcp_last_ip'])
1661 dhcp_cidr = str(server_net['network']['cidr'])
1662 vm_dhcp_ip = c2[0]["ip_address"]
1663 config_dic['host_threads'][server['host_id']].insert_task("create-ovs-bridge-port", vlan)
1664
1665 set_mac_dhcp(vm_dhcp_ip, vlan, dhcp_firt_ip, dhcp_last_ip, dhcp_cidr, c2[0]['mac'])
1666 launch_dhcp_server(vlan, dhcp_firt_ip, dhcp_last_ip, dhcp_cidr)
1667
1668 #Start server
1669 server['uuid'] = new_instance
1670 server_start = server.get('start', 'yes')
1671
1672 if server_start != 'no':
1673 server['paused'] = True if server_start == 'paused' else False
1674 server['action'] = {"start":None}
1675 server['status'] = "CREATING"
1676 #Program task
1677 r,c = config_dic['host_threads'][ server['host_id'] ].insert_task( 'instance',server )
1678 if r<0:
1679 my.db.update_rows('instances', {'status':"ERROR"}, {'uuid':server['uuid'], 'last_error':c}, log=True)
1680
1681 return http_get_server_id(tenant_id, new_instance)
1682 else:
1683 bottle.abort(HTTP_Bad_Request, content)
1684 return
1685
1686 def http_server_action(server_id, tenant_id, action):
1687 '''Perform actions over a server as resume, reboot, terminate, ...'''
1688 my = config_dic['http_threads'][ threading.current_thread().name ]
1689 server={"uuid": server_id, "action":action}
1690 where={'uuid': server_id}
1691 if tenant_id!='any':
1692 where['tenant_id']= tenant_id
1693 result, content = my.db.get_table(FROM='instances', WHERE=where)
1694 if result == 0:
1695 bottle.abort(HTTP_Not_Found, "server %s not found" % server_id)
1696 return
1697 if result < 0:
1698 print "http_post_server_action error getting data %d %s" % (result, content)
1699 bottle.abort(HTTP_Internal_Server_Error, content)
1700 return
1701 server.update(content[0])
1702 tenant_id = server["tenant_id"]
1703
1704 #TODO check a right content
1705 new_status = None
1706 if 'terminate' in action:
1707 new_status='DELETING'
1708 elif server['status'] == 'ERROR': #or server['status'] == 'CREATING':
1709 if 'terminate' not in action and 'rebuild' not in action:
1710 bottle.abort(HTTP_Method_Not_Allowed, "Server is in ERROR status, must be rebuit or deleted ")
1711 return
1712 # elif server['status'] == 'INACTIVE':
1713 # if 'start' not in action and 'createImage' not in action:
1714 # bottle.abort(HTTP_Method_Not_Allowed, "The only possible action over an instance in 'INACTIVE' status is 'start'")
1715 # return
1716 # if 'start' in action:
1717 # new_status='CREATING'
1718 # server['paused']='no'
1719 # elif server['status'] == 'PAUSED':
1720 # if 'resume' not in action:
1721 # bottle.abort(HTTP_Method_Not_Allowed, "The only possible action over an instance in 'PAUSED' status is 'resume'")
1722 # return
1723 # elif server['status'] == 'ACTIVE':
1724 # if 'pause' not in action and 'reboot'not in action and 'shutoff'not in action:
1725 # bottle.abort(HTTP_Method_Not_Allowed, "The only possible action over an instance in 'ACTIVE' status is 'pause','reboot' or 'shutoff'")
1726 # return
1727
1728 if 'start' in action or 'createImage' in action or 'rebuild' in action:
1729 #check image valid and take info
1730 image_id = server['image_id']
1731 if 'createImage' in action:
1732 if 'imageRef' in action['createImage']:
1733 image_id = action['createImage']['imageRef']
1734 elif 'disk' in action['createImage']:
1735 result, content = my.db.get_table(FROM='instance_devices',
1736 SELECT=('image_id','dev'), WHERE={'instance_id':server['uuid'],"type":"disk"})
1737 if result<=0:
1738 bottle.abort(HTTP_Not_Found, 'disk not found for server')
1739 return
1740 elif result>1:
1741 disk_id=None
1742 if action['createImage']['imageRef']['disk'] != None:
1743 for disk in content:
1744 if disk['dev'] == action['createImage']['imageRef']['disk']:
1745 disk_id = disk['image_id']
1746 break
1747 if disk_id == None:
1748 bottle.abort(HTTP_Not_Found, 'disk %s not found for server' % action['createImage']['imageRef']['disk'])
1749 return
1750 else:
1751 bottle.abort(HTTP_Not_Found, 'more than one disk found for server' )
1752 return
1753 image_id = disk_id
1754 else: #result==1
1755 image_id = content[0]['image_id']
1756
1757 result, content = my.db.get_table(FROM='tenants_images as ti right join images as i on ti.image_id=i.uuid',
1758 SELECT=('path','metadata'), WHERE={'uuid':image_id, "status":"ACTIVE"},
1759 WHERE_OR={'tenant_id':tenant_id, 'public': 'yes'}, WHERE_AND_OR="AND", DISTINCT=True)
1760 if result<=0:
1761 bottle.abort(HTTP_Not_Found, 'image_id %s not found or not ACTIVE' % image_id)
1762 return
1763 if content[0]['metadata'] is not None:
1764 try:
1765 metadata = json.loads(content[0]['metadata'])
1766 except:
1767 return -HTTP_Internal_Server_Error, "Can not decode image metadata"
1768 content[0]['metadata']=metadata
1769 else:
1770 content[0]['metadata'] = {}
1771 server['image']=content[0]
1772 if 'createImage' in action:
1773 action['createImage']['source'] = {'image_id': image_id, 'path': content[0]['path']}
1774 if 'createImage' in action:
1775 #Create an entry in Database for the new image
1776 new_image={'status':'BUILD', 'progress': 0 }
1777 new_image_metadata=content[0]
1778 if 'metadata' in server['image'] and server['image']['metadata'] != None:
1779 new_image_metadata.update(server['image']['metadata'])
1780 new_image_metadata = {"use_incremental":"no"}
1781 if 'metadata' in action['createImage']:
1782 new_image_metadata.update(action['createImage']['metadata'])
1783 new_image['metadata'] = json.dumps(new_image_metadata)
1784 new_image['name'] = action['createImage'].get('name', None)
1785 new_image['description'] = action['createImage'].get('description', None)
1786 new_image['uuid']=my.db.new_uuid()
1787 if 'path' in action['createImage']:
1788 new_image['path'] = action['createImage']['path']
1789 else:
1790 new_image['path']="/provisional/path/" + new_image['uuid']
1791 result, image_uuid = my.db.new_image(new_image, tenant_id)
1792 if result<=0:
1793 bottle.abort(HTTP_Bad_Request, 'Error: ' + image_uuid)
1794 return
1795 server['new_image'] = new_image
1796
1797
1798 #Program task
1799 r,c = config_dic['host_threads'][ server['host_id'] ].insert_task( 'instance',server )
1800 if r<0:
1801 print "Task queue full at host ", server['host_id']
1802 bottle.abort(HTTP_Request_Timeout, c)
1803 if 'createImage' in action and result >= 0:
1804 return http_get_image_id(tenant_id, image_uuid)
1805
1806 #Update DB only for CREATING or DELETING status
1807 data={'result' : 'in process'}
1808 if new_status != None and new_status == 'DELETING':
1809 nets=[]
1810 ports_to_free=[]
1811
1812 net_ovs_list = []
1813 #look for dhcp ip address
1814 r2, c2 = my.db.get_table(FROM="ports", SELECT=["mac", "net_id"], WHERE={"instance_id": server_id})
1815 r, c = my.db.delete_instance(server_id, tenant_id, nets, ports_to_free, net_ovs_list, "requested by http")
1816 for port in ports_to_free:
1817 r1,c1 = config_dic['host_threads'][ server['host_id'] ].insert_task( 'restore-iface',*port )
1818 if r1 < 0:
1819 print ' http_post_server_action error at server deletion ERROR resore-iface !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!' + c1
1820 data={'result' : 'deleting in process, but ifaces cannot be restored!!!!!'}
1821 for net in nets:
1822 r1,c1 = config_dic['of_thread'].insert_task("update-net", net)
1823 if r1 < 0:
1824 print ' http_post_server_action error at server deletion ERROR UPDATING NETS !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!' + c1
1825 data={'result' : 'deleting in process, but openflow rules cannot be deleted!!!!!'}
1826 #look for dhcp ip address
1827 if r2 >0 and config_dic.get("dhcp_server"):
1828 for iface in c2:
1829 if iface["net_id"] in config_dic["dhcp_nets"]:
1830 r,c = config_dic['dhcp_thread'].insert_task("del", iface["mac"])
1831 #print "dhcp insert del task"
1832 if r < 0:
1833 print ':http_post_servers ERROR UPDATING dhcp_server !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!' + c
1834 # delete ovs-port and linux bridge, contains a list of tuple (net_id,vlan)
1835 for net in net_ovs_list:
1836 mac = str(net[3])
1837 vm_ip = str(net[2])
1838 vlan = str(net[1])
1839 net_id = net[0]
1840 delete_dhcp_ovs_bridge(vlan, net_id)
1841 delete_mac_dhcp(vm_ip, vlan, mac)
1842 config_dic['host_threads'][server['host_id']].insert_task('del-ovs-port', vlan, net_id)
1843 return format_out(data)
1844
1845
1846
1847 @bottle.route(url_base + '/<tenant_id>/servers/<server_id>', method='DELETE')
1848 def http_delete_server_id(tenant_id, server_id):
1849 '''delete a server'''
1850 my = config_dic['http_threads'][ threading.current_thread().name ]
1851 #check valid tenant_id
1852 result,content = check_valid_tenant(my, tenant_id)
1853 if result != 0:
1854 bottle.abort(result, content)
1855 return
1856
1857 return http_server_action(server_id, tenant_id, {"terminate":None} )
1858
1859
1860 @bottle.route(url_base + '/<tenant_id>/servers/<server_id>/action', method='POST')
1861 def http_post_server_action(tenant_id, server_id):
1862 '''take an action over a server'''
1863 my = config_dic['http_threads'][ threading.current_thread().name ]
1864 #check valid tenant_id
1865 result,content = check_valid_tenant(my, tenant_id)
1866 if result != 0:
1867 bottle.abort(result, content)
1868 return
1869 http_content = format_in( server_action_schema )
1870 #r = remove_extra_items(http_content, server_action_schema)
1871 #if r is not None: print "http_post_server_action: Warning: remove extra items ", r
1872
1873 return http_server_action(server_id, tenant_id, http_content)
1874
1875 #
1876 # NETWORKS
1877 #
1878
1879
1880 @bottle.route(url_base + '/networks', method='GET')
1881 def http_get_networks():
1882 my = config_dic['http_threads'][ threading.current_thread().name ]
1883 #obtain data
1884 select_,where_,limit_ = filter_query_string(bottle.request.query, http2db_network,
1885 ('id','name','tenant_id','type',
1886 'shared','provider:vlan','status','last_error','admin_state_up','provider:physical') )
1887 #TODO temporally remove tenant_id
1888 if "tenant_id" in where_:
1889 del where_["tenant_id"]
1890 result, content = my.db.get_table(SELECT=select_, FROM='nets', WHERE=where_, LIMIT=limit_)
1891 if result < 0:
1892 print "http_get_networks error %d %s" % (result, content)
1893 bottle.abort(-result, content)
1894 else:
1895 convert_boolean(content, ('shared', 'admin_state_up', 'enable_dhcp'))
1896 delete_nulls(content)
1897 change_keys_http2db(content, http2db_network, reverse=True)
1898 data={'networks' : content}
1899 return format_out(data)
1900
1901 @bottle.route(url_base + '/networks/<network_id>', method='GET')
1902 def http_get_network_id(network_id):
1903 data = get_network_id(network_id)
1904 return format_out(data)
1905
1906 def get_network_id(network_id):
1907 my = config_dic['http_threads'][threading.current_thread().name]
1908 # obtain data
1909 where_ = bottle.request.query
1910 where_['uuid'] = network_id
1911 result, content = my.db.get_table(FROM='nets', WHERE=where_, LIMIT=100)
1912
1913 if result < 0:
1914 print "http_get_networks_id error %d %s" % (result, content)
1915 bottle.abort(-result, content)
1916 elif result==0:
1917 print "http_get_networks_id network '%s' not found" % network_id
1918 bottle.abort(HTTP_Not_Found, 'network %s not found' % network_id)
1919 else:
1920 convert_boolean(content, ('shared', 'admin_state_up', 'enable_dhcp'))
1921 change_keys_http2db(content, http2db_network, reverse=True)
1922 #get ports
1923 result, ports = my.db.get_table(FROM='ports', SELECT=('uuid as port_id',),
1924 WHERE={'net_id': network_id}, LIMIT=100)
1925 if len(ports) > 0:
1926 content[0]['ports'] = ports
1927 delete_nulls(content[0])
1928 data={'network' : content[0]}
1929 return data
1930
1931 @bottle.route(url_base + '/networks', method='POST')
1932 def http_post_networks():
1933 '''insert a network into the database.'''
1934 my = config_dic['http_threads'][ threading.current_thread().name ]
1935 #parse input data
1936 http_content = format_in( network_new_schema )
1937 r = remove_extra_items(http_content, network_new_schema)
1938 if r is not None: print "http_post_networks: Warning: remove extra items ", r
1939 change_keys_http2db(http_content['network'], http2db_network)
1940 network=http_content['network']
1941 #check valid tenant_id
1942 tenant_id= network.get('tenant_id')
1943 if tenant_id!=None:
1944 result, _ = my.db.get_table(FROM='tenants', SELECT=('uuid',), WHERE={'uuid': tenant_id,"enabled":True})
1945 if result<=0:
1946 bottle.abort(HTTP_Not_Found, 'tenant %s not found or not enabled' % tenant_id)
1947 return
1948 bridge_net = None
1949 #check valid params
1950 net_provider = network.get('provider')
1951 net_type = network.get('type')
1952 net_enable_dhcp = network.get('enable_dhcp')
1953 if net_enable_dhcp:
1954 net_cidr = network.get('cidr')
1955
1956 net_vlan = network.get("vlan")
1957 net_bind_net = network.get("bind_net")
1958 net_bind_type= network.get("bind_type")
1959 name = network["name"]
1960
1961 #check if network name ends with :<vlan_tag> and network exist in order to make and automated bindning
1962 vlan_index =name.rfind(":")
1963 if net_bind_net==None and net_bind_type==None and vlan_index > 1:
1964 try:
1965 vlan_tag = int(name[vlan_index+1:])
1966 if vlan_tag >0 and vlan_tag < 4096:
1967 net_bind_net = name[:vlan_index]
1968 net_bind_type = "vlan:" + name[vlan_index+1:]
1969 except:
1970 pass
1971
1972 if net_bind_net != None:
1973 #look for a valid net
1974 if check_valid_uuid(net_bind_net):
1975 net_bind_key = "uuid"
1976 else:
1977 net_bind_key = "name"
1978 result, content = my.db.get_table(FROM='nets', WHERE={net_bind_key: net_bind_net} )
1979 if result<0:
1980 bottle.abort(HTTP_Internal_Server_Error, 'getting nets from db ' + content)
1981 return
1982 elif result==0:
1983 bottle.abort(HTTP_Bad_Request, "bind_net %s '%s'not found" % (net_bind_key, net_bind_net) )
1984 return
1985 elif result>1:
1986 bottle.abort(HTTP_Bad_Request, "more than one bind_net %s '%s' found, use uuid" % (net_bind_key, net_bind_net) )
1987 return
1988 network["bind_net"] = content[0]["uuid"]
1989 if net_bind_type != None:
1990 if net_bind_type[0:5] != "vlan:":
1991 bottle.abort(HTTP_Bad_Request, "bad format for 'bind_type', must be 'vlan:<tag>'")
1992 return
1993 if int(net_bind_type[5:]) > 4095 or int(net_bind_type[5:])<=0 :
1994 bottle.abort(HTTP_Bad_Request, "bad format for 'bind_type', must be 'vlan:<tag>' with a tag between 1 and 4095")
1995 return
1996 network["bind_type"] = net_bind_type
1997
1998 if net_provider!=None:
1999 if net_provider[:9]=="openflow:":
2000 if net_type!=None:
2001 if net_type!="ptp" and net_type!="data":
2002 bottle.abort(HTTP_Bad_Request, "Only 'ptp' or 'data' net types can be bound to 'openflow'")
2003 else:
2004 net_type='data'
2005 else:
2006 if net_type!=None:
2007 if net_type!="bridge_man" and net_type!="bridge_data":
2008 bottle.abort(HTTP_Bad_Request, "Only 'bridge_man' or 'bridge_data' net types can be bound to 'bridge', 'macvtap' or 'default")
2009 else:
2010 net_type='bridge_man'
2011
2012 if net_type==None:
2013 net_type='bridge_man'
2014
2015 if net_provider != None:
2016 if net_provider[:7]=='bridge:':
2017 #check it is one of the pre-provisioned bridges
2018 bridge_net_name = net_provider[7:]
2019 for brnet in config_dic['bridge_nets']:
2020 if brnet[0]==bridge_net_name: # free
2021 if brnet[3] != None:
2022 bottle.abort(HTTP_Conflict, "invalid 'provider:physical', bridge '%s' is already used" % bridge_net_name)
2023 return
2024 bridge_net=brnet
2025 net_vlan = brnet[1]
2026 break
2027 # if bridge_net==None:
2028 # 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)
2029 # return
2030 elif config_dic['network_type'] == 'bridge' and ( net_type =='bridge_data' or net_type == 'bridge_man' ):
2031 #look for a free precreated nets
2032 for brnet in config_dic['bridge_nets']:
2033 if brnet[3]==None: # free
2034 if bridge_net != None:
2035 if net_type=='bridge_man': #look for the smaller speed
2036 if brnet[2] < bridge_net[2]: bridge_net = brnet
2037 else: #look for the larger speed
2038 if brnet[2] > bridge_net[2]: bridge_net = brnet
2039 else:
2040 bridge_net = brnet
2041 net_vlan = brnet[1]
2042 if bridge_net==None:
2043 bottle.abort(HTTP_Bad_Request, "Max limits of bridge networks reached. Future versions of VIM will overcome this limit")
2044 return
2045 else:
2046 print "using net", bridge_net
2047 net_provider = "bridge:"+bridge_net[0]
2048 net_vlan = bridge_net[1]
2049 elif net_type == 'bridge_data' or net_type == 'bridge_man' and config_dic['network_type'] == 'ovs':
2050 net_provider = 'OVS'
2051 if not net_vlan and (net_type == "data" or net_type == "ptp" or net_provider == "OVS"):
2052 net_vlan = my.db.get_free_net_vlan()
2053 if net_vlan < 0:
2054 bottle.abort(HTTP_Internal_Server_Error, "Error getting an available vlan")
2055 return
2056 if net_provider == 'OVS':
2057 net_provider = 'OVS' + ":" + str(net_vlan)
2058
2059 network['provider'] = net_provider
2060 network['type'] = net_type
2061 network['vlan'] = net_vlan
2062
2063 if 'enable_dhcp' in network and network['enable_dhcp']:
2064 check_dhcp_data_integrity(network)
2065
2066 result, content = my.db.new_row('nets', network, True, True)
2067
2068 if result >= 0:
2069 if bridge_net!=None:
2070 bridge_net[3] = content
2071 if config_dic.get("dhcp_server") and config_dic['network_type'] == 'bridge':
2072 if network["name"] in config_dic["dhcp_server"].get("nets", () ):
2073 config_dic["dhcp_nets"].append(content)
2074 print "dhcp_server: add new net", content
2075 elif bridge_net != None and bridge_net[0] in config_dic["dhcp_server"].get("bridge_ifaces", () ):
2076 config_dic["dhcp_nets"].append(content)
2077 print "dhcp_server: add new net", content
2078 return http_get_network_id(content)
2079 else:
2080 print "http_post_networks error %d %s" % (result, content)
2081 bottle.abort(-result, content)
2082 return
2083
2084
2085 def check_dhcp_data_integrity(network):
2086 """
2087 Check if all dhcp parameter for anet are valid, if not will be calculated from cidr value
2088 :param network: list with user nets paramters
2089 :return:
2090 """
2091 control_iface = []
2092
2093 if "cidr" in network:
2094 cidr = network["cidr"]
2095
2096 ips = IPNetwork(cidr)
2097 if "dhcp_first_ip" not in network:
2098 network["dhcp_first_ip"] = str(ips[2])
2099 if "dhcp_last_ip" not in network:
2100 network["dhcp_last_ip"] = str(ips[-2])
2101
2102
2103 @bottle.route(url_base + '/networks/<network_id>', method='PUT')
2104 def http_put_network_id(network_id):
2105 '''update a network_id into the database.'''
2106 my = config_dic['http_threads'][ threading.current_thread().name ]
2107 #parse input data
2108 http_content = format_in( network_update_schema )
2109 r = remove_extra_items(http_content, network_update_schema)
2110 change_keys_http2db(http_content['network'], http2db_network)
2111 network=http_content['network']
2112
2113 #Look for the previous data
2114 where_ = {'uuid': network_id}
2115 result, network_old = my.db.get_table(FROM='nets', WHERE=where_)
2116 if result < 0:
2117 print "http_put_network_id error %d %s" % (result, network_old)
2118 bottle.abort(-result, network_old)
2119 return
2120 elif result==0:
2121 print "http_put_network_id network '%s' not found" % network_id
2122 bottle.abort(HTTP_Not_Found, 'network %s not found' % network_id)
2123 return
2124 #get ports
2125 nbports, content = my.db.get_table(FROM='ports', SELECT=('uuid as port_id',),
2126 WHERE={'net_id': network_id}, LIMIT=100)
2127 if result < 0:
2128 print "http_put_network_id error %d %s" % (result, network_old)
2129 bottle.abort(-result, content)
2130 return
2131 if nbports>0:
2132 if 'type' in network and network['type'] != network_old[0]['type']:
2133 bottle.abort(HTTP_Method_Not_Allowed, "Can not change type of network while having ports attached")
2134 if 'vlan' in network and network['vlan'] != network_old[0]['vlan']:
2135 bottle.abort(HTTP_Method_Not_Allowed, "Can not change vlan of network while having ports attached")
2136
2137 #check valid params
2138 net_provider = network.get('provider', network_old[0]['provider'])
2139 net_type = network.get('type', network_old[0]['type'])
2140 net_bind_net = network.get("bind_net")
2141 net_bind_type= network.get("bind_type")
2142 if net_bind_net != None:
2143 #look for a valid net
2144 if check_valid_uuid(net_bind_net):
2145 net_bind_key = "uuid"
2146 else:
2147 net_bind_key = "name"
2148 result, content = my.db.get_table(FROM='nets', WHERE={net_bind_key: net_bind_net} )
2149 if result<0:
2150 bottle.abort(HTTP_Internal_Server_Error, 'getting nets from db ' + content)
2151 return
2152 elif result==0:
2153 bottle.abort(HTTP_Bad_Request, "bind_net %s '%s'not found" % (net_bind_key, net_bind_net) )
2154 return
2155 elif result>1:
2156 bottle.abort(HTTP_Bad_Request, "more than one bind_net %s '%s' found, use uuid" % (net_bind_key, net_bind_net) )
2157 return
2158 network["bind_net"] = content[0]["uuid"]
2159 if net_bind_type != None:
2160 if net_bind_type[0:5] != "vlan:":
2161 bottle.abort(HTTP_Bad_Request, "bad format for 'bind_type', must be 'vlan:<tag>'")
2162 return
2163 if int(net_bind_type[5:]) > 4095 or int(net_bind_type[5:])<=0 :
2164 bottle.abort(HTTP_Bad_Request, "bad format for 'bind_type', must be 'vlan:<tag>' with a tag between 1 and 4095")
2165 return
2166 if net_provider!=None:
2167 if net_provider[:9]=="openflow:":
2168 if net_type!="ptp" and net_type!="data":
2169 bottle.abort(HTTP_Bad_Request, "Only 'ptp' or 'data' net types can be bound to 'openflow'")
2170 else:
2171 if net_type!="bridge_man" and net_type!="bridge_data":
2172 bottle.abort(HTTP_Bad_Request, "Only 'bridge_man' or 'bridge_data' net types can be bound to 'bridge', 'macvtap' or 'default")
2173
2174 #insert in data base
2175 result, content = my.db.update_rows('nets', network, WHERE={'uuid': network_id}, log=True )
2176 if result >= 0:
2177 if result>0: # and nbports>0 and 'admin_state_up' in network and network['admin_state_up'] != network_old[0]['admin_state_up']:
2178 r,c = config_dic['of_thread'].insert_task("update-net", network_id)
2179 if r < 0:
2180 print "http_put_network_id error while launching openflow rules"
2181 bottle.abort(HTTP_Internal_Server_Error, c)
2182 if config_dic.get("dhcp_server"):
2183 if network_id in config_dic["dhcp_nets"]:
2184 config_dic["dhcp_nets"].remove(network_id)
2185 print "dhcp_server: delete net", network_id
2186 if network.get("name", network_old["name"]) in config_dic["dhcp_server"].get("nets", () ):
2187 config_dic["dhcp_nets"].append(network_id)
2188 print "dhcp_server: add new net", network_id
2189 else:
2190 net_bind = network.get("bind", network_old["bind"] )
2191 if net_bind and net_bind[:7]=="bridge:" and net_bind[7:] in config_dic["dhcp_server"].get("bridge_ifaces", () ):
2192 config_dic["dhcp_nets"].append(network_id)
2193 print "dhcp_server: add new net", network_id
2194 return http_get_network_id(network_id)
2195 else:
2196 bottle.abort(-result, content)
2197 return
2198
2199
2200 @bottle.route(url_base + '/networks/<network_id>', method='DELETE')
2201 def http_delete_network_id(network_id):
2202 '''delete a network_id from the database.'''
2203 my = config_dic['http_threads'][ threading.current_thread().name ]
2204
2205 #delete from the data base
2206 result, content = my.db.delete_row('nets', network_id )
2207
2208 if result == 0:
2209 bottle.abort(HTTP_Not_Found, content)
2210 elif result >0:
2211 for brnet in config_dic['bridge_nets']:
2212 if brnet[3]==network_id:
2213 brnet[3]=None
2214 break
2215 if config_dic.get("dhcp_server") and network_id in config_dic["dhcp_nets"]:
2216 config_dic["dhcp_nets"].remove(network_id)
2217 print "dhcp_server: delete net", network_id
2218 data={'result' : content}
2219 return format_out(data)
2220 else:
2221 print "http_delete_network_id error",result, content
2222 bottle.abort(-result, content)
2223 return
2224 #
2225 # OPENFLOW
2226 #
2227 @bottle.route(url_base + '/networks/<network_id>/openflow', method='GET')
2228 def http_get_openflow_id(network_id):
2229 '''To obtain the list of openflow rules of a network
2230 '''
2231 my = config_dic['http_threads'][ threading.current_thread().name ]
2232 #ignore input data
2233 if network_id=='all':
2234 where_={}
2235 else:
2236 where_={"net_id": network_id}
2237 result, content = my.db.get_table(SELECT=("name","net_id","priority","vlan_id","ingress_port","src_mac","dst_mac","actions"),
2238 WHERE=where_, FROM='of_flows')
2239 if result < 0:
2240 bottle.abort(-result, content)
2241 return
2242 data={'openflow-rules' : content}
2243 return format_out(data)
2244
2245 @bottle.route(url_base + '/networks/<network_id>/openflow', method='PUT')
2246 def http_put_openflow_id(network_id):
2247 '''To make actions over the net. The action is to reinstall the openflow rules
2248 network_id can be 'all'
2249 '''
2250 my = config_dic['http_threads'][ threading.current_thread().name ]
2251 if not my.admin:
2252 bottle.abort(HTTP_Unauthorized, "Needed admin privileges")
2253 return
2254 #ignore input data
2255 if network_id=='all':
2256 where_={}
2257 else:
2258 where_={"uuid": network_id}
2259 result, content = my.db.get_table(SELECT=("uuid","type"), WHERE=where_, FROM='nets')
2260 if result < 0:
2261 bottle.abort(-result, content)
2262 return
2263
2264 for net in content:
2265 if net["type"]!="ptp" and net["type"]!="data":
2266 result-=1
2267 continue
2268 r,c = config_dic['of_thread'].insert_task("update-net", net['uuid'])
2269 if r < 0:
2270 print "http_put_openflow_id error while launching openflow rules"
2271 bottle.abort(HTTP_Internal_Server_Error, c)
2272 data={'result' : str(result)+" nets updates"}
2273 return format_out(data)
2274
2275 @bottle.route(url_base + '/networks/openflow/clear', method='DELETE')
2276 @bottle.route(url_base + '/networks/clear/openflow', method='DELETE')
2277 def http_clear_openflow_rules():
2278 '''To make actions over the net. The action is to delete ALL openflow rules
2279 '''
2280 my = config_dic['http_threads'][ threading.current_thread().name ]
2281 if not my.admin:
2282 bottle.abort(HTTP_Unauthorized, "Needed admin privileges")
2283 return
2284 #ignore input data
2285 r,c = config_dic['of_thread'].insert_task("clear-all")
2286 if r < 0:
2287 print "http_delete_openflow_id error while launching openflow rules"
2288 bottle.abort(HTTP_Internal_Server_Error, c)
2289 return
2290
2291 data={'result' : " Clearing openflow rules in process"}
2292 return format_out(data)
2293
2294 @bottle.route(url_base + '/networks/openflow/ports', method='GET')
2295 def http_get_openflow_ports():
2296 '''Obtain switch ports names of openflow controller
2297 '''
2298 data={'ports' : config_dic['of_thread'].OF_connector.pp2ofi}
2299 return format_out(data)
2300
2301
2302 #
2303 # PORTS
2304 #
2305
2306 @bottle.route(url_base + '/ports', method='GET')
2307 def http_get_ports():
2308 #obtain data
2309 my = config_dic['http_threads'][ threading.current_thread().name ]
2310 select_,where_,limit_ = filter_query_string(bottle.request.query, http2db_port,
2311 ('id','name','tenant_id','network_id','vpci','mac_address','device_owner','device_id',
2312 'binding:switch_port','binding:vlan','bandwidth','status','admin_state_up','ip_address') )
2313 try:
2314 ports = my.ovim.get_ports(columns=select_, filter=where_, limit=limit_)
2315 delete_nulls(ports)
2316 change_keys_http2db(ports, http2db_port, reverse=True)
2317 data={'ports' : ports}
2318 return format_out(data)
2319 except ovim.ovimException as e:
2320 my.logger.error(str(e), exc_info=True)
2321 bottle.abort(e.http_code, str(e))
2322 except Exception as e:
2323 my.logger.error(str(e), exc_info=True)
2324 bottle.abort(HTTP_Bad_Request, str(e))
2325
2326 @bottle.route(url_base + '/ports/<port_id>', method='GET')
2327 def http_get_port_id(port_id):
2328 my = config_dic['http_threads'][ threading.current_thread().name ]
2329 try:
2330 ports = my.ovim.get_ports(filter={"uuid": port_id})
2331 if not ports:
2332 bottle.abort(HTTP_Not_Found, 'port %s not found' % port_id)
2333 return
2334 delete_nulls(ports)
2335 change_keys_http2db(ports, http2db_port, reverse=True)
2336 data = {'port': ports[0]}
2337 return format_out(data)
2338 except ovim.ovimException as e:
2339 my.logger.error(str(e), exc_info=True)
2340 bottle.abort(e.http_code, str(e))
2341 except Exception as e:
2342 my.logger.error(str(e), exc_info=True)
2343 bottle.abort(HTTP_Bad_Request, str(e))
2344
2345 @bottle.route(url_base + '/ports', method='POST')
2346 def http_post_ports():
2347 '''insert an external port into the database.'''
2348 my = config_dic['http_threads'][ threading.current_thread().name ]
2349 if not my.admin:
2350 bottle.abort(HTTP_Unauthorized, "Needed admin privileges")
2351 #parse input data
2352 http_content = format_in( port_new_schema )
2353 r = remove_extra_items(http_content, port_new_schema)
2354 if r is not None: print "http_post_ports: Warning: remove extra items ", r
2355 change_keys_http2db(http_content['port'], http2db_port)
2356 port=http_content['port']
2357 try:
2358 port_id = my.ovim.new_port(port)
2359 ports = my.ovim.get_ports(filter={"uuid": port_id})
2360 if not ports:
2361 bottle.abort(HTTP_Internal_Server_Error, "port '{}' inserted but not found at database".format(port_id))
2362 return
2363 delete_nulls(ports)
2364 change_keys_http2db(ports, http2db_port, reverse=True)
2365 data = {'port': ports[0]}
2366 return format_out(data)
2367 except ovim.ovimException as e:
2368 my.logger.error(str(e), exc_info=True)
2369 bottle.abort(e.http_code, str(e))
2370 except Exception as e:
2371 my.logger.error(str(e), exc_info=True)
2372 bottle.abort(HTTP_Bad_Request, str(e))
2373
2374 @bottle.route(url_base + '/ports/<port_id>', method='PUT')
2375 def http_put_port_id(port_id):
2376 '''update a port_id into the database.'''
2377 my = config_dic['http_threads'][ threading.current_thread().name ]
2378 #parse input data
2379 http_content = format_in( port_update_schema )
2380 change_keys_http2db(http_content['port'], http2db_port)
2381 port_dict=http_content['port']
2382
2383 for k in ('vlan', 'switch_port', 'mac_address', 'tenant_id'):
2384 if k in port_dict and not my.admin:
2385 bottle.abort(HTTP_Unauthorized, "Needed admin privileges for changing " + k)
2386 return
2387 try:
2388 port_id = my.ovim.edit_port(port_id, port_dict, my.admin)
2389 ports = my.ovim.get_ports(filter={"uuid": port_id})
2390 if not ports:
2391 bottle.abort(HTTP_Internal_Server_Error, "port '{}' edited but not found at database".format(port_id))
2392 return
2393 delete_nulls(ports)
2394 change_keys_http2db(ports, http2db_port, reverse=True)
2395 data = {'port': ports[0]}
2396 return format_out(data)
2397 except ovim.ovimException as e:
2398 my.logger.error(str(e), exc_info=True)
2399 bottle.abort(e.http_code, str(e))
2400 except Exception as e:
2401 my.logger.error(str(e), exc_info=True)
2402 bottle.abort(HTTP_Bad_Request, str(e))
2403
2404
2405 @bottle.route(url_base + '/ports/<port_id>', method='DELETE')
2406 def http_delete_port_id(port_id):
2407 '''delete a port_id from the database.'''
2408 my = config_dic['http_threads'][ threading.current_thread().name ]
2409 if not my.admin:
2410 bottle.abort(HTTP_Unauthorized, "Needed admin privileges")
2411 return
2412 try:
2413 result = my.ovim.delete_port(port_id)
2414 data = {'result': result}
2415 return format_out(data)
2416 except ovim.ovimException as e:
2417 my.logger.error(str(e), exc_info=True)
2418 bottle.abort(e.http_code, str(e))
2419 except Exception as e:
2420 my.logger.error(str(e), exc_info=True)
2421 bottle.abort(HTTP_Bad_Request, str(e))
2422
2423