Refactor ssh commands
[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, dhcp_path)
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 dns = server_net['network'].get("dns")
1680 if dns:
1681 dns = yaml.safe_load(server_net['network'].get("dns"))
1682 routes = server_net['network'].get("routes")
1683 if routes:
1684 routes = yaml.safe_load(server_net['network'].get("routes"))
1685 links = server_net['network'].get("links")
1686 if links:
1687 links = yaml.safe_load(server_net['network'].get("links"))
1688 if dhcp_enable:
1689 dhcp_firt_ip = str(server_net['network']['dhcp_first_ip'])
1690 dhcp_last_ip = str(server_net['network']['dhcp_last_ip'])
1691 dhcp_cidr = str(server_net['network']['cidr'])
1692 gateway = str(server_net['network']['gateway_ip'])
1693
1694 http_controller = config_dic['http_threads'][threading.current_thread().name]
1695 http_controller.ovim.launch_dhcp_server(vlan, dhcp_firt_ip, dhcp_last_ip,
1696 dhcp_cidr, gateway, dns, routes)
1697 set_mac_dhcp(vm_dhcp_ip, vlan, dhcp_firt_ip, dhcp_last_ip, dhcp_cidr, c2[0]['mac'])
1698
1699 if links:
1700 http_controller.ovim.launch_link_bridge_to_ovs(vlan, gateway, dhcp_cidr, links, routes)
1701
1702
1703 #Start server
1704 server['uuid'] = new_instance
1705 server_start = server.get('start', 'yes')
1706
1707 if server_start != 'no':
1708 server['paused'] = True if server_start == 'paused' else False
1709 server['action'] = {"start":None}
1710 server['status'] = "CREATING"
1711 #Program task
1712 r,c = config_dic['host_threads'][ server['host_id'] ].insert_task( 'instance',server )
1713 if r<0:
1714 my.db.update_rows('instances', {'status':"ERROR"}, {'uuid':server['uuid'], 'last_error':c}, log=True)
1715
1716 return http_get_server_id(tenant_id, new_instance)
1717 else:
1718 bottle.abort(HTTP_Bad_Request, content)
1719 return
1720
1721
1722 def http_server_action(server_id, tenant_id, action):
1723 '''Perform actions over a server as resume, reboot, terminate, ...'''
1724 my = config_dic['http_threads'][ threading.current_thread().name ]
1725 server={"uuid": server_id, "action":action}
1726 where={'uuid': server_id}
1727 if tenant_id!='any':
1728 where['tenant_id']= tenant_id
1729 result, content = my.db.get_table(FROM='instances', WHERE=where)
1730 if result == 0:
1731 bottle.abort(HTTP_Not_Found, "server %s not found" % server_id)
1732 return
1733 if result < 0:
1734 print "http_post_server_action error getting data %d %s" % (result, content)
1735 bottle.abort(HTTP_Internal_Server_Error, content)
1736 return
1737 server.update(content[0])
1738 tenant_id = server["tenant_id"]
1739
1740 #TODO check a right content
1741 new_status = None
1742 if 'terminate' in action:
1743 new_status='DELETING'
1744 elif server['status'] == 'ERROR': #or server['status'] == 'CREATING':
1745 if 'terminate' not in action and 'rebuild' not in action:
1746 bottle.abort(HTTP_Method_Not_Allowed, "Server is in ERROR status, must be rebuit or deleted ")
1747 return
1748 # elif server['status'] == 'INACTIVE':
1749 # if 'start' not in action and 'createImage' not in action:
1750 # bottle.abort(HTTP_Method_Not_Allowed, "The only possible action over an instance in 'INACTIVE' status is 'start'")
1751 # return
1752 # if 'start' in action:
1753 # new_status='CREATING'
1754 # server['paused']='no'
1755 # elif server['status'] == 'PAUSED':
1756 # if 'resume' not in action:
1757 # bottle.abort(HTTP_Method_Not_Allowed, "The only possible action over an instance in 'PAUSED' status is 'resume'")
1758 # return
1759 # elif server['status'] == 'ACTIVE':
1760 # if 'pause' not in action and 'reboot'not in action and 'shutoff'not in action:
1761 # bottle.abort(HTTP_Method_Not_Allowed, "The only possible action over an instance in 'ACTIVE' status is 'pause','reboot' or 'shutoff'")
1762 # return
1763
1764 if 'start' in action or 'createImage' in action or 'rebuild' in action:
1765 #check image valid and take info
1766 image_id = server['image_id']
1767 if 'createImage' in action:
1768 if 'imageRef' in action['createImage']:
1769 image_id = action['createImage']['imageRef']
1770 elif 'disk' in action['createImage']:
1771 result, content = my.db.get_table(FROM='instance_devices',
1772 SELECT=('image_id','dev'), WHERE={'instance_id':server['uuid'],"type":"disk"})
1773 if result<=0:
1774 bottle.abort(HTTP_Not_Found, 'disk not found for server')
1775 return
1776 elif result>1:
1777 disk_id=None
1778 if action['createImage']['imageRef']['disk'] != None:
1779 for disk in content:
1780 if disk['dev'] == action['createImage']['imageRef']['disk']:
1781 disk_id = disk['image_id']
1782 break
1783 if disk_id == None:
1784 bottle.abort(HTTP_Not_Found, 'disk %s not found for server' % action['createImage']['imageRef']['disk'])
1785 return
1786 else:
1787 bottle.abort(HTTP_Not_Found, 'more than one disk found for server' )
1788 return
1789 image_id = disk_id
1790 else: #result==1
1791 image_id = content[0]['image_id']
1792
1793 result, content = my.db.get_table(FROM='tenants_images as ti right join images as i on ti.image_id=i.uuid',
1794 SELECT=('path','metadata'), WHERE={'uuid':image_id, "status":"ACTIVE"},
1795 WHERE_OR={'tenant_id':tenant_id, 'public': 'yes'}, WHERE_AND_OR="AND", DISTINCT=True)
1796 if result<=0:
1797 bottle.abort(HTTP_Not_Found, 'image_id %s not found or not ACTIVE' % image_id)
1798 return
1799 if content[0]['metadata'] is not None:
1800 try:
1801 metadata = json.loads(content[0]['metadata'])
1802 except:
1803 return -HTTP_Internal_Server_Error, "Can not decode image metadata"
1804 content[0]['metadata']=metadata
1805 else:
1806 content[0]['metadata'] = {}
1807 server['image']=content[0]
1808 if 'createImage' in action:
1809 action['createImage']['source'] = {'image_id': image_id, 'path': content[0]['path']}
1810 if 'createImage' in action:
1811 #Create an entry in Database for the new image
1812 new_image={'status':'BUILD', 'progress': 0 }
1813 new_image_metadata=content[0]
1814 if 'metadata' in server['image'] and server['image']['metadata'] != None:
1815 new_image_metadata.update(server['image']['metadata'])
1816 new_image_metadata = {"use_incremental":"no"}
1817 if 'metadata' in action['createImage']:
1818 new_image_metadata.update(action['createImage']['metadata'])
1819 new_image['metadata'] = json.dumps(new_image_metadata)
1820 new_image['name'] = action['createImage'].get('name', None)
1821 new_image['description'] = action['createImage'].get('description', None)
1822 new_image['uuid']=my.db.new_uuid()
1823 if 'path' in action['createImage']:
1824 new_image['path'] = action['createImage']['path']
1825 else:
1826 new_image['path']="/provisional/path/" + new_image['uuid']
1827 result, image_uuid = my.db.new_image(new_image, tenant_id)
1828 if result<=0:
1829 bottle.abort(HTTP_Bad_Request, 'Error: ' + image_uuid)
1830 return
1831 server['new_image'] = new_image
1832
1833
1834 #Program task
1835 r,c = config_dic['host_threads'][ server['host_id'] ].insert_task( 'instance',server )
1836 if r<0:
1837 print "Task queue full at host ", server['host_id']
1838 bottle.abort(HTTP_Request_Timeout, c)
1839 if 'createImage' in action and result >= 0:
1840 return http_get_image_id(tenant_id, image_uuid)
1841
1842 #Update DB only for CREATING or DELETING status
1843 data={'result' : 'deleting in process'}
1844 warn_text=""
1845 if new_status != None and new_status == 'DELETING':
1846 nets=[]
1847 ports_to_free=[]
1848
1849 net_ovs_list = []
1850 #look for dhcp ip address
1851 r2, c2 = my.db.get_table(FROM="ports", SELECT=["mac", "net_id"], WHERE={"instance_id": server_id})
1852 r, c = my.db.delete_instance(server_id, tenant_id, nets, ports_to_free, net_ovs_list, "requested by http")
1853 for port in ports_to_free:
1854 r1,c1 = config_dic['host_threads'][ server['host_id'] ].insert_task( 'restore-iface',*port )
1855 if r1 < 0:
1856 my.logger.error("http_post_server_action server deletion ERROR at resore-iface!!!! " + c1)
1857 warn_text += "; Error iface '{}' cannot be restored '{}'".format(str(port), str(e))
1858 for net_id in nets:
1859 try:
1860 my.ovim.net_update_ofc_thread(net_id)
1861 except ovim.ovimException as e:
1862 my.logger.error("http_server_action, Error updating network with id '{}', '{}'".format(net_id, str(e)))
1863 warn_text += "; Error openflow rules of network '{}' cannot be restore '{}'".format(net_id, str (e))
1864
1865 # look for dhcp ip address
1866 if r2 >0 and config_dic.get("dhcp_server"):
1867 for iface in c2:
1868 if iface["net_id"] in config_dic["dhcp_nets"]:
1869 r,c = config_dic['dhcp_thread'].insert_task("del", iface["mac"])
1870 #print "dhcp insert del task"
1871 if r < 0:
1872 print ':http_post_servers ERROR UPDATING dhcp_server !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!' + c
1873 # delete ovs-port and linux bridge, contains a list of tuple (net_id,vlan, vm_ip, mac)
1874
1875 for net in net_ovs_list:
1876 mac = str(net[3])
1877 vm_ip = str(net[2])
1878 vlan = str(net[1])
1879 net_id = net[0]
1880
1881 delete_dhcp_ovs_bridge(vlan, net_id)
1882 delete_mac_dhcp(vm_ip, vlan, mac)
1883
1884 net_data = my.ovim.show_network(net_id)
1885 if net_data.get('links'):
1886 links = yaml.load(net_data.get('links'))
1887 my.ovim.delete_link_bridge_to_ovs(vlan, links)
1888
1889 config_dic['host_threads'][server['host_id']].insert_task('del-ovs-port', vlan, net_id)
1890 if warn_text:
1891 data["result"] += warn_text
1892 return format_out(data)
1893
1894
1895
1896 @bottle.route(url_base + '/<tenant_id>/servers/<server_id>', method='DELETE')
1897 def http_delete_server_id(tenant_id, server_id):
1898 '''delete a server'''
1899 my = config_dic['http_threads'][ threading.current_thread().name ]
1900 #check valid tenant_id
1901 result,content = check_valid_tenant(my, tenant_id)
1902 if result != 0:
1903 bottle.abort(result, content)
1904 return
1905
1906 return http_server_action(server_id, tenant_id, {"terminate":None} )
1907
1908
1909 @bottle.route(url_base + '/<tenant_id>/servers/<server_id>/action', method='POST')
1910 def http_post_server_action(tenant_id, server_id):
1911 '''take an action over a server'''
1912 my = config_dic['http_threads'][ threading.current_thread().name ]
1913 #check valid tenant_id
1914 result,content = check_valid_tenant(my, tenant_id)
1915 if result != 0:
1916 bottle.abort(result, content)
1917 return
1918 http_content = format_in( server_action_schema )
1919 #r = remove_extra_items(http_content, server_action_schema)
1920 #if r is not None: print "http_post_server_action: Warning: remove extra items ", r
1921
1922 return http_server_action(server_id, tenant_id, http_content)
1923
1924 #
1925 # NETWORKS
1926 #
1927
1928
1929 @bottle.route(url_base + '/networks', method='GET')
1930 def http_get_networks():
1931 """
1932 Get all networks available
1933 :return:
1934 """
1935 my = config_dic['http_threads'][threading.current_thread().name]
1936
1937 try:
1938 # obtain data
1939 select_, where_, limit_ = filter_query_string(bottle.request.query, http2db_network,
1940 ('id', 'name', 'tenant_id', 'type',
1941 'shared', 'provider:vlan', 'status', 'last_error',
1942 'admin_state_up', 'provider:physical'))
1943 if "tenant_id" in where_:
1944 del where_["tenant_id"]
1945
1946 content = my.ovim.get_networks(select_, where_, limit_)
1947
1948 delete_nulls(content)
1949 change_keys_http2db(content, http2db_network, reverse=True)
1950 data = {'networks': content}
1951 return format_out(data)
1952
1953 except ovim.ovimException as e:
1954 my.logger.error(str(e), exc_info=True)
1955 bottle.abort(e.http_code, str(e))
1956 except Exception as e:
1957 my.logger.error(str(e), exc_info=True)
1958 bottle.abort(HTTP_Bad_Request, str(e))
1959
1960
1961 @bottle.route(url_base + '/networks/<network_id>', method='GET')
1962 def http_get_network_id(network_id):
1963 """
1964 Get a network data by id
1965 :param network_id:
1966 :return:
1967 """
1968 data = get_network_id(network_id)
1969 return format_out(data)
1970
1971
1972 def get_network_id(network_id):
1973 """
1974 Get network from DB by id
1975 :param network_id: network Id
1976 :return:
1977 """
1978 my = config_dic['http_threads'][threading.current_thread().name]
1979
1980 try:
1981 # obtain data
1982 where_ = bottle.request.query
1983 content = my.ovim.show_network(network_id, where_)
1984
1985 change_keys_http2db(content, http2db_network, reverse=True)
1986 delete_nulls(content)
1987 data = {'network': content}
1988 return data
1989 except ovim.ovimException as e:
1990 my.logger.error(str(e), exc_info=True)
1991 bottle.abort(e.http_code, str(e))
1992 except Exception as e:
1993 my.logger.error(str(e), exc_info=True)
1994 bottle.abort(HTTP_Bad_Request, str(e))
1995
1996
1997 @bottle.route(url_base + '/networks', method='POST')
1998 def http_post_networks():
1999 """
2000 Insert a network into the database.
2001 :return:
2002 """
2003 my = config_dic['http_threads'][threading.current_thread().name]
2004
2005 try:
2006 # parse input data
2007 http_content = format_in(network_new_schema)
2008 r = remove_extra_items(http_content, network_new_schema)
2009 if r is not None:
2010 print "http_post_networks: Warning: remove extra items ", r
2011 change_keys_http2db(http_content['network'], http2db_network)
2012 network = http_content['network']
2013 content = my.ovim.new_network(network)
2014 return format_out(get_network_id(content))
2015 except ovim.ovimException as e:
2016 my.logger.error(str(e), exc_info=True)
2017 bottle.abort(e.http_code, str(e))
2018 except Exception as e:
2019 my.logger.error(str(e), exc_info=True)
2020 bottle.abort(HTTP_Bad_Request, str(e))
2021
2022
2023 @bottle.route(url_base + '/networks/<network_id>', method='PUT')
2024 def http_put_network_id(network_id):
2025 """
2026 Update a network_id into DB.
2027 :param network_id: network id
2028 :return:
2029 """
2030 my = config_dic['http_threads'][threading.current_thread().name]
2031
2032 try:
2033 # parse input data
2034 http_content = format_in(network_update_schema)
2035 change_keys_http2db(http_content['network'], http2db_network)
2036 network = http_content['network']
2037 return format_out(my.ovim.edit_network(network_id, network))
2038
2039 except ovim.ovimException as e:
2040 my.logger.error(str(e), exc_info=True)
2041 bottle.abort(e.http_code, str(e))
2042 except Exception as e:
2043 my.logger.error(str(e), exc_info=True)
2044 bottle.abort(HTTP_Bad_Request, str(e))
2045
2046
2047 @bottle.route(url_base + '/networks/<network_id>', method='DELETE')
2048 def http_delete_network_id(network_id):
2049 """
2050 Delete a network_id from the database.
2051 :param network_id: Network id
2052 :return:
2053 """
2054 my = config_dic['http_threads'][threading.current_thread().name]
2055
2056 try:
2057 # delete from the data base
2058 content = my.ovim.delete_network(network_id)
2059 data = {'result': content}
2060 return format_out(data)
2061
2062 except ovim.ovimException as e:
2063 my.logger.error(str(e), exc_info=True)
2064 bottle.abort(e.http_code, str(e))
2065 except Exception as e:
2066 my.logger.error(str(e), exc_info=True)
2067 bottle.abort(HTTP_Bad_Request, str(e))
2068
2069 #
2070 # OPENFLOW
2071 #
2072
2073
2074 @bottle.route(url_base + '/openflow/controller', method='GET')
2075 def http_get_openflow_controller():
2076 """
2077 Retrieve a openflow controllers list from DB.
2078 :return:
2079 """
2080 # TODO check if show a proper list
2081 my = config_dic['http_threads'][threading.current_thread().name]
2082
2083 try:
2084 select_, where_, limit_ = filter_query_string(bottle.request.query, http2db_ofc,
2085 ('id', 'name', 'dpid', 'ip', 'port', 'type',
2086 'version', 'user', 'password'))
2087
2088 content = my.ovim.get_of_controllers(select_, where_)
2089 delete_nulls(content)
2090 change_keys_http2db(content, http2db_ofc, reverse=True)
2091 data = {'ofcs': content}
2092 return format_out(data)
2093 except ovim.ovimException as e:
2094 my.logger.error(str(e), exc_info=True)
2095 bottle.abort(e.http_code, str(e))
2096 except Exception as e:
2097 my.logger.error(str(e), exc_info=True)
2098 bottle.abort(HTTP_Bad_Request, str(e))
2099
2100
2101 @bottle.route(url_base + '/openflow/controller/<uuid>', method='GET')
2102 def http_get_openflow_controller_id(uuid):
2103 """
2104 Get an openflow controller by dpid from DB.get_of_controllers
2105 """
2106 my = config_dic['http_threads'][threading.current_thread().name]
2107
2108 try:
2109
2110 content = my.ovim.show_of_controller(uuid)
2111 delete_nulls(content)
2112 change_keys_http2db(content, http2db_ofc, reverse=True)
2113 data = {'ofc': content}
2114 return format_out(data)
2115 except ovim.ovimException as e:
2116 my.logger.error(str(e), exc_info=True)
2117 bottle.abort(e.http_code, str(e))
2118 except Exception as e:
2119 my.logger.error(str(e), exc_info=True)
2120 bottle.abort(HTTP_Bad_Request, str(e))
2121
2122
2123 @bottle.route(url_base + '/openflow/controller/', method='POST')
2124 def http_post_openflow_controller():
2125 """
2126 Create a new openflow controller into DB
2127 :return:
2128 """
2129 my = config_dic['http_threads'][threading.current_thread().name]
2130
2131 try:
2132 http_content = format_in(openflow_controller_schema)
2133 of_c = http_content['ofc']
2134 uuid = my.ovim.new_of_controller(of_c)
2135 content = my.ovim.show_of_controller(uuid)
2136 delete_nulls(content)
2137 change_keys_http2db(content, http2db_ofc, reverse=True)
2138 data = {'ofc': content}
2139 return format_out(data)
2140 except ovim.ovimException as e:
2141 my.logger.error(str(e), exc_info=True)
2142 bottle.abort(e.http_code, str(e))
2143 except Exception as e:
2144 my.logger.error(str(e), exc_info=True)
2145 bottle.abort(HTTP_Bad_Request, str(e))
2146
2147
2148 @bottle.route(url_base + '/openflow/controller/<of_controller_id>', method='PUT')
2149 def http_put_openflow_controller_by_id(of_controller_id):
2150 """
2151 Create an openflow controller into DB
2152 :param of_controller_id: openflow controller dpid
2153 :return:
2154 """
2155 my = config_dic['http_threads'][threading.current_thread().name]
2156
2157 try:
2158 http_content = format_in(openflow_controller_schema)
2159 of_c = http_content['ofc']
2160
2161 content = my.ovim.edit_of_controller(of_controller_id, of_c)
2162 delete_nulls(content)
2163 change_keys_http2db(content, http2db_ofc, reverse=True)
2164 data = {'ofc': content}
2165 return format_out(data)
2166 except ovim.ovimException as e:
2167 my.logger.error(str(e), exc_info=True)
2168 bottle.abort(e.http_code, str(e))
2169 except Exception as e:
2170 my.logger.error(str(e), exc_info=True)
2171 bottle.abort(HTTP_Bad_Request, str(e))
2172
2173
2174 @bottle.route(url_base + '/openflow/controller/<of_controller_id>', method='DELETE')
2175 def http_delete_openflow_controller(of_controller_id):
2176 """
2177 Delete an openflow controller from DB.
2178 :param of_controller_id: openflow controller dpid
2179 :return:
2180 """
2181 my = config_dic['http_threads'][threading.current_thread().name]
2182
2183 try:
2184 content = my.ovim.delete_of_controller(of_controller_id)
2185 data = {'result': content}
2186 return format_out(data)
2187 except ovim.ovimException as e:
2188 my.logger.error(str(e), exc_info=True)
2189 bottle.abort(e.http_code, str(e))
2190 except Exception as e:
2191 my.logger.error(str(e), exc_info=True)
2192 bottle.abort(HTTP_Bad_Request, str(e))
2193
2194
2195 @bottle.route(url_base + '/networks/<network_id>/openflow', method='GET')
2196 def http_get_openflow_id(network_id):
2197 """
2198 To obtain the list of openflow rules of a network
2199 :param network_id: network id
2200 :return:
2201 """
2202 my = config_dic['http_threads'][threading.current_thread().name]
2203
2204 # ignore input data
2205 if network_id == 'all':
2206 network_id = None
2207 try:
2208 content = my.ovim.get_openflow_rules(network_id)
2209 data = {'openflow-rules': content}
2210 except ovim.ovimException as e:
2211 my.logger.error(str(e), exc_info=True)
2212 bottle.abort(e.http_code, str(e))
2213 except Exception as e:
2214 my.logger.error(str(e), exc_info=True)
2215 bottle.abort(HTTP_Bad_Request, str(e))
2216
2217 return format_out(data)
2218
2219
2220 @bottle.route(url_base + '/networks/<network_id>/openflow', method='PUT')
2221 def http_put_openflow_id(network_id):
2222 """
2223 To make actions over the net. The action is to reinstall the openflow rules
2224 network_id can be 'all'
2225 :param network_id: network id
2226 :return:
2227 """
2228 my = config_dic['http_threads'][threading.current_thread().name]
2229
2230 if not my.admin:
2231 bottle.abort(HTTP_Unauthorized, "Needed admin privileges")
2232
2233 if network_id == 'all':
2234 network_id = None
2235
2236 try:
2237 result = my.ovim.edit_openflow_rules(network_id)
2238 except ovim.ovimException as e:
2239 my.logger.error(str(e), exc_info=True)
2240 bottle.abort(e.http_code, str(e))
2241 except Exception as e:
2242 my.logger.error(str(e), exc_info=True)
2243 bottle.abort(HTTP_Bad_Request, str(e))
2244
2245 data = {'result': str(result) + " nets updates"}
2246 return format_out(data)
2247
2248 @bottle.route(url_base + '/networks/clear/openflow/<ofc_id>', method='DELETE')
2249 @bottle.route(url_base + '/networks/clear/openflow', method='DELETE')
2250 def http_clear_openflow_rules(ofc_id=None):
2251 """
2252 To make actions over the net. The action is to delete ALL openflow rules
2253 :return:
2254 """
2255 my = config_dic['http_threads'][ threading.current_thread().name]
2256
2257 if not my.admin:
2258 bottle.abort(HTTP_Unauthorized, "Needed admin privileges")
2259 try:
2260 my.ovim.delete_openflow_rules(ofc_id)
2261 except ovim.ovimException as e:
2262 my.logger.error(str(e), exc_info=True)
2263 bottle.abort(e.http_code, str(e))
2264 except Exception as e:
2265 my.logger.error(str(e), exc_info=True)
2266 bottle.abort(HTTP_Bad_Request, str(e))
2267
2268 data = {'result': " Clearing openflow rules in process"}
2269 return format_out(data)
2270
2271 @bottle.route(url_base + '/networks/openflow/ports/<ofc_id>', method='GET')
2272 @bottle.route(url_base + '/networks/openflow/ports', method='GET')
2273 def http_get_openflow_ports(ofc_id=None):
2274 """
2275 Obtain switch ports names of openflow controller
2276 :return:
2277 """
2278 my = config_dic['http_threads'][threading.current_thread().name]
2279
2280 try:
2281 ports = my.ovim.get_openflow_ports(ofc_id)
2282 data = {'ports': ports}
2283 except ovim.ovimException as e:
2284 my.logger.error(str(e), exc_info=True)
2285 bottle.abort(e.http_code, str(e))
2286 except Exception as e:
2287 my.logger.error(str(e), exc_info=True)
2288 bottle.abort(HTTP_Bad_Request, str(e))
2289
2290 return format_out(data)
2291 #
2292 # PORTS
2293 #
2294
2295
2296 @bottle.route(url_base + '/ports', method='GET')
2297 def http_get_ports():
2298 #obtain data
2299 my = config_dic['http_threads'][ threading.current_thread().name ]
2300 select_,where_,limit_ = filter_query_string(bottle.request.query, http2db_port,
2301 ('id','name','tenant_id','network_id','vpci','mac_address','device_owner','device_id',
2302 'binding:switch_port','binding:vlan','bandwidth','status','admin_state_up','ip_address') )
2303 try:
2304 ports = my.ovim.get_ports(columns=select_, filter=where_, limit=limit_)
2305 delete_nulls(ports)
2306 change_keys_http2db(ports, http2db_port, reverse=True)
2307 data={'ports' : ports}
2308 return format_out(data)
2309 except ovim.ovimException as e:
2310 my.logger.error(str(e), exc_info=True)
2311 bottle.abort(e.http_code, str(e))
2312 except Exception as e:
2313 my.logger.error(str(e), exc_info=True)
2314 bottle.abort(HTTP_Bad_Request, str(e))
2315
2316 @bottle.route(url_base + '/ports/<port_id>', method='GET')
2317 def http_get_port_id(port_id):
2318 my = config_dic['http_threads'][ threading.current_thread().name ]
2319 try:
2320 ports = my.ovim.get_ports(filter={"uuid": port_id})
2321 if not ports:
2322 bottle.abort(HTTP_Not_Found, 'port %s not found' % port_id)
2323 return
2324 delete_nulls(ports)
2325 change_keys_http2db(ports, http2db_port, reverse=True)
2326 data = {'port': ports[0]}
2327 return format_out(data)
2328 except ovim.ovimException as e:
2329 my.logger.error(str(e), exc_info=True)
2330 bottle.abort(e.http_code, str(e))
2331 except Exception as e:
2332 my.logger.error(str(e), exc_info=True)
2333 bottle.abort(HTTP_Bad_Request, str(e))
2334
2335 @bottle.route(url_base + '/ports', method='POST')
2336 def http_post_ports():
2337 '''insert an external port into the database.'''
2338 my = config_dic['http_threads'][ threading.current_thread().name ]
2339 if not my.admin:
2340 bottle.abort(HTTP_Unauthorized, "Needed admin privileges")
2341 #parse input data
2342 http_content = format_in( port_new_schema )
2343 r = remove_extra_items(http_content, port_new_schema)
2344 if r is not None: print "http_post_ports: Warning: remove extra items ", r
2345 change_keys_http2db(http_content['port'], http2db_port)
2346 port=http_content['port']
2347 try:
2348 port_id = my.ovim.new_port(port)
2349 ports = my.ovim.get_ports(filter={"uuid": port_id})
2350 if not ports:
2351 bottle.abort(HTTP_Internal_Server_Error, "port '{}' inserted but not found at database".format(port_id))
2352 return
2353 delete_nulls(ports)
2354 change_keys_http2db(ports, http2db_port, reverse=True)
2355 data = {'port': ports[0]}
2356 return format_out(data)
2357 except ovim.ovimException as e:
2358 my.logger.error(str(e), exc_info=True)
2359 bottle.abort(e.http_code, str(e))
2360 except Exception as e:
2361 my.logger.error(str(e), exc_info=True)
2362 bottle.abort(HTTP_Bad_Request, str(e))
2363
2364 @bottle.route(url_base + '/ports/<port_id>', method='PUT')
2365 def http_put_port_id(port_id):
2366 '''update a port_id into the database.'''
2367 my = config_dic['http_threads'][ threading.current_thread().name ]
2368 #parse input data
2369 http_content = format_in( port_update_schema )
2370 change_keys_http2db(http_content['port'], http2db_port)
2371 port_dict=http_content['port']
2372
2373 for k in ('vlan', 'switch_port', 'mac_address', 'tenant_id'):
2374 if k in port_dict and not my.admin:
2375 bottle.abort(HTTP_Unauthorized, "Needed admin privileges for changing " + k)
2376 return
2377 try:
2378 port_id = my.ovim.edit_port(port_id, port_dict, my.admin)
2379 ports = my.ovim.get_ports(filter={"uuid": port_id})
2380 if not ports:
2381 bottle.abort(HTTP_Internal_Server_Error, "port '{}' edited but not found at database".format(port_id))
2382 return
2383 delete_nulls(ports)
2384 change_keys_http2db(ports, http2db_port, reverse=True)
2385 data = {'port': ports[0]}
2386 return format_out(data)
2387 except ovim.ovimException as e:
2388 my.logger.error(str(e), exc_info=True)
2389 bottle.abort(e.http_code, str(e))
2390 except Exception as e:
2391 my.logger.error(str(e), exc_info=True)
2392 bottle.abort(HTTP_Bad_Request, str(e))
2393
2394
2395 @bottle.route(url_base + '/ports/<port_id>', method='DELETE')
2396 def http_delete_port_id(port_id):
2397 '''delete a port_id from the database.'''
2398 my = config_dic['http_threads'][ threading.current_thread().name ]
2399 if not my.admin:
2400 bottle.abort(HTTP_Unauthorized, "Needed admin privileges")
2401 return
2402 try:
2403 result = my.ovim.delete_port(port_id)
2404 data = {'result': result}
2405 return format_out(data)
2406 except ovim.ovimException as e:
2407 my.logger.error(str(e), exc_info=True)
2408 bottle.abort(e.http_code, str(e))
2409 except Exception as e:
2410 my.logger.error(str(e), exc_info=True)
2411 bottle.abort(HTTP_Bad_Request, str(e))
2412
2413
2414 @bottle.route(url_base + '/openflow/mapping', method='POST')
2415 def http_of_port_mapping():
2416 """
2417 Create new compute port mapping entry
2418 :return:
2419 """
2420 my = config_dic['http_threads'][threading.current_thread().name]
2421
2422 try:
2423 http_content = format_in(of_port_map_new_schema)
2424 r = remove_extra_items(http_content, of_port_map_new_schema)
2425 if r is not None:
2426 my.logger.error("http_of_port_mapping: Warning: remove extra items " + str(r), exc_info=True)
2427
2428 # insert in data base
2429 port_mapping = my.ovim.set_of_port_mapping(http_content['of_port_mapings'])
2430 change_keys_http2db(port_mapping, http2db_id, reverse=True)
2431 delete_nulls(port_mapping)
2432 data = {'of_port_mappings': port_mapping}
2433 return format_out(data)
2434 except ovim.ovimException as e:
2435 my.logger.error(str(e), exc_info=True)
2436 bottle.abort(e.http_code, str(e))
2437 except Exception as e:
2438 my.logger.error(str(e), exc_info=True)
2439 bottle.abort(HTTP_Bad_Request, str(e))
2440
2441
2442 @bottle.route(url_base + '/openflow/mapping', method='GET')
2443 def get_of_port_mapping():
2444 """
2445 Get compute port mapping
2446 :return:
2447 """
2448 my = config_dic['http_threads'][threading.current_thread().name]
2449
2450 try:
2451 select_, where_, limit_ = filter_query_string(bottle.request.query, http2db_id,
2452 ('id', 'ofc_id', 'region', 'compute_node', 'pci',
2453 'switch_dpid', 'switch_port', 'switch_mac'))
2454 # insert in data base
2455 port_mapping = my.ovim.get_of_port_mappings(select_, where_)
2456 change_keys_http2db(port_mapping, http2db_id, reverse=True)
2457 delete_nulls(port_mapping)
2458 data = {'of_port_mappings': port_mapping}
2459 return format_out(data)
2460 except ovim.ovimException as e:
2461 my.logger.error(str(e), exc_info=True)
2462 bottle.abort(e.http_code, str(e))
2463 except Exception as e:
2464 my.logger.error(str(e), exc_info=True)
2465 bottle.abort(HTTP_Bad_Request, str(e))
2466
2467
2468 @bottle.route(url_base + '/openflow/mapping/<region>', method='DELETE')
2469 def delete_of_port_mapping(region):
2470 """
2471 Insert a tenant into the database.
2472 :return:
2473 """
2474 my = config_dic['http_threads'][threading.current_thread().name]
2475
2476 try:
2477 # insert in data base
2478 db_filter = {'region': region}
2479 result = my.ovim.clear_of_port_mapping(db_filter)
2480 data = {'result': result}
2481 return format_out(data)
2482 except ovim.ovimException as e:
2483 my.logger.error(str(e), exc_info=True)
2484 bottle.abort(e.http_code, str(e))
2485 except Exception as e:
2486 my.logger.error(str(e), exc_info=True)
2487 bottle.abort(HTTP_Bad_Request, str(e))
2488