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 from netaddr
import IPNetwork
, IPAddress
, all_matching_cidrs
42 #import only if needed because not needed in test mode. To allow an easier installation import RADclass
43 from jsonschema
import validate
as js_v
, exceptions
as js_e
44 import host_thread
as ht
45 from vim_schema
import host_new_schema
, host_edit_schema
, tenant_new_schema
, \
47 flavor_new_schema
, flavor_update_schema
, \
48 image_new_schema
, image_update_schema
, \
49 server_new_schema
, server_action_schema
, network_new_schema
, network_update_schema
, \
50 port_new_schema
, port_update_schema
55 global RADclass_module
56 RADclass
=None #RADclass module is charged only if not in test mode
60 HTTP_Bad_Request
= 400
61 HTTP_Unauthorized
= 401
64 HTTP_Method_Not_Allowed
= 405
65 HTTP_Not_Acceptable
= 406
66 HTTP_Request_Timeout
= 408
68 HTTP_Service_Unavailable
= 503
69 HTTP_Internal_Server_Error
= 500
72 hash_md5
= hashlib
.md5()
73 with
open(fname
, "rb") as f
:
74 for chunk
in iter(lambda: f
.read(4096), b
""):
75 hash_md5
.update(chunk
)
76 return hash_md5
.hexdigest()
78 def check_extended(extended
, allow_net_attach
=False):
79 '''Makes and extra checking of extended input that cannot be done using jsonschema
81 allow_net_attach: for allowing or not the uuid field at interfaces
82 that are allowed for instance, but not for flavors
83 Return: (<0, error_text) if error; (0,None) if not error '''
84 if "numas" not in extended
: return 0, None
87 for numa
in extended
["numas"]:
91 if "cores-id" in numa
:
92 if len(numa
["cores-id"]) != numa
["cores"]:
93 return -HTTP_Bad_Request
, "different number of cores-id (%d) than cores (%d) at numa %d" % (len(numa
["cores-id"]), numa
["cores"],numaid
)
94 id_s
.extend(numa
["cores-id"])
97 if "threads-id" in numa
:
98 if len(numa
["threads-id"]) != numa
["threads"]:
99 return -HTTP_Bad_Request
, "different number of threads-id (%d) than threads (%d) at numa %d" % (len(numa
["threads-id"]), numa
["threads"],numaid
)
100 id_s
.extend(numa
["threads-id"])
101 if "paired-threads" in numa
:
103 if "paired-threads-id" in numa
:
104 if len(numa
["paired-threads-id"]) != numa
["paired-threads"]:
105 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
)
106 for pair
in numa
["paired-threads-id"]:
108 return -HTTP_Bad_Request
, "paired-threads-id must contain a list of two elements list at numa %d" % (numaid
)
111 return -HTTP_Service_Unavailable
, "only one of cores, threads, paired-threads are allowed in this version at numa %d" % numaid
113 if "interfaces" in numa
:
117 for interface
in numa
["interfaces"]:
118 if "uuid" in interface
and not allow_net_attach
:
119 return -HTTP_Bad_Request
, "uuid field is not allowed at numa %d interface %s position %d" % (numaid
, interface
.get("name",""), ifaceid
)
120 if "mac_address" in interface
and interface
["dedicated"]=="yes":
121 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
)
122 if "name" in interface
:
123 if interface
["name"] in names
:
124 return -HTTP_Bad_Request
, "name repeated at numa %d, interface %s position %d" % (numaid
, interface
.get("name",""), ifaceid
)
125 names
.append(interface
["name"])
126 if "vpci" in interface
:
127 if interface
["vpci"] in vpcis
:
128 return -HTTP_Bad_Request
, "vpci %s repeated at numa %d, interface %s position %d" % (interface
["vpci"], numaid
, interface
.get("name",""), ifaceid
)
129 vpcis
.append(interface
["vpci"])
133 return -HTTP_Service_Unavailable
, "only one numa can be defined in this version "
134 for a
in range(0,len(id_s
)):
136 return -HTTP_Bad_Request
, "core/thread identifiers must start at 0 and gaps are not alloed. Missing id number %d" % a
141 # dictionaries that change from HTTP API to database naming
143 http2db_host
={'id':'uuid'}
144 http2db_tenant
={'id':'uuid'}
145 http2db_flavor
={'id':'uuid','imageRef':'image_id'}
146 http2db_image
={'id':'uuid', 'created':'created_at', 'updated':'modified_at', 'public': 'public'}
147 http2db_server
={'id':'uuid','hostId':'host_id','flavorRef':'flavor_id','imageRef':'image_id','created':'created_at'}
148 http2db_network
={'id':'uuid','provider:vlan':'vlan', 'provider:physical': 'provider'}
149 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'}
151 def remove_extra_items(data
, schema
):
153 if type(data
) is tuple or type(data
) is list:
155 a
= remove_extra_items(d
, schema
['items'])
156 if a
is not None: deleted
.append(a
)
157 elif type(data
) is dict:
158 for k
in data
.keys():
159 if 'properties' not in schema
or k
not in schema
['properties'].keys():
163 a
= remove_extra_items(data
[k
], schema
['properties'][k
])
164 if a
is not None: deleted
.append({k
:a
})
165 if len(deleted
) == 0: return None
166 elif len(deleted
) == 1: return deleted
[0]
169 def delete_nulls(var
):
170 if type(var
) is dict:
172 if var
[k
] is None: del var
[k
]
173 elif type(var
[k
]) is dict or type(var
[k
]) is list or type(var
[k
]) is tuple:
174 if delete_nulls(var
[k
]): del var
[k
]
175 if len(var
) == 0: return True
176 elif type(var
) is list or type(var
) is tuple:
178 if type(k
) is dict: delete_nulls(k
)
179 if len(var
) == 0: return True
183 class httpserver(threading
.Thread
):
184 def __init__(self
, db_conn
, name
="http", host
='localhost', port
=8080, admin
=False, config_
=None):
186 Creates a new thread to attend the http connections
188 db_conn: database connection
189 name: name of this thread
190 host: ip or name where to listen
191 port: port where to listen
192 admin: if this has privileges of administrator or not
193 config_: unless the first thread must be provided. It is a global dictionary where to allocate the self variable
199 if config_
is not None:
201 if 'http_threads' not in config_dic
:
202 config_dic
['http_threads'] = {}
203 threading
.Thread
.__init
__(self
)
208 if name
in config_dic
:
209 print "httpserver Warning!!! Onether thread with the same name", name
211 while name
+str(n
) in config_dic
:
215 self
.url_preffix
= 'http://' + self
.host
+ ':' + str(self
.port
) + url_base
216 config_dic
['http_threads'][name
] = self
218 #Ensure that when the main program exits the thread will also exit
223 bottle
.run(host
=self
.host
, port
=self
.port
, debug
=True) #quiet=True
225 def gethost(self
, host_id
):
226 result
, content
= self
.db
.get_host(host_id
)
228 print "httpserver.gethost error %d %s" % (result
, content
)
229 bottle
.abort(-result
, content
)
231 print "httpserver.gethost host '%s' not found" % host_id
232 bottle
.abort(HTTP_Not_Found
, content
)
234 data
={'host' : content
}
235 convert_boolean(content
, ('admin_state_up',) )
236 change_keys_http2db(content
, http2db_host
, reverse
=True)
238 return format_out(data
)
240 @bottle.route(url_base
+ '/', method
='GET')
243 return 'works' #TODO: put links or redirection to /openvim???
249 def change_keys_http2db(data
, http_db
, reverse
=False):
250 '''Change keys of dictionary data according to the key_dict values
251 This allow change from http interface names to database names.
252 When reverse is True, the change is otherwise
254 data: can be a dictionary or a list
255 http_db: is a dictionary with hhtp names as keys and database names as value
256 reverse: by default change is done from http API to database. If True change is done otherwise
257 Return: None, but data is modified'''
258 if type(data
) is tuple or type(data
) is list:
260 change_keys_http2db(d
, http_db
, reverse
)
261 elif type(data
) is dict or type(data
) is bottle
.FormsDict
:
263 for k
,v
in http_db
.items():
264 if v
in data
: data
[k
]=data
.pop(v
)
266 for k
,v
in http_db
.items():
267 if k
in data
: data
[v
]=data
.pop(k
)
271 def format_out(data
):
272 '''return string of dictionary data according to requested json, yaml, xml. By default json'''
273 if 'application/yaml' in bottle
.request
.headers
.get('Accept'):
274 bottle
.response
.content_type
='application/yaml'
275 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='"'
276 else: #by default json
277 bottle
.response
.content_type
='application/json'
278 #return data #json no style
279 return json
.dumps(data
, indent
=4) + "\n"
281 def format_in(schema
):
283 error_text
= "Invalid header format "
284 format_type
= bottle
.request
.headers
.get('Content-Type', 'application/json')
285 if 'application/json' in format_type
:
286 error_text
= "Invalid json format "
287 #Use the json decoder instead of bottle decoder because it informs about the location of error formats with a ValueError exception
288 client_data
= json
.load(bottle
.request
.body
)
289 #client_data = bottle.request.json()
290 elif 'application/yaml' in format_type
:
291 error_text
= "Invalid yaml format "
292 client_data
= yaml
.load(bottle
.request
.body
)
293 elif format_type
== 'application/xml':
294 bottle
.abort(501, "Content-Type: application/xml not supported yet.")
296 print "HTTP HEADERS: " + str(bottle
.request
.headers
.items())
297 bottle
.abort(HTTP_Not_Acceptable
, 'Content-Type ' + str(format_type
) + ' not supported.')
299 #if client_data == None:
300 # bottle.abort(HTTP_Bad_Request, "Content error, empty")
304 #print "HTTP input data: ", str(client_data)
305 error_text
= "Invalid content "
306 js_v(client_data
, schema
)
309 except (ValueError, yaml
.YAMLError
) as exc
:
310 error_text
+= str(exc
)
312 bottle
.abort(HTTP_Bad_Request
, error_text
)
313 except js_e
.ValidationError
as exc
:
314 print "HTTP validate_in error, jsonschema exception ", exc
.message
, "at", exc
.path
315 print " CONTENT: " + str(bottle
.request
.body
.readlines())
317 if len(exc
.path
)>0: error_pos
=" at '" + ":".join(map(str, exc
.path
)) + "'"
318 bottle
.abort(HTTP_Bad_Request
, error_text
+ error_pos
+": "+exc
.message
)
320 # bottle.abort(HTTP_Bad_Request, "Content error: Failed to parse Content-Type", error_pos)
323 def filter_query_string(qs
, http2db
, allowed
):
324 '''Process query string (qs) checking that contains only valid tokens for avoiding SQL injection
326 'qs': bottle.FormsDict variable to be processed. None or empty is considered valid
327 'allowed': list of allowed string tokens (API http naming). All the keys of 'qs' must be one of 'allowed'
328 'http2db': dictionary with change from http API naming (dictionary key) to database naming(dictionary value)
329 Return: A tuple with the (select,where,limit) to be use in a database query. All of then transformed to the database naming
330 select: list of items to retrieve, filtered by query string 'field=token'. If no 'field' is present, allowed list is returned
331 where: dictionary with key, value, taken from the query string token=value. Empty if nothing is provided
332 limit: limit dictated by user with the query string 'limit'. 100 by default
333 abort if not permitted, using bottel.abort
338 if type(qs
) is not bottle
.FormsDict
:
339 print '!!!!!!!!!!!!!!invalid query string not a dictionary'
340 #bottle.abort(HTTP_Internal_Server_Error, "call programmer")
344 select
+= qs
.getall(k
)
347 bottle
.abort(HTTP_Bad_Request
, "Invalid query string at 'field="+v
+"'")
352 bottle
.abort(HTTP_Bad_Request
, "Invalid query string at 'limit="+qs
[k
]+"'")
355 bottle
.abort(HTTP_Bad_Request
, "Invalid query string at '"+k
+"="+qs
[k
]+"'")
356 if qs
[k
]!="null": where
[k
]=qs
[k
]
358 if len(select
)==0: select
+= allowed
359 #change from http api to database naming
360 for i
in range(0,len(select
)):
363 select
[i
] = http2db
[k
]
364 change_keys_http2db(where
, http2db
)
365 #print "filter_query_string", select,where,limit
367 return select
,where
,limit
370 def convert_bandwidth(data
, reverse
=False):
371 '''Check the field bandwidth recursively and when found, it removes units and convert to number
372 It assumes that bandwidth is well formed
374 'data': dictionary bottle.FormsDict variable to be checked. None or empty is considered valid
375 'reverse': by default convert form str to int (Mbps), if True it convert from number to units
379 if type(data
) is dict:
380 for k
in data
.keys():
381 if type(data
[k
]) is dict or type(data
[k
]) is tuple or type(data
[k
]) is list:
382 convert_bandwidth(data
[k
], reverse
)
383 if "bandwidth" in data
:
385 value
=str(data
["bandwidth"])
387 pos
= value
.find("bps")
389 if value
[pos
-1]=="G": data
["bandwidth"] = int(data
["bandwidth"][:pos
-1]) * 1000
390 elif value
[pos
-1]=="k": data
["bandwidth"]= int(data
["bandwidth"][:pos
-1]) / 1000
391 else: data
["bandwidth"]= int(data
["bandwidth"][:pos
-1])
393 value
= int(data
["bandwidth"])
394 if value
% 1000 == 0: data
["bandwidth"]=str(value
/1000) + " Gbps"
395 else: data
["bandwidth"]=str(value
) + " Mbps"
397 print "convert_bandwidth exception for type", type(data
["bandwidth"]), " data", data
["bandwidth"]
399 if type(data
) is tuple or type(data
) is list:
401 if type(k
) is dict or type(k
) is tuple or type(k
) is list:
402 convert_bandwidth(k
, reverse
)
404 def convert_boolean(data
, items
):
405 '''Check recursively the content of data, and if there is an key contained in items, convert value from string to boolean
406 It assumes that bandwidth is well formed
408 'data': dictionary bottle.FormsDict variable to be checked. None or empty is consideted valid
409 'items': tuple of keys to convert
413 if type(data
) is dict:
414 for k
in data
.keys():
415 if type(data
[k
]) is dict or type(data
[k
]) is tuple or type(data
[k
]) is list:
416 convert_boolean(data
[k
], items
)
418 if type(data
[k
]) is str:
419 if data
[k
]=="false": data
[k
]=False
420 elif data
[k
]=="true": data
[k
]=True
421 if type(data
) is tuple or type(data
) is list:
423 if type(k
) is dict or type(k
) is tuple or type(k
) is list:
424 convert_boolean(k
, items
)
426 def convert_datetime2str(var
):
427 '''Converts a datetime variable to a string with the format '%Y-%m-%dT%H:%i:%s'
428 It enters recursively in the dict var finding this kind of variables
430 if type(var
) is dict:
431 for k
,v
in var
.items():
432 if type(v
) is datetime
.datetime
:
433 var
[k
]= v
.strftime('%Y-%m-%dT%H:%M:%S')
434 elif type(v
) is dict or type(v
) is list or type(v
) is tuple:
435 convert_datetime2str(v
)
436 if len(var
) == 0: return True
437 elif type(var
) is list or type(var
) is tuple:
439 convert_datetime2str(v
)
441 def check_valid_tenant(my
, tenant_id
):
444 return HTTP_Unauthorized
, "Needed admin privileges"
446 result
, _
= my
.db
.get_table(FROM
='tenants', SELECT
=('uuid',), WHERE
={'uuid': tenant_id
})
448 return HTTP_Not_Found
, "tenant '%s' not found" % tenant_id
451 def check_valid_uuid(uuid
):
452 id_schema
= {"type" : "string", "pattern": "^[a-fA-F0-9]{8}(-[a-fA-F0-9]{4}){3}-[a-fA-F0-9]{12}$"}
454 js_v(uuid
, id_schema
)
456 except js_e
.ValidationError
:
462 Check if string value is a well-wormed url
463 :param url: string url
464 :return: True if is a valid url, False if is not well-formed
467 parsed_url
= urlparse
.urlparse(url
)
482 e
={"error":{"code":error
.status_code
, "type":error
.status
, "description":error
.body
}}
485 @bottle.hook('after_request')
487 #TODO: Alf: Is it needed??
488 bottle
.response
.headers
['Access-Control-Allow-Origin'] = '*'
494 @bottle.route(url_base
+ '/hosts', method
='GET')
495 def http_get_hosts():
496 return format_out(get_hosts())
500 select_
, where_
, limit_
= filter_query_string(bottle
.request
.query
, http2db_host
,
501 ('id', 'name', 'description', 'status', 'admin_state_up', 'ip_name'))
503 myself
= config_dic
['http_threads'][ threading
.current_thread().name
]
504 result
, content
= myself
.db
.get_table(FROM
='hosts', SELECT
=select_
, WHERE
=where_
, LIMIT
=limit_
)
506 print "http_get_hosts Error", content
507 bottle
.abort(-result
, content
)
509 convert_boolean(content
, ('admin_state_up',) )
510 change_keys_http2db(content
, http2db_host
, reverse
=True)
512 row
['links'] = ( {'href': myself
.url_preffix
+ '/hosts/' + str(row
['id']), 'rel': 'bookmark'}, )
513 data
={'hosts' : content
}
516 @bottle.route(url_base
+ '/hosts/<host_id>', method
='GET')
517 def http_get_host_id(host_id
):
518 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
519 return my
.gethost(host_id
)
521 @bottle.route(url_base
+ '/hosts', method
='POST')
522 def http_post_hosts():
523 '''insert a host into the database. All resources are got and inserted'''
524 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
527 bottle
.abort(HTTP_Unauthorized
, "Needed admin privileges")
530 http_content
= format_in( host_new_schema
)
531 r
= remove_extra_items(http_content
, host_new_schema
)
532 if r
is not None: print "http_post_host_id: Warning: remove extra items ", r
533 change_keys_http2db(http_content
['host'], http2db_host
)
535 host
= http_content
['host']
537 if 'host-data' in http_content
:
538 host
.update(http_content
['host-data'])
539 ip_name
=http_content
['host-data']['ip_name']
540 user
=http_content
['host-data']['user']
541 password
=http_content
['host-data'].get('password', None)
543 ip_name
=host
['ip_name']
545 password
=host
.get('password', None)
546 if not RADclass_module
:
548 RADclass_module
= imp
.find_module("RADclass")
549 except (IOError, ImportError) as e
:
550 raise ImportError("Cannot import RADclass.py Openvim not properly installed" +str(e
))
553 rad
= RADclass_module
.RADclass()
554 (return_status
, code
) = rad
.obtain_RAD(user
, password
, ip_name
)
557 if not return_status
:
558 print 'http_post_hosts ERROR obtaining RAD', code
559 bottle
.abort(HTTP_Bad_Request
, code
)
562 rad_structure
= yaml
.load(rad
.to_text())
563 print 'rad_structure\n---------------------'
564 print json
.dumps(rad_structure
, indent
=4)
565 print '---------------------'
567 WHERE_
={"family":rad_structure
['processor']['family'], 'manufacturer':rad_structure
['processor']['manufacturer'], 'version':rad_structure
['processor']['version']}
568 result
, content
= my
.db
.get_table(FROM
='host_ranking',
572 host
['ranking'] = content
[0]['ranking']
574 #error_text= "Host " + str(WHERE_)+ " not found in ranking table. Not valid for VIM management"
575 #bottle.abort(HTTP_Bad_Request, error_text)
577 warning_text
+= "Host " + str(WHERE_
)+ " not found in ranking table. Assuming lowest value 100\n"
578 host
['ranking'] = 100 #TODO: as not used in this version, set the lowest value
580 features
= rad_structure
['processor'].get('features', ())
581 host
['features'] = ",".join(features
)
584 for node
in (rad_structure
['resource topology']['nodes'] or {}).itervalues():
589 for core
in node
['cpu']['eligible_cores']:
590 eligible_cores
.extend(core
)
591 for core
in node
['cpu']['cores']:
592 for thread_id
in core
:
593 c
={'core_id': count
, 'thread_id': thread_id
}
594 if thread_id
not in eligible_cores
: c
['status'] = 'noteligible'
599 for port_k
, port_v
in node
['nics']['nic 0']['ports'].iteritems():
600 if port_v
['virtual']:
604 for port_k2
, port_v2
in node
['nics']['nic 0']['ports'].iteritems():
605 if port_v2
['virtual'] and port_v2
['PF_pci_id']==port_k
:
606 sriovs
.append({'pci':port_k2
, 'mac':port_v2
['mac'], 'source_name':port_v2
['source_name']})
608 #sort sriov according to pci and rename them to the vf number
609 new_sriovs
= sorted(sriovs
, key
=lambda k
: k
['pci'])
611 for sriov
in new_sriovs
:
612 sriov
['source_name'] = index
614 interfaces
.append ({'pci':str(port_k
), 'Mbps': port_v
['speed']/1000000, 'sriovs': new_sriovs
, 'mac':port_v
['mac'], 'source_name':port_v
['source_name']})
615 memory
=node
['memory']['node_size'] / (1024*1024*1024)
616 #memory=get_next_2pow(node['memory']['hugepage_nr'])
617 host
['numas'].append( {'numa_socket': node
['id'], 'hugepages': node
['memory']['hugepage_nr'], 'memory':memory
, 'interfaces': interfaces
, 'cores': cores
} )
618 print json
.dumps(host
, indent
=4)
622 result
, content
= my
.db
.new_host(host
)
624 if content
['admin_state_up']:
626 host_test_mode
= True if config_dic
['mode']=='test' or config_dic
['mode']=="OF only" else False
627 host_develop_mode
= True if config_dic
['mode']=='development' else False
628 host_develop_bridge_iface
= config_dic
.get('development_bridge', None)
629 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'],
630 test
=host_test_mode
, image_path
=config_dic
['image_path'],
631 version
=config_dic
['version'], host_id
=content
['uuid'],
632 develop_mode
=host_develop_mode
, develop_bridge_iface
=host_develop_bridge_iface
)
634 config_dic
['host_threads'][ content
['uuid'] ] = thread
636 if config_dic
['network_type'] == 'ovs':
638 create_dhcp_ovs_bridge()
639 config_dic
['host_threads'][content
['uuid']].insert_task("new-ovsbridge")
640 # check if more host exist
641 create_vxlan_mesh(content
['uuid'])
644 change_keys_http2db(content
, http2db_host
, reverse
=True)
645 if len(warning_text
)>0:
646 content
["warning"]= warning_text
647 data
={'host' : content
}
648 return format_out(data
)
650 bottle
.abort(HTTP_Bad_Request
, content
)
654 def get_dhcp_controller():
656 Create an host_thread object for manage openvim controller and not create a thread for itself
657 :return: dhcp_host openvim controller object
660 if 'openvim_controller' in config_dic
['host_threads']:
661 return config_dic
['host_threads']['openvim_controller']
664 controller_ip
= config_dic
['ovs_controller_ip']
665 ovs_controller_user
= config_dic
['ovs_controller_user']
667 host_test_mode
= True if config_dic
['mode'] == 'test' or config_dic
['mode'] == "OF only" else False
668 host_develop_mode
= True if config_dic
['mode'] == 'development' else False
670 dhcp_host
= ht
.host_thread(name
='openvim_controller', user
=ovs_controller_user
, host
=controller_ip
, db
=config_dic
['db'],
671 db_lock
=config_dic
['db_lock'], test
=host_test_mode
,
672 image_path
=config_dic
['image_path'], version
=config_dic
['version'],
673 host_id
='openvim_controller', develop_mode
=host_develop_mode
,
674 develop_bridge_iface
=bridge_ifaces
)
676 config_dic
['host_threads']['openvim_controller'] = dhcp_host
677 dhcp_host
.ssh_connect()
681 def delete_dhcp_ovs_bridge(vlan
, net_uuid
):
683 Delete bridges and port created during dhcp launching at openvim controller
684 :param vlan: net vlan id
685 :param net_uuid: network identifier
688 dhcp_path
= config_dic
['ovs_controller_file_path']
690 controller_host
= get_dhcp_controller()
691 controller_host
.delete_dhcp_port(vlan
, net_uuid
)
692 controller_host
.delete_dhcp_server(vlan
, net_uuid
, dhcp_path
)
695 def create_dhcp_ovs_bridge():
697 Initialize bridge to allocate the dhcp server at openvim controller
700 controller_host
= get_dhcp_controller()
701 controller_host
.create_ovs_bridge()
704 def set_mac_dhcp(vm_ip
, vlan
, first_ip
, last_ip
, cidr
, mac
):
706 Launch a dhcpserver base on dnsmasq attached to the net base on vlan id across the the openvim computes
707 :param vm_ip: IP address asigned to a VM
708 :param vlan: Segmentation id
709 :param first_ip: First dhcp range ip
710 :param last_ip: Last dhcp range ip
711 :param cidr: net cidr
712 :param mac: VM vnic mac to be macthed with the IP received
716 ip_tools
= IPNetwork(cidr
)
717 cidr_len
= ip_tools
.prefixlen
718 dhcp_netmask
= str(ip_tools
.netmask
)
719 dhcp_path
= config_dic
['ovs_controller_file_path']
721 new_cidr
= [first_ip
+ '/' + str(cidr_len
)]
722 if not len(all_matching_cidrs(vm_ip
, new_cidr
)):
725 controller_host
= get_dhcp_controller()
726 controller_host
.set_mac_dhcp_server(vm_ip
, mac
, vlan
, dhcp_netmask
, dhcp_path
)
729 def delete_mac_dhcp(vm_ip
, vlan
, mac
):
731 Delete into dhcp conf file the ip assigned to a specific MAC address
732 :param vm_ip: IP address asigned to a VM
733 :param vlan: Segmentation id
734 :param mac: VM vnic mac to be macthed with the IP received
738 dhcp_path
= config_dic
['ovs_controller_file_path']
740 controller_host
= get_dhcp_controller()
741 controller_host
.delete_mac_dhcp_server(vm_ip
, mac
, vlan
, dhcp_path
)
744 def launch_dhcp_server(vlan
, first_ip
, last_ip
, cidr
):
746 Launch a dhcpserver base on dnsmasq attached to the net base on vlan id across the the openvim computes
747 :param vlan: vlan identifier
748 :param first_ip: First dhcp range ip
749 :param last_ip: Last dhcp range ip
750 :param cidr: net cidr
753 ip_tools
= IPNetwork(cidr
)
754 dhcp_netmask
= str(ip_tools
.netmask
)
755 ip_range
= [first_ip
, last_ip
]
756 dhcp_path
= config_dic
['ovs_controller_file_path']
758 controller_host
= get_dhcp_controller()
759 controller_host
.create_linux_bridge(vlan
)
760 controller_host
.create_dhcp_interfaces(vlan
, first_ip
, dhcp_netmask
)
761 controller_host
.launch_dhcp_server(vlan
, ip_range
, dhcp_netmask
, dhcp_path
)
764 def create_vxlan_mesh(host_id
):
766 Create vxlan mesh across all openvimc controller and computes.
767 :param host_id: host identifier
768 :param host_id: host identifier
771 dhcp_compute_name
= get_vxlan_interface("dhcp")
772 existing_hosts
= get_hosts()
773 if len(existing_hosts
['hosts']) > 0:
774 # vlxan mesh creation between openvim controller and computes
775 computes_available
= existing_hosts
['hosts']
776 controller_host
= get_dhcp_controller()
777 for compute
in computes_available
:
778 vxlan_interface_name
= get_vxlan_interface(compute
['id'][:8])
779 config_dic
['host_threads'][compute
['id']].insert_task("new-vxlan", dhcp_compute_name
, controller_host
.host
)
780 controller_host
.create_ovs_vxlan_tunnel(vxlan_interface_name
, compute
['ip_name'])
782 # vlxan mesh creation between openvim computes
783 for count
, compute_owner
in enumerate(computes_available
):
784 for compute
in computes_available
:
785 if compute_owner
['id'] == compute
['id']:
788 vxlan_interface_name
= get_vxlan_interface(compute_owner
['id'][:8])
789 controller_host
.create_ovs_vxlan_tunnel(vxlan_interface_name
, compute_owner
['ip_name'])
790 config_dic
['host_threads'][compute
['id']].insert_task("new-vxlan",
791 vxlan_interface_name
,
792 compute_owner
['ip_name'])
795 def delete_vxlan_mesh(host_id
):
797 Create a task for remove a specific compute of the vlxan mesh
798 :param host_id: host id to be deleted.
800 existing_hosts
= get_hosts()
801 computes_available
= existing_hosts
['hosts']
803 vxlan_interface_name
= get_vxlan_interface(host_id
[:8])
804 controller_host
= get_dhcp_controller()
805 controller_host
.delete_ovs_vxlan_tunnel(vxlan_interface_name
)
806 # remove bridge from openvim controller if no more computes exist
807 if len(existing_hosts
):
808 controller_host
.delete_ovs_bridge()
810 for compute
in computes_available
:
811 if host_id
== compute
['id']:
814 controller_host
.delete_ovs_vxlan_tunnel(vxlan_interface_name
)
815 config_dic
['host_threads'][compute
['id']].insert_task("del-vxlan", vxlan_interface_name
)
818 def get_vxlan_interface(local_uuid
):
820 Genearte a vxlan interface name
821 :param local_uuid: host id
822 :return: vlxan-8digits
824 return 'vxlan-' + local_uuid
[:8]
827 @bottle.route(url_base
+ '/hosts/<host_id>', method
='PUT')
828 def http_put_host_id(host_id
):
829 '''modify a host into the database. All resources are got and inserted'''
830 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
833 bottle
.abort(HTTP_Unauthorized
, "Needed admin privileges")
836 http_content
= format_in( host_edit_schema
)
837 r
= remove_extra_items(http_content
, host_edit_schema
)
838 if r
is not None: print "http_post_host_id: Warning: remove extra items ", r
839 change_keys_http2db(http_content
['host'], http2db_host
)
842 result
, content
= my
.db
.edit_host(host_id
, http_content
['host'])
844 convert_boolean(content
, ('admin_state_up',) )
845 change_keys_http2db(content
, http2db_host
, reverse
=True)
846 data
={'host' : content
}
848 if config_dic
['network_type'] == 'ovs':
849 delete_vxlan_mesh(host_id
)
850 config_dic
['host_threads'][host_id
].insert_task("del-ovsbridge")
853 config_dic
['host_threads'][host_id
].name
= content
.get('name',content
['ip_name'])
854 config_dic
['host_threads'][host_id
].user
= content
['user']
855 config_dic
['host_threads'][host_id
].host
= content
['ip_name']
856 config_dic
['host_threads'][host_id
].insert_task("reload")
858 if config_dic
['network_type'] == 'ovs':
859 # create mesh with new host data
860 config_dic
['host_threads'][host_id
].insert_task("new-ovsbridge")
861 create_vxlan_mesh(host_id
)
864 return format_out(data
)
866 bottle
.abort(HTTP_Bad_Request
, content
)
871 @bottle.route(url_base
+ '/hosts/<host_id>', method
='DELETE')
872 def http_delete_host_id(host_id
):
873 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
876 bottle
.abort(HTTP_Unauthorized
, "Needed admin privileges")
877 result
, content
= my
.db
.delete_row('hosts', host_id
)
879 bottle
.abort(HTTP_Not_Found
, content
)
881 if config_dic
['network_type'] == 'ovs':
882 delete_vxlan_mesh(host_id
)
884 if host_id
in config_dic
['host_threads']:
885 if config_dic
['network_type'] == 'ovs':
886 config_dic
['host_threads'][host_id
].insert_task("del-ovsbridge")
887 config_dic
['host_threads'][host_id
].insert_task("exit")
889 data
={'result' : content
}
890 return format_out(data
)
892 print "http_delete_host_id error",result
, content
893 bottle
.abort(-result
, content
)
902 @bottle.route(url_base
+ '/tenants', method
='GET')
903 def http_get_tenants():
904 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
905 select_
,where_
,limit_
= filter_query_string(bottle
.request
.query
, http2db_tenant
,
906 ('id','name','description','enabled') )
907 result
, content
= my
.db
.get_table(FROM
='tenants', SELECT
=select_
,WHERE
=where_
,LIMIT
=limit_
)
909 print "http_get_tenants Error", content
910 bottle
.abort(-result
, content
)
912 change_keys_http2db(content
, http2db_tenant
, reverse
=True)
913 convert_boolean(content
, ('enabled',))
914 data
={'tenants' : content
}
915 #data['tenants_links'] = dict([('tenant', row['id']) for row in content])
916 return format_out(data
)
918 @bottle.route(url_base
+ '/tenants/<tenant_id>', method
='GET')
919 def http_get_tenant_id(tenant_id
):
920 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
921 result
, content
= my
.db
.get_table(FROM
='tenants', SELECT
=('uuid','name','description', 'enabled'),WHERE
={'uuid': tenant_id
} )
923 print "http_get_tenant_id error %d %s" % (result
, content
)
924 bottle
.abort(-result
, content
)
926 print "http_get_tenant_id tenant '%s' not found" % tenant_id
927 bottle
.abort(HTTP_Not_Found
, "tenant %s not found" % tenant_id
)
929 change_keys_http2db(content
, http2db_tenant
, reverse
=True)
930 convert_boolean(content
, ('enabled',))
931 data
={'tenant' : content
[0]}
932 #data['tenants_links'] = dict([('tenant', row['id']) for row in content])
933 return format_out(data
)
936 @bottle.route(url_base
+ '/tenants', method
='POST')
937 def http_post_tenants():
938 '''insert a tenant into the database.'''
939 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
941 http_content
= format_in( tenant_new_schema
)
942 r
= remove_extra_items(http_content
, tenant_new_schema
)
943 if r
is not None: print "http_post_tenants: Warning: remove extra items ", r
944 change_keys_http2db(http_content
['tenant'], http2db_tenant
)
947 result
, content
= my
.db
.new_tenant(http_content
['tenant'])
950 return http_get_tenant_id(content
)
952 bottle
.abort(-result
, content
)
955 @bottle.route(url_base
+ '/tenants/<tenant_id>', method
='PUT')
956 def http_put_tenant_id(tenant_id
):
957 '''update a tenant into the database.'''
958 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
960 http_content
= format_in( tenant_edit_schema
)
961 r
= remove_extra_items(http_content
, tenant_edit_schema
)
962 if r
is not None: print "http_put_tenant_id: Warning: remove extra items ", r
963 change_keys_http2db(http_content
['tenant'], http2db_tenant
)
966 result
, content
= my
.db
.update_rows('tenants', http_content
['tenant'], WHERE
={'uuid': tenant_id
}, log
=True )
968 return http_get_tenant_id(tenant_id
)
970 bottle
.abort(-result
, content
)
973 @bottle.route(url_base
+ '/tenants/<tenant_id>', method
='DELETE')
974 def http_delete_tenant_id(tenant_id
):
975 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
977 r
, tenants_flavors
= my
.db
.get_table(FROM
='tenants_flavors', SELECT
=('flavor_id','tenant_id'), WHERE
={'tenant_id': tenant_id
})
980 r
, tenants_images
= my
.db
.get_table(FROM
='tenants_images', SELECT
=('image_id','tenant_id'), WHERE
={'tenant_id': tenant_id
})
983 result
, content
= my
.db
.delete_row('tenants', tenant_id
)
985 bottle
.abort(HTTP_Not_Found
, content
)
987 print "alf", tenants_flavors
, tenants_images
988 for flavor
in tenants_flavors
:
989 my
.db
.delete_row_by_key("flavors", "uuid", flavor
['flavor_id'])
990 for image
in tenants_images
:
991 my
.db
.delete_row_by_key("images", "uuid", image
['image_id'])
992 data
={'result' : content
}
993 return format_out(data
)
995 print "http_delete_tenant_id error",result
, content
996 bottle
.abort(-result
, content
)
1003 @bottle.route(url_base
+ '/<tenant_id>/flavors', method
='GET')
1004 def http_get_flavors(tenant_id
):
1005 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
1006 #check valid tenant_id
1007 result
,content
= check_valid_tenant(my
, tenant_id
)
1009 bottle
.abort(result
, content
)
1011 select_
,where_
,limit_
= filter_query_string(bottle
.request
.query
, http2db_flavor
,
1012 ('id','name','description','public') )
1013 if tenant_id
=='any':
1016 from_
='tenants_flavors inner join flavors on tenants_flavors.flavor_id=flavors.uuid'
1017 where_
['tenant_id'] = tenant_id
1018 result
, content
= my
.db
.get_table(FROM
=from_
, SELECT
=select_
, WHERE
=where_
, LIMIT
=limit_
)
1020 print "http_get_flavors Error", content
1021 bottle
.abort(-result
, content
)
1023 change_keys_http2db(content
, http2db_flavor
, reverse
=True)
1025 row
['links']=[ {'href': "/".join( (my
.url_preffix
, tenant_id
, 'flavors', str(row
['id']) ) ), 'rel':'bookmark' } ]
1026 data
={'flavors' : content
}
1027 return format_out(data
)
1029 @bottle.route(url_base
+ '/<tenant_id>/flavors/<flavor_id>', method
='GET')
1030 def http_get_flavor_id(tenant_id
, flavor_id
):
1031 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
1032 #check valid tenant_id
1033 result
,content
= check_valid_tenant(my
, tenant_id
)
1035 bottle
.abort(result
, content
)
1037 select_
,where_
,limit_
= filter_query_string(bottle
.request
.query
, http2db_flavor
,
1038 ('id','name','description','ram', 'vcpus', 'extended', 'disk', 'public') )
1039 if tenant_id
=='any':
1042 from_
='tenants_flavors as tf inner join flavors as f on tf.flavor_id=f.uuid'
1043 where_
['tenant_id'] = tenant_id
1044 where_
['uuid'] = flavor_id
1045 result
, content
= my
.db
.get_table(SELECT
=select_
, FROM
=from_
, WHERE
=where_
, LIMIT
=limit_
)
1048 print "http_get_flavor_id error %d %s" % (result
, content
)
1049 bottle
.abort(-result
, content
)
1051 print "http_get_flavors_id flavor '%s' not found" % str(flavor_id
)
1052 bottle
.abort(HTTP_Not_Found
, 'flavor %s not found' % flavor_id
)
1054 change_keys_http2db(content
, http2db_flavor
, reverse
=True)
1055 if 'extended' in content
[0] and content
[0]['extended'] is not None:
1056 extended
= json
.loads(content
[0]['extended'])
1057 if 'devices' in extended
:
1058 change_keys_http2db(extended
['devices'], http2db_flavor
, reverse
=True)
1059 content
[0]['extended']=extended
1060 convert_bandwidth(content
[0], reverse
=True)
1061 content
[0]['links']=[ {'href': "/".join( (my
.url_preffix
, tenant_id
, 'flavors', str(content
[0]['id']) ) ), 'rel':'bookmark' } ]
1062 data
={'flavor' : content
[0]}
1063 #data['tenants_links'] = dict([('tenant', row['id']) for row in content])
1064 return format_out(data
)
1067 @bottle.route(url_base
+ '/<tenant_id>/flavors', method
='POST')
1068 def http_post_flavors(tenant_id
):
1069 '''insert a flavor into the database, and attach to tenant.'''
1070 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
1071 #check valid tenant_id
1072 result
,content
= check_valid_tenant(my
, tenant_id
)
1074 bottle
.abort(result
, content
)
1075 http_content
= format_in( flavor_new_schema
)
1076 r
= remove_extra_items(http_content
, flavor_new_schema
)
1077 if r
is not None: print "http_post_flavors: Warning: remove extra items ", r
1078 change_keys_http2db(http_content
['flavor'], http2db_flavor
)
1079 extended_dict
= http_content
['flavor'].pop('extended', None)
1080 if extended_dict
is not None:
1081 result
, content
= check_extended(extended_dict
)
1083 print "http_post_flavors wrong input extended error %d %s" % (result
, content
)
1084 bottle
.abort(-result
, content
)
1086 convert_bandwidth(extended_dict
)
1087 if 'devices' in extended_dict
: change_keys_http2db(extended_dict
['devices'], http2db_flavor
)
1088 http_content
['flavor']['extended'] = json
.dumps(extended_dict
)
1089 #insert in data base
1090 result
, content
= my
.db
.new_flavor(http_content
['flavor'], tenant_id
)
1092 return http_get_flavor_id(tenant_id
, content
)
1094 print "http_psot_flavors error %d %s" % (result
, content
)
1095 bottle
.abort(-result
, content
)
1098 @bottle.route(url_base
+ '/<tenant_id>/flavors/<flavor_id>', method
='DELETE')
1099 def http_delete_flavor_id(tenant_id
, flavor_id
):
1100 '''Deletes the flavor_id of a tenant. IT removes from tenants_flavors table.'''
1101 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
1102 #check valid tenant_id
1103 result
,content
= check_valid_tenant(my
, tenant_id
)
1105 bottle
.abort(result
, content
)
1107 result
, content
= my
.db
.delete_image_flavor('flavor', flavor_id
, tenant_id
)
1109 bottle
.abort(HTTP_Not_Found
, content
)
1111 data
={'result' : content
}
1112 return format_out(data
)
1114 print "http_delete_flavor_id error",result
, content
1115 bottle
.abort(-result
, content
)
1118 @bottle.route(url_base
+ '/<tenant_id>/flavors/<flavor_id>/<action>', method
='POST')
1119 def http_attach_detach_flavors(tenant_id
, flavor_id
, action
):
1120 '''attach/detach an existing flavor in this tenant. That is insert/remove at tenants_flavors table.'''
1121 #TODO alf: not tested at all!!!
1122 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
1123 #check valid tenant_id
1124 result
,content
= check_valid_tenant(my
, tenant_id
)
1126 bottle
.abort(result
, content
)
1127 if tenant_id
=='any':
1128 bottle
.abort(HTTP_Bad_Request
, "Invalid tenant 'any' with this command")
1130 if action
!='attach' and action
!= 'detach':
1131 bottle
.abort(HTTP_Method_Not_Allowed
, "actions can be attach or detach")
1134 #Ensure that flavor exist
1135 from_
='tenants_flavors as tf right join flavors as f on tf.flavor_id=f.uuid'
1136 where_
={'uuid': flavor_id
}
1137 result
, content
= my
.db
.get_table(SELECT
=('public','tenant_id'), FROM
=from_
, WHERE
=where_
)
1139 if action
=='attach':
1140 text_error
="Flavor '%s' not found" % flavor_id
1142 text_error
="Flavor '%s' not found for tenant '%s'" % (flavor_id
, tenant_id
)
1143 bottle
.abort(HTTP_Not_Found
, text_error
)
1147 if action
=='attach':
1148 if flavor
['tenant_id']!=None:
1149 bottle
.abort(HTTP_Conflict
, "Flavor '%s' already attached to tenant '%s'" % (flavor_id
, tenant_id
))
1150 if flavor
['public']=='no' and not my
.admin
:
1151 #allow only attaching public flavors
1152 bottle
.abort(HTTP_Unauthorized
, "Needed admin rights to attach a private flavor")
1154 #insert in data base
1155 result
, content
= my
.db
.new_row('tenants_flavors', {'flavor_id':flavor_id
, 'tenant_id': tenant_id
})
1157 return http_get_flavor_id(tenant_id
, flavor_id
)
1159 if flavor
['tenant_id']==None:
1160 bottle
.abort(HTTP_Not_Found
, "Flavor '%s' not attached to tenant '%s'" % (flavor_id
, tenant_id
))
1161 result
, content
= my
.db
.delete_row_by_dict(FROM
='tenants_flavors', WHERE
={'flavor_id':flavor_id
, 'tenant_id':tenant_id
})
1163 if flavor
['public']=='no':
1164 #try to delete the flavor completely to avoid orphan flavors, IGNORE error
1165 my
.db
.delete_row_by_dict(FROM
='flavors', WHERE
={'uuid':flavor_id
})
1166 data
={'result' : "flavor detached"}
1167 return format_out(data
)
1169 #if get here is because an error
1170 print "http_attach_detach_flavors error %d %s" % (result
, content
)
1171 bottle
.abort(-result
, content
)
1174 @bottle.route(url_base
+ '/<tenant_id>/flavors/<flavor_id>', method
='PUT')
1175 def http_put_flavor_id(tenant_id
, flavor_id
):
1176 '''update a flavor_id into the database.'''
1177 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
1178 #check valid tenant_id
1179 result
,content
= check_valid_tenant(my
, tenant_id
)
1181 bottle
.abort(result
, content
)
1183 http_content
= format_in( flavor_update_schema
)
1184 r
= remove_extra_items(http_content
, flavor_update_schema
)
1185 if r
is not None: print "http_put_flavor_id: Warning: remove extra items ", r
1186 change_keys_http2db(http_content
['flavor'], http2db_flavor
)
1187 extended_dict
= http_content
['flavor'].pop('extended', None)
1188 if extended_dict
is not None:
1189 result
, content
= check_extended(extended_dict
)
1191 print "http_put_flavor_id wrong input extended error %d %s" % (result
, content
)
1192 bottle
.abort(-result
, content
)
1194 convert_bandwidth(extended_dict
)
1195 if 'devices' in extended_dict
: change_keys_http2db(extended_dict
['devices'], http2db_flavor
)
1196 http_content
['flavor']['extended'] = json
.dumps(extended_dict
)
1197 #Ensure that flavor exist
1198 where_
={'uuid': flavor_id
}
1199 if tenant_id
=='any':
1202 from_
='tenants_flavors as ti inner join flavors as i on ti.flavor_id=i.uuid'
1203 where_
['tenant_id'] = tenant_id
1204 result
, content
= my
.db
.get_table(SELECT
=('public',), FROM
=from_
, WHERE
=where_
)
1206 text_error
="Flavor '%s' not found" % flavor_id
1207 if tenant_id
!='any':
1208 text_error
+=" for tenant '%s'" % flavor_id
1209 bottle
.abort(HTTP_Not_Found
, text_error
)
1212 if content
[0]['public']=='yes' and not my
.admin
:
1213 #allow only modifications over private flavors
1214 bottle
.abort(HTTP_Unauthorized
, "Needed admin rights to edit a public flavor")
1216 #insert in data base
1217 result
, content
= my
.db
.update_rows('flavors', http_content
['flavor'], {'uuid': flavor_id
})
1220 print "http_put_flavor_id error %d %s" % (result
, content
)
1221 bottle
.abort(-result
, content
)
1224 return http_get_flavor_id(tenant_id
, flavor_id
)
1232 @bottle.route(url_base
+ '/<tenant_id>/images', method
='GET')
1233 def http_get_images(tenant_id
):
1234 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
1235 #check valid tenant_id
1236 result
,content
= check_valid_tenant(my
, tenant_id
)
1238 bottle
.abort(result
, content
)
1240 select_
,where_
,limit_
= filter_query_string(bottle
.request
.query
, http2db_image
,
1241 ('id','name','description','path','public') )
1242 if tenant_id
=='any':
1245 from_
='tenants_images inner join images on tenants_images.image_id=images.uuid'
1246 where_
['tenant_id'] = tenant_id
1247 result
, content
= my
.db
.get_table(SELECT
=select_
, FROM
=from_
, WHERE
=where_
, LIMIT
=limit_
)
1249 print "http_get_images Error", content
1250 bottle
.abort(-result
, content
)
1252 change_keys_http2db(content
, http2db_image
, reverse
=True)
1253 #for row in content: row['links']=[ {'href': "/".join( (my.url_preffix, tenant_id, 'images', str(row['id']) ) ), 'rel':'bookmark' } ]
1254 data
={'images' : content
}
1255 return format_out(data
)
1257 @bottle.route(url_base
+ '/<tenant_id>/images/<image_id>', method
='GET')
1258 def http_get_image_id(tenant_id
, image_id
):
1259 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
1260 #check valid tenant_id
1261 result
,content
= check_valid_tenant(my
, tenant_id
)
1263 bottle
.abort(result
, content
)
1265 select_
,where_
,limit_
= filter_query_string(bottle
.request
.query
, http2db_image
,
1266 ('id','name','description','progress', 'status','path', 'created', 'updated','public') )
1267 if tenant_id
=='any':
1270 from_
='tenants_images as ti inner join images as i on ti.image_id=i.uuid'
1271 where_
['tenant_id'] = tenant_id
1272 where_
['uuid'] = image_id
1273 result
, content
= my
.db
.get_table(SELECT
=select_
, FROM
=from_
, WHERE
=where_
, LIMIT
=limit_
)
1276 print "http_get_images error %d %s" % (result
, content
)
1277 bottle
.abort(-result
, content
)
1279 print "http_get_images image '%s' not found" % str(image_id
)
1280 bottle
.abort(HTTP_Not_Found
, 'image %s not found' % image_id
)
1282 convert_datetime2str(content
)
1283 change_keys_http2db(content
, http2db_image
, reverse
=True)
1284 if 'metadata' in content
[0] and content
[0]['metadata'] is not None:
1285 metadata
= json
.loads(content
[0]['metadata'])
1286 content
[0]['metadata']=metadata
1287 content
[0]['links']=[ {'href': "/".join( (my
.url_preffix
, tenant_id
, 'images', str(content
[0]['id']) ) ), 'rel':'bookmark' } ]
1288 data
={'image' : content
[0]}
1289 #data['tenants_links'] = dict([('tenant', row['id']) for row in content])
1290 return format_out(data
)
1292 @bottle.route(url_base
+ '/<tenant_id>/images', method
='POST')
1293 def http_post_images(tenant_id
):
1294 '''insert a image into the database, and attach to tenant.'''
1295 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
1296 #check valid tenant_id
1297 result
,content
= check_valid_tenant(my
, tenant_id
)
1299 bottle
.abort(result
, content
)
1300 http_content
= format_in(image_new_schema
)
1301 r
= remove_extra_items(http_content
, image_new_schema
)
1302 if r
is not None: print "http_post_images: Warning: remove extra items ", r
1303 change_keys_http2db(http_content
['image'], http2db_image
)
1304 metadata_dict
= http_content
['image'].pop('metadata', None)
1305 if metadata_dict
is not None:
1306 http_content
['image']['metadata'] = json
.dumps(metadata_dict
)
1308 host_test_mode
= True if config_dic
['mode']=='test' or config_dic
['mode']=="OF only" else False
1310 image_file
= http_content
['image'].get('path',None)
1311 if os
.path
.exists(image_file
):
1312 http_content
['image']['checksum'] = md5(image_file
)
1313 elif is_url(image_file
):
1316 if not host_test_mode
:
1317 content
= "Image file not found"
1318 print "http_post_images error: %d %s" % (HTTP_Bad_Request
, content
)
1319 bottle
.abort(HTTP_Bad_Request
, content
)
1320 except Exception as e
:
1321 print "ERROR. Unexpected exception: %s" % (str(e
))
1322 bottle
.abort(HTTP_Internal_Server_Error
, type(e
).__name
__ + ": " + str(e
))
1323 #insert in data base
1324 result
, content
= my
.db
.new_image(http_content
['image'], tenant_id
)
1326 return http_get_image_id(tenant_id
, content
)
1328 print "http_post_images error %d %s" % (result
, content
)
1329 bottle
.abort(-result
, content
)
1332 @bottle.route(url_base
+ '/<tenant_id>/images/<image_id>', method
='DELETE')
1333 def http_delete_image_id(tenant_id
, image_id
):
1334 '''Deletes the image_id of a tenant. IT removes from tenants_images table.'''
1335 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
1336 #check valid tenant_id
1337 result
,content
= check_valid_tenant(my
, tenant_id
)
1339 bottle
.abort(result
, content
)
1340 result
, content
= my
.db
.delete_image_flavor('image', image_id
, tenant_id
)
1342 bottle
.abort(HTTP_Not_Found
, content
)
1344 data
={'result' : content
}
1345 return format_out(data
)
1347 print "http_delete_image_id error",result
, content
1348 bottle
.abort(-result
, content
)
1351 @bottle.route(url_base
+ '/<tenant_id>/images/<image_id>/<action>', method
='POST')
1352 def http_attach_detach_images(tenant_id
, image_id
, action
):
1353 '''attach/detach an existing image in this tenant. That is insert/remove at tenants_images table.'''
1354 #TODO alf: not tested at all!!!
1355 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
1356 #check valid tenant_id
1357 result
,content
= check_valid_tenant(my
, tenant_id
)
1359 bottle
.abort(result
, content
)
1360 if tenant_id
=='any':
1361 bottle
.abort(HTTP_Bad_Request
, "Invalid tenant 'any' with this command")
1363 if action
!='attach' and action
!= 'detach':
1364 bottle
.abort(HTTP_Method_Not_Allowed
, "actions can be attach or detach")
1367 #Ensure that image exist
1368 from_
='tenants_images as ti right join images as i on ti.image_id=i.uuid'
1369 where_
={'uuid': image_id
}
1370 result
, content
= my
.db
.get_table(SELECT
=('public','tenant_id'), FROM
=from_
, WHERE
=where_
)
1372 if action
=='attach':
1373 text_error
="Image '%s' not found" % image_id
1375 text_error
="Image '%s' not found for tenant '%s'" % (image_id
, tenant_id
)
1376 bottle
.abort(HTTP_Not_Found
, text_error
)
1380 if action
=='attach':
1381 if image
['tenant_id']!=None:
1382 bottle
.abort(HTTP_Conflict
, "Image '%s' already attached to tenant '%s'" % (image_id
, tenant_id
))
1383 if image
['public']=='no' and not my
.admin
:
1384 #allow only attaching public images
1385 bottle
.abort(HTTP_Unauthorized
, "Needed admin rights to attach a private image")
1387 #insert in data base
1388 result
, content
= my
.db
.new_row('tenants_images', {'image_id':image_id
, 'tenant_id': tenant_id
})
1390 return http_get_image_id(tenant_id
, image_id
)
1392 if image
['tenant_id']==None:
1393 bottle
.abort(HTTP_Not_Found
, "Image '%s' not attached to tenant '%s'" % (image_id
, tenant_id
))
1394 result
, content
= my
.db
.delete_row_by_dict(FROM
='tenants_images', WHERE
={'image_id':image_id
, 'tenant_id':tenant_id
})
1396 if image
['public']=='no':
1397 #try to delete the image completely to avoid orphan images, IGNORE error
1398 my
.db
.delete_row_by_dict(FROM
='images', WHERE
={'uuid':image_id
})
1399 data
={'result' : "image detached"}
1400 return format_out(data
)
1402 #if get here is because an error
1403 print "http_attach_detach_images error %d %s" % (result
, content
)
1404 bottle
.abort(-result
, content
)
1407 @bottle.route(url_base
+ '/<tenant_id>/images/<image_id>', method
='PUT')
1408 def http_put_image_id(tenant_id
, image_id
):
1409 '''update a image_id into the database.'''
1410 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
1411 #check valid tenant_id
1412 result
,content
= check_valid_tenant(my
, tenant_id
)
1414 bottle
.abort(result
, content
)
1416 http_content
= format_in( image_update_schema
)
1417 r
= remove_extra_items(http_content
, image_update_schema
)
1418 if r
is not None: print "http_put_image_id: Warning: remove extra items ", r
1419 change_keys_http2db(http_content
['image'], http2db_image
)
1420 metadata_dict
= http_content
['image'].pop('metadata', None)
1421 if metadata_dict
is not None:
1422 http_content
['image']['metadata'] = json
.dumps(metadata_dict
)
1423 #Ensure that image exist
1424 where_
={'uuid': image_id
}
1425 if tenant_id
=='any':
1428 from_
='tenants_images as ti inner join images as i on ti.image_id=i.uuid'
1429 where_
['tenant_id'] = tenant_id
1430 result
, content
= my
.db
.get_table(SELECT
=('public',), FROM
=from_
, WHERE
=where_
)
1432 text_error
="Image '%s' not found" % image_id
1433 if tenant_id
!='any':
1434 text_error
+=" for tenant '%s'" % image_id
1435 bottle
.abort(HTTP_Not_Found
, text_error
)
1438 if content
[0]['public']=='yes' and not my
.admin
:
1439 #allow only modifications over private images
1440 bottle
.abort(HTTP_Unauthorized
, "Needed admin rights to edit a public image")
1442 #insert in data base
1443 result
, content
= my
.db
.update_rows('images', http_content
['image'], {'uuid': image_id
})
1446 print "http_put_image_id error %d %s" % (result
, content
)
1447 bottle
.abort(-result
, content
)
1450 return http_get_image_id(tenant_id
, image_id
)
1457 @bottle.route(url_base
+ '/<tenant_id>/servers', method
='GET')
1458 def http_get_servers(tenant_id
):
1459 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
1460 result
,content
= check_valid_tenant(my
, tenant_id
)
1462 bottle
.abort(result
, content
)
1465 select_
,where_
,limit_
= filter_query_string(bottle
.request
.query
, http2db_server
,
1466 ('id','name','description','hostId','imageRef','flavorRef','status', 'tenant_id') )
1467 if tenant_id
!='any':
1468 where_
['tenant_id'] = tenant_id
1469 result
, content
= my
.db
.get_table(SELECT
=select_
, FROM
='instances', WHERE
=where_
, LIMIT
=limit_
)
1471 print "http_get_servers Error", content
1472 bottle
.abort(-result
, content
)
1474 change_keys_http2db(content
, http2db_server
, reverse
=True)
1476 tenant_id
= row
.pop('tenant_id')
1477 row
['links']=[ {'href': "/".join( (my
.url_preffix
, tenant_id
, 'servers', str(row
['id']) ) ), 'rel':'bookmark' } ]
1478 data
={'servers' : content
}
1479 return format_out(data
)
1481 @bottle.route(url_base
+ '/<tenant_id>/servers/<server_id>', method
='GET')
1482 def http_get_server_id(tenant_id
, server_id
):
1483 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
1484 #check valid tenant_id
1485 result
,content
= check_valid_tenant(my
, tenant_id
)
1487 bottle
.abort(result
, content
)
1490 result
, content
= my
.db
.get_instance(server_id
)
1492 bottle
.abort(HTTP_Not_Found
, content
)
1494 #change image/flavor-id to id and link
1495 convert_bandwidth(content
, reverse
=True)
1496 convert_datetime2str(content
)
1497 if content
["ram"]==0 : del content
["ram"]
1498 if content
["vcpus"]==0 : del content
["vcpus"]
1499 if 'flavor_id' in content
:
1500 if content
['flavor_id'] is not None:
1501 content
['flavor'] = {'id':content
['flavor_id'],
1502 'links':[{'href': "/".join( (my
.url_preffix
, content
['tenant_id'], 'flavors', str(content
['flavor_id']) ) ), 'rel':'bookmark'}]
1504 del content
['flavor_id']
1505 if 'image_id' in content
:
1506 if content
['image_id'] is not None:
1507 content
['image'] = {'id':content
['image_id'],
1508 'links':[{'href': "/".join( (my
.url_preffix
, content
['tenant_id'], 'images', str(content
['image_id']) ) ), 'rel':'bookmark'}]
1510 del content
['image_id']
1511 change_keys_http2db(content
, http2db_server
, reverse
=True)
1512 if 'extended' in content
:
1513 if 'devices' in content
['extended']: change_keys_http2db(content
['extended']['devices'], http2db_server
, reverse
=True)
1515 data
={'server' : content
}
1516 return format_out(data
)
1518 bottle
.abort(-result
, content
)
1521 @bottle.route(url_base
+ '/<tenant_id>/servers', method
='POST')
1522 def http_post_server_id(tenant_id
):
1523 '''deploys a new server'''
1524 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
1525 #check valid tenant_id
1526 result
,content
= check_valid_tenant(my
, tenant_id
)
1528 bottle
.abort(result
, content
)
1530 if tenant_id
=='any':
1531 bottle
.abort(HTTP_Bad_Request
, "Invalid tenant 'any' with this command")
1533 http_content
= format_in( server_new_schema
)
1534 r
= remove_extra_items(http_content
, server_new_schema
)
1535 if r
is not None: print "http_post_serves: Warning: remove extra items ", r
1536 change_keys_http2db(http_content
['server'], http2db_server
)
1537 extended_dict
= http_content
['server'].get('extended', None)
1538 if extended_dict
is not None:
1539 result
, content
= check_extended(extended_dict
, True)
1541 print "http_post_servers wrong input extended error %d %s" % (result
, content
)
1542 bottle
.abort(-result
, content
)
1544 convert_bandwidth(extended_dict
)
1545 if 'devices' in extended_dict
: change_keys_http2db(extended_dict
['devices'], http2db_server
)
1547 server
= http_content
['server']
1548 server_start
= server
.get('start', 'yes')
1549 server
['tenant_id'] = tenant_id
1550 #check flavor valid and take info
1551 result
, content
= my
.db
.get_table(FROM
='tenants_flavors as tf join flavors as f on tf.flavor_id=f.uuid',
1552 SELECT
=('ram','vcpus','extended'), WHERE
={'uuid':server
['flavor_id'], 'tenant_id':tenant_id
})
1554 bottle
.abort(HTTP_Not_Found
, 'flavor_id %s not found' % server
['flavor_id'])
1556 server
['flavor']=content
[0]
1557 #check image valid and take info
1558 result
, content
= my
.db
.get_table(FROM
='tenants_images as ti join images as i on ti.image_id=i.uuid',
1559 SELECT
=('path','metadata'), WHERE
={'uuid':server
['image_id'], 'tenant_id':tenant_id
, "status":"ACTIVE"})
1561 bottle
.abort(HTTP_Not_Found
, 'image_id %s not found or not ACTIVE' % server
['image_id'])
1563 server
['image']=content
[0]
1564 if "hosts_id" in server
:
1565 result
, content
= my
.db
.get_table(FROM
='hosts', SELECT
=('uuid',), WHERE
={'uuid': server
['host_id']})
1567 bottle
.abort(HTTP_Not_Found
, 'hostId %s not found' % server
['host_id'])
1569 #print json.dumps(server, indent=4)
1571 result
, content
= ht
.create_server(server
, config_dic
['db'], config_dic
['db_lock'], config_dic
['mode']=='normal')
1574 #Insert instance to database
1577 print "inserting at DB"
1579 if server_start
== 'no':
1580 content
['status'] = 'INACTIVE'
1582 for net
in http_content
['server']['networks']:
1583 if net
['type'] == 'instance:ovs':
1584 dhcp_nets_id
.append(get_network_id(net
['net_id']))
1587 new_instance_result
, new_instance
= my
.db
.new_instance(content
, nets
, ports_to_free
)
1588 if new_instance_result
< 0:
1589 print "Error http_post_servers() :", new_instance_result
, new_instance
1590 bottle
.abort(-new_instance_result
, new_instance
)
1593 print "inserted at DB"
1595 for port
in ports_to_free
:
1596 r
,c
= config_dic
['host_threads'][ server
['host_id'] ].insert_task( 'restore-iface',*port
)
1598 print ' http_post_servers ERROR RESTORE IFACE!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!' + c
1601 r
,c
= config_dic
['of_thread'].insert_task("update-net", net
)
1603 print ':http_post_servers ERROR UPDATING NETS !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!' + c
1606 #look for dhcp ip address
1607 r2
, c2
= my
.db
.get_table(FROM
="ports", SELECT
=["mac", "ip_address", "net_id"], WHERE
={"instance_id": new_instance
})
1610 if config_dic
.get("dhcp_server") and iface
["net_id"] in config_dic
["dhcp_nets"]:
1611 #print "dhcp insert add task"
1612 r
,c
= config_dic
['dhcp_thread'].insert_task("add", iface
["mac"])
1614 print ':http_post_servers ERROR UPDATING dhcp_server !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!' + c
1616 #ensure compute contain the bridge for ovs networks:
1617 server_net
= get_network_id(iface
['net_id'])
1618 if server_net
["network"].get('provider:physical', "")[:3] == 'OVS':
1619 vlan
= str(server_net
['network']['provider:vlan'])
1620 dhcp_enable
= bool(server_net
['network']['enable_dhcp'])
1622 dhcp_firt_ip
= str(server_net
['network']['dhcp_first_ip'])
1623 dhcp_last_ip
= str(server_net
['network']['dhcp_last_ip'])
1624 dhcp_cidr
= str(server_net
['network']['cidr'])
1625 vm_dhcp_ip
= c2
[0]["ip_address"]
1626 config_dic
['host_threads'][server
['host_id']].insert_task("create-ovs-bridge-port", vlan
)
1628 set_mac_dhcp(vm_dhcp_ip
, vlan
, dhcp_firt_ip
, dhcp_last_ip
, dhcp_cidr
, c2
[0]['mac'])
1629 launch_dhcp_server(vlan
, dhcp_firt_ip
, dhcp_last_ip
, dhcp_cidr
)
1632 server
['uuid'] = new_instance
1633 server_start
= server
.get('start', 'yes')
1635 if server_start
!= 'no':
1636 server
['paused'] = True if server_start
== 'paused' else False
1637 server
['action'] = {"start":None}
1638 server
['status'] = "CREATING"
1640 r
,c
= config_dic
['host_threads'][ server
['host_id'] ].insert_task( 'instance',server
)
1642 my
.db
.update_rows('instances', {'status':"ERROR"}, {'uuid':server
['uuid'], 'last_error':c
}, log
=True)
1644 return http_get_server_id(tenant_id
, new_instance
)
1646 bottle
.abort(HTTP_Bad_Request
, content
)
1649 def http_server_action(server_id
, tenant_id
, action
):
1650 '''Perform actions over a server as resume, reboot, terminate, ...'''
1651 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
1652 server
={"uuid": server_id
, "action":action
}
1653 where
={'uuid': server_id
}
1654 if tenant_id
!='any':
1655 where
['tenant_id']= tenant_id
1656 result
, content
= my
.db
.get_table(FROM
='instances', WHERE
=where
)
1658 bottle
.abort(HTTP_Not_Found
, "server %s not found" % server_id
)
1661 print "http_post_server_action error getting data %d %s" % (result
, content
)
1662 bottle
.abort(HTTP_Internal_Server_Error
, content
)
1664 server
.update(content
[0])
1665 tenant_id
= server
["tenant_id"]
1667 #TODO check a right content
1669 if 'terminate' in action
:
1670 new_status
='DELETING'
1671 elif server
['status'] == 'ERROR': #or server['status'] == 'CREATING':
1672 if 'terminate' not in action
and 'rebuild' not in action
:
1673 bottle
.abort(HTTP_Method_Not_Allowed
, "Server is in ERROR status, must be rebuit or deleted ")
1675 # elif server['status'] == 'INACTIVE':
1676 # if 'start' not in action and 'createImage' not in action:
1677 # bottle.abort(HTTP_Method_Not_Allowed, "The only possible action over an instance in 'INACTIVE' status is 'start'")
1679 # if 'start' in action:
1680 # new_status='CREATING'
1681 # server['paused']='no'
1682 # elif server['status'] == 'PAUSED':
1683 # if 'resume' not in action:
1684 # bottle.abort(HTTP_Method_Not_Allowed, "The only possible action over an instance in 'PAUSED' status is 'resume'")
1686 # elif server['status'] == 'ACTIVE':
1687 # if 'pause' not in action and 'reboot'not in action and 'shutoff'not in action:
1688 # bottle.abort(HTTP_Method_Not_Allowed, "The only possible action over an instance in 'ACTIVE' status is 'pause','reboot' or 'shutoff'")
1691 if 'start' in action
or 'createImage' in action
or 'rebuild' in action
:
1692 #check image valid and take info
1693 image_id
= server
['image_id']
1694 if 'createImage' in action
:
1695 if 'imageRef' in action
['createImage']:
1696 image_id
= action
['createImage']['imageRef']
1697 elif 'disk' in action
['createImage']:
1698 result
, content
= my
.db
.get_table(FROM
='instance_devices',
1699 SELECT
=('image_id','dev'), WHERE
={'instance_id':server
['uuid'],"type":"disk"})
1701 bottle
.abort(HTTP_Not_Found
, 'disk not found for server')
1705 if action
['createImage']['imageRef']['disk'] != None:
1706 for disk
in content
:
1707 if disk
['dev'] == action
['createImage']['imageRef']['disk']:
1708 disk_id
= disk
['image_id']
1711 bottle
.abort(HTTP_Not_Found
, 'disk %s not found for server' % action
['createImage']['imageRef']['disk'])
1714 bottle
.abort(HTTP_Not_Found
, 'more than one disk found for server' )
1718 image_id
= content
[0]['image_id']
1720 result
, content
= my
.db
.get_table(FROM
='tenants_images as ti join images as i on ti.image_id=i.uuid',
1721 SELECT
=('path','metadata'), WHERE
={'uuid':image_id
, 'tenant_id':tenant_id
, "status":"ACTIVE"})
1723 bottle
.abort(HTTP_Not_Found
, 'image_id %s not found or not ACTIVE' % image_id
)
1725 if content
[0]['metadata'] is not None:
1727 metadata
= json
.loads(content
[0]['metadata'])
1729 return -HTTP_Internal_Server_Error
, "Can not decode image metadata"
1730 content
[0]['metadata']=metadata
1732 content
[0]['metadata'] = {}
1733 server
['image']=content
[0]
1734 if 'createImage' in action
:
1735 action
['createImage']['source'] = {'image_id': image_id
, 'path': content
[0]['path']}
1736 if 'createImage' in action
:
1737 #Create an entry in Database for the new image
1738 new_image
={'status':'BUILD', 'progress': 0 }
1739 new_image_metadata
=content
[0]
1740 if 'metadata' in server
['image'] and server
['image']['metadata'] != None:
1741 new_image_metadata
.update(server
['image']['metadata'])
1742 new_image_metadata
= {"use_incremental":"no"}
1743 if 'metadata' in action
['createImage']:
1744 new_image_metadata
.update(action
['createImage']['metadata'])
1745 new_image
['metadata'] = json
.dumps(new_image_metadata
)
1746 new_image
['name'] = action
['createImage'].get('name', None)
1747 new_image
['description'] = action
['createImage'].get('description', None)
1748 new_image
['uuid']=my
.db
.new_uuid()
1749 if 'path' in action
['createImage']:
1750 new_image
['path'] = action
['createImage']['path']
1752 new_image
['path']="/provisional/path/" + new_image
['uuid']
1753 result
, image_uuid
= my
.db
.new_image(new_image
, tenant_id
)
1755 bottle
.abort(HTTP_Bad_Request
, 'Error: ' + image_uuid
)
1757 server
['new_image'] = new_image
1761 r
,c
= config_dic
['host_threads'][ server
['host_id'] ].insert_task( 'instance',server
)
1763 print "Task queue full at host ", server
['host_id']
1764 bottle
.abort(HTTP_Request_Timeout
, c
)
1765 if 'createImage' in action
and result
>= 0:
1766 return http_get_image_id(tenant_id
, image_uuid
)
1768 #Update DB only for CREATING or DELETING status
1769 data
={'result' : 'in process'}
1770 if new_status
!= None and new_status
== 'DELETING':
1775 #look for dhcp ip address
1776 r2
, c2
= my
.db
.get_table(FROM
="ports", SELECT
=["mac", "net_id"], WHERE
={"instance_id": server_id
})
1777 r
, c
= my
.db
.delete_instance(server_id
, tenant_id
, nets
, ports_to_free
, net_ovs_list
, "requested by http")
1778 for port
in ports_to_free
:
1779 r1
,c1
= config_dic
['host_threads'][ server
['host_id'] ].insert_task( 'restore-iface',*port
)
1781 print ' http_post_server_action error at server deletion ERROR resore-iface !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!' + c1
1782 data
={'result' : 'deleting in process, but ifaces cannot be restored!!!!!'}
1784 r1
,c1
= config_dic
['of_thread'].insert_task("update-net", net
)
1786 print ' http_post_server_action error at server deletion ERROR UPDATING NETS !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!' + c1
1787 data
={'result' : 'deleting in process, but openflow rules cannot be deleted!!!!!'}
1788 #look for dhcp ip address
1789 if r2
>0 and config_dic
.get("dhcp_server"):
1791 if iface
["net_id"] in config_dic
["dhcp_nets"]:
1792 r
,c
= config_dic
['dhcp_thread'].insert_task("del", iface
["mac"])
1793 #print "dhcp insert del task"
1795 print ':http_post_servers ERROR UPDATING dhcp_server !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!' + c
1796 # delete ovs-port and linux bridge, contains a list of tuple (net_id,vlan)
1797 for net
in net_ovs_list
:
1802 delete_dhcp_ovs_bridge(vlan
, net_id
)
1803 delete_mac_dhcp(vm_ip
, vlan
, mac
)
1804 config_dic
['host_threads'][server
['host_id']].insert_task('del-ovs-port', vlan
, net_id
)
1805 return format_out(data
)
1809 @bottle.route(url_base
+ '/<tenant_id>/servers/<server_id>', method
='DELETE')
1810 def http_delete_server_id(tenant_id
, server_id
):
1811 '''delete a server'''
1812 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
1813 #check valid tenant_id
1814 result
,content
= check_valid_tenant(my
, tenant_id
)
1816 bottle
.abort(result
, content
)
1819 return http_server_action(server_id
, tenant_id
, {"terminate":None} )
1822 @bottle.route(url_base
+ '/<tenant_id>/servers/<server_id>/action', method
='POST')
1823 def http_post_server_action(tenant_id
, server_id
):
1824 '''take an action over a server'''
1825 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
1826 #check valid tenant_id
1827 result
,content
= check_valid_tenant(my
, tenant_id
)
1829 bottle
.abort(result
, content
)
1831 http_content
= format_in( server_action_schema
)
1832 #r = remove_extra_items(http_content, server_action_schema)
1833 #if r is not None: print "http_post_server_action: Warning: remove extra items ", r
1835 return http_server_action(server_id
, tenant_id
, http_content
)
1842 @bottle.route(url_base
+ '/networks', method
='GET')
1843 def http_get_networks():
1844 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
1846 select_
,where_
,limit_
= filter_query_string(bottle
.request
.query
, http2db_network
,
1847 ('id','name','tenant_id','type',
1848 'shared','provider:vlan','status','last_error','admin_state_up','provider:physical') )
1849 #TODO temporally remove tenant_id
1850 if "tenant_id" in where_
:
1851 del where_
["tenant_id"]
1852 result
, content
= my
.db
.get_table(SELECT
=select_
, FROM
='nets', WHERE
=where_
, LIMIT
=limit_
)
1854 print "http_get_networks error %d %s" % (result
, content
)
1855 bottle
.abort(-result
, content
)
1857 convert_boolean(content
, ('shared', 'admin_state_up', 'enable_dhcp'))
1858 delete_nulls(content
)
1859 change_keys_http2db(content
, http2db_network
, reverse
=True)
1860 data
={'networks' : content
}
1861 return format_out(data
)
1863 @bottle.route(url_base
+ '/networks/<network_id>', method
='GET')
1864 def http_get_network_id(network_id
):
1865 data
= get_network_id(network_id
)
1866 return format_out(data
)
1868 def get_network_id(network_id
):
1869 my
= config_dic
['http_threads'][threading
.current_thread().name
]
1871 where_
= bottle
.request
.query
1872 where_
['uuid'] = network_id
1873 result
, content
= my
.db
.get_table(FROM
='nets', WHERE
=where_
, LIMIT
=100)
1876 print "http_get_networks_id error %d %s" % (result
, content
)
1877 bottle
.abort(-result
, content
)
1879 print "http_get_networks_id network '%s' not found" % network_id
1880 bottle
.abort(HTTP_Not_Found
, 'network %s not found' % network_id
)
1882 convert_boolean(content
, ('shared', 'admin_state_up', 'enable_dhcp'))
1883 change_keys_http2db(content
, http2db_network
, reverse
=True)
1885 result
, ports
= my
.db
.get_table(FROM
='ports', SELECT
=('uuid as port_id',),
1886 WHERE
={'net_id': network_id
}, LIMIT
=100)
1888 content
[0]['ports'] = ports
1889 delete_nulls(content
[0])
1890 data
={'network' : content
[0]}
1893 @bottle.route(url_base
+ '/networks', method
='POST')
1894 def http_post_networks():
1895 '''insert a network into the database.'''
1896 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
1898 http_content
= format_in( network_new_schema
)
1899 r
= remove_extra_items(http_content
, network_new_schema
)
1900 if r
is not None: print "http_post_networks: Warning: remove extra items ", r
1901 change_keys_http2db(http_content
['network'], http2db_network
)
1902 network
=http_content
['network']
1903 #check valid tenant_id
1904 tenant_id
= network
.get('tenant_id')
1906 result
, _
= my
.db
.get_table(FROM
='tenants', SELECT
=('uuid',), WHERE
={'uuid': tenant_id
,"enabled":True})
1908 bottle
.abort(HTTP_Not_Found
, 'tenant %s not found or not enabled' % tenant_id
)
1912 net_provider
= network
.get('provider')
1913 net_type
= network
.get('type')
1914 net_type
= network
.get('type')
1915 net_enable_dhcp
= network
.get('enable_dhcp')
1917 net_cidr
= network
.get('cidr')
1919 net_vlan
= network
.get("vlan")
1920 net_bind_net
= network
.get("bind_net")
1921 net_bind_type
= network
.get("bind_type")
1922 name
= network
["name"]
1924 #check if network name ends with :<vlan_tag> and network exist in order to make and automated bindning
1925 vlan_index
=name
.rfind(":")
1926 if net_bind_net
==None and net_bind_type
==None and vlan_index
> 1:
1928 vlan_tag
= int(name
[vlan_index
+1:])
1929 if vlan_tag
>0 and vlan_tag
< 4096:
1930 net_bind_net
= name
[:vlan_index
]
1931 net_bind_type
= "vlan:" + name
[vlan_index
+1:]
1935 if net_bind_net
!= None:
1936 #look for a valid net
1937 if check_valid_uuid(net_bind_net
):
1938 net_bind_key
= "uuid"
1940 net_bind_key
= "name"
1941 result
, content
= my
.db
.get_table(FROM
='nets', WHERE
={net_bind_key
: net_bind_net
} )
1943 bottle
.abort(HTTP_Internal_Server_Error
, 'getting nets from db ' + content
)
1946 bottle
.abort(HTTP_Bad_Request
, "bind_net %s '%s'not found" % (net_bind_key
, net_bind_net
) )
1949 bottle
.abort(HTTP_Bad_Request
, "more than one bind_net %s '%s' found, use uuid" % (net_bind_key
, net_bind_net
) )
1951 network
["bind_net"] = content
[0]["uuid"]
1952 if net_bind_type
!= None:
1953 if net_bind_type
[0:5] != "vlan:":
1954 bottle
.abort(HTTP_Bad_Request
, "bad format for 'bind_type', must be 'vlan:<tag>'")
1956 if int(net_bind_type
[5:]) > 4095 or int(net_bind_type
[5:])<=0 :
1957 bottle
.abort(HTTP_Bad_Request
, "bad format for 'bind_type', must be 'vlan:<tag>' with a tag between 1 and 4095")
1959 network
["bind_type"] = net_bind_type
1961 if net_provider
!=None:
1962 if net_provider
[:9]=="openflow:":
1964 if net_type
!="ptp" and net_type
!="data":
1965 bottle
.abort(HTTP_Bad_Request
, "Only 'ptp' or 'data' net types can be bound to 'openflow'")
1970 if net_type
!="bridge_man" and net_type
!="bridge_data":
1971 bottle
.abort(HTTP_Bad_Request
, "Only 'bridge_man' or 'bridge_data' net types can be bound to 'bridge', 'macvtap' or 'default")
1973 net_type
='bridge_man'
1976 net_type
='bridge_man'
1978 if net_provider
!= None:
1979 if net_provider
[:7] == 'bridge:':
1980 bridge_net_name
= net_provider
[7:]
1981 for brnet
in config_dic
['bridge_nets']:
1982 if brnet
[0]==bridge_net_name
: # free
1983 if brnet
[3] != None:
1984 bottle
.abort(HTTP_Conflict
, "invalid 'provider:physical', bridge '%s' is already used" % bridge_net_name
)
1989 # if bridge_net==None:
1990 # 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)
1992 elif config_dic
['network_type'] == 'bridge' and ( net_type
=='bridge_data' or net_type
== 'bridge_man' ):
1993 #look for a free precreated nets
1994 for brnet
in config_dic
['bridge_nets']:
1995 if brnet
[3]==None: # free
1996 if bridge_net
!= None:
1997 if net_type
=='bridge_man': #look for the smaller speed
1998 if brnet
[2] < bridge_net
[2]: bridge_net
= brnet
1999 else: #look for the larger speed
2000 if brnet
[2] > bridge_net
[2]: bridge_net
= brnet
2004 if bridge_net
==None:
2005 bottle
.abort(HTTP_Bad_Request
, "Max limits of bridge networks reached. Future versions of VIM will overcome this limit")
2008 print "using net", bridge_net
2009 net_provider
= "bridge:"+bridge_net
[0]
2010 net_vlan
= bridge_net
[1]
2011 elif net_type
== 'bridge_data' or net_type
== 'bridge_man' and config_dic
['network_type'] == 'ovs':
2012 net_provider
= 'OVS'
2013 if not net_vlan
and (net_type
== "data" or net_type
== "ptp" or net_provider
== "OVS"):
2014 net_vlan
= my
.db
.get_free_net_vlan()
2016 bottle
.abort(HTTP_Internal_Server_Error
, "Error getting an available vlan")
2018 if net_provider
== 'OVS':
2019 net_provider
= 'OVS' + ":" + str(net_vlan
)
2021 network
['provider'] = net_provider
2022 network
['type'] = net_type
2023 network
['vlan'] = net_vlan
2025 if 'enable_dhcp' in network
and network
['enable_dhcp']:
2026 check_dhcp_data_integrity(network
)
2028 result
, content
= my
.db
.new_row('nets', network
, True, True)
2031 if bridge_net
!=None:
2032 bridge_net
[3] = content
2033 if config_dic
.get("dhcp_server") and config_dic
['network_type'] == 'bridge':
2034 if network
["name"] in config_dic
["dhcp_server"].get("nets", () ):
2035 config_dic
["dhcp_nets"].append(content
)
2036 print "dhcp_server: add new net", content
2037 elif bridge_net
!= None and bridge_net
[0] in config_dic
["dhcp_server"].get("bridge_ifaces", () ):
2038 config_dic
["dhcp_nets"].append(content
)
2039 print "dhcp_server: add new net", content
2040 return http_get_network_id(content
)
2042 print "http_post_networks error %d %s" % (result
, content
)
2043 bottle
.abort(-result
, content
)
2047 def check_dhcp_data_integrity(network
):
2049 Check if all dhcp parameter for anet are valid, if not will be calculated from cidr value
2050 :param network: list with user nets paramters
2055 if "cidr" in network
:
2056 cidr
= network
["cidr"]
2058 ips
= IPNetwork(cidr
)
2059 if "dhcp_first_ip" not in network
:
2060 network
["dhcp_first_ip"] = str(ips
[2])
2061 if "dhcp_last_ip" not in network
:
2062 network
["dhcp_last_ip"] = str(ips
[-2])
2065 @bottle.route(url_base
+ '/networks/<network_id>', method
='PUT')
2066 def http_put_network_id(network_id
):
2067 '''update a network_id into the database.'''
2068 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
2070 http_content
= format_in( network_update_schema
)
2071 r
= remove_extra_items(http_content
, network_update_schema
)
2072 change_keys_http2db(http_content
['network'], http2db_network
)
2073 network
=http_content
['network']
2075 #Look for the previous data
2076 where_
= {'uuid': network_id
}
2077 result
, network_old
= my
.db
.get_table(FROM
='nets', WHERE
=where_
)
2079 print "http_put_network_id error %d %s" % (result
, network_old
)
2080 bottle
.abort(-result
, network_old
)
2083 print "http_put_network_id network '%s' not found" % network_id
2084 bottle
.abort(HTTP_Not_Found
, 'network %s not found' % network_id
)
2087 nbports
, content
= my
.db
.get_table(FROM
='ports', SELECT
=('uuid as port_id',),
2088 WHERE
={'net_id': network_id
}, LIMIT
=100)
2090 print "http_put_network_id error %d %s" % (result
, network_old
)
2091 bottle
.abort(-result
, content
)
2094 if 'type' in network
and network
['type'] != network_old
[0]['type']:
2095 bottle
.abort(HTTP_Method_Not_Allowed
, "Can not change type of network while having ports attached")
2096 if 'vlan' in network
and network
['vlan'] != network_old
[0]['vlan']:
2097 bottle
.abort(HTTP_Method_Not_Allowed
, "Can not change vlan of network while having ports attached")
2100 net_provider
= network
.get('provider', network_old
[0]['provider'])
2101 net_type
= network
.get('type', network_old
[0]['type'])
2102 net_bind_net
= network
.get("bind_net")
2103 net_bind_type
= network
.get("bind_type")
2104 if net_bind_net
!= None:
2105 #look for a valid net
2106 if check_valid_uuid(net_bind_net
):
2107 net_bind_key
= "uuid"
2109 net_bind_key
= "name"
2110 result
, content
= my
.db
.get_table(FROM
='nets', WHERE
={net_bind_key
: net_bind_net
} )
2112 bottle
.abort(HTTP_Internal_Server_Error
, 'getting nets from db ' + content
)
2115 bottle
.abort(HTTP_Bad_Request
, "bind_net %s '%s'not found" % (net_bind_key
, net_bind_net
) )
2118 bottle
.abort(HTTP_Bad_Request
, "more than one bind_net %s '%s' found, use uuid" % (net_bind_key
, net_bind_net
) )
2120 network
["bind_net"] = content
[0]["uuid"]
2121 if net_bind_type
!= None:
2122 if net_bind_type
[0:5] != "vlan:":
2123 bottle
.abort(HTTP_Bad_Request
, "bad format for 'bind_type', must be 'vlan:<tag>'")
2125 if int(net_bind_type
[5:]) > 4095 or int(net_bind_type
[5:])<=0 :
2126 bottle
.abort(HTTP_Bad_Request
, "bad format for 'bind_type', must be 'vlan:<tag>' with a tag between 1 and 4095")
2128 if net_provider
!=None:
2129 if net_provider
[:9]=="openflow:":
2130 if net_type
!="ptp" and net_type
!="data":
2131 bottle
.abort(HTTP_Bad_Request
, "Only 'ptp' or 'data' net types can be bound to 'openflow'")
2133 if net_type
!="bridge_man" and net_type
!="bridge_data":
2134 bottle
.abort(HTTP_Bad_Request
, "Only 'bridge_man' or 'bridge_data' net types can be bound to 'bridge', 'macvtap' or 'default")
2136 #insert in data base
2137 result
, content
= my
.db
.update_rows('nets', network
, WHERE
={'uuid': network_id
}, log
=True )
2139 if result
>0: # and nbports>0 and 'admin_state_up' in network and network['admin_state_up'] != network_old[0]['admin_state_up']:
2140 r
,c
= config_dic
['of_thread'].insert_task("update-net", network_id
)
2142 print "http_put_network_id error while launching openflow rules"
2143 bottle
.abort(HTTP_Internal_Server_Error
, c
)
2144 if config_dic
.get("dhcp_server"):
2145 if network_id
in config_dic
["dhcp_nets"]:
2146 config_dic
["dhcp_nets"].remove(network_id
)
2147 print "dhcp_server: delete net", network_id
2148 if network
.get("name", network_old
["name"]) in config_dic
["dhcp_server"].get("nets", () ):
2149 config_dic
["dhcp_nets"].append(network_id
)
2150 print "dhcp_server: add new net", network_id
2152 net_bind
= network
.get("bind", network_old
["bind"] )
2153 if net_bind
and net_bind
[:7]=="bridge:" and net_bind
[7:] in config_dic
["dhcp_server"].get("bridge_ifaces", () ):
2154 config_dic
["dhcp_nets"].append(network_id
)
2155 print "dhcp_server: add new net", network_id
2156 return http_get_network_id(network_id
)
2158 bottle
.abort(-result
, content
)
2162 @bottle.route(url_base
+ '/networks/<network_id>', method
='DELETE')
2163 def http_delete_network_id(network_id
):
2164 '''delete a network_id from the database.'''
2165 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
2167 #delete from the data base
2168 result
, content
= my
.db
.delete_row('nets', network_id
)
2171 bottle
.abort(HTTP_Not_Found
, content
)
2173 for brnet
in config_dic
['bridge_nets']:
2174 if brnet
[3]==network_id
:
2177 if config_dic
.get("dhcp_server") and network_id
in config_dic
["dhcp_nets"]:
2178 config_dic
["dhcp_nets"].remove(network_id
)
2179 print "dhcp_server: delete net", network_id
2180 data
={'result' : content
}
2181 return format_out(data
)
2183 print "http_delete_network_id error",result
, content
2184 bottle
.abort(-result
, content
)
2189 @bottle.route(url_base
+ '/networks/<network_id>/openflow', method
='GET')
2190 def http_get_openflow_id(network_id
):
2191 '''To obtain the list of openflow rules of a network
2193 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
2195 if network_id
=='all':
2198 where_
={"net_id": network_id
}
2199 result
, content
= my
.db
.get_table(SELECT
=("name","net_id","priority","vlan_id","ingress_port","src_mac","dst_mac","actions"),
2200 WHERE
=where_
, FROM
='of_flows')
2202 bottle
.abort(-result
, content
)
2204 data
={'openflow-rules' : content
}
2205 return format_out(data
)
2207 @bottle.route(url_base
+ '/networks/<network_id>/openflow', method
='PUT')
2208 def http_put_openflow_id(network_id
):
2209 '''To make actions over the net. The action is to reinstall the openflow rules
2210 network_id can be 'all'
2212 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
2214 bottle
.abort(HTTP_Unauthorized
, "Needed admin privileges")
2217 if network_id
=='all':
2220 where_
={"uuid": network_id
}
2221 result
, content
= my
.db
.get_table(SELECT
=("uuid","type"), WHERE
=where_
, FROM
='nets')
2223 bottle
.abort(-result
, content
)
2227 if net
["type"]!="ptp" and net
["type"]!="data":
2230 r
,c
= config_dic
['of_thread'].insert_task("update-net", net
['uuid'])
2232 print "http_put_openflow_id error while launching openflow rules"
2233 bottle
.abort(HTTP_Internal_Server_Error
, c
)
2234 data
={'result' : str(result
)+" nets updates"}
2235 return format_out(data
)
2237 @bottle.route(url_base
+ '/networks/openflow/clear', method
='DELETE')
2238 @bottle.route(url_base
+ '/networks/clear/openflow', method
='DELETE')
2239 def http_clear_openflow_rules():
2240 '''To make actions over the net. The action is to delete ALL openflow rules
2242 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
2244 bottle
.abort(HTTP_Unauthorized
, "Needed admin privileges")
2247 r
,c
= config_dic
['of_thread'].insert_task("clear-all")
2249 print "http_delete_openflow_id error while launching openflow rules"
2250 bottle
.abort(HTTP_Internal_Server_Error
, c
)
2253 data
={'result' : " Clearing openflow rules in process"}
2254 return format_out(data
)
2256 @bottle.route(url_base
+ '/networks/openflow/ports', method
='GET')
2257 def http_get_openflow_ports():
2258 '''Obtain switch ports names of openflow controller
2260 data
={'ports' : config_dic
['of_thread'].OF_connector
.pp2ofi
}
2261 return format_out(data
)
2268 @bottle.route(url_base
+ '/ports', method
='GET')
2269 def http_get_ports():
2271 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
2272 select_
,where_
,limit_
= filter_query_string(bottle
.request
.query
, http2db_port
,
2273 ('id','name','tenant_id','network_id','vpci','mac_address','device_owner','device_id',
2274 'binding:switch_port','binding:vlan','bandwidth','status','admin_state_up','ip_address') )
2275 #result, content = my.db.get_ports(where_)
2276 result
, content
= my
.db
.get_table(SELECT
=select_
, WHERE
=where_
, FROM
='ports',LIMIT
=limit_
)
2278 print "http_get_ports Error", result
, content
2279 bottle
.abort(-result
, content
)
2282 convert_boolean(content
, ('admin_state_up',) )
2283 delete_nulls(content
)
2284 change_keys_http2db(content
, http2db_port
, reverse
=True)
2285 data
={'ports' : content
}
2286 return format_out(data
)
2288 @bottle.route(url_base
+ '/ports/<port_id>', method
='GET')
2289 def http_get_port_id(port_id
):
2290 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
2292 result
, content
= my
.db
.get_table(WHERE
={'uuid': port_id
}, FROM
='ports')
2294 print "http_get_ports error", result
, content
2295 bottle
.abort(-result
, content
)
2297 print "http_get_ports port '%s' not found" % str(port_id
)
2298 bottle
.abort(HTTP_Not_Found
, 'port %s not found' % port_id
)
2300 convert_boolean(content
, ('admin_state_up',) )
2301 delete_nulls(content
)
2302 change_keys_http2db(content
, http2db_port
, reverse
=True)
2303 data
={'port' : content
[0]}
2304 return format_out(data
)
2307 @bottle.route(url_base
+ '/ports', method
='POST')
2308 def http_post_ports():
2309 '''insert an external port into the database.'''
2310 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
2312 bottle
.abort(HTTP_Unauthorized
, "Needed admin privileges")
2314 http_content
= format_in( port_new_schema
)
2315 r
= remove_extra_items(http_content
, port_new_schema
)
2316 if r
is not None: print "http_post_ports: Warning: remove extra items ", r
2317 change_keys_http2db(http_content
['port'], http2db_port
)
2318 port
=http_content
['port']
2320 port
['type'] = 'external'
2321 if 'net_id' in port
and port
['net_id'] == None:
2324 if 'net_id' in port
:
2325 #check that new net has the correct type
2326 result
, new_net
= my
.db
.check_target_net(port
['net_id'], None, 'external' )
2328 bottle
.abort(HTTP_Bad_Request
, new_net
)
2330 #insert in data base
2331 result
, uuid
= my
.db
.new_row('ports', port
, True, True)
2333 if 'net_id' in port
:
2334 r
,c
= config_dic
['of_thread'].insert_task("update-net", port
['net_id'])
2336 print "http_post_ports error while launching openflow rules"
2337 bottle
.abort(HTTP_Internal_Server_Error
, c
)
2338 return http_get_port_id(uuid
)
2340 bottle
.abort(-result
, uuid
)
2343 @bottle.route(url_base
+ '/ports/<port_id>', method
='PUT')
2344 def http_put_port_id(port_id
):
2345 '''update a port_id into the database.'''
2347 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
2349 http_content
= format_in( port_update_schema
)
2350 change_keys_http2db(http_content
['port'], http2db_port
)
2351 port_dict
=http_content
['port']
2353 #Look for the previous port data
2354 where_
= {'uuid': port_id
}
2355 result
, content
= my
.db
.get_table(FROM
="ports",WHERE
=where_
)
2357 print "http_put_port_id error", result
, content
2358 bottle
.abort(-result
, content
)
2361 print "http_put_port_id port '%s' not found" % port_id
2362 bottle
.abort(HTTP_Not_Found
, 'port %s not found' % port_id
)
2365 for k
in ('vlan','switch_port','mac_address', 'tenant_id'):
2366 if k
in port_dict
and not my
.admin
:
2367 bottle
.abort(HTTP_Unauthorized
, "Needed admin privileges for changing " + k
)
2371 #change_keys_http2db(port, http2db_port, reverse=True)
2375 if 'net_id' in port_dict
:
2377 old_net
= port
.get('net_id', None)
2378 new_net
= port_dict
['net_id']
2379 if old_net
!= new_net
:
2381 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
2382 if old_net
is not None: nets
.append(old_net
)
2383 if port
['type'] == 'instance:bridge' or port
['type'] == 'instance:ovs':
2384 bottle
.abort(HTTP_Forbidden
, "bridge interfaces cannot be attached to a different net")
2386 elif port
['type'] == 'external':
2388 bottle
.abort(HTTP_Unauthorized
, "Needed admin privileges")
2392 #check that new net has the correct type
2393 result
, new_net_dict
= my
.db
.check_target_net(new_net
, None, port
['type'] )
2395 #change VLAN for SR-IOV ports
2396 if result
>=0 and port
["type"]=="instance:data" and port
["model"]=="VF": #TODO consider also VFnotShared
2398 port_dict
["vlan"] = None
2400 port_dict
["vlan"] = new_net_dict
["vlan"]
2401 #get host where this VM is allocated
2402 result
, content
= my
.db
.get_table(FROM
="instances",WHERE
={"uuid":port
["instance_id"]})
2404 print "http_put_port_id database error", content
2406 host_id
= content
[0]["host_id"]
2408 #insert in data base
2410 result
, content
= my
.db
.update_rows('ports', port_dict
, WHERE
={'uuid': port_id
}, log
=False )
2412 #Insert task to complete actions
2415 r
,v
= config_dic
['of_thread'].insert_task("update-net", net_id
)
2416 if r
<0: print "Error ********* http_put_port_id update_of_flows: ", v
2417 #TODO Do something if fails
2419 config_dic
['host_threads'][host_id
].insert_task("edit-iface", port_id
, old_net
, new_net
)
2422 return http_get_port_id(port_id
)
2424 bottle
.abort(HTTP_Bad_Request
, content
)
2428 @bottle.route(url_base
+ '/ports/<port_id>', method
='DELETE')
2429 def http_delete_port_id(port_id
):
2430 '''delete a port_id from the database.'''
2431 my
= config_dic
['http_threads'][ threading
.current_thread().name
]
2433 bottle
.abort(HTTP_Unauthorized
, "Needed admin privileges")
2436 #Look for the previous port data
2437 where_
= {'uuid': port_id
, "type": "external"}
2438 result
, ports
= my
.db
.get_table(WHERE
=where_
, FROM
='ports',LIMIT
=100)
2441 print "http_delete_port_id port '%s' not found" % port_id
2442 bottle
.abort(HTTP_Not_Found
, 'port %s not found or device_owner is not external' % port_id
)
2444 #delete from the data base
2445 result
, content
= my
.db
.delete_row('ports', port_id
)
2448 bottle
.abort(HTTP_Not_Found
, content
)
2450 network
= ports
[0].get('net_id', None)
2451 if network
is not None:
2453 r
,c
= config_dic
['of_thread'].insert_task("update-net", network
)
2454 if r
<0: print "!!!!!! http_delete_port_id update_of_flows error", r
, c
2455 data
={'result' : content
}
2456 return format_out(data
)
2458 print "http_delete_port_id error",result
, content
2459 bottle
.abort(-result
, content
)