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