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$"
38 from jsonschema
import validate
as js_v
, exceptions
as js_e
39 import host_thread
as ht
40 from vim_schema
import host_new_schema
, host_edit_schema
, tenant_new_schema
, \
42 flavor_new_schema
, flavor_update_schema
, \
43 image_new_schema
, image_update_schema
, \
44 server_new_schema
, server_action_schema
, network_new_schema
, network_update_schema
, \
45 port_new_schema
, port_update_schema
53 HTTP_Bad_Request
= 400
54 HTTP_Unauthorized
= 401
57 HTTP_Method_Not_Allowed
= 405
58 HTTP_Not_Acceptable
= 406
59 HTTP_Request_Timeout
= 408
61 HTTP_Service_Unavailable
= 503
62 HTTP_Internal_Server_Error
= 500
65 def check_extended(extended
, allow_net_attach
=False):
66 '''Makes and extra checking of extended input that cannot be done using jsonschema
68 allow_net_attach: for allowing or not the uuid field at interfaces
69 that are allowed for instance, but not for flavors
70 Return: (<0, error_text) if error; (0,None) if not error '''
71 if "numas" not in extended
: return 0, None
74 for numa
in extended
["numas"]:
78 if "cores-id" in numa
:
79 if len(numa
["cores-id"]) != numa
["cores"]:
80 return -HTTP_Bad_Request
, "different number of cores-id (%d) than cores (%d) at numa %d" % (len(numa
["cores-id"]), numa
["cores"],numaid
)
81 id_s
.extend(numa
["cores-id"])
84 if "threads-id" in numa
:
85 if len(numa
["threads-id"]) != numa
["threads"]:
86 return -HTTP_Bad_Request
, "different number of threads-id (%d) than threads (%d) at numa %d" % (len(numa
["threads-id"]), numa
["threads"],numaid
)
87 id_s
.extend(numa
["threads-id"])
88 if "paired-threads" in numa
:
90 if "paired-threads-id" in numa
:
91 if len(numa
["paired-threads-id"]) != numa
["paired-threads"]:
92 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
)
93 for pair
in numa
["paired-threads-id"]:
95 return -HTTP_Bad_Request
, "paired-threads-id must contain a list of two elements list at numa %d" % (numaid
)
98 return -HTTP_Service_Unavailable
, "only one of cores, threads, paired-threads are allowed in this version at numa %d" % numaid
100 if "interfaces" in numa
:
104 for interface
in numa
["interfaces"]:
105 if "uuid" in interface
and not allow_net_attach
:
106 return -HTTP_Bad_Request
, "uuid field is not allowed at numa %d interface %s position %d" % (numaid
, interface
.get("name",""), ifaceid
)
107 if "mac_address" in interface
and interface
["dedicated"]=="yes":
108 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
)
109 if "name" in interface
:
110 if interface
["name"] in names
:
111 return -HTTP_Bad_Request
, "name repeated at numa %d, interface %s position %d" % (numaid
, interface
.get("name",""), ifaceid
)
112 names
.append(interface
["name"])
113 if "vpci" in interface
:
114 if interface
["vpci"] in vpcis
:
115 return -HTTP_Bad_Request
, "vpci %s repeated at numa %d, interface %s position %d" % (interface
["vpci"], numaid
, interface
.get("name",""), ifaceid
)
116 vpcis
.append(interface
["vpci"])
120 return -HTTP_Service_Unavailable
, "only one numa can be defined in this version "
121 for a
in range(0,len(id_s
)):
123 return -HTTP_Bad_Request
, "core/thread identifiers must start at 0 and gaps are not alloed. Missing id number %d" % a
128 # dictionaries that change from HTTP API to database naming
130 http2db_host
={'id':'uuid'}
131 http2db_tenant
={'id':'uuid'}
132 http2db_flavor
={'id':'uuid','imageRef':'image_id'}
133 http2db_image
={'id':'uuid', 'created':'created_at', 'updated':'modified_at', 'public': 'public'}
134 http2db_server
={'id':'uuid','hostId':'host_id','flavorRef':'flavor_id','imageRef':'image_id','created':'created_at'}
135 http2db_network
={'id':'uuid','provider:vlan':'vlan', 'provider:physical': 'provider'}
136 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'}
138 def remove_extra_items(data
, schema
):
140 if type(data
) is tuple or type(data
) is list:
142 a
= remove_extra_items(d
, schema
['items'])
143 if a
is not None: deleted
.append(a
)
144 elif type(data
) is dict:
145 for k
in data
.keys():
146 if 'properties' not in schema
or k
not in schema
['properties'].keys():
150 a
= remove_extra_items(data
[k
], schema
['properties'][k
])
151 if a
is not None: deleted
.append({k
:a
})
152 if len(deleted
) == 0: return None
153 elif len(deleted
) == 1: return deleted
[0]
156 def delete_nulls(var
):
157 if type(var
) is dict:
159 if var
[k
] is None: del var
[k
]
160 elif type(var
[k
]) is dict or type(var
[k
]) is list or type(var
[k
]) is tuple:
161 if delete_nulls(var
[k
]): del var
[k
]
162 if len(var
) == 0: return True
163 elif type(var
) is list or type(var
) is tuple:
165 if type(k
) is dict: delete_nulls(k
)
166 if len(var
) == 0: return True
170 class httpserver(threading
.Thread
):
171 def __init__(self
, db_conn
, name
="http", host
='localhost', port
=8080, admin
=False, config_
=None):
173 Creates a new thread to attend the http connections
175 db_conn: database connection
176 name: name of this thread
177 host: ip or name where to listen
178 port: port where to listen
179 admin: if this has privileges of administrator or not
180 config_: unless the first thread must be provided. It is a global dictionary where to allocate the self variable
186 if config_
is not None:
188 if 'http_threads' not in config_dic
:
189 config_dic
['http_threads'] = {}
190 threading
.Thread
.__init
__(self
)
195 if name
in config_dic
:
196 print "httpserver Warning!!! Onether thread with the same name", name
198 while name
+str(n
) in config_dic
:
202 self
.url_preffix
= 'http://' + self
.host
+ ':' + str(self
.port
) + url_base
203 config_dic
['http_threads'][name
] = self
205 #Ensure that when the main program exits the thread will also exit
210 bottle
.run(host
=self
.host
, port
=self
.port
, debug
=True) #quiet=True
212 def gethost(self
, host_id
):
213 result
, content
= self
.db
.get_host(host_id
)
215 print "httpserver.gethost error %d %s" % (result
, content
)
216 bottle
.abort(-result
, content
)
218 print "httpserver.gethost host '%s' not found" % host_id
219 bottle
.abort(HTTP_Not_Found
, content
)
221 data
={'host' : content
}
222 convert_boolean(content
, ('admin_state_up',) )
223 change_keys_http2db(content
, http2db_host
, reverse
=True)
225 return format_out(data
)
227 @bottle.route(url_base
+ '/', method
='GET')
230 return 'works' #TODO: put links or redirection to /openvim???
236 def change_keys_http2db(data
, http_db
, reverse
=False):
237 '''Change keys of dictionary data according to the key_dict values
238 This allow change from http interface names to database names.
239 When reverse is True, the change is otherwise
241 data: can be a dictionary or a list
242 http_db: is a dictionary with hhtp names as keys and database names as value
243 reverse: by default change is done from http API to database. If True change is done otherwise
244 Return: None, but data is modified'''
245 if type(data
) is tuple or type(data
) is list:
247 change_keys_http2db(d
, http_db
, reverse
)
248 elif type(data
) is dict or type(data
) is bottle
.FormsDict
:
250 for k
,v
in http_db
.items():
251 if v
in data
: data
[k
]=data
.pop(v
)
253 for k
,v
in http_db
.items():
254 if k
in data
: data
[v
]=data
.pop(k
)
258 def format_out(data
):
259 '''return string of dictionary data according to requested json, yaml, xml. By default json'''
260 if 'application/yaml' in bottle
.request
.headers
.get('Accept'):
261 bottle
.response
.content_type
='application/yaml'
262 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='"'
263 else: #by default json
264 bottle
.response
.content_type
='application/json'
265 #return data #json no style
266 return json
.dumps(data
, indent
=4) + "\n"
268 def format_in(schema
):
270 error_text
= "Invalid header format "
271 format_type
= bottle
.request
.headers
.get('Content-Type', 'application/json')
272 if 'application/json' in format_type
:
273 error_text
= "Invalid json format "
274 #Use the json decoder instead of bottle decoder because it informs about the location of error formats with a ValueError exception
275 client_data
= json
.load(bottle
.request
.body
)
276 #client_data = bottle.request.json()
277 elif 'application/yaml' in format_type
:
278 error_text
= "Invalid yaml format "
279 client_data
= yaml
.load(bottle
.request
.body
)
280 elif format_type
== 'application/xml':
281 bottle
.abort(501, "Content-Type: application/xml not supported yet.")
283 print "HTTP HEADERS: " + str(bottle
.request
.headers
.items())
284 bottle
.abort(HTTP_Not_Acceptable
, 'Content-Type ' + str(format_type
) + ' not supported.')
286 #if client_data == None:
287 # bottle.abort(HTTP_Bad_Request, "Content error, empty")
291 #print "HTTP input data: ", str(client_data)
292 error_text
= "Invalid content "
293 js_v(client_data
, schema
)
296 except (ValueError, yaml
.YAMLError
) as exc
:
297 error_text
+= str(exc
)
299 bottle
.abort(HTTP_Bad_Request
, error_text
)
300 except js_e
.ValidationError
as exc
:
301 print "HTTP validate_in error, jsonschema exception ", exc
.message
, "at", exc
.path
302 print " CONTENT: " + str(bottle
.request
.body
.readlines())
304 if len(exc
.path
)>0: error_pos
=" at '" + ":".join(map(str, exc
.path
)) + "'"
305 bottle
.abort(HTTP_Bad_Request
, error_text
+ error_pos
+": "+exc
.message
)
307 # bottle.abort(HTTP_Bad_Request, "Content error: Failed to parse Content-Type", error_pos)
310 def filter_query_string(qs
, http2db
, allowed
):
311 '''Process query string (qs) checking that contains only valid tokens for avoiding SQL injection
313 'qs': bottle.FormsDict variable to be processed. None or empty is considered valid
314 'allowed': list of allowed string tokens (API http naming). All the keys of 'qs' must be one of 'allowed'
315 'http2db': dictionary with change from http API naming (dictionary key) to database naming(dictionary value)
316 Return: A tuple with the (select,where,limit) to be use in a database query. All of then transformed to the database naming
317 select: list of items to retrieve, filtered by query string 'field=token'. If no 'field' is present, allowed list is returned
318 where: dictionary with key, value, taken from the query string token=value. Empty if nothing is provided
319 limit: limit dictated by user with the query string 'limit'. 100 by default
320 abort if not permitted, using bottel.abort
325 if type(qs
) is not bottle
.FormsDict
:
326 print '!!!!!!!!!!!!!!invalid query string not a dictionary'
327 #bottle.abort(HTTP_Internal_Server_Error, "call programmer")
331 select
+= qs
.getall(k
)
334 bottle
.abort(HTTP_Bad_Request
, "Invalid query string at 'field="+v
+"'")
339 bottle
.abort(HTTP_Bad_Request
, "Invalid query string at 'limit="+qs
[k
]+"'")
342 bottle
.abort(HTTP_Bad_Request
, "Invalid query string at '"+k
+"="+qs
[k
]+"'")
343 if qs
[k
]!="null": where
[k
]=qs
[k
]
345 if len(select
)==0: select
+= allowed
346 #change from http api to database naming
347 for i
in range(0,len(select
)):
350 select
[i
] = http2db
[k
]
351 change_keys_http2db(where
, http2db
)
352 #print "filter_query_string", select,where,limit
354 return select
,where
,limit
357 def convert_bandwidth(data
, reverse
=False):
358 '''Check the field bandwidth recursively and when found, it removes units and convert to number
359 It assumes that bandwidth is well formed
361 'data': dictionary bottle.FormsDict variable to be checked. None or empty is considered valid
362 'reverse': by default convert form str to int (Mbps), if True it convert from number to units
366 if type(data
) is dict:
367 for k
in data
.keys():
368 if type(data
[k
]) is dict or type(data
[k
]) is tuple or type(data
[k
]) is list:
369 convert_bandwidth(data
[k
], reverse
)
370 if "bandwidth" in data
:
372 value
=str(data
["bandwidth"])
374 pos
= value
.find("bps")
376 if value
[pos
-1]=="G": data
["bandwidth"] = int(data
["bandwidth"][:pos
-1]) * 1000
377 elif value
[pos
-1]=="k": data
["bandwidth"]= int(data
["bandwidth"][:pos
-1]) / 1000
378 else: data
["bandwidth"]= int(data
["bandwidth"][:pos
-1])
380 value
= int(data
["bandwidth"])
381 if value
% 1000 == 0: data
["bandwidth"]=str(value
/1000) + " Gbps"
382 else: data
["bandwidth"]=str(value
) + " Mbps"
384 print "convert_bandwidth exception for type", type(data
["bandwidth"]), " data", data
["bandwidth"]
386 if type(data
) is tuple or type(data
) is list:
388 if type(k
) is dict or type(k
) is tuple or type(k
) is list:
389 convert_bandwidth(k
, reverse
)
391 def convert_boolean(data
, items
):
392 '''Check recursively the content of data, and if there is an key contained in items, convert value from string to boolean
393 It assumes that bandwidth is well formed
395 'data': dictionary bottle.FormsDict variable to be checked. None or empty is consideted valid
396 'items': tuple of keys to convert
400 if type(data
) is dict:
401 for k
in data
.keys():
402 if type(data
[k
]) is dict or type(data
[k
]) is tuple or type(data
[k
]) is list:
403 convert_boolean(data
[k
], items
)
405 if type(data
[k
]) is str:
406 if data
[k
]=="false": data
[k
]=False
407 elif data
[k
]=="true": data
[k
]=True
408 if type(data
) is tuple or type(data
) is list:
410 if type(k
) is dict or type(k
) is tuple or type(k
) is list:
411 convert_boolean(k
, items
)
413 def convert_datetime2str(var
):
414 '''Converts a datetime variable to a string with the format '%Y-%m-%dT%H:%i:%s'
415 It enters recursively in the dict var finding this kind of variables
417 if type(var
) is dict:
418 for k
,v
in var
.items():
419 if type(v
) is datetime
.datetime
:
420 var
[k
]= v
.strftime('%Y-%m-%dT%H:%M:%S')
421 elif type(v
) is dict or type(v
) is list or type(v
) is tuple:
422 convert_datetime2str(v
)
423 if len(var
) == 0: return True
424 elif type(var
) is list or type(var
) is tuple:
426 convert_datetime2str(v
)
428 def check_valid_tenant(my
, tenant_id
):
431 return HTTP_Unauthorized
, "Needed admin privileges"
433 result
, _
= my
.db
.get_table(FROM
='tenants', SELECT
=('uuid',), WHERE
={'uuid': tenant_id
})
435 return HTTP_Not_Found
, "tenant '%s' not found" % tenant_id
438 def check_valid_uuid(uuid
):
439 id_schema
= {"type" : "string", "pattern": "^[a-fA-F0-9]{8}(-[a-fA-F0-9]{4}){3}-[a-fA-F0-9]{12}$"}
441 js_v(uuid
, id_schema
)
443 except js_e
.ValidationError
:
457 e
={"error":{"code":error
.status_code
, "type":error
.status
, "description":error
.body
}}
460 @bottle.hook('after_request')
462 #TODO: Alf: Is it needed??
463 bottle
.response
.headers
['Access-Control-Allow-Origin'] = '*'
469 @bottle.route(url_base
+ '/hosts', method
='GET')
470 def http_get_hosts():
471 select_
,where_
,limit_
= filter_query_string(bottle
.request
.query
, http2db_host
,
472 ('id','name','description','status','admin_state_up') )
474 myself
= config_dic
['http_threads'][ threading
.current_thread().name
]
475 result
, content
= myself
.db
.get_table(FROM
='hosts', SELECT
=select_
, WHERE
=where_
, LIMIT
=limit_
)
477 print "http_get_hosts Error", content
478 bottle
.abort(-result
, content
)
480 convert_boolean(content
, ('admin_state_up',) )
481 change_keys_http2db(content
, http2db_host
, reverse
=True)
483 row
['links'] = ( {'href': myself
.url_preffix
+ '/hosts/' + str(row
['id']), 'rel': 'bookmark'}, )
484 data
={'hosts' : content
}
485 return format_out(data
)
487 @bottle.route(url_base
+ '/hosts/<host_id>', method
='GET')
488 def http_get_host_id(host_id
):
489 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
490 return my
.gethost(host_id
)
492 @bottle.route(url_base
+ '/hosts', method
='POST')
493 def http_post_hosts():
494 '''insert a host into the database. All resources are got and inserted'''
495 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
498 bottle
.abort(HTTP_Unauthorized
, "Needed admin privileges")
501 http_content
= format_in( host_new_schema
)
502 r
= remove_extra_items(http_content
, host_new_schema
)
503 if r
is not None: print "http_post_host_id: Warning: remove extra items ", r
504 change_keys_http2db(http_content
['host'], http2db_host
)
506 host
= http_content
['host']
508 if 'host-data' in http_content
:
509 host
.update(http_content
['host-data'])
510 ip_name
=http_content
['host-data']['ip_name']
511 user
=http_content
['host-data']['user']
512 password
=http_content
['host-data'].get('password', None)
514 ip_name
=host
['ip_name']
516 password
=host
.get('password', None)
519 rad
= RADclass
.RADclass()
520 (return_status
, code
) = rad
.obtain_RAD(user
, password
, ip_name
)
523 if not return_status
:
524 print 'http_post_hosts ERROR obtaining RAD', code
525 bottle
.abort(HTTP_Bad_Request
, code
)
528 rad_structure
= yaml
.load(rad
.to_text())
529 print 'rad_structure\n---------------------'
530 print json
.dumps(rad_structure
, indent
=4)
531 print '---------------------'
533 WHERE_
={"family":rad_structure
['processor']['family'], 'manufacturer':rad_structure
['processor']['manufacturer'], 'version':rad_structure
['processor']['version']}
534 result
, content
= my
.db
.get_table(FROM
='host_ranking',
538 host
['ranking'] = content
[0]['ranking']
540 #error_text= "Host " + str(WHERE_)+ " not found in ranking table. Not valid for VIM management"
541 #bottle.abort(HTTP_Bad_Request, error_text)
543 warning_text
+= "Host " + str(WHERE_
)+ " not found in ranking table. Assuming lowest value 100\n"
544 host
['ranking'] = 100 #TODO: as not used in this version, set the lowest value
546 features
= rad_structure
['processor'].get('features', ())
547 host
['features'] = ",".join(features
)
550 for node
in (rad_structure
['resource topology']['nodes'] or {}).itervalues():
555 for core
in node
['cpu']['eligible_cores']:
556 eligible_cores
.extend(core
)
557 for core
in node
['cpu']['cores']:
558 for thread_id
in core
:
559 c
={'core_id': count
, 'thread_id': thread_id
}
560 if thread_id
not in eligible_cores
: c
['status'] = 'noteligible'
565 for port_k
, port_v
in node
['nics']['nic 0']['ports'].iteritems():
566 if port_v
['virtual']:
570 for port_k2
, port_v2
in node
['nics']['nic 0']['ports'].iteritems():
571 if port_v2
['virtual'] and port_v2
['PF_pci_id']==port_k
:
572 sriovs
.append({'pci':port_k2
, 'mac':port_v2
['mac'], 'source_name':port_v2
['source_name']})
574 #sort sriov according to pci and rename them to the vf number
575 new_sriovs
= sorted(sriovs
, key
=lambda k
: k
['pci'])
577 for sriov
in new_sriovs
:
578 sriov
['source_name'] = index
580 interfaces
.append ({'pci':str(port_k
), 'Mbps': port_v
['speed']/1000000, 'sriovs': new_sriovs
, 'mac':port_v
['mac'], 'source_name':port_v
['source_name']})
581 #@TODO LA memoria devuelta por el RAD es incorrecta, almenos para IVY1, NFV100
582 memory
=node
['memory']['node_size'] / (1024*1024*1024)
583 #memory=get_next_2pow(node['memory']['hugepage_nr'])
584 host
['numas'].append( {'numa_socket': node
['id'], 'hugepages': node
['memory']['hugepage_nr'], 'memory':memory
, 'interfaces': interfaces
, 'cores': cores
} )
585 print json
.dumps(host
, indent
=4)
589 result
, content
= my
.db
.new_host(host
)
591 if content
['admin_state_up']:
593 host_test_mode
= True if config_dic
['mode']=='test' or config_dic
['mode']=="OF only" else False
594 host_develop_mode
= True if config_dic
['mode']=='development' else False
595 host_develop_bridge_iface
= config_dic
.get('development_bridge', None)
596 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'],
597 test
=host_test_mode
, image_path
=config_dic
['image_path'],
598 version
=config_dic
['version'], host_id
=content
['uuid'],
599 develop_mode
=host_develop_mode
, develop_bridge_iface
=host_develop_bridge_iface
)
601 config_dic
['host_threads'][ content
['uuid'] ] = thread
604 change_keys_http2db(content
, http2db_host
, reverse
=True)
605 if len(warning_text
)>0:
606 content
["warning"]= warning_text
607 data
={'host' : content
}
608 return format_out(data
)
610 bottle
.abort(HTTP_Bad_Request
, content
)
613 @bottle.route(url_base
+ '/hosts/<host_id>', method
='PUT')
614 def http_put_host_id(host_id
):
615 '''modify a host into the database. All resources are got and inserted'''
616 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
619 bottle
.abort(HTTP_Unauthorized
, "Needed admin privileges")
622 http_content
= format_in( host_edit_schema
)
623 r
= remove_extra_items(http_content
, host_edit_schema
)
624 if r
is not None: print "http_post_host_id: Warning: remove extra items ", r
625 change_keys_http2db(http_content
['host'], http2db_host
)
628 result
, content
= my
.db
.edit_host(host_id
, http_content
['host'])
630 convert_boolean(content
, ('admin_state_up',) )
631 change_keys_http2db(content
, http2db_host
, reverse
=True)
632 data
={'host' : content
}
635 config_dic
['host_threads'][host_id
].name
= content
.get('name',content
['ip_name'])
636 config_dic
['host_threads'][host_id
].user
= content
['user']
637 config_dic
['host_threads'][host_id
].host
= content
['ip_name']
638 config_dic
['host_threads'][host_id
].insert_task("reload")
641 return format_out(data
)
643 bottle
.abort(HTTP_Bad_Request
, content
)
648 @bottle.route(url_base
+ '/hosts/<host_id>', method
='DELETE')
649 def http_delete_host_id(host_id
):
650 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
653 bottle
.abort(HTTP_Unauthorized
, "Needed admin privileges")
654 result
, content
= my
.db
.delete_row('hosts', host_id
)
656 bottle
.abort(HTTP_Not_Found
, content
)
659 if host_id
in config_dic
['host_threads']:
660 config_dic
['host_threads'][host_id
].insert_task("exit")
662 data
={'result' : content
}
663 return format_out(data
)
665 print "http_delete_host_id error",result
, content
666 bottle
.abort(-result
, content
)
675 @bottle.route(url_base
+ '/tenants', method
='GET')
676 def http_get_tenants():
677 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
678 select_
,where_
,limit_
= filter_query_string(bottle
.request
.query
, http2db_tenant
,
679 ('id','name','description','enabled') )
680 result
, content
= my
.db
.get_table(FROM
='tenants', SELECT
=select_
,WHERE
=where_
,LIMIT
=limit_
)
682 print "http_get_tenants Error", content
683 bottle
.abort(-result
, content
)
685 change_keys_http2db(content
, http2db_tenant
, reverse
=True)
686 convert_boolean(content
, ('enabled',))
687 data
={'tenants' : content
}
688 #data['tenants_links'] = dict([('tenant', row['id']) for row in content])
689 return format_out(data
)
691 @bottle.route(url_base
+ '/tenants/<tenant_id>', method
='GET')
692 def http_get_tenant_id(tenant_id
):
693 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
694 result
, content
= my
.db
.get_table(FROM
='tenants', SELECT
=('uuid','name','description', 'enabled'),WHERE
={'uuid': tenant_id
} )
696 print "http_get_tenant_id error %d %s" % (result
, content
)
697 bottle
.abort(-result
, content
)
699 print "http_get_tenant_id tenant '%s' not found" % tenant_id
700 bottle
.abort(HTTP_Not_Found
, "tenant %s not found" % tenant_id
)
702 change_keys_http2db(content
, http2db_tenant
, reverse
=True)
703 convert_boolean(content
, ('enabled',))
704 data
={'tenant' : content
[0]}
705 #data['tenants_links'] = dict([('tenant', row['id']) for row in content])
706 return format_out(data
)
709 @bottle.route(url_base
+ '/tenants', method
='POST')
710 def http_post_tenants():
711 '''insert a tenant into the database.'''
712 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
714 http_content
= format_in( tenant_new_schema
)
715 r
= remove_extra_items(http_content
, tenant_new_schema
)
716 if r
is not None: print "http_post_tenants: Warning: remove extra items ", r
717 change_keys_http2db(http_content
['tenant'], http2db_tenant
)
720 result
, content
= my
.db
.new_tenant(http_content
['tenant'])
723 return http_get_tenant_id(content
)
725 bottle
.abort(-result
, content
)
728 @bottle.route(url_base
+ '/tenants/<tenant_id>', method
='PUT')
729 def http_put_tenant_id(tenant_id
):
730 '''update a tenant into the database.'''
731 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
733 http_content
= format_in( tenant_edit_schema
)
734 r
= remove_extra_items(http_content
, tenant_edit_schema
)
735 if r
is not None: print "http_put_tenant_id: Warning: remove extra items ", r
736 change_keys_http2db(http_content
['tenant'], http2db_tenant
)
739 result
, content
= my
.db
.update_rows('tenants', http_content
['tenant'], WHERE
={'uuid': tenant_id
}, log
=True )
741 return http_get_tenant_id(tenant_id
)
743 bottle
.abort(-result
, content
)
746 @bottle.route(url_base
+ '/tenants/<tenant_id>', method
='DELETE')
747 def http_delete_tenant_id(tenant_id
):
748 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
750 r
, tenants_flavors
= my
.db
.get_table(FROM
='tenants_flavors', SELECT
=('flavor_id','tenant_id'), WHERE
={'tenant_id': tenant_id
})
753 r
, tenants_images
= my
.db
.get_table(FROM
='tenants_images', SELECT
=('image_id','tenant_id'), WHERE
={'tenant_id': tenant_id
})
756 result
, content
= my
.db
.delete_row('tenants', tenant_id
)
758 bottle
.abort(HTTP_Not_Found
, content
)
760 print "alf", tenants_flavors
, tenants_images
761 for flavor
in tenants_flavors
:
762 my
.db
.delete_row_by_key("flavors", "uuid", flavor
['flavor_id'])
763 for image
in tenants_images
:
764 my
.db
.delete_row_by_key("images", "uuid", image
['image_id'])
765 data
={'result' : content
}
766 return format_out(data
)
768 print "http_delete_tenant_id error",result
, content
769 bottle
.abort(-result
, content
)
776 @bottle.route(url_base
+ '/<tenant_id>/flavors', method
='GET')
777 def http_get_flavors(tenant_id
):
778 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
779 #check valid tenant_id
780 result
,content
= check_valid_tenant(my
, tenant_id
)
782 bottle
.abort(result
, content
)
784 select_
,where_
,limit_
= filter_query_string(bottle
.request
.query
, http2db_flavor
,
785 ('id','name','description','public') )
789 from_
='tenants_flavors inner join flavors on tenants_flavors.flavor_id=flavors.uuid'
790 where_
['tenant_id'] = tenant_id
791 result
, content
= my
.db
.get_table(FROM
=from_
, SELECT
=select_
, WHERE
=where_
, LIMIT
=limit_
)
793 print "http_get_flavors Error", content
794 bottle
.abort(-result
, content
)
796 change_keys_http2db(content
, http2db_flavor
, reverse
=True)
798 row
['links']=[ {'href': "/".join( (my
.url_preffix
, tenant_id
, 'flavors', str(row
['id']) ) ), 'rel':'bookmark' } ]
799 data
={'flavors' : content
}
800 return format_out(data
)
802 @bottle.route(url_base
+ '/<tenant_id>/flavors/<flavor_id>', method
='GET')
803 def http_get_flavor_id(tenant_id
, flavor_id
):
804 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
805 #check valid tenant_id
806 result
,content
= check_valid_tenant(my
, tenant_id
)
808 bottle
.abort(result
, content
)
810 select_
,where_
,limit_
= filter_query_string(bottle
.request
.query
, http2db_flavor
,
811 ('id','name','description','ram', 'vcpus', 'extended', 'disk', 'public') )
815 from_
='tenants_flavors as tf inner join flavors as f on tf.flavor_id=f.uuid'
816 where_
['tenant_id'] = tenant_id
817 where_
['uuid'] = flavor_id
818 result
, content
= my
.db
.get_table(SELECT
=select_
, FROM
=from_
, WHERE
=where_
, LIMIT
=limit_
)
821 print "http_get_flavor_id error %d %s" % (result
, content
)
822 bottle
.abort(-result
, content
)
824 print "http_get_flavors_id flavor '%s' not found" % str(flavor_id
)
825 bottle
.abort(HTTP_Not_Found
, 'flavor %s not found' % flavor_id
)
827 change_keys_http2db(content
, http2db_flavor
, reverse
=True)
828 if 'extended' in content
[0] and content
[0]['extended'] is not None:
829 extended
= json
.loads(content
[0]['extended'])
830 if 'devices' in extended
:
831 change_keys_http2db(extended
['devices'], http2db_flavor
, reverse
=True)
832 content
[0]['extended']=extended
833 convert_bandwidth(content
[0], reverse
=True)
834 content
[0]['links']=[ {'href': "/".join( (my
.url_preffix
, tenant_id
, 'flavors', str(content
[0]['id']) ) ), 'rel':'bookmark' } ]
835 data
={'flavor' : content
[0]}
836 #data['tenants_links'] = dict([('tenant', row['id']) for row in content])
837 return format_out(data
)
840 @bottle.route(url_base
+ '/<tenant_id>/flavors', method
='POST')
841 def http_post_flavors(tenant_id
):
842 '''insert a flavor into the database, and attach to tenant.'''
843 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
844 #check valid tenant_id
845 result
,content
= check_valid_tenant(my
, tenant_id
)
847 bottle
.abort(result
, content
)
848 http_content
= format_in( flavor_new_schema
)
849 r
= remove_extra_items(http_content
, flavor_new_schema
)
850 if r
is not None: print "http_post_flavors: Warning: remove extra items ", r
851 change_keys_http2db(http_content
['flavor'], http2db_flavor
)
852 extended_dict
= http_content
['flavor'].pop('extended', None)
853 if extended_dict
is not None:
854 result
, content
= check_extended(extended_dict
)
856 print "http_post_flavors wrong input extended error %d %s" % (result
, content
)
857 bottle
.abort(-result
, content
)
859 convert_bandwidth(extended_dict
)
860 if 'devices' in extended_dict
: change_keys_http2db(extended_dict
['devices'], http2db_flavor
)
861 http_content
['flavor']['extended'] = json
.dumps(extended_dict
)
863 result
, content
= my
.db
.new_flavor(http_content
['flavor'], tenant_id
)
865 return http_get_flavor_id(tenant_id
, content
)
867 print "http_psot_flavors error %d %s" % (result
, content
)
868 bottle
.abort(-result
, content
)
871 @bottle.route(url_base
+ '/<tenant_id>/flavors/<flavor_id>', method
='DELETE')
872 def http_delete_flavor_id(tenant_id
, flavor_id
):
873 '''Deletes the flavor_id of a tenant. IT removes from tenants_flavors table.'''
874 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
875 #check valid tenant_id
876 result
,content
= check_valid_tenant(my
, tenant_id
)
878 bottle
.abort(result
, content
)
880 result
, content
= my
.db
.delete_image_flavor('flavor', flavor_id
, tenant_id
)
882 bottle
.abort(HTTP_Not_Found
, content
)
884 data
={'result' : content
}
885 return format_out(data
)
887 print "http_delete_flavor_id error",result
, content
888 bottle
.abort(-result
, content
)
891 @bottle.route(url_base
+ '/<tenant_id>/flavors/<flavor_id>/<action>', method
='POST')
892 def http_attach_detach_flavors(tenant_id
, flavor_id
, action
):
893 '''attach/detach an existing flavor in this tenant. That is insert/remove at tenants_flavors table.'''
894 #TODO alf: not tested at all!!!
895 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
896 #check valid tenant_id
897 result
,content
= check_valid_tenant(my
, tenant_id
)
899 bottle
.abort(result
, content
)
901 bottle
.abort(HTTP_Bad_Request
, "Invalid tenant 'any' with this command")
903 if action
!='attach' and action
!= 'detach':
904 bottle
.abort(HTTP_Method_Not_Allowed
, "actions can be attach or detach")
907 #Ensure that flavor exist
908 from_
='tenants_flavors as tf right join flavors as f on tf.flavor_id=f.uuid'
909 where_
={'uuid': flavor_id
}
910 result
, content
= my
.db
.get_table(SELECT
=('public','tenant_id'), FROM
=from_
, WHERE
=where_
)
913 text_error
="Flavor '%s' not found" % flavor_id
915 text_error
="Flavor '%s' not found for tenant '%s'" % (flavor_id
, tenant_id
)
916 bottle
.abort(HTTP_Not_Found
, text_error
)
921 if flavor
['tenant_id']!=None:
922 bottle
.abort(HTTP_Conflict
, "Flavor '%s' already attached to tenant '%s'" % (flavor_id
, tenant_id
))
923 if flavor
['public']=='no' and not my
.admin
:
924 #allow only attaching public flavors
925 bottle
.abort(HTTP_Unauthorized
, "Needed admin rights to attach a private flavor")
928 result
, content
= my
.db
.new_row('tenants_flavors', {'flavor_id':flavor_id
, 'tenant_id': tenant_id
})
930 return http_get_flavor_id(tenant_id
, flavor_id
)
932 if flavor
['tenant_id']==None:
933 bottle
.abort(HTTP_Not_Found
, "Flavor '%s' not attached to tenant '%s'" % (flavor_id
, tenant_id
))
934 result
, content
= my
.db
.delete_row_by_dict(FROM
='tenants_flavors', WHERE
={'flavor_id':flavor_id
, 'tenant_id':tenant_id
})
936 if flavor
['public']=='no':
937 #try to delete the flavor completely to avoid orphan flavors, IGNORE error
938 my
.db
.delete_row_by_dict(FROM
='flavors', WHERE
={'uuid':flavor_id
})
939 data
={'result' : "flavor detached"}
940 return format_out(data
)
942 #if get here is because an error
943 print "http_attach_detach_flavors error %d %s" % (result
, content
)
944 bottle
.abort(-result
, content
)
947 @bottle.route(url_base
+ '/<tenant_id>/flavors/<flavor_id>', method
='PUT')
948 def http_put_flavor_id(tenant_id
, flavor_id
):
949 '''update a flavor_id into the database.'''
950 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
951 #check valid tenant_id
952 result
,content
= check_valid_tenant(my
, tenant_id
)
954 bottle
.abort(result
, content
)
956 http_content
= format_in( flavor_update_schema
)
957 r
= remove_extra_items(http_content
, flavor_update_schema
)
958 if r
is not None: print "http_put_flavor_id: Warning: remove extra items ", r
959 change_keys_http2db(http_content
['flavor'], http2db_flavor
)
960 extended_dict
= http_content
['flavor'].pop('extended', None)
961 if extended_dict
is not None:
962 result
, content
= check_extended(extended_dict
)
964 print "http_put_flavor_id wrong input extended error %d %s" % (result
, content
)
965 bottle
.abort(-result
, content
)
967 convert_bandwidth(extended_dict
)
968 if 'devices' in extended_dict
: change_keys_http2db(extended_dict
['devices'], http2db_flavor
)
969 http_content
['flavor']['extended'] = json
.dumps(extended_dict
)
970 #Ensure that flavor exist
971 where_
={'uuid': flavor_id
}
975 from_
='tenants_flavors as ti inner join flavors as i on ti.flavor_id=i.uuid'
976 where_
['tenant_id'] = tenant_id
977 result
, content
= my
.db
.get_table(SELECT
=('public',), FROM
=from_
, WHERE
=where_
)
979 text_error
="Flavor '%s' not found" % flavor_id
981 text_error
+=" for tenant '%s'" % flavor_id
982 bottle
.abort(HTTP_Not_Found
, text_error
)
985 if content
[0]['public']=='yes' and not my
.admin
:
986 #allow only modifications over private flavors
987 bottle
.abort(HTTP_Unauthorized
, "Needed admin rights to edit a public flavor")
990 result
, content
= my
.db
.update_rows('flavors', http_content
['flavor'], {'uuid': flavor_id
})
993 print "http_put_flavor_id error %d %s" % (result
, content
)
994 bottle
.abort(-result
, content
)
997 return http_get_flavor_id(tenant_id
, flavor_id
)
1005 @bottle.route(url_base
+ '/<tenant_id>/images', method
='GET')
1006 def http_get_images(tenant_id
):
1007 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
1008 #check valid tenant_id
1009 result
,content
= check_valid_tenant(my
, tenant_id
)
1011 bottle
.abort(result
, content
)
1013 select_
,where_
,limit_
= filter_query_string(bottle
.request
.query
, http2db_image
,
1014 ('id','name','description','path','public') )
1015 if tenant_id
=='any':
1018 from_
='tenants_images inner join images on tenants_images.image_id=images.uuid'
1019 where_
['tenant_id'] = tenant_id
1020 result
, content
= my
.db
.get_table(SELECT
=select_
, FROM
=from_
, WHERE
=where_
, LIMIT
=limit_
)
1022 print "http_get_images Error", content
1023 bottle
.abort(-result
, content
)
1025 change_keys_http2db(content
, http2db_image
, reverse
=True)
1026 #for row in content: row['links']=[ {'href': "/".join( (my.url_preffix, tenant_id, 'images', str(row['id']) ) ), 'rel':'bookmark' } ]
1027 data
={'images' : content
}
1028 return format_out(data
)
1030 @bottle.route(url_base
+ '/<tenant_id>/images/<image_id>', method
='GET')
1031 def http_get_image_id(tenant_id
, image_id
):
1032 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
1033 #check valid tenant_id
1034 result
,content
= check_valid_tenant(my
, tenant_id
)
1036 bottle
.abort(result
, content
)
1038 select_
,where_
,limit_
= filter_query_string(bottle
.request
.query
, http2db_image
,
1039 ('id','name','description','progress', 'status','path', 'created', 'updated','public') )
1040 if tenant_id
=='any':
1043 from_
='tenants_images as ti inner join images as i on ti.image_id=i.uuid'
1044 where_
['tenant_id'] = tenant_id
1045 where_
['uuid'] = image_id
1046 result
, content
= my
.db
.get_table(SELECT
=select_
, FROM
=from_
, WHERE
=where_
, LIMIT
=limit_
)
1049 print "http_get_images error %d %s" % (result
, content
)
1050 bottle
.abort(-result
, content
)
1052 print "http_get_images image '%s' not found" % str(image_id
)
1053 bottle
.abort(HTTP_Not_Found
, 'image %s not found' % image_id
)
1055 convert_datetime2str(content
)
1056 change_keys_http2db(content
, http2db_image
, reverse
=True)
1057 if 'metadata' in content
[0] and content
[0]['metadata'] is not None:
1058 metadata
= json
.loads(content
[0]['metadata'])
1059 content
[0]['metadata']=metadata
1060 content
[0]['links']=[ {'href': "/".join( (my
.url_preffix
, tenant_id
, 'images', str(content
[0]['id']) ) ), 'rel':'bookmark' } ]
1061 data
={'image' : content
[0]}
1062 #data['tenants_links'] = dict([('tenant', row['id']) for row in content])
1063 return format_out(data
)
1065 @bottle.route(url_base
+ '/<tenant_id>/images', method
='POST')
1066 def http_post_images(tenant_id
):
1067 '''insert a image into the database, and attach to tenant.'''
1068 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
1069 #check valid tenant_id
1070 result
,content
= check_valid_tenant(my
, tenant_id
)
1072 bottle
.abort(result
, content
)
1073 http_content
= format_in(image_new_schema
)
1074 r
= remove_extra_items(http_content
, image_new_schema
)
1075 if r
is not None: print "http_post_images: Warning: remove extra items ", r
1076 change_keys_http2db(http_content
['image'], http2db_image
)
1077 metadata_dict
= http_content
['image'].pop('metadata', None)
1078 if metadata_dict
is not None:
1079 http_content
['image']['metadata'] = json
.dumps(metadata_dict
)
1080 #insert in data base
1081 result
, content
= my
.db
.new_image(http_content
['image'], tenant_id
)
1083 return http_get_image_id(tenant_id
, content
)
1085 print "http_post_images error %d %s" % (result
, content
)
1086 bottle
.abort(-result
, content
)
1089 @bottle.route(url_base
+ '/<tenant_id>/images/<image_id>', method
='DELETE')
1090 def http_delete_image_id(tenant_id
, image_id
):
1091 '''Deletes the image_id of a tenant. IT removes from tenants_images table.'''
1092 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
1093 #check valid tenant_id
1094 result
,content
= check_valid_tenant(my
, tenant_id
)
1096 bottle
.abort(result
, content
)
1097 result
, content
= my
.db
.delete_image_flavor('image', image_id
, tenant_id
)
1099 bottle
.abort(HTTP_Not_Found
, content
)
1101 data
={'result' : content
}
1102 return format_out(data
)
1104 print "http_delete_image_id error",result
, content
1105 bottle
.abort(-result
, content
)
1108 @bottle.route(url_base
+ '/<tenant_id>/images/<image_id>/<action>', method
='POST')
1109 def http_attach_detach_images(tenant_id
, image_id
, action
):
1110 '''attach/detach an existing image in this tenant. That is insert/remove at tenants_images table.'''
1111 #TODO alf: not tested at all!!!
1112 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
1113 #check valid tenant_id
1114 result
,content
= check_valid_tenant(my
, tenant_id
)
1116 bottle
.abort(result
, content
)
1117 if tenant_id
=='any':
1118 bottle
.abort(HTTP_Bad_Request
, "Invalid tenant 'any' with this command")
1120 if action
!='attach' and action
!= 'detach':
1121 bottle
.abort(HTTP_Method_Not_Allowed
, "actions can be attach or detach")
1124 #Ensure that image exist
1125 from_
='tenants_images as ti right join images as i on ti.image_id=i.uuid'
1126 where_
={'uuid': image_id
}
1127 result
, content
= my
.db
.get_table(SELECT
=('public','tenant_id'), FROM
=from_
, WHERE
=where_
)
1129 if action
=='attach':
1130 text_error
="Image '%s' not found" % image_id
1132 text_error
="Image '%s' not found for tenant '%s'" % (image_id
, tenant_id
)
1133 bottle
.abort(HTTP_Not_Found
, text_error
)
1137 if action
=='attach':
1138 if image
['tenant_id']!=None:
1139 bottle
.abort(HTTP_Conflict
, "Image '%s' already attached to tenant '%s'" % (image_id
, tenant_id
))
1140 if image
['public']=='no' and not my
.admin
:
1141 #allow only attaching public images
1142 bottle
.abort(HTTP_Unauthorized
, "Needed admin rights to attach a private image")
1144 #insert in data base
1145 result
, content
= my
.db
.new_row('tenants_images', {'image_id':image_id
, 'tenant_id': tenant_id
})
1147 return http_get_image_id(tenant_id
, image_id
)
1149 if image
['tenant_id']==None:
1150 bottle
.abort(HTTP_Not_Found
, "Image '%s' not attached to tenant '%s'" % (image_id
, tenant_id
))
1151 result
, content
= my
.db
.delete_row_by_dict(FROM
='tenants_images', WHERE
={'image_id':image_id
, 'tenant_id':tenant_id
})
1153 if image
['public']=='no':
1154 #try to delete the image completely to avoid orphan images, IGNORE error
1155 my
.db
.delete_row_by_dict(FROM
='images', WHERE
={'uuid':image_id
})
1156 data
={'result' : "image detached"}
1157 return format_out(data
)
1159 #if get here is because an error
1160 print "http_attach_detach_images error %d %s" % (result
, content
)
1161 bottle
.abort(-result
, content
)
1164 @bottle.route(url_base
+ '/<tenant_id>/images/<image_id>', method
='PUT')
1165 def http_put_image_id(tenant_id
, image_id
):
1166 '''update a image_id into the database.'''
1167 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
1168 #check valid tenant_id
1169 result
,content
= check_valid_tenant(my
, tenant_id
)
1171 bottle
.abort(result
, content
)
1173 http_content
= format_in( image_update_schema
)
1174 r
= remove_extra_items(http_content
, image_update_schema
)
1175 if r
is not None: print "http_put_image_id: Warning: remove extra items ", r
1176 change_keys_http2db(http_content
['image'], http2db_image
)
1177 metadata_dict
= http_content
['image'].pop('metadata', None)
1178 if metadata_dict
is not None:
1179 http_content
['image']['metadata'] = json
.dumps(metadata_dict
)
1180 #Ensure that image exist
1181 where_
={'uuid': image_id
}
1182 if tenant_id
=='any':
1185 from_
='tenants_images as ti inner join images as i on ti.image_id=i.uuid'
1186 where_
['tenant_id'] = tenant_id
1187 result
, content
= my
.db
.get_table(SELECT
=('public',), FROM
=from_
, WHERE
=where_
)
1189 text_error
="Image '%s' not found" % image_id
1190 if tenant_id
!='any':
1191 text_error
+=" for tenant '%s'" % image_id
1192 bottle
.abort(HTTP_Not_Found
, text_error
)
1195 if content
[0]['public']=='yes' and not my
.admin
:
1196 #allow only modifications over private images
1197 bottle
.abort(HTTP_Unauthorized
, "Needed admin rights to edit a public image")
1199 #insert in data base
1200 result
, content
= my
.db
.update_rows('images', http_content
['image'], {'uuid': image_id
})
1203 print "http_put_image_id error %d %s" % (result
, content
)
1204 bottle
.abort(-result
, content
)
1207 return http_get_image_id(tenant_id
, image_id
)
1214 @bottle.route(url_base
+ '/<tenant_id>/servers', method
='GET')
1215 def http_get_servers(tenant_id
):
1216 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
1217 result
,content
= check_valid_tenant(my
, tenant_id
)
1219 bottle
.abort(result
, content
)
1222 select_
,where_
,limit_
= filter_query_string(bottle
.request
.query
, http2db_server
,
1223 ('id','name','description','hostId','imageRef','flavorRef','status', 'tenant_id') )
1224 if tenant_id
!='any':
1225 where_
['tenant_id'] = tenant_id
1226 result
, content
= my
.db
.get_table(SELECT
=select_
, FROM
='instances', WHERE
=where_
, LIMIT
=limit_
)
1228 print "http_get_servers Error", content
1229 bottle
.abort(-result
, content
)
1231 change_keys_http2db(content
, http2db_server
, reverse
=True)
1233 tenant_id
= row
.pop('tenant_id')
1234 row
['links']=[ {'href': "/".join( (my
.url_preffix
, tenant_id
, 'servers', str(row
['id']) ) ), 'rel':'bookmark' } ]
1235 data
={'servers' : content
}
1236 return format_out(data
)
1238 @bottle.route(url_base
+ '/<tenant_id>/servers/<server_id>', method
='GET')
1239 def http_get_server_id(tenant_id
, server_id
):
1240 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
1241 #check valid tenant_id
1242 result
,content
= check_valid_tenant(my
, tenant_id
)
1244 bottle
.abort(result
, content
)
1247 result
, content
= my
.db
.get_instance(server_id
)
1249 bottle
.abort(HTTP_Not_Found
, content
)
1251 #change image/flavor-id to id and link
1252 convert_bandwidth(content
, reverse
=True)
1253 convert_datetime2str(content
)
1254 if content
["ram"]==0 : del content
["ram"]
1255 if content
["vcpus"]==0 : del content
["vcpus"]
1256 if 'flavor_id' in content
:
1257 if content
['flavor_id'] is not None:
1258 content
['flavor'] = {'id':content
['flavor_id'],
1259 'links':[{'href': "/".join( (my
.url_preffix
, content
['tenant_id'], 'flavors', str(content
['flavor_id']) ) ), 'rel':'bookmark'}]
1261 del content
['flavor_id']
1262 if 'image_id' in content
:
1263 if content
['image_id'] is not None:
1264 content
['image'] = {'id':content
['image_id'],
1265 'links':[{'href': "/".join( (my
.url_preffix
, content
['tenant_id'], 'images', str(content
['image_id']) ) ), 'rel':'bookmark'}]
1267 del content
['image_id']
1268 change_keys_http2db(content
, http2db_server
, reverse
=True)
1269 if 'extended' in content
:
1270 if 'devices' in content
['extended']: change_keys_http2db(content
['extended']['devices'], http2db_server
, reverse
=True)
1272 data
={'server' : content
}
1273 return format_out(data
)
1275 bottle
.abort(-result
, content
)
1278 @bottle.route(url_base
+ '/<tenant_id>/servers', method
='POST')
1279 def http_post_server_id(tenant_id
):
1280 '''deploys a new server'''
1281 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
1282 #check valid tenant_id
1283 result
,content
= check_valid_tenant(my
, tenant_id
)
1285 bottle
.abort(result
, content
)
1287 if tenant_id
=='any':
1288 bottle
.abort(HTTP_Bad_Request
, "Invalid tenant 'any' with this command")
1290 http_content
= format_in( server_new_schema
)
1291 r
= remove_extra_items(http_content
, server_new_schema
)
1292 if r
is not None: print "http_post_serves: Warning: remove extra items ", r
1293 change_keys_http2db(http_content
['server'], http2db_server
)
1294 extended_dict
= http_content
['server'].get('extended', None)
1295 if extended_dict
is not None:
1296 result
, content
= check_extended(extended_dict
, True)
1298 print "http_post_servers wrong input extended error %d %s" % (result
, content
)
1299 bottle
.abort(-result
, content
)
1301 convert_bandwidth(extended_dict
)
1302 if 'devices' in extended_dict
: change_keys_http2db(extended_dict
['devices'], http2db_server
)
1304 server
= http_content
['server']
1305 server_start
= server
.get('start', 'yes')
1306 server
['tenant_id'] = tenant_id
1307 #check flavor valid and take info
1308 result
, content
= my
.db
.get_table(FROM
='tenants_flavors as tf join flavors as f on tf.flavor_id=f.uuid',
1309 SELECT
=('ram','vcpus','extended'), WHERE
={'uuid':server
['flavor_id'], 'tenant_id':tenant_id
})
1311 bottle
.abort(HTTP_Not_Found
, 'flavor_id %s not found' % server
['flavor_id'])
1313 server
['flavor']=content
[0]
1314 #check image valid and take info
1315 result
, content
= my
.db
.get_table(FROM
='tenants_images as ti join images as i on ti.image_id=i.uuid',
1316 SELECT
=('path','metadata'), WHERE
={'uuid':server
['image_id'], 'tenant_id':tenant_id
, "status":"ACTIVE"})
1318 bottle
.abort(HTTP_Not_Found
, 'image_id %s not found or not ACTIVE' % server
['image_id'])
1320 server
['image']=content
[0]
1321 if "hosts_id" in server
:
1322 result
, content
= my
.db
.get_table(FROM
='hosts', SELECT
=('uuid',), WHERE
={'uuid': server
['host_id']})
1324 bottle
.abort(HTTP_Not_Found
, 'hostId %s not found' % server
['host_id'])
1326 #print json.dumps(server, indent=4)
1328 result
, content
= ht
.create_server(server
, config_dic
['db'], config_dic
['db_lock'], config_dic
['mode']=='normal')
1331 #Insert instance to database
1334 print "inserting at DB"
1336 if server_start
== 'no':
1337 content
['status'] = 'INACTIVE'
1339 new_instance_result
, new_instance
= my
.db
.new_instance(content
, nets
, ports_to_free
)
1340 if new_instance_result
< 0:
1341 print "Error http_post_servers() :", new_instance_result
, new_instance
1342 bottle
.abort(-new_instance_result
, new_instance
)
1345 print "inserted at DB"
1347 for port
in ports_to_free
:
1348 r
,c
= config_dic
['host_threads'][ server
['host_id'] ].insert_task( 'restore-iface',*port
)
1350 print ' http_post_servers ERROR RESTORE IFACE!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!' + c
1353 r
,c
= config_dic
['of_thread'].insert_task("update-net", net
)
1355 print ':http_post_servers ERROR UPDATING NETS !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!' + c
1359 #look for dhcp ip address
1360 r2
, c2
= my
.db
.get_table(FROM
="ports", SELECT
=["mac", "net_id"], WHERE
={"instance_id": new_instance
})
1361 if r2
>0 and config_dic
.get("dhcp_server"):
1363 if iface
["net_id"] in config_dic
["dhcp_nets"]:
1364 #print "dhcp insert add task"
1365 r
,c
= config_dic
['dhcp_thread'].insert_task("add", iface
["mac"])
1367 print ':http_post_servers ERROR UPDATING dhcp_server !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!' + c
1371 server
['uuid'] = new_instance
1372 #server_start = server.get('start', 'yes')
1373 if server_start
!= 'no':
1374 server
['paused'] = True if server_start
== 'paused' else False
1375 server
['action'] = {"start":None}
1376 server
['status'] = "CREATING"
1378 r
,c
= config_dic
['host_threads'][ server
['host_id'] ].insert_task( 'instance',server
)
1380 my
.db
.update_rows('instances', {'status':"ERROR"}, {'uuid':server
['uuid'], 'last_error':c
}, log
=True)
1382 return http_get_server_id(tenant_id
, new_instance
)
1384 bottle
.abort(HTTP_Bad_Request
, content
)
1387 def http_server_action(server_id
, tenant_id
, action
):
1388 '''Perform actions over a server as resume, reboot, terminate, ...'''
1389 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
1390 server
={"uuid": server_id
, "action":action
}
1391 where
={'uuid': server_id
}
1392 if tenant_id
!='any':
1393 where
['tenant_id']= tenant_id
1394 result
, content
= my
.db
.get_table(FROM
='instances', WHERE
=where
)
1396 bottle
.abort(HTTP_Not_Found
, "server %s not found" % server_id
)
1399 print "http_post_server_action error getting data %d %s" % (result
, content
)
1400 bottle
.abort(HTTP_Internal_Server_Error
, content
)
1402 server
.update(content
[0])
1403 tenant_id
= server
["tenant_id"]
1405 #TODO check a right content
1407 if 'terminate' in action
:
1408 new_status
='DELETING'
1409 elif server
['status'] == 'ERROR': #or server['status'] == 'CREATING':
1410 if 'terminate' not in action
and 'rebuild' not in action
:
1411 bottle
.abort(HTTP_Method_Not_Allowed
, "Server is in ERROR status, must be rebuit or deleted ")
1413 # elif server['status'] == 'INACTIVE':
1414 # if 'start' not in action and 'createImage' not in action:
1415 # bottle.abort(HTTP_Method_Not_Allowed, "The only possible action over an instance in 'INACTIVE' status is 'start'")
1417 # if 'start' in action:
1418 # new_status='CREATING'
1419 # server['paused']='no'
1420 # elif server['status'] == 'PAUSED':
1421 # if 'resume' not in action:
1422 # bottle.abort(HTTP_Method_Not_Allowed, "The only possible action over an instance in 'PAUSED' status is 'resume'")
1424 # elif server['status'] == 'ACTIVE':
1425 # if 'pause' not in action and 'reboot'not in action and 'shutoff'not in action:
1426 # bottle.abort(HTTP_Method_Not_Allowed, "The only possible action over an instance in 'ACTIVE' status is 'pause','reboot' or 'shutoff'")
1429 if 'start' in action
or 'createImage' in action
or 'rebuild' in action
:
1430 #check image valid and take info
1431 image_id
= server
['image_id']
1432 if 'createImage' in action
:
1433 if 'imageRef' in action
['createImage']:
1434 image_id
= action
['createImage']['imageRef']
1435 elif 'disk' in action
['createImage']:
1436 result
, content
= my
.db
.get_table(FROM
='instance_devices',
1437 SELECT
=('image_id','dev'), WHERE
={'instance_id':server
['uuid'],"type":"disk"})
1439 bottle
.abort(HTTP_Not_Found
, 'disk not found for server')
1443 if action
['createImage']['imageRef']['disk'] != None:
1444 for disk
in content
:
1445 if disk
['dev'] == action
['createImage']['imageRef']['disk']:
1446 disk_id
= disk
['image_id']
1449 bottle
.abort(HTTP_Not_Found
, 'disk %s not found for server' % action
['createImage']['imageRef']['disk'])
1452 bottle
.abort(HTTP_Not_Found
, 'more than one disk found for server' )
1456 image_id
= content
[0]['image_id']
1458 result
, content
= my
.db
.get_table(FROM
='tenants_images as ti join images as i on ti.image_id=i.uuid',
1459 SELECT
=('path','metadata'), WHERE
={'uuid':image_id
, 'tenant_id':tenant_id
, "status":"ACTIVE"})
1461 bottle
.abort(HTTP_Not_Found
, 'image_id %s not found or not ACTIVE' % image_id
)
1463 if content
[0]['metadata'] is not None:
1465 metadata
= json
.loads(content
[0]['metadata'])
1467 return -HTTP_Internal_Server_Error
, "Can not decode image metadata"
1468 content
[0]['metadata']=metadata
1470 content
[0]['metadata'] = {}
1471 server
['image']=content
[0]
1472 if 'createImage' in action
:
1473 action
['createImage']['source'] = {'image_id': image_id
, 'path': content
[0]['path']}
1474 if 'createImage' in action
:
1475 #Create an entry in Database for the new image
1476 new_image
={'status':'BUILD', 'progress': 0 }
1477 new_image_metadata
=content
[0]
1478 if 'metadata' in server
['image'] and server
['image']['metadata'] != None:
1479 new_image_metadata
.update(server
['image']['metadata'])
1480 new_image_metadata
= {"use_incremental":"no"}
1481 if 'metadata' in action
['createImage']:
1482 new_image_metadata
.update(action
['createImage']['metadata'])
1483 new_image
['metadata'] = json
.dumps(new_image_metadata
)
1484 new_image
['name'] = action
['createImage'].get('name', None)
1485 new_image
['description'] = action
['createImage'].get('description', None)
1486 new_image
['uuid']=my
.db
.new_uuid()
1487 if 'path' in action
['createImage']:
1488 new_image
['path'] = action
['createImage']['path']
1490 new_image
['path']="/provisional/path/" + new_image
['uuid']
1491 result
, image_uuid
= my
.db
.new_image(new_image
, tenant_id
)
1493 bottle
.abort(HTTP_Bad_Request
, 'Error: ' + image_uuid
)
1495 server
['new_image'] = new_image
1499 r
,c
= config_dic
['host_threads'][ server
['host_id'] ].insert_task( 'instance',server
)
1501 print "Task queue full at host ", server
['host_id']
1502 bottle
.abort(HTTP_Request_Timeout
, c
)
1503 if 'createImage' in action
and result
>= 0:
1504 return http_get_image_id(tenant_id
, image_uuid
)
1506 #Update DB only for CREATING or DELETING status
1507 data
={'result' : 'in process'}
1508 if new_status
!= None and new_status
== 'DELETING':
1511 #look for dhcp ip address
1512 r2
, c2
= my
.db
.get_table(FROM
="ports", SELECT
=["mac", "net_id"], WHERE
={"instance_id": server_id
})
1513 r
,c
= my
.db
.delete_instance(server_id
, tenant_id
, nets
, ports_to_free
, "requested by http")
1514 for port
in ports_to_free
:
1515 r1
,c1
= config_dic
['host_threads'][ server
['host_id'] ].insert_task( 'restore-iface',*port
)
1517 print ' http_post_server_action error at server deletion ERROR resore-iface !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!' + c1
1518 data
={'result' : 'deleting in process, but ifaces cannot be restored!!!!!'}
1520 r1
,c1
= config_dic
['of_thread'].insert_task("update-net", net
)
1522 print ' http_post_server_action error at server deletion ERROR UPDATING NETS !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!' + c1
1523 data
={'result' : 'deleting in process, but openflow rules cannot be deleted!!!!!'}
1524 #look for dhcp ip address
1525 if r2
>0 and config_dic
.get("dhcp_server"):
1527 if iface
["net_id"] in config_dic
["dhcp_nets"]:
1528 r
,c
= config_dic
['dhcp_thread'].insert_task("del", iface
["mac"])
1529 #print "dhcp insert del task"
1531 print ':http_post_servers ERROR UPDATING dhcp_server !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!' + c
1533 return format_out(data
)
1537 @bottle.route(url_base
+ '/<tenant_id>/servers/<server_id>', method
='DELETE')
1538 def http_delete_server_id(tenant_id
, server_id
):
1539 '''delete a server'''
1540 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
1541 #check valid tenant_id
1542 result
,content
= check_valid_tenant(my
, tenant_id
)
1544 bottle
.abort(result
, content
)
1547 return http_server_action(server_id
, tenant_id
, {"terminate":None} )
1550 @bottle.route(url_base
+ '/<tenant_id>/servers/<server_id>/action', method
='POST')
1551 def http_post_server_action(tenant_id
, server_id
):
1552 '''take an action over a server'''
1553 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
1554 #check valid tenant_id
1555 result
,content
= check_valid_tenant(my
, tenant_id
)
1557 bottle
.abort(result
, content
)
1559 http_content
= format_in( server_action_schema
)
1560 #r = remove_extra_items(http_content, server_action_schema)
1561 #if r is not None: print "http_post_server_action: Warning: remove extra items ", r
1563 return http_server_action(server_id
, tenant_id
, http_content
)
1570 @bottle.route(url_base
+ '/networks', method
='GET')
1571 def http_get_networks():
1572 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
1574 select_
,where_
,limit_
= filter_query_string(bottle
.request
.query
, http2db_network
,
1575 ('id','name','tenant_id','type',
1576 'shared','provider:vlan','status','last_error','admin_state_up','provider:physical') )
1577 #TODO temporally remove tenant_id
1578 if "tenant_id" in where_
:
1579 del where_
["tenant_id"]
1580 result
, content
= my
.db
.get_table(SELECT
=select_
, FROM
='nets', WHERE
=where_
, LIMIT
=limit_
)
1582 print "http_get_networks error %d %s" % (result
, content
)
1583 bottle
.abort(-result
, content
)
1585 convert_boolean(content
, ('shared', 'admin_state_up', 'enable_dhcp') )
1586 delete_nulls(content
)
1587 change_keys_http2db(content
, http2db_network
, reverse
=True)
1588 data
={'networks' : content
}
1589 return format_out(data
)
1591 @bottle.route(url_base
+ '/networks/<network_id>', method
='GET')
1592 def http_get_network_id(network_id
):
1593 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
1595 where_
= bottle
.request
.query
1596 where_
['uuid'] = network_id
1597 result
, content
= my
.db
.get_table(FROM
='nets', WHERE
=where_
, LIMIT
=100)
1600 print "http_get_networks_id error %d %s" % (result
, content
)
1601 bottle
.abort(-result
, content
)
1603 print "http_get_networks_id network '%s' not found" % network_id
1604 bottle
.abort(HTTP_Not_Found
, 'network %s not found' % network_id
)
1606 convert_boolean(content
, ('shared', 'admin_state_up', 'enale_dhcp') )
1607 change_keys_http2db(content
, http2db_network
, reverse
=True)
1609 result
, ports
= my
.db
.get_table(FROM
='ports', SELECT
=('uuid as port_id',),
1610 WHERE
={'net_id': network_id
}, LIMIT
=100)
1612 content
[0]['ports'] = ports
1613 delete_nulls(content
[0])
1614 data
={'network' : content
[0]}
1615 return format_out(data
)
1617 @bottle.route(url_base
+ '/networks', method
='POST')
1618 def http_post_networks():
1619 '''insert a network into the database.'''
1620 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
1622 http_content
= format_in( network_new_schema
)
1623 r
= remove_extra_items(http_content
, network_new_schema
)
1624 if r
is not None: print "http_post_networks: Warning: remove extra items ", r
1625 change_keys_http2db(http_content
['network'], http2db_network
)
1626 network
=http_content
['network']
1627 #check valid tenant_id
1628 tenant_id
= network
.get('tenant_id')
1630 result
, _
= my
.db
.get_table(FROM
='tenants', SELECT
=('uuid',), WHERE
={'uuid': tenant_id
,"enabled":True})
1632 bottle
.abort(HTTP_Not_Found
, 'tenant %s not found or not enabled' % tenant_id
)
1636 net_provider
= network
.get('provider')
1637 net_type
= network
.get('type')
1638 net_vlan
= network
.get("vlan")
1639 net_bind_net
= network
.get("bind_net")
1640 net_bind_type
= network
.get("bind_type")
1641 name
= network
["name"]
1643 #check if network name ends with :<vlan_tag> and network exist in order to make and automated bindning
1644 vlan_index
=name
.rfind(":")
1645 if net_bind_net
==None and net_bind_type
==None and vlan_index
> 1:
1647 vlan_tag
= int(name
[vlan_index
+1:])
1648 if vlan_tag
>0 and vlan_tag
< 4096:
1649 net_bind_net
= name
[:vlan_index
]
1650 net_bind_type
= "vlan:" + name
[vlan_index
+1:]
1654 if net_bind_net
!= None:
1655 #look for a valid net
1656 if check_valid_uuid(net_bind_net
):
1657 net_bind_key
= "uuid"
1659 net_bind_key
= "name"
1660 result
, content
= my
.db
.get_table(FROM
='nets', WHERE
={net_bind_key
: net_bind_net
} )
1662 bottle
.abort(HTTP_Internal_Server_Error
, 'getting nets from db ' + content
)
1665 bottle
.abort(HTTP_Bad_Request
, "bind_net %s '%s'not found" % (net_bind_key
, net_bind_net
) )
1668 bottle
.abort(HTTP_Bad_Request
, "more than one bind_net %s '%s' found, use uuid" % (net_bind_key
, net_bind_net
) )
1670 network
["bind_net"] = content
[0]["uuid"]
1671 if net_bind_type
!= None:
1672 if net_bind_type
[0:5] != "vlan:":
1673 bottle
.abort(HTTP_Bad_Request
, "bad format for 'bind_type', must be 'vlan:<tag>'")
1675 if int(net_bind_type
[5:]) > 4095 or int(net_bind_type
[5:])<=0 :
1676 bottle
.abort(HTTP_Bad_Request
, "bad format for 'bind_type', must be 'vlan:<tag>' with a tag between 1 and 4095")
1678 network
["bind_type"] = net_bind_type
1680 if net_provider
!=None:
1681 if net_provider
[:9]=="openflow:":
1683 if net_type
!="ptp" and net_type
!="data":
1684 bottle
.abort(HTTP_Bad_Request
, "Only 'ptp' or 'data' net types can be bound to 'openflow'")
1689 if net_type
!="bridge_man" and net_type
!="bridge_data":
1690 bottle
.abort(HTTP_Bad_Request
, "Only 'bridge_man' or 'bridge_data' net types can be bound to 'bridge', 'macvtap' or 'default")
1692 net_type
='bridge_man'
1695 net_type
='bridge_man'
1697 if net_provider
!= None:
1698 if net_provider
[:7]=='bridge:':
1699 #check it is one of the pre-provisioned bridges
1700 bridge_net_name
= net_provider
[7:]
1701 for brnet
in config_dic
['bridge_nets']:
1702 if brnet
[0]==bridge_net_name
: # free
1703 if brnet
[3] != None:
1704 bottle
.abort(HTTP_Conflict
, "invalid 'provider:physical', bridge '%s' is already used" % bridge_net_name
)
1709 # if bridge_net==None:
1710 # 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)
1712 elif net_type
=='bridge_data' or net_type
=='bridge_man':
1713 #look for a free precreated nets
1714 for brnet
in config_dic
['bridge_nets']:
1715 if brnet
[3]==None: # free
1716 if bridge_net
!= None:
1717 if net_type
=='bridge_man': #look for the smaller speed
1718 if brnet
[2] < bridge_net
[2]: bridge_net
= brnet
1719 else: #look for the larger speed
1720 if brnet
[2] > bridge_net
[2]: bridge_net
= brnet
1724 if bridge_net
==None:
1725 bottle
.abort(HTTP_Bad_Request
, "Max limits of bridge networks reached. Future versions of VIM will overcome this limit")
1728 print "using net", bridge_net
1729 net_provider
= "bridge:"+bridge_net
[0]
1730 net_vlan
= bridge_net
[1]
1731 if net_vlan
==None and (net_type
=="data" or net_type
=="ptp"):
1732 net_vlan
= my
.db
.get_free_net_vlan()
1734 bottle
.abort(HTTP_Internal_Server_Error
, "Error getting an available vlan")
1737 network
['provider'] = net_provider
1738 network
['type'] = net_type
1739 network
['vlan'] = net_vlan
1740 result
, content
= my
.db
.new_row('nets', network
, True, True)
1743 if bridge_net
!=None:
1744 bridge_net
[3] = content
1746 if config_dic
.get("dhcp_server"):
1747 if network
["name"] in config_dic
["dhcp_server"].get("nets", () ):
1748 config_dic
["dhcp_nets"].append(content
)
1749 print "dhcp_server: add new net", content
1750 elif bridge_net
!= None and bridge_net
[0] in config_dic
["dhcp_server"].get("bridge_ifaces", () ):
1751 config_dic
["dhcp_nets"].append(content
)
1752 print "dhcp_server: add new net", content
1753 return http_get_network_id(content
)
1755 print "http_post_networks error %d %s" % (result
, content
)
1756 bottle
.abort(-result
, content
)
1760 @bottle.route(url_base
+ '/networks/<network_id>', method
='PUT')
1761 def http_put_network_id(network_id
):
1762 '''update a network_id into the database.'''
1763 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
1765 http_content
= format_in( network_update_schema
)
1766 r
= remove_extra_items(http_content
, network_update_schema
)
1767 change_keys_http2db(http_content
['network'], http2db_network
)
1768 network
=http_content
['network']
1770 #Look for the previous data
1771 where_
= {'uuid': network_id
}
1772 result
, network_old
= my
.db
.get_table(FROM
='nets', WHERE
=where_
)
1774 print "http_put_network_id error %d %s" % (result
, network_old
)
1775 bottle
.abort(-result
, network_old
)
1778 print "http_put_network_id network '%s' not found" % network_id
1779 bottle
.abort(HTTP_Not_Found
, 'network %s not found' % network_id
)
1782 nbports
, content
= my
.db
.get_table(FROM
='ports', SELECT
=('uuid as port_id',),
1783 WHERE
={'net_id': network_id
}, LIMIT
=100)
1785 print "http_put_network_id error %d %s" % (result
, network_old
)
1786 bottle
.abort(-result
, content
)
1789 if 'type' in network
and network
['type'] != network_old
[0]['type']:
1790 bottle
.abort(HTTP_Method_Not_Allowed
, "Can not change type of network while having ports attached")
1791 if 'vlan' in network
and network
['vlan'] != network_old
[0]['vlan']:
1792 bottle
.abort(HTTP_Method_Not_Allowed
, "Can not change vlan of network while having ports attached")
1795 net_provider
= network
.get('provider', network_old
[0]['provider'])
1796 net_type
= network
.get('type', network_old
[0]['type'])
1797 net_bind_net
= network
.get("bind_net")
1798 net_bind_type
= network
.get("bind_type")
1799 if net_bind_net
!= None:
1800 #look for a valid net
1801 if check_valid_uuid(net_bind_net
):
1802 net_bind_key
= "uuid"
1804 net_bind_key
= "name"
1805 result
, content
= my
.db
.get_table(FROM
='nets', WHERE
={net_bind_key
: net_bind_net
} )
1807 bottle
.abort(HTTP_Internal_Server_Error
, 'getting nets from db ' + content
)
1810 bottle
.abort(HTTP_Bad_Request
, "bind_net %s '%s'not found" % (net_bind_key
, net_bind_net
) )
1813 bottle
.abort(HTTP_Bad_Request
, "more than one bind_net %s '%s' found, use uuid" % (net_bind_key
, net_bind_net
) )
1815 network
["bind_net"] = content
[0]["uuid"]
1816 if net_bind_type
!= None:
1817 if net_bind_type
[0:5] != "vlan:":
1818 bottle
.abort(HTTP_Bad_Request
, "bad format for 'bind_type', must be 'vlan:<tag>'")
1820 if int(net_bind_type
[5:]) > 4095 or int(net_bind_type
[5:])<=0 :
1821 bottle
.abort(HTTP_Bad_Request
, "bad format for 'bind_type', must be 'vlan:<tag>' with a tag between 1 and 4095")
1823 if net_provider
!=None:
1824 if net_provider
[:9]=="openflow:":
1825 if net_type
!="ptp" and net_type
!="data":
1826 bottle
.abort(HTTP_Bad_Request
, "Only 'ptp' or 'data' net types can be bound to 'openflow'")
1828 if net_type
!="bridge_man" and net_type
!="bridge_data":
1829 bottle
.abort(HTTP_Bad_Request
, "Only 'bridge_man' or 'bridge_data' net types can be bound to 'bridge', 'macvtap' or 'default")
1831 #insert in data base
1832 result
, content
= my
.db
.update_rows('nets', network
, WHERE
={'uuid': network_id
}, log
=True )
1834 if result
>0: # and nbports>0 and 'admin_state_up' in network and network['admin_state_up'] != network_old[0]['admin_state_up']:
1835 r
,c
= config_dic
['of_thread'].insert_task("update-net", network_id
)
1837 print "http_put_network_id error while launching openflow rules"
1838 bottle
.abort(HTTP_Internal_Server_Error
, c
)
1839 if config_dic
.get("dhcp_server"):
1840 if network_id
in config_dic
["dhcp_nets"]:
1841 config_dic
["dhcp_nets"].remove(network_id
)
1842 print "dhcp_server: delete net", network_id
1843 if network
.get("name", network_old
["name"]) in config_dic
["dhcp_server"].get("nets", () ):
1844 config_dic
["dhcp_nets"].append(network_id
)
1845 print "dhcp_server: add new net", network_id
1847 net_bind
= network
.get("bind", network_old
["bind"] )
1848 if net_bind
and net_bind
[:7]=="bridge:" and net_bind
[7:] in config_dic
["dhcp_server"].get("bridge_ifaces", () ):
1849 config_dic
["dhcp_nets"].append(network_id
)
1850 print "dhcp_server: add new net", network_id
1851 return http_get_network_id(network_id
)
1853 bottle
.abort(-result
, content
)
1857 @bottle.route(url_base
+ '/networks/<network_id>', method
='DELETE')
1858 def http_delete_network_id(network_id
):
1859 '''delete a network_id from the database.'''
1860 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
1862 #delete from the data base
1863 result
, content
= my
.db
.delete_row('nets', network_id
)
1866 bottle
.abort(HTTP_Not_Found
, content
)
1868 for brnet
in config_dic
['bridge_nets']:
1869 if brnet
[3]==network_id
:
1872 if config_dic
.get("dhcp_server") and network_id
in config_dic
["dhcp_nets"]:
1873 config_dic
["dhcp_nets"].remove(network_id
)
1874 print "dhcp_server: delete net", network_id
1875 data
={'result' : content
}
1876 return format_out(data
)
1878 print "http_delete_network_id error",result
, content
1879 bottle
.abort(-result
, content
)
1884 @bottle.route(url_base
+ '/networks/<network_id>/openflow', method
='GET')
1885 def http_get_openflow_id(network_id
):
1886 '''To obtain the list of openflow rules of a network
1888 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
1890 if network_id
=='all':
1893 where_
={"net_id": network_id
}
1894 result
, content
= my
.db
.get_table(SELECT
=("name","net_id","priority","vlan_id","ingress_port","src_mac","dst_mac","actions"),
1895 WHERE
=where_
, FROM
='of_flows')
1897 bottle
.abort(-result
, content
)
1899 data
={'openflow-rules' : content
}
1900 return format_out(data
)
1902 @bottle.route(url_base
+ '/networks/<network_id>/openflow', method
='PUT')
1903 def http_put_openflow_id(network_id
):
1904 '''To make actions over the net. The action is to reinstall the openflow rules
1905 network_id can be 'all'
1907 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
1909 bottle
.abort(HTTP_Unauthorized
, "Needed admin privileges")
1912 if network_id
=='all':
1915 where_
={"uuid": network_id
}
1916 result
, content
= my
.db
.get_table(SELECT
=("uuid","type"), WHERE
=where_
, FROM
='nets')
1918 bottle
.abort(-result
, content
)
1922 if net
["type"]!="ptp" and net
["type"]!="data":
1925 r
,c
= config_dic
['of_thread'].insert_task("update-net", net
['uuid'])
1927 print "http_put_openflow_id error while launching openflow rules"
1928 bottle
.abort(HTTP_Internal_Server_Error
, c
)
1929 data
={'result' : str(result
)+" nets updates"}
1930 return format_out(data
)
1932 @bottle.route(url_base
+ '/networks/openflow/clear', method
='DELETE')
1933 @bottle.route(url_base
+ '/networks/clear/openflow', method
='DELETE')
1934 def http_clear_openflow_rules():
1935 '''To make actions over the net. The action is to delete ALL openflow rules
1937 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
1939 bottle
.abort(HTTP_Unauthorized
, "Needed admin privileges")
1942 r
,c
= config_dic
['of_thread'].insert_task("clear-all")
1944 print "http_delete_openflow_id error while launching openflow rules"
1945 bottle
.abort(HTTP_Internal_Server_Error
, c
)
1948 data
={'result' : " Clearing openflow rules in process"}
1949 return format_out(data
)
1951 @bottle.route(url_base
+ '/networks/openflow/ports', method
='GET')
1952 def http_get_openflow_ports():
1953 '''Obtain switch ports names of openflow controller
1955 data
={'ports' : config_dic
['of_thread'].OF_connector
.pp2ofi
}
1956 return format_out(data
)
1963 @bottle.route(url_base
+ '/ports', method
='GET')
1964 def http_get_ports():
1966 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
1967 select_
,where_
,limit_
= filter_query_string(bottle
.request
.query
, http2db_port
,
1968 ('id','name','tenant_id','network_id','vpci','mac_address','device_owner','device_id',
1969 'binding:switch_port','binding:vlan','bandwidth','status','admin_state_up','ip_address') )
1970 #result, content = my.db.get_ports(where_)
1971 result
, content
= my
.db
.get_table(SELECT
=select_
, WHERE
=where_
, FROM
='ports',LIMIT
=limit_
)
1973 print "http_get_ports Error", result
, content
1974 bottle
.abort(-result
, content
)
1977 convert_boolean(content
, ('admin_state_up',) )
1978 delete_nulls(content
)
1979 change_keys_http2db(content
, http2db_port
, reverse
=True)
1980 data
={'ports' : content
}
1981 return format_out(data
)
1983 @bottle.route(url_base
+ '/ports/<port_id>', method
='GET')
1984 def http_get_port_id(port_id
):
1985 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
1987 result
, content
= my
.db
.get_table(WHERE
={'uuid': port_id
}, FROM
='ports')
1989 print "http_get_ports error", result
, content
1990 bottle
.abort(-result
, content
)
1992 print "http_get_ports port '%s' not found" % str(port_id
)
1993 bottle
.abort(HTTP_Not_Found
, 'port %s not found' % port_id
)
1995 convert_boolean(content
, ('admin_state_up',) )
1996 delete_nulls(content
)
1997 change_keys_http2db(content
, http2db_port
, reverse
=True)
1998 data
={'port' : content
[0]}
1999 return format_out(data
)
2002 @bottle.route(url_base
+ '/ports', method
='POST')
2003 def http_post_ports():
2004 '''insert an external port into the database.'''
2005 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
2007 bottle
.abort(HTTP_Unauthorized
, "Needed admin privileges")
2009 http_content
= format_in( port_new_schema
)
2010 r
= remove_extra_items(http_content
, port_new_schema
)
2011 if r
is not None: print "http_post_ports: Warning: remove extra items ", r
2012 change_keys_http2db(http_content
['port'], http2db_port
)
2013 port
=http_content
['port']
2015 port
['type'] = 'external'
2016 if 'net_id' in port
and port
['net_id'] == None:
2019 if 'net_id' in port
:
2020 #check that new net has the correct type
2021 result
, new_net
= my
.db
.check_target_net(port
['net_id'], None, 'external' )
2023 bottle
.abort(HTTP_Bad_Request
, new_net
)
2025 #insert in data base
2026 result
, uuid
= my
.db
.new_row('ports', port
, True, True)
2028 if 'net_id' in port
:
2029 r
,c
= config_dic
['of_thread'].insert_task("update-net", port
['net_id'])
2031 print "http_post_ports error while launching openflow rules"
2032 bottle
.abort(HTTP_Internal_Server_Error
, c
)
2033 return http_get_port_id(uuid
)
2035 bottle
.abort(-result
, uuid
)
2038 @bottle.route(url_base
+ '/ports/<port_id>', method
='PUT')
2039 def http_put_port_id(port_id
):
2040 '''update a port_id into the database.'''
2042 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
2044 http_content
= format_in( port_update_schema
)
2045 change_keys_http2db(http_content
['port'], http2db_port
)
2046 port_dict
=http_content
['port']
2048 #Look for the previous port data
2049 where_
= {'uuid': port_id
}
2050 result
, content
= my
.db
.get_table(FROM
="ports",WHERE
=where_
)
2052 print "http_put_port_id error", result
, content
2053 bottle
.abort(-result
, content
)
2056 print "http_put_port_id port '%s' not found" % port_id
2057 bottle
.abort(HTTP_Not_Found
, 'port %s not found' % port_id
)
2060 for k
in ('vlan','switch_port','mac_address', 'tenant_id'):
2061 if k
in port_dict
and not my
.admin
:
2062 bottle
.abort(HTTP_Unauthorized
, "Needed admin privileges for changing " + k
)
2066 #change_keys_http2db(port, http2db_port, reverse=True)
2070 if 'net_id' in port_dict
:
2072 old_net
= port
.get('net_id', None)
2073 new_net
= port_dict
['net_id']
2074 if old_net
!= new_net
:
2076 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
2077 if old_net
is not None: nets
.append(old_net
)
2078 if port
['type'] == 'instance:bridge':
2079 bottle
.abort(HTTP_Forbidden
, "bridge interfaces cannot be attached to a different net")
2081 elif port
['type'] == 'external':
2083 bottle
.abort(HTTP_Unauthorized
, "Needed admin privileges")
2087 #check that new net has the correct type
2088 result
, new_net_dict
= my
.db
.check_target_net(new_net
, None, port
['type'] )
2090 #change VLAN for SR-IOV ports
2091 if result
>=0 and port
["type"]=="instance:data" and port
["model"]=="VF": #TODO consider also VFnotShared
2093 port_dict
["vlan"] = None
2095 port_dict
["vlan"] = new_net_dict
["vlan"]
2096 #get host where this VM is allocated
2097 result
, content
= my
.db
.get_table(FROM
="instances",WHERE
={"uuid":port
["instance_id"]})
2099 print "http_put_port_id database error", content
2101 host_id
= content
[0]["host_id"]
2103 #insert in data base
2105 result
, content
= my
.db
.update_rows('ports', port_dict
, WHERE
={'uuid': port_id
}, log
=False )
2107 #Insert task to complete actions
2110 r
,v
= config_dic
['of_thread'].insert_task("update-net", net_id
)
2111 if r
<0: print "Error ********* http_put_port_id update_of_flows: ", v
2112 #TODO Do something if fails
2114 config_dic
['host_threads'][host_id
].insert_task("edit-iface", port_id
, old_net
, new_net
)
2117 return http_get_port_id(port_id
)
2119 bottle
.abort(HTTP_Bad_Request
, content
)
2123 @bottle.route(url_base
+ '/ports/<port_id>', method
='DELETE')
2124 def http_delete_port_id(port_id
):
2125 '''delete a port_id from the database.'''
2126 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
2128 bottle
.abort(HTTP_Unauthorized
, "Needed admin privileges")
2131 #Look for the previous port data
2132 where_
= {'uuid': port_id
, "type": "external"}
2133 result
, ports
= my
.db
.get_table(WHERE
=where_
, FROM
='ports',LIMIT
=100)
2136 print "http_delete_port_id port '%s' not found" % port_id
2137 bottle
.abort(HTTP_Not_Found
, 'port %s not found or device_owner is not external' % port_id
)
2139 #delete from the data base
2140 result
, content
= my
.db
.delete_row('ports', port_id
)
2143 bottle
.abort(HTTP_Not_Found
, content
)
2145 network
= ports
[0].get('net_id', None)
2146 if network
is not None:
2148 r
,c
= config_dic
['of_thread'].insert_task("update-net", network
)
2149 if r
<0: print "!!!!!! http_delete_port_id update_of_flows error", r
, c
2150 data
={'result' : content
}
2151 return format_out(data
)
2153 print "http_delete_port_id error",result
, content
2154 bottle
.abort(-result
, content
)