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, Leonardo Mirabal"
30 __date__
= "$10-jul-2014 12:07:15$"
41 #import only if needed because not needed in test mode. To allow an easier installation import RADclass
42 from jsonschema
import validate
as js_v
, exceptions
as js_e
43 import host_thread
as ht
44 from vim_schema
import host_new_schema
, host_edit_schema
, tenant_new_schema
, \
46 flavor_new_schema
, flavor_update_schema
, \
47 image_new_schema
, image_update_schema
, \
48 server_new_schema
, server_action_schema
, network_new_schema
, network_update_schema
, \
49 port_new_schema
, port_update_schema
54 global RADclass_module
55 RADclass
=None #RADclass module is charged only if not in test mode
59 HTTP_Bad_Request
= 400
60 HTTP_Unauthorized
= 401
63 HTTP_Method_Not_Allowed
= 405
64 HTTP_Not_Acceptable
= 406
65 HTTP_Request_Timeout
= 408
67 HTTP_Service_Unavailable
= 503
68 HTTP_Internal_Server_Error
= 500
71 hash_md5
= hashlib
.md5()
72 with
open(fname
, "rb") as f
:
73 for chunk
in iter(lambda: f
.read(4096), b
""):
74 hash_md5
.update(chunk
)
75 return hash_md5
.hexdigest()
77 def check_extended(extended
, allow_net_attach
=False):
78 '''Makes and extra checking of extended input that cannot be done using jsonschema
80 allow_net_attach: for allowing or not the uuid field at interfaces
81 that are allowed for instance, but not for flavors
82 Return: (<0, error_text) if error; (0,None) if not error '''
83 if "numas" not in extended
: return 0, None
86 for numa
in extended
["numas"]:
90 if "cores-id" in numa
:
91 if len(numa
["cores-id"]) != numa
["cores"]:
92 return -HTTP_Bad_Request
, "different number of cores-id (%d) than cores (%d) at numa %d" % (len(numa
["cores-id"]), numa
["cores"],numaid
)
93 id_s
.extend(numa
["cores-id"])
96 if "threads-id" in numa
:
97 if len(numa
["threads-id"]) != numa
["threads"]:
98 return -HTTP_Bad_Request
, "different number of threads-id (%d) than threads (%d) at numa %d" % (len(numa
["threads-id"]), numa
["threads"],numaid
)
99 id_s
.extend(numa
["threads-id"])
100 if "paired-threads" in numa
:
102 if "paired-threads-id" in numa
:
103 if len(numa
["paired-threads-id"]) != numa
["paired-threads"]:
104 return -HTTP_Bad_Request
, "different number of paired-threads-id (%d) than paired-threads (%d) at numa %d" % (len(numa
["paired-threads-id"]), numa
["paired-threads"],numaid
)
105 for pair
in numa
["paired-threads-id"]:
107 return -HTTP_Bad_Request
, "paired-threads-id must contain a list of two elements list at numa %d" % (numaid
)
110 return -HTTP_Service_Unavailable
, "only one of cores, threads, paired-threads are allowed in this version at numa %d" % numaid
112 if "interfaces" in numa
:
116 for interface
in numa
["interfaces"]:
117 if "uuid" in interface
and not allow_net_attach
:
118 return -HTTP_Bad_Request
, "uuid field is not allowed at numa %d interface %s position %d" % (numaid
, interface
.get("name",""), ifaceid
)
119 if "mac_address" in interface
and interface
["dedicated"]=="yes":
120 return -HTTP_Bad_Request
, "mac_address can not be set for dedicated (passthrough) at numa %d, interface %s position %d" % (numaid
, interface
.get("name",""), ifaceid
)
121 if "name" in interface
:
122 if interface
["name"] in names
:
123 return -HTTP_Bad_Request
, "name repeated at numa %d, interface %s position %d" % (numaid
, interface
.get("name",""), ifaceid
)
124 names
.append(interface
["name"])
125 if "vpci" in interface
:
126 if interface
["vpci"] in vpcis
:
127 return -HTTP_Bad_Request
, "vpci %s repeated at numa %d, interface %s position %d" % (interface
["vpci"], numaid
, interface
.get("name",""), ifaceid
)
128 vpcis
.append(interface
["vpci"])
132 return -HTTP_Service_Unavailable
, "only one numa can be defined in this version "
133 for a
in range(0,len(id_s
)):
135 return -HTTP_Bad_Request
, "core/thread identifiers must start at 0 and gaps are not alloed. Missing id number %d" % a
140 # dictionaries that change from HTTP API to database naming
142 http2db_host
={'id':'uuid'}
143 http2db_tenant
={'id':'uuid'}
144 http2db_flavor
={'id':'uuid','imageRef':'image_id'}
145 http2db_image
={'id':'uuid', 'created':'created_at', 'updated':'modified_at', 'public': 'public'}
146 http2db_server
={'id':'uuid','hostId':'host_id','flavorRef':'flavor_id','imageRef':'image_id','created':'created_at'}
147 http2db_network
={'id':'uuid','provider:vlan':'vlan', 'provider:physical': 'provider'}
148 http2db_port
={'id':'uuid', 'network_id':'net_id', 'mac_address':'mac', 'device_owner':'type','device_id':'instance_id','binding:switch_port':'switch_port','binding:vlan':'vlan', 'bandwidth':'Mbps'}
150 def remove_extra_items(data
, schema
):
152 if type(data
) is tuple or type(data
) is list:
154 a
= remove_extra_items(d
, schema
['items'])
155 if a
is not None: deleted
.append(a
)
156 elif type(data
) is dict:
157 for k
in data
.keys():
158 if 'properties' not in schema
or k
not in schema
['properties'].keys():
162 a
= remove_extra_items(data
[k
], schema
['properties'][k
])
163 if a
is not None: deleted
.append({k
:a
})
164 if len(deleted
) == 0: return None
165 elif len(deleted
) == 1: return deleted
[0]
168 def delete_nulls(var
):
169 if type(var
) is dict:
171 if var
[k
] is None: del var
[k
]
172 elif type(var
[k
]) is dict or type(var
[k
]) is list or type(var
[k
]) is tuple:
173 if delete_nulls(var
[k
]): del var
[k
]
174 if len(var
) == 0: return True
175 elif type(var
) is list or type(var
) is tuple:
177 if type(k
) is dict: delete_nulls(k
)
178 if len(var
) == 0: return True
182 class httpserver(threading
.Thread
):
183 def __init__(self
, db_conn
, name
="http", host
='localhost', port
=8080, admin
=False, config_
=None):
185 Creates a new thread to attend the http connections
187 db_conn: database connection
188 name: name of this thread
189 host: ip or name where to listen
190 port: port where to listen
191 admin: if this has privileges of administrator or not
192 config_: unless the first thread must be provided. It is a global dictionary where to allocate the self variable
198 if config_
is not None:
200 if 'http_threads' not in config_dic
:
201 config_dic
['http_threads'] = {}
202 threading
.Thread
.__init
__(self
)
207 if name
in config_dic
:
208 print "httpserver Warning!!! Onether thread with the same name", name
210 while name
+str(n
) in config_dic
:
214 self
.url_preffix
= 'http://' + self
.host
+ ':' + str(self
.port
) + url_base
215 config_dic
['http_threads'][name
] = self
217 #Ensure that when the main program exits the thread will also exit
222 bottle
.run(host
=self
.host
, port
=self
.port
, debug
=True) #quiet=True
224 def gethost(self
, host_id
):
225 result
, content
= self
.db
.get_host(host_id
)
227 print "httpserver.gethost error %d %s" % (result
, content
)
228 bottle
.abort(-result
, content
)
230 print "httpserver.gethost host '%s' not found" % host_id
231 bottle
.abort(HTTP_Not_Found
, content
)
233 data
={'host' : content
}
234 convert_boolean(content
, ('admin_state_up',) )
235 change_keys_http2db(content
, http2db_host
, reverse
=True)
237 return format_out(data
)
239 @bottle.route(url_base
+ '/', method
='GET')
242 return 'works' #TODO: put links or redirection to /openvim???
248 def change_keys_http2db(data
, http_db
, reverse
=False):
249 '''Change keys of dictionary data according to the key_dict values
250 This allow change from http interface names to database names.
251 When reverse is True, the change is otherwise
253 data: can be a dictionary or a list
254 http_db: is a dictionary with hhtp names as keys and database names as value
255 reverse: by default change is done from http API to database. If True change is done otherwise
256 Return: None, but data is modified'''
257 if type(data
) is tuple or type(data
) is list:
259 change_keys_http2db(d
, http_db
, reverse
)
260 elif type(data
) is dict or type(data
) is bottle
.FormsDict
:
262 for k
,v
in http_db
.items():
263 if v
in data
: data
[k
]=data
.pop(v
)
265 for k
,v
in http_db
.items():
266 if k
in data
: data
[v
]=data
.pop(k
)
270 def format_out(data
):
271 '''return string of dictionary data according to requested json, yaml, xml. By default json'''
272 if 'application/yaml' in bottle
.request
.headers
.get('Accept'):
273 bottle
.response
.content_type
='application/yaml'
274 return yaml
.safe_dump(data
, explicit_start
=True, indent
=4, default_flow_style
=False, tags
=False, encoding
='utf-8', allow_unicode
=True) #, canonical=True, default_style='"'
275 else: #by default json
276 bottle
.response
.content_type
='application/json'
277 #return data #json no style
278 return json
.dumps(data
, indent
=4) + "\n"
280 def format_in(schema
):
282 error_text
= "Invalid header format "
283 format_type
= bottle
.request
.headers
.get('Content-Type', 'application/json')
284 if 'application/json' in format_type
:
285 error_text
= "Invalid json format "
286 #Use the json decoder instead of bottle decoder because it informs about the location of error formats with a ValueError exception
287 client_data
= json
.load(bottle
.request
.body
)
288 #client_data = bottle.request.json()
289 elif 'application/yaml' in format_type
:
290 error_text
= "Invalid yaml format "
291 client_data
= yaml
.load(bottle
.request
.body
)
292 elif format_type
== 'application/xml':
293 bottle
.abort(501, "Content-Type: application/xml not supported yet.")
295 print "HTTP HEADERS: " + str(bottle
.request
.headers
.items())
296 bottle
.abort(HTTP_Not_Acceptable
, 'Content-Type ' + str(format_type
) + ' not supported.')
298 #if client_data == None:
299 # bottle.abort(HTTP_Bad_Request, "Content error, empty")
303 #print "HTTP input data: ", str(client_data)
304 error_text
= "Invalid content "
305 js_v(client_data
, schema
)
308 except (ValueError, yaml
.YAMLError
) as exc
:
309 error_text
+= str(exc
)
311 bottle
.abort(HTTP_Bad_Request
, error_text
)
312 except js_e
.ValidationError
as exc
:
313 print "HTTP validate_in error, jsonschema exception ", exc
.message
, "at", exc
.path
314 print " CONTENT: " + str(bottle
.request
.body
.readlines())
316 if len(exc
.path
)>0: error_pos
=" at '" + ":".join(map(str, exc
.path
)) + "'"
317 bottle
.abort(HTTP_Bad_Request
, error_text
+ error_pos
+": "+exc
.message
)
319 # bottle.abort(HTTP_Bad_Request, "Content error: Failed to parse Content-Type", error_pos)
322 def filter_query_string(qs
, http2db
, allowed
):
323 '''Process query string (qs) checking that contains only valid tokens for avoiding SQL injection
325 'qs': bottle.FormsDict variable to be processed. None or empty is considered valid
326 'allowed': list of allowed string tokens (API http naming). All the keys of 'qs' must be one of 'allowed'
327 'http2db': dictionary with change from http API naming (dictionary key) to database naming(dictionary value)
328 Return: A tuple with the (select,where,limit) to be use in a database query. All of then transformed to the database naming
329 select: list of items to retrieve, filtered by query string 'field=token'. If no 'field' is present, allowed list is returned
330 where: dictionary with key, value, taken from the query string token=value. Empty if nothing is provided
331 limit: limit dictated by user with the query string 'limit'. 100 by default
332 abort if not permitted, using bottel.abort
337 if type(qs
) is not bottle
.FormsDict
:
338 print '!!!!!!!!!!!!!!invalid query string not a dictionary'
339 #bottle.abort(HTTP_Internal_Server_Error, "call programmer")
343 select
+= qs
.getall(k
)
346 bottle
.abort(HTTP_Bad_Request
, "Invalid query string at 'field="+v
+"'")
351 bottle
.abort(HTTP_Bad_Request
, "Invalid query string at 'limit="+qs
[k
]+"'")
354 bottle
.abort(HTTP_Bad_Request
, "Invalid query string at '"+k
+"="+qs
[k
]+"'")
355 if qs
[k
]!="null": where
[k
]=qs
[k
]
357 if len(select
)==0: select
+= allowed
358 #change from http api to database naming
359 for i
in range(0,len(select
)):
362 select
[i
] = http2db
[k
]
363 change_keys_http2db(where
, http2db
)
364 #print "filter_query_string", select,where,limit
366 return select
,where
,limit
369 def convert_bandwidth(data
, reverse
=False):
370 '''Check the field bandwidth recursively and when found, it removes units and convert to number
371 It assumes that bandwidth is well formed
373 'data': dictionary bottle.FormsDict variable to be checked. None or empty is considered valid
374 'reverse': by default convert form str to int (Mbps), if True it convert from number to units
378 if type(data
) is dict:
379 for k
in data
.keys():
380 if type(data
[k
]) is dict or type(data
[k
]) is tuple or type(data
[k
]) is list:
381 convert_bandwidth(data
[k
], reverse
)
382 if "bandwidth" in data
:
384 value
=str(data
["bandwidth"])
386 pos
= value
.find("bps")
388 if value
[pos
-1]=="G": data
["bandwidth"] = int(data
["bandwidth"][:pos
-1]) * 1000
389 elif value
[pos
-1]=="k": data
["bandwidth"]= int(data
["bandwidth"][:pos
-1]) / 1000
390 else: data
["bandwidth"]= int(data
["bandwidth"][:pos
-1])
392 value
= int(data
["bandwidth"])
393 if value
% 1000 == 0: data
["bandwidth"]=str(value
/1000) + " Gbps"
394 else: data
["bandwidth"]=str(value
) + " Mbps"
396 print "convert_bandwidth exception for type", type(data
["bandwidth"]), " data", data
["bandwidth"]
398 if type(data
) is tuple or type(data
) is list:
400 if type(k
) is dict or type(k
) is tuple or type(k
) is list:
401 convert_bandwidth(k
, reverse
)
403 def convert_boolean(data
, items
):
404 '''Check recursively the content of data, and if there is an key contained in items, convert value from string to boolean
405 It assumes that bandwidth is well formed
407 'data': dictionary bottle.FormsDict variable to be checked. None or empty is consideted valid
408 'items': tuple of keys to convert
412 if type(data
) is dict:
413 for k
in data
.keys():
414 if type(data
[k
]) is dict or type(data
[k
]) is tuple or type(data
[k
]) is list:
415 convert_boolean(data
[k
], items
)
417 if type(data
[k
]) is str:
418 if data
[k
]=="false": data
[k
]=False
419 elif data
[k
]=="true": data
[k
]=True
420 if type(data
) is tuple or type(data
) is list:
422 if type(k
) is dict or type(k
) is tuple or type(k
) is list:
423 convert_boolean(k
, items
)
425 def convert_datetime2str(var
):
426 '''Converts a datetime variable to a string with the format '%Y-%m-%dT%H:%i:%s'
427 It enters recursively in the dict var finding this kind of variables
429 if type(var
) is dict:
430 for k
,v
in var
.items():
431 if type(v
) is datetime
.datetime
:
432 var
[k
]= v
.strftime('%Y-%m-%dT%H:%M:%S')
433 elif type(v
) is dict or type(v
) is list or type(v
) is tuple:
434 convert_datetime2str(v
)
435 if len(var
) == 0: return True
436 elif type(var
) is list or type(var
) is tuple:
438 convert_datetime2str(v
)
440 def check_valid_tenant(my
, tenant_id
):
443 return HTTP_Unauthorized
, "Needed admin privileges"
445 result
, _
= my
.db
.get_table(FROM
='tenants', SELECT
=('uuid',), WHERE
={'uuid': tenant_id
})
447 return HTTP_Not_Found
, "tenant '%s' not found" % tenant_id
450 def check_valid_uuid(uuid
):
451 id_schema
= {"type" : "string", "pattern": "^[a-fA-F0-9]{8}(-[a-fA-F0-9]{4}){3}-[a-fA-F0-9]{12}$"}
453 js_v(uuid
, id_schema
)
455 except js_e
.ValidationError
:
461 Check if string value is a well-wormed url
462 :param url: string url
463 :return: True if is a valid url, False if is not well-formed
466 parsed_url
= urlparse
.urlparse(url
)
481 e
={"error":{"code":error
.status_code
, "type":error
.status
, "description":error
.body
}}
484 @bottle.hook('after_request')
486 #TODO: Alf: Is it needed??
487 bottle
.response
.headers
['Access-Control-Allow-Origin'] = '*'
493 @bottle.route(url_base
+ '/hosts', method
='GET')
494 def http_get_hosts():
495 return format_out(get_hosts())
499 select_
, where_
, limit_
= filter_query_string(bottle
.request
.query
, http2db_host
,
500 ('id', 'name', 'description', 'status', 'admin_state_up', 'ip_name'))
502 myself
= config_dic
['http_threads'][ threading
.current_thread().name
]
503 result
, content
= myself
.db
.get_table(FROM
='hosts', SELECT
=select_
, WHERE
=where_
, LIMIT
=limit_
)
505 print "http_get_hosts Error", content
506 bottle
.abort(-result
, content
)
508 convert_boolean(content
, ('admin_state_up',) )
509 change_keys_http2db(content
, http2db_host
, reverse
=True)
511 row
['links'] = ( {'href': myself
.url_preffix
+ '/hosts/' + str(row
['id']), 'rel': 'bookmark'}, )
512 data
={'hosts' : content
}
515 @bottle.route(url_base
+ '/hosts/<host_id>', method
='GET')
516 def http_get_host_id(host_id
):
517 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
518 return my
.gethost(host_id
)
520 @bottle.route(url_base
+ '/hosts', method
='POST')
521 def http_post_hosts():
522 '''insert a host into the database. All resources are got and inserted'''
523 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
526 bottle
.abort(HTTP_Unauthorized
, "Needed admin privileges")
529 http_content
= format_in( host_new_schema
)
530 r
= remove_extra_items(http_content
, host_new_schema
)
531 if r
is not None: print "http_post_host_id: Warning: remove extra items ", r
532 change_keys_http2db(http_content
['host'], http2db_host
)
534 host
= http_content
['host']
536 if 'host-data' in http_content
:
537 host
.update(http_content
['host-data'])
538 ip_name
=http_content
['host-data']['ip_name']
539 user
=http_content
['host-data']['user']
540 password
=http_content
['host-data'].get('password', None)
542 ip_name
=host
['ip_name']
544 password
=host
.get('password', None)
545 if not RADclass_module
:
547 RADclass_module
= imp
.find_module("RADclass")
548 except (IOError, ImportError) as e
:
549 raise ImportError("Cannot import RADclass.py Openvim not properly installed" +str(e
))
552 rad
= RADclass_module
.RADclass()
553 (return_status
, code
) = rad
.obtain_RAD(user
, password
, ip_name
)
556 if not return_status
:
557 print 'http_post_hosts ERROR obtaining RAD', code
558 bottle
.abort(HTTP_Bad_Request
, code
)
561 rad_structure
= yaml
.load(rad
.to_text())
562 print 'rad_structure\n---------------------'
563 print json
.dumps(rad_structure
, indent
=4)
564 print '---------------------'
566 WHERE_
={"family":rad_structure
['processor']['family'], 'manufacturer':rad_structure
['processor']['manufacturer'], 'version':rad_structure
['processor']['version']}
567 result
, content
= my
.db
.get_table(FROM
='host_ranking',
571 host
['ranking'] = content
[0]['ranking']
573 #error_text= "Host " + str(WHERE_)+ " not found in ranking table. Not valid for VIM management"
574 #bottle.abort(HTTP_Bad_Request, error_text)
576 warning_text
+= "Host " + str(WHERE_
)+ " not found in ranking table. Assuming lowest value 100\n"
577 host
['ranking'] = 100 #TODO: as not used in this version, set the lowest value
579 features
= rad_structure
['processor'].get('features', ())
580 host
['features'] = ",".join(features
)
583 for node
in (rad_structure
['resource topology']['nodes'] or {}).itervalues():
588 for core
in node
['cpu']['eligible_cores']:
589 eligible_cores
.extend(core
)
590 for core
in node
['cpu']['cores']:
591 for thread_id
in core
:
592 c
={'core_id': count
, 'thread_id': thread_id
}
593 if thread_id
not in eligible_cores
: c
['status'] = 'noteligible'
598 for port_k
, port_v
in node
['nics']['nic 0']['ports'].iteritems():
599 if port_v
['virtual']:
603 for port_k2
, port_v2
in node
['nics']['nic 0']['ports'].iteritems():
604 if port_v2
['virtual'] and port_v2
['PF_pci_id']==port_k
:
605 sriovs
.append({'pci':port_k2
, 'mac':port_v2
['mac'], 'source_name':port_v2
['source_name']})
607 #sort sriov according to pci and rename them to the vf number
608 new_sriovs
= sorted(sriovs
, key
=lambda k
: k
['pci'])
610 for sriov
in new_sriovs
:
611 sriov
['source_name'] = index
613 interfaces
.append ({'pci':str(port_k
), 'Mbps': port_v
['speed']/1000000, 'sriovs': new_sriovs
, 'mac':port_v
['mac'], 'source_name':port_v
['source_name']})
614 memory
=node
['memory']['node_size'] / (1024*1024*1024)
615 #memory=get_next_2pow(node['memory']['hugepage_nr'])
616 host
['numas'].append( {'numa_socket': node
['id'], 'hugepages': node
['memory']['hugepage_nr'], 'memory':memory
, 'interfaces': interfaces
, 'cores': cores
} )
617 print json
.dumps(host
, indent
=4)
621 result
, content
= my
.db
.new_host(host
)
623 if content
['admin_state_up']:
625 host_test_mode
= True if config_dic
['mode']=='test' or config_dic
['mode']=="OF only" else False
626 host_develop_mode
= True if config_dic
['mode']=='development' else False
627 host_develop_bridge_iface
= config_dic
.get('development_bridge', None)
628 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'],
629 test
=host_test_mode
, image_path
=config_dic
['image_path'],
630 version
=config_dic
['version'], host_id
=content
['uuid'],
631 develop_mode
=host_develop_mode
, develop_bridge_iface
=host_develop_bridge_iface
)
633 config_dic
['host_threads'][ content
['uuid'] ] = thread
635 if config_dic
['network_type'] == 'ovs':
637 config_dic
['host_threads'][content
['uuid']].insert_task("new-ovsbridge")
638 # check if more host exist
639 create_vxlan_mesh(content
['uuid'])
642 change_keys_http2db(content
, http2db_host
, reverse
=True)
643 if len(warning_text
)>0:
644 content
["warning"]= warning_text
645 data
={'host' : content
}
646 return format_out(data
)
648 bottle
.abort(HTTP_Bad_Request
, content
)
652 def create_vxlan_mesh(host_id
):
653 existing_hosts
= get_hosts()
654 if len(existing_hosts
['hosts']) > 1:
655 computes_available
= existing_hosts
['hosts']
657 # TUNEL openvim controller
659 for count
, compute_owner
in enumerate(computes_available
):
660 for compute
in computes_available
:
661 if compute_owner
['id'] == compute
['id']:
664 vxlan_interface_name
= get_vxlan_interface(compute_owner
['id'][:8])
665 config_dic
['host_threads'][compute
['id']].insert_task("new-vxlan",
666 vxlan_interface_name
,
667 compute_owner
['ip_name'])
670 def delete_vxlan_mesh(host_id
):
672 Create a task for remove a specific compute of the vlxan mesh
673 :param host_id: host id to be deleted.
675 existing_hosts
= get_hosts()
676 computes_available
= existing_hosts
['hosts']
677 vxlan_interface_name
= get_vxlan_interface(host_id
[:8])
679 for compute
in computes_available
:
680 if host_id
== compute
['id']:
683 config_dic
['host_threads'][compute
['id']].insert_task("del-vxlan",vxlan_interface_name
)
686 def get_vxlan_interface(local_uuid
):
688 Genearte a vxlan interface name
689 :param local_uuid: host id
690 :return: vlxan-8digits
692 return 'vxlan-' + local_uuid
[:8]
695 @bottle.route(url_base
+ '/hosts/<host_id>', method
='PUT')
696 def http_put_host_id(host_id
):
697 '''modify a host into the database. All resources are got and inserted'''
698 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
701 bottle
.abort(HTTP_Unauthorized
, "Needed admin privileges")
704 http_content
= format_in( host_edit_schema
)
705 r
= remove_extra_items(http_content
, host_edit_schema
)
706 if r
is not None: print "http_post_host_id: Warning: remove extra items ", r
707 change_keys_http2db(http_content
['host'], http2db_host
)
710 result
, content
= my
.db
.edit_host(host_id
, http_content
['host'])
712 convert_boolean(content
, ('admin_state_up',) )
713 change_keys_http2db(content
, http2db_host
, reverse
=True)
714 data
={'host' : content
}
716 if config_dic
['network_type'] == 'ovs':
717 delete_vxlan_mesh(host_id
)
718 config_dic
['host_threads'][host_id
].insert_task("del-ovsbridge")
721 config_dic
['host_threads'][host_id
].name
= content
.get('name',content
['ip_name'])
722 config_dic
['host_threads'][host_id
].user
= content
['user']
723 config_dic
['host_threads'][host_id
].host
= content
['ip_name']
724 config_dic
['host_threads'][host_id
].insert_task("reload")
726 if config_dic
['network_type'] == 'ovs':
727 # create mesh with new host data
728 config_dic
['host_threads'][host_id
].insert_task("new-ovsbridge")
729 create_vxlan_mesh(host_id
)
732 return format_out(data
)
734 bottle
.abort(HTTP_Bad_Request
, content
)
739 @bottle.route(url_base
+ '/hosts/<host_id>', method
='DELETE')
740 def http_delete_host_id(host_id
):
741 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
744 bottle
.abort(HTTP_Unauthorized
, "Needed admin privileges")
745 result
, content
= my
.db
.delete_row('hosts', host_id
)
747 bottle
.abort(HTTP_Not_Found
, content
)
749 if config_dic
['network_type'] == 'ovs':
750 delete_vxlan_mesh(host_id
)
752 if host_id
in config_dic
['host_threads']:
753 if config_dic
['network_type'] == 'ovs':
754 config_dic
['host_threads'][host_id
].insert_task("del-ovsbridge")
755 config_dic
['host_threads'][host_id
].insert_task("exit")
757 data
={'result' : content
}
758 return format_out(data
)
760 print "http_delete_host_id error",result
, content
761 bottle
.abort(-result
, content
)
770 @bottle.route(url_base
+ '/tenants', method
='GET')
771 def http_get_tenants():
772 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
773 select_
,where_
,limit_
= filter_query_string(bottle
.request
.query
, http2db_tenant
,
774 ('id','name','description','enabled') )
775 result
, content
= my
.db
.get_table(FROM
='tenants', SELECT
=select_
,WHERE
=where_
,LIMIT
=limit_
)
777 print "http_get_tenants Error", content
778 bottle
.abort(-result
, content
)
780 change_keys_http2db(content
, http2db_tenant
, reverse
=True)
781 convert_boolean(content
, ('enabled',))
782 data
={'tenants' : content
}
783 #data['tenants_links'] = dict([('tenant', row['id']) for row in content])
784 return format_out(data
)
786 @bottle.route(url_base
+ '/tenants/<tenant_id>', method
='GET')
787 def http_get_tenant_id(tenant_id
):
788 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
789 result
, content
= my
.db
.get_table(FROM
='tenants', SELECT
=('uuid','name','description', 'enabled'),WHERE
={'uuid': tenant_id
} )
791 print "http_get_tenant_id error %d %s" % (result
, content
)
792 bottle
.abort(-result
, content
)
794 print "http_get_tenant_id tenant '%s' not found" % tenant_id
795 bottle
.abort(HTTP_Not_Found
, "tenant %s not found" % tenant_id
)
797 change_keys_http2db(content
, http2db_tenant
, reverse
=True)
798 convert_boolean(content
, ('enabled',))
799 data
={'tenant' : content
[0]}
800 #data['tenants_links'] = dict([('tenant', row['id']) for row in content])
801 return format_out(data
)
804 @bottle.route(url_base
+ '/tenants', method
='POST')
805 def http_post_tenants():
806 '''insert a tenant into the database.'''
807 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
809 http_content
= format_in( tenant_new_schema
)
810 r
= remove_extra_items(http_content
, tenant_new_schema
)
811 if r
is not None: print "http_post_tenants: Warning: remove extra items ", r
812 change_keys_http2db(http_content
['tenant'], http2db_tenant
)
815 result
, content
= my
.db
.new_tenant(http_content
['tenant'])
818 return http_get_tenant_id(content
)
820 bottle
.abort(-result
, content
)
823 @bottle.route(url_base
+ '/tenants/<tenant_id>', method
='PUT')
824 def http_put_tenant_id(tenant_id
):
825 '''update a tenant into the database.'''
826 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
828 http_content
= format_in( tenant_edit_schema
)
829 r
= remove_extra_items(http_content
, tenant_edit_schema
)
830 if r
is not None: print "http_put_tenant_id: Warning: remove extra items ", r
831 change_keys_http2db(http_content
['tenant'], http2db_tenant
)
834 result
, content
= my
.db
.update_rows('tenants', http_content
['tenant'], WHERE
={'uuid': tenant_id
}, log
=True )
836 return http_get_tenant_id(tenant_id
)
838 bottle
.abort(-result
, content
)
841 @bottle.route(url_base
+ '/tenants/<tenant_id>', method
='DELETE')
842 def http_delete_tenant_id(tenant_id
):
843 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
845 r
, tenants_flavors
= my
.db
.get_table(FROM
='tenants_flavors', SELECT
=('flavor_id','tenant_id'), WHERE
={'tenant_id': tenant_id
})
848 r
, tenants_images
= my
.db
.get_table(FROM
='tenants_images', SELECT
=('image_id','tenant_id'), WHERE
={'tenant_id': tenant_id
})
851 result
, content
= my
.db
.delete_row('tenants', tenant_id
)
853 bottle
.abort(HTTP_Not_Found
, content
)
855 print "alf", tenants_flavors
, tenants_images
856 for flavor
in tenants_flavors
:
857 my
.db
.delete_row_by_key("flavors", "uuid", flavor
['flavor_id'])
858 for image
in tenants_images
:
859 my
.db
.delete_row_by_key("images", "uuid", image
['image_id'])
860 data
={'result' : content
}
861 return format_out(data
)
863 print "http_delete_tenant_id error",result
, content
864 bottle
.abort(-result
, content
)
871 @bottle.route(url_base
+ '/<tenant_id>/flavors', method
='GET')
872 def http_get_flavors(tenant_id
):
873 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
874 #check valid tenant_id
875 result
,content
= check_valid_tenant(my
, tenant_id
)
877 bottle
.abort(result
, content
)
879 select_
,where_
,limit_
= filter_query_string(bottle
.request
.query
, http2db_flavor
,
880 ('id','name','description','public') )
884 from_
='tenants_flavors inner join flavors on tenants_flavors.flavor_id=flavors.uuid'
885 where_
['tenant_id'] = tenant_id
886 result
, content
= my
.db
.get_table(FROM
=from_
, SELECT
=select_
, WHERE
=where_
, LIMIT
=limit_
)
888 print "http_get_flavors Error", content
889 bottle
.abort(-result
, content
)
891 change_keys_http2db(content
, http2db_flavor
, reverse
=True)
893 row
['links']=[ {'href': "/".join( (my
.url_preffix
, tenant_id
, 'flavors', str(row
['id']) ) ), 'rel':'bookmark' } ]
894 data
={'flavors' : content
}
895 return format_out(data
)
897 @bottle.route(url_base
+ '/<tenant_id>/flavors/<flavor_id>', method
='GET')
898 def http_get_flavor_id(tenant_id
, flavor_id
):
899 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
900 #check valid tenant_id
901 result
,content
= check_valid_tenant(my
, tenant_id
)
903 bottle
.abort(result
, content
)
905 select_
,where_
,limit_
= filter_query_string(bottle
.request
.query
, http2db_flavor
,
906 ('id','name','description','ram', 'vcpus', 'extended', 'disk', 'public') )
910 from_
='tenants_flavors as tf inner join flavors as f on tf.flavor_id=f.uuid'
911 where_
['tenant_id'] = tenant_id
912 where_
['uuid'] = flavor_id
913 result
, content
= my
.db
.get_table(SELECT
=select_
, FROM
=from_
, WHERE
=where_
, LIMIT
=limit_
)
916 print "http_get_flavor_id error %d %s" % (result
, content
)
917 bottle
.abort(-result
, content
)
919 print "http_get_flavors_id flavor '%s' not found" % str(flavor_id
)
920 bottle
.abort(HTTP_Not_Found
, 'flavor %s not found' % flavor_id
)
922 change_keys_http2db(content
, http2db_flavor
, reverse
=True)
923 if 'extended' in content
[0] and content
[0]['extended'] is not None:
924 extended
= json
.loads(content
[0]['extended'])
925 if 'devices' in extended
:
926 change_keys_http2db(extended
['devices'], http2db_flavor
, reverse
=True)
927 content
[0]['extended']=extended
928 convert_bandwidth(content
[0], reverse
=True)
929 content
[0]['links']=[ {'href': "/".join( (my
.url_preffix
, tenant_id
, 'flavors', str(content
[0]['id']) ) ), 'rel':'bookmark' } ]
930 data
={'flavor' : content
[0]}
931 #data['tenants_links'] = dict([('tenant', row['id']) for row in content])
932 return format_out(data
)
935 @bottle.route(url_base
+ '/<tenant_id>/flavors', method
='POST')
936 def http_post_flavors(tenant_id
):
937 '''insert a flavor into the database, and attach to tenant.'''
938 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
939 #check valid tenant_id
940 result
,content
= check_valid_tenant(my
, tenant_id
)
942 bottle
.abort(result
, content
)
943 http_content
= format_in( flavor_new_schema
)
944 r
= remove_extra_items(http_content
, flavor_new_schema
)
945 if r
is not None: print "http_post_flavors: Warning: remove extra items ", r
946 change_keys_http2db(http_content
['flavor'], http2db_flavor
)
947 extended_dict
= http_content
['flavor'].pop('extended', None)
948 if extended_dict
is not None:
949 result
, content
= check_extended(extended_dict
)
951 print "http_post_flavors wrong input extended error %d %s" % (result
, content
)
952 bottle
.abort(-result
, content
)
954 convert_bandwidth(extended_dict
)
955 if 'devices' in extended_dict
: change_keys_http2db(extended_dict
['devices'], http2db_flavor
)
956 http_content
['flavor']['extended'] = json
.dumps(extended_dict
)
958 result
, content
= my
.db
.new_flavor(http_content
['flavor'], tenant_id
)
960 return http_get_flavor_id(tenant_id
, content
)
962 print "http_psot_flavors error %d %s" % (result
, content
)
963 bottle
.abort(-result
, content
)
966 @bottle.route(url_base
+ '/<tenant_id>/flavors/<flavor_id>', method
='DELETE')
967 def http_delete_flavor_id(tenant_id
, flavor_id
):
968 '''Deletes the flavor_id of a tenant. IT removes from tenants_flavors table.'''
969 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
970 #check valid tenant_id
971 result
,content
= check_valid_tenant(my
, tenant_id
)
973 bottle
.abort(result
, content
)
975 result
, content
= my
.db
.delete_image_flavor('flavor', flavor_id
, tenant_id
)
977 bottle
.abort(HTTP_Not_Found
, content
)
979 data
={'result' : content
}
980 return format_out(data
)
982 print "http_delete_flavor_id error",result
, content
983 bottle
.abort(-result
, content
)
986 @bottle.route(url_base
+ '/<tenant_id>/flavors/<flavor_id>/<action>', method
='POST')
987 def http_attach_detach_flavors(tenant_id
, flavor_id
, action
):
988 '''attach/detach an existing flavor in this tenant. That is insert/remove at tenants_flavors table.'''
989 #TODO alf: not tested at all!!!
990 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
991 #check valid tenant_id
992 result
,content
= check_valid_tenant(my
, tenant_id
)
994 bottle
.abort(result
, content
)
996 bottle
.abort(HTTP_Bad_Request
, "Invalid tenant 'any' with this command")
998 if action
!='attach' and action
!= 'detach':
999 bottle
.abort(HTTP_Method_Not_Allowed
, "actions can be attach or detach")
1002 #Ensure that flavor exist
1003 from_
='tenants_flavors as tf right join flavors as f on tf.flavor_id=f.uuid'
1004 where_
={'uuid': flavor_id
}
1005 result
, content
= my
.db
.get_table(SELECT
=('public','tenant_id'), FROM
=from_
, WHERE
=where_
)
1007 if action
=='attach':
1008 text_error
="Flavor '%s' not found" % flavor_id
1010 text_error
="Flavor '%s' not found for tenant '%s'" % (flavor_id
, tenant_id
)
1011 bottle
.abort(HTTP_Not_Found
, text_error
)
1015 if action
=='attach':
1016 if flavor
['tenant_id']!=None:
1017 bottle
.abort(HTTP_Conflict
, "Flavor '%s' already attached to tenant '%s'" % (flavor_id
, tenant_id
))
1018 if flavor
['public']=='no' and not my
.admin
:
1019 #allow only attaching public flavors
1020 bottle
.abort(HTTP_Unauthorized
, "Needed admin rights to attach a private flavor")
1022 #insert in data base
1023 result
, content
= my
.db
.new_row('tenants_flavors', {'flavor_id':flavor_id
, 'tenant_id': tenant_id
})
1025 return http_get_flavor_id(tenant_id
, flavor_id
)
1027 if flavor
['tenant_id']==None:
1028 bottle
.abort(HTTP_Not_Found
, "Flavor '%s' not attached to tenant '%s'" % (flavor_id
, tenant_id
))
1029 result
, content
= my
.db
.delete_row_by_dict(FROM
='tenants_flavors', WHERE
={'flavor_id':flavor_id
, 'tenant_id':tenant_id
})
1031 if flavor
['public']=='no':
1032 #try to delete the flavor completely to avoid orphan flavors, IGNORE error
1033 my
.db
.delete_row_by_dict(FROM
='flavors', WHERE
={'uuid':flavor_id
})
1034 data
={'result' : "flavor detached"}
1035 return format_out(data
)
1037 #if get here is because an error
1038 print "http_attach_detach_flavors error %d %s" % (result
, content
)
1039 bottle
.abort(-result
, content
)
1042 @bottle.route(url_base
+ '/<tenant_id>/flavors/<flavor_id>', method
='PUT')
1043 def http_put_flavor_id(tenant_id
, flavor_id
):
1044 '''update a flavor_id into the database.'''
1045 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
1046 #check valid tenant_id
1047 result
,content
= check_valid_tenant(my
, tenant_id
)
1049 bottle
.abort(result
, content
)
1051 http_content
= format_in( flavor_update_schema
)
1052 r
= remove_extra_items(http_content
, flavor_update_schema
)
1053 if r
is not None: print "http_put_flavor_id: Warning: remove extra items ", r
1054 change_keys_http2db(http_content
['flavor'], http2db_flavor
)
1055 extended_dict
= http_content
['flavor'].pop('extended', None)
1056 if extended_dict
is not None:
1057 result
, content
= check_extended(extended_dict
)
1059 print "http_put_flavor_id wrong input extended error %d %s" % (result
, content
)
1060 bottle
.abort(-result
, content
)
1062 convert_bandwidth(extended_dict
)
1063 if 'devices' in extended_dict
: change_keys_http2db(extended_dict
['devices'], http2db_flavor
)
1064 http_content
['flavor']['extended'] = json
.dumps(extended_dict
)
1065 #Ensure that flavor exist
1066 where_
={'uuid': flavor_id
}
1067 if tenant_id
=='any':
1070 from_
='tenants_flavors as ti inner join flavors as i on ti.flavor_id=i.uuid'
1071 where_
['tenant_id'] = tenant_id
1072 result
, content
= my
.db
.get_table(SELECT
=('public',), FROM
=from_
, WHERE
=where_
)
1074 text_error
="Flavor '%s' not found" % flavor_id
1075 if tenant_id
!='any':
1076 text_error
+=" for tenant '%s'" % flavor_id
1077 bottle
.abort(HTTP_Not_Found
, text_error
)
1080 if content
[0]['public']=='yes' and not my
.admin
:
1081 #allow only modifications over private flavors
1082 bottle
.abort(HTTP_Unauthorized
, "Needed admin rights to edit a public flavor")
1084 #insert in data base
1085 result
, content
= my
.db
.update_rows('flavors', http_content
['flavor'], {'uuid': flavor_id
})
1088 print "http_put_flavor_id error %d %s" % (result
, content
)
1089 bottle
.abort(-result
, content
)
1092 return http_get_flavor_id(tenant_id
, flavor_id
)
1100 @bottle.route(url_base
+ '/<tenant_id>/images', method
='GET')
1101 def http_get_images(tenant_id
):
1102 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
1103 #check valid tenant_id
1104 result
,content
= check_valid_tenant(my
, tenant_id
)
1106 bottle
.abort(result
, content
)
1108 select_
,where_
,limit_
= filter_query_string(bottle
.request
.query
, http2db_image
,
1109 ('id','name','description','path','public') )
1110 if tenant_id
=='any':
1113 from_
='tenants_images inner join images on tenants_images.image_id=images.uuid'
1114 where_
['tenant_id'] = tenant_id
1115 result
, content
= my
.db
.get_table(SELECT
=select_
, FROM
=from_
, WHERE
=where_
, LIMIT
=limit_
)
1117 print "http_get_images Error", content
1118 bottle
.abort(-result
, content
)
1120 change_keys_http2db(content
, http2db_image
, reverse
=True)
1121 #for row in content: row['links']=[ {'href': "/".join( (my.url_preffix, tenant_id, 'images', str(row['id']) ) ), 'rel':'bookmark' } ]
1122 data
={'images' : content
}
1123 return format_out(data
)
1125 @bottle.route(url_base
+ '/<tenant_id>/images/<image_id>', method
='GET')
1126 def http_get_image_id(tenant_id
, image_id
):
1127 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
1128 #check valid tenant_id
1129 result
,content
= check_valid_tenant(my
, tenant_id
)
1131 bottle
.abort(result
, content
)
1133 select_
,where_
,limit_
= filter_query_string(bottle
.request
.query
, http2db_image
,
1134 ('id','name','description','progress', 'status','path', 'created', 'updated','public') )
1135 if tenant_id
=='any':
1138 from_
='tenants_images as ti inner join images as i on ti.image_id=i.uuid'
1139 where_
['tenant_id'] = tenant_id
1140 where_
['uuid'] = image_id
1141 result
, content
= my
.db
.get_table(SELECT
=select_
, FROM
=from_
, WHERE
=where_
, LIMIT
=limit_
)
1144 print "http_get_images error %d %s" % (result
, content
)
1145 bottle
.abort(-result
, content
)
1147 print "http_get_images image '%s' not found" % str(image_id
)
1148 bottle
.abort(HTTP_Not_Found
, 'image %s not found' % image_id
)
1150 convert_datetime2str(content
)
1151 change_keys_http2db(content
, http2db_image
, reverse
=True)
1152 if 'metadata' in content
[0] and content
[0]['metadata'] is not None:
1153 metadata
= json
.loads(content
[0]['metadata'])
1154 content
[0]['metadata']=metadata
1155 content
[0]['links']=[ {'href': "/".join( (my
.url_preffix
, tenant_id
, 'images', str(content
[0]['id']) ) ), 'rel':'bookmark' } ]
1156 data
={'image' : content
[0]}
1157 #data['tenants_links'] = dict([('tenant', row['id']) for row in content])
1158 return format_out(data
)
1160 @bottle.route(url_base
+ '/<tenant_id>/images', method
='POST')
1161 def http_post_images(tenant_id
):
1162 '''insert a image into the database, and attach to tenant.'''
1163 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
1164 #check valid tenant_id
1165 result
,content
= check_valid_tenant(my
, tenant_id
)
1167 bottle
.abort(result
, content
)
1168 http_content
= format_in(image_new_schema
)
1169 r
= remove_extra_items(http_content
, image_new_schema
)
1170 if r
is not None: print "http_post_images: Warning: remove extra items ", r
1171 change_keys_http2db(http_content
['image'], http2db_image
)
1172 metadata_dict
= http_content
['image'].pop('metadata', None)
1173 if metadata_dict
is not None:
1174 http_content
['image']['metadata'] = json
.dumps(metadata_dict
)
1176 host_test_mode
= True if config_dic
['mode']=='test' or config_dic
['mode']=="OF only" else False
1178 image_file
= http_content
['image'].get('path',None)
1179 if os
.path
.exists(image_file
):
1180 http_content
['image']['checksum'] = md5(image_file
)
1181 elif is_url(image_file
):
1184 if not host_test_mode
:
1185 content
= "Image file not found"
1186 print "http_post_images error: %d %s" % (HTTP_Bad_Request
, content
)
1187 bottle
.abort(HTTP_Bad_Request
, content
)
1188 except Exception as e
:
1189 print "ERROR. Unexpected exception: %s" % (str(e
))
1190 bottle
.abort(HTTP_Internal_Server_Error
, type(e
).__name
__ + ": " + str(e
))
1191 #insert in data base
1192 result
, content
= my
.db
.new_image(http_content
['image'], tenant_id
)
1194 return http_get_image_id(tenant_id
, content
)
1196 print "http_post_images error %d %s" % (result
, content
)
1197 bottle
.abort(-result
, content
)
1200 @bottle.route(url_base
+ '/<tenant_id>/images/<image_id>', method
='DELETE')
1201 def http_delete_image_id(tenant_id
, image_id
):
1202 '''Deletes the image_id of a tenant. IT removes from tenants_images table.'''
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
)
1208 result
, content
= my
.db
.delete_image_flavor('image', image_id
, tenant_id
)
1210 bottle
.abort(HTTP_Not_Found
, content
)
1212 data
={'result' : content
}
1213 return format_out(data
)
1215 print "http_delete_image_id error",result
, content
1216 bottle
.abort(-result
, content
)
1219 @bottle.route(url_base
+ '/<tenant_id>/images/<image_id>/<action>', method
='POST')
1220 def http_attach_detach_images(tenant_id
, image_id
, action
):
1221 '''attach/detach an existing image in this tenant. That is insert/remove at tenants_images table.'''
1222 #TODO alf: not tested at all!!!
1223 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
1224 #check valid tenant_id
1225 result
,content
= check_valid_tenant(my
, tenant_id
)
1227 bottle
.abort(result
, content
)
1228 if tenant_id
=='any':
1229 bottle
.abort(HTTP_Bad_Request
, "Invalid tenant 'any' with this command")
1231 if action
!='attach' and action
!= 'detach':
1232 bottle
.abort(HTTP_Method_Not_Allowed
, "actions can be attach or detach")
1235 #Ensure that image exist
1236 from_
='tenants_images as ti right join images as i on ti.image_id=i.uuid'
1237 where_
={'uuid': image_id
}
1238 result
, content
= my
.db
.get_table(SELECT
=('public','tenant_id'), FROM
=from_
, WHERE
=where_
)
1240 if action
=='attach':
1241 text_error
="Image '%s' not found" % image_id
1243 text_error
="Image '%s' not found for tenant '%s'" % (image_id
, tenant_id
)
1244 bottle
.abort(HTTP_Not_Found
, text_error
)
1248 if action
=='attach':
1249 if image
['tenant_id']!=None:
1250 bottle
.abort(HTTP_Conflict
, "Image '%s' already attached to tenant '%s'" % (image_id
, tenant_id
))
1251 if image
['public']=='no' and not my
.admin
:
1252 #allow only attaching public images
1253 bottle
.abort(HTTP_Unauthorized
, "Needed admin rights to attach a private image")
1255 #insert in data base
1256 result
, content
= my
.db
.new_row('tenants_images', {'image_id':image_id
, 'tenant_id': tenant_id
})
1258 return http_get_image_id(tenant_id
, image_id
)
1260 if image
['tenant_id']==None:
1261 bottle
.abort(HTTP_Not_Found
, "Image '%s' not attached to tenant '%s'" % (image_id
, tenant_id
))
1262 result
, content
= my
.db
.delete_row_by_dict(FROM
='tenants_images', WHERE
={'image_id':image_id
, 'tenant_id':tenant_id
})
1264 if image
['public']=='no':
1265 #try to delete the image completely to avoid orphan images, IGNORE error
1266 my
.db
.delete_row_by_dict(FROM
='images', WHERE
={'uuid':image_id
})
1267 data
={'result' : "image detached"}
1268 return format_out(data
)
1270 #if get here is because an error
1271 print "http_attach_detach_images error %d %s" % (result
, content
)
1272 bottle
.abort(-result
, content
)
1275 @bottle.route(url_base
+ '/<tenant_id>/images/<image_id>', method
='PUT')
1276 def http_put_image_id(tenant_id
, image_id
):
1277 '''update a image_id into the database.'''
1278 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
1279 #check valid tenant_id
1280 result
,content
= check_valid_tenant(my
, tenant_id
)
1282 bottle
.abort(result
, content
)
1284 http_content
= format_in( image_update_schema
)
1285 r
= remove_extra_items(http_content
, image_update_schema
)
1286 if r
is not None: print "http_put_image_id: Warning: remove extra items ", r
1287 change_keys_http2db(http_content
['image'], http2db_image
)
1288 metadata_dict
= http_content
['image'].pop('metadata', None)
1289 if metadata_dict
is not None:
1290 http_content
['image']['metadata'] = json
.dumps(metadata_dict
)
1291 #Ensure that image exist
1292 where_
={'uuid': image_id
}
1293 if tenant_id
=='any':
1296 from_
='tenants_images as ti inner join images as i on ti.image_id=i.uuid'
1297 where_
['tenant_id'] = tenant_id
1298 result
, content
= my
.db
.get_table(SELECT
=('public',), FROM
=from_
, WHERE
=where_
)
1300 text_error
="Image '%s' not found" % image_id
1301 if tenant_id
!='any':
1302 text_error
+=" for tenant '%s'" % image_id
1303 bottle
.abort(HTTP_Not_Found
, text_error
)
1306 if content
[0]['public']=='yes' and not my
.admin
:
1307 #allow only modifications over private images
1308 bottle
.abort(HTTP_Unauthorized
, "Needed admin rights to edit a public image")
1310 #insert in data base
1311 result
, content
= my
.db
.update_rows('images', http_content
['image'], {'uuid': image_id
})
1314 print "http_put_image_id error %d %s" % (result
, content
)
1315 bottle
.abort(-result
, content
)
1318 return http_get_image_id(tenant_id
, image_id
)
1325 @bottle.route(url_base
+ '/<tenant_id>/servers', method
='GET')
1326 def http_get_servers(tenant_id
):
1327 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
1328 result
,content
= check_valid_tenant(my
, tenant_id
)
1330 bottle
.abort(result
, content
)
1333 select_
,where_
,limit_
= filter_query_string(bottle
.request
.query
, http2db_server
,
1334 ('id','name','description','hostId','imageRef','flavorRef','status', 'tenant_id') )
1335 if tenant_id
!='any':
1336 where_
['tenant_id'] = tenant_id
1337 result
, content
= my
.db
.get_table(SELECT
=select_
, FROM
='instances', WHERE
=where_
, LIMIT
=limit_
)
1339 print "http_get_servers Error", content
1340 bottle
.abort(-result
, content
)
1342 change_keys_http2db(content
, http2db_server
, reverse
=True)
1344 tenant_id
= row
.pop('tenant_id')
1345 row
['links']=[ {'href': "/".join( (my
.url_preffix
, tenant_id
, 'servers', str(row
['id']) ) ), 'rel':'bookmark' } ]
1346 data
={'servers' : content
}
1347 return format_out(data
)
1349 @bottle.route(url_base
+ '/<tenant_id>/servers/<server_id>', method
='GET')
1350 def http_get_server_id(tenant_id
, server_id
):
1351 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
1352 #check valid tenant_id
1353 result
,content
= check_valid_tenant(my
, tenant_id
)
1355 bottle
.abort(result
, content
)
1358 result
, content
= my
.db
.get_instance(server_id
)
1360 bottle
.abort(HTTP_Not_Found
, content
)
1362 #change image/flavor-id to id and link
1363 convert_bandwidth(content
, reverse
=True)
1364 convert_datetime2str(content
)
1365 if content
["ram"]==0 : del content
["ram"]
1366 if content
["vcpus"]==0 : del content
["vcpus"]
1367 if 'flavor_id' in content
:
1368 if content
['flavor_id'] is not None:
1369 content
['flavor'] = {'id':content
['flavor_id'],
1370 'links':[{'href': "/".join( (my
.url_preffix
, content
['tenant_id'], 'flavors', str(content
['flavor_id']) ) ), 'rel':'bookmark'}]
1372 del content
['flavor_id']
1373 if 'image_id' in content
:
1374 if content
['image_id'] is not None:
1375 content
['image'] = {'id':content
['image_id'],
1376 'links':[{'href': "/".join( (my
.url_preffix
, content
['tenant_id'], 'images', str(content
['image_id']) ) ), 'rel':'bookmark'}]
1378 del content
['image_id']
1379 change_keys_http2db(content
, http2db_server
, reverse
=True)
1380 if 'extended' in content
:
1381 if 'devices' in content
['extended']: change_keys_http2db(content
['extended']['devices'], http2db_server
, reverse
=True)
1383 data
={'server' : content
}
1384 return format_out(data
)
1386 bottle
.abort(-result
, content
)
1389 @bottle.route(url_base
+ '/<tenant_id>/servers', method
='POST')
1390 def http_post_server_id(tenant_id
):
1391 '''deploys a new server'''
1392 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
1393 #check valid tenant_id
1394 result
,content
= check_valid_tenant(my
, tenant_id
)
1396 bottle
.abort(result
, content
)
1398 if tenant_id
=='any':
1399 bottle
.abort(HTTP_Bad_Request
, "Invalid tenant 'any' with this command")
1401 http_content
= format_in( server_new_schema
)
1402 r
= remove_extra_items(http_content
, server_new_schema
)
1403 if r
is not None: print "http_post_serves: Warning: remove extra items ", r
1404 change_keys_http2db(http_content
['server'], http2db_server
)
1405 extended_dict
= http_content
['server'].get('extended', None)
1406 if extended_dict
is not None:
1407 result
, content
= check_extended(extended_dict
, True)
1409 print "http_post_servers wrong input extended error %d %s" % (result
, content
)
1410 bottle
.abort(-result
, content
)
1412 convert_bandwidth(extended_dict
)
1413 if 'devices' in extended_dict
: change_keys_http2db(extended_dict
['devices'], http2db_server
)
1415 server
= http_content
['server']
1416 server_start
= server
.get('start', 'yes')
1417 server
['tenant_id'] = tenant_id
1418 #check flavor valid and take info
1419 result
, content
= my
.db
.get_table(FROM
='tenants_flavors as tf join flavors as f on tf.flavor_id=f.uuid',
1420 SELECT
=('ram','vcpus','extended'), WHERE
={'uuid':server
['flavor_id'], 'tenant_id':tenant_id
})
1422 bottle
.abort(HTTP_Not_Found
, 'flavor_id %s not found' % server
['flavor_id'])
1424 server
['flavor']=content
[0]
1425 #check image valid and take info
1426 result
, content
= my
.db
.get_table(FROM
='tenants_images as ti join images as i on ti.image_id=i.uuid',
1427 SELECT
=('path','metadata'), WHERE
={'uuid':server
['image_id'], 'tenant_id':tenant_id
, "status":"ACTIVE"})
1429 bottle
.abort(HTTP_Not_Found
, 'image_id %s not found or not ACTIVE' % server
['image_id'])
1431 server
['image']=content
[0]
1432 if "hosts_id" in server
:
1433 result
, content
= my
.db
.get_table(FROM
='hosts', SELECT
=('uuid',), WHERE
={'uuid': server
['host_id']})
1435 bottle
.abort(HTTP_Not_Found
, 'hostId %s not found' % server
['host_id'])
1437 #print json.dumps(server, indent=4)
1439 result
, content
= ht
.create_server(server
, config_dic
['db'], config_dic
['db_lock'], config_dic
['mode']=='normal')
1442 #Insert instance to database
1445 print "inserting at DB"
1447 if server_start
== 'no':
1448 content
['status'] = 'INACTIVE'
1450 new_instance_result
, new_instance
= my
.db
.new_instance(content
, nets
, ports_to_free
)
1451 if new_instance_result
< 0:
1452 print "Error http_post_servers() :", new_instance_result
, new_instance
1453 bottle
.abort(-new_instance_result
, new_instance
)
1456 print "inserted at DB"
1458 for port
in ports_to_free
:
1459 r
,c
= config_dic
['host_threads'][ server
['host_id'] ].insert_task( 'restore-iface',*port
)
1461 print ' http_post_servers ERROR RESTORE IFACE!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!' + c
1464 r
,c
= config_dic
['of_thread'].insert_task("update-net", net
)
1466 print ':http_post_servers ERROR UPDATING NETS !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!' + c
1470 #look for dhcp ip address
1471 r2
, c2
= my
.db
.get_table(FROM
="ports", SELECT
=["mac", "net_id"], WHERE
={"instance_id": new_instance
})
1472 if r2
>0 and config_dic
.get("dhcp_server"):
1474 if iface
["net_id"] in config_dic
["dhcp_nets"]:
1475 #print "dhcp insert add task"
1476 r
,c
= config_dic
['dhcp_thread'].insert_task("add", iface
["mac"])
1478 print ':http_post_servers ERROR UPDATING dhcp_server !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!' + c
1482 server
['uuid'] = new_instance
1483 # server_start = server.get('start', 'yes')
1484 if config_dic
['network_type'] == 'ovs':
1485 server_net
= get_network_id(c2
[0]['net_id'])
1486 vlan
= str(server_net
['network']['provider:vlan'])
1487 config_dic
['host_threads'][server
['host_id']].insert_task("create-ovs-bridge-port", vlan
)
1488 if server_start
!= 'no':
1489 server
['paused'] = True if server_start
== 'paused' else False
1490 server
['action'] = {"start":None}
1491 server
['status'] = "CREATING"
1493 r
,c
= config_dic
['host_threads'][ server
['host_id'] ].insert_task( 'instance',server
)
1495 my
.db
.update_rows('instances', {'status':"ERROR"}, {'uuid':server
['uuid'], 'last_error':c
}, log
=True)
1497 return http_get_server_id(tenant_id
, new_instance
)
1499 bottle
.abort(HTTP_Bad_Request
, content
)
1502 def http_server_action(server_id
, tenant_id
, action
):
1503 '''Perform actions over a server as resume, reboot, terminate, ...'''
1504 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
1505 server
={"uuid": server_id
, "action":action
}
1506 where
={'uuid': server_id
}
1507 if tenant_id
!='any':
1508 where
['tenant_id']= tenant_id
1509 result
, content
= my
.db
.get_table(FROM
='instances', WHERE
=where
)
1511 bottle
.abort(HTTP_Not_Found
, "server %s not found" % server_id
)
1514 print "http_post_server_action error getting data %d %s" % (result
, content
)
1515 bottle
.abort(HTTP_Internal_Server_Error
, content
)
1517 server
.update(content
[0])
1518 tenant_id
= server
["tenant_id"]
1520 #TODO check a right content
1522 if 'terminate' in action
:
1523 new_status
='DELETING'
1524 elif server
['status'] == 'ERROR': #or server['status'] == 'CREATING':
1525 if 'terminate' not in action
and 'rebuild' not in action
:
1526 bottle
.abort(HTTP_Method_Not_Allowed
, "Server is in ERROR status, must be rebuit or deleted ")
1528 # elif server['status'] == 'INACTIVE':
1529 # if 'start' not in action and 'createImage' not in action:
1530 # bottle.abort(HTTP_Method_Not_Allowed, "The only possible action over an instance in 'INACTIVE' status is 'start'")
1532 # if 'start' in action:
1533 # new_status='CREATING'
1534 # server['paused']='no'
1535 # elif server['status'] == 'PAUSED':
1536 # if 'resume' not in action:
1537 # bottle.abort(HTTP_Method_Not_Allowed, "The only possible action over an instance in 'PAUSED' status is 'resume'")
1539 # elif server['status'] == 'ACTIVE':
1540 # if 'pause' not in action and 'reboot'not in action and 'shutoff'not in action:
1541 # bottle.abort(HTTP_Method_Not_Allowed, "The only possible action over an instance in 'ACTIVE' status is 'pause','reboot' or 'shutoff'")
1544 if 'start' in action
or 'createImage' in action
or 'rebuild' in action
:
1545 #check image valid and take info
1546 image_id
= server
['image_id']
1547 if 'createImage' in action
:
1548 if 'imageRef' in action
['createImage']:
1549 image_id
= action
['createImage']['imageRef']
1550 elif 'disk' in action
['createImage']:
1551 result
, content
= my
.db
.get_table(FROM
='instance_devices',
1552 SELECT
=('image_id','dev'), WHERE
={'instance_id':server
['uuid'],"type":"disk"})
1554 bottle
.abort(HTTP_Not_Found
, 'disk not found for server')
1558 if action
['createImage']['imageRef']['disk'] != None:
1559 for disk
in content
:
1560 if disk
['dev'] == action
['createImage']['imageRef']['disk']:
1561 disk_id
= disk
['image_id']
1564 bottle
.abort(HTTP_Not_Found
, 'disk %s not found for server' % action
['createImage']['imageRef']['disk'])
1567 bottle
.abort(HTTP_Not_Found
, 'more than one disk found for server' )
1571 image_id
= content
[0]['image_id']
1573 result
, content
= my
.db
.get_table(FROM
='tenants_images as ti join images as i on ti.image_id=i.uuid',
1574 SELECT
=('path','metadata'), WHERE
={'uuid':image_id
, 'tenant_id':tenant_id
, "status":"ACTIVE"})
1576 bottle
.abort(HTTP_Not_Found
, 'image_id %s not found or not ACTIVE' % image_id
)
1578 if content
[0]['metadata'] is not None:
1580 metadata
= json
.loads(content
[0]['metadata'])
1582 return -HTTP_Internal_Server_Error
, "Can not decode image metadata"
1583 content
[0]['metadata']=metadata
1585 content
[0]['metadata'] = {}
1586 server
['image']=content
[0]
1587 if 'createImage' in action
:
1588 action
['createImage']['source'] = {'image_id': image_id
, 'path': content
[0]['path']}
1589 if 'createImage' in action
:
1590 #Create an entry in Database for the new image
1591 new_image
={'status':'BUILD', 'progress': 0 }
1592 new_image_metadata
=content
[0]
1593 if 'metadata' in server
['image'] and server
['image']['metadata'] != None:
1594 new_image_metadata
.update(server
['image']['metadata'])
1595 new_image_metadata
= {"use_incremental":"no"}
1596 if 'metadata' in action
['createImage']:
1597 new_image_metadata
.update(action
['createImage']['metadata'])
1598 new_image
['metadata'] = json
.dumps(new_image_metadata
)
1599 new_image
['name'] = action
['createImage'].get('name', None)
1600 new_image
['description'] = action
['createImage'].get('description', None)
1601 new_image
['uuid']=my
.db
.new_uuid()
1602 if 'path' in action
['createImage']:
1603 new_image
['path'] = action
['createImage']['path']
1605 new_image
['path']="/provisional/path/" + new_image
['uuid']
1606 result
, image_uuid
= my
.db
.new_image(new_image
, tenant_id
)
1608 bottle
.abort(HTTP_Bad_Request
, 'Error: ' + image_uuid
)
1610 server
['new_image'] = new_image
1614 r
,c
= config_dic
['host_threads'][ server
['host_id'] ].insert_task( 'instance',server
)
1616 print "Task queue full at host ", server
['host_id']
1617 bottle
.abort(HTTP_Request_Timeout
, c
)
1618 if 'createImage' in action
and result
>= 0:
1619 return http_get_image_id(tenant_id
, image_uuid
)
1621 #Update DB only for CREATING or DELETING status
1622 data
={'result' : 'in process'}
1623 if new_status
!= None and new_status
== 'DELETING':
1628 #look for dhcp ip address
1629 r2
, c2
= my
.db
.get_table(FROM
="ports", SELECT
=["mac", "net_id"], WHERE
={"instance_id": server_id
})
1630 r
, c
= my
.db
.delete_instance(server_id
, tenant_id
, nets
, ports_to_free
, net_ovs_list
, "requested by http")
1631 for port
in ports_to_free
:
1632 r1
,c1
= config_dic
['host_threads'][ server
['host_id'] ].insert_task( 'restore-iface',*port
)
1634 print ' http_post_server_action error at server deletion ERROR resore-iface !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!' + c1
1635 data
={'result' : 'deleting in process, but ifaces cannot be restored!!!!!'}
1637 r1
,c1
= config_dic
['of_thread'].insert_task("update-net", net
)
1639 print ' http_post_server_action error at server deletion ERROR UPDATING NETS !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!' + c1
1640 data
={'result' : 'deleting in process, but openflow rules cannot be deleted!!!!!'}
1641 #look for dhcp ip address
1642 if r2
>0 and config_dic
.get("dhcp_server"):
1644 if iface
["net_id"] in config_dic
["dhcp_nets"]:
1645 r
,c
= config_dic
['dhcp_thread'].insert_task("del", iface
["mac"])
1646 #print "dhcp insert del task"
1648 print ':http_post_servers ERROR UPDATING dhcp_server !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!' + c
1649 if config_dic
['network_type'] == 'ovs':
1650 # delete ovs-port and linux bridge
1651 for net
in net_ovs_list
:
1652 server_net
= get_network_id(net
)
1653 vlan
= str(server_net
['network']['provider:vlan'])
1654 config_dic
['host_threads'][server
['host_id']].insert_task('del-ovs-port', vlan
, server_net
['network']['id'])
1655 return format_out(data
)
1659 @bottle.route(url_base
+ '/<tenant_id>/servers/<server_id>', method
='DELETE')
1660 def http_delete_server_id(tenant_id
, server_id
):
1661 '''delete a server'''
1662 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
1663 #check valid tenant_id
1664 result
,content
= check_valid_tenant(my
, tenant_id
)
1666 bottle
.abort(result
, content
)
1669 return http_server_action(server_id
, tenant_id
, {"terminate":None} )
1672 @bottle.route(url_base
+ '/<tenant_id>/servers/<server_id>/action', method
='POST')
1673 def http_post_server_action(tenant_id
, server_id
):
1674 '''take an action over a server'''
1675 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
1676 #check valid tenant_id
1677 result
,content
= check_valid_tenant(my
, tenant_id
)
1679 bottle
.abort(result
, content
)
1681 http_content
= format_in( server_action_schema
)
1682 #r = remove_extra_items(http_content, server_action_schema)
1683 #if r is not None: print "http_post_server_action: Warning: remove extra items ", r
1685 return http_server_action(server_id
, tenant_id
, http_content
)
1692 @bottle.route(url_base
+ '/networks', method
='GET')
1693 def http_get_networks():
1694 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
1696 select_
,where_
,limit_
= filter_query_string(bottle
.request
.query
, http2db_network
,
1697 ('id','name','tenant_id','type',
1698 'shared','provider:vlan','status','last_error','admin_state_up','provider:physical') )
1699 #TODO temporally remove tenant_id
1700 if "tenant_id" in where_
:
1701 del where_
["tenant_id"]
1702 result
, content
= my
.db
.get_table(SELECT
=select_
, FROM
='nets', WHERE
=where_
, LIMIT
=limit_
)
1704 print "http_get_networks error %d %s" % (result
, content
)
1705 bottle
.abort(-result
, content
)
1707 convert_boolean(content
, ('shared', 'admin_state_up', 'enable_dhcp') )
1708 delete_nulls(content
)
1709 change_keys_http2db(content
, http2db_network
, reverse
=True)
1710 data
={'networks' : content
}
1711 return format_out(data
)
1713 @bottle.route(url_base
+ '/networks/<network_id>', method
='GET')
1714 def http_get_network_id(network_id
):
1715 data
= get_network_id(network_id
)
1716 return format_out(data
)
1718 def get_network_id(network_id
):
1719 my
= config_dic
['http_threads'][threading
.current_thread().name
]
1721 where_
= bottle
.request
.query
1722 where_
['uuid'] = network_id
1723 result
, content
= my
.db
.get_table(FROM
='nets', WHERE
=where_
, LIMIT
=100)
1726 print "http_get_networks_id error %d %s" % (result
, content
)
1727 bottle
.abort(-result
, content
)
1729 print "http_get_networks_id network '%s' not found" % network_id
1730 bottle
.abort(HTTP_Not_Found
, 'network %s not found' % network_id
)
1732 convert_boolean(content
, ('shared', 'admin_state_up', 'enale_dhcp') )
1733 change_keys_http2db(content
, http2db_network
, reverse
=True)
1735 result
, ports
= my
.db
.get_table(FROM
='ports', SELECT
=('uuid as port_id',),
1736 WHERE
={'net_id': network_id
}, LIMIT
=100)
1738 content
[0]['ports'] = ports
1739 delete_nulls(content
[0])
1740 data
={'network' : content
[0]}
1743 @bottle.route(url_base
+ '/networks', method
='POST')
1744 def http_post_networks():
1745 '''insert a network into the database.'''
1746 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
1748 http_content
= format_in( network_new_schema
)
1749 r
= remove_extra_items(http_content
, network_new_schema
)
1750 if r
is not None: print "http_post_networks: Warning: remove extra items ", r
1751 change_keys_http2db(http_content
['network'], http2db_network
)
1752 network
=http_content
['network']
1753 #check valid tenant_id
1754 tenant_id
= network
.get('tenant_id')
1756 result
, _
= my
.db
.get_table(FROM
='tenants', SELECT
=('uuid',), WHERE
={'uuid': tenant_id
,"enabled":True})
1758 bottle
.abort(HTTP_Not_Found
, 'tenant %s not found or not enabled' % tenant_id
)
1762 net_provider
= network
.get('provider')
1763 net_type
= network
.get('type')
1764 net_vlan
= network
.get("vlan")
1765 net_bind_net
= network
.get("bind_net")
1766 net_bind_type
= network
.get("bind_type")
1767 name
= network
["name"]
1769 #check if network name ends with :<vlan_tag> and network exist in order to make and automated bindning
1770 vlan_index
=name
.rfind(":")
1771 if net_bind_net
==None and net_bind_type
==None and vlan_index
> 1:
1773 vlan_tag
= int(name
[vlan_index
+1:])
1774 if vlan_tag
>0 and vlan_tag
< 4096:
1775 net_bind_net
= name
[:vlan_index
]
1776 net_bind_type
= "vlan:" + name
[vlan_index
+1:]
1780 if net_bind_net
!= None:
1781 #look for a valid net
1782 if check_valid_uuid(net_bind_net
):
1783 net_bind_key
= "uuid"
1785 net_bind_key
= "name"
1786 result
, content
= my
.db
.get_table(FROM
='nets', WHERE
={net_bind_key
: net_bind_net
} )
1788 bottle
.abort(HTTP_Internal_Server_Error
, 'getting nets from db ' + content
)
1791 bottle
.abort(HTTP_Bad_Request
, "bind_net %s '%s'not found" % (net_bind_key
, net_bind_net
) )
1794 bottle
.abort(HTTP_Bad_Request
, "more than one bind_net %s '%s' found, use uuid" % (net_bind_key
, net_bind_net
) )
1796 network
["bind_net"] = content
[0]["uuid"]
1797 if net_bind_type
!= None:
1798 if net_bind_type
[0:5] != "vlan:":
1799 bottle
.abort(HTTP_Bad_Request
, "bad format for 'bind_type', must be 'vlan:<tag>'")
1801 if int(net_bind_type
[5:]) > 4095 or int(net_bind_type
[5:])<=0 :
1802 bottle
.abort(HTTP_Bad_Request
, "bad format for 'bind_type', must be 'vlan:<tag>' with a tag between 1 and 4095")
1804 network
["bind_type"] = net_bind_type
1806 if net_provider
!=None:
1807 if net_provider
[:9]=="openflow:":
1809 if net_type
!="ptp" and net_type
!="data":
1810 bottle
.abort(HTTP_Bad_Request
, "Only 'ptp' or 'data' net types can be bound to 'openflow'")
1815 if net_type
!="bridge_man" and net_type
!="bridge_data":
1816 bottle
.abort(HTTP_Bad_Request
, "Only 'bridge_man' or 'bridge_data' net types can be bound to 'bridge', 'macvtap' or 'default")
1818 net_type
='bridge_man'
1821 net_type
='bridge_man'
1823 if net_provider
!= None:
1824 if net_provider
[:7] == 'bridge:':
1825 bridge_net_name
= net_provider
[7:]
1826 for brnet
in config_dic
['bridge_nets']:
1827 if brnet
[0]==bridge_net_name
: # free
1828 if brnet
[3] != None:
1829 bottle
.abort(HTTP_Conflict
, "invalid 'provider:physical', bridge '%s' is already used" % bridge_net_name
)
1834 # if bridge_net==None:
1835 # 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)
1837 elif config_dic
['network_type'] == 'bridge' and net_type
=='bridge_data' or net_type
== 'bridge_man' :
1838 #look for a free precreated nets
1839 for brnet
in config_dic
['bridge_nets']:
1840 if brnet
[3]==None: # free
1841 if bridge_net
!= None:
1842 if net_type
=='bridge_man': #look for the smaller speed
1843 if brnet
[2] < bridge_net
[2]: bridge_net
= brnet
1844 else: #look for the larger speed
1845 if brnet
[2] > bridge_net
[2]: bridge_net
= brnet
1849 if bridge_net
==None:
1850 bottle
.abort(HTTP_Bad_Request
, "Max limits of bridge networks reached. Future versions of VIM will overcome this limit")
1853 print "using net", bridge_net
1854 net_provider
= "bridge:"+bridge_net
[0]
1855 net_vlan
= bridge_net
[1]
1856 elif net_type
== 'bridge_data' or net_type
== 'bridge_man' and config_dic
['network_type'] == 'ovs':
1857 net_provider
= 'OVS'
1858 if not net_vlan
and (net_type
== "data" or net_type
== "ptp" or net_provider
== "OVS"):
1859 net_vlan
= my
.db
.get_free_net_vlan()
1861 bottle
.abort(HTTP_Internal_Server_Error
, "Error getting an available vlan")
1863 if config_dic
['network_type'] == 'ovs':
1864 net_provider
= 'OVS' + ":" + str(net_vlan
)
1866 network
['provider'] = net_provider
1867 network
['type'] = net_type
1868 network
['vlan'] = net_vlan
1869 result
, content
= my
.db
.new_row('nets', network
, True, True)
1872 if bridge_net
!=None:
1873 bridge_net
[3] = content
1875 if config_dic
.get("dhcp_server") and config_dic
['network_type'] == 'bridge':
1876 if network
["name"] in config_dic
["dhcp_server"].get("nets", () ):
1877 config_dic
["dhcp_nets"].append(content
)
1878 print "dhcp_server: add new net", content
1879 elif bridge_net
!= None and bridge_net
[0] in config_dic
["dhcp_server"].get("bridge_ifaces", () ):
1880 config_dic
["dhcp_nets"].append(content
)
1881 print "dhcp_server: add new net", content
1882 return http_get_network_id(content
)
1884 print "http_post_networks error %d %s" % (result
, content
)
1885 bottle
.abort(-result
, content
)
1889 @bottle.route(url_base
+ '/networks/<network_id>', method
='PUT')
1890 def http_put_network_id(network_id
):
1891 '''update a network_id into the database.'''
1892 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
1894 http_content
= format_in( network_update_schema
)
1895 r
= remove_extra_items(http_content
, network_update_schema
)
1896 change_keys_http2db(http_content
['network'], http2db_network
)
1897 network
=http_content
['network']
1899 #Look for the previous data
1900 where_
= {'uuid': network_id
}
1901 result
, network_old
= my
.db
.get_table(FROM
='nets', WHERE
=where_
)
1903 print "http_put_network_id error %d %s" % (result
, network_old
)
1904 bottle
.abort(-result
, network_old
)
1907 print "http_put_network_id network '%s' not found" % network_id
1908 bottle
.abort(HTTP_Not_Found
, 'network %s not found' % network_id
)
1911 nbports
, content
= my
.db
.get_table(FROM
='ports', SELECT
=('uuid as port_id',),
1912 WHERE
={'net_id': network_id
}, LIMIT
=100)
1914 print "http_put_network_id error %d %s" % (result
, network_old
)
1915 bottle
.abort(-result
, content
)
1918 if 'type' in network
and network
['type'] != network_old
[0]['type']:
1919 bottle
.abort(HTTP_Method_Not_Allowed
, "Can not change type of network while having ports attached")
1920 if 'vlan' in network
and network
['vlan'] != network_old
[0]['vlan']:
1921 bottle
.abort(HTTP_Method_Not_Allowed
, "Can not change vlan of network while having ports attached")
1924 net_provider
= network
.get('provider', network_old
[0]['provider'])
1925 net_type
= network
.get('type', network_old
[0]['type'])
1926 net_bind_net
= network
.get("bind_net")
1927 net_bind_type
= network
.get("bind_type")
1928 if net_bind_net
!= None:
1929 #look for a valid net
1930 if check_valid_uuid(net_bind_net
):
1931 net_bind_key
= "uuid"
1933 net_bind_key
= "name"
1934 result
, content
= my
.db
.get_table(FROM
='nets', WHERE
={net_bind_key
: net_bind_net
} )
1936 bottle
.abort(HTTP_Internal_Server_Error
, 'getting nets from db ' + content
)
1939 bottle
.abort(HTTP_Bad_Request
, "bind_net %s '%s'not found" % (net_bind_key
, net_bind_net
) )
1942 bottle
.abort(HTTP_Bad_Request
, "more than one bind_net %s '%s' found, use uuid" % (net_bind_key
, net_bind_net
) )
1944 network
["bind_net"] = content
[0]["uuid"]
1945 if net_bind_type
!= None:
1946 if net_bind_type
[0:5] != "vlan:":
1947 bottle
.abort(HTTP_Bad_Request
, "bad format for 'bind_type', must be 'vlan:<tag>'")
1949 if int(net_bind_type
[5:]) > 4095 or int(net_bind_type
[5:])<=0 :
1950 bottle
.abort(HTTP_Bad_Request
, "bad format for 'bind_type', must be 'vlan:<tag>' with a tag between 1 and 4095")
1952 if net_provider
!=None:
1953 if net_provider
[:9]=="openflow:":
1954 if net_type
!="ptp" and net_type
!="data":
1955 bottle
.abort(HTTP_Bad_Request
, "Only 'ptp' or 'data' net types can be bound to 'openflow'")
1957 if net_type
!="bridge_man" and net_type
!="bridge_data":
1958 bottle
.abort(HTTP_Bad_Request
, "Only 'bridge_man' or 'bridge_data' net types can be bound to 'bridge', 'macvtap' or 'default")
1960 #insert in data base
1961 result
, content
= my
.db
.update_rows('nets', network
, WHERE
={'uuid': network_id
}, log
=True )
1963 if result
>0: # and nbports>0 and 'admin_state_up' in network and network['admin_state_up'] != network_old[0]['admin_state_up']:
1964 r
,c
= config_dic
['of_thread'].insert_task("update-net", network_id
)
1966 print "http_put_network_id error while launching openflow rules"
1967 bottle
.abort(HTTP_Internal_Server_Error
, c
)
1968 if config_dic
.get("dhcp_server"):
1969 if network_id
in config_dic
["dhcp_nets"]:
1970 config_dic
["dhcp_nets"].remove(network_id
)
1971 print "dhcp_server: delete net", network_id
1972 if network
.get("name", network_old
["name"]) in config_dic
["dhcp_server"].get("nets", () ):
1973 config_dic
["dhcp_nets"].append(network_id
)
1974 print "dhcp_server: add new net", network_id
1976 net_bind
= network
.get("bind", network_old
["bind"] )
1977 if net_bind
and net_bind
[:7]=="bridge:" and net_bind
[7:] in config_dic
["dhcp_server"].get("bridge_ifaces", () ):
1978 config_dic
["dhcp_nets"].append(network_id
)
1979 print "dhcp_server: add new net", network_id
1980 return http_get_network_id(network_id
)
1982 bottle
.abort(-result
, content
)
1986 @bottle.route(url_base
+ '/networks/<network_id>', method
='DELETE')
1987 def http_delete_network_id(network_id
):
1988 '''delete a network_id from the database.'''
1989 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
1991 #delete from the data base
1992 result
, content
= my
.db
.delete_row('nets', network_id
)
1995 bottle
.abort(HTTP_Not_Found
, content
)
1997 for brnet
in config_dic
['bridge_nets']:
1998 if brnet
[3]==network_id
:
2001 if config_dic
.get("dhcp_server") and network_id
in config_dic
["dhcp_nets"]:
2002 config_dic
["dhcp_nets"].remove(network_id
)
2003 print "dhcp_server: delete net", network_id
2004 data
={'result' : content
}
2005 return format_out(data
)
2007 print "http_delete_network_id error",result
, content
2008 bottle
.abort(-result
, content
)
2013 @bottle.route(url_base
+ '/networks/<network_id>/openflow', method
='GET')
2014 def http_get_openflow_id(network_id
):
2015 '''To obtain the list of openflow rules of a network
2017 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
2019 if network_id
=='all':
2022 where_
={"net_id": network_id
}
2023 result
, content
= my
.db
.get_table(SELECT
=("name","net_id","priority","vlan_id","ingress_port","src_mac","dst_mac","actions"),
2024 WHERE
=where_
, FROM
='of_flows')
2026 bottle
.abort(-result
, content
)
2028 data
={'openflow-rules' : content
}
2029 return format_out(data
)
2031 @bottle.route(url_base
+ '/networks/<network_id>/openflow', method
='PUT')
2032 def http_put_openflow_id(network_id
):
2033 '''To make actions over the net. The action is to reinstall the openflow rules
2034 network_id can be 'all'
2036 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
2038 bottle
.abort(HTTP_Unauthorized
, "Needed admin privileges")
2041 if network_id
=='all':
2044 where_
={"uuid": network_id
}
2045 result
, content
= my
.db
.get_table(SELECT
=("uuid","type"), WHERE
=where_
, FROM
='nets')
2047 bottle
.abort(-result
, content
)
2051 if net
["type"]!="ptp" and net
["type"]!="data":
2054 r
,c
= config_dic
['of_thread'].insert_task("update-net", net
['uuid'])
2056 print "http_put_openflow_id error while launching openflow rules"
2057 bottle
.abort(HTTP_Internal_Server_Error
, c
)
2058 data
={'result' : str(result
)+" nets updates"}
2059 return format_out(data
)
2061 @bottle.route(url_base
+ '/networks/openflow/clear', method
='DELETE')
2062 @bottle.route(url_base
+ '/networks/clear/openflow', method
='DELETE')
2063 def http_clear_openflow_rules():
2064 '''To make actions over the net. The action is to delete ALL openflow rules
2066 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
2068 bottle
.abort(HTTP_Unauthorized
, "Needed admin privileges")
2071 r
,c
= config_dic
['of_thread'].insert_task("clear-all")
2073 print "http_delete_openflow_id error while launching openflow rules"
2074 bottle
.abort(HTTP_Internal_Server_Error
, c
)
2077 data
={'result' : " Clearing openflow rules in process"}
2078 return format_out(data
)
2080 @bottle.route(url_base
+ '/networks/openflow/ports', method
='GET')
2081 def http_get_openflow_ports():
2082 '''Obtain switch ports names of openflow controller
2084 data
={'ports' : config_dic
['of_thread'].OF_connector
.pp2ofi
}
2085 return format_out(data
)
2092 @bottle.route(url_base
+ '/ports', method
='GET')
2093 def http_get_ports():
2095 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
2096 select_
,where_
,limit_
= filter_query_string(bottle
.request
.query
, http2db_port
,
2097 ('id','name','tenant_id','network_id','vpci','mac_address','device_owner','device_id',
2098 'binding:switch_port','binding:vlan','bandwidth','status','admin_state_up','ip_address') )
2099 #result, content = my.db.get_ports(where_)
2100 result
, content
= my
.db
.get_table(SELECT
=select_
, WHERE
=where_
, FROM
='ports',LIMIT
=limit_
)
2102 print "http_get_ports Error", result
, content
2103 bottle
.abort(-result
, content
)
2106 convert_boolean(content
, ('admin_state_up',) )
2107 delete_nulls(content
)
2108 change_keys_http2db(content
, http2db_port
, reverse
=True)
2109 data
={'ports' : content
}
2110 return format_out(data
)
2112 @bottle.route(url_base
+ '/ports/<port_id>', method
='GET')
2113 def http_get_port_id(port_id
):
2114 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
2116 result
, content
= my
.db
.get_table(WHERE
={'uuid': port_id
}, FROM
='ports')
2118 print "http_get_ports error", result
, content
2119 bottle
.abort(-result
, content
)
2121 print "http_get_ports port '%s' not found" % str(port_id
)
2122 bottle
.abort(HTTP_Not_Found
, 'port %s not found' % port_id
)
2124 convert_boolean(content
, ('admin_state_up',) )
2125 delete_nulls(content
)
2126 change_keys_http2db(content
, http2db_port
, reverse
=True)
2127 data
={'port' : content
[0]}
2128 return format_out(data
)
2131 @bottle.route(url_base
+ '/ports', method
='POST')
2132 def http_post_ports():
2133 '''insert an external port into the database.'''
2134 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
2136 bottle
.abort(HTTP_Unauthorized
, "Needed admin privileges")
2138 http_content
= format_in( port_new_schema
)
2139 r
= remove_extra_items(http_content
, port_new_schema
)
2140 if r
is not None: print "http_post_ports: Warning: remove extra items ", r
2141 change_keys_http2db(http_content
['port'], http2db_port
)
2142 port
=http_content
['port']
2144 port
['type'] = 'external'
2145 if 'net_id' in port
and port
['net_id'] == None:
2148 if 'net_id' in port
:
2149 #check that new net has the correct type
2150 result
, new_net
= my
.db
.check_target_net(port
['net_id'], None, 'external' )
2152 bottle
.abort(HTTP_Bad_Request
, new_net
)
2154 #insert in data base
2155 result
, uuid
= my
.db
.new_row('ports', port
, True, True)
2157 if 'net_id' in port
:
2158 r
,c
= config_dic
['of_thread'].insert_task("update-net", port
['net_id'])
2160 print "http_post_ports error while launching openflow rules"
2161 bottle
.abort(HTTP_Internal_Server_Error
, c
)
2162 return http_get_port_id(uuid
)
2164 bottle
.abort(-result
, uuid
)
2167 @bottle.route(url_base
+ '/ports/<port_id>', method
='PUT')
2168 def http_put_port_id(port_id
):
2169 '''update a port_id into the database.'''
2171 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
2173 http_content
= format_in( port_update_schema
)
2174 change_keys_http2db(http_content
['port'], http2db_port
)
2175 port_dict
=http_content
['port']
2177 #Look for the previous port data
2178 where_
= {'uuid': port_id
}
2179 result
, content
= my
.db
.get_table(FROM
="ports",WHERE
=where_
)
2181 print "http_put_port_id error", result
, content
2182 bottle
.abort(-result
, content
)
2185 print "http_put_port_id port '%s' not found" % port_id
2186 bottle
.abort(HTTP_Not_Found
, 'port %s not found' % port_id
)
2189 for k
in ('vlan','switch_port','mac_address', 'tenant_id'):
2190 if k
in port_dict
and not my
.admin
:
2191 bottle
.abort(HTTP_Unauthorized
, "Needed admin privileges for changing " + k
)
2195 #change_keys_http2db(port, http2db_port, reverse=True)
2199 if 'net_id' in port_dict
:
2201 old_net
= port
.get('net_id', None)
2202 new_net
= port_dict
['net_id']
2203 if old_net
!= new_net
:
2205 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
2206 if old_net
is not None: nets
.append(old_net
)
2207 if port
['type'] == 'instance:bridge':
2208 bottle
.abort(HTTP_Forbidden
, "bridge interfaces cannot be attached to a different net")
2210 elif port
['type'] == 'external':
2212 bottle
.abort(HTTP_Unauthorized
, "Needed admin privileges")
2216 #check that new net has the correct type
2217 result
, new_net_dict
= my
.db
.check_target_net(new_net
, None, port
['type'] )
2219 #change VLAN for SR-IOV ports
2220 if result
>=0 and port
["type"]=="instance:data" and port
["model"]=="VF": #TODO consider also VFnotShared
2222 port_dict
["vlan"] = None
2224 port_dict
["vlan"] = new_net_dict
["vlan"]
2225 #get host where this VM is allocated
2226 result
, content
= my
.db
.get_table(FROM
="instances",WHERE
={"uuid":port
["instance_id"]})
2228 print "http_put_port_id database error", content
2230 host_id
= content
[0]["host_id"]
2232 #insert in data base
2234 result
, content
= my
.db
.update_rows('ports', port_dict
, WHERE
={'uuid': port_id
}, log
=False )
2236 #Insert task to complete actions
2239 r
,v
= config_dic
['of_thread'].insert_task("update-net", net_id
)
2240 if r
<0: print "Error ********* http_put_port_id update_of_flows: ", v
2241 #TODO Do something if fails
2243 config_dic
['host_threads'][host_id
].insert_task("edit-iface", port_id
, old_net
, new_net
)
2246 return http_get_port_id(port_id
)
2248 bottle
.abort(HTTP_Bad_Request
, content
)
2252 @bottle.route(url_base
+ '/ports/<port_id>', method
='DELETE')
2253 def http_delete_port_id(port_id
):
2254 '''delete a port_id from the database.'''
2255 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
2257 bottle
.abort(HTTP_Unauthorized
, "Needed admin privileges")
2260 #Look for the previous port data
2261 where_
= {'uuid': port_id
, "type": "external"}
2262 result
, ports
= my
.db
.get_table(WHERE
=where_
, FROM
='ports',LIMIT
=100)
2265 print "http_delete_port_id port '%s' not found" % port_id
2266 bottle
.abort(HTTP_Not_Found
, 'port %s not found or device_owner is not external' % port_id
)
2268 #delete from the data base
2269 result
, content
= my
.db
.delete_row('ports', port_id
)
2272 bottle
.abort(HTTP_Not_Found
, content
)
2274 network
= ports
[0].get('net_id', None)
2275 if network
is not None:
2277 r
,c
= config_dic
['of_thread'].insert_task("update-net", network
)
2278 if r
<0: print "!!!!!! http_delete_port_id update_of_flows error", r
, c
2279 data
={'result' : content
}
2280 return format_out(data
)
2282 print "http_delete_port_id error",result
, content
2283 bottle
.abort(-result
, content
)