1 # -*- coding: utf-8 -*-
4 # Copyright 2015 Telefónica Investigación y Desarrollo, S.A.U.
5 # This file is part of openvim
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
12 # http://www.apache.org/licenses/LICENSE-2.0
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
20 # For those usages not covered by the Apache License, Version 2.0 please
21 # contact with: nfvlabs@tid.es
25 This is the thread for the http server North API.
26 Two thread will be launched, with normal and administrative permissions.
29 __author__
="Alfonso Tierno"
30 __date__
="$10-jul-2014 12:07:15$"
41 #import only if needed because not needed in test mode. To allow an easier installation import RADclass
42 from jsonschema
import validate
as js_v
, exceptions
as js_e
43 import host_thread
as ht
44 from vim_schema
import host_new_schema
, host_edit_schema
, tenant_new_schema
, \
46 flavor_new_schema
, flavor_update_schema
, \
47 image_new_schema
, image_update_schema
, \
48 server_new_schema
, server_action_schema
, network_new_schema
, network_update_schema
, \
49 port_new_schema
, port_update_schema
54 global RADclass_module
55 RADclass
=None #RADclass module is charged only if not in test mode
59 HTTP_Bad_Request
= 400
60 HTTP_Unauthorized
= 401
63 HTTP_Method_Not_Allowed
= 405
64 HTTP_Not_Acceptable
= 406
65 HTTP_Request_Timeout
= 408
67 HTTP_Service_Unavailable
= 503
68 HTTP_Internal_Server_Error
= 500
71 hash_md5
= hashlib
.md5()
72 with
open(fname
, "rb") as f
:
73 for chunk
in iter(lambda: f
.read(4096), b
""):
74 hash_md5
.update(chunk
)
75 return hash_md5
.hexdigest()
77 def check_extended(extended
, allow_net_attach
=False):
78 '''Makes and extra checking of extended input that cannot be done using jsonschema
80 allow_net_attach: for allowing or not the uuid field at interfaces
81 that are allowed for instance, but not for flavors
82 Return: (<0, error_text) if error; (0,None) if not error '''
83 if "numas" not in extended
: return 0, None
86 for numa
in extended
["numas"]:
90 if "cores-id" in numa
:
91 if len(numa
["cores-id"]) != numa
["cores"]:
92 return -HTTP_Bad_Request
, "different number of cores-id (%d) than cores (%d) at numa %d" % (len(numa
["cores-id"]), numa
["cores"],numaid
)
93 id_s
.extend(numa
["cores-id"])
96 if "threads-id" in numa
:
97 if len(numa
["threads-id"]) != numa
["threads"]:
98 return -HTTP_Bad_Request
, "different number of threads-id (%d) than threads (%d) at numa %d" % (len(numa
["threads-id"]), numa
["threads"],numaid
)
99 id_s
.extend(numa
["threads-id"])
100 if "paired-threads" in numa
:
102 if "paired-threads-id" in numa
:
103 if len(numa
["paired-threads-id"]) != numa
["paired-threads"]:
104 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
)
105 for pair
in numa
["paired-threads-id"]:
107 return -HTTP_Bad_Request
, "paired-threads-id must contain a list of two elements list at numa %d" % (numaid
)
110 return -HTTP_Service_Unavailable
, "only one of cores, threads, paired-threads are allowed in this version at numa %d" % numaid
112 if "interfaces" in numa
:
116 for interface
in numa
["interfaces"]:
117 if "uuid" in interface
and not allow_net_attach
:
118 return -HTTP_Bad_Request
, "uuid field is not allowed at numa %d interface %s position %d" % (numaid
, interface
.get("name",""), ifaceid
)
119 if "mac_address" in interface
and interface
["dedicated"]=="yes":
120 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
)
121 if "name" in interface
:
122 if interface
["name"] in names
:
123 return -HTTP_Bad_Request
, "name repeated at numa %d, interface %s position %d" % (numaid
, interface
.get("name",""), ifaceid
)
124 names
.append(interface
["name"])
125 if "vpci" in interface
:
126 if interface
["vpci"] in vpcis
:
127 return -HTTP_Bad_Request
, "vpci %s repeated at numa %d, interface %s position %d" % (interface
["vpci"], numaid
, interface
.get("name",""), ifaceid
)
128 vpcis
.append(interface
["vpci"])
132 return -HTTP_Service_Unavailable
, "only one numa can be defined in this version "
133 for a
in range(0,len(id_s
)):
135 return -HTTP_Bad_Request
, "core/thread identifiers must start at 0 and gaps are not alloed. Missing id number %d" % a
140 # dictionaries that change from HTTP API to database naming
142 http2db_host
={'id':'uuid'}
143 http2db_tenant
={'id':'uuid'}
144 http2db_flavor
={'id':'uuid','imageRef':'image_id'}
145 http2db_image
={'id':'uuid', 'created':'created_at', 'updated':'modified_at', 'public': 'public'}
146 http2db_server
={'id':'uuid','hostId':'host_id','flavorRef':'flavor_id','imageRef':'image_id','created':'created_at'}
147 http2db_network
={'id':'uuid','provider:vlan':'vlan', 'provider:physical': 'provider'}
148 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'}
150 def remove_extra_items(data
, schema
):
152 if type(data
) is tuple or type(data
) is list:
154 a
= remove_extra_items(d
, schema
['items'])
155 if a
is not None: deleted
.append(a
)
156 elif type(data
) is dict:
157 for k
in data
.keys():
158 if 'properties' not in schema
or k
not in schema
['properties'].keys():
162 a
= remove_extra_items(data
[k
], schema
['properties'][k
])
163 if a
is not None: deleted
.append({k
:a
})
164 if len(deleted
) == 0: return None
165 elif len(deleted
) == 1: return deleted
[0]
168 def delete_nulls(var
):
169 if type(var
) is dict:
171 if var
[k
] is None: del var
[k
]
172 elif type(var
[k
]) is dict or type(var
[k
]) is list or type(var
[k
]) is tuple:
173 if delete_nulls(var
[k
]): del var
[k
]
174 if len(var
) == 0: return True
175 elif type(var
) is list or type(var
) is tuple:
177 if type(k
) is dict: delete_nulls(k
)
178 if len(var
) == 0: return True
182 class httpserver(threading
.Thread
):
183 def __init__(self
, db_conn
, name
="http", host
='localhost', port
=8080, admin
=False, config_
=None):
185 Creates a new thread to attend the http connections
187 db_conn: database connection
188 name: name of this thread
189 host: ip or name where to listen
190 port: port where to listen
191 admin: if this has privileges of administrator or not
192 config_: unless the first thread must be provided. It is a global dictionary where to allocate the self variable
198 if config_
is not None:
200 if 'http_threads' not in config_dic
:
201 config_dic
['http_threads'] = {}
202 threading
.Thread
.__init
__(self
)
207 if name
in config_dic
:
208 print "httpserver Warning!!! Onether thread with the same name", name
210 while name
+str(n
) in config_dic
:
214 self
.url_preffix
= 'http://' + self
.host
+ ':' + str(self
.port
) + url_base
215 config_dic
['http_threads'][name
] = self
217 #Ensure that when the main program exits the thread will also exit
222 bottle
.run(host
=self
.host
, port
=self
.port
, debug
=True) #quiet=True
224 def gethost(self
, host_id
):
225 result
, content
= self
.db
.get_host(host_id
)
227 print "httpserver.gethost error %d %s" % (result
, content
)
228 bottle
.abort(-result
, content
)
230 print "httpserver.gethost host '%s' not found" % host_id
231 bottle
.abort(HTTP_Not_Found
, content
)
233 data
={'host' : content
}
234 convert_boolean(content
, ('admin_state_up',) )
235 change_keys_http2db(content
, http2db_host
, reverse
=True)
237 return format_out(data
)
239 @bottle.route(url_base
+ '/', method
='GET')
242 return 'works' #TODO: put links or redirection to /openvim???
248 def change_keys_http2db(data
, http_db
, reverse
=False):
249 '''Change keys of dictionary data according to the key_dict values
250 This allow change from http interface names to database names.
251 When reverse is True, the change is otherwise
253 data: can be a dictionary or a list
254 http_db: is a dictionary with hhtp names as keys and database names as value
255 reverse: by default change is done from http API to database. If True change is done otherwise
256 Return: None, but data is modified'''
257 if type(data
) is tuple or type(data
) is list:
259 change_keys_http2db(d
, http_db
, reverse
)
260 elif type(data
) is dict or type(data
) is bottle
.FormsDict
:
262 for k
,v
in http_db
.items():
263 if v
in data
: data
[k
]=data
.pop(v
)
265 for k
,v
in http_db
.items():
266 if k
in data
: data
[v
]=data
.pop(k
)
270 def format_out(data
):
271 '''return string of dictionary data according to requested json, yaml, xml. By default json'''
272 if 'application/yaml' in bottle
.request
.headers
.get('Accept'):
273 bottle
.response
.content_type
='application/yaml'
274 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='"'
275 else: #by default json
276 bottle
.response
.content_type
='application/json'
277 #return data #json no style
278 return json
.dumps(data
, indent
=4) + "\n"
280 def format_in(schema
):
282 error_text
= "Invalid header format "
283 format_type
= bottle
.request
.headers
.get('Content-Type', 'application/json')
284 if 'application/json' in format_type
:
285 error_text
= "Invalid json format "
286 #Use the json decoder instead of bottle decoder because it informs about the location of error formats with a ValueError exception
287 client_data
= json
.load(bottle
.request
.body
)
288 #client_data = bottle.request.json()
289 elif 'application/yaml' in format_type
:
290 error_text
= "Invalid yaml format "
291 client_data
= yaml
.load(bottle
.request
.body
)
292 elif format_type
== 'application/xml':
293 bottle
.abort(501, "Content-Type: application/xml not supported yet.")
295 print "HTTP HEADERS: " + str(bottle
.request
.headers
.items())
296 bottle
.abort(HTTP_Not_Acceptable
, 'Content-Type ' + str(format_type
) + ' not supported.')
298 #if client_data == None:
299 # bottle.abort(HTTP_Bad_Request, "Content error, empty")
303 #print "HTTP input data: ", str(client_data)
304 error_text
= "Invalid content "
305 js_v(client_data
, schema
)
308 except (ValueError, yaml
.YAMLError
) as exc
:
309 error_text
+= str(exc
)
311 bottle
.abort(HTTP_Bad_Request
, error_text
)
312 except js_e
.ValidationError
as exc
:
313 print "HTTP validate_in error, jsonschema exception ", exc
.message
, "at", exc
.path
314 print " CONTENT: " + str(bottle
.request
.body
.readlines())
316 if len(exc
.path
)>0: error_pos
=" at '" + ":".join(map(str, exc
.path
)) + "'"
317 bottle
.abort(HTTP_Bad_Request
, error_text
+ error_pos
+": "+exc
.message
)
319 # bottle.abort(HTTP_Bad_Request, "Content error: Failed to parse Content-Type", error_pos)
322 def filter_query_string(qs
, http2db
, allowed
):
323 '''Process query string (qs) checking that contains only valid tokens for avoiding SQL injection
325 'qs': bottle.FormsDict variable to be processed. None or empty is considered valid
326 'allowed': list of allowed string tokens (API http naming). All the keys of 'qs' must be one of 'allowed'
327 'http2db': dictionary with change from http API naming (dictionary key) to database naming(dictionary value)
328 Return: A tuple with the (select,where,limit) to be use in a database query. All of then transformed to the database naming
329 select: list of items to retrieve, filtered by query string 'field=token'. If no 'field' is present, allowed list is returned
330 where: dictionary with key, value, taken from the query string token=value. Empty if nothing is provided
331 limit: limit dictated by user with the query string 'limit'. 100 by default
332 abort if not permitted, using bottel.abort
337 if type(qs
) is not bottle
.FormsDict
:
338 print '!!!!!!!!!!!!!!invalid query string not a dictionary'
339 #bottle.abort(HTTP_Internal_Server_Error, "call programmer")
343 select
+= qs
.getall(k
)
346 bottle
.abort(HTTP_Bad_Request
, "Invalid query string at 'field="+v
+"'")
351 bottle
.abort(HTTP_Bad_Request
, "Invalid query string at 'limit="+qs
[k
]+"'")
354 bottle
.abort(HTTP_Bad_Request
, "Invalid query string at '"+k
+"="+qs
[k
]+"'")
355 if qs
[k
]!="null": where
[k
]=qs
[k
]
357 if len(select
)==0: select
+= allowed
358 #change from http api to database naming
359 for i
in range(0,len(select
)):
362 select
[i
] = http2db
[k
]
363 change_keys_http2db(where
, http2db
)
364 #print "filter_query_string", select,where,limit
366 return select
,where
,limit
369 def convert_bandwidth(data
, reverse
=False):
370 '''Check the field bandwidth recursively and when found, it removes units and convert to number
371 It assumes that bandwidth is well formed
373 'data': dictionary bottle.FormsDict variable to be checked. None or empty is considered valid
374 'reverse': by default convert form str to int (Mbps), if True it convert from number to units
378 if type(data
) is dict:
379 for k
in data
.keys():
380 if type(data
[k
]) is dict or type(data
[k
]) is tuple or type(data
[k
]) is list:
381 convert_bandwidth(data
[k
], reverse
)
382 if "bandwidth" in data
:
384 value
=str(data
["bandwidth"])
386 pos
= value
.find("bps")
388 if value
[pos
-1]=="G": data
["bandwidth"] = int(data
["bandwidth"][:pos
-1]) * 1000
389 elif value
[pos
-1]=="k": data
["bandwidth"]= int(data
["bandwidth"][:pos
-1]) / 1000
390 else: data
["bandwidth"]= int(data
["bandwidth"][:pos
-1])
392 value
= int(data
["bandwidth"])
393 if value
% 1000 == 0: data
["bandwidth"]=str(value
/1000) + " Gbps"
394 else: data
["bandwidth"]=str(value
) + " Mbps"
396 print "convert_bandwidth exception for type", type(data
["bandwidth"]), " data", data
["bandwidth"]
398 if type(data
) is tuple or type(data
) is list:
400 if type(k
) is dict or type(k
) is tuple or type(k
) is list:
401 convert_bandwidth(k
, reverse
)
403 def convert_boolean(data
, items
):
404 '''Check recursively the content of data, and if there is an key contained in items, convert value from string to boolean
405 It assumes that bandwidth is well formed
407 'data': dictionary bottle.FormsDict variable to be checked. None or empty is consideted valid
408 'items': tuple of keys to convert
412 if type(data
) is dict:
413 for k
in data
.keys():
414 if type(data
[k
]) is dict or type(data
[k
]) is tuple or type(data
[k
]) is list:
415 convert_boolean(data
[k
], items
)
417 if type(data
[k
]) is str:
418 if data
[k
]=="false": data
[k
]=False
419 elif data
[k
]=="true": data
[k
]=True
420 if type(data
) is tuple or type(data
) is list:
422 if type(k
) is dict or type(k
) is tuple or type(k
) is list:
423 convert_boolean(k
, items
)
425 def convert_datetime2str(var
):
426 '''Converts a datetime variable to a string with the format '%Y-%m-%dT%H:%i:%s'
427 It enters recursively in the dict var finding this kind of variables
429 if type(var
) is dict:
430 for k
,v
in var
.items():
431 if type(v
) is datetime
.datetime
:
432 var
[k
]= v
.strftime('%Y-%m-%dT%H:%M:%S')
433 elif type(v
) is dict or type(v
) is list or type(v
) is tuple:
434 convert_datetime2str(v
)
435 if len(var
) == 0: return True
436 elif type(var
) is list or type(var
) is tuple:
438 convert_datetime2str(v
)
440 def check_valid_tenant(my
, tenant_id
):
443 return HTTP_Unauthorized
, "Needed admin privileges"
445 result
, _
= my
.db
.get_table(FROM
='tenants', SELECT
=('uuid',), WHERE
={'uuid': tenant_id
})
447 return HTTP_Not_Found
, "tenant '%s' not found" % tenant_id
450 def check_valid_uuid(uuid
):
451 id_schema
= {"type" : "string", "pattern": "^[a-fA-F0-9]{8}(-[a-fA-F0-9]{4}){3}-[a-fA-F0-9]{12}$"}
453 js_v(uuid
, id_schema
)
455 except js_e
.ValidationError
:
461 Check if string value is a well-wormed url
462 :param url: string url
463 :return: True if is a valid url, False if is not well-formed
466 parsed_url
= urlparse
.urlparse(url
)
481 e
={"error":{"code":error
.status_code
, "type":error
.status
, "description":error
.body
}}
484 @bottle.hook('after_request')
486 #TODO: Alf: Is it needed??
487 bottle
.response
.headers
['Access-Control-Allow-Origin'] = '*'
493 @bottle.route(url_base
+ '/hosts', method
='GET')
494 def http_get_hosts():
495 select_
,where_
,limit_
= filter_query_string(bottle
.request
.query
, http2db_host
,
496 ('id','name','description','status','admin_state_up') )
498 myself
= config_dic
['http_threads'][ threading
.current_thread().name
]
499 result
, content
= myself
.db
.get_table(FROM
='hosts', SELECT
=select_
, WHERE
=where_
, LIMIT
=limit_
)
501 print "http_get_hosts Error", content
502 bottle
.abort(-result
, content
)
504 convert_boolean(content
, ('admin_state_up',) )
505 change_keys_http2db(content
, http2db_host
, reverse
=True)
507 row
['links'] = ( {'href': myself
.url_preffix
+ '/hosts/' + str(row
['id']), 'rel': 'bookmark'}, )
508 data
={'hosts' : content
}
509 return format_out(data
)
511 @bottle.route(url_base
+ '/hosts/<host_id>', method
='GET')
512 def http_get_host_id(host_id
):
513 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
514 return my
.gethost(host_id
)
516 @bottle.route(url_base
+ '/hosts', method
='POST')
517 def http_post_hosts():
518 '''insert a host into the database. All resources are got and inserted'''
519 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
522 bottle
.abort(HTTP_Unauthorized
, "Needed admin privileges")
525 http_content
= format_in( host_new_schema
)
526 r
= remove_extra_items(http_content
, host_new_schema
)
527 if r
is not None: print "http_post_host_id: Warning: remove extra items ", r
528 change_keys_http2db(http_content
['host'], http2db_host
)
530 host
= http_content
['host']
532 if 'host-data' in http_content
:
533 host
.update(http_content
['host-data'])
534 ip_name
=http_content
['host-data']['ip_name']
535 user
=http_content
['host-data']['user']
536 password
=http_content
['host-data'].get('password', None)
538 ip_name
=host
['ip_name']
540 password
=host
.get('password', None)
541 if not RADclass_module
:
543 RADclass_module
= imp
.find_module("RADclass")
544 except (IOError, ImportError) as e
:
545 raise ImportError("Cannot import RADclass.py Openvim not properly installed" +str(e
))
548 rad
= RADclass_module
.RADclass()
549 (return_status
, code
) = rad
.obtain_RAD(user
, password
, ip_name
)
552 if not return_status
:
553 print 'http_post_hosts ERROR obtaining RAD', code
554 bottle
.abort(HTTP_Bad_Request
, code
)
557 rad_structure
= yaml
.load(rad
.to_text())
558 print 'rad_structure\n---------------------'
559 print json
.dumps(rad_structure
, indent
=4)
560 print '---------------------'
562 WHERE_
={"family":rad_structure
['processor']['family'], 'manufacturer':rad_structure
['processor']['manufacturer'], 'version':rad_structure
['processor']['version']}
563 result
, content
= my
.db
.get_table(FROM
='host_ranking',
567 host
['ranking'] = content
[0]['ranking']
569 #error_text= "Host " + str(WHERE_)+ " not found in ranking table. Not valid for VIM management"
570 #bottle.abort(HTTP_Bad_Request, error_text)
572 warning_text
+= "Host " + str(WHERE_
)+ " not found in ranking table. Assuming lowest value 100\n"
573 host
['ranking'] = 100 #TODO: as not used in this version, set the lowest value
575 features
= rad_structure
['processor'].get('features', ())
576 host
['features'] = ",".join(features
)
579 for node
in (rad_structure
['resource topology']['nodes'] or {}).itervalues():
584 for core
in node
['cpu']['eligible_cores']:
585 eligible_cores
.extend(core
)
586 for core
in node
['cpu']['cores']:
587 for thread_id
in core
:
588 c
={'core_id': count
, 'thread_id': thread_id
}
589 if thread_id
not in eligible_cores
: c
['status'] = 'noteligible'
594 for port_k
, port_v
in node
['nics']['nic 0']['ports'].iteritems():
595 if port_v
['virtual']:
599 for port_k2
, port_v2
in node
['nics']['nic 0']['ports'].iteritems():
600 if port_v2
['virtual'] and port_v2
['PF_pci_id']==port_k
:
601 sriovs
.append({'pci':port_k2
, 'mac':port_v2
['mac'], 'source_name':port_v2
['source_name']})
603 #sort sriov according to pci and rename them to the vf number
604 new_sriovs
= sorted(sriovs
, key
=lambda k
: k
['pci'])
606 for sriov
in new_sriovs
:
607 sriov
['source_name'] = index
609 interfaces
.append ({'pci':str(port_k
), 'Mbps': port_v
['speed']/1000000, 'sriovs': new_sriovs
, 'mac':port_v
['mac'], 'source_name':port_v
['source_name']})
610 memory
=node
['memory']['node_size'] / (1024*1024*1024)
611 #memory=get_next_2pow(node['memory']['hugepage_nr'])
612 host
['numas'].append( {'numa_socket': node
['id'], 'hugepages': node
['memory']['hugepage_nr'], 'memory':memory
, 'interfaces': interfaces
, 'cores': cores
} )
613 print json
.dumps(host
, indent
=4)
617 result
, content
= my
.db
.new_host(host
)
619 if content
['admin_state_up']:
621 host_test_mode
= True if config_dic
['mode']=='test' or config_dic
['mode']=="OF only" else False
622 host_develop_mode
= True if config_dic
['mode']=='development' else False
623 host_develop_bridge_iface
= config_dic
.get('development_bridge', None)
624 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'],
625 test
=host_test_mode
, image_path
=config_dic
['image_path'],
626 version
=config_dic
['version'], host_id
=content
['uuid'],
627 develop_mode
=host_develop_mode
, develop_bridge_iface
=host_develop_bridge_iface
)
629 config_dic
['host_threads'][ content
['uuid'] ] = thread
632 change_keys_http2db(content
, http2db_host
, reverse
=True)
633 if len(warning_text
)>0:
634 content
["warning"]= warning_text
635 data
={'host' : content
}
636 return format_out(data
)
638 bottle
.abort(HTTP_Bad_Request
, content
)
641 @bottle.route(url_base
+ '/hosts/<host_id>', method
='PUT')
642 def http_put_host_id(host_id
):
643 '''modify a host into the database. All resources are got and inserted'''
644 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
647 bottle
.abort(HTTP_Unauthorized
, "Needed admin privileges")
650 http_content
= format_in( host_edit_schema
)
651 r
= remove_extra_items(http_content
, host_edit_schema
)
652 if r
is not None: print "http_post_host_id: Warning: remove extra items ", r
653 change_keys_http2db(http_content
['host'], http2db_host
)
656 result
, content
= my
.db
.edit_host(host_id
, http_content
['host'])
658 convert_boolean(content
, ('admin_state_up',) )
659 change_keys_http2db(content
, http2db_host
, reverse
=True)
660 data
={'host' : content
}
663 config_dic
['host_threads'][host_id
].name
= content
.get('name',content
['ip_name'])
664 config_dic
['host_threads'][host_id
].user
= content
['user']
665 config_dic
['host_threads'][host_id
].host
= content
['ip_name']
666 config_dic
['host_threads'][host_id
].insert_task("reload")
669 return format_out(data
)
671 bottle
.abort(HTTP_Bad_Request
, content
)
676 @bottle.route(url_base
+ '/hosts/<host_id>', method
='DELETE')
677 def http_delete_host_id(host_id
):
678 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
681 bottle
.abort(HTTP_Unauthorized
, "Needed admin privileges")
682 result
, content
= my
.db
.delete_row('hosts', host_id
)
684 bottle
.abort(HTTP_Not_Found
, content
)
687 if host_id
in config_dic
['host_threads']:
688 config_dic
['host_threads'][host_id
].insert_task("exit")
690 data
={'result' : content
}
691 return format_out(data
)
693 print "http_delete_host_id error",result
, content
694 bottle
.abort(-result
, content
)
703 @bottle.route(url_base
+ '/tenants', method
='GET')
704 def http_get_tenants():
705 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
706 select_
,where_
,limit_
= filter_query_string(bottle
.request
.query
, http2db_tenant
,
707 ('id','name','description','enabled') )
708 result
, content
= my
.db
.get_table(FROM
='tenants', SELECT
=select_
,WHERE
=where_
,LIMIT
=limit_
)
710 print "http_get_tenants Error", content
711 bottle
.abort(-result
, content
)
713 change_keys_http2db(content
, http2db_tenant
, reverse
=True)
714 convert_boolean(content
, ('enabled',))
715 data
={'tenants' : content
}
716 #data['tenants_links'] = dict([('tenant', row['id']) for row in content])
717 return format_out(data
)
719 @bottle.route(url_base
+ '/tenants/<tenant_id>', method
='GET')
720 def http_get_tenant_id(tenant_id
):
721 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
722 result
, content
= my
.db
.get_table(FROM
='tenants', SELECT
=('uuid','name','description', 'enabled'),WHERE
={'uuid': tenant_id
} )
724 print "http_get_tenant_id error %d %s" % (result
, content
)
725 bottle
.abort(-result
, content
)
727 print "http_get_tenant_id tenant '%s' not found" % tenant_id
728 bottle
.abort(HTTP_Not_Found
, "tenant %s not found" % tenant_id
)
730 change_keys_http2db(content
, http2db_tenant
, reverse
=True)
731 convert_boolean(content
, ('enabled',))
732 data
={'tenant' : content
[0]}
733 #data['tenants_links'] = dict([('tenant', row['id']) for row in content])
734 return format_out(data
)
737 @bottle.route(url_base
+ '/tenants', method
='POST')
738 def http_post_tenants():
739 '''insert a tenant into the database.'''
740 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
742 http_content
= format_in( tenant_new_schema
)
743 r
= remove_extra_items(http_content
, tenant_new_schema
)
744 if r
is not None: print "http_post_tenants: Warning: remove extra items ", r
745 change_keys_http2db(http_content
['tenant'], http2db_tenant
)
748 result
, content
= my
.db
.new_tenant(http_content
['tenant'])
751 return http_get_tenant_id(content
)
753 bottle
.abort(-result
, content
)
756 @bottle.route(url_base
+ '/tenants/<tenant_id>', method
='PUT')
757 def http_put_tenant_id(tenant_id
):
758 '''update a tenant into the database.'''
759 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
761 http_content
= format_in( tenant_edit_schema
)
762 r
= remove_extra_items(http_content
, tenant_edit_schema
)
763 if r
is not None: print "http_put_tenant_id: Warning: remove extra items ", r
764 change_keys_http2db(http_content
['tenant'], http2db_tenant
)
767 result
, content
= my
.db
.update_rows('tenants', http_content
['tenant'], WHERE
={'uuid': tenant_id
}, log
=True )
769 return http_get_tenant_id(tenant_id
)
771 bottle
.abort(-result
, content
)
774 @bottle.route(url_base
+ '/tenants/<tenant_id>', method
='DELETE')
775 def http_delete_tenant_id(tenant_id
):
776 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
778 r
, tenants_flavors
= my
.db
.get_table(FROM
='tenants_flavors', SELECT
=('flavor_id','tenant_id'), WHERE
={'tenant_id': tenant_id
})
781 r
, tenants_images
= my
.db
.get_table(FROM
='tenants_images', SELECT
=('image_id','tenant_id'), WHERE
={'tenant_id': tenant_id
})
784 result
, content
= my
.db
.delete_row('tenants', tenant_id
)
786 bottle
.abort(HTTP_Not_Found
, content
)
788 print "alf", tenants_flavors
, tenants_images
789 for flavor
in tenants_flavors
:
790 my
.db
.delete_row_by_key("flavors", "uuid", flavor
['flavor_id'])
791 for image
in tenants_images
:
792 my
.db
.delete_row_by_key("images", "uuid", image
['image_id'])
793 data
={'result' : content
}
794 return format_out(data
)
796 print "http_delete_tenant_id error",result
, content
797 bottle
.abort(-result
, content
)
804 @bottle.route(url_base
+ '/<tenant_id>/flavors', method
='GET')
805 def http_get_flavors(tenant_id
):
806 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
807 #check valid tenant_id
808 result
,content
= check_valid_tenant(my
, tenant_id
)
810 bottle
.abort(result
, content
)
812 select_
,where_
,limit_
= filter_query_string(bottle
.request
.query
, http2db_flavor
,
813 ('id','name','description','public') )
817 from_
='tenants_flavors inner join flavors on tenants_flavors.flavor_id=flavors.uuid'
818 where_
['tenant_id'] = tenant_id
819 result
, content
= my
.db
.get_table(FROM
=from_
, SELECT
=select_
, WHERE
=where_
, LIMIT
=limit_
)
821 print "http_get_flavors Error", content
822 bottle
.abort(-result
, content
)
824 change_keys_http2db(content
, http2db_flavor
, reverse
=True)
826 row
['links']=[ {'href': "/".join( (my
.url_preffix
, tenant_id
, 'flavors', str(row
['id']) ) ), 'rel':'bookmark' } ]
827 data
={'flavors' : content
}
828 return format_out(data
)
830 @bottle.route(url_base
+ '/<tenant_id>/flavors/<flavor_id>', method
='GET')
831 def http_get_flavor_id(tenant_id
, flavor_id
):
832 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
833 #check valid tenant_id
834 result
,content
= check_valid_tenant(my
, tenant_id
)
836 bottle
.abort(result
, content
)
838 select_
,where_
,limit_
= filter_query_string(bottle
.request
.query
, http2db_flavor
,
839 ('id','name','description','ram', 'vcpus', 'extended', 'disk', 'public') )
843 from_
='tenants_flavors as tf inner join flavors as f on tf.flavor_id=f.uuid'
844 where_
['tenant_id'] = tenant_id
845 where_
['uuid'] = flavor_id
846 result
, content
= my
.db
.get_table(SELECT
=select_
, FROM
=from_
, WHERE
=where_
, LIMIT
=limit_
)
849 print "http_get_flavor_id error %d %s" % (result
, content
)
850 bottle
.abort(-result
, content
)
852 print "http_get_flavors_id flavor '%s' not found" % str(flavor_id
)
853 bottle
.abort(HTTP_Not_Found
, 'flavor %s not found' % flavor_id
)
855 change_keys_http2db(content
, http2db_flavor
, reverse
=True)
856 if 'extended' in content
[0] and content
[0]['extended'] is not None:
857 extended
= json
.loads(content
[0]['extended'])
858 if 'devices' in extended
:
859 change_keys_http2db(extended
['devices'], http2db_flavor
, reverse
=True)
860 content
[0]['extended']=extended
861 convert_bandwidth(content
[0], reverse
=True)
862 content
[0]['links']=[ {'href': "/".join( (my
.url_preffix
, tenant_id
, 'flavors', str(content
[0]['id']) ) ), 'rel':'bookmark' } ]
863 data
={'flavor' : content
[0]}
864 #data['tenants_links'] = dict([('tenant', row['id']) for row in content])
865 return format_out(data
)
868 @bottle.route(url_base
+ '/<tenant_id>/flavors', method
='POST')
869 def http_post_flavors(tenant_id
):
870 '''insert a flavor into the database, and attach to tenant.'''
871 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
872 #check valid tenant_id
873 result
,content
= check_valid_tenant(my
, tenant_id
)
875 bottle
.abort(result
, content
)
876 http_content
= format_in( flavor_new_schema
)
877 r
= remove_extra_items(http_content
, flavor_new_schema
)
878 if r
is not None: print "http_post_flavors: Warning: remove extra items ", r
879 change_keys_http2db(http_content
['flavor'], http2db_flavor
)
880 extended_dict
= http_content
['flavor'].pop('extended', None)
881 if extended_dict
is not None:
882 result
, content
= check_extended(extended_dict
)
884 print "http_post_flavors wrong input extended error %d %s" % (result
, content
)
885 bottle
.abort(-result
, content
)
887 convert_bandwidth(extended_dict
)
888 if 'devices' in extended_dict
: change_keys_http2db(extended_dict
['devices'], http2db_flavor
)
889 http_content
['flavor']['extended'] = json
.dumps(extended_dict
)
891 result
, content
= my
.db
.new_flavor(http_content
['flavor'], tenant_id
)
893 return http_get_flavor_id(tenant_id
, content
)
895 print "http_psot_flavors error %d %s" % (result
, content
)
896 bottle
.abort(-result
, content
)
899 @bottle.route(url_base
+ '/<tenant_id>/flavors/<flavor_id>', method
='DELETE')
900 def http_delete_flavor_id(tenant_id
, flavor_id
):
901 '''Deletes the flavor_id of a tenant. IT removes from tenants_flavors table.'''
902 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
903 #check valid tenant_id
904 result
,content
= check_valid_tenant(my
, tenant_id
)
906 bottle
.abort(result
, content
)
908 result
, content
= my
.db
.delete_image_flavor('flavor', flavor_id
, tenant_id
)
910 bottle
.abort(HTTP_Not_Found
, content
)
912 data
={'result' : content
}
913 return format_out(data
)
915 print "http_delete_flavor_id error",result
, content
916 bottle
.abort(-result
, content
)
919 @bottle.route(url_base
+ '/<tenant_id>/flavors/<flavor_id>/<action>', method
='POST')
920 def http_attach_detach_flavors(tenant_id
, flavor_id
, action
):
921 '''attach/detach an existing flavor in this tenant. That is insert/remove at tenants_flavors table.'''
922 #TODO alf: not tested at all!!!
923 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
924 #check valid tenant_id
925 result
,content
= check_valid_tenant(my
, tenant_id
)
927 bottle
.abort(result
, content
)
929 bottle
.abort(HTTP_Bad_Request
, "Invalid tenant 'any' with this command")
931 if action
!='attach' and action
!= 'detach':
932 bottle
.abort(HTTP_Method_Not_Allowed
, "actions can be attach or detach")
935 #Ensure that flavor exist
936 from_
='tenants_flavors as tf right join flavors as f on tf.flavor_id=f.uuid'
937 where_
={'uuid': flavor_id
}
938 result
, content
= my
.db
.get_table(SELECT
=('public','tenant_id'), FROM
=from_
, WHERE
=where_
)
941 text_error
="Flavor '%s' not found" % flavor_id
943 text_error
="Flavor '%s' not found for tenant '%s'" % (flavor_id
, tenant_id
)
944 bottle
.abort(HTTP_Not_Found
, text_error
)
949 if flavor
['tenant_id']!=None:
950 bottle
.abort(HTTP_Conflict
, "Flavor '%s' already attached to tenant '%s'" % (flavor_id
, tenant_id
))
951 if flavor
['public']=='no' and not my
.admin
:
952 #allow only attaching public flavors
953 bottle
.abort(HTTP_Unauthorized
, "Needed admin rights to attach a private flavor")
956 result
, content
= my
.db
.new_row('tenants_flavors', {'flavor_id':flavor_id
, 'tenant_id': tenant_id
})
958 return http_get_flavor_id(tenant_id
, flavor_id
)
960 if flavor
['tenant_id']==None:
961 bottle
.abort(HTTP_Not_Found
, "Flavor '%s' not attached to tenant '%s'" % (flavor_id
, tenant_id
))
962 result
, content
= my
.db
.delete_row_by_dict(FROM
='tenants_flavors', WHERE
={'flavor_id':flavor_id
, 'tenant_id':tenant_id
})
964 if flavor
['public']=='no':
965 #try to delete the flavor completely to avoid orphan flavors, IGNORE error
966 my
.db
.delete_row_by_dict(FROM
='flavors', WHERE
={'uuid':flavor_id
})
967 data
={'result' : "flavor detached"}
968 return format_out(data
)
970 #if get here is because an error
971 print "http_attach_detach_flavors error %d %s" % (result
, content
)
972 bottle
.abort(-result
, content
)
975 @bottle.route(url_base
+ '/<tenant_id>/flavors/<flavor_id>', method
='PUT')
976 def http_put_flavor_id(tenant_id
, flavor_id
):
977 '''update a flavor_id into the database.'''
978 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
979 #check valid tenant_id
980 result
,content
= check_valid_tenant(my
, tenant_id
)
982 bottle
.abort(result
, content
)
984 http_content
= format_in( flavor_update_schema
)
985 r
= remove_extra_items(http_content
, flavor_update_schema
)
986 if r
is not None: print "http_put_flavor_id: Warning: remove extra items ", r
987 change_keys_http2db(http_content
['flavor'], http2db_flavor
)
988 extended_dict
= http_content
['flavor'].pop('extended', None)
989 if extended_dict
is not None:
990 result
, content
= check_extended(extended_dict
)
992 print "http_put_flavor_id wrong input extended error %d %s" % (result
, content
)
993 bottle
.abort(-result
, content
)
995 convert_bandwidth(extended_dict
)
996 if 'devices' in extended_dict
: change_keys_http2db(extended_dict
['devices'], http2db_flavor
)
997 http_content
['flavor']['extended'] = json
.dumps(extended_dict
)
998 #Ensure that flavor exist
999 where_
={'uuid': flavor_id
}
1000 if tenant_id
=='any':
1003 from_
='tenants_flavors as ti inner join flavors as i on ti.flavor_id=i.uuid'
1004 where_
['tenant_id'] = tenant_id
1005 result
, content
= my
.db
.get_table(SELECT
=('public',), FROM
=from_
, WHERE
=where_
)
1007 text_error
="Flavor '%s' not found" % flavor_id
1008 if tenant_id
!='any':
1009 text_error
+=" for tenant '%s'" % flavor_id
1010 bottle
.abort(HTTP_Not_Found
, text_error
)
1013 if content
[0]['public']=='yes' and not my
.admin
:
1014 #allow only modifications over private flavors
1015 bottle
.abort(HTTP_Unauthorized
, "Needed admin rights to edit a public flavor")
1017 #insert in data base
1018 result
, content
= my
.db
.update_rows('flavors', http_content
['flavor'], {'uuid': flavor_id
})
1021 print "http_put_flavor_id error %d %s" % (result
, content
)
1022 bottle
.abort(-result
, content
)
1025 return http_get_flavor_id(tenant_id
, flavor_id
)
1033 @bottle.route(url_base
+ '/<tenant_id>/images', method
='GET')
1034 def http_get_images(tenant_id
):
1035 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
1036 #check valid tenant_id
1037 result
,content
= check_valid_tenant(my
, tenant_id
)
1039 bottle
.abort(result
, content
)
1041 select_
,where_
,limit_
= filter_query_string(bottle
.request
.query
, http2db_image
,
1042 ('id','name','description','path','public') )
1043 if tenant_id
=='any':
1047 from_
='tenants_images right join images on tenants_images.image_id=images.uuid'
1048 where_or_
= {'tenant_id': tenant_id
, 'public': 'yes'}
1049 result
, content
= my
.db
.get_table(SELECT
=select_
, FROM
=from_
, WHERE
=where_
, WHERE_OR
=where_or_
, WHERE_AND_OR
="AND", LIMIT
=limit_
)
1051 print "http_get_images Error", content
1052 bottle
.abort(-result
, content
)
1054 change_keys_http2db(content
, http2db_image
, reverse
=True)
1055 #for row in content: row['links']=[ {'href': "/".join( (my.url_preffix, tenant_id, 'images', str(row['id']) ) ), 'rel':'bookmark' } ]
1056 data
={'images' : content
}
1057 return format_out(data
)
1059 @bottle.route(url_base
+ '/<tenant_id>/images/<image_id>', method
='GET')
1060 def http_get_image_id(tenant_id
, image_id
):
1061 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
1062 #check valid tenant_id
1063 result
,content
= check_valid_tenant(my
, tenant_id
)
1065 bottle
.abort(result
, content
)
1067 select_
,where_
,limit_
= filter_query_string(bottle
.request
.query
, http2db_image
,
1068 ('id','name','description','progress', 'status','path', 'created', 'updated','public') )
1069 if tenant_id
=='any':
1073 from_
='tenants_images as ti right join images as i on ti.image_id=i.uuid'
1074 where_or_
= {'tenant_id': tenant_id
, 'public': "yes"}
1075 where_
['uuid'] = image_id
1076 result
, content
= my
.db
.get_table(SELECT
=select_
, FROM
=from_
, WHERE
=where_
, WHERE_OR
=where_or_
, WHERE_AND_OR
="AND", LIMIT
=limit_
)
1079 print "http_get_images error %d %s" % (result
, content
)
1080 bottle
.abort(-result
, content
)
1082 print "http_get_images image '%s' not found" % str(image_id
)
1083 bottle
.abort(HTTP_Not_Found
, 'image %s not found' % image_id
)
1085 convert_datetime2str(content
)
1086 change_keys_http2db(content
, http2db_image
, reverse
=True)
1087 if 'metadata' in content
[0] and content
[0]['metadata'] is not None:
1088 metadata
= json
.loads(content
[0]['metadata'])
1089 content
[0]['metadata']=metadata
1090 content
[0]['links']=[ {'href': "/".join( (my
.url_preffix
, tenant_id
, 'images', str(content
[0]['id']) ) ), 'rel':'bookmark' } ]
1091 data
={'image' : content
[0]}
1092 #data['tenants_links'] = dict([('tenant', row['id']) for row in content])
1093 return format_out(data
)
1095 @bottle.route(url_base
+ '/<tenant_id>/images', method
='POST')
1096 def http_post_images(tenant_id
):
1097 '''insert a image into the database, and attach to tenant.'''
1098 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
1099 #check valid tenant_id
1100 result
,content
= check_valid_tenant(my
, tenant_id
)
1102 bottle
.abort(result
, content
)
1103 http_content
= format_in(image_new_schema
)
1104 r
= remove_extra_items(http_content
, image_new_schema
)
1105 if r
is not None: print "http_post_images: Warning: remove extra items ", r
1106 change_keys_http2db(http_content
['image'], http2db_image
)
1107 metadata_dict
= http_content
['image'].pop('metadata', None)
1108 if metadata_dict
is not None:
1109 http_content
['image']['metadata'] = json
.dumps(metadata_dict
)
1111 host_test_mode
= True if config_dic
['mode']=='test' or config_dic
['mode']=="OF only" else False
1113 image_file
= http_content
['image'].get('path',None)
1114 if os
.path
.exists(image_file
):
1115 http_content
['image']['checksum'] = md5(image_file
)
1116 elif is_url(image_file
):
1119 if not host_test_mode
:
1120 content
= "Image file not found"
1121 print "http_post_images error: %d %s" % (HTTP_Bad_Request
, content
)
1122 bottle
.abort(HTTP_Bad_Request
, content
)
1123 except Exception as e
:
1124 print "ERROR. Unexpected exception: %s" % (str(e
))
1125 bottle
.abort(HTTP_Internal_Server_Error
, type(e
).__name
__ + ": " + str(e
))
1126 #insert in data base
1127 result
, content
= my
.db
.new_image(http_content
['image'], tenant_id
)
1129 return http_get_image_id(tenant_id
, content
)
1131 print "http_post_images error %d %s" % (result
, content
)
1132 bottle
.abort(-result
, content
)
1135 @bottle.route(url_base
+ '/<tenant_id>/images/<image_id>', method
='DELETE')
1136 def http_delete_image_id(tenant_id
, image_id
):
1137 '''Deletes the image_id of a tenant. IT removes from tenants_images table.'''
1138 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
1139 #check valid tenant_id
1140 result
,content
= check_valid_tenant(my
, tenant_id
)
1142 bottle
.abort(result
, content
)
1143 result
, content
= my
.db
.delete_image_flavor('image', image_id
, tenant_id
)
1145 bottle
.abort(HTTP_Not_Found
, content
)
1147 data
={'result' : content
}
1148 return format_out(data
)
1150 print "http_delete_image_id error",result
, content
1151 bottle
.abort(-result
, content
)
1154 @bottle.route(url_base
+ '/<tenant_id>/images/<image_id>/<action>', method
='POST')
1155 def http_attach_detach_images(tenant_id
, image_id
, action
):
1156 '''attach/detach an existing image in this tenant. That is insert/remove at tenants_images table.'''
1157 #TODO alf: not tested at all!!!
1158 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
1159 #check valid tenant_id
1160 result
,content
= check_valid_tenant(my
, tenant_id
)
1162 bottle
.abort(result
, content
)
1163 if tenant_id
=='any':
1164 bottle
.abort(HTTP_Bad_Request
, "Invalid tenant 'any' with this command")
1166 if action
!='attach' and action
!= 'detach':
1167 bottle
.abort(HTTP_Method_Not_Allowed
, "actions can be attach or detach")
1170 #Ensure that image exist
1171 from_
='tenants_images as ti right join images as i on ti.image_id=i.uuid'
1172 where_
={'uuid': image_id
}
1173 result
, content
= my
.db
.get_table(SELECT
=('public','tenant_id'), FROM
=from_
, WHERE
=where_
)
1175 if action
=='attach':
1176 text_error
="Image '%s' not found" % image_id
1178 text_error
="Image '%s' not found for tenant '%s'" % (image_id
, tenant_id
)
1179 bottle
.abort(HTTP_Not_Found
, text_error
)
1183 if action
=='attach':
1184 if image
['tenant_id']!=None:
1185 bottle
.abort(HTTP_Conflict
, "Image '%s' already attached to tenant '%s'" % (image_id
, tenant_id
))
1186 if image
['public']=='no' and not my
.admin
:
1187 #allow only attaching public images
1188 bottle
.abort(HTTP_Unauthorized
, "Needed admin rights to attach a private image")
1190 #insert in data base
1191 result
, content
= my
.db
.new_row('tenants_images', {'image_id':image_id
, 'tenant_id': tenant_id
})
1193 return http_get_image_id(tenant_id
, image_id
)
1195 if image
['tenant_id']==None:
1196 bottle
.abort(HTTP_Not_Found
, "Image '%s' not attached to tenant '%s'" % (image_id
, tenant_id
))
1197 result
, content
= my
.db
.delete_row_by_dict(FROM
='tenants_images', WHERE
={'image_id':image_id
, 'tenant_id':tenant_id
})
1199 if image
['public']=='no':
1200 #try to delete the image completely to avoid orphan images, IGNORE error
1201 my
.db
.delete_row_by_dict(FROM
='images', WHERE
={'uuid':image_id
})
1202 data
={'result' : "image detached"}
1203 return format_out(data
)
1205 #if get here is because an error
1206 print "http_attach_detach_images error %d %s" % (result
, content
)
1207 bottle
.abort(-result
, content
)
1210 @bottle.route(url_base
+ '/<tenant_id>/images/<image_id>', method
='PUT')
1211 def http_put_image_id(tenant_id
, image_id
):
1212 '''update a image_id into the database.'''
1213 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
1214 #check valid tenant_id
1215 result
,content
= check_valid_tenant(my
, tenant_id
)
1217 bottle
.abort(result
, content
)
1219 http_content
= format_in( image_update_schema
)
1220 r
= remove_extra_items(http_content
, image_update_schema
)
1221 if r
is not None: print "http_put_image_id: Warning: remove extra items ", r
1222 change_keys_http2db(http_content
['image'], http2db_image
)
1223 metadata_dict
= http_content
['image'].pop('metadata', None)
1224 if metadata_dict
is not None:
1225 http_content
['image']['metadata'] = json
.dumps(metadata_dict
)
1226 #Ensure that image exist
1227 where_
={'uuid': image_id
}
1228 if tenant_id
=='any':
1231 from_
='tenants_images as ti inner join images as i on ti.image_id=i.uuid'
1232 where_
['tenant_id'] = tenant_id
1233 result
, content
= my
.db
.get_table(SELECT
=('public',), FROM
=from_
, WHERE
=where_
)
1235 text_error
="Image '%s' not found" % image_id
1236 if tenant_id
!='any':
1237 text_error
+=" for tenant '%s'" % image_id
1238 bottle
.abort(HTTP_Not_Found
, text_error
)
1241 if content
[0]['public']=='yes' and not my
.admin
:
1242 #allow only modifications over private images
1243 bottle
.abort(HTTP_Unauthorized
, "Needed admin rights to edit a public image")
1245 #insert in data base
1246 result
, content
= my
.db
.update_rows('images', http_content
['image'], {'uuid': image_id
})
1249 print "http_put_image_id error %d %s" % (result
, content
)
1250 bottle
.abort(-result
, content
)
1253 return http_get_image_id(tenant_id
, image_id
)
1260 @bottle.route(url_base
+ '/<tenant_id>/servers', method
='GET')
1261 def http_get_servers(tenant_id
):
1262 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
1263 result
,content
= check_valid_tenant(my
, tenant_id
)
1265 bottle
.abort(result
, content
)
1268 select_
,where_
,limit_
= filter_query_string(bottle
.request
.query
, http2db_server
,
1269 ('id','name','description','hostId','imageRef','flavorRef','status', 'tenant_id') )
1270 if tenant_id
!='any':
1271 where_
['tenant_id'] = tenant_id
1272 result
, content
= my
.db
.get_table(SELECT
=select_
, FROM
='instances', WHERE
=where_
, LIMIT
=limit_
)
1274 print "http_get_servers Error", content
1275 bottle
.abort(-result
, content
)
1277 change_keys_http2db(content
, http2db_server
, reverse
=True)
1279 tenant_id
= row
.pop('tenant_id')
1280 row
['links']=[ {'href': "/".join( (my
.url_preffix
, tenant_id
, 'servers', str(row
['id']) ) ), 'rel':'bookmark' } ]
1281 data
={'servers' : content
}
1282 return format_out(data
)
1284 @bottle.route(url_base
+ '/<tenant_id>/servers/<server_id>', method
='GET')
1285 def http_get_server_id(tenant_id
, server_id
):
1286 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
1287 #check valid tenant_id
1288 result
,content
= check_valid_tenant(my
, tenant_id
)
1290 bottle
.abort(result
, content
)
1293 result
, content
= my
.db
.get_instance(server_id
)
1295 bottle
.abort(HTTP_Not_Found
, content
)
1297 #change image/flavor-id to id and link
1298 convert_bandwidth(content
, reverse
=True)
1299 convert_datetime2str(content
)
1300 if content
["ram"]==0 : del content
["ram"]
1301 if content
["vcpus"]==0 : del content
["vcpus"]
1302 if 'flavor_id' in content
:
1303 if content
['flavor_id'] is not None:
1304 content
['flavor'] = {'id':content
['flavor_id'],
1305 'links':[{'href': "/".join( (my
.url_preffix
, content
['tenant_id'], 'flavors', str(content
['flavor_id']) ) ), 'rel':'bookmark'}]
1307 del content
['flavor_id']
1308 if 'image_id' in content
:
1309 if content
['image_id'] is not None:
1310 content
['image'] = {'id':content
['image_id'],
1311 'links':[{'href': "/".join( (my
.url_preffix
, content
['tenant_id'], 'images', str(content
['image_id']) ) ), 'rel':'bookmark'}]
1313 del content
['image_id']
1314 change_keys_http2db(content
, http2db_server
, reverse
=True)
1315 if 'extended' in content
:
1316 if 'devices' in content
['extended']: change_keys_http2db(content
['extended']['devices'], http2db_server
, reverse
=True)
1318 data
={'server' : content
}
1319 return format_out(data
)
1321 bottle
.abort(-result
, content
)
1324 @bottle.route(url_base
+ '/<tenant_id>/servers', method
='POST')
1325 def http_post_server_id(tenant_id
):
1326 '''deploys a new server'''
1327 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
1328 #check valid tenant_id
1329 result
,content
= check_valid_tenant(my
, tenant_id
)
1331 bottle
.abort(result
, content
)
1333 if tenant_id
=='any':
1334 bottle
.abort(HTTP_Bad_Request
, "Invalid tenant 'any' with this command")
1336 http_content
= format_in( server_new_schema
)
1337 r
= remove_extra_items(http_content
, server_new_schema
)
1338 if r
is not None: print "http_post_serves: Warning: remove extra items ", r
1339 change_keys_http2db(http_content
['server'], http2db_server
)
1340 extended_dict
= http_content
['server'].get('extended', None)
1341 if extended_dict
is not None:
1342 result
, content
= check_extended(extended_dict
, True)
1344 print "http_post_servers wrong input extended error %d %s" % (result
, content
)
1345 bottle
.abort(-result
, content
)
1347 convert_bandwidth(extended_dict
)
1348 if 'devices' in extended_dict
: change_keys_http2db(extended_dict
['devices'], http2db_server
)
1350 server
= http_content
['server']
1351 server_start
= server
.get('start', 'yes')
1352 server
['tenant_id'] = tenant_id
1353 #check flavor valid and take info
1354 result
, content
= my
.db
.get_table(FROM
='tenants_flavors as tf join flavors as f on tf.flavor_id=f.uuid',
1355 SELECT
=('ram','vcpus','extended'), WHERE
={'uuid':server
['flavor_id'], 'tenant_id':tenant_id
})
1357 bottle
.abort(HTTP_Not_Found
, 'flavor_id %s not found' % server
['flavor_id'])
1359 server
['flavor']=content
[0]
1360 #check image valid and take info
1361 result
, content
= my
.db
.get_table(FROM
='tenants_images as ti join images as i on ti.image_id=i.uuid',
1362 SELECT
=('path','metadata'), WHERE
={'uuid':server
['image_id'], 'tenant_id':tenant_id
, "status":"ACTIVE"})
1364 bottle
.abort(HTTP_Not_Found
, 'image_id %s not found or not ACTIVE' % server
['image_id'])
1366 server
['image']=content
[0]
1367 if "hosts_id" in server
:
1368 result
, content
= my
.db
.get_table(FROM
='hosts', SELECT
=('uuid',), WHERE
={'uuid': server
['host_id']})
1370 bottle
.abort(HTTP_Not_Found
, 'hostId %s not found' % server
['host_id'])
1372 #print json.dumps(server, indent=4)
1374 result
, content
= ht
.create_server(server
, config_dic
['db'], config_dic
['db_lock'], config_dic
['mode']=='normal')
1377 #Insert instance to database
1380 print "inserting at DB"
1382 if server_start
== 'no':
1383 content
['status'] = 'INACTIVE'
1385 new_instance_result
, new_instance
= my
.db
.new_instance(content
, nets
, ports_to_free
)
1386 if new_instance_result
< 0:
1387 print "Error http_post_servers() :", new_instance_result
, new_instance
1388 bottle
.abort(-new_instance_result
, new_instance
)
1391 print "inserted at DB"
1393 for port
in ports_to_free
:
1394 r
,c
= config_dic
['host_threads'][ server
['host_id'] ].insert_task( 'restore-iface',*port
)
1396 print ' http_post_servers ERROR RESTORE IFACE!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!' + c
1399 r
,c
= config_dic
['of_thread'].insert_task("update-net", net
)
1401 print ':http_post_servers ERROR UPDATING NETS !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!' + c
1405 #look for dhcp ip address
1406 r2
, c2
= my
.db
.get_table(FROM
="ports", SELECT
=["mac", "net_id"], WHERE
={"instance_id": new_instance
})
1407 if r2
>0 and config_dic
.get("dhcp_server"):
1409 if iface
["net_id"] in config_dic
["dhcp_nets"]:
1410 #print "dhcp insert add task"
1411 r
,c
= config_dic
['dhcp_thread'].insert_task("add", iface
["mac"])
1413 print ':http_post_servers ERROR UPDATING dhcp_server !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!' + c
1417 server
['uuid'] = new_instance
1418 #server_start = server.get('start', 'yes')
1419 if server_start
!= 'no':
1420 server
['paused'] = True if server_start
== 'paused' else False
1421 server
['action'] = {"start":None}
1422 server
['status'] = "CREATING"
1424 r
,c
= config_dic
['host_threads'][ server
['host_id'] ].insert_task( 'instance',server
)
1426 my
.db
.update_rows('instances', {'status':"ERROR"}, {'uuid':server
['uuid'], 'last_error':c
}, log
=True)
1428 return http_get_server_id(tenant_id
, new_instance
)
1430 bottle
.abort(HTTP_Bad_Request
, content
)
1433 def http_server_action(server_id
, tenant_id
, action
):
1434 '''Perform actions over a server as resume, reboot, terminate, ...'''
1435 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
1436 server
={"uuid": server_id
, "action":action
}
1437 where
={'uuid': server_id
}
1438 if tenant_id
!='any':
1439 where
['tenant_id']= tenant_id
1440 result
, content
= my
.db
.get_table(FROM
='instances', WHERE
=where
)
1442 bottle
.abort(HTTP_Not_Found
, "server %s not found" % server_id
)
1445 print "http_post_server_action error getting data %d %s" % (result
, content
)
1446 bottle
.abort(HTTP_Internal_Server_Error
, content
)
1448 server
.update(content
[0])
1449 tenant_id
= server
["tenant_id"]
1451 #TODO check a right content
1453 if 'terminate' in action
:
1454 new_status
='DELETING'
1455 elif server
['status'] == 'ERROR': #or server['status'] == 'CREATING':
1456 if 'terminate' not in action
and 'rebuild' not in action
:
1457 bottle
.abort(HTTP_Method_Not_Allowed
, "Server is in ERROR status, must be rebuit or deleted ")
1459 # elif server['status'] == 'INACTIVE':
1460 # if 'start' not in action and 'createImage' not in action:
1461 # bottle.abort(HTTP_Method_Not_Allowed, "The only possible action over an instance in 'INACTIVE' status is 'start'")
1463 # if 'start' in action:
1464 # new_status='CREATING'
1465 # server['paused']='no'
1466 # elif server['status'] == 'PAUSED':
1467 # if 'resume' not in action:
1468 # bottle.abort(HTTP_Method_Not_Allowed, "The only possible action over an instance in 'PAUSED' status is 'resume'")
1470 # elif server['status'] == 'ACTIVE':
1471 # if 'pause' not in action and 'reboot'not in action and 'shutoff'not in action:
1472 # bottle.abort(HTTP_Method_Not_Allowed, "The only possible action over an instance in 'ACTIVE' status is 'pause','reboot' or 'shutoff'")
1475 if 'start' in action
or 'createImage' in action
or 'rebuild' in action
:
1476 #check image valid and take info
1477 image_id
= server
['image_id']
1478 if 'createImage' in action
:
1479 if 'imageRef' in action
['createImage']:
1480 image_id
= action
['createImage']['imageRef']
1481 elif 'disk' in action
['createImage']:
1482 result
, content
= my
.db
.get_table(FROM
='instance_devices',
1483 SELECT
=('image_id','dev'), WHERE
={'instance_id':server
['uuid'],"type":"disk"})
1485 bottle
.abort(HTTP_Not_Found
, 'disk not found for server')
1489 if action
['createImage']['imageRef']['disk'] != None:
1490 for disk
in content
:
1491 if disk
['dev'] == action
['createImage']['imageRef']['disk']:
1492 disk_id
= disk
['image_id']
1495 bottle
.abort(HTTP_Not_Found
, 'disk %s not found for server' % action
['createImage']['imageRef']['disk'])
1498 bottle
.abort(HTTP_Not_Found
, 'more than one disk found for server' )
1502 image_id
= content
[0]['image_id']
1504 result
, content
= my
.db
.get_table(FROM
='tenants_images as ti join images as i on ti.image_id=i.uuid',
1505 SELECT
=('path','metadata'), WHERE
={'uuid':image_id
, 'tenant_id':tenant_id
, "status":"ACTIVE"})
1507 bottle
.abort(HTTP_Not_Found
, 'image_id %s not found or not ACTIVE' % image_id
)
1509 if content
[0]['metadata'] is not None:
1511 metadata
= json
.loads(content
[0]['metadata'])
1513 return -HTTP_Internal_Server_Error
, "Can not decode image metadata"
1514 content
[0]['metadata']=metadata
1516 content
[0]['metadata'] = {}
1517 server
['image']=content
[0]
1518 if 'createImage' in action
:
1519 action
['createImage']['source'] = {'image_id': image_id
, 'path': content
[0]['path']}
1520 if 'createImage' in action
:
1521 #Create an entry in Database for the new image
1522 new_image
={'status':'BUILD', 'progress': 0 }
1523 new_image_metadata
=content
[0]
1524 if 'metadata' in server
['image'] and server
['image']['metadata'] != None:
1525 new_image_metadata
.update(server
['image']['metadata'])
1526 new_image_metadata
= {"use_incremental":"no"}
1527 if 'metadata' in action
['createImage']:
1528 new_image_metadata
.update(action
['createImage']['metadata'])
1529 new_image
['metadata'] = json
.dumps(new_image_metadata
)
1530 new_image
['name'] = action
['createImage'].get('name', None)
1531 new_image
['description'] = action
['createImage'].get('description', None)
1532 new_image
['uuid']=my
.db
.new_uuid()
1533 if 'path' in action
['createImage']:
1534 new_image
['path'] = action
['createImage']['path']
1536 new_image
['path']="/provisional/path/" + new_image
['uuid']
1537 result
, image_uuid
= my
.db
.new_image(new_image
, tenant_id
)
1539 bottle
.abort(HTTP_Bad_Request
, 'Error: ' + image_uuid
)
1541 server
['new_image'] = new_image
1545 r
,c
= config_dic
['host_threads'][ server
['host_id'] ].insert_task( 'instance',server
)
1547 print "Task queue full at host ", server
['host_id']
1548 bottle
.abort(HTTP_Request_Timeout
, c
)
1549 if 'createImage' in action
and result
>= 0:
1550 return http_get_image_id(tenant_id
, image_uuid
)
1552 #Update DB only for CREATING or DELETING status
1553 data
={'result' : 'in process'}
1554 if new_status
!= None and new_status
== 'DELETING':
1557 #look for dhcp ip address
1558 r2
, c2
= my
.db
.get_table(FROM
="ports", SELECT
=["mac", "net_id"], WHERE
={"instance_id": server_id
})
1559 r
,c
= my
.db
.delete_instance(server_id
, tenant_id
, nets
, ports_to_free
, "requested by http")
1560 for port
in ports_to_free
:
1561 r1
,c1
= config_dic
['host_threads'][ server
['host_id'] ].insert_task( 'restore-iface',*port
)
1563 print ' http_post_server_action error at server deletion ERROR resore-iface !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!' + c1
1564 data
={'result' : 'deleting in process, but ifaces cannot be restored!!!!!'}
1566 r1
,c1
= config_dic
['of_thread'].insert_task("update-net", net
)
1568 print ' http_post_server_action error at server deletion ERROR UPDATING NETS !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!' + c1
1569 data
={'result' : 'deleting in process, but openflow rules cannot be deleted!!!!!'}
1570 #look for dhcp ip address
1571 if r2
>0 and config_dic
.get("dhcp_server"):
1573 if iface
["net_id"] in config_dic
["dhcp_nets"]:
1574 r
,c
= config_dic
['dhcp_thread'].insert_task("del", iface
["mac"])
1575 #print "dhcp insert del task"
1577 print ':http_post_servers ERROR UPDATING dhcp_server !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!' + c
1579 return format_out(data
)
1583 @bottle.route(url_base
+ '/<tenant_id>/servers/<server_id>', method
='DELETE')
1584 def http_delete_server_id(tenant_id
, server_id
):
1585 '''delete a server'''
1586 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
1587 #check valid tenant_id
1588 result
,content
= check_valid_tenant(my
, tenant_id
)
1590 bottle
.abort(result
, content
)
1593 return http_server_action(server_id
, tenant_id
, {"terminate":None} )
1596 @bottle.route(url_base
+ '/<tenant_id>/servers/<server_id>/action', method
='POST')
1597 def http_post_server_action(tenant_id
, server_id
):
1598 '''take an action over a server'''
1599 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
1600 #check valid tenant_id
1601 result
,content
= check_valid_tenant(my
, tenant_id
)
1603 bottle
.abort(result
, content
)
1605 http_content
= format_in( server_action_schema
)
1606 #r = remove_extra_items(http_content, server_action_schema)
1607 #if r is not None: print "http_post_server_action: Warning: remove extra items ", r
1609 return http_server_action(server_id
, tenant_id
, http_content
)
1616 @bottle.route(url_base
+ '/networks', method
='GET')
1617 def http_get_networks():
1618 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
1620 select_
,where_
,limit_
= filter_query_string(bottle
.request
.query
, http2db_network
,
1621 ('id','name','tenant_id','type',
1622 'shared','provider:vlan','status','last_error','admin_state_up','provider:physical') )
1623 #TODO temporally remove tenant_id
1624 if "tenant_id" in where_
:
1625 del where_
["tenant_id"]
1626 result
, content
= my
.db
.get_table(SELECT
=select_
, FROM
='nets', WHERE
=where_
, LIMIT
=limit_
)
1628 print "http_get_networks error %d %s" % (result
, content
)
1629 bottle
.abort(-result
, content
)
1631 convert_boolean(content
, ('shared', 'admin_state_up', 'enable_dhcp') )
1632 delete_nulls(content
)
1633 change_keys_http2db(content
, http2db_network
, reverse
=True)
1634 data
={'networks' : content
}
1635 return format_out(data
)
1637 @bottle.route(url_base
+ '/networks/<network_id>', method
='GET')
1638 def http_get_network_id(network_id
):
1639 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
1641 where_
= bottle
.request
.query
1642 where_
['uuid'] = network_id
1643 result
, content
= my
.db
.get_table(FROM
='nets', WHERE
=where_
, LIMIT
=100)
1646 print "http_get_networks_id error %d %s" % (result
, content
)
1647 bottle
.abort(-result
, content
)
1649 print "http_get_networks_id network '%s' not found" % network_id
1650 bottle
.abort(HTTP_Not_Found
, 'network %s not found' % network_id
)
1652 convert_boolean(content
, ('shared', 'admin_state_up', 'enale_dhcp') )
1653 change_keys_http2db(content
, http2db_network
, reverse
=True)
1655 result
, ports
= my
.db
.get_table(FROM
='ports', SELECT
=('uuid as port_id',),
1656 WHERE
={'net_id': network_id
}, LIMIT
=100)
1658 content
[0]['ports'] = ports
1659 delete_nulls(content
[0])
1660 data
={'network' : content
[0]}
1661 return format_out(data
)
1663 @bottle.route(url_base
+ '/networks', method
='POST')
1664 def http_post_networks():
1665 '''insert a network into the database.'''
1666 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
1668 http_content
= format_in( network_new_schema
)
1669 r
= remove_extra_items(http_content
, network_new_schema
)
1670 if r
is not None: print "http_post_networks: Warning: remove extra items ", r
1671 change_keys_http2db(http_content
['network'], http2db_network
)
1672 network
=http_content
['network']
1673 #check valid tenant_id
1674 tenant_id
= network
.get('tenant_id')
1676 result
, _
= my
.db
.get_table(FROM
='tenants', SELECT
=('uuid',), WHERE
={'uuid': tenant_id
,"enabled":True})
1678 bottle
.abort(HTTP_Not_Found
, 'tenant %s not found or not enabled' % tenant_id
)
1682 net_provider
= network
.get('provider')
1683 net_type
= network
.get('type')
1684 net_vlan
= network
.get("vlan")
1685 net_bind_net
= network
.get("bind_net")
1686 net_bind_type
= network
.get("bind_type")
1687 name
= network
["name"]
1689 #check if network name ends with :<vlan_tag> and network exist in order to make and automated bindning
1690 vlan_index
=name
.rfind(":")
1691 if net_bind_net
==None and net_bind_type
==None and vlan_index
> 1:
1693 vlan_tag
= int(name
[vlan_index
+1:])
1694 if vlan_tag
>0 and vlan_tag
< 4096:
1695 net_bind_net
= name
[:vlan_index
]
1696 net_bind_type
= "vlan:" + name
[vlan_index
+1:]
1700 if net_bind_net
!= None:
1701 #look for a valid net
1702 if check_valid_uuid(net_bind_net
):
1703 net_bind_key
= "uuid"
1705 net_bind_key
= "name"
1706 result
, content
= my
.db
.get_table(FROM
='nets', WHERE
={net_bind_key
: net_bind_net
} )
1708 bottle
.abort(HTTP_Internal_Server_Error
, 'getting nets from db ' + content
)
1711 bottle
.abort(HTTP_Bad_Request
, "bind_net %s '%s'not found" % (net_bind_key
, net_bind_net
) )
1714 bottle
.abort(HTTP_Bad_Request
, "more than one bind_net %s '%s' found, use uuid" % (net_bind_key
, net_bind_net
) )
1716 network
["bind_net"] = content
[0]["uuid"]
1717 if net_bind_type
!= None:
1718 if net_bind_type
[0:5] != "vlan:":
1719 bottle
.abort(HTTP_Bad_Request
, "bad format for 'bind_type', must be 'vlan:<tag>'")
1721 if int(net_bind_type
[5:]) > 4095 or int(net_bind_type
[5:])<=0 :
1722 bottle
.abort(HTTP_Bad_Request
, "bad format for 'bind_type', must be 'vlan:<tag>' with a tag between 1 and 4095")
1724 network
["bind_type"] = net_bind_type
1726 if net_provider
!=None:
1727 if net_provider
[:9]=="openflow:":
1729 if net_type
!="ptp" and net_type
!="data":
1730 bottle
.abort(HTTP_Bad_Request
, "Only 'ptp' or 'data' net types can be bound to 'openflow'")
1735 if net_type
!="bridge_man" and net_type
!="bridge_data":
1736 bottle
.abort(HTTP_Bad_Request
, "Only 'bridge_man' or 'bridge_data' net types can be bound to 'bridge', 'macvtap' or 'default")
1738 net_type
='bridge_man'
1741 net_type
='bridge_man'
1743 if net_provider
!= None:
1744 if net_provider
[:7]=='bridge:':
1745 #check it is one of the pre-provisioned bridges
1746 bridge_net_name
= net_provider
[7:]
1747 for brnet
in config_dic
['bridge_nets']:
1748 if brnet
[0]==bridge_net_name
: # free
1749 if brnet
[3] != None:
1750 bottle
.abort(HTTP_Conflict
, "invalid 'provider:physical', bridge '%s' is already used" % bridge_net_name
)
1755 # if bridge_net==None:
1756 # 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)
1758 elif net_type
=='bridge_data' or net_type
=='bridge_man':
1759 #look for a free precreated nets
1760 for brnet
in config_dic
['bridge_nets']:
1761 if brnet
[3]==None: # free
1762 if bridge_net
!= None:
1763 if net_type
=='bridge_man': #look for the smaller speed
1764 if brnet
[2] < bridge_net
[2]: bridge_net
= brnet
1765 else: #look for the larger speed
1766 if brnet
[2] > bridge_net
[2]: bridge_net
= brnet
1770 if bridge_net
==None:
1771 bottle
.abort(HTTP_Bad_Request
, "Max limits of bridge networks reached. Future versions of VIM will overcome this limit")
1774 print "using net", bridge_net
1775 net_provider
= "bridge:"+bridge_net
[0]
1776 net_vlan
= bridge_net
[1]
1777 if net_vlan
==None and (net_type
=="data" or net_type
=="ptp"):
1778 net_vlan
= my
.db
.get_free_net_vlan()
1780 bottle
.abort(HTTP_Internal_Server_Error
, "Error getting an available vlan")
1783 network
['provider'] = net_provider
1784 network
['type'] = net_type
1785 network
['vlan'] = net_vlan
1786 result
, content
= my
.db
.new_row('nets', network
, True, True)
1789 if bridge_net
!=None:
1790 bridge_net
[3] = content
1792 if config_dic
.get("dhcp_server"):
1793 if network
["name"] in config_dic
["dhcp_server"].get("nets", () ):
1794 config_dic
["dhcp_nets"].append(content
)
1795 print "dhcp_server: add new net", content
1796 elif bridge_net
!= None and bridge_net
[0] in config_dic
["dhcp_server"].get("bridge_ifaces", () ):
1797 config_dic
["dhcp_nets"].append(content
)
1798 print "dhcp_server: add new net", content
1799 return http_get_network_id(content
)
1801 print "http_post_networks error %d %s" % (result
, content
)
1802 bottle
.abort(-result
, content
)
1806 @bottle.route(url_base
+ '/networks/<network_id>', method
='PUT')
1807 def http_put_network_id(network_id
):
1808 '''update a network_id into the database.'''
1809 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
1811 http_content
= format_in( network_update_schema
)
1812 r
= remove_extra_items(http_content
, network_update_schema
)
1813 change_keys_http2db(http_content
['network'], http2db_network
)
1814 network
=http_content
['network']
1816 #Look for the previous data
1817 where_
= {'uuid': network_id
}
1818 result
, network_old
= my
.db
.get_table(FROM
='nets', WHERE
=where_
)
1820 print "http_put_network_id error %d %s" % (result
, network_old
)
1821 bottle
.abort(-result
, network_old
)
1824 print "http_put_network_id network '%s' not found" % network_id
1825 bottle
.abort(HTTP_Not_Found
, 'network %s not found' % network_id
)
1828 nbports
, content
= my
.db
.get_table(FROM
='ports', SELECT
=('uuid as port_id',),
1829 WHERE
={'net_id': network_id
}, LIMIT
=100)
1831 print "http_put_network_id error %d %s" % (result
, network_old
)
1832 bottle
.abort(-result
, content
)
1835 if 'type' in network
and network
['type'] != network_old
[0]['type']:
1836 bottle
.abort(HTTP_Method_Not_Allowed
, "Can not change type of network while having ports attached")
1837 if 'vlan' in network
and network
['vlan'] != network_old
[0]['vlan']:
1838 bottle
.abort(HTTP_Method_Not_Allowed
, "Can not change vlan of network while having ports attached")
1841 net_provider
= network
.get('provider', network_old
[0]['provider'])
1842 net_type
= network
.get('type', network_old
[0]['type'])
1843 net_bind_net
= network
.get("bind_net")
1844 net_bind_type
= network
.get("bind_type")
1845 if net_bind_net
!= None:
1846 #look for a valid net
1847 if check_valid_uuid(net_bind_net
):
1848 net_bind_key
= "uuid"
1850 net_bind_key
= "name"
1851 result
, content
= my
.db
.get_table(FROM
='nets', WHERE
={net_bind_key
: net_bind_net
} )
1853 bottle
.abort(HTTP_Internal_Server_Error
, 'getting nets from db ' + content
)
1856 bottle
.abort(HTTP_Bad_Request
, "bind_net %s '%s'not found" % (net_bind_key
, net_bind_net
) )
1859 bottle
.abort(HTTP_Bad_Request
, "more than one bind_net %s '%s' found, use uuid" % (net_bind_key
, net_bind_net
) )
1861 network
["bind_net"] = content
[0]["uuid"]
1862 if net_bind_type
!= None:
1863 if net_bind_type
[0:5] != "vlan:":
1864 bottle
.abort(HTTP_Bad_Request
, "bad format for 'bind_type', must be 'vlan:<tag>'")
1866 if int(net_bind_type
[5:]) > 4095 or int(net_bind_type
[5:])<=0 :
1867 bottle
.abort(HTTP_Bad_Request
, "bad format for 'bind_type', must be 'vlan:<tag>' with a tag between 1 and 4095")
1869 if net_provider
!=None:
1870 if net_provider
[:9]=="openflow:":
1871 if net_type
!="ptp" and net_type
!="data":
1872 bottle
.abort(HTTP_Bad_Request
, "Only 'ptp' or 'data' net types can be bound to 'openflow'")
1874 if net_type
!="bridge_man" and net_type
!="bridge_data":
1875 bottle
.abort(HTTP_Bad_Request
, "Only 'bridge_man' or 'bridge_data' net types can be bound to 'bridge', 'macvtap' or 'default")
1877 #insert in data base
1878 result
, content
= my
.db
.update_rows('nets', network
, WHERE
={'uuid': network_id
}, log
=True )
1880 if result
>0: # and nbports>0 and 'admin_state_up' in network and network['admin_state_up'] != network_old[0]['admin_state_up']:
1881 r
,c
= config_dic
['of_thread'].insert_task("update-net", network_id
)
1883 print "http_put_network_id error while launching openflow rules"
1884 bottle
.abort(HTTP_Internal_Server_Error
, c
)
1885 if config_dic
.get("dhcp_server"):
1886 if network_id
in config_dic
["dhcp_nets"]:
1887 config_dic
["dhcp_nets"].remove(network_id
)
1888 print "dhcp_server: delete net", network_id
1889 if network
.get("name", network_old
["name"]) in config_dic
["dhcp_server"].get("nets", () ):
1890 config_dic
["dhcp_nets"].append(network_id
)
1891 print "dhcp_server: add new net", network_id
1893 net_bind
= network
.get("bind", network_old
["bind"] )
1894 if net_bind
and net_bind
[:7]=="bridge:" and net_bind
[7:] in config_dic
["dhcp_server"].get("bridge_ifaces", () ):
1895 config_dic
["dhcp_nets"].append(network_id
)
1896 print "dhcp_server: add new net", network_id
1897 return http_get_network_id(network_id
)
1899 bottle
.abort(-result
, content
)
1903 @bottle.route(url_base
+ '/networks/<network_id>', method
='DELETE')
1904 def http_delete_network_id(network_id
):
1905 '''delete a network_id from the database.'''
1906 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
1908 #delete from the data base
1909 result
, content
= my
.db
.delete_row('nets', network_id
)
1912 bottle
.abort(HTTP_Not_Found
, content
)
1914 for brnet
in config_dic
['bridge_nets']:
1915 if brnet
[3]==network_id
:
1918 if config_dic
.get("dhcp_server") and network_id
in config_dic
["dhcp_nets"]:
1919 config_dic
["dhcp_nets"].remove(network_id
)
1920 print "dhcp_server: delete net", network_id
1921 data
={'result' : content
}
1922 return format_out(data
)
1924 print "http_delete_network_id error",result
, content
1925 bottle
.abort(-result
, content
)
1930 @bottle.route(url_base
+ '/networks/<network_id>/openflow', method
='GET')
1931 def http_get_openflow_id(network_id
):
1932 '''To obtain the list of openflow rules of a network
1934 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
1936 if network_id
=='all':
1939 where_
={"net_id": network_id
}
1940 result
, content
= my
.db
.get_table(SELECT
=("name","net_id","priority","vlan_id","ingress_port","src_mac","dst_mac","actions"),
1941 WHERE
=where_
, FROM
='of_flows')
1943 bottle
.abort(-result
, content
)
1945 data
={'openflow-rules' : content
}
1946 return format_out(data
)
1948 @bottle.route(url_base
+ '/networks/<network_id>/openflow', method
='PUT')
1949 def http_put_openflow_id(network_id
):
1950 '''To make actions over the net. The action is to reinstall the openflow rules
1951 network_id can be 'all'
1953 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
1955 bottle
.abort(HTTP_Unauthorized
, "Needed admin privileges")
1958 if network_id
=='all':
1961 where_
={"uuid": network_id
}
1962 result
, content
= my
.db
.get_table(SELECT
=("uuid","type"), WHERE
=where_
, FROM
='nets')
1964 bottle
.abort(-result
, content
)
1968 if net
["type"]!="ptp" and net
["type"]!="data":
1971 r
,c
= config_dic
['of_thread'].insert_task("update-net", net
['uuid'])
1973 print "http_put_openflow_id error while launching openflow rules"
1974 bottle
.abort(HTTP_Internal_Server_Error
, c
)
1975 data
={'result' : str(result
)+" nets updates"}
1976 return format_out(data
)
1978 @bottle.route(url_base
+ '/networks/openflow/clear', method
='DELETE')
1979 @bottle.route(url_base
+ '/networks/clear/openflow', method
='DELETE')
1980 def http_clear_openflow_rules():
1981 '''To make actions over the net. The action is to delete ALL openflow rules
1983 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
1985 bottle
.abort(HTTP_Unauthorized
, "Needed admin privileges")
1988 r
,c
= config_dic
['of_thread'].insert_task("clear-all")
1990 print "http_delete_openflow_id error while launching openflow rules"
1991 bottle
.abort(HTTP_Internal_Server_Error
, c
)
1994 data
={'result' : " Clearing openflow rules in process"}
1995 return format_out(data
)
1997 @bottle.route(url_base
+ '/networks/openflow/ports', method
='GET')
1998 def http_get_openflow_ports():
1999 '''Obtain switch ports names of openflow controller
2001 data
={'ports' : config_dic
['of_thread'].OF_connector
.pp2ofi
}
2002 return format_out(data
)
2009 @bottle.route(url_base
+ '/ports', method
='GET')
2010 def http_get_ports():
2012 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
2013 select_
,where_
,limit_
= filter_query_string(bottle
.request
.query
, http2db_port
,
2014 ('id','name','tenant_id','network_id','vpci','mac_address','device_owner','device_id',
2015 'binding:switch_port','binding:vlan','bandwidth','status','admin_state_up','ip_address') )
2016 #result, content = my.db.get_ports(where_)
2017 result
, content
= my
.db
.get_table(SELECT
=select_
, WHERE
=where_
, FROM
='ports',LIMIT
=limit_
)
2019 print "http_get_ports Error", result
, content
2020 bottle
.abort(-result
, content
)
2023 convert_boolean(content
, ('admin_state_up',) )
2024 delete_nulls(content
)
2025 change_keys_http2db(content
, http2db_port
, reverse
=True)
2026 data
={'ports' : content
}
2027 return format_out(data
)
2029 @bottle.route(url_base
+ '/ports/<port_id>', method
='GET')
2030 def http_get_port_id(port_id
):
2031 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
2033 result
, content
= my
.db
.get_table(WHERE
={'uuid': port_id
}, FROM
='ports')
2035 print "http_get_ports error", result
, content
2036 bottle
.abort(-result
, content
)
2038 print "http_get_ports port '%s' not found" % str(port_id
)
2039 bottle
.abort(HTTP_Not_Found
, 'port %s not found' % port_id
)
2041 convert_boolean(content
, ('admin_state_up',) )
2042 delete_nulls(content
)
2043 change_keys_http2db(content
, http2db_port
, reverse
=True)
2044 data
={'port' : content
[0]}
2045 return format_out(data
)
2048 @bottle.route(url_base
+ '/ports', method
='POST')
2049 def http_post_ports():
2050 '''insert an external port into the database.'''
2051 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
2053 bottle
.abort(HTTP_Unauthorized
, "Needed admin privileges")
2055 http_content
= format_in( port_new_schema
)
2056 r
= remove_extra_items(http_content
, port_new_schema
)
2057 if r
is not None: print "http_post_ports: Warning: remove extra items ", r
2058 change_keys_http2db(http_content
['port'], http2db_port
)
2059 port
=http_content
['port']
2061 port
['type'] = 'external'
2062 if 'net_id' in port
and port
['net_id'] == None:
2065 if 'net_id' in port
:
2066 #check that new net has the correct type
2067 result
, new_net
= my
.db
.check_target_net(port
['net_id'], None, 'external' )
2069 bottle
.abort(HTTP_Bad_Request
, new_net
)
2071 #insert in data base
2072 result
, uuid
= my
.db
.new_row('ports', port
, True, True)
2074 if 'net_id' in port
:
2075 r
,c
= config_dic
['of_thread'].insert_task("update-net", port
['net_id'])
2077 print "http_post_ports error while launching openflow rules"
2078 bottle
.abort(HTTP_Internal_Server_Error
, c
)
2079 return http_get_port_id(uuid
)
2081 bottle
.abort(-result
, uuid
)
2084 @bottle.route(url_base
+ '/ports/<port_id>', method
='PUT')
2085 def http_put_port_id(port_id
):
2086 '''update a port_id into the database.'''
2088 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
2090 http_content
= format_in( port_update_schema
)
2091 change_keys_http2db(http_content
['port'], http2db_port
)
2092 port_dict
=http_content
['port']
2094 #Look for the previous port data
2095 where_
= {'uuid': port_id
}
2096 result
, content
= my
.db
.get_table(FROM
="ports",WHERE
=where_
)
2098 print "http_put_port_id error", result
, content
2099 bottle
.abort(-result
, content
)
2102 print "http_put_port_id port '%s' not found" % port_id
2103 bottle
.abort(HTTP_Not_Found
, 'port %s not found' % port_id
)
2106 for k
in ('vlan','switch_port','mac_address', 'tenant_id'):
2107 if k
in port_dict
and not my
.admin
:
2108 bottle
.abort(HTTP_Unauthorized
, "Needed admin privileges for changing " + k
)
2112 #change_keys_http2db(port, http2db_port, reverse=True)
2116 if 'net_id' in port_dict
:
2118 old_net
= port
.get('net_id', None)
2119 new_net
= port_dict
['net_id']
2120 if old_net
!= new_net
:
2122 if new_net
is not None: nets
.append(new_net
) #put first the new net, so that new openflow rules are created before removing the old ones
2123 if old_net
is not None: nets
.append(old_net
)
2124 if port
['type'] == 'instance:bridge':
2125 bottle
.abort(HTTP_Forbidden
, "bridge interfaces cannot be attached to a different net")
2127 elif port
['type'] == 'external':
2129 bottle
.abort(HTTP_Unauthorized
, "Needed admin privileges")
2133 #check that new net has the correct type
2134 result
, new_net_dict
= my
.db
.check_target_net(new_net
, None, port
['type'] )
2136 #change VLAN for SR-IOV ports
2137 if result
>=0 and port
["type"]=="instance:data" and port
["model"]=="VF": #TODO consider also VFnotShared
2139 port_dict
["vlan"] = None
2141 port_dict
["vlan"] = new_net_dict
["vlan"]
2142 #get host where this VM is allocated
2143 result
, content
= my
.db
.get_table(FROM
="instances",WHERE
={"uuid":port
["instance_id"]})
2145 print "http_put_port_id database error", content
2147 host_id
= content
[0]["host_id"]
2149 #insert in data base
2151 result
, content
= my
.db
.update_rows('ports', port_dict
, WHERE
={'uuid': port_id
}, log
=False )
2153 #Insert task to complete actions
2156 r
,v
= config_dic
['of_thread'].insert_task("update-net", net_id
)
2157 if r
<0: print "Error ********* http_put_port_id update_of_flows: ", v
2158 #TODO Do something if fails
2160 config_dic
['host_threads'][host_id
].insert_task("edit-iface", port_id
, old_net
, new_net
)
2163 return http_get_port_id(port_id
)
2165 bottle
.abort(HTTP_Bad_Request
, content
)
2169 @bottle.route(url_base
+ '/ports/<port_id>', method
='DELETE')
2170 def http_delete_port_id(port_id
):
2171 '''delete a port_id from the database.'''
2172 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
2174 bottle
.abort(HTTP_Unauthorized
, "Needed admin privileges")
2177 #Look for the previous port data
2178 where_
= {'uuid': port_id
, "type": "external"}
2179 result
, ports
= my
.db
.get_table(WHERE
=where_
, FROM
='ports',LIMIT
=100)
2182 print "http_delete_port_id port '%s' not found" % port_id
2183 bottle
.abort(HTTP_Not_Found
, 'port %s not found or device_owner is not external' % port_id
)
2185 #delete from the data base
2186 result
, content
= my
.db
.delete_row('ports', port_id
)
2189 bottle
.abort(HTTP_Not_Found
, content
)
2191 network
= ports
[0].get('net_id', None)
2192 if network
is not None:
2194 r
,c
= config_dic
['of_thread'].insert_task("update-net", network
)
2195 if r
<0: print "!!!!!! http_delete_port_id update_of_flows error", r
, c
2196 data
={'result' : content
}
2197 return format_out(data
)
2199 print "http_delete_port_id error",result
, content
2200 bottle
.abort(-result
, content
)