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