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
})
1474 if config_dic
.get("dhcp_server") and 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
1480 #ensure compute contain the bridge for ovs networks:
1481 server_net
= get_network_id(iface
['net_id'])
1482 if server_net
["network"].get('provider:physical', "")[:3] == 'OVS':
1483 vlan
= str(server_net
['network']['provider:vlan'])
1484 config_dic
['host_threads'][server
['host_id']].insert_task("create-ovs-bridge-port", vlan
)
1487 server
['uuid'] = new_instance
1488 server_start
= server
.get('start', 'yes')
1490 if server_start
!= 'no':
1491 server
['paused'] = True if server_start
== 'paused' else False
1492 server
['action'] = {"start":None}
1493 server
['status'] = "CREATING"
1495 r
,c
= config_dic
['host_threads'][ server
['host_id'] ].insert_task( 'instance',server
)
1497 my
.db
.update_rows('instances', {'status':"ERROR"}, {'uuid':server
['uuid'], 'last_error':c
}, log
=True)
1499 return http_get_server_id(tenant_id
, new_instance
)
1501 bottle
.abort(HTTP_Bad_Request
, content
)
1504 def http_server_action(server_id
, tenant_id
, action
):
1505 '''Perform actions over a server as resume, reboot, terminate, ...'''
1506 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
1507 server
={"uuid": server_id
, "action":action
}
1508 where
={'uuid': server_id
}
1509 if tenant_id
!='any':
1510 where
['tenant_id']= tenant_id
1511 result
, content
= my
.db
.get_table(FROM
='instances', WHERE
=where
)
1513 bottle
.abort(HTTP_Not_Found
, "server %s not found" % server_id
)
1516 print "http_post_server_action error getting data %d %s" % (result
, content
)
1517 bottle
.abort(HTTP_Internal_Server_Error
, content
)
1519 server
.update(content
[0])
1520 tenant_id
= server
["tenant_id"]
1522 #TODO check a right content
1524 if 'terminate' in action
:
1525 new_status
='DELETING'
1526 elif server
['status'] == 'ERROR': #or server['status'] == 'CREATING':
1527 if 'terminate' not in action
and 'rebuild' not in action
:
1528 bottle
.abort(HTTP_Method_Not_Allowed
, "Server is in ERROR status, must be rebuit or deleted ")
1530 # elif server['status'] == 'INACTIVE':
1531 # if 'start' not in action and 'createImage' not in action:
1532 # bottle.abort(HTTP_Method_Not_Allowed, "The only possible action over an instance in 'INACTIVE' status is 'start'")
1534 # if 'start' in action:
1535 # new_status='CREATING'
1536 # server['paused']='no'
1537 # elif server['status'] == 'PAUSED':
1538 # if 'resume' not in action:
1539 # bottle.abort(HTTP_Method_Not_Allowed, "The only possible action over an instance in 'PAUSED' status is 'resume'")
1541 # elif server['status'] == 'ACTIVE':
1542 # if 'pause' not in action and 'reboot'not in action and 'shutoff'not in action:
1543 # bottle.abort(HTTP_Method_Not_Allowed, "The only possible action over an instance in 'ACTIVE' status is 'pause','reboot' or 'shutoff'")
1546 if 'start' in action
or 'createImage' in action
or 'rebuild' in action
:
1547 #check image valid and take info
1548 image_id
= server
['image_id']
1549 if 'createImage' in action
:
1550 if 'imageRef' in action
['createImage']:
1551 image_id
= action
['createImage']['imageRef']
1552 elif 'disk' in action
['createImage']:
1553 result
, content
= my
.db
.get_table(FROM
='instance_devices',
1554 SELECT
=('image_id','dev'), WHERE
={'instance_id':server
['uuid'],"type":"disk"})
1556 bottle
.abort(HTTP_Not_Found
, 'disk not found for server')
1560 if action
['createImage']['imageRef']['disk'] != None:
1561 for disk
in content
:
1562 if disk
['dev'] == action
['createImage']['imageRef']['disk']:
1563 disk_id
= disk
['image_id']
1566 bottle
.abort(HTTP_Not_Found
, 'disk %s not found for server' % action
['createImage']['imageRef']['disk'])
1569 bottle
.abort(HTTP_Not_Found
, 'more than one disk found for server' )
1573 image_id
= content
[0]['image_id']
1575 result
, content
= my
.db
.get_table(FROM
='tenants_images as ti join images as i on ti.image_id=i.uuid',
1576 SELECT
=('path','metadata'), WHERE
={'uuid':image_id
, 'tenant_id':tenant_id
, "status":"ACTIVE"})
1578 bottle
.abort(HTTP_Not_Found
, 'image_id %s not found or not ACTIVE' % image_id
)
1580 if content
[0]['metadata'] is not None:
1582 metadata
= json
.loads(content
[0]['metadata'])
1584 return -HTTP_Internal_Server_Error
, "Can not decode image metadata"
1585 content
[0]['metadata']=metadata
1587 content
[0]['metadata'] = {}
1588 server
['image']=content
[0]
1589 if 'createImage' in action
:
1590 action
['createImage']['source'] = {'image_id': image_id
, 'path': content
[0]['path']}
1591 if 'createImage' in action
:
1592 #Create an entry in Database for the new image
1593 new_image
={'status':'BUILD', 'progress': 0 }
1594 new_image_metadata
=content
[0]
1595 if 'metadata' in server
['image'] and server
['image']['metadata'] != None:
1596 new_image_metadata
.update(server
['image']['metadata'])
1597 new_image_metadata
= {"use_incremental":"no"}
1598 if 'metadata' in action
['createImage']:
1599 new_image_metadata
.update(action
['createImage']['metadata'])
1600 new_image
['metadata'] = json
.dumps(new_image_metadata
)
1601 new_image
['name'] = action
['createImage'].get('name', None)
1602 new_image
['description'] = action
['createImage'].get('description', None)
1603 new_image
['uuid']=my
.db
.new_uuid()
1604 if 'path' in action
['createImage']:
1605 new_image
['path'] = action
['createImage']['path']
1607 new_image
['path']="/provisional/path/" + new_image
['uuid']
1608 result
, image_uuid
= my
.db
.new_image(new_image
, tenant_id
)
1610 bottle
.abort(HTTP_Bad_Request
, 'Error: ' + image_uuid
)
1612 server
['new_image'] = new_image
1616 r
,c
= config_dic
['host_threads'][ server
['host_id'] ].insert_task( 'instance',server
)
1618 print "Task queue full at host ", server
['host_id']
1619 bottle
.abort(HTTP_Request_Timeout
, c
)
1620 if 'createImage' in action
and result
>= 0:
1621 return http_get_image_id(tenant_id
, image_uuid
)
1623 #Update DB only for CREATING or DELETING status
1624 data
={'result' : 'in process'}
1625 if new_status
!= None and new_status
== 'DELETING':
1630 #look for dhcp ip address
1631 r2
, c2
= my
.db
.get_table(FROM
="ports", SELECT
=["mac", "net_id"], WHERE
={"instance_id": server_id
})
1632 r
, c
= my
.db
.delete_instance(server_id
, tenant_id
, nets
, ports_to_free
, net_ovs_list
, "requested by http")
1633 for port
in ports_to_free
:
1634 r1
,c1
= config_dic
['host_threads'][ server
['host_id'] ].insert_task( 'restore-iface',*port
)
1636 print ' http_post_server_action error at server deletion ERROR resore-iface !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!' + c1
1637 data
={'result' : 'deleting in process, but ifaces cannot be restored!!!!!'}
1639 r1
,c1
= config_dic
['of_thread'].insert_task("update-net", net
)
1641 print ' http_post_server_action error at server deletion ERROR UPDATING NETS !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!' + c1
1642 data
={'result' : 'deleting in process, but openflow rules cannot be deleted!!!!!'}
1643 #look for dhcp ip address
1644 if r2
>0 and config_dic
.get("dhcp_server"):
1646 if iface
["net_id"] in config_dic
["dhcp_nets"]:
1647 r
,c
= config_dic
['dhcp_thread'].insert_task("del", iface
["mac"])
1648 #print "dhcp insert del task"
1650 print ':http_post_servers ERROR UPDATING dhcp_server !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!' + c
1651 if config_dic
['network_type'] == 'ovs':
1652 # delete ovs-port and linux bridge
1653 for net
in net_ovs_list
:
1654 server_net
= get_network_id(net
)
1655 vlan
= str(server_net
['network']['provider:vlan'])
1656 config_dic
['host_threads'][server
['host_id']].insert_task('del-ovs-port', vlan
, server_net
['network']['id'])
1657 return format_out(data
)
1661 @bottle.route(url_base
+ '/<tenant_id>/servers/<server_id>', method
='DELETE')
1662 def http_delete_server_id(tenant_id
, server_id
):
1663 '''delete a server'''
1664 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
1665 #check valid tenant_id
1666 result
,content
= check_valid_tenant(my
, tenant_id
)
1668 bottle
.abort(result
, content
)
1671 return http_server_action(server_id
, tenant_id
, {"terminate":None} )
1674 @bottle.route(url_base
+ '/<tenant_id>/servers/<server_id>/action', method
='POST')
1675 def http_post_server_action(tenant_id
, server_id
):
1676 '''take an action over a server'''
1677 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
1678 #check valid tenant_id
1679 result
,content
= check_valid_tenant(my
, tenant_id
)
1681 bottle
.abort(result
, content
)
1683 http_content
= format_in( server_action_schema
)
1684 #r = remove_extra_items(http_content, server_action_schema)
1685 #if r is not None: print "http_post_server_action: Warning: remove extra items ", r
1687 return http_server_action(server_id
, tenant_id
, http_content
)
1694 @bottle.route(url_base
+ '/networks', method
='GET')
1695 def http_get_networks():
1696 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
1698 select_
,where_
,limit_
= filter_query_string(bottle
.request
.query
, http2db_network
,
1699 ('id','name','tenant_id','type',
1700 'shared','provider:vlan','status','last_error','admin_state_up','provider:physical') )
1701 #TODO temporally remove tenant_id
1702 if "tenant_id" in where_
:
1703 del where_
["tenant_id"]
1704 result
, content
= my
.db
.get_table(SELECT
=select_
, FROM
='nets', WHERE
=where_
, LIMIT
=limit_
)
1706 print "http_get_networks error %d %s" % (result
, content
)
1707 bottle
.abort(-result
, content
)
1709 convert_boolean(content
, ('shared', 'admin_state_up', 'enable_dhcp') )
1710 delete_nulls(content
)
1711 change_keys_http2db(content
, http2db_network
, reverse
=True)
1712 data
={'networks' : content
}
1713 return format_out(data
)
1715 @bottle.route(url_base
+ '/networks/<network_id>', method
='GET')
1716 def http_get_network_id(network_id
):
1717 data
= get_network_id(network_id
)
1718 return format_out(data
)
1720 def get_network_id(network_id
):
1721 my
= config_dic
['http_threads'][threading
.current_thread().name
]
1723 where_
= bottle
.request
.query
1724 where_
['uuid'] = network_id
1725 result
, content
= my
.db
.get_table(FROM
='nets', WHERE
=where_
, LIMIT
=100)
1728 print "http_get_networks_id error %d %s" % (result
, content
)
1729 bottle
.abort(-result
, content
)
1731 print "http_get_networks_id network '%s' not found" % network_id
1732 bottle
.abort(HTTP_Not_Found
, 'network %s not found' % network_id
)
1734 convert_boolean(content
, ('shared', 'admin_state_up', 'enale_dhcp') )
1735 change_keys_http2db(content
, http2db_network
, reverse
=True)
1737 result
, ports
= my
.db
.get_table(FROM
='ports', SELECT
=('uuid as port_id',),
1738 WHERE
={'net_id': network_id
}, LIMIT
=100)
1740 content
[0]['ports'] = ports
1741 delete_nulls(content
[0])
1742 data
={'network' : content
[0]}
1745 @bottle.route(url_base
+ '/networks', method
='POST')
1746 def http_post_networks():
1747 '''insert a network into the database.'''
1748 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
1750 http_content
= format_in( network_new_schema
)
1751 r
= remove_extra_items(http_content
, network_new_schema
)
1752 if r
is not None: print "http_post_networks: Warning: remove extra items ", r
1753 change_keys_http2db(http_content
['network'], http2db_network
)
1754 network
=http_content
['network']
1755 #check valid tenant_id
1756 tenant_id
= network
.get('tenant_id')
1758 result
, _
= my
.db
.get_table(FROM
='tenants', SELECT
=('uuid',), WHERE
={'uuid': tenant_id
,"enabled":True})
1760 bottle
.abort(HTTP_Not_Found
, 'tenant %s not found or not enabled' % tenant_id
)
1764 net_provider
= network
.get('provider')
1765 net_type
= network
.get('type')
1766 net_vlan
= network
.get("vlan")
1767 net_bind_net
= network
.get("bind_net")
1768 net_bind_type
= network
.get("bind_type")
1769 name
= network
["name"]
1771 #check if network name ends with :<vlan_tag> and network exist in order to make and automated bindning
1772 vlan_index
=name
.rfind(":")
1773 if net_bind_net
==None and net_bind_type
==None and vlan_index
> 1:
1775 vlan_tag
= int(name
[vlan_index
+1:])
1776 if vlan_tag
>0 and vlan_tag
< 4096:
1777 net_bind_net
= name
[:vlan_index
]
1778 net_bind_type
= "vlan:" + name
[vlan_index
+1:]
1782 if net_bind_net
!= None:
1783 #look for a valid net
1784 if check_valid_uuid(net_bind_net
):
1785 net_bind_key
= "uuid"
1787 net_bind_key
= "name"
1788 result
, content
= my
.db
.get_table(FROM
='nets', WHERE
={net_bind_key
: net_bind_net
} )
1790 bottle
.abort(HTTP_Internal_Server_Error
, 'getting nets from db ' + content
)
1793 bottle
.abort(HTTP_Bad_Request
, "bind_net %s '%s'not found" % (net_bind_key
, net_bind_net
) )
1796 bottle
.abort(HTTP_Bad_Request
, "more than one bind_net %s '%s' found, use uuid" % (net_bind_key
, net_bind_net
) )
1798 network
["bind_net"] = content
[0]["uuid"]
1799 if net_bind_type
!= None:
1800 if net_bind_type
[0:5] != "vlan:":
1801 bottle
.abort(HTTP_Bad_Request
, "bad format for 'bind_type', must be 'vlan:<tag>'")
1803 if int(net_bind_type
[5:]) > 4095 or int(net_bind_type
[5:])<=0 :
1804 bottle
.abort(HTTP_Bad_Request
, "bad format for 'bind_type', must be 'vlan:<tag>' with a tag between 1 and 4095")
1806 network
["bind_type"] = net_bind_type
1808 if net_provider
!=None:
1809 if net_provider
[:9]=="openflow:":
1811 if net_type
!="ptp" and net_type
!="data":
1812 bottle
.abort(HTTP_Bad_Request
, "Only 'ptp' or 'data' net types can be bound to 'openflow'")
1817 if net_type
!="bridge_man" and net_type
!="bridge_data":
1818 bottle
.abort(HTTP_Bad_Request
, "Only 'bridge_man' or 'bridge_data' net types can be bound to 'bridge', 'macvtap' or 'default")
1820 net_type
='bridge_man'
1823 net_type
='bridge_man'
1825 if net_provider
!= None:
1826 if net_provider
[:7] == 'bridge:':
1827 bridge_net_name
= net_provider
[7:]
1828 for brnet
in config_dic
['bridge_nets']:
1829 if brnet
[0]==bridge_net_name
: # free
1830 if brnet
[3] != None:
1831 bottle
.abort(HTTP_Conflict
, "invalid 'provider:physical', bridge '%s' is already used" % bridge_net_name
)
1836 # if bridge_net==None:
1837 # 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)
1839 elif config_dic
['network_type'] == 'bridge' and ( net_type
=='bridge_data' or net_type
== 'bridge_man' ):
1840 #look for a free precreated nets
1841 for brnet
in config_dic
['bridge_nets']:
1842 if brnet
[3]==None: # free
1843 if bridge_net
!= None:
1844 if net_type
=='bridge_man': #look for the smaller speed
1845 if brnet
[2] < bridge_net
[2]: bridge_net
= brnet
1846 else: #look for the larger speed
1847 if brnet
[2] > bridge_net
[2]: bridge_net
= brnet
1851 if bridge_net
==None:
1852 bottle
.abort(HTTP_Bad_Request
, "Max limits of bridge networks reached. Future versions of VIM will overcome this limit")
1855 print "using net", bridge_net
1856 net_provider
= "bridge:"+bridge_net
[0]
1857 net_vlan
= bridge_net
[1]
1858 elif net_type
== 'bridge_data' or net_type
== 'bridge_man' and config_dic
['network_type'] == 'ovs':
1859 net_provider
= 'OVS'
1860 if not net_vlan
and (net_type
== "data" or net_type
== "ptp" or net_provider
== "OVS"):
1861 net_vlan
= my
.db
.get_free_net_vlan()
1863 bottle
.abort(HTTP_Internal_Server_Error
, "Error getting an available vlan")
1865 if net_provider
== 'OVS':
1866 net_provider
= 'OVS' + ":" + str(net_vlan
)
1868 network
['provider'] = net_provider
1869 network
['type'] = net_type
1870 network
['vlan'] = net_vlan
1871 result
, content
= my
.db
.new_row('nets', network
, True, True)
1874 if bridge_net
!=None:
1875 bridge_net
[3] = content
1877 if config_dic
.get("dhcp_server") and config_dic
['network_type'] == 'bridge':
1878 if network
["name"] in config_dic
["dhcp_server"].get("nets", () ):
1879 config_dic
["dhcp_nets"].append(content
)
1880 print "dhcp_server: add new net", content
1881 elif bridge_net
!= None and bridge_net
[0] in config_dic
["dhcp_server"].get("bridge_ifaces", () ):
1882 config_dic
["dhcp_nets"].append(content
)
1883 print "dhcp_server: add new net", content
1884 return http_get_network_id(content
)
1886 print "http_post_networks error %d %s" % (result
, content
)
1887 bottle
.abort(-result
, content
)
1891 @bottle.route(url_base
+ '/networks/<network_id>', method
='PUT')
1892 def http_put_network_id(network_id
):
1893 '''update a network_id into the database.'''
1894 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
1896 http_content
= format_in( network_update_schema
)
1897 r
= remove_extra_items(http_content
, network_update_schema
)
1898 change_keys_http2db(http_content
['network'], http2db_network
)
1899 network
=http_content
['network']
1901 #Look for the previous data
1902 where_
= {'uuid': network_id
}
1903 result
, network_old
= my
.db
.get_table(FROM
='nets', WHERE
=where_
)
1905 print "http_put_network_id error %d %s" % (result
, network_old
)
1906 bottle
.abort(-result
, network_old
)
1909 print "http_put_network_id network '%s' not found" % network_id
1910 bottle
.abort(HTTP_Not_Found
, 'network %s not found' % network_id
)
1913 nbports
, content
= my
.db
.get_table(FROM
='ports', SELECT
=('uuid as port_id',),
1914 WHERE
={'net_id': network_id
}, LIMIT
=100)
1916 print "http_put_network_id error %d %s" % (result
, network_old
)
1917 bottle
.abort(-result
, content
)
1920 if 'type' in network
and network
['type'] != network_old
[0]['type']:
1921 bottle
.abort(HTTP_Method_Not_Allowed
, "Can not change type of network while having ports attached")
1922 if 'vlan' in network
and network
['vlan'] != network_old
[0]['vlan']:
1923 bottle
.abort(HTTP_Method_Not_Allowed
, "Can not change vlan of network while having ports attached")
1926 net_provider
= network
.get('provider', network_old
[0]['provider'])
1927 net_type
= network
.get('type', network_old
[0]['type'])
1928 net_bind_net
= network
.get("bind_net")
1929 net_bind_type
= network
.get("bind_type")
1930 if net_bind_net
!= None:
1931 #look for a valid net
1932 if check_valid_uuid(net_bind_net
):
1933 net_bind_key
= "uuid"
1935 net_bind_key
= "name"
1936 result
, content
= my
.db
.get_table(FROM
='nets', WHERE
={net_bind_key
: net_bind_net
} )
1938 bottle
.abort(HTTP_Internal_Server_Error
, 'getting nets from db ' + content
)
1941 bottle
.abort(HTTP_Bad_Request
, "bind_net %s '%s'not found" % (net_bind_key
, net_bind_net
) )
1944 bottle
.abort(HTTP_Bad_Request
, "more than one bind_net %s '%s' found, use uuid" % (net_bind_key
, net_bind_net
) )
1946 network
["bind_net"] = content
[0]["uuid"]
1947 if net_bind_type
!= None:
1948 if net_bind_type
[0:5] != "vlan:":
1949 bottle
.abort(HTTP_Bad_Request
, "bad format for 'bind_type', must be 'vlan:<tag>'")
1951 if int(net_bind_type
[5:]) > 4095 or int(net_bind_type
[5:])<=0 :
1952 bottle
.abort(HTTP_Bad_Request
, "bad format for 'bind_type', must be 'vlan:<tag>' with a tag between 1 and 4095")
1954 if net_provider
!=None:
1955 if net_provider
[:9]=="openflow:":
1956 if net_type
!="ptp" and net_type
!="data":
1957 bottle
.abort(HTTP_Bad_Request
, "Only 'ptp' or 'data' net types can be bound to 'openflow'")
1959 if net_type
!="bridge_man" and net_type
!="bridge_data":
1960 bottle
.abort(HTTP_Bad_Request
, "Only 'bridge_man' or 'bridge_data' net types can be bound to 'bridge', 'macvtap' or 'default")
1962 #insert in data base
1963 result
, content
= my
.db
.update_rows('nets', network
, WHERE
={'uuid': network_id
}, log
=True )
1965 if result
>0: # and nbports>0 and 'admin_state_up' in network and network['admin_state_up'] != network_old[0]['admin_state_up']:
1966 r
,c
= config_dic
['of_thread'].insert_task("update-net", network_id
)
1968 print "http_put_network_id error while launching openflow rules"
1969 bottle
.abort(HTTP_Internal_Server_Error
, c
)
1970 if config_dic
.get("dhcp_server"):
1971 if network_id
in config_dic
["dhcp_nets"]:
1972 config_dic
["dhcp_nets"].remove(network_id
)
1973 print "dhcp_server: delete net", network_id
1974 if network
.get("name", network_old
["name"]) in config_dic
["dhcp_server"].get("nets", () ):
1975 config_dic
["dhcp_nets"].append(network_id
)
1976 print "dhcp_server: add new net", network_id
1978 net_bind
= network
.get("bind", network_old
["bind"] )
1979 if net_bind
and net_bind
[:7]=="bridge:" and net_bind
[7:] in config_dic
["dhcp_server"].get("bridge_ifaces", () ):
1980 config_dic
["dhcp_nets"].append(network_id
)
1981 print "dhcp_server: add new net", network_id
1982 return http_get_network_id(network_id
)
1984 bottle
.abort(-result
, content
)
1988 @bottle.route(url_base
+ '/networks/<network_id>', method
='DELETE')
1989 def http_delete_network_id(network_id
):
1990 '''delete a network_id from the database.'''
1991 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
1993 #delete from the data base
1994 result
, content
= my
.db
.delete_row('nets', network_id
)
1997 bottle
.abort(HTTP_Not_Found
, content
)
1999 for brnet
in config_dic
['bridge_nets']:
2000 if brnet
[3]==network_id
:
2003 if config_dic
.get("dhcp_server") and network_id
in config_dic
["dhcp_nets"]:
2004 config_dic
["dhcp_nets"].remove(network_id
)
2005 print "dhcp_server: delete net", network_id
2006 data
={'result' : content
}
2007 return format_out(data
)
2009 print "http_delete_network_id error",result
, content
2010 bottle
.abort(-result
, content
)
2015 @bottle.route(url_base
+ '/networks/<network_id>/openflow', method
='GET')
2016 def http_get_openflow_id(network_id
):
2017 '''To obtain the list of openflow rules of a network
2019 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
2021 if network_id
=='all':
2024 where_
={"net_id": network_id
}
2025 result
, content
= my
.db
.get_table(SELECT
=("name","net_id","priority","vlan_id","ingress_port","src_mac","dst_mac","actions"),
2026 WHERE
=where_
, FROM
='of_flows')
2028 bottle
.abort(-result
, content
)
2030 data
={'openflow-rules' : content
}
2031 return format_out(data
)
2033 @bottle.route(url_base
+ '/networks/<network_id>/openflow', method
='PUT')
2034 def http_put_openflow_id(network_id
):
2035 '''To make actions over the net. The action is to reinstall the openflow rules
2036 network_id can be 'all'
2038 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
2040 bottle
.abort(HTTP_Unauthorized
, "Needed admin privileges")
2043 if network_id
=='all':
2046 where_
={"uuid": network_id
}
2047 result
, content
= my
.db
.get_table(SELECT
=("uuid","type"), WHERE
=where_
, FROM
='nets')
2049 bottle
.abort(-result
, content
)
2053 if net
["type"]!="ptp" and net
["type"]!="data":
2056 r
,c
= config_dic
['of_thread'].insert_task("update-net", net
['uuid'])
2058 print "http_put_openflow_id error while launching openflow rules"
2059 bottle
.abort(HTTP_Internal_Server_Error
, c
)
2060 data
={'result' : str(result
)+" nets updates"}
2061 return format_out(data
)
2063 @bottle.route(url_base
+ '/networks/openflow/clear', method
='DELETE')
2064 @bottle.route(url_base
+ '/networks/clear/openflow', method
='DELETE')
2065 def http_clear_openflow_rules():
2066 '''To make actions over the net. The action is to delete ALL openflow rules
2068 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
2070 bottle
.abort(HTTP_Unauthorized
, "Needed admin privileges")
2073 r
,c
= config_dic
['of_thread'].insert_task("clear-all")
2075 print "http_delete_openflow_id error while launching openflow rules"
2076 bottle
.abort(HTTP_Internal_Server_Error
, c
)
2079 data
={'result' : " Clearing openflow rules in process"}
2080 return format_out(data
)
2082 @bottle.route(url_base
+ '/networks/openflow/ports', method
='GET')
2083 def http_get_openflow_ports():
2084 '''Obtain switch ports names of openflow controller
2086 data
={'ports' : config_dic
['of_thread'].OF_connector
.pp2ofi
}
2087 return format_out(data
)
2094 @bottle.route(url_base
+ '/ports', method
='GET')
2095 def http_get_ports():
2097 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
2098 select_
,where_
,limit_
= filter_query_string(bottle
.request
.query
, http2db_port
,
2099 ('id','name','tenant_id','network_id','vpci','mac_address','device_owner','device_id',
2100 'binding:switch_port','binding:vlan','bandwidth','status','admin_state_up','ip_address') )
2101 #result, content = my.db.get_ports(where_)
2102 result
, content
= my
.db
.get_table(SELECT
=select_
, WHERE
=where_
, FROM
='ports',LIMIT
=limit_
)
2104 print "http_get_ports Error", result
, content
2105 bottle
.abort(-result
, content
)
2108 convert_boolean(content
, ('admin_state_up',) )
2109 delete_nulls(content
)
2110 change_keys_http2db(content
, http2db_port
, reverse
=True)
2111 data
={'ports' : content
}
2112 return format_out(data
)
2114 @bottle.route(url_base
+ '/ports/<port_id>', method
='GET')
2115 def http_get_port_id(port_id
):
2116 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
2118 result
, content
= my
.db
.get_table(WHERE
={'uuid': port_id
}, FROM
='ports')
2120 print "http_get_ports error", result
, content
2121 bottle
.abort(-result
, content
)
2123 print "http_get_ports port '%s' not found" % str(port_id
)
2124 bottle
.abort(HTTP_Not_Found
, 'port %s not found' % port_id
)
2126 convert_boolean(content
, ('admin_state_up',) )
2127 delete_nulls(content
)
2128 change_keys_http2db(content
, http2db_port
, reverse
=True)
2129 data
={'port' : content
[0]}
2130 return format_out(data
)
2133 @bottle.route(url_base
+ '/ports', method
='POST')
2134 def http_post_ports():
2135 '''insert an external port into the database.'''
2136 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
2138 bottle
.abort(HTTP_Unauthorized
, "Needed admin privileges")
2140 http_content
= format_in( port_new_schema
)
2141 r
= remove_extra_items(http_content
, port_new_schema
)
2142 if r
is not None: print "http_post_ports: Warning: remove extra items ", r
2143 change_keys_http2db(http_content
['port'], http2db_port
)
2144 port
=http_content
['port']
2146 port
['type'] = 'external'
2147 if 'net_id' in port
and port
['net_id'] == None:
2150 if 'net_id' in port
:
2151 #check that new net has the correct type
2152 result
, new_net
= my
.db
.check_target_net(port
['net_id'], None, 'external' )
2154 bottle
.abort(HTTP_Bad_Request
, new_net
)
2156 #insert in data base
2157 result
, uuid
= my
.db
.new_row('ports', port
, True, True)
2159 if 'net_id' in port
:
2160 r
,c
= config_dic
['of_thread'].insert_task("update-net", port
['net_id'])
2162 print "http_post_ports error while launching openflow rules"
2163 bottle
.abort(HTTP_Internal_Server_Error
, c
)
2164 return http_get_port_id(uuid
)
2166 bottle
.abort(-result
, uuid
)
2169 @bottle.route(url_base
+ '/ports/<port_id>', method
='PUT')
2170 def http_put_port_id(port_id
):
2171 '''update a port_id into the database.'''
2173 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
2175 http_content
= format_in( port_update_schema
)
2176 change_keys_http2db(http_content
['port'], http2db_port
)
2177 port_dict
=http_content
['port']
2179 #Look for the previous port data
2180 where_
= {'uuid': port_id
}
2181 result
, content
= my
.db
.get_table(FROM
="ports",WHERE
=where_
)
2183 print "http_put_port_id error", result
, content
2184 bottle
.abort(-result
, content
)
2187 print "http_put_port_id port '%s' not found" % port_id
2188 bottle
.abort(HTTP_Not_Found
, 'port %s not found' % port_id
)
2191 for k
in ('vlan','switch_port','mac_address', 'tenant_id'):
2192 if k
in port_dict
and not my
.admin
:
2193 bottle
.abort(HTTP_Unauthorized
, "Needed admin privileges for changing " + k
)
2197 #change_keys_http2db(port, http2db_port, reverse=True)
2201 if 'net_id' in port_dict
:
2203 old_net
= port
.get('net_id', None)
2204 new_net
= port_dict
['net_id']
2205 if old_net
!= new_net
:
2207 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
2208 if old_net
is not None: nets
.append(old_net
)
2209 if port
['type'] == 'instance:bridge':
2210 bottle
.abort(HTTP_Forbidden
, "bridge interfaces cannot be attached to a different net")
2212 elif port
['type'] == 'external':
2214 bottle
.abort(HTTP_Unauthorized
, "Needed admin privileges")
2218 #check that new net has the correct type
2219 result
, new_net_dict
= my
.db
.check_target_net(new_net
, None, port
['type'] )
2221 #change VLAN for SR-IOV ports
2222 if result
>=0 and port
["type"]=="instance:data" and port
["model"]=="VF": #TODO consider also VFnotShared
2224 port_dict
["vlan"] = None
2226 port_dict
["vlan"] = new_net_dict
["vlan"]
2227 #get host where this VM is allocated
2228 result
, content
= my
.db
.get_table(FROM
="instances",WHERE
={"uuid":port
["instance_id"]})
2230 print "http_put_port_id database error", content
2232 host_id
= content
[0]["host_id"]
2234 #insert in data base
2236 result
, content
= my
.db
.update_rows('ports', port_dict
, WHERE
={'uuid': port_id
}, log
=False )
2238 #Insert task to complete actions
2241 r
,v
= config_dic
['of_thread'].insert_task("update-net", net_id
)
2242 if r
<0: print "Error ********* http_put_port_id update_of_flows: ", v
2243 #TODO Do something if fails
2245 config_dic
['host_threads'][host_id
].insert_task("edit-iface", port_id
, old_net
, new_net
)
2248 return http_get_port_id(port_id
)
2250 bottle
.abort(HTTP_Bad_Request
, content
)
2254 @bottle.route(url_base
+ '/ports/<port_id>', method
='DELETE')
2255 def http_delete_port_id(port_id
):
2256 '''delete a port_id from the database.'''
2257 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
2259 bottle
.abort(HTTP_Unauthorized
, "Needed admin privileges")
2262 #Look for the previous port data
2263 where_
= {'uuid': port_id
, "type": "external"}
2264 result
, ports
= my
.db
.get_table(WHERE
=where_
, FROM
='ports',LIMIT
=100)
2267 print "http_delete_port_id port '%s' not found" % port_id
2268 bottle
.abort(HTTP_Not_Found
, 'port %s not found or device_owner is not external' % port_id
)
2270 #delete from the data base
2271 result
, content
= my
.db
.delete_row('ports', port_id
)
2274 bottle
.abort(HTTP_Not_Found
, content
)
2276 network
= ports
[0].get('net_id', None)
2277 if network
is not None:
2279 r
,c
= config_dic
['of_thread'].insert_task("update-net", network
)
2280 if r
<0: print "!!!!!! http_delete_port_id update_of_flows error", r
, c
2281 data
={'result' : content
}
2282 return format_out(data
)
2284 print "http_delete_port_id error",result
, content
2285 bottle
.abort(-result
, content
)