1 # -*- coding: utf-8 -*-
4 # Copyright 2015 Telefónica Investigación y Desarrollo, S.A.U.
5 # This file is part of openvim
8 # Licensed under the Apache License, Version 2.0 (the "License"); you may
9 # not use this file except in compliance with the License. You may obtain
10 # a copy of the License at
12 # http://www.apache.org/licenses/LICENSE-2.0
14 # Unless required by applicable law or agreed to in writing, software
15 # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
16 # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
17 # License for the specific language governing permissions and limitations
20 # For those usages not covered by the Apache License, Version 2.0 please
21 # contact with: nfvlabs@tid.es
25 This is the thread for the http server North API.
26 Two thread will be launched, with normal and administrative permissions.
29 __author__
="Alfonso Tierno"
30 __date__
="$10-jul-2014 12:07:15$"
41 from jsonschema
import validate
as js_v
, exceptions
as js_e
42 import host_thread
as ht
43 from vim_schema
import host_new_schema
, host_edit_schema
, tenant_new_schema
, \
45 flavor_new_schema
, flavor_update_schema
, \
46 image_new_schema
, image_update_schema
, \
47 server_new_schema
, server_action_schema
, network_new_schema
, network_update_schema
, \
48 port_new_schema
, port_update_schema
56 HTTP_Bad_Request
= 400
57 HTTP_Unauthorized
= 401
60 HTTP_Method_Not_Allowed
= 405
61 HTTP_Not_Acceptable
= 406
62 HTTP_Request_Timeout
= 408
64 HTTP_Service_Unavailable
= 503
65 HTTP_Internal_Server_Error
= 500
68 hash_md5
= hashlib
.md5()
69 with
open(fname
, "rb") as f
:
70 for chunk
in iter(lambda: f
.read(4096), b
""):
71 hash_md5
.update(chunk
)
72 return hash_md5
.hexdigest()
74 def check_extended(extended
, allow_net_attach
=False):
75 '''Makes and extra checking of extended input that cannot be done using jsonschema
77 allow_net_attach: for allowing or not the uuid field at interfaces
78 that are allowed for instance, but not for flavors
79 Return: (<0, error_text) if error; (0,None) if not error '''
80 if "numas" not in extended
: return 0, None
83 for numa
in extended
["numas"]:
87 if "cores-id" in numa
:
88 if len(numa
["cores-id"]) != numa
["cores"]:
89 return -HTTP_Bad_Request
, "different number of cores-id (%d) than cores (%d) at numa %d" % (len(numa
["cores-id"]), numa
["cores"],numaid
)
90 id_s
.extend(numa
["cores-id"])
93 if "threads-id" in numa
:
94 if len(numa
["threads-id"]) != numa
["threads"]:
95 return -HTTP_Bad_Request
, "different number of threads-id (%d) than threads (%d) at numa %d" % (len(numa
["threads-id"]), numa
["threads"],numaid
)
96 id_s
.extend(numa
["threads-id"])
97 if "paired-threads" in numa
:
99 if "paired-threads-id" in numa
:
100 if len(numa
["paired-threads-id"]) != numa
["paired-threads"]:
101 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
)
102 for pair
in numa
["paired-threads-id"]:
104 return -HTTP_Bad_Request
, "paired-threads-id must contain a list of two elements list at numa %d" % (numaid
)
107 return -HTTP_Service_Unavailable
, "only one of cores, threads, paired-threads are allowed in this version at numa %d" % numaid
109 if "interfaces" in numa
:
113 for interface
in numa
["interfaces"]:
114 if "uuid" in interface
and not allow_net_attach
:
115 return -HTTP_Bad_Request
, "uuid field is not allowed at numa %d interface %s position %d" % (numaid
, interface
.get("name",""), ifaceid
)
116 if "mac_address" in interface
and interface
["dedicated"]=="yes":
117 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
)
118 if "name" in interface
:
119 if interface
["name"] in names
:
120 return -HTTP_Bad_Request
, "name repeated at numa %d, interface %s position %d" % (numaid
, interface
.get("name",""), ifaceid
)
121 names
.append(interface
["name"])
122 if "vpci" in interface
:
123 if interface
["vpci"] in vpcis
:
124 return -HTTP_Bad_Request
, "vpci %s repeated at numa %d, interface %s position %d" % (interface
["vpci"], numaid
, interface
.get("name",""), ifaceid
)
125 vpcis
.append(interface
["vpci"])
129 return -HTTP_Service_Unavailable
, "only one numa can be defined in this version "
130 for a
in range(0,len(id_s
)):
132 return -HTTP_Bad_Request
, "core/thread identifiers must start at 0 and gaps are not alloed. Missing id number %d" % a
137 # dictionaries that change from HTTP API to database naming
139 http2db_host
={'id':'uuid'}
140 http2db_tenant
={'id':'uuid'}
141 http2db_flavor
={'id':'uuid','imageRef':'image_id'}
142 http2db_image
={'id':'uuid', 'created':'created_at', 'updated':'modified_at', 'public': 'public'}
143 http2db_server
={'id':'uuid','hostId':'host_id','flavorRef':'flavor_id','imageRef':'image_id','created':'created_at'}
144 http2db_network
={'id':'uuid','provider:vlan':'vlan', 'provider:physical': 'provider'}
145 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'}
147 def remove_extra_items(data
, schema
):
149 if type(data
) is tuple or type(data
) is list:
151 a
= remove_extra_items(d
, schema
['items'])
152 if a
is not None: deleted
.append(a
)
153 elif type(data
) is dict:
154 for k
in data
.keys():
155 if 'properties' not in schema
or k
not in schema
['properties'].keys():
159 a
= remove_extra_items(data
[k
], schema
['properties'][k
])
160 if a
is not None: deleted
.append({k
:a
})
161 if len(deleted
) == 0: return None
162 elif len(deleted
) == 1: return deleted
[0]
165 def delete_nulls(var
):
166 if type(var
) is dict:
168 if var
[k
] is None: del var
[k
]
169 elif type(var
[k
]) is dict or type(var
[k
]) is list or type(var
[k
]) is tuple:
170 if delete_nulls(var
[k
]): del var
[k
]
171 if len(var
) == 0: return True
172 elif type(var
) is list or type(var
) is tuple:
174 if type(k
) is dict: delete_nulls(k
)
175 if len(var
) == 0: return True
179 class httpserver(threading
.Thread
):
180 def __init__(self
, db_conn
, name
="http", host
='localhost', port
=8080, admin
=False, config_
=None):
182 Creates a new thread to attend the http connections
184 db_conn: database connection
185 name: name of this thread
186 host: ip or name where to listen
187 port: port where to listen
188 admin: if this has privileges of administrator or not
189 config_: unless the first thread must be provided. It is a global dictionary where to allocate the self variable
195 if config_
is not None:
197 if 'http_threads' not in config_dic
:
198 config_dic
['http_threads'] = {}
199 threading
.Thread
.__init
__(self
)
204 if name
in config_dic
:
205 print "httpserver Warning!!! Onether thread with the same name", name
207 while name
+str(n
) in config_dic
:
211 self
.url_preffix
= 'http://' + self
.host
+ ':' + str(self
.port
) + url_base
212 config_dic
['http_threads'][name
] = self
214 #Ensure that when the main program exits the thread will also exit
219 bottle
.run(host
=self
.host
, port
=self
.port
, debug
=True) #quiet=True
221 def gethost(self
, host_id
):
222 result
, content
= self
.db
.get_host(host_id
)
224 print "httpserver.gethost error %d %s" % (result
, content
)
225 bottle
.abort(-result
, content
)
227 print "httpserver.gethost host '%s' not found" % host_id
228 bottle
.abort(HTTP_Not_Found
, content
)
230 data
={'host' : content
}
231 convert_boolean(content
, ('admin_state_up',) )
232 change_keys_http2db(content
, http2db_host
, reverse
=True)
234 return format_out(data
)
236 @bottle.route(url_base
+ '/', method
='GET')
239 return 'works' #TODO: put links or redirection to /openvim???
245 def change_keys_http2db(data
, http_db
, reverse
=False):
246 '''Change keys of dictionary data according to the key_dict values
247 This allow change from http interface names to database names.
248 When reverse is True, the change is otherwise
250 data: can be a dictionary or a list
251 http_db: is a dictionary with hhtp names as keys and database names as value
252 reverse: by default change is done from http API to database. If True change is done otherwise
253 Return: None, but data is modified'''
254 if type(data
) is tuple or type(data
) is list:
256 change_keys_http2db(d
, http_db
, reverse
)
257 elif type(data
) is dict or type(data
) is bottle
.FormsDict
:
259 for k
,v
in http_db
.items():
260 if v
in data
: data
[k
]=data
.pop(v
)
262 for k
,v
in http_db
.items():
263 if k
in data
: data
[v
]=data
.pop(k
)
267 def format_out(data
):
268 '''return string of dictionary data according to requested json, yaml, xml. By default json'''
269 if 'application/yaml' in bottle
.request
.headers
.get('Accept'):
270 bottle
.response
.content_type
='application/yaml'
271 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='"'
272 else: #by default json
273 bottle
.response
.content_type
='application/json'
274 #return data #json no style
275 return json
.dumps(data
, indent
=4) + "\n"
277 def format_in(schema
):
279 error_text
= "Invalid header format "
280 format_type
= bottle
.request
.headers
.get('Content-Type', 'application/json')
281 if 'application/json' in format_type
:
282 error_text
= "Invalid json format "
283 #Use the json decoder instead of bottle decoder because it informs about the location of error formats with a ValueError exception
284 client_data
= json
.load(bottle
.request
.body
)
285 #client_data = bottle.request.json()
286 elif 'application/yaml' in format_type
:
287 error_text
= "Invalid yaml format "
288 client_data
= yaml
.load(bottle
.request
.body
)
289 elif format_type
== 'application/xml':
290 bottle
.abort(501, "Content-Type: application/xml not supported yet.")
292 print "HTTP HEADERS: " + str(bottle
.request
.headers
.items())
293 bottle
.abort(HTTP_Not_Acceptable
, 'Content-Type ' + str(format_type
) + ' not supported.')
295 #if client_data == None:
296 # bottle.abort(HTTP_Bad_Request, "Content error, empty")
300 #print "HTTP input data: ", str(client_data)
301 error_text
= "Invalid content "
302 js_v(client_data
, schema
)
305 except (ValueError, yaml
.YAMLError
) as exc
:
306 error_text
+= str(exc
)
308 bottle
.abort(HTTP_Bad_Request
, error_text
)
309 except js_e
.ValidationError
as exc
:
310 print "HTTP validate_in error, jsonschema exception ", exc
.message
, "at", exc
.path
311 print " CONTENT: " + str(bottle
.request
.body
.readlines())
313 if len(exc
.path
)>0: error_pos
=" at '" + ":".join(map(str, exc
.path
)) + "'"
314 bottle
.abort(HTTP_Bad_Request
, error_text
+ error_pos
+": "+exc
.message
)
316 # bottle.abort(HTTP_Bad_Request, "Content error: Failed to parse Content-Type", error_pos)
319 def filter_query_string(qs
, http2db
, allowed
):
320 '''Process query string (qs) checking that contains only valid tokens for avoiding SQL injection
322 'qs': bottle.FormsDict variable to be processed. None or empty is considered valid
323 'allowed': list of allowed string tokens (API http naming). All the keys of 'qs' must be one of 'allowed'
324 'http2db': dictionary with change from http API naming (dictionary key) to database naming(dictionary value)
325 Return: A tuple with the (select,where,limit) to be use in a database query. All of then transformed to the database naming
326 select: list of items to retrieve, filtered by query string 'field=token'. If no 'field' is present, allowed list is returned
327 where: dictionary with key, value, taken from the query string token=value. Empty if nothing is provided
328 limit: limit dictated by user with the query string 'limit'. 100 by default
329 abort if not permitted, using bottel.abort
334 if type(qs
) is not bottle
.FormsDict
:
335 print '!!!!!!!!!!!!!!invalid query string not a dictionary'
336 #bottle.abort(HTTP_Internal_Server_Error, "call programmer")
340 select
+= qs
.getall(k
)
343 bottle
.abort(HTTP_Bad_Request
, "Invalid query string at 'field="+v
+"'")
348 bottle
.abort(HTTP_Bad_Request
, "Invalid query string at 'limit="+qs
[k
]+"'")
351 bottle
.abort(HTTP_Bad_Request
, "Invalid query string at '"+k
+"="+qs
[k
]+"'")
352 if qs
[k
]!="null": where
[k
]=qs
[k
]
354 if len(select
)==0: select
+= allowed
355 #change from http api to database naming
356 for i
in range(0,len(select
)):
359 select
[i
] = http2db
[k
]
360 change_keys_http2db(where
, http2db
)
361 #print "filter_query_string", select,where,limit
363 return select
,where
,limit
366 def convert_bandwidth(data
, reverse
=False):
367 '''Check the field bandwidth recursively and when found, it removes units and convert to number
368 It assumes that bandwidth is well formed
370 'data': dictionary bottle.FormsDict variable to be checked. None or empty is considered valid
371 'reverse': by default convert form str to int (Mbps), if True it convert from number to units
375 if type(data
) is dict:
376 for k
in data
.keys():
377 if type(data
[k
]) is dict or type(data
[k
]) is tuple or type(data
[k
]) is list:
378 convert_bandwidth(data
[k
], reverse
)
379 if "bandwidth" in data
:
381 value
=str(data
["bandwidth"])
383 pos
= value
.find("bps")
385 if value
[pos
-1]=="G": data
["bandwidth"] = int(data
["bandwidth"][:pos
-1]) * 1000
386 elif value
[pos
-1]=="k": data
["bandwidth"]= int(data
["bandwidth"][:pos
-1]) / 1000
387 else: data
["bandwidth"]= int(data
["bandwidth"][:pos
-1])
389 value
= int(data
["bandwidth"])
390 if value
% 1000 == 0: data
["bandwidth"]=str(value
/1000) + " Gbps"
391 else: data
["bandwidth"]=str(value
) + " Mbps"
393 print "convert_bandwidth exception for type", type(data
["bandwidth"]), " data", data
["bandwidth"]
395 if type(data
) is tuple or type(data
) is list:
397 if type(k
) is dict or type(k
) is tuple or type(k
) is list:
398 convert_bandwidth(k
, reverse
)
400 def convert_boolean(data
, items
):
401 '''Check recursively the content of data, and if there is an key contained in items, convert value from string to boolean
402 It assumes that bandwidth is well formed
404 'data': dictionary bottle.FormsDict variable to be checked. None or empty is consideted valid
405 'items': tuple of keys to convert
409 if type(data
) is dict:
410 for k
in data
.keys():
411 if type(data
[k
]) is dict or type(data
[k
]) is tuple or type(data
[k
]) is list:
412 convert_boolean(data
[k
], items
)
414 if type(data
[k
]) is str:
415 if data
[k
]=="false": data
[k
]=False
416 elif data
[k
]=="true": data
[k
]=True
417 if type(data
) is tuple or type(data
) is list:
419 if type(k
) is dict or type(k
) is tuple or type(k
) is list:
420 convert_boolean(k
, items
)
422 def convert_datetime2str(var
):
423 '''Converts a datetime variable to a string with the format '%Y-%m-%dT%H:%i:%s'
424 It enters recursively in the dict var finding this kind of variables
426 if type(var
) is dict:
427 for k
,v
in var
.items():
428 if type(v
) is datetime
.datetime
:
429 var
[k
]= v
.strftime('%Y-%m-%dT%H:%M:%S')
430 elif type(v
) is dict or type(v
) is list or type(v
) is tuple:
431 convert_datetime2str(v
)
432 if len(var
) == 0: return True
433 elif type(var
) is list or type(var
) is tuple:
435 convert_datetime2str(v
)
437 def check_valid_tenant(my
, tenant_id
):
440 return HTTP_Unauthorized
, "Needed admin privileges"
442 result
, _
= my
.db
.get_table(FROM
='tenants', SELECT
=('uuid',), WHERE
={'uuid': tenant_id
})
444 return HTTP_Not_Found
, "tenant '%s' not found" % tenant_id
447 def check_valid_uuid(uuid
):
448 id_schema
= {"type" : "string", "pattern": "^[a-fA-F0-9]{8}(-[a-fA-F0-9]{4}){3}-[a-fA-F0-9]{12}$"}
450 js_v(uuid
, id_schema
)
452 except js_e
.ValidationError
:
458 Check if string value is a well-wormed url
459 :param url: string url
460 :return: True if is a valid url, False if is not well-formed
463 parsed_url
= urlparse
.urlparse(url
)
478 e
={"error":{"code":error
.status_code
, "type":error
.status
, "description":error
.body
}}
481 @bottle.hook('after_request')
483 #TODO: Alf: Is it needed??
484 bottle
.response
.headers
['Access-Control-Allow-Origin'] = '*'
490 @bottle.route(url_base
+ '/hosts', method
='GET')
491 def http_get_hosts():
492 select_
,where_
,limit_
= filter_query_string(bottle
.request
.query
, http2db_host
,
493 ('id','name','description','status','admin_state_up') )
495 myself
= config_dic
['http_threads'][ threading
.current_thread().name
]
496 result
, content
= myself
.db
.get_table(FROM
='hosts', SELECT
=select_
, WHERE
=where_
, LIMIT
=limit_
)
498 print "http_get_hosts Error", content
499 bottle
.abort(-result
, content
)
501 convert_boolean(content
, ('admin_state_up',) )
502 change_keys_http2db(content
, http2db_host
, reverse
=True)
504 row
['links'] = ( {'href': myself
.url_preffix
+ '/hosts/' + str(row
['id']), 'rel': 'bookmark'}, )
505 data
={'hosts' : content
}
506 return format_out(data
)
508 @bottle.route(url_base
+ '/hosts/<host_id>', method
='GET')
509 def http_get_host_id(host_id
):
510 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
511 return my
.gethost(host_id
)
513 @bottle.route(url_base
+ '/hosts', method
='POST')
514 def http_post_hosts():
515 '''insert a host into the database. All resources are got and inserted'''
516 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
519 bottle
.abort(HTTP_Unauthorized
, "Needed admin privileges")
522 http_content
= format_in( host_new_schema
)
523 r
= remove_extra_items(http_content
, host_new_schema
)
524 if r
is not None: print "http_post_host_id: Warning: remove extra items ", r
525 change_keys_http2db(http_content
['host'], http2db_host
)
527 host
= http_content
['host']
529 if 'host-data' in http_content
:
530 host
.update(http_content
['host-data'])
531 ip_name
=http_content
['host-data']['ip_name']
532 user
=http_content
['host-data']['user']
533 password
=http_content
['host-data'].get('password', None)
535 ip_name
=host
['ip_name']
537 password
=host
.get('password', None)
540 rad
= RADclass
.RADclass()
541 (return_status
, code
) = rad
.obtain_RAD(user
, password
, ip_name
)
544 if not return_status
:
545 print 'http_post_hosts ERROR obtaining RAD', code
546 bottle
.abort(HTTP_Bad_Request
, code
)
549 rad_structure
= yaml
.load(rad
.to_text())
550 print 'rad_structure\n---------------------'
551 print json
.dumps(rad_structure
, indent
=4)
552 print '---------------------'
554 WHERE_
={"family":rad_structure
['processor']['family'], 'manufacturer':rad_structure
['processor']['manufacturer'], 'version':rad_structure
['processor']['version']}
555 result
, content
= my
.db
.get_table(FROM
='host_ranking',
559 host
['ranking'] = content
[0]['ranking']
561 #error_text= "Host " + str(WHERE_)+ " not found in ranking table. Not valid for VIM management"
562 #bottle.abort(HTTP_Bad_Request, error_text)
564 warning_text
+= "Host " + str(WHERE_
)+ " not found in ranking table. Assuming lowest value 100\n"
565 host
['ranking'] = 100 #TODO: as not used in this version, set the lowest value
567 features
= rad_structure
['processor'].get('features', ())
568 host
['features'] = ",".join(features
)
571 for node
in (rad_structure
['resource topology']['nodes'] or {}).itervalues():
576 for core
in node
['cpu']['eligible_cores']:
577 eligible_cores
.extend(core
)
578 for core
in node
['cpu']['cores']:
579 for thread_id
in core
:
580 c
={'core_id': count
, 'thread_id': thread_id
}
581 if thread_id
not in eligible_cores
: c
['status'] = 'noteligible'
586 for port_k
, port_v
in node
['nics']['nic 0']['ports'].iteritems():
587 if port_v
['virtual']:
591 for port_k2
, port_v2
in node
['nics']['nic 0']['ports'].iteritems():
592 if port_v2
['virtual'] and port_v2
['PF_pci_id']==port_k
:
593 sriovs
.append({'pci':port_k2
, 'mac':port_v2
['mac'], 'source_name':port_v2
['source_name']})
595 #sort sriov according to pci and rename them to the vf number
596 new_sriovs
= sorted(sriovs
, key
=lambda k
: k
['pci'])
598 for sriov
in new_sriovs
:
599 sriov
['source_name'] = index
601 interfaces
.append ({'pci':str(port_k
), 'Mbps': port_v
['speed']/1000000, 'sriovs': new_sriovs
, 'mac':port_v
['mac'], 'source_name':port_v
['source_name']})
602 #@TODO LA memoria devuelta por el RAD es incorrecta, almenos para IVY1, NFV100
603 memory
=node
['memory']['node_size'] / (1024*1024*1024)
604 #memory=get_next_2pow(node['memory']['hugepage_nr'])
605 host
['numas'].append( {'numa_socket': node
['id'], 'hugepages': node
['memory']['hugepage_nr'], 'memory':memory
, 'interfaces': interfaces
, 'cores': cores
} )
606 print json
.dumps(host
, indent
=4)
610 result
, content
= my
.db
.new_host(host
)
612 if content
['admin_state_up']:
614 host_test_mode
= True if config_dic
['mode']=='test' or config_dic
['mode']=="OF only" else False
615 host_develop_mode
= True if config_dic
['mode']=='development' else False
616 host_develop_bridge_iface
= config_dic
.get('development_bridge', None)
617 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'],
618 test
=host_test_mode
, image_path
=config_dic
['image_path'],
619 version
=config_dic
['version'], host_id
=content
['uuid'],
620 develop_mode
=host_develop_mode
, develop_bridge_iface
=host_develop_bridge_iface
)
622 config_dic
['host_threads'][ content
['uuid'] ] = thread
625 change_keys_http2db(content
, http2db_host
, reverse
=True)
626 if len(warning_text
)>0:
627 content
["warning"]= warning_text
628 data
={'host' : content
}
629 return format_out(data
)
631 bottle
.abort(HTTP_Bad_Request
, content
)
634 @bottle.route(url_base
+ '/hosts/<host_id>', method
='PUT')
635 def http_put_host_id(host_id
):
636 '''modify a host into the database. All resources are got and inserted'''
637 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
640 bottle
.abort(HTTP_Unauthorized
, "Needed admin privileges")
643 http_content
= format_in( host_edit_schema
)
644 r
= remove_extra_items(http_content
, host_edit_schema
)
645 if r
is not None: print "http_post_host_id: Warning: remove extra items ", r
646 change_keys_http2db(http_content
['host'], http2db_host
)
649 result
, content
= my
.db
.edit_host(host_id
, http_content
['host'])
651 convert_boolean(content
, ('admin_state_up',) )
652 change_keys_http2db(content
, http2db_host
, reverse
=True)
653 data
={'host' : content
}
656 config_dic
['host_threads'][host_id
].name
= content
.get('name',content
['ip_name'])
657 config_dic
['host_threads'][host_id
].user
= content
['user']
658 config_dic
['host_threads'][host_id
].host
= content
['ip_name']
659 config_dic
['host_threads'][host_id
].insert_task("reload")
662 return format_out(data
)
664 bottle
.abort(HTTP_Bad_Request
, content
)
669 @bottle.route(url_base
+ '/hosts/<host_id>', method
='DELETE')
670 def http_delete_host_id(host_id
):
671 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
674 bottle
.abort(HTTP_Unauthorized
, "Needed admin privileges")
675 result
, content
= my
.db
.delete_row('hosts', host_id
)
677 bottle
.abort(HTTP_Not_Found
, content
)
680 if host_id
in config_dic
['host_threads']:
681 config_dic
['host_threads'][host_id
].insert_task("exit")
683 data
={'result' : content
}
684 return format_out(data
)
686 print "http_delete_host_id error",result
, content
687 bottle
.abort(-result
, content
)
696 @bottle.route(url_base
+ '/tenants', method
='GET')
697 def http_get_tenants():
698 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
699 select_
,where_
,limit_
= filter_query_string(bottle
.request
.query
, http2db_tenant
,
700 ('id','name','description','enabled') )
701 result
, content
= my
.db
.get_table(FROM
='tenants', SELECT
=select_
,WHERE
=where_
,LIMIT
=limit_
)
703 print "http_get_tenants Error", content
704 bottle
.abort(-result
, content
)
706 change_keys_http2db(content
, http2db_tenant
, reverse
=True)
707 convert_boolean(content
, ('enabled',))
708 data
={'tenants' : content
}
709 #data['tenants_links'] = dict([('tenant', row['id']) for row in content])
710 return format_out(data
)
712 @bottle.route(url_base
+ '/tenants/<tenant_id>', method
='GET')
713 def http_get_tenant_id(tenant_id
):
714 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
715 result
, content
= my
.db
.get_table(FROM
='tenants', SELECT
=('uuid','name','description', 'enabled'),WHERE
={'uuid': tenant_id
} )
717 print "http_get_tenant_id error %d %s" % (result
, content
)
718 bottle
.abort(-result
, content
)
720 print "http_get_tenant_id tenant '%s' not found" % tenant_id
721 bottle
.abort(HTTP_Not_Found
, "tenant %s not found" % tenant_id
)
723 change_keys_http2db(content
, http2db_tenant
, reverse
=True)
724 convert_boolean(content
, ('enabled',))
725 data
={'tenant' : content
[0]}
726 #data['tenants_links'] = dict([('tenant', row['id']) for row in content])
727 return format_out(data
)
730 @bottle.route(url_base
+ '/tenants', method
='POST')
731 def http_post_tenants():
732 '''insert a tenant into the database.'''
733 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
735 http_content
= format_in( tenant_new_schema
)
736 r
= remove_extra_items(http_content
, tenant_new_schema
)
737 if r
is not None: print "http_post_tenants: Warning: remove extra items ", r
738 change_keys_http2db(http_content
['tenant'], http2db_tenant
)
741 result
, content
= my
.db
.new_tenant(http_content
['tenant'])
744 return http_get_tenant_id(content
)
746 bottle
.abort(-result
, content
)
749 @bottle.route(url_base
+ '/tenants/<tenant_id>', method
='PUT')
750 def http_put_tenant_id(tenant_id
):
751 '''update a tenant into the database.'''
752 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
754 http_content
= format_in( tenant_edit_schema
)
755 r
= remove_extra_items(http_content
, tenant_edit_schema
)
756 if r
is not None: print "http_put_tenant_id: Warning: remove extra items ", r
757 change_keys_http2db(http_content
['tenant'], http2db_tenant
)
760 result
, content
= my
.db
.update_rows('tenants', http_content
['tenant'], WHERE
={'uuid': tenant_id
}, log
=True )
762 return http_get_tenant_id(tenant_id
)
764 bottle
.abort(-result
, content
)
767 @bottle.route(url_base
+ '/tenants/<tenant_id>', method
='DELETE')
768 def http_delete_tenant_id(tenant_id
):
769 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
771 r
, tenants_flavors
= my
.db
.get_table(FROM
='tenants_flavors', SELECT
=('flavor_id','tenant_id'), WHERE
={'tenant_id': tenant_id
})
774 r
, tenants_images
= my
.db
.get_table(FROM
='tenants_images', SELECT
=('image_id','tenant_id'), WHERE
={'tenant_id': tenant_id
})
777 result
, content
= my
.db
.delete_row('tenants', tenant_id
)
779 bottle
.abort(HTTP_Not_Found
, content
)
781 print "alf", tenants_flavors
, tenants_images
782 for flavor
in tenants_flavors
:
783 my
.db
.delete_row_by_key("flavors", "uuid", flavor
['flavor_id'])
784 for image
in tenants_images
:
785 my
.db
.delete_row_by_key("images", "uuid", image
['image_id'])
786 data
={'result' : content
}
787 return format_out(data
)
789 print "http_delete_tenant_id error",result
, content
790 bottle
.abort(-result
, content
)
797 @bottle.route(url_base
+ '/<tenant_id>/flavors', method
='GET')
798 def http_get_flavors(tenant_id
):
799 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
800 #check valid tenant_id
801 result
,content
= check_valid_tenant(my
, tenant_id
)
803 bottle
.abort(result
, content
)
805 select_
,where_
,limit_
= filter_query_string(bottle
.request
.query
, http2db_flavor
,
806 ('id','name','description','public') )
810 from_
='tenants_flavors inner join flavors on tenants_flavors.flavor_id=flavors.uuid'
811 where_
['tenant_id'] = tenant_id
812 result
, content
= my
.db
.get_table(FROM
=from_
, SELECT
=select_
, WHERE
=where_
, LIMIT
=limit_
)
814 print "http_get_flavors Error", content
815 bottle
.abort(-result
, content
)
817 change_keys_http2db(content
, http2db_flavor
, reverse
=True)
819 row
['links']=[ {'href': "/".join( (my
.url_preffix
, tenant_id
, 'flavors', str(row
['id']) ) ), 'rel':'bookmark' } ]
820 data
={'flavors' : content
}
821 return format_out(data
)
823 @bottle.route(url_base
+ '/<tenant_id>/flavors/<flavor_id>', method
='GET')
824 def http_get_flavor_id(tenant_id
, flavor_id
):
825 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
826 #check valid tenant_id
827 result
,content
= check_valid_tenant(my
, tenant_id
)
829 bottle
.abort(result
, content
)
831 select_
,where_
,limit_
= filter_query_string(bottle
.request
.query
, http2db_flavor
,
832 ('id','name','description','ram', 'vcpus', 'extended', 'disk', 'public') )
836 from_
='tenants_flavors as tf inner join flavors as f on tf.flavor_id=f.uuid'
837 where_
['tenant_id'] = tenant_id
838 where_
['uuid'] = flavor_id
839 result
, content
= my
.db
.get_table(SELECT
=select_
, FROM
=from_
, WHERE
=where_
, LIMIT
=limit_
)
842 print "http_get_flavor_id error %d %s" % (result
, content
)
843 bottle
.abort(-result
, content
)
845 print "http_get_flavors_id flavor '%s' not found" % str(flavor_id
)
846 bottle
.abort(HTTP_Not_Found
, 'flavor %s not found' % flavor_id
)
848 change_keys_http2db(content
, http2db_flavor
, reverse
=True)
849 if 'extended' in content
[0] and content
[0]['extended'] is not None:
850 extended
= json
.loads(content
[0]['extended'])
851 if 'devices' in extended
:
852 change_keys_http2db(extended
['devices'], http2db_flavor
, reverse
=True)
853 content
[0]['extended']=extended
854 convert_bandwidth(content
[0], reverse
=True)
855 content
[0]['links']=[ {'href': "/".join( (my
.url_preffix
, tenant_id
, 'flavors', str(content
[0]['id']) ) ), 'rel':'bookmark' } ]
856 data
={'flavor' : content
[0]}
857 #data['tenants_links'] = dict([('tenant', row['id']) for row in content])
858 return format_out(data
)
861 @bottle.route(url_base
+ '/<tenant_id>/flavors', method
='POST')
862 def http_post_flavors(tenant_id
):
863 '''insert a flavor into the database, and attach to tenant.'''
864 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
865 #check valid tenant_id
866 result
,content
= check_valid_tenant(my
, tenant_id
)
868 bottle
.abort(result
, content
)
869 http_content
= format_in( flavor_new_schema
)
870 r
= remove_extra_items(http_content
, flavor_new_schema
)
871 if r
is not None: print "http_post_flavors: Warning: remove extra items ", r
872 change_keys_http2db(http_content
['flavor'], http2db_flavor
)
873 extended_dict
= http_content
['flavor'].pop('extended', None)
874 if extended_dict
is not None:
875 result
, content
= check_extended(extended_dict
)
877 print "http_post_flavors wrong input extended error %d %s" % (result
, content
)
878 bottle
.abort(-result
, content
)
880 convert_bandwidth(extended_dict
)
881 if 'devices' in extended_dict
: change_keys_http2db(extended_dict
['devices'], http2db_flavor
)
882 http_content
['flavor']['extended'] = json
.dumps(extended_dict
)
884 result
, content
= my
.db
.new_flavor(http_content
['flavor'], tenant_id
)
886 return http_get_flavor_id(tenant_id
, content
)
888 print "http_psot_flavors error %d %s" % (result
, content
)
889 bottle
.abort(-result
, content
)
892 @bottle.route(url_base
+ '/<tenant_id>/flavors/<flavor_id>', method
='DELETE')
893 def http_delete_flavor_id(tenant_id
, flavor_id
):
894 '''Deletes the flavor_id of a tenant. IT removes from tenants_flavors table.'''
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 result
, content
= my
.db
.delete_image_flavor('flavor', flavor_id
, tenant_id
)
903 bottle
.abort(HTTP_Not_Found
, content
)
905 data
={'result' : content
}
906 return format_out(data
)
908 print "http_delete_flavor_id error",result
, content
909 bottle
.abort(-result
, content
)
912 @bottle.route(url_base
+ '/<tenant_id>/flavors/<flavor_id>/<action>', method
='POST')
913 def http_attach_detach_flavors(tenant_id
, flavor_id
, action
):
914 '''attach/detach an existing flavor in this tenant. That is insert/remove at tenants_flavors table.'''
915 #TODO alf: not tested at all!!!
916 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
917 #check valid tenant_id
918 result
,content
= check_valid_tenant(my
, tenant_id
)
920 bottle
.abort(result
, content
)
922 bottle
.abort(HTTP_Bad_Request
, "Invalid tenant 'any' with this command")
924 if action
!='attach' and action
!= 'detach':
925 bottle
.abort(HTTP_Method_Not_Allowed
, "actions can be attach or detach")
928 #Ensure that flavor exist
929 from_
='tenants_flavors as tf right join flavors as f on tf.flavor_id=f.uuid'
930 where_
={'uuid': flavor_id
}
931 result
, content
= my
.db
.get_table(SELECT
=('public','tenant_id'), FROM
=from_
, WHERE
=where_
)
934 text_error
="Flavor '%s' not found" % flavor_id
936 text_error
="Flavor '%s' not found for tenant '%s'" % (flavor_id
, tenant_id
)
937 bottle
.abort(HTTP_Not_Found
, text_error
)
942 if flavor
['tenant_id']!=None:
943 bottle
.abort(HTTP_Conflict
, "Flavor '%s' already attached to tenant '%s'" % (flavor_id
, tenant_id
))
944 if flavor
['public']=='no' and not my
.admin
:
945 #allow only attaching public flavors
946 bottle
.abort(HTTP_Unauthorized
, "Needed admin rights to attach a private flavor")
949 result
, content
= my
.db
.new_row('tenants_flavors', {'flavor_id':flavor_id
, 'tenant_id': tenant_id
})
951 return http_get_flavor_id(tenant_id
, flavor_id
)
953 if flavor
['tenant_id']==None:
954 bottle
.abort(HTTP_Not_Found
, "Flavor '%s' not attached to tenant '%s'" % (flavor_id
, tenant_id
))
955 result
, content
= my
.db
.delete_row_by_dict(FROM
='tenants_flavors', WHERE
={'flavor_id':flavor_id
, 'tenant_id':tenant_id
})
957 if flavor
['public']=='no':
958 #try to delete the flavor completely to avoid orphan flavors, IGNORE error
959 my
.db
.delete_row_by_dict(FROM
='flavors', WHERE
={'uuid':flavor_id
})
960 data
={'result' : "flavor detached"}
961 return format_out(data
)
963 #if get here is because an error
964 print "http_attach_detach_flavors error %d %s" % (result
, content
)
965 bottle
.abort(-result
, content
)
968 @bottle.route(url_base
+ '/<tenant_id>/flavors/<flavor_id>', method
='PUT')
969 def http_put_flavor_id(tenant_id
, flavor_id
):
970 '''update a flavor_id into the database.'''
971 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
972 #check valid tenant_id
973 result
,content
= check_valid_tenant(my
, tenant_id
)
975 bottle
.abort(result
, content
)
977 http_content
= format_in( flavor_update_schema
)
978 r
= remove_extra_items(http_content
, flavor_update_schema
)
979 if r
is not None: print "http_put_flavor_id: Warning: remove extra items ", r
980 change_keys_http2db(http_content
['flavor'], http2db_flavor
)
981 extended_dict
= http_content
['flavor'].pop('extended', None)
982 if extended_dict
is not None:
983 result
, content
= check_extended(extended_dict
)
985 print "http_put_flavor_id wrong input extended error %d %s" % (result
, content
)
986 bottle
.abort(-result
, content
)
988 convert_bandwidth(extended_dict
)
989 if 'devices' in extended_dict
: change_keys_http2db(extended_dict
['devices'], http2db_flavor
)
990 http_content
['flavor']['extended'] = json
.dumps(extended_dict
)
991 #Ensure that flavor exist
992 where_
={'uuid': flavor_id
}
996 from_
='tenants_flavors as ti inner join flavors as i on ti.flavor_id=i.uuid'
997 where_
['tenant_id'] = tenant_id
998 result
, content
= my
.db
.get_table(SELECT
=('public',), FROM
=from_
, WHERE
=where_
)
1000 text_error
="Flavor '%s' not found" % flavor_id
1001 if tenant_id
!='any':
1002 text_error
+=" for tenant '%s'" % flavor_id
1003 bottle
.abort(HTTP_Not_Found
, text_error
)
1006 if content
[0]['public']=='yes' and not my
.admin
:
1007 #allow only modifications over private flavors
1008 bottle
.abort(HTTP_Unauthorized
, "Needed admin rights to edit a public flavor")
1010 #insert in data base
1011 result
, content
= my
.db
.update_rows('flavors', http_content
['flavor'], {'uuid': flavor_id
})
1014 print "http_put_flavor_id error %d %s" % (result
, content
)
1015 bottle
.abort(-result
, content
)
1018 return http_get_flavor_id(tenant_id
, flavor_id
)
1026 @bottle.route(url_base
+ '/<tenant_id>/images', method
='GET')
1027 def http_get_images(tenant_id
):
1028 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
1029 #check valid tenant_id
1030 result
,content
= check_valid_tenant(my
, tenant_id
)
1032 bottle
.abort(result
, content
)
1034 select_
,where_
,limit_
= filter_query_string(bottle
.request
.query
, http2db_image
,
1035 ('id','name','description','path','public') )
1036 if tenant_id
=='any':
1039 from_
='tenants_images inner join images on tenants_images.image_id=images.uuid'
1040 where_
['tenant_id'] = tenant_id
1041 result
, content
= my
.db
.get_table(SELECT
=select_
, FROM
=from_
, WHERE
=where_
, LIMIT
=limit_
)
1043 print "http_get_images Error", content
1044 bottle
.abort(-result
, content
)
1046 change_keys_http2db(content
, http2db_image
, reverse
=True)
1047 #for row in content: row['links']=[ {'href': "/".join( (my.url_preffix, tenant_id, 'images', str(row['id']) ) ), 'rel':'bookmark' } ]
1048 data
={'images' : content
}
1049 return format_out(data
)
1051 @bottle.route(url_base
+ '/<tenant_id>/images/<image_id>', method
='GET')
1052 def http_get_image_id(tenant_id
, image_id
):
1053 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
1054 #check valid tenant_id
1055 result
,content
= check_valid_tenant(my
, tenant_id
)
1057 bottle
.abort(result
, content
)
1059 select_
,where_
,limit_
= filter_query_string(bottle
.request
.query
, http2db_image
,
1060 ('id','name','description','progress', 'status','path', 'created', 'updated','public') )
1061 if tenant_id
=='any':
1064 from_
='tenants_images as ti inner join images as i on ti.image_id=i.uuid'
1065 where_
['tenant_id'] = tenant_id
1066 where_
['uuid'] = image_id
1067 result
, content
= my
.db
.get_table(SELECT
=select_
, FROM
=from_
, WHERE
=where_
, LIMIT
=limit_
)
1070 print "http_get_images error %d %s" % (result
, content
)
1071 bottle
.abort(-result
, content
)
1073 print "http_get_images image '%s' not found" % str(image_id
)
1074 bottle
.abort(HTTP_Not_Found
, 'image %s not found' % image_id
)
1076 convert_datetime2str(content
)
1077 change_keys_http2db(content
, http2db_image
, reverse
=True)
1078 if 'metadata' in content
[0] and content
[0]['metadata'] is not None:
1079 metadata
= json
.loads(content
[0]['metadata'])
1080 content
[0]['metadata']=metadata
1081 content
[0]['links']=[ {'href': "/".join( (my
.url_preffix
, tenant_id
, 'images', str(content
[0]['id']) ) ), 'rel':'bookmark' } ]
1082 data
={'image' : content
[0]}
1083 #data['tenants_links'] = dict([('tenant', row['id']) for row in content])
1084 return format_out(data
)
1086 @bottle.route(url_base
+ '/<tenant_id>/images', method
='POST')
1087 def http_post_images(tenant_id
):
1088 '''insert a image into the database, and attach to tenant.'''
1089 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
1090 #check valid tenant_id
1091 result
,content
= check_valid_tenant(my
, tenant_id
)
1093 bottle
.abort(result
, content
)
1094 http_content
= format_in(image_new_schema
)
1095 r
= remove_extra_items(http_content
, image_new_schema
)
1096 if r
is not None: print "http_post_images: Warning: remove extra items ", r
1097 change_keys_http2db(http_content
['image'], http2db_image
)
1098 metadata_dict
= http_content
['image'].pop('metadata', None)
1099 if metadata_dict
is not None:
1100 http_content
['image']['metadata'] = json
.dumps(metadata_dict
)
1102 host_test_mode
= True if config_dic
['mode']=='test' or config_dic
['mode']=="OF only" else False
1104 image_file
= http_content
['image'].get('path',None)
1105 if os
.path
.exists(image_file
):
1106 http_content
['image']['checksum'] = md5(image_file
)
1107 elif is_url(image_file
):
1110 if not host_test_mode
:
1111 content
= "Image file not found"
1112 print "http_post_images error: %d %s" % (HTTP_Bad_Request
, content
)
1113 bottle
.abort(HTTP_Bad_Request
, content
)
1114 except Exception as e
:
1115 print "ERROR. Unexpected exception: %s" % (str(e
))
1116 bottle
.abort(HTTP_Internal_Server_Error
, type(e
).__name
__ + ": " + str(e
))
1117 #insert in data base
1118 result
, content
= my
.db
.new_image(http_content
['image'], tenant_id
)
1120 return http_get_image_id(tenant_id
, content
)
1122 print "http_post_images error %d %s" % (result
, content
)
1123 bottle
.abort(-result
, content
)
1126 @bottle.route(url_base
+ '/<tenant_id>/images/<image_id>', method
='DELETE')
1127 def http_delete_image_id(tenant_id
, image_id
):
1128 '''Deletes the image_id of a tenant. IT removes from tenants_images table.'''
1129 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
1130 #check valid tenant_id
1131 result
,content
= check_valid_tenant(my
, tenant_id
)
1133 bottle
.abort(result
, content
)
1134 result
, content
= my
.db
.delete_image_flavor('image', image_id
, tenant_id
)
1136 bottle
.abort(HTTP_Not_Found
, content
)
1138 data
={'result' : content
}
1139 return format_out(data
)
1141 print "http_delete_image_id error",result
, content
1142 bottle
.abort(-result
, content
)
1145 @bottle.route(url_base
+ '/<tenant_id>/images/<image_id>/<action>', method
='POST')
1146 def http_attach_detach_images(tenant_id
, image_id
, action
):
1147 '''attach/detach an existing image in this tenant. That is insert/remove at tenants_images table.'''
1148 #TODO alf: not tested at all!!!
1149 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
1150 #check valid tenant_id
1151 result
,content
= check_valid_tenant(my
, tenant_id
)
1153 bottle
.abort(result
, content
)
1154 if tenant_id
=='any':
1155 bottle
.abort(HTTP_Bad_Request
, "Invalid tenant 'any' with this command")
1157 if action
!='attach' and action
!= 'detach':
1158 bottle
.abort(HTTP_Method_Not_Allowed
, "actions can be attach or detach")
1161 #Ensure that image exist
1162 from_
='tenants_images as ti right join images as i on ti.image_id=i.uuid'
1163 where_
={'uuid': image_id
}
1164 result
, content
= my
.db
.get_table(SELECT
=('public','tenant_id'), FROM
=from_
, WHERE
=where_
)
1166 if action
=='attach':
1167 text_error
="Image '%s' not found" % image_id
1169 text_error
="Image '%s' not found for tenant '%s'" % (image_id
, tenant_id
)
1170 bottle
.abort(HTTP_Not_Found
, text_error
)
1174 if action
=='attach':
1175 if image
['tenant_id']!=None:
1176 bottle
.abort(HTTP_Conflict
, "Image '%s' already attached to tenant '%s'" % (image_id
, tenant_id
))
1177 if image
['public']=='no' and not my
.admin
:
1178 #allow only attaching public images
1179 bottle
.abort(HTTP_Unauthorized
, "Needed admin rights to attach a private image")
1181 #insert in data base
1182 result
, content
= my
.db
.new_row('tenants_images', {'image_id':image_id
, 'tenant_id': tenant_id
})
1184 return http_get_image_id(tenant_id
, image_id
)
1186 if image
['tenant_id']==None:
1187 bottle
.abort(HTTP_Not_Found
, "Image '%s' not attached to tenant '%s'" % (image_id
, tenant_id
))
1188 result
, content
= my
.db
.delete_row_by_dict(FROM
='tenants_images', WHERE
={'image_id':image_id
, 'tenant_id':tenant_id
})
1190 if image
['public']=='no':
1191 #try to delete the image completely to avoid orphan images, IGNORE error
1192 my
.db
.delete_row_by_dict(FROM
='images', WHERE
={'uuid':image_id
})
1193 data
={'result' : "image detached"}
1194 return format_out(data
)
1196 #if get here is because an error
1197 print "http_attach_detach_images error %d %s" % (result
, content
)
1198 bottle
.abort(-result
, content
)
1201 @bottle.route(url_base
+ '/<tenant_id>/images/<image_id>', method
='PUT')
1202 def http_put_image_id(tenant_id
, image_id
):
1203 '''update a image_id into the database.'''
1204 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
1205 #check valid tenant_id
1206 result
,content
= check_valid_tenant(my
, tenant_id
)
1208 bottle
.abort(result
, content
)
1210 http_content
= format_in( image_update_schema
)
1211 r
= remove_extra_items(http_content
, image_update_schema
)
1212 if r
is not None: print "http_put_image_id: Warning: remove extra items ", r
1213 change_keys_http2db(http_content
['image'], http2db_image
)
1214 metadata_dict
= http_content
['image'].pop('metadata', None)
1215 if metadata_dict
is not None:
1216 http_content
['image']['metadata'] = json
.dumps(metadata_dict
)
1217 #Ensure that image exist
1218 where_
={'uuid': image_id
}
1219 if tenant_id
=='any':
1222 from_
='tenants_images as ti inner join images as i on ti.image_id=i.uuid'
1223 where_
['tenant_id'] = tenant_id
1224 result
, content
= my
.db
.get_table(SELECT
=('public',), FROM
=from_
, WHERE
=where_
)
1226 text_error
="Image '%s' not found" % image_id
1227 if tenant_id
!='any':
1228 text_error
+=" for tenant '%s'" % image_id
1229 bottle
.abort(HTTP_Not_Found
, text_error
)
1232 if content
[0]['public']=='yes' and not my
.admin
:
1233 #allow only modifications over private images
1234 bottle
.abort(HTTP_Unauthorized
, "Needed admin rights to edit a public image")
1236 #insert in data base
1237 result
, content
= my
.db
.update_rows('images', http_content
['image'], {'uuid': image_id
})
1240 print "http_put_image_id error %d %s" % (result
, content
)
1241 bottle
.abort(-result
, content
)
1244 return http_get_image_id(tenant_id
, image_id
)
1251 @bottle.route(url_base
+ '/<tenant_id>/servers', method
='GET')
1252 def http_get_servers(tenant_id
):
1253 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
1254 result
,content
= check_valid_tenant(my
, tenant_id
)
1256 bottle
.abort(result
, content
)
1259 select_
,where_
,limit_
= filter_query_string(bottle
.request
.query
, http2db_server
,
1260 ('id','name','description','hostId','imageRef','flavorRef','status', 'tenant_id') )
1261 if tenant_id
!='any':
1262 where_
['tenant_id'] = tenant_id
1263 result
, content
= my
.db
.get_table(SELECT
=select_
, FROM
='instances', WHERE
=where_
, LIMIT
=limit_
)
1265 print "http_get_servers Error", content
1266 bottle
.abort(-result
, content
)
1268 change_keys_http2db(content
, http2db_server
, reverse
=True)
1270 tenant_id
= row
.pop('tenant_id')
1271 row
['links']=[ {'href': "/".join( (my
.url_preffix
, tenant_id
, 'servers', str(row
['id']) ) ), 'rel':'bookmark' } ]
1272 data
={'servers' : content
}
1273 return format_out(data
)
1275 @bottle.route(url_base
+ '/<tenant_id>/servers/<server_id>', method
='GET')
1276 def http_get_server_id(tenant_id
, server_id
):
1277 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
1278 #check valid tenant_id
1279 result
,content
= check_valid_tenant(my
, tenant_id
)
1281 bottle
.abort(result
, content
)
1284 result
, content
= my
.db
.get_instance(server_id
)
1286 bottle
.abort(HTTP_Not_Found
, content
)
1288 #change image/flavor-id to id and link
1289 convert_bandwidth(content
, reverse
=True)
1290 convert_datetime2str(content
)
1291 if content
["ram"]==0 : del content
["ram"]
1292 if content
["vcpus"]==0 : del content
["vcpus"]
1293 if 'flavor_id' in content
:
1294 if content
['flavor_id'] is not None:
1295 content
['flavor'] = {'id':content
['flavor_id'],
1296 'links':[{'href': "/".join( (my
.url_preffix
, content
['tenant_id'], 'flavors', str(content
['flavor_id']) ) ), 'rel':'bookmark'}]
1298 del content
['flavor_id']
1299 if 'image_id' in content
:
1300 if content
['image_id'] is not None:
1301 content
['image'] = {'id':content
['image_id'],
1302 'links':[{'href': "/".join( (my
.url_preffix
, content
['tenant_id'], 'images', str(content
['image_id']) ) ), 'rel':'bookmark'}]
1304 del content
['image_id']
1305 change_keys_http2db(content
, http2db_server
, reverse
=True)
1306 if 'extended' in content
:
1307 if 'devices' in content
['extended']: change_keys_http2db(content
['extended']['devices'], http2db_server
, reverse
=True)
1309 data
={'server' : content
}
1310 return format_out(data
)
1312 bottle
.abort(-result
, content
)
1315 @bottle.route(url_base
+ '/<tenant_id>/servers', method
='POST')
1316 def http_post_server_id(tenant_id
):
1317 '''deploys a new server'''
1318 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
1319 #check valid tenant_id
1320 result
,content
= check_valid_tenant(my
, tenant_id
)
1322 bottle
.abort(result
, content
)
1324 if tenant_id
=='any':
1325 bottle
.abort(HTTP_Bad_Request
, "Invalid tenant 'any' with this command")
1327 http_content
= format_in( server_new_schema
)
1328 r
= remove_extra_items(http_content
, server_new_schema
)
1329 if r
is not None: print "http_post_serves: Warning: remove extra items ", r
1330 change_keys_http2db(http_content
['server'], http2db_server
)
1331 extended_dict
= http_content
['server'].get('extended', None)
1332 if extended_dict
is not None:
1333 result
, content
= check_extended(extended_dict
, True)
1335 print "http_post_servers wrong input extended error %d %s" % (result
, content
)
1336 bottle
.abort(-result
, content
)
1338 convert_bandwidth(extended_dict
)
1339 if 'devices' in extended_dict
: change_keys_http2db(extended_dict
['devices'], http2db_server
)
1341 server
= http_content
['server']
1342 server_start
= server
.get('start', 'yes')
1343 server
['tenant_id'] = tenant_id
1344 #check flavor valid and take info
1345 result
, content
= my
.db
.get_table(FROM
='tenants_flavors as tf join flavors as f on tf.flavor_id=f.uuid',
1346 SELECT
=('ram','vcpus','extended'), WHERE
={'uuid':server
['flavor_id'], 'tenant_id':tenant_id
})
1348 bottle
.abort(HTTP_Not_Found
, 'flavor_id %s not found' % server
['flavor_id'])
1350 server
['flavor']=content
[0]
1351 #check image valid and take info
1352 result
, content
= my
.db
.get_table(FROM
='tenants_images as ti join images as i on ti.image_id=i.uuid',
1353 SELECT
=('path','metadata'), WHERE
={'uuid':server
['image_id'], 'tenant_id':tenant_id
, "status":"ACTIVE"})
1355 bottle
.abort(HTTP_Not_Found
, 'image_id %s not found or not ACTIVE' % server
['image_id'])
1357 server
['image']=content
[0]
1358 if "hosts_id" in server
:
1359 result
, content
= my
.db
.get_table(FROM
='hosts', SELECT
=('uuid',), WHERE
={'uuid': server
['host_id']})
1361 bottle
.abort(HTTP_Not_Found
, 'hostId %s not found' % server
['host_id'])
1363 #print json.dumps(server, indent=4)
1365 result
, content
= ht
.create_server(server
, config_dic
['db'], config_dic
['db_lock'], config_dic
['mode']=='normal')
1368 #Insert instance to database
1371 print "inserting at DB"
1373 if server_start
== 'no':
1374 content
['status'] = 'INACTIVE'
1376 new_instance_result
, new_instance
= my
.db
.new_instance(content
, nets
, ports_to_free
)
1377 if new_instance_result
< 0:
1378 print "Error http_post_servers() :", new_instance_result
, new_instance
1379 bottle
.abort(-new_instance_result
, new_instance
)
1382 print "inserted at DB"
1384 for port
in ports_to_free
:
1385 r
,c
= config_dic
['host_threads'][ server
['host_id'] ].insert_task( 'restore-iface',*port
)
1387 print ' http_post_servers ERROR RESTORE IFACE!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!' + c
1390 r
,c
= config_dic
['of_thread'].insert_task("update-net", net
)
1392 print ':http_post_servers ERROR UPDATING NETS !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!' + c
1396 #look for dhcp ip address
1397 r2
, c2
= my
.db
.get_table(FROM
="ports", SELECT
=["mac", "net_id"], WHERE
={"instance_id": new_instance
})
1398 if r2
>0 and config_dic
.get("dhcp_server"):
1400 if iface
["net_id"] in config_dic
["dhcp_nets"]:
1401 #print "dhcp insert add task"
1402 r
,c
= config_dic
['dhcp_thread'].insert_task("add", iface
["mac"])
1404 print ':http_post_servers ERROR UPDATING dhcp_server !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!' + c
1408 server
['uuid'] = new_instance
1409 #server_start = server.get('start', 'yes')
1410 if server_start
!= 'no':
1411 server
['paused'] = True if server_start
== 'paused' else False
1412 server
['action'] = {"start":None}
1413 server
['status'] = "CREATING"
1415 r
,c
= config_dic
['host_threads'][ server
['host_id'] ].insert_task( 'instance',server
)
1417 my
.db
.update_rows('instances', {'status':"ERROR"}, {'uuid':server
['uuid'], 'last_error':c
}, log
=True)
1419 return http_get_server_id(tenant_id
, new_instance
)
1421 bottle
.abort(HTTP_Bad_Request
, content
)
1424 def http_server_action(server_id
, tenant_id
, action
):
1425 '''Perform actions over a server as resume, reboot, terminate, ...'''
1426 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
1427 server
={"uuid": server_id
, "action":action
}
1428 where
={'uuid': server_id
}
1429 if tenant_id
!='any':
1430 where
['tenant_id']= tenant_id
1431 result
, content
= my
.db
.get_table(FROM
='instances', WHERE
=where
)
1433 bottle
.abort(HTTP_Not_Found
, "server %s not found" % server_id
)
1436 print "http_post_server_action error getting data %d %s" % (result
, content
)
1437 bottle
.abort(HTTP_Internal_Server_Error
, content
)
1439 server
.update(content
[0])
1440 tenant_id
= server
["tenant_id"]
1442 #TODO check a right content
1444 if 'terminate' in action
:
1445 new_status
='DELETING'
1446 elif server
['status'] == 'ERROR': #or server['status'] == 'CREATING':
1447 if 'terminate' not in action
and 'rebuild' not in action
:
1448 bottle
.abort(HTTP_Method_Not_Allowed
, "Server is in ERROR status, must be rebuit or deleted ")
1450 # elif server['status'] == 'INACTIVE':
1451 # if 'start' not in action and 'createImage' not in action:
1452 # bottle.abort(HTTP_Method_Not_Allowed, "The only possible action over an instance in 'INACTIVE' status is 'start'")
1454 # if 'start' in action:
1455 # new_status='CREATING'
1456 # server['paused']='no'
1457 # elif server['status'] == 'PAUSED':
1458 # if 'resume' not in action:
1459 # bottle.abort(HTTP_Method_Not_Allowed, "The only possible action over an instance in 'PAUSED' status is 'resume'")
1461 # elif server['status'] == 'ACTIVE':
1462 # if 'pause' not in action and 'reboot'not in action and 'shutoff'not in action:
1463 # bottle.abort(HTTP_Method_Not_Allowed, "The only possible action over an instance in 'ACTIVE' status is 'pause','reboot' or 'shutoff'")
1466 if 'start' in action
or 'createImage' in action
or 'rebuild' in action
:
1467 #check image valid and take info
1468 image_id
= server
['image_id']
1469 if 'createImage' in action
:
1470 if 'imageRef' in action
['createImage']:
1471 image_id
= action
['createImage']['imageRef']
1472 elif 'disk' in action
['createImage']:
1473 result
, content
= my
.db
.get_table(FROM
='instance_devices',
1474 SELECT
=('image_id','dev'), WHERE
={'instance_id':server
['uuid'],"type":"disk"})
1476 bottle
.abort(HTTP_Not_Found
, 'disk not found for server')
1480 if action
['createImage']['imageRef']['disk'] != None:
1481 for disk
in content
:
1482 if disk
['dev'] == action
['createImage']['imageRef']['disk']:
1483 disk_id
= disk
['image_id']
1486 bottle
.abort(HTTP_Not_Found
, 'disk %s not found for server' % action
['createImage']['imageRef']['disk'])
1489 bottle
.abort(HTTP_Not_Found
, 'more than one disk found for server' )
1493 image_id
= content
[0]['image_id']
1495 result
, content
= my
.db
.get_table(FROM
='tenants_images as ti join images as i on ti.image_id=i.uuid',
1496 SELECT
=('path','metadata'), WHERE
={'uuid':image_id
, 'tenant_id':tenant_id
, "status":"ACTIVE"})
1498 bottle
.abort(HTTP_Not_Found
, 'image_id %s not found or not ACTIVE' % image_id
)
1500 if content
[0]['metadata'] is not None:
1502 metadata
= json
.loads(content
[0]['metadata'])
1504 return -HTTP_Internal_Server_Error
, "Can not decode image metadata"
1505 content
[0]['metadata']=metadata
1507 content
[0]['metadata'] = {}
1508 server
['image']=content
[0]
1509 if 'createImage' in action
:
1510 action
['createImage']['source'] = {'image_id': image_id
, 'path': content
[0]['path']}
1511 if 'createImage' in action
:
1512 #Create an entry in Database for the new image
1513 new_image
={'status':'BUILD', 'progress': 0 }
1514 new_image_metadata
=content
[0]
1515 if 'metadata' in server
['image'] and server
['image']['metadata'] != None:
1516 new_image_metadata
.update(server
['image']['metadata'])
1517 new_image_metadata
= {"use_incremental":"no"}
1518 if 'metadata' in action
['createImage']:
1519 new_image_metadata
.update(action
['createImage']['metadata'])
1520 new_image
['metadata'] = json
.dumps(new_image_metadata
)
1521 new_image
['name'] = action
['createImage'].get('name', None)
1522 new_image
['description'] = action
['createImage'].get('description', None)
1523 new_image
['uuid']=my
.db
.new_uuid()
1524 if 'path' in action
['createImage']:
1525 new_image
['path'] = action
['createImage']['path']
1527 new_image
['path']="/provisional/path/" + new_image
['uuid']
1528 result
, image_uuid
= my
.db
.new_image(new_image
, tenant_id
)
1530 bottle
.abort(HTTP_Bad_Request
, 'Error: ' + image_uuid
)
1532 server
['new_image'] = new_image
1536 r
,c
= config_dic
['host_threads'][ server
['host_id'] ].insert_task( 'instance',server
)
1538 print "Task queue full at host ", server
['host_id']
1539 bottle
.abort(HTTP_Request_Timeout
, c
)
1540 if 'createImage' in action
and result
>= 0:
1541 return http_get_image_id(tenant_id
, image_uuid
)
1543 #Update DB only for CREATING or DELETING status
1544 data
={'result' : 'in process'}
1545 if new_status
!= None and new_status
== 'DELETING':
1548 #look for dhcp ip address
1549 r2
, c2
= my
.db
.get_table(FROM
="ports", SELECT
=["mac", "net_id"], WHERE
={"instance_id": server_id
})
1550 r
,c
= my
.db
.delete_instance(server_id
, tenant_id
, nets
, ports_to_free
, "requested by http")
1551 for port
in ports_to_free
:
1552 r1
,c1
= config_dic
['host_threads'][ server
['host_id'] ].insert_task( 'restore-iface',*port
)
1554 print ' http_post_server_action error at server deletion ERROR resore-iface !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!' + c1
1555 data
={'result' : 'deleting in process, but ifaces cannot be restored!!!!!'}
1557 r1
,c1
= config_dic
['of_thread'].insert_task("update-net", net
)
1559 print ' http_post_server_action error at server deletion ERROR UPDATING NETS !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!' + c1
1560 data
={'result' : 'deleting in process, but openflow rules cannot be deleted!!!!!'}
1561 #look for dhcp ip address
1562 if r2
>0 and config_dic
.get("dhcp_server"):
1564 if iface
["net_id"] in config_dic
["dhcp_nets"]:
1565 r
,c
= config_dic
['dhcp_thread'].insert_task("del", iface
["mac"])
1566 #print "dhcp insert del task"
1568 print ':http_post_servers ERROR UPDATING dhcp_server !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!' + c
1570 return format_out(data
)
1574 @bottle.route(url_base
+ '/<tenant_id>/servers/<server_id>', method
='DELETE')
1575 def http_delete_server_id(tenant_id
, server_id
):
1576 '''delete a server'''
1577 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
1578 #check valid tenant_id
1579 result
,content
= check_valid_tenant(my
, tenant_id
)
1581 bottle
.abort(result
, content
)
1584 return http_server_action(server_id
, tenant_id
, {"terminate":None} )
1587 @bottle.route(url_base
+ '/<tenant_id>/servers/<server_id>/action', method
='POST')
1588 def http_post_server_action(tenant_id
, server_id
):
1589 '''take an action over a server'''
1590 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
1591 #check valid tenant_id
1592 result
,content
= check_valid_tenant(my
, tenant_id
)
1594 bottle
.abort(result
, content
)
1596 http_content
= format_in( server_action_schema
)
1597 #r = remove_extra_items(http_content, server_action_schema)
1598 #if r is not None: print "http_post_server_action: Warning: remove extra items ", r
1600 return http_server_action(server_id
, tenant_id
, http_content
)
1607 @bottle.route(url_base
+ '/networks', method
='GET')
1608 def http_get_networks():
1609 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
1611 select_
,where_
,limit_
= filter_query_string(bottle
.request
.query
, http2db_network
,
1612 ('id','name','tenant_id','type',
1613 'shared','provider:vlan','status','last_error','admin_state_up','provider:physical') )
1614 #TODO temporally remove tenant_id
1615 if "tenant_id" in where_
:
1616 del where_
["tenant_id"]
1617 result
, content
= my
.db
.get_table(SELECT
=select_
, FROM
='nets', WHERE
=where_
, LIMIT
=limit_
)
1619 print "http_get_networks error %d %s" % (result
, content
)
1620 bottle
.abort(-result
, content
)
1622 convert_boolean(content
, ('shared', 'admin_state_up', 'enable_dhcp') )
1623 delete_nulls(content
)
1624 change_keys_http2db(content
, http2db_network
, reverse
=True)
1625 data
={'networks' : content
}
1626 return format_out(data
)
1628 @bottle.route(url_base
+ '/networks/<network_id>', method
='GET')
1629 def http_get_network_id(network_id
):
1630 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
1632 where_
= bottle
.request
.query
1633 where_
['uuid'] = network_id
1634 result
, content
= my
.db
.get_table(FROM
='nets', WHERE
=where_
, LIMIT
=100)
1637 print "http_get_networks_id error %d %s" % (result
, content
)
1638 bottle
.abort(-result
, content
)
1640 print "http_get_networks_id network '%s' not found" % network_id
1641 bottle
.abort(HTTP_Not_Found
, 'network %s not found' % network_id
)
1643 convert_boolean(content
, ('shared', 'admin_state_up', 'enale_dhcp') )
1644 change_keys_http2db(content
, http2db_network
, reverse
=True)
1646 result
, ports
= my
.db
.get_table(FROM
='ports', SELECT
=('uuid as port_id',),
1647 WHERE
={'net_id': network_id
}, LIMIT
=100)
1649 content
[0]['ports'] = ports
1650 delete_nulls(content
[0])
1651 data
={'network' : content
[0]}
1652 return format_out(data
)
1654 @bottle.route(url_base
+ '/networks', method
='POST')
1655 def http_post_networks():
1656 '''insert a network into the database.'''
1657 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
1659 http_content
= format_in( network_new_schema
)
1660 r
= remove_extra_items(http_content
, network_new_schema
)
1661 if r
is not None: print "http_post_networks: Warning: remove extra items ", r
1662 change_keys_http2db(http_content
['network'], http2db_network
)
1663 network
=http_content
['network']
1664 #check valid tenant_id
1665 tenant_id
= network
.get('tenant_id')
1667 result
, _
= my
.db
.get_table(FROM
='tenants', SELECT
=('uuid',), WHERE
={'uuid': tenant_id
,"enabled":True})
1669 bottle
.abort(HTTP_Not_Found
, 'tenant %s not found or not enabled' % tenant_id
)
1673 net_provider
= network
.get('provider')
1674 net_type
= network
.get('type')
1675 net_vlan
= network
.get("vlan")
1676 net_bind_net
= network
.get("bind_net")
1677 net_bind_type
= network
.get("bind_type")
1678 name
= network
["name"]
1680 #check if network name ends with :<vlan_tag> and network exist in order to make and automated bindning
1681 vlan_index
=name
.rfind(":")
1682 if net_bind_net
==None and net_bind_type
==None and vlan_index
> 1:
1684 vlan_tag
= int(name
[vlan_index
+1:])
1685 if vlan_tag
>0 and vlan_tag
< 4096:
1686 net_bind_net
= name
[:vlan_index
]
1687 net_bind_type
= "vlan:" + name
[vlan_index
+1:]
1691 if net_bind_net
!= None:
1692 #look for a valid net
1693 if check_valid_uuid(net_bind_net
):
1694 net_bind_key
= "uuid"
1696 net_bind_key
= "name"
1697 result
, content
= my
.db
.get_table(FROM
='nets', WHERE
={net_bind_key
: net_bind_net
} )
1699 bottle
.abort(HTTP_Internal_Server_Error
, 'getting nets from db ' + content
)
1702 bottle
.abort(HTTP_Bad_Request
, "bind_net %s '%s'not found" % (net_bind_key
, net_bind_net
) )
1705 bottle
.abort(HTTP_Bad_Request
, "more than one bind_net %s '%s' found, use uuid" % (net_bind_key
, net_bind_net
) )
1707 network
["bind_net"] = content
[0]["uuid"]
1708 if net_bind_type
!= None:
1709 if net_bind_type
[0:5] != "vlan:":
1710 bottle
.abort(HTTP_Bad_Request
, "bad format for 'bind_type', must be 'vlan:<tag>'")
1712 if int(net_bind_type
[5:]) > 4095 or int(net_bind_type
[5:])<=0 :
1713 bottle
.abort(HTTP_Bad_Request
, "bad format for 'bind_type', must be 'vlan:<tag>' with a tag between 1 and 4095")
1715 network
["bind_type"] = net_bind_type
1717 if net_provider
!=None:
1718 if net_provider
[:9]=="openflow:":
1720 if net_type
!="ptp" and net_type
!="data":
1721 bottle
.abort(HTTP_Bad_Request
, "Only 'ptp' or 'data' net types can be bound to 'openflow'")
1726 if net_type
!="bridge_man" and net_type
!="bridge_data":
1727 bottle
.abort(HTTP_Bad_Request
, "Only 'bridge_man' or 'bridge_data' net types can be bound to 'bridge', 'macvtap' or 'default")
1729 net_type
='bridge_man'
1732 net_type
='bridge_man'
1734 if net_provider
!= None:
1735 if net_provider
[:7]=='bridge:':
1736 #check it is one of the pre-provisioned bridges
1737 bridge_net_name
= net_provider
[7:]
1738 for brnet
in config_dic
['bridge_nets']:
1739 if brnet
[0]==bridge_net_name
: # free
1740 if brnet
[3] != None:
1741 bottle
.abort(HTTP_Conflict
, "invalid 'provider:physical', bridge '%s' is already used" % bridge_net_name
)
1746 # if bridge_net==None:
1747 # 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)
1749 elif net_type
=='bridge_data' or net_type
=='bridge_man':
1750 #look for a free precreated nets
1751 for brnet
in config_dic
['bridge_nets']:
1752 if brnet
[3]==None: # free
1753 if bridge_net
!= None:
1754 if net_type
=='bridge_man': #look for the smaller speed
1755 if brnet
[2] < bridge_net
[2]: bridge_net
= brnet
1756 else: #look for the larger speed
1757 if brnet
[2] > bridge_net
[2]: bridge_net
= brnet
1761 if bridge_net
==None:
1762 bottle
.abort(HTTP_Bad_Request
, "Max limits of bridge networks reached. Future versions of VIM will overcome this limit")
1765 print "using net", bridge_net
1766 net_provider
= "bridge:"+bridge_net
[0]
1767 net_vlan
= bridge_net
[1]
1768 if net_vlan
==None and (net_type
=="data" or net_type
=="ptp"):
1769 net_vlan
= my
.db
.get_free_net_vlan()
1771 bottle
.abort(HTTP_Internal_Server_Error
, "Error getting an available vlan")
1774 network
['provider'] = net_provider
1775 network
['type'] = net_type
1776 network
['vlan'] = net_vlan
1777 result
, content
= my
.db
.new_row('nets', network
, True, True)
1780 if bridge_net
!=None:
1781 bridge_net
[3] = content
1783 if config_dic
.get("dhcp_server"):
1784 if network
["name"] in config_dic
["dhcp_server"].get("nets", () ):
1785 config_dic
["dhcp_nets"].append(content
)
1786 print "dhcp_server: add new net", content
1787 elif bridge_net
!= None and bridge_net
[0] in config_dic
["dhcp_server"].get("bridge_ifaces", () ):
1788 config_dic
["dhcp_nets"].append(content
)
1789 print "dhcp_server: add new net", content
1790 return http_get_network_id(content
)
1792 print "http_post_networks error %d %s" % (result
, content
)
1793 bottle
.abort(-result
, content
)
1797 @bottle.route(url_base
+ '/networks/<network_id>', method
='PUT')
1798 def http_put_network_id(network_id
):
1799 '''update a network_id into the database.'''
1800 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
1802 http_content
= format_in( network_update_schema
)
1803 r
= remove_extra_items(http_content
, network_update_schema
)
1804 change_keys_http2db(http_content
['network'], http2db_network
)
1805 network
=http_content
['network']
1807 #Look for the previous data
1808 where_
= {'uuid': network_id
}
1809 result
, network_old
= my
.db
.get_table(FROM
='nets', WHERE
=where_
)
1811 print "http_put_network_id error %d %s" % (result
, network_old
)
1812 bottle
.abort(-result
, network_old
)
1815 print "http_put_network_id network '%s' not found" % network_id
1816 bottle
.abort(HTTP_Not_Found
, 'network %s not found' % network_id
)
1819 nbports
, content
= my
.db
.get_table(FROM
='ports', SELECT
=('uuid as port_id',),
1820 WHERE
={'net_id': network_id
}, LIMIT
=100)
1822 print "http_put_network_id error %d %s" % (result
, network_old
)
1823 bottle
.abort(-result
, content
)
1826 if 'type' in network
and network
['type'] != network_old
[0]['type']:
1827 bottle
.abort(HTTP_Method_Not_Allowed
, "Can not change type of network while having ports attached")
1828 if 'vlan' in network
and network
['vlan'] != network_old
[0]['vlan']:
1829 bottle
.abort(HTTP_Method_Not_Allowed
, "Can not change vlan of network while having ports attached")
1832 net_provider
= network
.get('provider', network_old
[0]['provider'])
1833 net_type
= network
.get('type', network_old
[0]['type'])
1834 net_bind_net
= network
.get("bind_net")
1835 net_bind_type
= network
.get("bind_type")
1836 if net_bind_net
!= None:
1837 #look for a valid net
1838 if check_valid_uuid(net_bind_net
):
1839 net_bind_key
= "uuid"
1841 net_bind_key
= "name"
1842 result
, content
= my
.db
.get_table(FROM
='nets', WHERE
={net_bind_key
: net_bind_net
} )
1844 bottle
.abort(HTTP_Internal_Server_Error
, 'getting nets from db ' + content
)
1847 bottle
.abort(HTTP_Bad_Request
, "bind_net %s '%s'not found" % (net_bind_key
, net_bind_net
) )
1850 bottle
.abort(HTTP_Bad_Request
, "more than one bind_net %s '%s' found, use uuid" % (net_bind_key
, net_bind_net
) )
1852 network
["bind_net"] = content
[0]["uuid"]
1853 if net_bind_type
!= None:
1854 if net_bind_type
[0:5] != "vlan:":
1855 bottle
.abort(HTTP_Bad_Request
, "bad format for 'bind_type', must be 'vlan:<tag>'")
1857 if int(net_bind_type
[5:]) > 4095 or int(net_bind_type
[5:])<=0 :
1858 bottle
.abort(HTTP_Bad_Request
, "bad format for 'bind_type', must be 'vlan:<tag>' with a tag between 1 and 4095")
1860 if net_provider
!=None:
1861 if net_provider
[:9]=="openflow:":
1862 if net_type
!="ptp" and net_type
!="data":
1863 bottle
.abort(HTTP_Bad_Request
, "Only 'ptp' or 'data' net types can be bound to 'openflow'")
1865 if net_type
!="bridge_man" and net_type
!="bridge_data":
1866 bottle
.abort(HTTP_Bad_Request
, "Only 'bridge_man' or 'bridge_data' net types can be bound to 'bridge', 'macvtap' or 'default")
1868 #insert in data base
1869 result
, content
= my
.db
.update_rows('nets', network
, WHERE
={'uuid': network_id
}, log
=True )
1871 if result
>0: # and nbports>0 and 'admin_state_up' in network and network['admin_state_up'] != network_old[0]['admin_state_up']:
1872 r
,c
= config_dic
['of_thread'].insert_task("update-net", network_id
)
1874 print "http_put_network_id error while launching openflow rules"
1875 bottle
.abort(HTTP_Internal_Server_Error
, c
)
1876 if config_dic
.get("dhcp_server"):
1877 if network_id
in config_dic
["dhcp_nets"]:
1878 config_dic
["dhcp_nets"].remove(network_id
)
1879 print "dhcp_server: delete net", network_id
1880 if network
.get("name", network_old
["name"]) in config_dic
["dhcp_server"].get("nets", () ):
1881 config_dic
["dhcp_nets"].append(network_id
)
1882 print "dhcp_server: add new net", network_id
1884 net_bind
= network
.get("bind", network_old
["bind"] )
1885 if net_bind
and net_bind
[:7]=="bridge:" and net_bind
[7:] in config_dic
["dhcp_server"].get("bridge_ifaces", () ):
1886 config_dic
["dhcp_nets"].append(network_id
)
1887 print "dhcp_server: add new net", network_id
1888 return http_get_network_id(network_id
)
1890 bottle
.abort(-result
, content
)
1894 @bottle.route(url_base
+ '/networks/<network_id>', method
='DELETE')
1895 def http_delete_network_id(network_id
):
1896 '''delete a network_id from the database.'''
1897 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
1899 #delete from the data base
1900 result
, content
= my
.db
.delete_row('nets', network_id
)
1903 bottle
.abort(HTTP_Not_Found
, content
)
1905 for brnet
in config_dic
['bridge_nets']:
1906 if brnet
[3]==network_id
:
1909 if config_dic
.get("dhcp_server") and network_id
in config_dic
["dhcp_nets"]:
1910 config_dic
["dhcp_nets"].remove(network_id
)
1911 print "dhcp_server: delete net", network_id
1912 data
={'result' : content
}
1913 return format_out(data
)
1915 print "http_delete_network_id error",result
, content
1916 bottle
.abort(-result
, content
)
1921 @bottle.route(url_base
+ '/networks/<network_id>/openflow', method
='GET')
1922 def http_get_openflow_id(network_id
):
1923 '''To obtain the list of openflow rules of a network
1925 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
1927 if network_id
=='all':
1930 where_
={"net_id": network_id
}
1931 result
, content
= my
.db
.get_table(SELECT
=("name","net_id","priority","vlan_id","ingress_port","src_mac","dst_mac","actions"),
1932 WHERE
=where_
, FROM
='of_flows')
1934 bottle
.abort(-result
, content
)
1936 data
={'openflow-rules' : content
}
1937 return format_out(data
)
1939 @bottle.route(url_base
+ '/networks/<network_id>/openflow', method
='PUT')
1940 def http_put_openflow_id(network_id
):
1941 '''To make actions over the net. The action is to reinstall the openflow rules
1942 network_id can be 'all'
1944 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
1946 bottle
.abort(HTTP_Unauthorized
, "Needed admin privileges")
1949 if network_id
=='all':
1952 where_
={"uuid": network_id
}
1953 result
, content
= my
.db
.get_table(SELECT
=("uuid","type"), WHERE
=where_
, FROM
='nets')
1955 bottle
.abort(-result
, content
)
1959 if net
["type"]!="ptp" and net
["type"]!="data":
1962 r
,c
= config_dic
['of_thread'].insert_task("update-net", net
['uuid'])
1964 print "http_put_openflow_id error while launching openflow rules"
1965 bottle
.abort(HTTP_Internal_Server_Error
, c
)
1966 data
={'result' : str(result
)+" nets updates"}
1967 return format_out(data
)
1969 @bottle.route(url_base
+ '/networks/openflow/clear', method
='DELETE')
1970 @bottle.route(url_base
+ '/networks/clear/openflow', method
='DELETE')
1971 def http_clear_openflow_rules():
1972 '''To make actions over the net. The action is to delete ALL openflow rules
1974 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
1976 bottle
.abort(HTTP_Unauthorized
, "Needed admin privileges")
1979 r
,c
= config_dic
['of_thread'].insert_task("clear-all")
1981 print "http_delete_openflow_id error while launching openflow rules"
1982 bottle
.abort(HTTP_Internal_Server_Error
, c
)
1985 data
={'result' : " Clearing openflow rules in process"}
1986 return format_out(data
)
1988 @bottle.route(url_base
+ '/networks/openflow/ports', method
='GET')
1989 def http_get_openflow_ports():
1990 '''Obtain switch ports names of openflow controller
1992 data
={'ports' : config_dic
['of_thread'].OF_connector
.pp2ofi
}
1993 return format_out(data
)
2000 @bottle.route(url_base
+ '/ports', method
='GET')
2001 def http_get_ports():
2003 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
2004 select_
,where_
,limit_
= filter_query_string(bottle
.request
.query
, http2db_port
,
2005 ('id','name','tenant_id','network_id','vpci','mac_address','device_owner','device_id',
2006 'binding:switch_port','binding:vlan','bandwidth','status','admin_state_up','ip_address') )
2007 #result, content = my.db.get_ports(where_)
2008 result
, content
= my
.db
.get_table(SELECT
=select_
, WHERE
=where_
, FROM
='ports',LIMIT
=limit_
)
2010 print "http_get_ports Error", result
, content
2011 bottle
.abort(-result
, content
)
2014 convert_boolean(content
, ('admin_state_up',) )
2015 delete_nulls(content
)
2016 change_keys_http2db(content
, http2db_port
, reverse
=True)
2017 data
={'ports' : content
}
2018 return format_out(data
)
2020 @bottle.route(url_base
+ '/ports/<port_id>', method
='GET')
2021 def http_get_port_id(port_id
):
2022 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
2024 result
, content
= my
.db
.get_table(WHERE
={'uuid': port_id
}, FROM
='ports')
2026 print "http_get_ports error", result
, content
2027 bottle
.abort(-result
, content
)
2029 print "http_get_ports port '%s' not found" % str(port_id
)
2030 bottle
.abort(HTTP_Not_Found
, 'port %s not found' % port_id
)
2032 convert_boolean(content
, ('admin_state_up',) )
2033 delete_nulls(content
)
2034 change_keys_http2db(content
, http2db_port
, reverse
=True)
2035 data
={'port' : content
[0]}
2036 return format_out(data
)
2039 @bottle.route(url_base
+ '/ports', method
='POST')
2040 def http_post_ports():
2041 '''insert an external port into the database.'''
2042 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
2044 bottle
.abort(HTTP_Unauthorized
, "Needed admin privileges")
2046 http_content
= format_in( port_new_schema
)
2047 r
= remove_extra_items(http_content
, port_new_schema
)
2048 if r
is not None: print "http_post_ports: Warning: remove extra items ", r
2049 change_keys_http2db(http_content
['port'], http2db_port
)
2050 port
=http_content
['port']
2052 port
['type'] = 'external'
2053 if 'net_id' in port
and port
['net_id'] == None:
2056 if 'net_id' in port
:
2057 #check that new net has the correct type
2058 result
, new_net
= my
.db
.check_target_net(port
['net_id'], None, 'external' )
2060 bottle
.abort(HTTP_Bad_Request
, new_net
)
2062 #insert in data base
2063 result
, uuid
= my
.db
.new_row('ports', port
, True, True)
2065 if 'net_id' in port
:
2066 r
,c
= config_dic
['of_thread'].insert_task("update-net", port
['net_id'])
2068 print "http_post_ports error while launching openflow rules"
2069 bottle
.abort(HTTP_Internal_Server_Error
, c
)
2070 return http_get_port_id(uuid
)
2072 bottle
.abort(-result
, uuid
)
2075 @bottle.route(url_base
+ '/ports/<port_id>', method
='PUT')
2076 def http_put_port_id(port_id
):
2077 '''update a port_id into the database.'''
2079 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
2081 http_content
= format_in( port_update_schema
)
2082 change_keys_http2db(http_content
['port'], http2db_port
)
2083 port_dict
=http_content
['port']
2085 #Look for the previous port data
2086 where_
= {'uuid': port_id
}
2087 result
, content
= my
.db
.get_table(FROM
="ports",WHERE
=where_
)
2089 print "http_put_port_id error", result
, content
2090 bottle
.abort(-result
, content
)
2093 print "http_put_port_id port '%s' not found" % port_id
2094 bottle
.abort(HTTP_Not_Found
, 'port %s not found' % port_id
)
2097 for k
in ('vlan','switch_port','mac_address', 'tenant_id'):
2098 if k
in port_dict
and not my
.admin
:
2099 bottle
.abort(HTTP_Unauthorized
, "Needed admin privileges for changing " + k
)
2103 #change_keys_http2db(port, http2db_port, reverse=True)
2107 if 'net_id' in port_dict
:
2109 old_net
= port
.get('net_id', None)
2110 new_net
= port_dict
['net_id']
2111 if old_net
!= new_net
:
2113 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
2114 if old_net
is not None: nets
.append(old_net
)
2115 if port
['type'] == 'instance:bridge':
2116 bottle
.abort(HTTP_Forbidden
, "bridge interfaces cannot be attached to a different net")
2118 elif port
['type'] == 'external':
2120 bottle
.abort(HTTP_Unauthorized
, "Needed admin privileges")
2124 #check that new net has the correct type
2125 result
, new_net_dict
= my
.db
.check_target_net(new_net
, None, port
['type'] )
2127 #change VLAN for SR-IOV ports
2128 if result
>=0 and port
["type"]=="instance:data" and port
["model"]=="VF": #TODO consider also VFnotShared
2130 port_dict
["vlan"] = None
2132 port_dict
["vlan"] = new_net_dict
["vlan"]
2133 #get host where this VM is allocated
2134 result
, content
= my
.db
.get_table(FROM
="instances",WHERE
={"uuid":port
["instance_id"]})
2136 print "http_put_port_id database error", content
2138 host_id
= content
[0]["host_id"]
2140 #insert in data base
2142 result
, content
= my
.db
.update_rows('ports', port_dict
, WHERE
={'uuid': port_id
}, log
=False )
2144 #Insert task to complete actions
2147 r
,v
= config_dic
['of_thread'].insert_task("update-net", net_id
)
2148 if r
<0: print "Error ********* http_put_port_id update_of_flows: ", v
2149 #TODO Do something if fails
2151 config_dic
['host_threads'][host_id
].insert_task("edit-iface", port_id
, old_net
, new_net
)
2154 return http_get_port_id(port_id
)
2156 bottle
.abort(HTTP_Bad_Request
, content
)
2160 @bottle.route(url_base
+ '/ports/<port_id>', method
='DELETE')
2161 def http_delete_port_id(port_id
):
2162 '''delete a port_id from the database.'''
2163 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
2165 bottle
.abort(HTTP_Unauthorized
, "Needed admin privileges")
2168 #Look for the previous port data
2169 where_
= {'uuid': port_id
, "type": "external"}
2170 result
, ports
= my
.db
.get_table(WHERE
=where_
, FROM
='ports',LIMIT
=100)
2173 print "http_delete_port_id port '%s' not found" % port_id
2174 bottle
.abort(HTTP_Not_Found
, 'port %s not found or device_owner is not external' % port_id
)
2176 #delete from the data base
2177 result
, content
= my
.db
.delete_row('ports', port_id
)
2180 bottle
.abort(HTTP_Not_Found
, content
)
2182 network
= ports
[0].get('net_id', None)
2183 if network
is not None:
2185 r
,c
= config_dic
['of_thread'].insert_task("update-net", network
)
2186 if r
<0: print "!!!!!! http_delete_port_id update_of_flows error", r
, c
2187 data
={'result' : content
}
2188 return format_out(data
)
2190 print "http_delete_port_id error",result
, content
2191 bottle
.abort(-result
, content
)