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