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$"
40 from jsonschema
import validate
as js_v
, exceptions
as js_e
41 import host_thread
as ht
42 from vim_schema
import host_new_schema
, host_edit_schema
, tenant_new_schema
, \
44 flavor_new_schema
, flavor_update_schema
, \
45 image_new_schema
, image_update_schema
, \
46 server_new_schema
, server_action_schema
, network_new_schema
, network_update_schema
, \
47 port_new_schema
, port_update_schema
55 HTTP_Bad_Request
= 400
56 HTTP_Unauthorized
= 401
59 HTTP_Method_Not_Allowed
= 405
60 HTTP_Not_Acceptable
= 406
61 HTTP_Request_Timeout
= 408
63 HTTP_Service_Unavailable
= 503
64 HTTP_Internal_Server_Error
= 500
67 hash_md5
= hashlib
.md5()
68 with
open(fname
, "rb") as f
:
69 for chunk
in iter(lambda: f
.read(4096), b
""):
70 hash_md5
.update(chunk
)
71 return hash_md5
.hexdigest()
73 def check_extended(extended
, allow_net_attach
=False):
74 '''Makes and extra checking of extended input that cannot be done using jsonschema
76 allow_net_attach: for allowing or not the uuid field at interfaces
77 that are allowed for instance, but not for flavors
78 Return: (<0, error_text) if error; (0,None) if not error '''
79 if "numas" not in extended
: return 0, None
82 for numa
in extended
["numas"]:
86 if "cores-id" in numa
:
87 if len(numa
["cores-id"]) != numa
["cores"]:
88 return -HTTP_Bad_Request
, "different number of cores-id (%d) than cores (%d) at numa %d" % (len(numa
["cores-id"]), numa
["cores"],numaid
)
89 id_s
.extend(numa
["cores-id"])
92 if "threads-id" in numa
:
93 if len(numa
["threads-id"]) != numa
["threads"]:
94 return -HTTP_Bad_Request
, "different number of threads-id (%d) than threads (%d) at numa %d" % (len(numa
["threads-id"]), numa
["threads"],numaid
)
95 id_s
.extend(numa
["threads-id"])
96 if "paired-threads" in numa
:
98 if "paired-threads-id" in numa
:
99 if len(numa
["paired-threads-id"]) != numa
["paired-threads"]:
100 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
)
101 for pair
in numa
["paired-threads-id"]:
103 return -HTTP_Bad_Request
, "paired-threads-id must contain a list of two elements list at numa %d" % (numaid
)
106 return -HTTP_Service_Unavailable
, "only one of cores, threads, paired-threads are allowed in this version at numa %d" % numaid
108 if "interfaces" in numa
:
112 for interface
in numa
["interfaces"]:
113 if "uuid" in interface
and not allow_net_attach
:
114 return -HTTP_Bad_Request
, "uuid field is not allowed at numa %d interface %s position %d" % (numaid
, interface
.get("name",""), ifaceid
)
115 if "mac_address" in interface
and interface
["dedicated"]=="yes":
116 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
)
117 if "name" in interface
:
118 if interface
["name"] in names
:
119 return -HTTP_Bad_Request
, "name repeated at numa %d, interface %s position %d" % (numaid
, interface
.get("name",""), ifaceid
)
120 names
.append(interface
["name"])
121 if "vpci" in interface
:
122 if interface
["vpci"] in vpcis
:
123 return -HTTP_Bad_Request
, "vpci %s repeated at numa %d, interface %s position %d" % (interface
["vpci"], numaid
, interface
.get("name",""), ifaceid
)
124 vpcis
.append(interface
["vpci"])
128 return -HTTP_Service_Unavailable
, "only one numa can be defined in this version "
129 for a
in range(0,len(id_s
)):
131 return -HTTP_Bad_Request
, "core/thread identifiers must start at 0 and gaps are not alloed. Missing id number %d" % a
136 # dictionaries that change from HTTP API to database naming
138 http2db_host
={'id':'uuid'}
139 http2db_tenant
={'id':'uuid'}
140 http2db_flavor
={'id':'uuid','imageRef':'image_id'}
141 http2db_image
={'id':'uuid', 'created':'created_at', 'updated':'modified_at', 'public': 'public'}
142 http2db_server
={'id':'uuid','hostId':'host_id','flavorRef':'flavor_id','imageRef':'image_id','created':'created_at'}
143 http2db_network
={'id':'uuid','provider:vlan':'vlan', 'provider:physical': 'provider'}
144 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'}
146 def remove_extra_items(data
, schema
):
148 if type(data
) is tuple or type(data
) is list:
150 a
= remove_extra_items(d
, schema
['items'])
151 if a
is not None: deleted
.append(a
)
152 elif type(data
) is dict:
153 for k
in data
.keys():
154 if 'properties' not in schema
or k
not in schema
['properties'].keys():
158 a
= remove_extra_items(data
[k
], schema
['properties'][k
])
159 if a
is not None: deleted
.append({k
:a
})
160 if len(deleted
) == 0: return None
161 elif len(deleted
) == 1: return deleted
[0]
164 def delete_nulls(var
):
165 if type(var
) is dict:
167 if var
[k
] is None: del var
[k
]
168 elif type(var
[k
]) is dict or type(var
[k
]) is list or type(var
[k
]) is tuple:
169 if delete_nulls(var
[k
]): del var
[k
]
170 if len(var
) == 0: return True
171 elif type(var
) is list or type(var
) is tuple:
173 if type(k
) is dict: delete_nulls(k
)
174 if len(var
) == 0: return True
178 class httpserver(threading
.Thread
):
179 def __init__(self
, db_conn
, name
="http", host
='localhost', port
=8080, admin
=False, config_
=None):
181 Creates a new thread to attend the http connections
183 db_conn: database connection
184 name: name of this thread
185 host: ip or name where to listen
186 port: port where to listen
187 admin: if this has privileges of administrator or not
188 config_: unless the first thread must be provided. It is a global dictionary where to allocate the self variable
194 if config_
is not None:
196 if 'http_threads' not in config_dic
:
197 config_dic
['http_threads'] = {}
198 threading
.Thread
.__init
__(self
)
203 if name
in config_dic
:
204 print "httpserver Warning!!! Onether thread with the same name", name
206 while name
+str(n
) in config_dic
:
210 self
.url_preffix
= 'http://' + self
.host
+ ':' + str(self
.port
) + url_base
211 config_dic
['http_threads'][name
] = self
213 #Ensure that when the main program exits the thread will also exit
218 bottle
.run(host
=self
.host
, port
=self
.port
, debug
=True) #quiet=True
220 def gethost(self
, host_id
):
221 result
, content
= self
.db
.get_host(host_id
)
223 print "httpserver.gethost error %d %s" % (result
, content
)
224 bottle
.abort(-result
, content
)
226 print "httpserver.gethost host '%s' not found" % host_id
227 bottle
.abort(HTTP_Not_Found
, content
)
229 data
={'host' : content
}
230 convert_boolean(content
, ('admin_state_up',) )
231 change_keys_http2db(content
, http2db_host
, reverse
=True)
233 return format_out(data
)
235 @bottle.route(url_base
+ '/', method
='GET')
238 return 'works' #TODO: put links or redirection to /openvim???
244 def change_keys_http2db(data
, http_db
, reverse
=False):
245 '''Change keys of dictionary data according to the key_dict values
246 This allow change from http interface names to database names.
247 When reverse is True, the change is otherwise
249 data: can be a dictionary or a list
250 http_db: is a dictionary with hhtp names as keys and database names as value
251 reverse: by default change is done from http API to database. If True change is done otherwise
252 Return: None, but data is modified'''
253 if type(data
) is tuple or type(data
) is list:
255 change_keys_http2db(d
, http_db
, reverse
)
256 elif type(data
) is dict or type(data
) is bottle
.FormsDict
:
258 for k
,v
in http_db
.items():
259 if v
in data
: data
[k
]=data
.pop(v
)
261 for k
,v
in http_db
.items():
262 if k
in data
: data
[v
]=data
.pop(k
)
266 def format_out(data
):
267 '''return string of dictionary data according to requested json, yaml, xml. By default json'''
268 if 'application/yaml' in bottle
.request
.headers
.get('Accept'):
269 bottle
.response
.content_type
='application/yaml'
270 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='"'
271 else: #by default json
272 bottle
.response
.content_type
='application/json'
273 #return data #json no style
274 return json
.dumps(data
, indent
=4) + "\n"
276 def format_in(schema
):
278 error_text
= "Invalid header format "
279 format_type
= bottle
.request
.headers
.get('Content-Type', 'application/json')
280 if 'application/json' in format_type
:
281 error_text
= "Invalid json format "
282 #Use the json decoder instead of bottle decoder because it informs about the location of error formats with a ValueError exception
283 client_data
= json
.load(bottle
.request
.body
)
284 #client_data = bottle.request.json()
285 elif 'application/yaml' in format_type
:
286 error_text
= "Invalid yaml format "
287 client_data
= yaml
.load(bottle
.request
.body
)
288 elif format_type
== 'application/xml':
289 bottle
.abort(501, "Content-Type: application/xml not supported yet.")
291 print "HTTP HEADERS: " + str(bottle
.request
.headers
.items())
292 bottle
.abort(HTTP_Not_Acceptable
, 'Content-Type ' + str(format_type
) + ' not supported.')
294 #if client_data == None:
295 # bottle.abort(HTTP_Bad_Request, "Content error, empty")
299 #print "HTTP input data: ", str(client_data)
300 error_text
= "Invalid content "
301 js_v(client_data
, schema
)
304 except (ValueError, yaml
.YAMLError
) as exc
:
305 error_text
+= str(exc
)
307 bottle
.abort(HTTP_Bad_Request
, error_text
)
308 except js_e
.ValidationError
as exc
:
309 print "HTTP validate_in error, jsonschema exception ", exc
.message
, "at", exc
.path
310 print " CONTENT: " + str(bottle
.request
.body
.readlines())
312 if len(exc
.path
)>0: error_pos
=" at '" + ":".join(map(str, exc
.path
)) + "'"
313 bottle
.abort(HTTP_Bad_Request
, error_text
+ error_pos
+": "+exc
.message
)
315 # bottle.abort(HTTP_Bad_Request, "Content error: Failed to parse Content-Type", error_pos)
318 def filter_query_string(qs
, http2db
, allowed
):
319 '''Process query string (qs) checking that contains only valid tokens for avoiding SQL injection
321 'qs': bottle.FormsDict variable to be processed. None or empty is considered valid
322 'allowed': list of allowed string tokens (API http naming). All the keys of 'qs' must be one of 'allowed'
323 'http2db': dictionary with change from http API naming (dictionary key) to database naming(dictionary value)
324 Return: A tuple with the (select,where,limit) to be use in a database query. All of then transformed to the database naming
325 select: list of items to retrieve, filtered by query string 'field=token'. If no 'field' is present, allowed list is returned
326 where: dictionary with key, value, taken from the query string token=value. Empty if nothing is provided
327 limit: limit dictated by user with the query string 'limit'. 100 by default
328 abort if not permitted, using bottel.abort
333 if type(qs
) is not bottle
.FormsDict
:
334 print '!!!!!!!!!!!!!!invalid query string not a dictionary'
335 #bottle.abort(HTTP_Internal_Server_Error, "call programmer")
339 select
+= qs
.getall(k
)
342 bottle
.abort(HTTP_Bad_Request
, "Invalid query string at 'field="+v
+"'")
347 bottle
.abort(HTTP_Bad_Request
, "Invalid query string at 'limit="+qs
[k
]+"'")
350 bottle
.abort(HTTP_Bad_Request
, "Invalid query string at '"+k
+"="+qs
[k
]+"'")
351 if qs
[k
]!="null": where
[k
]=qs
[k
]
353 if len(select
)==0: select
+= allowed
354 #change from http api to database naming
355 for i
in range(0,len(select
)):
358 select
[i
] = http2db
[k
]
359 change_keys_http2db(where
, http2db
)
360 #print "filter_query_string", select,where,limit
362 return select
,where
,limit
365 def convert_bandwidth(data
, reverse
=False):
366 '''Check the field bandwidth recursively and when found, it removes units and convert to number
367 It assumes that bandwidth is well formed
369 'data': dictionary bottle.FormsDict variable to be checked. None or empty is considered valid
370 'reverse': by default convert form str to int (Mbps), if True it convert from number to units
374 if type(data
) is dict:
375 for k
in data
.keys():
376 if type(data
[k
]) is dict or type(data
[k
]) is tuple or type(data
[k
]) is list:
377 convert_bandwidth(data
[k
], reverse
)
378 if "bandwidth" in data
:
380 value
=str(data
["bandwidth"])
382 pos
= value
.find("bps")
384 if value
[pos
-1]=="G": data
["bandwidth"] = int(data
["bandwidth"][:pos
-1]) * 1000
385 elif value
[pos
-1]=="k": data
["bandwidth"]= int(data
["bandwidth"][:pos
-1]) / 1000
386 else: data
["bandwidth"]= int(data
["bandwidth"][:pos
-1])
388 value
= int(data
["bandwidth"])
389 if value
% 1000 == 0: data
["bandwidth"]=str(value
/1000) + " Gbps"
390 else: data
["bandwidth"]=str(value
) + " Mbps"
392 print "convert_bandwidth exception for type", type(data
["bandwidth"]), " data", data
["bandwidth"]
394 if type(data
) is tuple or type(data
) is list:
396 if type(k
) is dict or type(k
) is tuple or type(k
) is list:
397 convert_bandwidth(k
, reverse
)
399 def convert_boolean(data
, items
):
400 '''Check recursively the content of data, and if there is an key contained in items, convert value from string to boolean
401 It assumes that bandwidth is well formed
403 'data': dictionary bottle.FormsDict variable to be checked. None or empty is consideted valid
404 'items': tuple of keys to convert
408 if type(data
) is dict:
409 for k
in data
.keys():
410 if type(data
[k
]) is dict or type(data
[k
]) is tuple or type(data
[k
]) is list:
411 convert_boolean(data
[k
], items
)
413 if type(data
[k
]) is str:
414 if data
[k
]=="false": data
[k
]=False
415 elif data
[k
]=="true": data
[k
]=True
416 if type(data
) is tuple or type(data
) is list:
418 if type(k
) is dict or type(k
) is tuple or type(k
) is list:
419 convert_boolean(k
, items
)
421 def convert_datetime2str(var
):
422 '''Converts a datetime variable to a string with the format '%Y-%m-%dT%H:%i:%s'
423 It enters recursively in the dict var finding this kind of variables
425 if type(var
) is dict:
426 for k
,v
in var
.items():
427 if type(v
) is datetime
.datetime
:
428 var
[k
]= v
.strftime('%Y-%m-%dT%H:%M:%S')
429 elif type(v
) is dict or type(v
) is list or type(v
) is tuple:
430 convert_datetime2str(v
)
431 if len(var
) == 0: return True
432 elif type(var
) is list or type(var
) is tuple:
434 convert_datetime2str(v
)
436 def check_valid_tenant(my
, tenant_id
):
439 return HTTP_Unauthorized
, "Needed admin privileges"
441 result
, _
= my
.db
.get_table(FROM
='tenants', SELECT
=('uuid',), WHERE
={'uuid': tenant_id
})
443 return HTTP_Not_Found
, "tenant '%s' not found" % tenant_id
446 def check_valid_uuid(uuid
):
447 id_schema
= {"type" : "string", "pattern": "^[a-fA-F0-9]{8}(-[a-fA-F0-9]{4}){3}-[a-fA-F0-9]{12}$"}
449 js_v(uuid
, id_schema
)
451 except js_e
.ValidationError
:
465 e
={"error":{"code":error
.status_code
, "type":error
.status
, "description":error
.body
}}
468 @bottle.hook('after_request')
470 #TODO: Alf: Is it needed??
471 bottle
.response
.headers
['Access-Control-Allow-Origin'] = '*'
477 @bottle.route(url_base
+ '/hosts', method
='GET')
478 def http_get_hosts():
479 select_
,where_
,limit_
= filter_query_string(bottle
.request
.query
, http2db_host
,
480 ('id','name','description','status','admin_state_up') )
482 myself
= config_dic
['http_threads'][ threading
.current_thread().name
]
483 result
, content
= myself
.db
.get_table(FROM
='hosts', SELECT
=select_
, WHERE
=where_
, LIMIT
=limit_
)
485 print "http_get_hosts Error", content
486 bottle
.abort(-result
, content
)
488 convert_boolean(content
, ('admin_state_up',) )
489 change_keys_http2db(content
, http2db_host
, reverse
=True)
491 row
['links'] = ( {'href': myself
.url_preffix
+ '/hosts/' + str(row
['id']), 'rel': 'bookmark'}, )
492 data
={'hosts' : content
}
493 return format_out(data
)
495 @bottle.route(url_base
+ '/hosts/<host_id>', method
='GET')
496 def http_get_host_id(host_id
):
497 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
498 return my
.gethost(host_id
)
500 @bottle.route(url_base
+ '/hosts', method
='POST')
501 def http_post_hosts():
502 '''insert a host into the database. All resources are got and inserted'''
503 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
506 bottle
.abort(HTTP_Unauthorized
, "Needed admin privileges")
509 http_content
= format_in( host_new_schema
)
510 r
= remove_extra_items(http_content
, host_new_schema
)
511 if r
is not None: print "http_post_host_id: Warning: remove extra items ", r
512 change_keys_http2db(http_content
['host'], http2db_host
)
514 host
= http_content
['host']
516 if 'host-data' in http_content
:
517 host
.update(http_content
['host-data'])
518 ip_name
=http_content
['host-data']['ip_name']
519 user
=http_content
['host-data']['user']
520 password
=http_content
['host-data'].get('password', None)
522 ip_name
=host
['ip_name']
524 password
=host
.get('password', None)
527 rad
= RADclass
.RADclass()
528 (return_status
, code
) = rad
.obtain_RAD(user
, password
, ip_name
)
531 if not return_status
:
532 print 'http_post_hosts ERROR obtaining RAD', code
533 bottle
.abort(HTTP_Bad_Request
, code
)
536 rad_structure
= yaml
.load(rad
.to_text())
537 print 'rad_structure\n---------------------'
538 print json
.dumps(rad_structure
, indent
=4)
539 print '---------------------'
541 WHERE_
={"family":rad_structure
['processor']['family'], 'manufacturer':rad_structure
['processor']['manufacturer'], 'version':rad_structure
['processor']['version']}
542 result
, content
= my
.db
.get_table(FROM
='host_ranking',
546 host
['ranking'] = content
[0]['ranking']
548 #error_text= "Host " + str(WHERE_)+ " not found in ranking table. Not valid for VIM management"
549 #bottle.abort(HTTP_Bad_Request, error_text)
551 warning_text
+= "Host " + str(WHERE_
)+ " not found in ranking table. Assuming lowest value 100\n"
552 host
['ranking'] = 100 #TODO: as not used in this version, set the lowest value
554 features
= rad_structure
['processor'].get('features', ())
555 host
['features'] = ",".join(features
)
558 for node
in (rad_structure
['resource topology']['nodes'] or {}).itervalues():
563 for core
in node
['cpu']['eligible_cores']:
564 eligible_cores
.extend(core
)
565 for core
in node
['cpu']['cores']:
566 for thread_id
in core
:
567 c
={'core_id': count
, 'thread_id': thread_id
}
568 if thread_id
not in eligible_cores
: c
['status'] = 'noteligible'
573 for port_k
, port_v
in node
['nics']['nic 0']['ports'].iteritems():
574 if port_v
['virtual']:
578 for port_k2
, port_v2
in node
['nics']['nic 0']['ports'].iteritems():
579 if port_v2
['virtual'] and port_v2
['PF_pci_id']==port_k
:
580 sriovs
.append({'pci':port_k2
, 'mac':port_v2
['mac'], 'source_name':port_v2
['source_name']})
582 #sort sriov according to pci and rename them to the vf number
583 new_sriovs
= sorted(sriovs
, key
=lambda k
: k
['pci'])
585 for sriov
in new_sriovs
:
586 sriov
['source_name'] = index
588 interfaces
.append ({'pci':str(port_k
), 'Mbps': port_v
['speed']/1000000, 'sriovs': new_sriovs
, 'mac':port_v
['mac'], 'source_name':port_v
['source_name']})
589 #@TODO LA memoria devuelta por el RAD es incorrecta, almenos para IVY1, NFV100
590 memory
=node
['memory']['node_size'] / (1024*1024*1024)
591 #memory=get_next_2pow(node['memory']['hugepage_nr'])
592 host
['numas'].append( {'numa_socket': node
['id'], 'hugepages': node
['memory']['hugepage_nr'], 'memory':memory
, 'interfaces': interfaces
, 'cores': cores
} )
593 print json
.dumps(host
, indent
=4)
597 result
, content
= my
.db
.new_host(host
)
599 if content
['admin_state_up']:
601 host_test_mode
= True if config_dic
['mode']=='test' or config_dic
['mode']=="OF only" else False
602 host_develop_mode
= True if config_dic
['mode']=='development' else False
603 host_develop_bridge_iface
= config_dic
.get('development_bridge', None)
604 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'],
605 test
=host_test_mode
, image_path
=config_dic
['image_path'],
606 version
=config_dic
['version'], host_id
=content
['uuid'],
607 develop_mode
=host_develop_mode
, develop_bridge_iface
=host_develop_bridge_iface
)
609 config_dic
['host_threads'][ content
['uuid'] ] = thread
612 change_keys_http2db(content
, http2db_host
, reverse
=True)
613 if len(warning_text
)>0:
614 content
["warning"]= warning_text
615 data
={'host' : content
}
616 return format_out(data
)
618 bottle
.abort(HTTP_Bad_Request
, content
)
621 @bottle.route(url_base
+ '/hosts/<host_id>', method
='PUT')
622 def http_put_host_id(host_id
):
623 '''modify a host into the database. All resources are got and inserted'''
624 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
627 bottle
.abort(HTTP_Unauthorized
, "Needed admin privileges")
630 http_content
= format_in( host_edit_schema
)
631 r
= remove_extra_items(http_content
, host_edit_schema
)
632 if r
is not None: print "http_post_host_id: Warning: remove extra items ", r
633 change_keys_http2db(http_content
['host'], http2db_host
)
636 result
, content
= my
.db
.edit_host(host_id
, http_content
['host'])
638 convert_boolean(content
, ('admin_state_up',) )
639 change_keys_http2db(content
, http2db_host
, reverse
=True)
640 data
={'host' : content
}
643 config_dic
['host_threads'][host_id
].name
= content
.get('name',content
['ip_name'])
644 config_dic
['host_threads'][host_id
].user
= content
['user']
645 config_dic
['host_threads'][host_id
].host
= content
['ip_name']
646 config_dic
['host_threads'][host_id
].insert_task("reload")
649 return format_out(data
)
651 bottle
.abort(HTTP_Bad_Request
, content
)
656 @bottle.route(url_base
+ '/hosts/<host_id>', method
='DELETE')
657 def http_delete_host_id(host_id
):
658 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
661 bottle
.abort(HTTP_Unauthorized
, "Needed admin privileges")
662 result
, content
= my
.db
.delete_row('hosts', host_id
)
664 bottle
.abort(HTTP_Not_Found
, content
)
667 if host_id
in config_dic
['host_threads']:
668 config_dic
['host_threads'][host_id
].insert_task("exit")
670 data
={'result' : content
}
671 return format_out(data
)
673 print "http_delete_host_id error",result
, content
674 bottle
.abort(-result
, content
)
683 @bottle.route(url_base
+ '/tenants', method
='GET')
684 def http_get_tenants():
685 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
686 select_
,where_
,limit_
= filter_query_string(bottle
.request
.query
, http2db_tenant
,
687 ('id','name','description','enabled') )
688 result
, content
= my
.db
.get_table(FROM
='tenants', SELECT
=select_
,WHERE
=where_
,LIMIT
=limit_
)
690 print "http_get_tenants Error", content
691 bottle
.abort(-result
, content
)
693 change_keys_http2db(content
, http2db_tenant
, reverse
=True)
694 convert_boolean(content
, ('enabled',))
695 data
={'tenants' : content
}
696 #data['tenants_links'] = dict([('tenant', row['id']) for row in content])
697 return format_out(data
)
699 @bottle.route(url_base
+ '/tenants/<tenant_id>', method
='GET')
700 def http_get_tenant_id(tenant_id
):
701 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
702 result
, content
= my
.db
.get_table(FROM
='tenants', SELECT
=('uuid','name','description', 'enabled'),WHERE
={'uuid': tenant_id
} )
704 print "http_get_tenant_id error %d %s" % (result
, content
)
705 bottle
.abort(-result
, content
)
707 print "http_get_tenant_id tenant '%s' not found" % tenant_id
708 bottle
.abort(HTTP_Not_Found
, "tenant %s not found" % tenant_id
)
710 change_keys_http2db(content
, http2db_tenant
, reverse
=True)
711 convert_boolean(content
, ('enabled',))
712 data
={'tenant' : content
[0]}
713 #data['tenants_links'] = dict([('tenant', row['id']) for row in content])
714 return format_out(data
)
717 @bottle.route(url_base
+ '/tenants', method
='POST')
718 def http_post_tenants():
719 '''insert a tenant into the database.'''
720 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
722 http_content
= format_in( tenant_new_schema
)
723 r
= remove_extra_items(http_content
, tenant_new_schema
)
724 if r
is not None: print "http_post_tenants: Warning: remove extra items ", r
725 change_keys_http2db(http_content
['tenant'], http2db_tenant
)
728 result
, content
= my
.db
.new_tenant(http_content
['tenant'])
731 return http_get_tenant_id(content
)
733 bottle
.abort(-result
, content
)
736 @bottle.route(url_base
+ '/tenants/<tenant_id>', method
='PUT')
737 def http_put_tenant_id(tenant_id
):
738 '''update a tenant into the database.'''
739 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
741 http_content
= format_in( tenant_edit_schema
)
742 r
= remove_extra_items(http_content
, tenant_edit_schema
)
743 if r
is not None: print "http_put_tenant_id: Warning: remove extra items ", r
744 change_keys_http2db(http_content
['tenant'], http2db_tenant
)
747 result
, content
= my
.db
.update_rows('tenants', http_content
['tenant'], WHERE
={'uuid': tenant_id
}, log
=True )
749 return http_get_tenant_id(tenant_id
)
751 bottle
.abort(-result
, content
)
754 @bottle.route(url_base
+ '/tenants/<tenant_id>', method
='DELETE')
755 def http_delete_tenant_id(tenant_id
):
756 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
758 r
, tenants_flavors
= my
.db
.get_table(FROM
='tenants_flavors', SELECT
=('flavor_id','tenant_id'), WHERE
={'tenant_id': tenant_id
})
761 r
, tenants_images
= my
.db
.get_table(FROM
='tenants_images', SELECT
=('image_id','tenant_id'), WHERE
={'tenant_id': tenant_id
})
764 result
, content
= my
.db
.delete_row('tenants', tenant_id
)
766 bottle
.abort(HTTP_Not_Found
, content
)
768 print "alf", tenants_flavors
, tenants_images
769 for flavor
in tenants_flavors
:
770 my
.db
.delete_row_by_key("flavors", "uuid", flavor
['flavor_id'])
771 for image
in tenants_images
:
772 my
.db
.delete_row_by_key("images", "uuid", image
['image_id'])
773 data
={'result' : content
}
774 return format_out(data
)
776 print "http_delete_tenant_id error",result
, content
777 bottle
.abort(-result
, content
)
784 @bottle.route(url_base
+ '/<tenant_id>/flavors', method
='GET')
785 def http_get_flavors(tenant_id
):
786 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
787 #check valid tenant_id
788 result
,content
= check_valid_tenant(my
, tenant_id
)
790 bottle
.abort(result
, content
)
792 select_
,where_
,limit_
= filter_query_string(bottle
.request
.query
, http2db_flavor
,
793 ('id','name','description','public') )
797 from_
='tenants_flavors inner join flavors on tenants_flavors.flavor_id=flavors.uuid'
798 where_
['tenant_id'] = tenant_id
799 result
, content
= my
.db
.get_table(FROM
=from_
, SELECT
=select_
, WHERE
=where_
, LIMIT
=limit_
)
801 print "http_get_flavors Error", content
802 bottle
.abort(-result
, content
)
804 change_keys_http2db(content
, http2db_flavor
, reverse
=True)
806 row
['links']=[ {'href': "/".join( (my
.url_preffix
, tenant_id
, 'flavors', str(row
['id']) ) ), 'rel':'bookmark' } ]
807 data
={'flavors' : content
}
808 return format_out(data
)
810 @bottle.route(url_base
+ '/<tenant_id>/flavors/<flavor_id>', method
='GET')
811 def http_get_flavor_id(tenant_id
, flavor_id
):
812 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
813 #check valid tenant_id
814 result
,content
= check_valid_tenant(my
, tenant_id
)
816 bottle
.abort(result
, content
)
818 select_
,where_
,limit_
= filter_query_string(bottle
.request
.query
, http2db_flavor
,
819 ('id','name','description','ram', 'vcpus', 'extended', 'disk', 'public') )
823 from_
='tenants_flavors as tf inner join flavors as f on tf.flavor_id=f.uuid'
824 where_
['tenant_id'] = tenant_id
825 where_
['uuid'] = flavor_id
826 result
, content
= my
.db
.get_table(SELECT
=select_
, FROM
=from_
, WHERE
=where_
, LIMIT
=limit_
)
829 print "http_get_flavor_id error %d %s" % (result
, content
)
830 bottle
.abort(-result
, content
)
832 print "http_get_flavors_id flavor '%s' not found" % str(flavor_id
)
833 bottle
.abort(HTTP_Not_Found
, 'flavor %s not found' % flavor_id
)
835 change_keys_http2db(content
, http2db_flavor
, reverse
=True)
836 if 'extended' in content
[0] and content
[0]['extended'] is not None:
837 extended
= json
.loads(content
[0]['extended'])
838 if 'devices' in extended
:
839 change_keys_http2db(extended
['devices'], http2db_flavor
, reverse
=True)
840 content
[0]['extended']=extended
841 convert_bandwidth(content
[0], reverse
=True)
842 content
[0]['links']=[ {'href': "/".join( (my
.url_preffix
, tenant_id
, 'flavors', str(content
[0]['id']) ) ), 'rel':'bookmark' } ]
843 data
={'flavor' : content
[0]}
844 #data['tenants_links'] = dict([('tenant', row['id']) for row in content])
845 return format_out(data
)
848 @bottle.route(url_base
+ '/<tenant_id>/flavors', method
='POST')
849 def http_post_flavors(tenant_id
):
850 '''insert a flavor into the database, and attach to tenant.'''
851 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
852 #check valid tenant_id
853 result
,content
= check_valid_tenant(my
, tenant_id
)
855 bottle
.abort(result
, content
)
856 http_content
= format_in( flavor_new_schema
)
857 r
= remove_extra_items(http_content
, flavor_new_schema
)
858 if r
is not None: print "http_post_flavors: Warning: remove extra items ", r
859 change_keys_http2db(http_content
['flavor'], http2db_flavor
)
860 extended_dict
= http_content
['flavor'].pop('extended', None)
861 if extended_dict
is not None:
862 result
, content
= check_extended(extended_dict
)
864 print "http_post_flavors wrong input extended error %d %s" % (result
, content
)
865 bottle
.abort(-result
, content
)
867 convert_bandwidth(extended_dict
)
868 if 'devices' in extended_dict
: change_keys_http2db(extended_dict
['devices'], http2db_flavor
)
869 http_content
['flavor']['extended'] = json
.dumps(extended_dict
)
871 result
, content
= my
.db
.new_flavor(http_content
['flavor'], tenant_id
)
873 return http_get_flavor_id(tenant_id
, content
)
875 print "http_psot_flavors error %d %s" % (result
, content
)
876 bottle
.abort(-result
, content
)
879 @bottle.route(url_base
+ '/<tenant_id>/flavors/<flavor_id>', method
='DELETE')
880 def http_delete_flavor_id(tenant_id
, flavor_id
):
881 '''Deletes the flavor_id of a tenant. IT removes from tenants_flavors table.'''
882 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
883 #check valid tenant_id
884 result
,content
= check_valid_tenant(my
, tenant_id
)
886 bottle
.abort(result
, content
)
888 result
, content
= my
.db
.delete_image_flavor('flavor', flavor_id
, tenant_id
)
890 bottle
.abort(HTTP_Not_Found
, content
)
892 data
={'result' : content
}
893 return format_out(data
)
895 print "http_delete_flavor_id error",result
, content
896 bottle
.abort(-result
, content
)
899 @bottle.route(url_base
+ '/<tenant_id>/flavors/<flavor_id>/<action>', method
='POST')
900 def http_attach_detach_flavors(tenant_id
, flavor_id
, action
):
901 '''attach/detach an existing flavor in this tenant. That is insert/remove at tenants_flavors table.'''
902 #TODO alf: not tested at all!!!
903 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
904 #check valid tenant_id
905 result
,content
= check_valid_tenant(my
, tenant_id
)
907 bottle
.abort(result
, content
)
909 bottle
.abort(HTTP_Bad_Request
, "Invalid tenant 'any' with this command")
911 if action
!='attach' and action
!= 'detach':
912 bottle
.abort(HTTP_Method_Not_Allowed
, "actions can be attach or detach")
915 #Ensure that flavor exist
916 from_
='tenants_flavors as tf right join flavors as f on tf.flavor_id=f.uuid'
917 where_
={'uuid': flavor_id
}
918 result
, content
= my
.db
.get_table(SELECT
=('public','tenant_id'), FROM
=from_
, WHERE
=where_
)
921 text_error
="Flavor '%s' not found" % flavor_id
923 text_error
="Flavor '%s' not found for tenant '%s'" % (flavor_id
, tenant_id
)
924 bottle
.abort(HTTP_Not_Found
, text_error
)
929 if flavor
['tenant_id']!=None:
930 bottle
.abort(HTTP_Conflict
, "Flavor '%s' already attached to tenant '%s'" % (flavor_id
, tenant_id
))
931 if flavor
['public']=='no' and not my
.admin
:
932 #allow only attaching public flavors
933 bottle
.abort(HTTP_Unauthorized
, "Needed admin rights to attach a private flavor")
936 result
, content
= my
.db
.new_row('tenants_flavors', {'flavor_id':flavor_id
, 'tenant_id': tenant_id
})
938 return http_get_flavor_id(tenant_id
, flavor_id
)
940 if flavor
['tenant_id']==None:
941 bottle
.abort(HTTP_Not_Found
, "Flavor '%s' not attached to tenant '%s'" % (flavor_id
, tenant_id
))
942 result
, content
= my
.db
.delete_row_by_dict(FROM
='tenants_flavors', WHERE
={'flavor_id':flavor_id
, 'tenant_id':tenant_id
})
944 if flavor
['public']=='no':
945 #try to delete the flavor completely to avoid orphan flavors, IGNORE error
946 my
.db
.delete_row_by_dict(FROM
='flavors', WHERE
={'uuid':flavor_id
})
947 data
={'result' : "flavor detached"}
948 return format_out(data
)
950 #if get here is because an error
951 print "http_attach_detach_flavors error %d %s" % (result
, content
)
952 bottle
.abort(-result
, content
)
955 @bottle.route(url_base
+ '/<tenant_id>/flavors/<flavor_id>', method
='PUT')
956 def http_put_flavor_id(tenant_id
, flavor_id
):
957 '''update a flavor_id into the database.'''
958 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
959 #check valid tenant_id
960 result
,content
= check_valid_tenant(my
, tenant_id
)
962 bottle
.abort(result
, content
)
964 http_content
= format_in( flavor_update_schema
)
965 r
= remove_extra_items(http_content
, flavor_update_schema
)
966 if r
is not None: print "http_put_flavor_id: Warning: remove extra items ", r
967 change_keys_http2db(http_content
['flavor'], http2db_flavor
)
968 extended_dict
= http_content
['flavor'].pop('extended', None)
969 if extended_dict
is not None:
970 result
, content
= check_extended(extended_dict
)
972 print "http_put_flavor_id wrong input extended error %d %s" % (result
, content
)
973 bottle
.abort(-result
, content
)
975 convert_bandwidth(extended_dict
)
976 if 'devices' in extended_dict
: change_keys_http2db(extended_dict
['devices'], http2db_flavor
)
977 http_content
['flavor']['extended'] = json
.dumps(extended_dict
)
978 #Ensure that flavor exist
979 where_
={'uuid': flavor_id
}
983 from_
='tenants_flavors as ti inner join flavors as i on ti.flavor_id=i.uuid'
984 where_
['tenant_id'] = tenant_id
985 result
, content
= my
.db
.get_table(SELECT
=('public',), FROM
=from_
, WHERE
=where_
)
987 text_error
="Flavor '%s' not found" % flavor_id
989 text_error
+=" for tenant '%s'" % flavor_id
990 bottle
.abort(HTTP_Not_Found
, text_error
)
993 if content
[0]['public']=='yes' and not my
.admin
:
994 #allow only modifications over private flavors
995 bottle
.abort(HTTP_Unauthorized
, "Needed admin rights to edit a public flavor")
998 result
, content
= my
.db
.update_rows('flavors', http_content
['flavor'], {'uuid': flavor_id
})
1001 print "http_put_flavor_id error %d %s" % (result
, content
)
1002 bottle
.abort(-result
, content
)
1005 return http_get_flavor_id(tenant_id
, flavor_id
)
1013 @bottle.route(url_base
+ '/<tenant_id>/images', method
='GET')
1014 def http_get_images(tenant_id
):
1015 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
1016 #check valid tenant_id
1017 result
,content
= check_valid_tenant(my
, tenant_id
)
1019 bottle
.abort(result
, content
)
1021 select_
,where_
,limit_
= filter_query_string(bottle
.request
.query
, http2db_image
,
1022 ('id','name','description','path','public') )
1023 if tenant_id
=='any':
1026 from_
='tenants_images inner join images on tenants_images.image_id=images.uuid'
1027 where_
['tenant_id'] = tenant_id
1028 result
, content
= my
.db
.get_table(SELECT
=select_
, FROM
=from_
, WHERE
=where_
, LIMIT
=limit_
)
1030 print "http_get_images Error", content
1031 bottle
.abort(-result
, content
)
1033 change_keys_http2db(content
, http2db_image
, reverse
=True)
1034 #for row in content: row['links']=[ {'href': "/".join( (my.url_preffix, tenant_id, 'images', str(row['id']) ) ), 'rel':'bookmark' } ]
1035 data
={'images' : content
}
1036 return format_out(data
)
1038 @bottle.route(url_base
+ '/<tenant_id>/images/<image_id>', method
='GET')
1039 def http_get_image_id(tenant_id
, image_id
):
1040 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
1041 #check valid tenant_id
1042 result
,content
= check_valid_tenant(my
, tenant_id
)
1044 bottle
.abort(result
, content
)
1046 select_
,where_
,limit_
= filter_query_string(bottle
.request
.query
, http2db_image
,
1047 ('id','name','description','progress', 'status','path', 'created', 'updated','public') )
1048 if tenant_id
=='any':
1051 from_
='tenants_images as ti inner join images as i on ti.image_id=i.uuid'
1052 where_
['tenant_id'] = tenant_id
1053 where_
['uuid'] = image_id
1054 result
, content
= my
.db
.get_table(SELECT
=select_
, FROM
=from_
, WHERE
=where_
, LIMIT
=limit_
)
1057 print "http_get_images error %d %s" % (result
, content
)
1058 bottle
.abort(-result
, content
)
1060 print "http_get_images image '%s' not found" % str(image_id
)
1061 bottle
.abort(HTTP_Not_Found
, 'image %s not found' % image_id
)
1063 convert_datetime2str(content
)
1064 change_keys_http2db(content
, http2db_image
, reverse
=True)
1065 if 'metadata' in content
[0] and content
[0]['metadata'] is not None:
1066 metadata
= json
.loads(content
[0]['metadata'])
1067 content
[0]['metadata']=metadata
1068 content
[0]['links']=[ {'href': "/".join( (my
.url_preffix
, tenant_id
, 'images', str(content
[0]['id']) ) ), 'rel':'bookmark' } ]
1069 data
={'image' : content
[0]}
1070 #data['tenants_links'] = dict([('tenant', row['id']) for row in content])
1071 return format_out(data
)
1073 @bottle.route(url_base
+ '/<tenant_id>/images', method
='POST')
1074 def http_post_images(tenant_id
):
1075 '''insert a image into the database, and attach to tenant.'''
1076 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
1077 #check valid tenant_id
1078 result
,content
= check_valid_tenant(my
, tenant_id
)
1080 bottle
.abort(result
, content
)
1081 http_content
= format_in(image_new_schema
)
1082 r
= remove_extra_items(http_content
, image_new_schema
)
1083 if r
is not None: print "http_post_images: Warning: remove extra items ", r
1084 change_keys_http2db(http_content
['image'], http2db_image
)
1085 metadata_dict
= http_content
['image'].pop('metadata', None)
1086 if metadata_dict
is not None:
1087 http_content
['image']['metadata'] = json
.dumps(metadata_dict
)
1089 host_test_mode
= True if config_dic
['mode']=='test' or config_dic
['mode']=="OF only" else False
1091 image_file
= http_content
['image'].get('path',None)
1092 if os
.path
.exists(image_file
):
1093 http_content
['image']['checksum'] = md5(image_file
)
1095 if not host_test_mode
:
1096 content
= "Image file not found"
1097 print "http_post_images error: %d %s" % (HTTP_Bad_Request
, content
)
1098 bottle
.abort(HTTP_Bad_Request
, content
)
1099 except Exception as e
:
1100 print "ERROR. Unexpected exception: %s" % (str(e
))
1101 bottle
.abort(HTTP_Internal_Server_Error
, type(e
).__name
__ + ": " + str(e
))
1102 #insert in data base
1103 result
, content
= my
.db
.new_image(http_content
['image'], tenant_id
)
1105 return http_get_image_id(tenant_id
, content
)
1107 print "http_post_images error %d %s" % (result
, content
)
1108 bottle
.abort(-result
, content
)
1111 @bottle.route(url_base
+ '/<tenant_id>/images/<image_id>', method
='DELETE')
1112 def http_delete_image_id(tenant_id
, image_id
):
1113 '''Deletes the image_id of a tenant. IT removes from tenants_images table.'''
1114 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
1115 #check valid tenant_id
1116 result
,content
= check_valid_tenant(my
, tenant_id
)
1118 bottle
.abort(result
, content
)
1119 result
, content
= my
.db
.delete_image_flavor('image', image_id
, tenant_id
)
1121 bottle
.abort(HTTP_Not_Found
, content
)
1123 data
={'result' : content
}
1124 return format_out(data
)
1126 print "http_delete_image_id error",result
, content
1127 bottle
.abort(-result
, content
)
1130 @bottle.route(url_base
+ '/<tenant_id>/images/<image_id>/<action>', method
='POST')
1131 def http_attach_detach_images(tenant_id
, image_id
, action
):
1132 '''attach/detach an existing image in this tenant. That is insert/remove at tenants_images table.'''
1133 #TODO alf: not tested at all!!!
1134 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
1135 #check valid tenant_id
1136 result
,content
= check_valid_tenant(my
, tenant_id
)
1138 bottle
.abort(result
, content
)
1139 if tenant_id
=='any':
1140 bottle
.abort(HTTP_Bad_Request
, "Invalid tenant 'any' with this command")
1142 if action
!='attach' and action
!= 'detach':
1143 bottle
.abort(HTTP_Method_Not_Allowed
, "actions can be attach or detach")
1146 #Ensure that image exist
1147 from_
='tenants_images as ti right join images as i on ti.image_id=i.uuid'
1148 where_
={'uuid': image_id
}
1149 result
, content
= my
.db
.get_table(SELECT
=('public','tenant_id'), FROM
=from_
, WHERE
=where_
)
1151 if action
=='attach':
1152 text_error
="Image '%s' not found" % image_id
1154 text_error
="Image '%s' not found for tenant '%s'" % (image_id
, tenant_id
)
1155 bottle
.abort(HTTP_Not_Found
, text_error
)
1159 if action
=='attach':
1160 if image
['tenant_id']!=None:
1161 bottle
.abort(HTTP_Conflict
, "Image '%s' already attached to tenant '%s'" % (image_id
, tenant_id
))
1162 if image
['public']=='no' and not my
.admin
:
1163 #allow only attaching public images
1164 bottle
.abort(HTTP_Unauthorized
, "Needed admin rights to attach a private image")
1166 #insert in data base
1167 result
, content
= my
.db
.new_row('tenants_images', {'image_id':image_id
, 'tenant_id': tenant_id
})
1169 return http_get_image_id(tenant_id
, image_id
)
1171 if image
['tenant_id']==None:
1172 bottle
.abort(HTTP_Not_Found
, "Image '%s' not attached to tenant '%s'" % (image_id
, tenant_id
))
1173 result
, content
= my
.db
.delete_row_by_dict(FROM
='tenants_images', WHERE
={'image_id':image_id
, 'tenant_id':tenant_id
})
1175 if image
['public']=='no':
1176 #try to delete the image completely to avoid orphan images, IGNORE error
1177 my
.db
.delete_row_by_dict(FROM
='images', WHERE
={'uuid':image_id
})
1178 data
={'result' : "image detached"}
1179 return format_out(data
)
1181 #if get here is because an error
1182 print "http_attach_detach_images error %d %s" % (result
, content
)
1183 bottle
.abort(-result
, content
)
1186 @bottle.route(url_base
+ '/<tenant_id>/images/<image_id>', method
='PUT')
1187 def http_put_image_id(tenant_id
, image_id
):
1188 '''update a image_id into the database.'''
1189 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
1190 #check valid tenant_id
1191 result
,content
= check_valid_tenant(my
, tenant_id
)
1193 bottle
.abort(result
, content
)
1195 http_content
= format_in( image_update_schema
)
1196 r
= remove_extra_items(http_content
, image_update_schema
)
1197 if r
is not None: print "http_put_image_id: Warning: remove extra items ", r
1198 change_keys_http2db(http_content
['image'], http2db_image
)
1199 metadata_dict
= http_content
['image'].pop('metadata', None)
1200 if metadata_dict
is not None:
1201 http_content
['image']['metadata'] = json
.dumps(metadata_dict
)
1202 #Ensure that image exist
1203 where_
={'uuid': image_id
}
1204 if tenant_id
=='any':
1207 from_
='tenants_images as ti inner join images as i on ti.image_id=i.uuid'
1208 where_
['tenant_id'] = tenant_id
1209 result
, content
= my
.db
.get_table(SELECT
=('public',), FROM
=from_
, WHERE
=where_
)
1211 text_error
="Image '%s' not found" % image_id
1212 if tenant_id
!='any':
1213 text_error
+=" for tenant '%s'" % image_id
1214 bottle
.abort(HTTP_Not_Found
, text_error
)
1217 if content
[0]['public']=='yes' and not my
.admin
:
1218 #allow only modifications over private images
1219 bottle
.abort(HTTP_Unauthorized
, "Needed admin rights to edit a public image")
1221 #insert in data base
1222 result
, content
= my
.db
.update_rows('images', http_content
['image'], {'uuid': image_id
})
1225 print "http_put_image_id error %d %s" % (result
, content
)
1226 bottle
.abort(-result
, content
)
1229 return http_get_image_id(tenant_id
, image_id
)
1236 @bottle.route(url_base
+ '/<tenant_id>/servers', method
='GET')
1237 def http_get_servers(tenant_id
):
1238 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
1239 result
,content
= check_valid_tenant(my
, tenant_id
)
1241 bottle
.abort(result
, content
)
1244 select_
,where_
,limit_
= filter_query_string(bottle
.request
.query
, http2db_server
,
1245 ('id','name','description','hostId','imageRef','flavorRef','status', 'tenant_id') )
1246 if tenant_id
!='any':
1247 where_
['tenant_id'] = tenant_id
1248 result
, content
= my
.db
.get_table(SELECT
=select_
, FROM
='instances', WHERE
=where_
, LIMIT
=limit_
)
1250 print "http_get_servers Error", content
1251 bottle
.abort(-result
, content
)
1253 change_keys_http2db(content
, http2db_server
, reverse
=True)
1255 tenant_id
= row
.pop('tenant_id')
1256 row
['links']=[ {'href': "/".join( (my
.url_preffix
, tenant_id
, 'servers', str(row
['id']) ) ), 'rel':'bookmark' } ]
1257 data
={'servers' : content
}
1258 return format_out(data
)
1260 @bottle.route(url_base
+ '/<tenant_id>/servers/<server_id>', method
='GET')
1261 def http_get_server_id(tenant_id
, server_id
):
1262 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
1263 #check valid tenant_id
1264 result
,content
= check_valid_tenant(my
, tenant_id
)
1266 bottle
.abort(result
, content
)
1269 result
, content
= my
.db
.get_instance(server_id
)
1271 bottle
.abort(HTTP_Not_Found
, content
)
1273 #change image/flavor-id to id and link
1274 convert_bandwidth(content
, reverse
=True)
1275 convert_datetime2str(content
)
1276 if content
["ram"]==0 : del content
["ram"]
1277 if content
["vcpus"]==0 : del content
["vcpus"]
1278 if 'flavor_id' in content
:
1279 if content
['flavor_id'] is not None:
1280 content
['flavor'] = {'id':content
['flavor_id'],
1281 'links':[{'href': "/".join( (my
.url_preffix
, content
['tenant_id'], 'flavors', str(content
['flavor_id']) ) ), 'rel':'bookmark'}]
1283 del content
['flavor_id']
1284 if 'image_id' in content
:
1285 if content
['image_id'] is not None:
1286 content
['image'] = {'id':content
['image_id'],
1287 'links':[{'href': "/".join( (my
.url_preffix
, content
['tenant_id'], 'images', str(content
['image_id']) ) ), 'rel':'bookmark'}]
1289 del content
['image_id']
1290 change_keys_http2db(content
, http2db_server
, reverse
=True)
1291 if 'extended' in content
:
1292 if 'devices' in content
['extended']: change_keys_http2db(content
['extended']['devices'], http2db_server
, reverse
=True)
1294 data
={'server' : content
}
1295 return format_out(data
)
1297 bottle
.abort(-result
, content
)
1300 @bottle.route(url_base
+ '/<tenant_id>/servers', method
='POST')
1301 def http_post_server_id(tenant_id
):
1302 '''deploys a new server'''
1303 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
1304 #check valid tenant_id
1305 result
,content
= check_valid_tenant(my
, tenant_id
)
1307 bottle
.abort(result
, content
)
1309 if tenant_id
=='any':
1310 bottle
.abort(HTTP_Bad_Request
, "Invalid tenant 'any' with this command")
1312 http_content
= format_in( server_new_schema
)
1313 r
= remove_extra_items(http_content
, server_new_schema
)
1314 if r
is not None: print "http_post_serves: Warning: remove extra items ", r
1315 change_keys_http2db(http_content
['server'], http2db_server
)
1316 extended_dict
= http_content
['server'].get('extended', None)
1317 if extended_dict
is not None:
1318 result
, content
= check_extended(extended_dict
, True)
1320 print "http_post_servers wrong input extended error %d %s" % (result
, content
)
1321 bottle
.abort(-result
, content
)
1323 convert_bandwidth(extended_dict
)
1324 if 'devices' in extended_dict
: change_keys_http2db(extended_dict
['devices'], http2db_server
)
1326 server
= http_content
['server']
1327 server_start
= server
.get('start', 'yes')
1328 server
['tenant_id'] = tenant_id
1329 #check flavor valid and take info
1330 result
, content
= my
.db
.get_table(FROM
='tenants_flavors as tf join flavors as f on tf.flavor_id=f.uuid',
1331 SELECT
=('ram','vcpus','extended'), WHERE
={'uuid':server
['flavor_id'], 'tenant_id':tenant_id
})
1333 bottle
.abort(HTTP_Not_Found
, 'flavor_id %s not found' % server
['flavor_id'])
1335 server
['flavor']=content
[0]
1336 #check image valid and take info
1337 result
, content
= my
.db
.get_table(FROM
='tenants_images as ti join images as i on ti.image_id=i.uuid',
1338 SELECT
=('path','metadata'), WHERE
={'uuid':server
['image_id'], 'tenant_id':tenant_id
, "status":"ACTIVE"})
1340 bottle
.abort(HTTP_Not_Found
, 'image_id %s not found or not ACTIVE' % server
['image_id'])
1342 server
['image']=content
[0]
1343 if "hosts_id" in server
:
1344 result
, content
= my
.db
.get_table(FROM
='hosts', SELECT
=('uuid',), WHERE
={'uuid': server
['host_id']})
1346 bottle
.abort(HTTP_Not_Found
, 'hostId %s not found' % server
['host_id'])
1348 #print json.dumps(server, indent=4)
1350 result
, content
= ht
.create_server(server
, config_dic
['db'], config_dic
['db_lock'], config_dic
['mode']=='normal')
1353 #Insert instance to database
1356 print "inserting at DB"
1358 if server_start
== 'no':
1359 content
['status'] = 'INACTIVE'
1361 new_instance_result
, new_instance
= my
.db
.new_instance(content
, nets
, ports_to_free
)
1362 if new_instance_result
< 0:
1363 print "Error http_post_servers() :", new_instance_result
, new_instance
1364 bottle
.abort(-new_instance_result
, new_instance
)
1367 print "inserted at DB"
1369 for port
in ports_to_free
:
1370 r
,c
= config_dic
['host_threads'][ server
['host_id'] ].insert_task( 'restore-iface',*port
)
1372 print ' http_post_servers ERROR RESTORE IFACE!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!' + c
1375 r
,c
= config_dic
['of_thread'].insert_task("update-net", net
)
1377 print ':http_post_servers ERROR UPDATING NETS !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!' + c
1381 #look for dhcp ip address
1382 r2
, c2
= my
.db
.get_table(FROM
="ports", SELECT
=["mac", "net_id"], WHERE
={"instance_id": new_instance
})
1383 if r2
>0 and config_dic
.get("dhcp_server"):
1385 if iface
["net_id"] in config_dic
["dhcp_nets"]:
1386 #print "dhcp insert add task"
1387 r
,c
= config_dic
['dhcp_thread'].insert_task("add", iface
["mac"])
1389 print ':http_post_servers ERROR UPDATING dhcp_server !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!' + c
1393 server
['uuid'] = new_instance
1394 #server_start = server.get('start', 'yes')
1395 if server_start
!= 'no':
1396 server
['paused'] = True if server_start
== 'paused' else False
1397 server
['action'] = {"start":None}
1398 server
['status'] = "CREATING"
1400 r
,c
= config_dic
['host_threads'][ server
['host_id'] ].insert_task( 'instance',server
)
1402 my
.db
.update_rows('instances', {'status':"ERROR"}, {'uuid':server
['uuid'], 'last_error':c
}, log
=True)
1404 return http_get_server_id(tenant_id
, new_instance
)
1406 bottle
.abort(HTTP_Bad_Request
, content
)
1409 def http_server_action(server_id
, tenant_id
, action
):
1410 '''Perform actions over a server as resume, reboot, terminate, ...'''
1411 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
1412 server
={"uuid": server_id
, "action":action
}
1413 where
={'uuid': server_id
}
1414 if tenant_id
!='any':
1415 where
['tenant_id']= tenant_id
1416 result
, content
= my
.db
.get_table(FROM
='instances', WHERE
=where
)
1418 bottle
.abort(HTTP_Not_Found
, "server %s not found" % server_id
)
1421 print "http_post_server_action error getting data %d %s" % (result
, content
)
1422 bottle
.abort(HTTP_Internal_Server_Error
, content
)
1424 server
.update(content
[0])
1425 tenant_id
= server
["tenant_id"]
1427 #TODO check a right content
1429 if 'terminate' in action
:
1430 new_status
='DELETING'
1431 elif server
['status'] == 'ERROR': #or server['status'] == 'CREATING':
1432 if 'terminate' not in action
and 'rebuild' not in action
:
1433 bottle
.abort(HTTP_Method_Not_Allowed
, "Server is in ERROR status, must be rebuit or deleted ")
1435 # elif server['status'] == 'INACTIVE':
1436 # if 'start' not in action and 'createImage' not in action:
1437 # bottle.abort(HTTP_Method_Not_Allowed, "The only possible action over an instance in 'INACTIVE' status is 'start'")
1439 # if 'start' in action:
1440 # new_status='CREATING'
1441 # server['paused']='no'
1442 # elif server['status'] == 'PAUSED':
1443 # if 'resume' not in action:
1444 # bottle.abort(HTTP_Method_Not_Allowed, "The only possible action over an instance in 'PAUSED' status is 'resume'")
1446 # elif server['status'] == 'ACTIVE':
1447 # if 'pause' not in action and 'reboot'not in action and 'shutoff'not in action:
1448 # bottle.abort(HTTP_Method_Not_Allowed, "The only possible action over an instance in 'ACTIVE' status is 'pause','reboot' or 'shutoff'")
1451 if 'start' in action
or 'createImage' in action
or 'rebuild' in action
:
1452 #check image valid and take info
1453 image_id
= server
['image_id']
1454 if 'createImage' in action
:
1455 if 'imageRef' in action
['createImage']:
1456 image_id
= action
['createImage']['imageRef']
1457 elif 'disk' in action
['createImage']:
1458 result
, content
= my
.db
.get_table(FROM
='instance_devices',
1459 SELECT
=('image_id','dev'), WHERE
={'instance_id':server
['uuid'],"type":"disk"})
1461 bottle
.abort(HTTP_Not_Found
, 'disk not found for server')
1465 if action
['createImage']['imageRef']['disk'] != None:
1466 for disk
in content
:
1467 if disk
['dev'] == action
['createImage']['imageRef']['disk']:
1468 disk_id
= disk
['image_id']
1471 bottle
.abort(HTTP_Not_Found
, 'disk %s not found for server' % action
['createImage']['imageRef']['disk'])
1474 bottle
.abort(HTTP_Not_Found
, 'more than one disk found for server' )
1478 image_id
= content
[0]['image_id']
1480 result
, content
= my
.db
.get_table(FROM
='tenants_images as ti join images as i on ti.image_id=i.uuid',
1481 SELECT
=('path','metadata'), WHERE
={'uuid':image_id
, 'tenant_id':tenant_id
, "status":"ACTIVE"})
1483 bottle
.abort(HTTP_Not_Found
, 'image_id %s not found or not ACTIVE' % image_id
)
1485 if content
[0]['metadata'] is not None:
1487 metadata
= json
.loads(content
[0]['metadata'])
1489 return -HTTP_Internal_Server_Error
, "Can not decode image metadata"
1490 content
[0]['metadata']=metadata
1492 content
[0]['metadata'] = {}
1493 server
['image']=content
[0]
1494 if 'createImage' in action
:
1495 action
['createImage']['source'] = {'image_id': image_id
, 'path': content
[0]['path']}
1496 if 'createImage' in action
:
1497 #Create an entry in Database for the new image
1498 new_image
={'status':'BUILD', 'progress': 0 }
1499 new_image_metadata
=content
[0]
1500 if 'metadata' in server
['image'] and server
['image']['metadata'] != None:
1501 new_image_metadata
.update(server
['image']['metadata'])
1502 new_image_metadata
= {"use_incremental":"no"}
1503 if 'metadata' in action
['createImage']:
1504 new_image_metadata
.update(action
['createImage']['metadata'])
1505 new_image
['metadata'] = json
.dumps(new_image_metadata
)
1506 new_image
['name'] = action
['createImage'].get('name', None)
1507 new_image
['description'] = action
['createImage'].get('description', None)
1508 new_image
['uuid']=my
.db
.new_uuid()
1509 if 'path' in action
['createImage']:
1510 new_image
['path'] = action
['createImage']['path']
1512 new_image
['path']="/provisional/path/" + new_image
['uuid']
1513 result
, image_uuid
= my
.db
.new_image(new_image
, tenant_id
)
1515 bottle
.abort(HTTP_Bad_Request
, 'Error: ' + image_uuid
)
1517 server
['new_image'] = new_image
1521 r
,c
= config_dic
['host_threads'][ server
['host_id'] ].insert_task( 'instance',server
)
1523 print "Task queue full at host ", server
['host_id']
1524 bottle
.abort(HTTP_Request_Timeout
, c
)
1525 if 'createImage' in action
and result
>= 0:
1526 return http_get_image_id(tenant_id
, image_uuid
)
1528 #Update DB only for CREATING or DELETING status
1529 data
={'result' : 'in process'}
1530 if new_status
!= None and new_status
== 'DELETING':
1533 #look for dhcp ip address
1534 r2
, c2
= my
.db
.get_table(FROM
="ports", SELECT
=["mac", "net_id"], WHERE
={"instance_id": server_id
})
1535 r
,c
= my
.db
.delete_instance(server_id
, tenant_id
, nets
, ports_to_free
, "requested by http")
1536 for port
in ports_to_free
:
1537 r1
,c1
= config_dic
['host_threads'][ server
['host_id'] ].insert_task( 'restore-iface',*port
)
1539 print ' http_post_server_action error at server deletion ERROR resore-iface !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!' + c1
1540 data
={'result' : 'deleting in process, but ifaces cannot be restored!!!!!'}
1542 r1
,c1
= config_dic
['of_thread'].insert_task("update-net", net
)
1544 print ' http_post_server_action error at server deletion ERROR UPDATING NETS !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!' + c1
1545 data
={'result' : 'deleting in process, but openflow rules cannot be deleted!!!!!'}
1546 #look for dhcp ip address
1547 if r2
>0 and config_dic
.get("dhcp_server"):
1549 if iface
["net_id"] in config_dic
["dhcp_nets"]:
1550 r
,c
= config_dic
['dhcp_thread'].insert_task("del", iface
["mac"])
1551 #print "dhcp insert del task"
1553 print ':http_post_servers ERROR UPDATING dhcp_server !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!' + c
1555 return format_out(data
)
1559 @bottle.route(url_base
+ '/<tenant_id>/servers/<server_id>', method
='DELETE')
1560 def http_delete_server_id(tenant_id
, server_id
):
1561 '''delete a server'''
1562 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
1563 #check valid tenant_id
1564 result
,content
= check_valid_tenant(my
, tenant_id
)
1566 bottle
.abort(result
, content
)
1569 return http_server_action(server_id
, tenant_id
, {"terminate":None} )
1572 @bottle.route(url_base
+ '/<tenant_id>/servers/<server_id>/action', method
='POST')
1573 def http_post_server_action(tenant_id
, server_id
):
1574 '''take an action over a server'''
1575 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
1576 #check valid tenant_id
1577 result
,content
= check_valid_tenant(my
, tenant_id
)
1579 bottle
.abort(result
, content
)
1581 http_content
= format_in( server_action_schema
)
1582 #r = remove_extra_items(http_content, server_action_schema)
1583 #if r is not None: print "http_post_server_action: Warning: remove extra items ", r
1585 return http_server_action(server_id
, tenant_id
, http_content
)
1592 @bottle.route(url_base
+ '/networks', method
='GET')
1593 def http_get_networks():
1594 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
1596 select_
,where_
,limit_
= filter_query_string(bottle
.request
.query
, http2db_network
,
1597 ('id','name','tenant_id','type',
1598 'shared','provider:vlan','status','last_error','admin_state_up','provider:physical') )
1599 #TODO temporally remove tenant_id
1600 if "tenant_id" in where_
:
1601 del where_
["tenant_id"]
1602 result
, content
= my
.db
.get_table(SELECT
=select_
, FROM
='nets', WHERE
=where_
, LIMIT
=limit_
)
1604 print "http_get_networks error %d %s" % (result
, content
)
1605 bottle
.abort(-result
, content
)
1607 convert_boolean(content
, ('shared', 'admin_state_up', 'enable_dhcp') )
1608 delete_nulls(content
)
1609 change_keys_http2db(content
, http2db_network
, reverse
=True)
1610 data
={'networks' : content
}
1611 return format_out(data
)
1613 @bottle.route(url_base
+ '/networks/<network_id>', method
='GET')
1614 def http_get_network_id(network_id
):
1615 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
1617 where_
= bottle
.request
.query
1618 where_
['uuid'] = network_id
1619 result
, content
= my
.db
.get_table(FROM
='nets', WHERE
=where_
, LIMIT
=100)
1622 print "http_get_networks_id error %d %s" % (result
, content
)
1623 bottle
.abort(-result
, content
)
1625 print "http_get_networks_id network '%s' not found" % network_id
1626 bottle
.abort(HTTP_Not_Found
, 'network %s not found' % network_id
)
1628 convert_boolean(content
, ('shared', 'admin_state_up', 'enale_dhcp') )
1629 change_keys_http2db(content
, http2db_network
, reverse
=True)
1631 result
, ports
= my
.db
.get_table(FROM
='ports', SELECT
=('uuid as port_id',),
1632 WHERE
={'net_id': network_id
}, LIMIT
=100)
1634 content
[0]['ports'] = ports
1635 delete_nulls(content
[0])
1636 data
={'network' : content
[0]}
1637 return format_out(data
)
1639 @bottle.route(url_base
+ '/networks', method
='POST')
1640 def http_post_networks():
1641 '''insert a network into the database.'''
1642 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
1644 http_content
= format_in( network_new_schema
)
1645 r
= remove_extra_items(http_content
, network_new_schema
)
1646 if r
is not None: print "http_post_networks: Warning: remove extra items ", r
1647 change_keys_http2db(http_content
['network'], http2db_network
)
1648 network
=http_content
['network']
1649 #check valid tenant_id
1650 tenant_id
= network
.get('tenant_id')
1652 result
, _
= my
.db
.get_table(FROM
='tenants', SELECT
=('uuid',), WHERE
={'uuid': tenant_id
,"enabled":True})
1654 bottle
.abort(HTTP_Not_Found
, 'tenant %s not found or not enabled' % tenant_id
)
1658 net_provider
= network
.get('provider')
1659 net_type
= network
.get('type')
1660 net_vlan
= network
.get("vlan")
1661 net_bind_net
= network
.get("bind_net")
1662 net_bind_type
= network
.get("bind_type")
1663 name
= network
["name"]
1665 #check if network name ends with :<vlan_tag> and network exist in order to make and automated bindning
1666 vlan_index
=name
.rfind(":")
1667 if net_bind_net
==None and net_bind_type
==None and vlan_index
> 1:
1669 vlan_tag
= int(name
[vlan_index
+1:])
1670 if vlan_tag
>0 and vlan_tag
< 4096:
1671 net_bind_net
= name
[:vlan_index
]
1672 net_bind_type
= "vlan:" + name
[vlan_index
+1:]
1676 if net_bind_net
!= None:
1677 #look for a valid net
1678 if check_valid_uuid(net_bind_net
):
1679 net_bind_key
= "uuid"
1681 net_bind_key
= "name"
1682 result
, content
= my
.db
.get_table(FROM
='nets', WHERE
={net_bind_key
: net_bind_net
} )
1684 bottle
.abort(HTTP_Internal_Server_Error
, 'getting nets from db ' + content
)
1687 bottle
.abort(HTTP_Bad_Request
, "bind_net %s '%s'not found" % (net_bind_key
, net_bind_net
) )
1690 bottle
.abort(HTTP_Bad_Request
, "more than one bind_net %s '%s' found, use uuid" % (net_bind_key
, net_bind_net
) )
1692 network
["bind_net"] = content
[0]["uuid"]
1693 if net_bind_type
!= None:
1694 if net_bind_type
[0:5] != "vlan:":
1695 bottle
.abort(HTTP_Bad_Request
, "bad format for 'bind_type', must be 'vlan:<tag>'")
1697 if int(net_bind_type
[5:]) > 4095 or int(net_bind_type
[5:])<=0 :
1698 bottle
.abort(HTTP_Bad_Request
, "bad format for 'bind_type', must be 'vlan:<tag>' with a tag between 1 and 4095")
1700 network
["bind_type"] = net_bind_type
1702 if net_provider
!=None:
1703 if net_provider
[:9]=="openflow:":
1705 if net_type
!="ptp" and net_type
!="data":
1706 bottle
.abort(HTTP_Bad_Request
, "Only 'ptp' or 'data' net types can be bound to 'openflow'")
1711 if net_type
!="bridge_man" and net_type
!="bridge_data":
1712 bottle
.abort(HTTP_Bad_Request
, "Only 'bridge_man' or 'bridge_data' net types can be bound to 'bridge', 'macvtap' or 'default")
1714 net_type
='bridge_man'
1717 net_type
='bridge_man'
1719 if net_provider
!= None:
1720 if net_provider
[:7]=='bridge:':
1721 #check it is one of the pre-provisioned bridges
1722 bridge_net_name
= net_provider
[7:]
1723 for brnet
in config_dic
['bridge_nets']:
1724 if brnet
[0]==bridge_net_name
: # free
1725 if brnet
[3] != None:
1726 bottle
.abort(HTTP_Conflict
, "invalid 'provider:physical', bridge '%s' is already used" % bridge_net_name
)
1731 # if bridge_net==None:
1732 # 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)
1734 elif net_type
=='bridge_data' or net_type
=='bridge_man':
1735 #look for a free precreated nets
1736 for brnet
in config_dic
['bridge_nets']:
1737 if brnet
[3]==None: # free
1738 if bridge_net
!= None:
1739 if net_type
=='bridge_man': #look for the smaller speed
1740 if brnet
[2] < bridge_net
[2]: bridge_net
= brnet
1741 else: #look for the larger speed
1742 if brnet
[2] > bridge_net
[2]: bridge_net
= brnet
1746 if bridge_net
==None:
1747 bottle
.abort(HTTP_Bad_Request
, "Max limits of bridge networks reached. Future versions of VIM will overcome this limit")
1750 print "using net", bridge_net
1751 net_provider
= "bridge:"+bridge_net
[0]
1752 net_vlan
= bridge_net
[1]
1753 if net_vlan
==None and (net_type
=="data" or net_type
=="ptp"):
1754 net_vlan
= my
.db
.get_free_net_vlan()
1756 bottle
.abort(HTTP_Internal_Server_Error
, "Error getting an available vlan")
1759 network
['provider'] = net_provider
1760 network
['type'] = net_type
1761 network
['vlan'] = net_vlan
1762 result
, content
= my
.db
.new_row('nets', network
, True, True)
1765 if bridge_net
!=None:
1766 bridge_net
[3] = content
1768 if config_dic
.get("dhcp_server"):
1769 if network
["name"] in config_dic
["dhcp_server"].get("nets", () ):
1770 config_dic
["dhcp_nets"].append(content
)
1771 print "dhcp_server: add new net", content
1772 elif bridge_net
!= None and bridge_net
[0] in config_dic
["dhcp_server"].get("bridge_ifaces", () ):
1773 config_dic
["dhcp_nets"].append(content
)
1774 print "dhcp_server: add new net", content
1775 return http_get_network_id(content
)
1777 print "http_post_networks error %d %s" % (result
, content
)
1778 bottle
.abort(-result
, content
)
1782 @bottle.route(url_base
+ '/networks/<network_id>', method
='PUT')
1783 def http_put_network_id(network_id
):
1784 '''update a network_id into the database.'''
1785 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
1787 http_content
= format_in( network_update_schema
)
1788 r
= remove_extra_items(http_content
, network_update_schema
)
1789 change_keys_http2db(http_content
['network'], http2db_network
)
1790 network
=http_content
['network']
1792 #Look for the previous data
1793 where_
= {'uuid': network_id
}
1794 result
, network_old
= my
.db
.get_table(FROM
='nets', WHERE
=where_
)
1796 print "http_put_network_id error %d %s" % (result
, network_old
)
1797 bottle
.abort(-result
, network_old
)
1800 print "http_put_network_id network '%s' not found" % network_id
1801 bottle
.abort(HTTP_Not_Found
, 'network %s not found' % network_id
)
1804 nbports
, content
= my
.db
.get_table(FROM
='ports', SELECT
=('uuid as port_id',),
1805 WHERE
={'net_id': network_id
}, LIMIT
=100)
1807 print "http_put_network_id error %d %s" % (result
, network_old
)
1808 bottle
.abort(-result
, content
)
1811 if 'type' in network
and network
['type'] != network_old
[0]['type']:
1812 bottle
.abort(HTTP_Method_Not_Allowed
, "Can not change type of network while having ports attached")
1813 if 'vlan' in network
and network
['vlan'] != network_old
[0]['vlan']:
1814 bottle
.abort(HTTP_Method_Not_Allowed
, "Can not change vlan of network while having ports attached")
1817 net_provider
= network
.get('provider', network_old
[0]['provider'])
1818 net_type
= network
.get('type', network_old
[0]['type'])
1819 net_bind_net
= network
.get("bind_net")
1820 net_bind_type
= network
.get("bind_type")
1821 if net_bind_net
!= None:
1822 #look for a valid net
1823 if check_valid_uuid(net_bind_net
):
1824 net_bind_key
= "uuid"
1826 net_bind_key
= "name"
1827 result
, content
= my
.db
.get_table(FROM
='nets', WHERE
={net_bind_key
: net_bind_net
} )
1829 bottle
.abort(HTTP_Internal_Server_Error
, 'getting nets from db ' + content
)
1832 bottle
.abort(HTTP_Bad_Request
, "bind_net %s '%s'not found" % (net_bind_key
, net_bind_net
) )
1835 bottle
.abort(HTTP_Bad_Request
, "more than one bind_net %s '%s' found, use uuid" % (net_bind_key
, net_bind_net
) )
1837 network
["bind_net"] = content
[0]["uuid"]
1838 if net_bind_type
!= None:
1839 if net_bind_type
[0:5] != "vlan:":
1840 bottle
.abort(HTTP_Bad_Request
, "bad format for 'bind_type', must be 'vlan:<tag>'")
1842 if int(net_bind_type
[5:]) > 4095 or int(net_bind_type
[5:])<=0 :
1843 bottle
.abort(HTTP_Bad_Request
, "bad format for 'bind_type', must be 'vlan:<tag>' with a tag between 1 and 4095")
1845 if net_provider
!=None:
1846 if net_provider
[:9]=="openflow:":
1847 if net_type
!="ptp" and net_type
!="data":
1848 bottle
.abort(HTTP_Bad_Request
, "Only 'ptp' or 'data' net types can be bound to 'openflow'")
1850 if net_type
!="bridge_man" and net_type
!="bridge_data":
1851 bottle
.abort(HTTP_Bad_Request
, "Only 'bridge_man' or 'bridge_data' net types can be bound to 'bridge', 'macvtap' or 'default")
1853 #insert in data base
1854 result
, content
= my
.db
.update_rows('nets', network
, WHERE
={'uuid': network_id
}, log
=True )
1856 if result
>0: # and nbports>0 and 'admin_state_up' in network and network['admin_state_up'] != network_old[0]['admin_state_up']:
1857 r
,c
= config_dic
['of_thread'].insert_task("update-net", network_id
)
1859 print "http_put_network_id error while launching openflow rules"
1860 bottle
.abort(HTTP_Internal_Server_Error
, c
)
1861 if config_dic
.get("dhcp_server"):
1862 if network_id
in config_dic
["dhcp_nets"]:
1863 config_dic
["dhcp_nets"].remove(network_id
)
1864 print "dhcp_server: delete net", network_id
1865 if network
.get("name", network_old
["name"]) in config_dic
["dhcp_server"].get("nets", () ):
1866 config_dic
["dhcp_nets"].append(network_id
)
1867 print "dhcp_server: add new net", network_id
1869 net_bind
= network
.get("bind", network_old
["bind"] )
1870 if net_bind
and net_bind
[:7]=="bridge:" and net_bind
[7:] in config_dic
["dhcp_server"].get("bridge_ifaces", () ):
1871 config_dic
["dhcp_nets"].append(network_id
)
1872 print "dhcp_server: add new net", network_id
1873 return http_get_network_id(network_id
)
1875 bottle
.abort(-result
, content
)
1879 @bottle.route(url_base
+ '/networks/<network_id>', method
='DELETE')
1880 def http_delete_network_id(network_id
):
1881 '''delete a network_id from the database.'''
1882 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
1884 #delete from the data base
1885 result
, content
= my
.db
.delete_row('nets', network_id
)
1888 bottle
.abort(HTTP_Not_Found
, content
)
1890 for brnet
in config_dic
['bridge_nets']:
1891 if brnet
[3]==network_id
:
1894 if config_dic
.get("dhcp_server") and network_id
in config_dic
["dhcp_nets"]:
1895 config_dic
["dhcp_nets"].remove(network_id
)
1896 print "dhcp_server: delete net", network_id
1897 data
={'result' : content
}
1898 return format_out(data
)
1900 print "http_delete_network_id error",result
, content
1901 bottle
.abort(-result
, content
)
1906 @bottle.route(url_base
+ '/networks/<network_id>/openflow', method
='GET')
1907 def http_get_openflow_id(network_id
):
1908 '''To obtain the list of openflow rules of a network
1910 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
1912 if network_id
=='all':
1915 where_
={"net_id": network_id
}
1916 result
, content
= my
.db
.get_table(SELECT
=("name","net_id","priority","vlan_id","ingress_port","src_mac","dst_mac","actions"),
1917 WHERE
=where_
, FROM
='of_flows')
1919 bottle
.abort(-result
, content
)
1921 data
={'openflow-rules' : content
}
1922 return format_out(data
)
1924 @bottle.route(url_base
+ '/networks/<network_id>/openflow', method
='PUT')
1925 def http_put_openflow_id(network_id
):
1926 '''To make actions over the net. The action is to reinstall the openflow rules
1927 network_id can be 'all'
1929 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
1931 bottle
.abort(HTTP_Unauthorized
, "Needed admin privileges")
1934 if network_id
=='all':
1937 where_
={"uuid": network_id
}
1938 result
, content
= my
.db
.get_table(SELECT
=("uuid","type"), WHERE
=where_
, FROM
='nets')
1940 bottle
.abort(-result
, content
)
1944 if net
["type"]!="ptp" and net
["type"]!="data":
1947 r
,c
= config_dic
['of_thread'].insert_task("update-net", net
['uuid'])
1949 print "http_put_openflow_id error while launching openflow rules"
1950 bottle
.abort(HTTP_Internal_Server_Error
, c
)
1951 data
={'result' : str(result
)+" nets updates"}
1952 return format_out(data
)
1954 @bottle.route(url_base
+ '/networks/openflow/clear', method
='DELETE')
1955 @bottle.route(url_base
+ '/networks/clear/openflow', method
='DELETE')
1956 def http_clear_openflow_rules():
1957 '''To make actions over the net. The action is to delete ALL openflow rules
1959 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
1961 bottle
.abort(HTTP_Unauthorized
, "Needed admin privileges")
1964 r
,c
= config_dic
['of_thread'].insert_task("clear-all")
1966 print "http_delete_openflow_id error while launching openflow rules"
1967 bottle
.abort(HTTP_Internal_Server_Error
, c
)
1970 data
={'result' : " Clearing openflow rules in process"}
1971 return format_out(data
)
1973 @bottle.route(url_base
+ '/networks/openflow/ports', method
='GET')
1974 def http_get_openflow_ports():
1975 '''Obtain switch ports names of openflow controller
1977 data
={'ports' : config_dic
['of_thread'].OF_connector
.pp2ofi
}
1978 return format_out(data
)
1985 @bottle.route(url_base
+ '/ports', method
='GET')
1986 def http_get_ports():
1988 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
1989 select_
,where_
,limit_
= filter_query_string(bottle
.request
.query
, http2db_port
,
1990 ('id','name','tenant_id','network_id','vpci','mac_address','device_owner','device_id',
1991 'binding:switch_port','binding:vlan','bandwidth','status','admin_state_up','ip_address') )
1992 #result, content = my.db.get_ports(where_)
1993 result
, content
= my
.db
.get_table(SELECT
=select_
, WHERE
=where_
, FROM
='ports',LIMIT
=limit_
)
1995 print "http_get_ports Error", result
, content
1996 bottle
.abort(-result
, content
)
1999 convert_boolean(content
, ('admin_state_up',) )
2000 delete_nulls(content
)
2001 change_keys_http2db(content
, http2db_port
, reverse
=True)
2002 data
={'ports' : content
}
2003 return format_out(data
)
2005 @bottle.route(url_base
+ '/ports/<port_id>', method
='GET')
2006 def http_get_port_id(port_id
):
2007 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
2009 result
, content
= my
.db
.get_table(WHERE
={'uuid': port_id
}, FROM
='ports')
2011 print "http_get_ports error", result
, content
2012 bottle
.abort(-result
, content
)
2014 print "http_get_ports port '%s' not found" % str(port_id
)
2015 bottle
.abort(HTTP_Not_Found
, 'port %s not found' % port_id
)
2017 convert_boolean(content
, ('admin_state_up',) )
2018 delete_nulls(content
)
2019 change_keys_http2db(content
, http2db_port
, reverse
=True)
2020 data
={'port' : content
[0]}
2021 return format_out(data
)
2024 @bottle.route(url_base
+ '/ports', method
='POST')
2025 def http_post_ports():
2026 '''insert an external port into the database.'''
2027 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
2029 bottle
.abort(HTTP_Unauthorized
, "Needed admin privileges")
2031 http_content
= format_in( port_new_schema
)
2032 r
= remove_extra_items(http_content
, port_new_schema
)
2033 if r
is not None: print "http_post_ports: Warning: remove extra items ", r
2034 change_keys_http2db(http_content
['port'], http2db_port
)
2035 port
=http_content
['port']
2037 port
['type'] = 'external'
2038 if 'net_id' in port
and port
['net_id'] == None:
2041 if 'net_id' in port
:
2042 #check that new net has the correct type
2043 result
, new_net
= my
.db
.check_target_net(port
['net_id'], None, 'external' )
2045 bottle
.abort(HTTP_Bad_Request
, new_net
)
2047 #insert in data base
2048 result
, uuid
= my
.db
.new_row('ports', port
, True, True)
2050 if 'net_id' in port
:
2051 r
,c
= config_dic
['of_thread'].insert_task("update-net", port
['net_id'])
2053 print "http_post_ports error while launching openflow rules"
2054 bottle
.abort(HTTP_Internal_Server_Error
, c
)
2055 return http_get_port_id(uuid
)
2057 bottle
.abort(-result
, uuid
)
2060 @bottle.route(url_base
+ '/ports/<port_id>', method
='PUT')
2061 def http_put_port_id(port_id
):
2062 '''update a port_id into the database.'''
2064 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
2066 http_content
= format_in( port_update_schema
)
2067 change_keys_http2db(http_content
['port'], http2db_port
)
2068 port_dict
=http_content
['port']
2070 #Look for the previous port data
2071 where_
= {'uuid': port_id
}
2072 result
, content
= my
.db
.get_table(FROM
="ports",WHERE
=where_
)
2074 print "http_put_port_id error", result
, content
2075 bottle
.abort(-result
, content
)
2078 print "http_put_port_id port '%s' not found" % port_id
2079 bottle
.abort(HTTP_Not_Found
, 'port %s not found' % port_id
)
2082 for k
in ('vlan','switch_port','mac_address', 'tenant_id'):
2083 if k
in port_dict
and not my
.admin
:
2084 bottle
.abort(HTTP_Unauthorized
, "Needed admin privileges for changing " + k
)
2088 #change_keys_http2db(port, http2db_port, reverse=True)
2092 if 'net_id' in port_dict
:
2094 old_net
= port
.get('net_id', None)
2095 new_net
= port_dict
['net_id']
2096 if old_net
!= new_net
:
2098 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
2099 if old_net
is not None: nets
.append(old_net
)
2100 if port
['type'] == 'instance:bridge':
2101 bottle
.abort(HTTP_Forbidden
, "bridge interfaces cannot be attached to a different net")
2103 elif port
['type'] == 'external':
2105 bottle
.abort(HTTP_Unauthorized
, "Needed admin privileges")
2109 #check that new net has the correct type
2110 result
, new_net_dict
= my
.db
.check_target_net(new_net
, None, port
['type'] )
2112 #change VLAN for SR-IOV ports
2113 if result
>=0 and port
["type"]=="instance:data" and port
["model"]=="VF": #TODO consider also VFnotShared
2115 port_dict
["vlan"] = None
2117 port_dict
["vlan"] = new_net_dict
["vlan"]
2118 #get host where this VM is allocated
2119 result
, content
= my
.db
.get_table(FROM
="instances",WHERE
={"uuid":port
["instance_id"]})
2121 print "http_put_port_id database error", content
2123 host_id
= content
[0]["host_id"]
2125 #insert in data base
2127 result
, content
= my
.db
.update_rows('ports', port_dict
, WHERE
={'uuid': port_id
}, log
=False )
2129 #Insert task to complete actions
2132 r
,v
= config_dic
['of_thread'].insert_task("update-net", net_id
)
2133 if r
<0: print "Error ********* http_put_port_id update_of_flows: ", v
2134 #TODO Do something if fails
2136 config_dic
['host_threads'][host_id
].insert_task("edit-iface", port_id
, old_net
, new_net
)
2139 return http_get_port_id(port_id
)
2141 bottle
.abort(HTTP_Bad_Request
, content
)
2145 @bottle.route(url_base
+ '/ports/<port_id>', method
='DELETE')
2146 def http_delete_port_id(port_id
):
2147 '''delete a port_id from the database.'''
2148 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
2150 bottle
.abort(HTTP_Unauthorized
, "Needed admin privileges")
2153 #Look for the previous port data
2154 where_
= {'uuid': port_id
, "type": "external"}
2155 result
, ports
= my
.db
.get_table(WHERE
=where_
, FROM
='ports',LIMIT
=100)
2158 print "http_delete_port_id port '%s' not found" % port_id
2159 bottle
.abort(HTTP_Not_Found
, 'port %s not found or device_owner is not external' % port_id
)
2161 #delete from the data base
2162 result
, content
= my
.db
.delete_row('ports', port_id
)
2165 bottle
.abort(HTTP_Not_Found
, content
)
2167 network
= ports
[0].get('net_id', None)
2168 if network
is not None:
2170 r
,c
= config_dic
['of_thread'].insert_task("update-net", network
)
2171 if r
<0: print "!!!!!! http_delete_port_id update_of_flows error", r
, c
2172 data
={'result' : content
}
2173 return format_out(data
)
2175 print "http_delete_port_id error",result
, content
2176 bottle
.abort(-result
, content
)