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 memory
=node
['memory']['node_size'] / (1024*1024*1024)
603 #memory=get_next_2pow(node['memory']['hugepage_nr'])
604 host
['numas'].append( {'numa_socket': node
['id'], 'hugepages': node
['memory']['hugepage_nr'], 'memory':memory
, 'interfaces': interfaces
, 'cores': cores
} )
605 print json
.dumps(host
, indent
=4)
609 result
, content
= my
.db
.new_host(host
)
611 if content
['admin_state_up']:
613 host_test_mode
= True if config_dic
['mode']=='test' or config_dic
['mode']=="OF only" else False
614 host_develop_mode
= True if config_dic
['mode']=='development' else False
615 host_develop_bridge_iface
= config_dic
.get('development_bridge', None)
616 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'],
617 test
=host_test_mode
, image_path
=config_dic
['image_path'],
618 version
=config_dic
['version'], host_id
=content
['uuid'],
619 develop_mode
=host_develop_mode
, develop_bridge_iface
=host_develop_bridge_iface
)
621 config_dic
['host_threads'][ content
['uuid'] ] = thread
624 change_keys_http2db(content
, http2db_host
, reverse
=True)
625 if len(warning_text
)>0:
626 content
["warning"]= warning_text
627 data
={'host' : content
}
628 return format_out(data
)
630 bottle
.abort(HTTP_Bad_Request
, content
)
633 @bottle.route(url_base
+ '/hosts/<host_id>', method
='PUT')
634 def http_put_host_id(host_id
):
635 '''modify a host into the database. All resources are got and inserted'''
636 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
639 bottle
.abort(HTTP_Unauthorized
, "Needed admin privileges")
642 http_content
= format_in( host_edit_schema
)
643 r
= remove_extra_items(http_content
, host_edit_schema
)
644 if r
is not None: print "http_post_host_id: Warning: remove extra items ", r
645 change_keys_http2db(http_content
['host'], http2db_host
)
648 result
, content
= my
.db
.edit_host(host_id
, http_content
['host'])
650 convert_boolean(content
, ('admin_state_up',) )
651 change_keys_http2db(content
, http2db_host
, reverse
=True)
652 data
={'host' : content
}
655 config_dic
['host_threads'][host_id
].name
= content
.get('name',content
['ip_name'])
656 config_dic
['host_threads'][host_id
].user
= content
['user']
657 config_dic
['host_threads'][host_id
].host
= content
['ip_name']
658 config_dic
['host_threads'][host_id
].insert_task("reload")
661 return format_out(data
)
663 bottle
.abort(HTTP_Bad_Request
, content
)
668 @bottle.route(url_base
+ '/hosts/<host_id>', method
='DELETE')
669 def http_delete_host_id(host_id
):
670 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
673 bottle
.abort(HTTP_Unauthorized
, "Needed admin privileges")
674 result
, content
= my
.db
.delete_row('hosts', host_id
)
676 bottle
.abort(HTTP_Not_Found
, content
)
679 if host_id
in config_dic
['host_threads']:
680 config_dic
['host_threads'][host_id
].insert_task("exit")
682 data
={'result' : content
}
683 return format_out(data
)
685 print "http_delete_host_id error",result
, content
686 bottle
.abort(-result
, content
)
695 @bottle.route(url_base
+ '/tenants', method
='GET')
696 def http_get_tenants():
697 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
698 select_
,where_
,limit_
= filter_query_string(bottle
.request
.query
, http2db_tenant
,
699 ('id','name','description','enabled') )
700 result
, content
= my
.db
.get_table(FROM
='tenants', SELECT
=select_
,WHERE
=where_
,LIMIT
=limit_
)
702 print "http_get_tenants Error", content
703 bottle
.abort(-result
, content
)
705 change_keys_http2db(content
, http2db_tenant
, reverse
=True)
706 convert_boolean(content
, ('enabled',))
707 data
={'tenants' : content
}
708 #data['tenants_links'] = dict([('tenant', row['id']) for row in content])
709 return format_out(data
)
711 @bottle.route(url_base
+ '/tenants/<tenant_id>', method
='GET')
712 def http_get_tenant_id(tenant_id
):
713 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
714 result
, content
= my
.db
.get_table(FROM
='tenants', SELECT
=('uuid','name','description', 'enabled'),WHERE
={'uuid': tenant_id
} )
716 print "http_get_tenant_id error %d %s" % (result
, content
)
717 bottle
.abort(-result
, content
)
719 print "http_get_tenant_id tenant '%s' not found" % tenant_id
720 bottle
.abort(HTTP_Not_Found
, "tenant %s not found" % tenant_id
)
722 change_keys_http2db(content
, http2db_tenant
, reverse
=True)
723 convert_boolean(content
, ('enabled',))
724 data
={'tenant' : content
[0]}
725 #data['tenants_links'] = dict([('tenant', row['id']) for row in content])
726 return format_out(data
)
729 @bottle.route(url_base
+ '/tenants', method
='POST')
730 def http_post_tenants():
731 '''insert a tenant into the database.'''
732 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
734 http_content
= format_in( tenant_new_schema
)
735 r
= remove_extra_items(http_content
, tenant_new_schema
)
736 if r
is not None: print "http_post_tenants: Warning: remove extra items ", r
737 change_keys_http2db(http_content
['tenant'], http2db_tenant
)
740 result
, content
= my
.db
.new_tenant(http_content
['tenant'])
743 return http_get_tenant_id(content
)
745 bottle
.abort(-result
, content
)
748 @bottle.route(url_base
+ '/tenants/<tenant_id>', method
='PUT')
749 def http_put_tenant_id(tenant_id
):
750 '''update a tenant into the database.'''
751 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
753 http_content
= format_in( tenant_edit_schema
)
754 r
= remove_extra_items(http_content
, tenant_edit_schema
)
755 if r
is not None: print "http_put_tenant_id: Warning: remove extra items ", r
756 change_keys_http2db(http_content
['tenant'], http2db_tenant
)
759 result
, content
= my
.db
.update_rows('tenants', http_content
['tenant'], WHERE
={'uuid': tenant_id
}, log
=True )
761 return http_get_tenant_id(tenant_id
)
763 bottle
.abort(-result
, content
)
766 @bottle.route(url_base
+ '/tenants/<tenant_id>', method
='DELETE')
767 def http_delete_tenant_id(tenant_id
):
768 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
770 r
, tenants_flavors
= my
.db
.get_table(FROM
='tenants_flavors', SELECT
=('flavor_id','tenant_id'), WHERE
={'tenant_id': tenant_id
})
773 r
, tenants_images
= my
.db
.get_table(FROM
='tenants_images', SELECT
=('image_id','tenant_id'), WHERE
={'tenant_id': tenant_id
})
776 result
, content
= my
.db
.delete_row('tenants', tenant_id
)
778 bottle
.abort(HTTP_Not_Found
, content
)
780 print "alf", tenants_flavors
, tenants_images
781 for flavor
in tenants_flavors
:
782 my
.db
.delete_row_by_key("flavors", "uuid", flavor
['flavor_id'])
783 for image
in tenants_images
:
784 my
.db
.delete_row_by_key("images", "uuid", image
['image_id'])
785 data
={'result' : content
}
786 return format_out(data
)
788 print "http_delete_tenant_id error",result
, content
789 bottle
.abort(-result
, content
)
796 @bottle.route(url_base
+ '/<tenant_id>/flavors', method
='GET')
797 def http_get_flavors(tenant_id
):
798 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
799 #check valid tenant_id
800 result
,content
= check_valid_tenant(my
, tenant_id
)
802 bottle
.abort(result
, content
)
804 select_
,where_
,limit_
= filter_query_string(bottle
.request
.query
, http2db_flavor
,
805 ('id','name','description','public') )
809 from_
='tenants_flavors inner join flavors on tenants_flavors.flavor_id=flavors.uuid'
810 where_
['tenant_id'] = tenant_id
811 result
, content
= my
.db
.get_table(FROM
=from_
, SELECT
=select_
, WHERE
=where_
, LIMIT
=limit_
)
813 print "http_get_flavors Error", content
814 bottle
.abort(-result
, content
)
816 change_keys_http2db(content
, http2db_flavor
, reverse
=True)
818 row
['links']=[ {'href': "/".join( (my
.url_preffix
, tenant_id
, 'flavors', str(row
['id']) ) ), 'rel':'bookmark' } ]
819 data
={'flavors' : content
}
820 return format_out(data
)
822 @bottle.route(url_base
+ '/<tenant_id>/flavors/<flavor_id>', method
='GET')
823 def http_get_flavor_id(tenant_id
, flavor_id
):
824 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
825 #check valid tenant_id
826 result
,content
= check_valid_tenant(my
, tenant_id
)
828 bottle
.abort(result
, content
)
830 select_
,where_
,limit_
= filter_query_string(bottle
.request
.query
, http2db_flavor
,
831 ('id','name','description','ram', 'vcpus', 'extended', 'disk', 'public') )
835 from_
='tenants_flavors as tf inner join flavors as f on tf.flavor_id=f.uuid'
836 where_
['tenant_id'] = tenant_id
837 where_
['uuid'] = flavor_id
838 result
, content
= my
.db
.get_table(SELECT
=select_
, FROM
=from_
, WHERE
=where_
, LIMIT
=limit_
)
841 print "http_get_flavor_id error %d %s" % (result
, content
)
842 bottle
.abort(-result
, content
)
844 print "http_get_flavors_id flavor '%s' not found" % str(flavor_id
)
845 bottle
.abort(HTTP_Not_Found
, 'flavor %s not found' % flavor_id
)
847 change_keys_http2db(content
, http2db_flavor
, reverse
=True)
848 if 'extended' in content
[0] and content
[0]['extended'] is not None:
849 extended
= json
.loads(content
[0]['extended'])
850 if 'devices' in extended
:
851 change_keys_http2db(extended
['devices'], http2db_flavor
, reverse
=True)
852 content
[0]['extended']=extended
853 convert_bandwidth(content
[0], reverse
=True)
854 content
[0]['links']=[ {'href': "/".join( (my
.url_preffix
, tenant_id
, 'flavors', str(content
[0]['id']) ) ), 'rel':'bookmark' } ]
855 data
={'flavor' : content
[0]}
856 #data['tenants_links'] = dict([('tenant', row['id']) for row in content])
857 return format_out(data
)
860 @bottle.route(url_base
+ '/<tenant_id>/flavors', method
='POST')
861 def http_post_flavors(tenant_id
):
862 '''insert a flavor into the database, and attach to tenant.'''
863 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
864 #check valid tenant_id
865 result
,content
= check_valid_tenant(my
, tenant_id
)
867 bottle
.abort(result
, content
)
868 http_content
= format_in( flavor_new_schema
)
869 r
= remove_extra_items(http_content
, flavor_new_schema
)
870 if r
is not None: print "http_post_flavors: Warning: remove extra items ", r
871 change_keys_http2db(http_content
['flavor'], http2db_flavor
)
872 extended_dict
= http_content
['flavor'].pop('extended', None)
873 if extended_dict
is not None:
874 result
, content
= check_extended(extended_dict
)
876 print "http_post_flavors wrong input extended error %d %s" % (result
, content
)
877 bottle
.abort(-result
, content
)
879 convert_bandwidth(extended_dict
)
880 if 'devices' in extended_dict
: change_keys_http2db(extended_dict
['devices'], http2db_flavor
)
881 http_content
['flavor']['extended'] = json
.dumps(extended_dict
)
883 result
, content
= my
.db
.new_flavor(http_content
['flavor'], tenant_id
)
885 return http_get_flavor_id(tenant_id
, content
)
887 print "http_psot_flavors error %d %s" % (result
, content
)
888 bottle
.abort(-result
, content
)
891 @bottle.route(url_base
+ '/<tenant_id>/flavors/<flavor_id>', method
='DELETE')
892 def http_delete_flavor_id(tenant_id
, flavor_id
):
893 '''Deletes the flavor_id of a tenant. IT removes from tenants_flavors table.'''
894 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
895 #check valid tenant_id
896 result
,content
= check_valid_tenant(my
, tenant_id
)
898 bottle
.abort(result
, content
)
900 result
, content
= my
.db
.delete_image_flavor('flavor', flavor_id
, tenant_id
)
902 bottle
.abort(HTTP_Not_Found
, content
)
904 data
={'result' : content
}
905 return format_out(data
)
907 print "http_delete_flavor_id error",result
, content
908 bottle
.abort(-result
, content
)
911 @bottle.route(url_base
+ '/<tenant_id>/flavors/<flavor_id>/<action>', method
='POST')
912 def http_attach_detach_flavors(tenant_id
, flavor_id
, action
):
913 '''attach/detach an existing flavor in this tenant. That is insert/remove at tenants_flavors table.'''
914 #TODO alf: not tested at all!!!
915 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
916 #check valid tenant_id
917 result
,content
= check_valid_tenant(my
, tenant_id
)
919 bottle
.abort(result
, content
)
921 bottle
.abort(HTTP_Bad_Request
, "Invalid tenant 'any' with this command")
923 if action
!='attach' and action
!= 'detach':
924 bottle
.abort(HTTP_Method_Not_Allowed
, "actions can be attach or detach")
927 #Ensure that flavor exist
928 from_
='tenants_flavors as tf right join flavors as f on tf.flavor_id=f.uuid'
929 where_
={'uuid': flavor_id
}
930 result
, content
= my
.db
.get_table(SELECT
=('public','tenant_id'), FROM
=from_
, WHERE
=where_
)
933 text_error
="Flavor '%s' not found" % flavor_id
935 text_error
="Flavor '%s' not found for tenant '%s'" % (flavor_id
, tenant_id
)
936 bottle
.abort(HTTP_Not_Found
, text_error
)
941 if flavor
['tenant_id']!=None:
942 bottle
.abort(HTTP_Conflict
, "Flavor '%s' already attached to tenant '%s'" % (flavor_id
, tenant_id
))
943 if flavor
['public']=='no' and not my
.admin
:
944 #allow only attaching public flavors
945 bottle
.abort(HTTP_Unauthorized
, "Needed admin rights to attach a private flavor")
948 result
, content
= my
.db
.new_row('tenants_flavors', {'flavor_id':flavor_id
, 'tenant_id': tenant_id
})
950 return http_get_flavor_id(tenant_id
, flavor_id
)
952 if flavor
['tenant_id']==None:
953 bottle
.abort(HTTP_Not_Found
, "Flavor '%s' not attached to tenant '%s'" % (flavor_id
, tenant_id
))
954 result
, content
= my
.db
.delete_row_by_dict(FROM
='tenants_flavors', WHERE
={'flavor_id':flavor_id
, 'tenant_id':tenant_id
})
956 if flavor
['public']=='no':
957 #try to delete the flavor completely to avoid orphan flavors, IGNORE error
958 my
.db
.delete_row_by_dict(FROM
='flavors', WHERE
={'uuid':flavor_id
})
959 data
={'result' : "flavor detached"}
960 return format_out(data
)
962 #if get here is because an error
963 print "http_attach_detach_flavors error %d %s" % (result
, content
)
964 bottle
.abort(-result
, content
)
967 @bottle.route(url_base
+ '/<tenant_id>/flavors/<flavor_id>', method
='PUT')
968 def http_put_flavor_id(tenant_id
, flavor_id
):
969 '''update a flavor_id into the database.'''
970 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
971 #check valid tenant_id
972 result
,content
= check_valid_tenant(my
, tenant_id
)
974 bottle
.abort(result
, content
)
976 http_content
= format_in( flavor_update_schema
)
977 r
= remove_extra_items(http_content
, flavor_update_schema
)
978 if r
is not None: print "http_put_flavor_id: Warning: remove extra items ", r
979 change_keys_http2db(http_content
['flavor'], http2db_flavor
)
980 extended_dict
= http_content
['flavor'].pop('extended', None)
981 if extended_dict
is not None:
982 result
, content
= check_extended(extended_dict
)
984 print "http_put_flavor_id wrong input extended error %d %s" % (result
, content
)
985 bottle
.abort(-result
, content
)
987 convert_bandwidth(extended_dict
)
988 if 'devices' in extended_dict
: change_keys_http2db(extended_dict
['devices'], http2db_flavor
)
989 http_content
['flavor']['extended'] = json
.dumps(extended_dict
)
990 #Ensure that flavor exist
991 where_
={'uuid': flavor_id
}
995 from_
='tenants_flavors as ti inner join flavors as i on ti.flavor_id=i.uuid'
996 where_
['tenant_id'] = tenant_id
997 result
, content
= my
.db
.get_table(SELECT
=('public',), FROM
=from_
, WHERE
=where_
)
999 text_error
="Flavor '%s' not found" % flavor_id
1000 if tenant_id
!='any':
1001 text_error
+=" for tenant '%s'" % flavor_id
1002 bottle
.abort(HTTP_Not_Found
, text_error
)
1005 if content
[0]['public']=='yes' and not my
.admin
:
1006 #allow only modifications over private flavors
1007 bottle
.abort(HTTP_Unauthorized
, "Needed admin rights to edit a public flavor")
1009 #insert in data base
1010 result
, content
= my
.db
.update_rows('flavors', http_content
['flavor'], {'uuid': flavor_id
})
1013 print "http_put_flavor_id error %d %s" % (result
, content
)
1014 bottle
.abort(-result
, content
)
1017 return http_get_flavor_id(tenant_id
, flavor_id
)
1025 @bottle.route(url_base
+ '/<tenant_id>/images', method
='GET')
1026 def http_get_images(tenant_id
):
1027 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
1028 #check valid tenant_id
1029 result
,content
= check_valid_tenant(my
, tenant_id
)
1031 bottle
.abort(result
, content
)
1033 select_
,where_
,limit_
= filter_query_string(bottle
.request
.query
, http2db_image
,
1034 ('id','name','description','path','public') )
1035 if tenant_id
=='any':
1038 from_
='tenants_images inner join images on tenants_images.image_id=images.uuid'
1039 where_
['tenant_id'] = tenant_id
1040 result
, content
= my
.db
.get_table(SELECT
=select_
, FROM
=from_
, WHERE
=where_
, LIMIT
=limit_
)
1042 print "http_get_images Error", content
1043 bottle
.abort(-result
, content
)
1045 change_keys_http2db(content
, http2db_image
, reverse
=True)
1046 #for row in content: row['links']=[ {'href': "/".join( (my.url_preffix, tenant_id, 'images', str(row['id']) ) ), 'rel':'bookmark' } ]
1047 data
={'images' : content
}
1048 return format_out(data
)
1050 @bottle.route(url_base
+ '/<tenant_id>/images/<image_id>', method
='GET')
1051 def http_get_image_id(tenant_id
, image_id
):
1052 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
1053 #check valid tenant_id
1054 result
,content
= check_valid_tenant(my
, tenant_id
)
1056 bottle
.abort(result
, content
)
1058 select_
,where_
,limit_
= filter_query_string(bottle
.request
.query
, http2db_image
,
1059 ('id','name','description','progress', 'status','path', 'created', 'updated','public') )
1060 if tenant_id
=='any':
1063 from_
='tenants_images as ti inner join images as i on ti.image_id=i.uuid'
1064 where_
['tenant_id'] = tenant_id
1065 where_
['uuid'] = image_id
1066 result
, content
= my
.db
.get_table(SELECT
=select_
, FROM
=from_
, WHERE
=where_
, LIMIT
=limit_
)
1069 print "http_get_images error %d %s" % (result
, content
)
1070 bottle
.abort(-result
, content
)
1072 print "http_get_images image '%s' not found" % str(image_id
)
1073 bottle
.abort(HTTP_Not_Found
, 'image %s not found' % image_id
)
1075 convert_datetime2str(content
)
1076 change_keys_http2db(content
, http2db_image
, reverse
=True)
1077 if 'metadata' in content
[0] and content
[0]['metadata'] is not None:
1078 metadata
= json
.loads(content
[0]['metadata'])
1079 content
[0]['metadata']=metadata
1080 content
[0]['links']=[ {'href': "/".join( (my
.url_preffix
, tenant_id
, 'images', str(content
[0]['id']) ) ), 'rel':'bookmark' } ]
1081 data
={'image' : content
[0]}
1082 #data['tenants_links'] = dict([('tenant', row['id']) for row in content])
1083 return format_out(data
)
1085 @bottle.route(url_base
+ '/<tenant_id>/images', method
='POST')
1086 def http_post_images(tenant_id
):
1087 '''insert a image into the database, and attach to tenant.'''
1088 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
1089 #check valid tenant_id
1090 result
,content
= check_valid_tenant(my
, tenant_id
)
1092 bottle
.abort(result
, content
)
1093 http_content
= format_in(image_new_schema
)
1094 r
= remove_extra_items(http_content
, image_new_schema
)
1095 if r
is not None: print "http_post_images: Warning: remove extra items ", r
1096 change_keys_http2db(http_content
['image'], http2db_image
)
1097 metadata_dict
= http_content
['image'].pop('metadata', None)
1098 if metadata_dict
is not None:
1099 http_content
['image']['metadata'] = json
.dumps(metadata_dict
)
1101 host_test_mode
= True if config_dic
['mode']=='test' or config_dic
['mode']=="OF only" else False
1103 image_file
= http_content
['image'].get('path',None)
1104 if os
.path
.exists(image_file
):
1105 http_content
['image']['checksum'] = md5(image_file
)
1106 elif is_url(image_file
):
1109 if not host_test_mode
:
1110 content
= "Image file not found"
1111 print "http_post_images error: %d %s" % (HTTP_Bad_Request
, content
)
1112 bottle
.abort(HTTP_Bad_Request
, content
)
1113 except Exception as e
:
1114 print "ERROR. Unexpected exception: %s" % (str(e
))
1115 bottle
.abort(HTTP_Internal_Server_Error
, type(e
).__name
__ + ": " + str(e
))
1116 #insert in data base
1117 result
, content
= my
.db
.new_image(http_content
['image'], tenant_id
)
1119 return http_get_image_id(tenant_id
, content
)
1121 print "http_post_images error %d %s" % (result
, content
)
1122 bottle
.abort(-result
, content
)
1125 @bottle.route(url_base
+ '/<tenant_id>/images/<image_id>', method
='DELETE')
1126 def http_delete_image_id(tenant_id
, image_id
):
1127 '''Deletes the image_id of a tenant. IT removes from tenants_images table.'''
1128 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
1129 #check valid tenant_id
1130 result
,content
= check_valid_tenant(my
, tenant_id
)
1132 bottle
.abort(result
, content
)
1133 result
, content
= my
.db
.delete_image_flavor('image', image_id
, tenant_id
)
1135 bottle
.abort(HTTP_Not_Found
, content
)
1137 data
={'result' : content
}
1138 return format_out(data
)
1140 print "http_delete_image_id error",result
, content
1141 bottle
.abort(-result
, content
)
1144 @bottle.route(url_base
+ '/<tenant_id>/images/<image_id>/<action>', method
='POST')
1145 def http_attach_detach_images(tenant_id
, image_id
, action
):
1146 '''attach/detach an existing image in this tenant. That is insert/remove at tenants_images table.'''
1147 #TODO alf: not tested at all!!!
1148 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
1149 #check valid tenant_id
1150 result
,content
= check_valid_tenant(my
, tenant_id
)
1152 bottle
.abort(result
, content
)
1153 if tenant_id
=='any':
1154 bottle
.abort(HTTP_Bad_Request
, "Invalid tenant 'any' with this command")
1156 if action
!='attach' and action
!= 'detach':
1157 bottle
.abort(HTTP_Method_Not_Allowed
, "actions can be attach or detach")
1160 #Ensure that image exist
1161 from_
='tenants_images as ti right join images as i on ti.image_id=i.uuid'
1162 where_
={'uuid': image_id
}
1163 result
, content
= my
.db
.get_table(SELECT
=('public','tenant_id'), FROM
=from_
, WHERE
=where_
)
1165 if action
=='attach':
1166 text_error
="Image '%s' not found" % image_id
1168 text_error
="Image '%s' not found for tenant '%s'" % (image_id
, tenant_id
)
1169 bottle
.abort(HTTP_Not_Found
, text_error
)
1173 if action
=='attach':
1174 if image
['tenant_id']!=None:
1175 bottle
.abort(HTTP_Conflict
, "Image '%s' already attached to tenant '%s'" % (image_id
, tenant_id
))
1176 if image
['public']=='no' and not my
.admin
:
1177 #allow only attaching public images
1178 bottle
.abort(HTTP_Unauthorized
, "Needed admin rights to attach a private image")
1180 #insert in data base
1181 result
, content
= my
.db
.new_row('tenants_images', {'image_id':image_id
, 'tenant_id': tenant_id
})
1183 return http_get_image_id(tenant_id
, image_id
)
1185 if image
['tenant_id']==None:
1186 bottle
.abort(HTTP_Not_Found
, "Image '%s' not attached to tenant '%s'" % (image_id
, tenant_id
))
1187 result
, content
= my
.db
.delete_row_by_dict(FROM
='tenants_images', WHERE
={'image_id':image_id
, 'tenant_id':tenant_id
})
1189 if image
['public']=='no':
1190 #try to delete the image completely to avoid orphan images, IGNORE error
1191 my
.db
.delete_row_by_dict(FROM
='images', WHERE
={'uuid':image_id
})
1192 data
={'result' : "image detached"}
1193 return format_out(data
)
1195 #if get here is because an error
1196 print "http_attach_detach_images error %d %s" % (result
, content
)
1197 bottle
.abort(-result
, content
)
1200 @bottle.route(url_base
+ '/<tenant_id>/images/<image_id>', method
='PUT')
1201 def http_put_image_id(tenant_id
, image_id
):
1202 '''update a image_id into the database.'''
1203 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
1204 #check valid tenant_id
1205 result
,content
= check_valid_tenant(my
, tenant_id
)
1207 bottle
.abort(result
, content
)
1209 http_content
= format_in( image_update_schema
)
1210 r
= remove_extra_items(http_content
, image_update_schema
)
1211 if r
is not None: print "http_put_image_id: Warning: remove extra items ", r
1212 change_keys_http2db(http_content
['image'], http2db_image
)
1213 metadata_dict
= http_content
['image'].pop('metadata', None)
1214 if metadata_dict
is not None:
1215 http_content
['image']['metadata'] = json
.dumps(metadata_dict
)
1216 #Ensure that image exist
1217 where_
={'uuid': image_id
}
1218 if tenant_id
=='any':
1221 from_
='tenants_images as ti inner join images as i on ti.image_id=i.uuid'
1222 where_
['tenant_id'] = tenant_id
1223 result
, content
= my
.db
.get_table(SELECT
=('public',), FROM
=from_
, WHERE
=where_
)
1225 text_error
="Image '%s' not found" % image_id
1226 if tenant_id
!='any':
1227 text_error
+=" for tenant '%s'" % image_id
1228 bottle
.abort(HTTP_Not_Found
, text_error
)
1231 if content
[0]['public']=='yes' and not my
.admin
:
1232 #allow only modifications over private images
1233 bottle
.abort(HTTP_Unauthorized
, "Needed admin rights to edit a public image")
1235 #insert in data base
1236 result
, content
= my
.db
.update_rows('images', http_content
['image'], {'uuid': image_id
})
1239 print "http_put_image_id error %d %s" % (result
, content
)
1240 bottle
.abort(-result
, content
)
1243 return http_get_image_id(tenant_id
, image_id
)
1250 @bottle.route(url_base
+ '/<tenant_id>/servers', method
='GET')
1251 def http_get_servers(tenant_id
):
1252 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
1253 result
,content
= check_valid_tenant(my
, tenant_id
)
1255 bottle
.abort(result
, content
)
1258 select_
,where_
,limit_
= filter_query_string(bottle
.request
.query
, http2db_server
,
1259 ('id','name','description','hostId','imageRef','flavorRef','status', 'tenant_id') )
1260 if tenant_id
!='any':
1261 where_
['tenant_id'] = tenant_id
1262 result
, content
= my
.db
.get_table(SELECT
=select_
, FROM
='instances', WHERE
=where_
, LIMIT
=limit_
)
1264 print "http_get_servers Error", content
1265 bottle
.abort(-result
, content
)
1267 change_keys_http2db(content
, http2db_server
, reverse
=True)
1269 tenant_id
= row
.pop('tenant_id')
1270 row
['links']=[ {'href': "/".join( (my
.url_preffix
, tenant_id
, 'servers', str(row
['id']) ) ), 'rel':'bookmark' } ]
1271 data
={'servers' : content
}
1272 return format_out(data
)
1274 @bottle.route(url_base
+ '/<tenant_id>/servers/<server_id>', method
='GET')
1275 def http_get_server_id(tenant_id
, server_id
):
1276 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
1277 #check valid tenant_id
1278 result
,content
= check_valid_tenant(my
, tenant_id
)
1280 bottle
.abort(result
, content
)
1283 result
, content
= my
.db
.get_instance(server_id
)
1285 bottle
.abort(HTTP_Not_Found
, content
)
1287 #change image/flavor-id to id and link
1288 convert_bandwidth(content
, reverse
=True)
1289 convert_datetime2str(content
)
1290 if content
["ram"]==0 : del content
["ram"]
1291 if content
["vcpus"]==0 : del content
["vcpus"]
1292 if 'flavor_id' in content
:
1293 if content
['flavor_id'] is not None:
1294 content
['flavor'] = {'id':content
['flavor_id'],
1295 'links':[{'href': "/".join( (my
.url_preffix
, content
['tenant_id'], 'flavors', str(content
['flavor_id']) ) ), 'rel':'bookmark'}]
1297 del content
['flavor_id']
1298 if 'image_id' in content
:
1299 if content
['image_id'] is not None:
1300 content
['image'] = {'id':content
['image_id'],
1301 'links':[{'href': "/".join( (my
.url_preffix
, content
['tenant_id'], 'images', str(content
['image_id']) ) ), 'rel':'bookmark'}]
1303 del content
['image_id']
1304 change_keys_http2db(content
, http2db_server
, reverse
=True)
1305 if 'extended' in content
:
1306 if 'devices' in content
['extended']: change_keys_http2db(content
['extended']['devices'], http2db_server
, reverse
=True)
1308 data
={'server' : content
}
1309 return format_out(data
)
1311 bottle
.abort(-result
, content
)
1314 @bottle.route(url_base
+ '/<tenant_id>/servers', method
='POST')
1315 def http_post_server_id(tenant_id
):
1316 '''deploys a new server'''
1317 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
1318 #check valid tenant_id
1319 result
,content
= check_valid_tenant(my
, tenant_id
)
1321 bottle
.abort(result
, content
)
1323 if tenant_id
=='any':
1324 bottle
.abort(HTTP_Bad_Request
, "Invalid tenant 'any' with this command")
1326 http_content
= format_in( server_new_schema
)
1327 r
= remove_extra_items(http_content
, server_new_schema
)
1328 if r
is not None: print "http_post_serves: Warning: remove extra items ", r
1329 change_keys_http2db(http_content
['server'], http2db_server
)
1330 extended_dict
= http_content
['server'].get('extended', None)
1331 if extended_dict
is not None:
1332 result
, content
= check_extended(extended_dict
, True)
1334 print "http_post_servers wrong input extended error %d %s" % (result
, content
)
1335 bottle
.abort(-result
, content
)
1337 convert_bandwidth(extended_dict
)
1338 if 'devices' in extended_dict
: change_keys_http2db(extended_dict
['devices'], http2db_server
)
1340 server
= http_content
['server']
1341 server_start
= server
.get('start', 'yes')
1342 server
['tenant_id'] = tenant_id
1343 #check flavor valid and take info
1344 result
, content
= my
.db
.get_table(FROM
='tenants_flavors as tf join flavors as f on tf.flavor_id=f.uuid',
1345 SELECT
=('ram','vcpus','extended'), WHERE
={'uuid':server
['flavor_id'], 'tenant_id':tenant_id
})
1347 bottle
.abort(HTTP_Not_Found
, 'flavor_id %s not found' % server
['flavor_id'])
1349 server
['flavor']=content
[0]
1350 #check image valid and take info
1351 result
, content
= my
.db
.get_table(FROM
='tenants_images as ti join images as i on ti.image_id=i.uuid',
1352 SELECT
=('path','metadata'), WHERE
={'uuid':server
['image_id'], 'tenant_id':tenant_id
, "status":"ACTIVE"})
1354 bottle
.abort(HTTP_Not_Found
, 'image_id %s not found or not ACTIVE' % server
['image_id'])
1356 server
['image']=content
[0]
1357 if "hosts_id" in server
:
1358 result
, content
= my
.db
.get_table(FROM
='hosts', SELECT
=('uuid',), WHERE
={'uuid': server
['host_id']})
1360 bottle
.abort(HTTP_Not_Found
, 'hostId %s not found' % server
['host_id'])
1362 #print json.dumps(server, indent=4)
1364 result
, content
= ht
.create_server(server
, config_dic
['db'], config_dic
['db_lock'], config_dic
['mode']=='normal')
1367 #Insert instance to database
1370 print "inserting at DB"
1372 if server_start
== 'no':
1373 content
['status'] = 'INACTIVE'
1375 new_instance_result
, new_instance
= my
.db
.new_instance(content
, nets
, ports_to_free
)
1376 if new_instance_result
< 0:
1377 print "Error http_post_servers() :", new_instance_result
, new_instance
1378 bottle
.abort(-new_instance_result
, new_instance
)
1381 print "inserted at DB"
1383 for port
in ports_to_free
:
1384 r
,c
= config_dic
['host_threads'][ server
['host_id'] ].insert_task( 'restore-iface',*port
)
1386 print ' http_post_servers ERROR RESTORE IFACE!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!' + c
1389 r
,c
= config_dic
['of_thread'].insert_task("update-net", net
)
1391 print ':http_post_servers ERROR UPDATING NETS !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!' + c
1395 #look for dhcp ip address
1396 r2
, c2
= my
.db
.get_table(FROM
="ports", SELECT
=["mac", "net_id"], WHERE
={"instance_id": new_instance
})
1397 if r2
>0 and config_dic
.get("dhcp_server"):
1399 if iface
["net_id"] in config_dic
["dhcp_nets"]:
1400 #print "dhcp insert add task"
1401 r
,c
= config_dic
['dhcp_thread'].insert_task("add", iface
["mac"])
1403 print ':http_post_servers ERROR UPDATING dhcp_server !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!' + c
1407 server
['uuid'] = new_instance
1408 #server_start = server.get('start', 'yes')
1409 if server_start
!= 'no':
1410 server
['paused'] = True if server_start
== 'paused' else False
1411 server
['action'] = {"start":None}
1412 server
['status'] = "CREATING"
1414 r
,c
= config_dic
['host_threads'][ server
['host_id'] ].insert_task( 'instance',server
)
1416 my
.db
.update_rows('instances', {'status':"ERROR"}, {'uuid':server
['uuid'], 'last_error':c
}, log
=True)
1418 return http_get_server_id(tenant_id
, new_instance
)
1420 bottle
.abort(HTTP_Bad_Request
, content
)
1423 def http_server_action(server_id
, tenant_id
, action
):
1424 '''Perform actions over a server as resume, reboot, terminate, ...'''
1425 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
1426 server
={"uuid": server_id
, "action":action
}
1427 where
={'uuid': server_id
}
1428 if tenant_id
!='any':
1429 where
['tenant_id']= tenant_id
1430 result
, content
= my
.db
.get_table(FROM
='instances', WHERE
=where
)
1432 bottle
.abort(HTTP_Not_Found
, "server %s not found" % server_id
)
1435 print "http_post_server_action error getting data %d %s" % (result
, content
)
1436 bottle
.abort(HTTP_Internal_Server_Error
, content
)
1438 server
.update(content
[0])
1439 tenant_id
= server
["tenant_id"]
1441 #TODO check a right content
1443 if 'terminate' in action
:
1444 new_status
='DELETING'
1445 elif server
['status'] == 'ERROR': #or server['status'] == 'CREATING':
1446 if 'terminate' not in action
and 'rebuild' not in action
:
1447 bottle
.abort(HTTP_Method_Not_Allowed
, "Server is in ERROR status, must be rebuit or deleted ")
1449 # elif server['status'] == 'INACTIVE':
1450 # if 'start' not in action and 'createImage' not in action:
1451 # bottle.abort(HTTP_Method_Not_Allowed, "The only possible action over an instance in 'INACTIVE' status is 'start'")
1453 # if 'start' in action:
1454 # new_status='CREATING'
1455 # server['paused']='no'
1456 # elif server['status'] == 'PAUSED':
1457 # if 'resume' not in action:
1458 # bottle.abort(HTTP_Method_Not_Allowed, "The only possible action over an instance in 'PAUSED' status is 'resume'")
1460 # elif server['status'] == 'ACTIVE':
1461 # if 'pause' not in action and 'reboot'not in action and 'shutoff'not in action:
1462 # bottle.abort(HTTP_Method_Not_Allowed, "The only possible action over an instance in 'ACTIVE' status is 'pause','reboot' or 'shutoff'")
1465 if 'start' in action
or 'createImage' in action
or 'rebuild' in action
:
1466 #check image valid and take info
1467 image_id
= server
['image_id']
1468 if 'createImage' in action
:
1469 if 'imageRef' in action
['createImage']:
1470 image_id
= action
['createImage']['imageRef']
1471 elif 'disk' in action
['createImage']:
1472 result
, content
= my
.db
.get_table(FROM
='instance_devices',
1473 SELECT
=('image_id','dev'), WHERE
={'instance_id':server
['uuid'],"type":"disk"})
1475 bottle
.abort(HTTP_Not_Found
, 'disk not found for server')
1479 if action
['createImage']['imageRef']['disk'] != None:
1480 for disk
in content
:
1481 if disk
['dev'] == action
['createImage']['imageRef']['disk']:
1482 disk_id
= disk
['image_id']
1485 bottle
.abort(HTTP_Not_Found
, 'disk %s not found for server' % action
['createImage']['imageRef']['disk'])
1488 bottle
.abort(HTTP_Not_Found
, 'more than one disk found for server' )
1492 image_id
= content
[0]['image_id']
1494 result
, content
= my
.db
.get_table(FROM
='tenants_images as ti join images as i on ti.image_id=i.uuid',
1495 SELECT
=('path','metadata'), WHERE
={'uuid':image_id
, 'tenant_id':tenant_id
, "status":"ACTIVE"})
1497 bottle
.abort(HTTP_Not_Found
, 'image_id %s not found or not ACTIVE' % image_id
)
1499 if content
[0]['metadata'] is not None:
1501 metadata
= json
.loads(content
[0]['metadata'])
1503 return -HTTP_Internal_Server_Error
, "Can not decode image metadata"
1504 content
[0]['metadata']=metadata
1506 content
[0]['metadata'] = {}
1507 server
['image']=content
[0]
1508 if 'createImage' in action
:
1509 action
['createImage']['source'] = {'image_id': image_id
, 'path': content
[0]['path']}
1510 if 'createImage' in action
:
1511 #Create an entry in Database for the new image
1512 new_image
={'status':'BUILD', 'progress': 0 }
1513 new_image_metadata
=content
[0]
1514 if 'metadata' in server
['image'] and server
['image']['metadata'] != None:
1515 new_image_metadata
.update(server
['image']['metadata'])
1516 new_image_metadata
= {"use_incremental":"no"}
1517 if 'metadata' in action
['createImage']:
1518 new_image_metadata
.update(action
['createImage']['metadata'])
1519 new_image
['metadata'] = json
.dumps(new_image_metadata
)
1520 new_image
['name'] = action
['createImage'].get('name', None)
1521 new_image
['description'] = action
['createImage'].get('description', None)
1522 new_image
['uuid']=my
.db
.new_uuid()
1523 if 'path' in action
['createImage']:
1524 new_image
['path'] = action
['createImage']['path']
1526 new_image
['path']="/provisional/path/" + new_image
['uuid']
1527 result
, image_uuid
= my
.db
.new_image(new_image
, tenant_id
)
1529 bottle
.abort(HTTP_Bad_Request
, 'Error: ' + image_uuid
)
1531 server
['new_image'] = new_image
1535 r
,c
= config_dic
['host_threads'][ server
['host_id'] ].insert_task( 'instance',server
)
1537 print "Task queue full at host ", server
['host_id']
1538 bottle
.abort(HTTP_Request_Timeout
, c
)
1539 if 'createImage' in action
and result
>= 0:
1540 return http_get_image_id(tenant_id
, image_uuid
)
1542 #Update DB only for CREATING or DELETING status
1543 data
={'result' : 'in process'}
1544 if new_status
!= None and new_status
== 'DELETING':
1547 #look for dhcp ip address
1548 r2
, c2
= my
.db
.get_table(FROM
="ports", SELECT
=["mac", "net_id"], WHERE
={"instance_id": server_id
})
1549 r
,c
= my
.db
.delete_instance(server_id
, tenant_id
, nets
, ports_to_free
, "requested by http")
1550 for port
in ports_to_free
:
1551 r1
,c1
= config_dic
['host_threads'][ server
['host_id'] ].insert_task( 'restore-iface',*port
)
1553 print ' http_post_server_action error at server deletion ERROR resore-iface !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!' + c1
1554 data
={'result' : 'deleting in process, but ifaces cannot be restored!!!!!'}
1556 r1
,c1
= config_dic
['of_thread'].insert_task("update-net", net
)
1558 print ' http_post_server_action error at server deletion ERROR UPDATING NETS !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!' + c1
1559 data
={'result' : 'deleting in process, but openflow rules cannot be deleted!!!!!'}
1560 #look for dhcp ip address
1561 if r2
>0 and config_dic
.get("dhcp_server"):
1563 if iface
["net_id"] in config_dic
["dhcp_nets"]:
1564 r
,c
= config_dic
['dhcp_thread'].insert_task("del", iface
["mac"])
1565 #print "dhcp insert del task"
1567 print ':http_post_servers ERROR UPDATING dhcp_server !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!' + c
1569 return format_out(data
)
1573 @bottle.route(url_base
+ '/<tenant_id>/servers/<server_id>', method
='DELETE')
1574 def http_delete_server_id(tenant_id
, server_id
):
1575 '''delete a server'''
1576 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
1577 #check valid tenant_id
1578 result
,content
= check_valid_tenant(my
, tenant_id
)
1580 bottle
.abort(result
, content
)
1583 return http_server_action(server_id
, tenant_id
, {"terminate":None} )
1586 @bottle.route(url_base
+ '/<tenant_id>/servers/<server_id>/action', method
='POST')
1587 def http_post_server_action(tenant_id
, server_id
):
1588 '''take an action over a server'''
1589 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
1590 #check valid tenant_id
1591 result
,content
= check_valid_tenant(my
, tenant_id
)
1593 bottle
.abort(result
, content
)
1595 http_content
= format_in( server_action_schema
)
1596 #r = remove_extra_items(http_content, server_action_schema)
1597 #if r is not None: print "http_post_server_action: Warning: remove extra items ", r
1599 return http_server_action(server_id
, tenant_id
, http_content
)
1606 @bottle.route(url_base
+ '/networks', method
='GET')
1607 def http_get_networks():
1608 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
1610 select_
,where_
,limit_
= filter_query_string(bottle
.request
.query
, http2db_network
,
1611 ('id','name','tenant_id','type',
1612 'shared','provider:vlan','status','last_error','admin_state_up','provider:physical') )
1613 #TODO temporally remove tenant_id
1614 if "tenant_id" in where_
:
1615 del where_
["tenant_id"]
1616 result
, content
= my
.db
.get_table(SELECT
=select_
, FROM
='nets', WHERE
=where_
, LIMIT
=limit_
)
1618 print "http_get_networks error %d %s" % (result
, content
)
1619 bottle
.abort(-result
, content
)
1621 convert_boolean(content
, ('shared', 'admin_state_up', 'enable_dhcp') )
1622 delete_nulls(content
)
1623 change_keys_http2db(content
, http2db_network
, reverse
=True)
1624 data
={'networks' : content
}
1625 return format_out(data
)
1627 @bottle.route(url_base
+ '/networks/<network_id>', method
='GET')
1628 def http_get_network_id(network_id
):
1629 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
1631 where_
= bottle
.request
.query
1632 where_
['uuid'] = network_id
1633 result
, content
= my
.db
.get_table(FROM
='nets', WHERE
=where_
, LIMIT
=100)
1636 print "http_get_networks_id error %d %s" % (result
, content
)
1637 bottle
.abort(-result
, content
)
1639 print "http_get_networks_id network '%s' not found" % network_id
1640 bottle
.abort(HTTP_Not_Found
, 'network %s not found' % network_id
)
1642 convert_boolean(content
, ('shared', 'admin_state_up', 'enale_dhcp') )
1643 change_keys_http2db(content
, http2db_network
, reverse
=True)
1645 result
, ports
= my
.db
.get_table(FROM
='ports', SELECT
=('uuid as port_id',),
1646 WHERE
={'net_id': network_id
}, LIMIT
=100)
1648 content
[0]['ports'] = ports
1649 delete_nulls(content
[0])
1650 data
={'network' : content
[0]}
1651 return format_out(data
)
1653 @bottle.route(url_base
+ '/networks', method
='POST')
1654 def http_post_networks():
1655 '''insert a network into the database.'''
1656 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
1658 http_content
= format_in( network_new_schema
)
1659 r
= remove_extra_items(http_content
, network_new_schema
)
1660 if r
is not None: print "http_post_networks: Warning: remove extra items ", r
1661 change_keys_http2db(http_content
['network'], http2db_network
)
1662 network
=http_content
['network']
1663 #check valid tenant_id
1664 tenant_id
= network
.get('tenant_id')
1666 result
, _
= my
.db
.get_table(FROM
='tenants', SELECT
=('uuid',), WHERE
={'uuid': tenant_id
,"enabled":True})
1668 bottle
.abort(HTTP_Not_Found
, 'tenant %s not found or not enabled' % tenant_id
)
1672 net_provider
= network
.get('provider')
1673 net_type
= network
.get('type')
1674 net_vlan
= network
.get("vlan")
1675 net_bind_net
= network
.get("bind_net")
1676 net_bind_type
= network
.get("bind_type")
1677 name
= network
["name"]
1679 #check if network name ends with :<vlan_tag> and network exist in order to make and automated bindning
1680 vlan_index
=name
.rfind(":")
1681 if net_bind_net
==None and net_bind_type
==None and vlan_index
> 1:
1683 vlan_tag
= int(name
[vlan_index
+1:])
1684 if vlan_tag
>0 and vlan_tag
< 4096:
1685 net_bind_net
= name
[:vlan_index
]
1686 net_bind_type
= "vlan:" + name
[vlan_index
+1:]
1690 if net_bind_net
!= None:
1691 #look for a valid net
1692 if check_valid_uuid(net_bind_net
):
1693 net_bind_key
= "uuid"
1695 net_bind_key
= "name"
1696 result
, content
= my
.db
.get_table(FROM
='nets', WHERE
={net_bind_key
: net_bind_net
} )
1698 bottle
.abort(HTTP_Internal_Server_Error
, 'getting nets from db ' + content
)
1701 bottle
.abort(HTTP_Bad_Request
, "bind_net %s '%s'not found" % (net_bind_key
, net_bind_net
) )
1704 bottle
.abort(HTTP_Bad_Request
, "more than one bind_net %s '%s' found, use uuid" % (net_bind_key
, net_bind_net
) )
1706 network
["bind_net"] = content
[0]["uuid"]
1707 if net_bind_type
!= None:
1708 if net_bind_type
[0:5] != "vlan:":
1709 bottle
.abort(HTTP_Bad_Request
, "bad format for 'bind_type', must be 'vlan:<tag>'")
1711 if int(net_bind_type
[5:]) > 4095 or int(net_bind_type
[5:])<=0 :
1712 bottle
.abort(HTTP_Bad_Request
, "bad format for 'bind_type', must be 'vlan:<tag>' with a tag between 1 and 4095")
1714 network
["bind_type"] = net_bind_type
1716 if net_provider
!=None:
1717 if net_provider
[:9]=="openflow:":
1719 if net_type
!="ptp" and net_type
!="data":
1720 bottle
.abort(HTTP_Bad_Request
, "Only 'ptp' or 'data' net types can be bound to 'openflow'")
1725 if net_type
!="bridge_man" and net_type
!="bridge_data":
1726 bottle
.abort(HTTP_Bad_Request
, "Only 'bridge_man' or 'bridge_data' net types can be bound to 'bridge', 'macvtap' or 'default")
1728 net_type
='bridge_man'
1731 net_type
='bridge_man'
1733 if net_provider
!= None:
1734 if net_provider
[:7]=='bridge:':
1735 #check it is one of the pre-provisioned bridges
1736 bridge_net_name
= net_provider
[7:]
1737 for brnet
in config_dic
['bridge_nets']:
1738 if brnet
[0]==bridge_net_name
: # free
1739 if brnet
[3] != None:
1740 bottle
.abort(HTTP_Conflict
, "invalid 'provider:physical', bridge '%s' is already used" % bridge_net_name
)
1745 # if bridge_net==None:
1746 # 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)
1748 elif net_type
=='bridge_data' or net_type
=='bridge_man':
1749 #look for a free precreated nets
1750 for brnet
in config_dic
['bridge_nets']:
1751 if brnet
[3]==None: # free
1752 if bridge_net
!= None:
1753 if net_type
=='bridge_man': #look for the smaller speed
1754 if brnet
[2] < bridge_net
[2]: bridge_net
= brnet
1755 else: #look for the larger speed
1756 if brnet
[2] > bridge_net
[2]: bridge_net
= brnet
1760 if bridge_net
==None:
1761 bottle
.abort(HTTP_Bad_Request
, "Max limits of bridge networks reached. Future versions of VIM will overcome this limit")
1764 print "using net", bridge_net
1765 net_provider
= "bridge:"+bridge_net
[0]
1766 net_vlan
= bridge_net
[1]
1767 if net_vlan
==None and (net_type
=="data" or net_type
=="ptp"):
1768 net_vlan
= my
.db
.get_free_net_vlan()
1770 bottle
.abort(HTTP_Internal_Server_Error
, "Error getting an available vlan")
1773 network
['provider'] = net_provider
1774 network
['type'] = net_type
1775 network
['vlan'] = net_vlan
1776 result
, content
= my
.db
.new_row('nets', network
, True, True)
1779 if bridge_net
!=None:
1780 bridge_net
[3] = content
1782 if config_dic
.get("dhcp_server"):
1783 if network
["name"] in config_dic
["dhcp_server"].get("nets", () ):
1784 config_dic
["dhcp_nets"].append(content
)
1785 print "dhcp_server: add new net", content
1786 elif bridge_net
!= None and bridge_net
[0] in config_dic
["dhcp_server"].get("bridge_ifaces", () ):
1787 config_dic
["dhcp_nets"].append(content
)
1788 print "dhcp_server: add new net", content
1789 return http_get_network_id(content
)
1791 print "http_post_networks error %d %s" % (result
, content
)
1792 bottle
.abort(-result
, content
)
1796 @bottle.route(url_base
+ '/networks/<network_id>', method
='PUT')
1797 def http_put_network_id(network_id
):
1798 '''update a network_id into the database.'''
1799 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
1801 http_content
= format_in( network_update_schema
)
1802 r
= remove_extra_items(http_content
, network_update_schema
)
1803 change_keys_http2db(http_content
['network'], http2db_network
)
1804 network
=http_content
['network']
1806 #Look for the previous data
1807 where_
= {'uuid': network_id
}
1808 result
, network_old
= my
.db
.get_table(FROM
='nets', WHERE
=where_
)
1810 print "http_put_network_id error %d %s" % (result
, network_old
)
1811 bottle
.abort(-result
, network_old
)
1814 print "http_put_network_id network '%s' not found" % network_id
1815 bottle
.abort(HTTP_Not_Found
, 'network %s not found' % network_id
)
1818 nbports
, content
= my
.db
.get_table(FROM
='ports', SELECT
=('uuid as port_id',),
1819 WHERE
={'net_id': network_id
}, LIMIT
=100)
1821 print "http_put_network_id error %d %s" % (result
, network_old
)
1822 bottle
.abort(-result
, content
)
1825 if 'type' in network
and network
['type'] != network_old
[0]['type']:
1826 bottle
.abort(HTTP_Method_Not_Allowed
, "Can not change type of network while having ports attached")
1827 if 'vlan' in network
and network
['vlan'] != network_old
[0]['vlan']:
1828 bottle
.abort(HTTP_Method_Not_Allowed
, "Can not change vlan of network while having ports attached")
1831 net_provider
= network
.get('provider', network_old
[0]['provider'])
1832 net_type
= network
.get('type', network_old
[0]['type'])
1833 net_bind_net
= network
.get("bind_net")
1834 net_bind_type
= network
.get("bind_type")
1835 if net_bind_net
!= None:
1836 #look for a valid net
1837 if check_valid_uuid(net_bind_net
):
1838 net_bind_key
= "uuid"
1840 net_bind_key
= "name"
1841 result
, content
= my
.db
.get_table(FROM
='nets', WHERE
={net_bind_key
: net_bind_net
} )
1843 bottle
.abort(HTTP_Internal_Server_Error
, 'getting nets from db ' + content
)
1846 bottle
.abort(HTTP_Bad_Request
, "bind_net %s '%s'not found" % (net_bind_key
, net_bind_net
) )
1849 bottle
.abort(HTTP_Bad_Request
, "more than one bind_net %s '%s' found, use uuid" % (net_bind_key
, net_bind_net
) )
1851 network
["bind_net"] = content
[0]["uuid"]
1852 if net_bind_type
!= None:
1853 if net_bind_type
[0:5] != "vlan:":
1854 bottle
.abort(HTTP_Bad_Request
, "bad format for 'bind_type', must be 'vlan:<tag>'")
1856 if int(net_bind_type
[5:]) > 4095 or int(net_bind_type
[5:])<=0 :
1857 bottle
.abort(HTTP_Bad_Request
, "bad format for 'bind_type', must be 'vlan:<tag>' with a tag between 1 and 4095")
1859 if net_provider
!=None:
1860 if net_provider
[:9]=="openflow:":
1861 if net_type
!="ptp" and net_type
!="data":
1862 bottle
.abort(HTTP_Bad_Request
, "Only 'ptp' or 'data' net types can be bound to 'openflow'")
1864 if net_type
!="bridge_man" and net_type
!="bridge_data":
1865 bottle
.abort(HTTP_Bad_Request
, "Only 'bridge_man' or 'bridge_data' net types can be bound to 'bridge', 'macvtap' or 'default")
1867 #insert in data base
1868 result
, content
= my
.db
.update_rows('nets', network
, WHERE
={'uuid': network_id
}, log
=True )
1870 if result
>0: # and nbports>0 and 'admin_state_up' in network and network['admin_state_up'] != network_old[0]['admin_state_up']:
1871 r
,c
= config_dic
['of_thread'].insert_task("update-net", network_id
)
1873 print "http_put_network_id error while launching openflow rules"
1874 bottle
.abort(HTTP_Internal_Server_Error
, c
)
1875 if config_dic
.get("dhcp_server"):
1876 if network_id
in config_dic
["dhcp_nets"]:
1877 config_dic
["dhcp_nets"].remove(network_id
)
1878 print "dhcp_server: delete net", network_id
1879 if network
.get("name", network_old
["name"]) in config_dic
["dhcp_server"].get("nets", () ):
1880 config_dic
["dhcp_nets"].append(network_id
)
1881 print "dhcp_server: add new net", network_id
1883 net_bind
= network
.get("bind", network_old
["bind"] )
1884 if net_bind
and net_bind
[:7]=="bridge:" and net_bind
[7:] in config_dic
["dhcp_server"].get("bridge_ifaces", () ):
1885 config_dic
["dhcp_nets"].append(network_id
)
1886 print "dhcp_server: add new net", network_id
1887 return http_get_network_id(network_id
)
1889 bottle
.abort(-result
, content
)
1893 @bottle.route(url_base
+ '/networks/<network_id>', method
='DELETE')
1894 def http_delete_network_id(network_id
):
1895 '''delete a network_id from the database.'''
1896 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
1898 #delete from the data base
1899 result
, content
= my
.db
.delete_row('nets', network_id
)
1902 bottle
.abort(HTTP_Not_Found
, content
)
1904 for brnet
in config_dic
['bridge_nets']:
1905 if brnet
[3]==network_id
:
1908 if config_dic
.get("dhcp_server") and network_id
in config_dic
["dhcp_nets"]:
1909 config_dic
["dhcp_nets"].remove(network_id
)
1910 print "dhcp_server: delete net", network_id
1911 data
={'result' : content
}
1912 return format_out(data
)
1914 print "http_delete_network_id error",result
, content
1915 bottle
.abort(-result
, content
)
1920 @bottle.route(url_base
+ '/networks/<network_id>/openflow', method
='GET')
1921 def http_get_openflow_id(network_id
):
1922 '''To obtain the list of openflow rules of a network
1924 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
1926 if network_id
=='all':
1929 where_
={"net_id": network_id
}
1930 result
, content
= my
.db
.get_table(SELECT
=("name","net_id","priority","vlan_id","ingress_port","src_mac","dst_mac","actions"),
1931 WHERE
=where_
, FROM
='of_flows')
1933 bottle
.abort(-result
, content
)
1935 data
={'openflow-rules' : content
}
1936 return format_out(data
)
1938 @bottle.route(url_base
+ '/networks/<network_id>/openflow', method
='PUT')
1939 def http_put_openflow_id(network_id
):
1940 '''To make actions over the net. The action is to reinstall the openflow rules
1941 network_id can be 'all'
1943 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
1945 bottle
.abort(HTTP_Unauthorized
, "Needed admin privileges")
1948 if network_id
=='all':
1951 where_
={"uuid": network_id
}
1952 result
, content
= my
.db
.get_table(SELECT
=("uuid","type"), WHERE
=where_
, FROM
='nets')
1954 bottle
.abort(-result
, content
)
1958 if net
["type"]!="ptp" and net
["type"]!="data":
1961 r
,c
= config_dic
['of_thread'].insert_task("update-net", net
['uuid'])
1963 print "http_put_openflow_id error while launching openflow rules"
1964 bottle
.abort(HTTP_Internal_Server_Error
, c
)
1965 data
={'result' : str(result
)+" nets updates"}
1966 return format_out(data
)
1968 @bottle.route(url_base
+ '/networks/openflow/clear', method
='DELETE')
1969 @bottle.route(url_base
+ '/networks/clear/openflow', method
='DELETE')
1970 def http_clear_openflow_rules():
1971 '''To make actions over the net. The action is to delete ALL openflow rules
1973 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
1975 bottle
.abort(HTTP_Unauthorized
, "Needed admin privileges")
1978 r
,c
= config_dic
['of_thread'].insert_task("clear-all")
1980 print "http_delete_openflow_id error while launching openflow rules"
1981 bottle
.abort(HTTP_Internal_Server_Error
, c
)
1984 data
={'result' : " Clearing openflow rules in process"}
1985 return format_out(data
)
1987 @bottle.route(url_base
+ '/networks/openflow/ports', method
='GET')
1988 def http_get_openflow_ports():
1989 '''Obtain switch ports names of openflow controller
1991 data
={'ports' : config_dic
['of_thread'].OF_connector
.pp2ofi
}
1992 return format_out(data
)
1999 @bottle.route(url_base
+ '/ports', method
='GET')
2000 def http_get_ports():
2002 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
2003 select_
,where_
,limit_
= filter_query_string(bottle
.request
.query
, http2db_port
,
2004 ('id','name','tenant_id','network_id','vpci','mac_address','device_owner','device_id',
2005 'binding:switch_port','binding:vlan','bandwidth','status','admin_state_up','ip_address') )
2006 #result, content = my.db.get_ports(where_)
2007 result
, content
= my
.db
.get_table(SELECT
=select_
, WHERE
=where_
, FROM
='ports',LIMIT
=limit_
)
2009 print "http_get_ports Error", result
, content
2010 bottle
.abort(-result
, content
)
2013 convert_boolean(content
, ('admin_state_up',) )
2014 delete_nulls(content
)
2015 change_keys_http2db(content
, http2db_port
, reverse
=True)
2016 data
={'ports' : content
}
2017 return format_out(data
)
2019 @bottle.route(url_base
+ '/ports/<port_id>', method
='GET')
2020 def http_get_port_id(port_id
):
2021 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
2023 result
, content
= my
.db
.get_table(WHERE
={'uuid': port_id
}, FROM
='ports')
2025 print "http_get_ports error", result
, content
2026 bottle
.abort(-result
, content
)
2028 print "http_get_ports port '%s' not found" % str(port_id
)
2029 bottle
.abort(HTTP_Not_Found
, 'port %s not found' % port_id
)
2031 convert_boolean(content
, ('admin_state_up',) )
2032 delete_nulls(content
)
2033 change_keys_http2db(content
, http2db_port
, reverse
=True)
2034 data
={'port' : content
[0]}
2035 return format_out(data
)
2038 @bottle.route(url_base
+ '/ports', method
='POST')
2039 def http_post_ports():
2040 '''insert an external port into the database.'''
2041 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
2043 bottle
.abort(HTTP_Unauthorized
, "Needed admin privileges")
2045 http_content
= format_in( port_new_schema
)
2046 r
= remove_extra_items(http_content
, port_new_schema
)
2047 if r
is not None: print "http_post_ports: Warning: remove extra items ", r
2048 change_keys_http2db(http_content
['port'], http2db_port
)
2049 port
=http_content
['port']
2051 port
['type'] = 'external'
2052 if 'net_id' in port
and port
['net_id'] == None:
2055 if 'net_id' in port
:
2056 #check that new net has the correct type
2057 result
, new_net
= my
.db
.check_target_net(port
['net_id'], None, 'external' )
2059 bottle
.abort(HTTP_Bad_Request
, new_net
)
2061 #insert in data base
2062 result
, uuid
= my
.db
.new_row('ports', port
, True, True)
2064 if 'net_id' in port
:
2065 r
,c
= config_dic
['of_thread'].insert_task("update-net", port
['net_id'])
2067 print "http_post_ports error while launching openflow rules"
2068 bottle
.abort(HTTP_Internal_Server_Error
, c
)
2069 return http_get_port_id(uuid
)
2071 bottle
.abort(-result
, uuid
)
2074 @bottle.route(url_base
+ '/ports/<port_id>', method
='PUT')
2075 def http_put_port_id(port_id
):
2076 '''update a port_id into the database.'''
2078 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
2080 http_content
= format_in( port_update_schema
)
2081 change_keys_http2db(http_content
['port'], http2db_port
)
2082 port_dict
=http_content
['port']
2084 #Look for the previous port data
2085 where_
= {'uuid': port_id
}
2086 result
, content
= my
.db
.get_table(FROM
="ports",WHERE
=where_
)
2088 print "http_put_port_id error", result
, content
2089 bottle
.abort(-result
, content
)
2092 print "http_put_port_id port '%s' not found" % port_id
2093 bottle
.abort(HTTP_Not_Found
, 'port %s not found' % port_id
)
2096 for k
in ('vlan','switch_port','mac_address', 'tenant_id'):
2097 if k
in port_dict
and not my
.admin
:
2098 bottle
.abort(HTTP_Unauthorized
, "Needed admin privileges for changing " + k
)
2102 #change_keys_http2db(port, http2db_port, reverse=True)
2106 if 'net_id' in port_dict
:
2108 old_net
= port
.get('net_id', None)
2109 new_net
= port_dict
['net_id']
2110 if old_net
!= new_net
:
2112 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
2113 if old_net
is not None: nets
.append(old_net
)
2114 if port
['type'] == 'instance:bridge':
2115 bottle
.abort(HTTP_Forbidden
, "bridge interfaces cannot be attached to a different net")
2117 elif port
['type'] == 'external':
2119 bottle
.abort(HTTP_Unauthorized
, "Needed admin privileges")
2123 #check that new net has the correct type
2124 result
, new_net_dict
= my
.db
.check_target_net(new_net
, None, port
['type'] )
2126 #change VLAN for SR-IOV ports
2127 if result
>=0 and port
["type"]=="instance:data" and port
["model"]=="VF": #TODO consider also VFnotShared
2129 port_dict
["vlan"] = None
2131 port_dict
["vlan"] = new_net_dict
["vlan"]
2132 #get host where this VM is allocated
2133 result
, content
= my
.db
.get_table(FROM
="instances",WHERE
={"uuid":port
["instance_id"]})
2135 print "http_put_port_id database error", content
2137 host_id
= content
[0]["host_id"]
2139 #insert in data base
2141 result
, content
= my
.db
.update_rows('ports', port_dict
, WHERE
={'uuid': port_id
}, log
=False )
2143 #Insert task to complete actions
2146 r
,v
= config_dic
['of_thread'].insert_task("update-net", net_id
)
2147 if r
<0: print "Error ********* http_put_port_id update_of_flows: ", v
2148 #TODO Do something if fails
2150 config_dic
['host_threads'][host_id
].insert_task("edit-iface", port_id
, old_net
, new_net
)
2153 return http_get_port_id(port_id
)
2155 bottle
.abort(HTTP_Bad_Request
, content
)
2159 @bottle.route(url_base
+ '/ports/<port_id>', method
='DELETE')
2160 def http_delete_port_id(port_id
):
2161 '''delete a port_id from the database.'''
2162 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
2164 bottle
.abort(HTTP_Unauthorized
, "Needed admin privileges")
2167 #Look for the previous port data
2168 where_
= {'uuid': port_id
, "type": "external"}
2169 result
, ports
= my
.db
.get_table(WHERE
=where_
, FROM
='ports',LIMIT
=100)
2172 print "http_delete_port_id port '%s' not found" % port_id
2173 bottle
.abort(HTTP_Not_Found
, 'port %s not found or device_owner is not external' % port_id
)
2175 #delete from the data base
2176 result
, content
= my
.db
.delete_row('ports', port_id
)
2179 bottle
.abort(HTTP_Not_Found
, content
)
2181 network
= ports
[0].get('net_id', None)
2182 if network
is not None:
2184 r
,c
= config_dic
['of_thread'].insert_task("update-net", network
)
2185 if r
<0: print "!!!!!! http_delete_port_id update_of_flows error", r
, c
2186 data
={'result' : content
}
2187 return format_out(data
)
2189 print "http_delete_port_id error",result
, content
2190 bottle
.abort(-result
, content
)