1 # -*- coding: utf-8 -*-
4 # Copyright 2015 Telefónica Investigación y Desarrollo, S.A.U.
5 # This file is part of openmano
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 osconnector implements all the methods to interact with openstack using the python-client.
27 __author__
="Alfonso Tierno, Gerardo Garcia"
28 __date__
="$22-jun-2014 11:19:29$"
34 from novaclient
import client
as nClient
, exceptions
as nvExceptions
35 import keystoneclient
.v2_0
.client
as ksClient
36 import keystoneclient
.exceptions
as ksExceptions
37 import glanceclient
.v2
.client
as glClient
38 import glanceclient
.client
as gl1Client
39 import glanceclient
.exc
as gl1Exceptions
40 from httplib
import HTTPException
41 from neutronclient
.neutron
import client
as neClient
42 from neutronclient
.common
import exceptions
as neExceptions
43 from requests
.exceptions
import ConnectionError
45 '''contain the openstack virtual machine status to openmano status'''
46 vmStatus2manoFormat
={'ACTIVE':'ACTIVE',
48 'SUSPENDED': 'SUSPENDED',
51 'ERROR':'ERROR','DELETED':'DELETED'
53 netStatus2manoFormat
={'ACTIVE':'ACTIVE','PAUSED':'PAUSED','INACTIVE':'INACTIVE','BUILD':'BUILD','ERROR':'ERROR','DELETED':'DELETED'
56 class vimconnector(vimconn
.vimconnector
):
57 def __init__(self
, uuid
, name
, tenant_id
, tenant_name
, url
, url_admin
=None, user
=None, passwd
=None, debug
=True, config
={}):
58 '''using common constructor parameters. In this case
59 'url' is the keystone authorization url,
60 'url_admin' is not use
62 vimconn
.vimconnector
.__init
__(self
, uuid
, name
, tenant_id
, tenant_name
, url
, url_admin
, user
, passwd
, debug
, config
)
67 raise TypeError, 'url param can not be NoneType'
68 self
.k_creds
['auth_url'] = url
69 self
.n_creds
['auth_url'] = url
71 self
.k_creds
['tenant_name'] = tenant_name
72 self
.n_creds
['project_id'] = tenant_name
74 self
.k_creds
['tenant_id'] = tenant_id
75 self
.n_creds
['tenant_id'] = tenant_id
77 self
.k_creds
['username'] = user
78 self
.n_creds
['username'] = user
80 self
.k_creds
['password'] = passwd
81 self
.n_creds
['api_key'] = passwd
82 self
.reload_client
= True
84 def __setitem__(self
,index
, value
):
85 '''Set individuals parameters
86 Throw TypeError, KeyError
88 if index
=='tenant_id':
89 self
.reload_client
=True
90 self
.tenant_id
= value
92 self
.k_creds
['tenant_id'] = value
93 self
.n_creds
['tenant_id'] = value
95 del self
.k_creds
['tenant_name']
96 del self
.n_creds
['project_id']
97 elif index
=='tenant_name':
98 self
.reload_client
=True
99 self
.tenant_name
= value
101 self
.k_creds
['tenant_name'] = value
102 self
.n_creds
['project_id'] = value
104 del self
.k_creds
['tenant_name']
105 del self
.n_creds
['project_id']
107 self
.reload_client
=True
110 self
.k_creds
['username'] = value
111 self
.n_creds
['username'] = value
113 del self
.k_creds
['username']
114 del self
.n_creds
['username']
115 elif index
=='passwd':
116 self
.reload_client
=True
119 self
.k_creds
['password'] = value
120 self
.n_creds
['api_key'] = value
122 del self
.k_creds
['password']
123 del self
.n_creds
['api_key']
125 self
.reload_client
=True
128 self
.k_creds
['auth_url'] = value
129 self
.n_creds
['auth_url'] = value
131 raise TypeError, 'url param can not be NoneType'
133 vimconn
.vimconnector
.__setitem
__(self
,index
, value
)
135 def _reload_connection(self
):
136 '''Called before any operation, it check if credentials has changed
137 Throw keystoneclient.apiclient.exceptions.AuthorizationFailure
139 #TODO control the timing and possible token timeout, but it seams that python client does this task for us :-)
140 if self
.reload_client
:
142 if len(self
.n_creds
) <4:
143 raise ksExceptions
.ClientException("Not enough parameters to connect to openstack")
144 self
.nova
= nClient
.Client(2, **self
.n_creds
)
145 self
.keystone
= ksClient
.Client(**self
.k_creds
)
146 self
.glance_endpoint
= self
.keystone
.service_catalog
.url_for(service_type
='image', endpoint_type
='publicURL')
147 self
.glance
= glClient
.Client(self
.glance_endpoint
, token
=self
.keystone
.auth_token
, **self
.k_creds
) #TODO check k_creds vs n_creds
148 self
.ne_endpoint
=self
.keystone
.service_catalog
.url_for(service_type
='network', endpoint_type
='publicURL')
149 self
.neutron
= neClient
.Client('2.0', endpoint_url
=self
.ne_endpoint
, token
=self
.keystone
.auth_token
, **self
.k_creds
)
150 self
.reload_client
= False
152 def new_external_port(self
, port_data
):
153 #TODO openstack if needed
154 '''Adds a external port to VIM'''
155 '''Returns the port identifier'''
156 return -vimconn
.HTTP_Internal_Server_Error
, "osconnector.new_external_port() not implemented"
158 def connect_port_network(self
, port_id
, network_id
, admin
=False):
159 #TODO openstack if needed
160 '''Connects a external port to a network'''
161 '''Returns status code of the VIM response'''
162 return -vimconn
.HTTP_Internal_Server_Error
, "osconnector.connect_port_network() not implemented"
164 def new_user(self
, user_name
, user_passwd
, tenant_id
=None):
165 '''Adds a new user to openstack VIM'''
166 '''Returns the user identifier'''
168 print "osconnector: Adding a new user to VIM"
170 self
._reload
_connection
()
171 user
=self
.keystone
.users
.create(user_name
, user_passwd
, tenant_id
=tenant_id
)
172 #self.keystone.tenants.add_user(self.k_creds["username"], #role)
174 except ksExceptions
.ConnectionError
as e
:
175 error_value
=-vimconn
.HTTP_Bad_Request
176 error_text
= type(e
).__name
__ + ": "+ (str(e
) if len(e
.args
)==0 else str(e
.args
[0]))
177 except ksExceptions
.ClientException
as e
: #TODO remove
178 error_value
=-vimconn
.HTTP_Bad_Request
179 error_text
= type(e
).__name
__ + ": "+ (str(e
) if len(e
.args
)==0 else str(e
.args
[0]))
180 #TODO insert exception vimconn.HTTP_Unauthorized
181 #if reaching here is because an exception
183 print "new_tenant " + error_text
184 return error_value
, error_text
186 def delete_user(self
, user_id
):
187 '''Delete a user from openstack VIM'''
188 '''Returns the user identifier'''
190 print "osconnector: Deleting a user from VIM"
192 self
._reload
_connection
()
193 self
.keystone
.users
.delete(user_id
)
195 except ksExceptions
.ConnectionError
as e
:
196 error_value
=-vimconn
.HTTP_Bad_Request
197 error_text
= type(e
).__name
__ + ": "+ (str(e
) if len(e
.args
)==0 else str(e
.args
[0]))
198 except ksExceptions
.NotFound
as e
:
199 error_value
=-vimconn
.HTTP_Not_Found
200 error_text
= type(e
).__name
__ + ": "+ (str(e
) if len(e
.args
)==0 else str(e
.args
[0]))
201 except ksExceptions
.ClientException
as e
: #TODO remove
202 error_value
=-vimconn
.HTTP_Bad_Request
203 error_text
= type(e
).__name
__ + ": "+ (str(e
) if len(e
.args
)==0 else str(e
.args
[0]))
204 #TODO insert exception vimconn.HTTP_Unauthorized
205 #if reaching here is because an exception
207 print "delete_tenant " + error_text
208 return error_value
, error_text
210 def new_tenant(self
,tenant_name
,tenant_description
):
211 '''Adds a new tenant to openstack VIM'''
212 '''Returns the tenant identifier'''
214 print "osconnector: Adding a new tenant to VIM"
216 self
._reload
_connection
()
217 tenant
=self
.keystone
.tenants
.create(tenant_name
, tenant_description
)
218 #self.keystone.tenants.add_user(self.k_creds["username"], #role)
220 except ksExceptions
.ConnectionError
as e
:
221 error_value
=-vimconn
.HTTP_Bad_Request
222 error_text
= type(e
).__name
__ + ": "+ (str(e
) if len(e
.args
)==0 else str(e
.args
[0]))
223 except ksExceptions
.ClientException
as e
: #TODO remove
224 error_value
=-vimconn
.HTTP_Bad_Request
225 error_text
= type(e
).__name
__ + ": "+ (str(e
) if len(e
.args
)==0 else str(e
.args
[0]))
226 #TODO insert exception vimconn.HTTP_Unauthorized
227 #if reaching here is because an exception
229 print "new_tenant " + error_text
230 return error_value
, error_text
232 def delete_tenant(self
,tenant_id
):
233 '''Delete a tenant from openstack VIM'''
234 '''Returns the tenant identifier'''
236 print "osconnector: Deleting a tenant from VIM"
238 self
._reload
_connection
()
239 self
.keystone
.tenants
.delete(tenant_id
)
240 #self.keystone.tenants.add_user(self.k_creds["username"], #role)
242 except ksExceptions
.ConnectionError
as e
:
243 error_value
=-vimconn
.HTTP_Bad_Request
244 error_text
= type(e
).__name
__ + ": "+ (str(e
) if len(e
.args
)==0 else str(e
.args
[0]))
245 except ksExceptions
.ClientException
as e
: #TODO remove
246 error_value
=-vimconn
.HTTP_Bad_Request
247 error_text
= type(e
).__name
__ + ": "+ (str(e
) if len(e
.args
)==0 else str(e
.args
[0]))
248 #TODO insert exception vimconn.HTTP_Unauthorized
249 #if reaching here is because an exception
251 print "delete_tenant " + error_text
252 return error_value
, error_text
254 def __net_os2mano(self
, net_list_dict
):
255 '''Transform the net openstack format to mano format
256 net_list_dict can be a list of dict or a single dict'''
257 if type(net_list_dict
) is dict:
258 net_list_
=(net_list_dict
,)
259 elif type(net_list_dict
) is list:
260 net_list_
=net_list_dict
262 raise TypeError("param net_list_dict must be a list or a dictionary")
263 for net
in net_list_
:
264 if net
.get('provider:network_type') == "vlan":
269 def new_tenant_network(self
,net_name
,net_type
,public
=False,cidr
=None,vlan
=None):
270 '''Adds a tenant network to VIM'''
271 '''Returns the network identifier'''
273 print "osconnector: Adding a new tenant network to VIM (tenant: " + str(self
.tenant_name
) + ", type: " + net_type
+ "): "+ net_name
275 self
._reload
_connection
()
276 network_dict
= {'name': net_name
, 'admin_state_up': True}
277 if net_type
=="data" or net_type
=="ptp":
278 if self
.config
.get('dataplane_physical_net') == None:
279 return -vimconn
.HTTP_Bad_Request
, "You must provide a 'dataplane_physical_net' at config value before creating sriov network "
281 network_dict
["provider:physical_network"] = self
.config
['dataplane_physical_net'] #"physnet_sriov" #TODO physical
282 network_dict
["provider:network_type"] = "vlan"
284 network_dict
["provider:network_type"] = vlan
285 network_dict
["shared"]=public
286 new_net
=self
.neutron
.create_network({'network':network_dict
})
288 #create fake subnetwork
290 cidr
="192.168.111.0/24"
291 subnet
={"name":net_name
+"-subnet",
292 "network_id": new_net
["network"]["id"],
296 self
.neutron
.create_subnet({"subnet": subnet
} )
297 return 1, new_net
["network"]["id"]
298 except neExceptions
.ConnectionFailed
as e
:
299 error_value
=-vimconn
.HTTP_Bad_Request
300 error_text
= type(e
).__name
__ + ": "+ (str(e
) if len(e
.args
)==0 else str(e
.args
[0]))
301 except (ksExceptions
.ClientException
, neExceptions
.NeutronException
) as e
:
302 error_value
=-vimconn
.HTTP_Bad_Request
303 error_text
= type(e
).__name
__ + ": "+ (str(e
) if len(e
.args
)==0 else str(e
.args
[0]))
304 #TODO insert exception vimconn.HTTP_Unauthorized
305 #if reaching here is because an exception
307 print "new_tenant_network " + error_text
308 return error_value
, error_text
310 def get_network_list(self
, filter_dict
={}):
311 '''Obtain tenant networks of VIM
317 admin_state_up: boolean
319 Returns the network list of dictionaries
322 print "osconnector.get_network_list(): Getting network from VIM (filter: " + str(filter_dict
) + "): "
324 self
._reload
_connection
()
325 net_dict
=self
.neutron
.list_networks(**filter_dict
)
326 net_list
=net_dict
["networks"]
327 self
.__net
_os
2mano
(net_list
)
329 except neClient
.exceptions
.ConnectionFailed
as e
:
330 error_value
=-vimconn
.HTTP_Bad_Request
331 error_text
= type(e
).__name
__ + ": "+ (str(e
) if len(e
.args
)==0 else str(e
.args
[0]))
332 except (ksExceptions
.ClientException
, neExceptions
.NeutronException
) as e
:
333 error_value
=-vimconn
.HTTP_Bad_Request
334 error_text
= type(e
).__name
__ + ": "+ (str(e
) if len(e
.args
)==0 else str(e
.args
[0]))
335 #TODO insert exception vimconn.HTTP_Unauthorized
336 #if reaching here is because an exception
338 print "get_network_list " + error_text
339 return error_value
, error_text
341 def get_tenant_network(self
, net_id
, tenant_id
=None):
342 '''Obtain tenant networks of VIM'''
343 '''Returns the network information from a network id'''
345 print "osconnector.get_tenant_network(): Getting tenant network %s from VIM" % net_id
346 filter_dict
={"id": net_id
}
348 filter_dict
["tenant_id"] = tenant_id
349 r
, net_list
= self
.get_network_list(filter_dict
)
353 return -vimconn
.HTTP_Not_Found
, "Network '%s' not found" % net_id
354 elif len(net_list
)>1:
355 return -vimconn
.HTTP_Conflict
, "Found more than one network with this criteria"
358 for subnet_id
in net
.get("subnets", () ):
360 subnet
= self
.neutron
.show_subnet(subnet_id
)
361 except Exception as e
:
362 error_text
= type(e
).__name
__ + ": "+ (str(e
) if len(e
.args
)==0 else str(e
.args
[0]))
363 print "osconnector.get_tenant_network(): Error getting subnet %s %s" % (net_id
, error_text
)
364 subnet
= {"id": subnet_id
, "fault": error_text
}
365 subnets
.append(subnet
)
366 net
["subnets"] = subnets
370 def delete_tenant_network(self
, net_id
):
371 '''Deletes a tenant network from VIM'''
372 '''Returns the network identifier'''
374 print "osconnector: Deleting a new tenant network from VIM tenant: " + str(self
.tenant_name
) + ", id: " + net_id
376 self
._reload
_connection
()
377 #delete VM ports attached to this networks before the network
378 ports
= self
.neutron
.list_ports(network_id
=net_id
)
379 for p
in ports
['ports']:
381 self
.neutron
.delete_port(p
["id"])
382 except Exception as e
:
383 print "Error deleting port: " + type(e
).__name
__ + ": "+ str(e
)
384 self
.neutron
.delete_network(net_id
)
386 except neClient
.exceptions
.ConnectionFailed
as e
:
387 error_value
=-vimconn
.HTTP_Bad_Request
388 error_text
= type(e
).__name
__ + ": "+ (str(e
) if len(e
.args
)==0 else str(e
.args
[0]))
389 except neExceptions
.NetworkNotFoundClient
as e
:
390 error_value
=-vimconn
.HTTP_Not_Found
391 error_text
= type(e
).__name
__ + ": "+ str(e
.args
[0])
392 except (ksExceptions
.ClientException
, neExceptions
.NeutronException
) as e
:
393 error_value
=-vimconn
.HTTP_Bad_Request
394 error_text
= type(e
).__name
__ + ": "+ (str(e
) if len(e
.args
)==0 else str(e
.args
[0]))
395 #TODO insert exception vimconn.HTTP_Unauthorized
396 #if reaching here is because an exception
398 print "delete_tenant_network " + error_text
399 return error_value
, error_text
401 def get_tenant_flavor(self
, flavor_id
):
402 '''Obtain flavor details from the VIM
403 Returns the flavor dict details
405 print "VIMConnector: Getting flavor from VIM"
407 self
._reload
_connection
()
408 flavor
= self
.nova
.flavors
.find(id=flavor_id
)
409 #TODO parse input and translate to VIM format (openmano_schemas.new_vminstance_response_schema)
410 return 1, {"flavor": flavor
.to_dict()}
411 except nvExceptions
.NotFound
as e
:
412 error_value
=-vimconn
.HTTP_Not_Found
413 error_text
= "flavor instance %s not found" % flavor_id
414 except (ksExceptions
.ClientException
, nvExceptions
.ClientException
) as e
:
415 error_value
=-vimconn
.HTTP_Bad_Request
416 error_text
= type(e
).__name
__ + ": "+ (str(e
) if len(e
.args
)==0 else str(e
.args
[0]))
417 #TODO insert exception vimconn.HTTP_Unauthorized
418 #if reaching here is because an exception
420 print "get_tenant_flavor " + error_text
421 return error_value
, error_text
423 def new_tenant_flavor(self
, flavor_dict
, change_name_if_used
=True):
424 '''Adds a tenant flavor to openstack VIM
425 if change_name_if_used is True, it will change name in case of conflict
426 Returns the flavor identifier
430 name
=flavor_dict
['name']
434 self
._reload
_connection
()
435 if change_name_if_used
:
438 fl
=self
.nova
.flavors
.list()
440 fl_names
.append(f
.name
)
441 while name
in fl_names
:
443 name
= flavor_dict
['name']+"-" + str(name_suffix
)
445 ram
= flavor_dict
.get('ram',64)
446 vcpus
= flavor_dict
.get('vcpus',1)
449 extended
=flavor_dict
.get("extended")
451 numas
=extended
.get("numas")
453 numa_nodes
= len(numas
)
455 return -1, "Can not add flavor with more than one numa"
456 numa_properties
= {"hw:numa_nodes":str(numa_nodes
)}
457 numa_properties
["hw:mem_page_size"] = "large"
458 numa_properties
["hw:cpu_policy"] = "dedicated"
459 numa_properties
["hw:numa_mempolicy"] = "strict"
461 #overwrite ram and vcpus
462 ram
= numa
['memory']*1024
463 if 'paired-threads' in numa
:
464 vcpus
= numa
['paired-threads']*2
465 numa_properties
["hw:cpu_threads_policy"] = "prefer"
466 elif 'cores' in numa
:
467 vcpus
= numa
['cores']
468 #numa_properties["hw:cpu_threads_policy"] = "prefer"
469 elif 'threads' in numa
:
470 vcpus
= numa
['threads']
471 numa_properties
["hw:cpu_policy"] = "isolated"
472 for interface
in numa
.get("interfaces",() ):
473 if interface
["dedicated"]=="yes":
474 error_value
=-vimconn
.HTTP_Bad_Request
475 error_text
= "Passthrough interfaces are not supported for the openstack connector"
477 #TODO, add the key 'pci_passthrough:alias"="<label at config>:<number ifaces>"' when a way to connect it is available
480 new_flavor
=self
.nova
.flavors
.create(name
,
483 flavor_dict
.get('disk',1),
484 is_public
=flavor_dict
.get('is_public', True)
488 new_flavor
.set_keys(numa_properties
)
489 return 1, new_flavor
.id
490 except nvExceptions
.Conflict
as e
:
491 error_value
=-vimconn
.HTTP_Conflict
493 if change_name_if_used
:
496 #except nvExceptions.BadRequest as e:
497 except (ksExceptions
.ClientException
, nvExceptions
.ClientException
, ConnectionError
) as e
:
498 error_value
=-vimconn
.HTTP_Bad_Request
499 error_text
= type(e
).__name
__ + ": "+ (str(e
) if len(e
.args
)==0 else str(e
.args
[0]))
501 #TODO insert exception vimconn.HTTP_Unauthorized
502 #if reaching here is because an exception
504 print "new_tenant_flavor " + error_text
505 return error_value
, error_text
507 def delete_tenant_flavor(self
,flavor_id
):
508 '''Deletes a tenant flavor from openstack VIM
509 Returns >0,id if ok; or <0,error_text if error
515 self
._reload
_connection
()
516 self
.nova
.flavors
.delete(flavor_id
)
518 except nvExceptions
.NotFound
as e
:
519 error_value
= -vimconn
.HTTP_Not_Found
520 error_text
= "flavor '%s' not found" % flavor_id
522 #except nvExceptions.BadRequest as e:
523 except (ksExceptions
.ClientException
, nvExceptions
.ClientException
) as e
:
524 error_value
=-vimconn
.HTTP_Bad_Request
525 error_text
= type(e
).__name
__ + ": "+ (str(e
) if len(e
.args
)==0 else str(e
.args
[0]))
528 print "delete_tenant_flavor " + error_text
529 #if reaching here is because an exception
530 return error_value
, error_text
532 def new_tenant_image(self
,image_dict
):
534 Adds a tenant image to VIM
535 if change_name_if_used is True, it will change name in case of conflict
537 >1, image-id if the image is created
538 <0, message if there is an error
541 #using version 1 of glance client
542 glancev1
= gl1Client
.Client('1',self
.glance_endpoint
, token
=self
.keystone
.auth_token
, **self
.k_creds
) #TODO check k_creds vs n_creds
546 self
._reload
_connection
()
547 #determine format http://docs.openstack.org/developer/glance/formats.html
548 if "disk_format" in image_dict
:
549 disk_format
=image_dict
["disk_format"]
550 else: #autodiscover base on extention
551 if image_dict
['location'][-6:]==".qcow2":
553 elif image_dict
['location'][-4:]==".vhd":
555 elif image_dict
['location'][-5:]==".vmdk":
557 elif image_dict
['location'][-4:]==".vdi":
559 elif image_dict
['location'][-4:]==".iso":
561 elif image_dict
['location'][-4:]==".aki":
563 elif image_dict
['location'][-4:]==".ari":
565 elif image_dict
['location'][-4:]==".ami":
569 print "new_tenant_image: '%s' loading from '%s'" % (image_dict
['name'], image_dict
['location'])
570 if image_dict
['location'][0:4]=="http":
571 new_image
= glancev1
.images
.create(name
=image_dict
['name'], is_public
=image_dict
.get('public',"yes")=="yes",
572 container_format
="bare", location
=image_dict
['location'], disk_format
=disk_format
)
574 with
open(image_dict
['location']) as fimage
:
575 new_image
= glancev1
.images
.create(name
=image_dict
['name'], is_public
=image_dict
.get('public',"yes")=="yes",
576 container_format
="bare", data
=fimage
, disk_format
=disk_format
)
577 #insert metadata. We cannot use 'new_image.properties.setdefault'
578 #because nova and glance are "INDEPENDENT" and we are using nova for reading metadata
579 new_image_nova
=self
.nova
.images
.find(id=new_image
.id)
580 new_image_nova
.metadata
.setdefault('location',image_dict
['location'])
581 metadata_to_load
= image_dict
.get('metadata')
583 for k
,v
in yaml
.load(metadata_to_load
).iteritems():
584 new_image_nova
.metadata
.setdefault(k
,v
)
585 return 1, new_image
.id
586 except nvExceptions
.Conflict
as e
:
587 error_value
=-vimconn
.HTTP_Conflict
588 error_text
= type(e
).__name
__ + ": "+ (str(e
) if len(e
.args
)==0 else str(e
.args
[0]))
590 except (HTTPException
, gl1Exceptions
.HTTPException
, gl1Exceptions
.CommunicationError
) as e
:
591 error_value
=-vimconn
.HTTP_Bad_Request
592 error_text
= type(e
).__name
__ + ": "+ (str(e
) if len(e
.args
)==0 else str(e
.args
[0]))
594 except IOError as e
: #can not open the file
595 error_value
=-vimconn
.HTTP_Bad_Request
596 error_text
= type(e
).__name
__ + ": "+ (str(e
) if len(e
.args
)==0 else str(e
.args
[0])) + " for " + image_dict
['location']
598 except (ksExceptions
.ClientException
, nvExceptions
.ClientException
) as e
:
599 error_value
=-vimconn
.HTTP_Bad_Request
600 error_text
= type(e
).__name
__ + ": "+ (str(e
) if len(e
.args
)==0 else str(e
.args
[0]))
602 #TODO insert exception vimconn.HTTP_Unauthorized
603 #if reaching here is because an exception
605 print "new_tenant_image " + error_text
606 return error_value
, error_text
608 def delete_tenant_image(self
, image_id
):
609 '''Deletes a tenant image from openstack VIM
610 Returns >0,id if ok; or <0,error_text if error
616 self
._reload
_connection
()
617 self
.nova
.images
.delete(image_id
)
619 except nvExceptions
.NotFound
as e
:
620 error_value
= -vimconn
.HTTP_Not_Found
621 error_text
= "flavor '%s' not found" % image_id
623 #except nvExceptions.BadRequest as e:
624 except (ksExceptions
.ClientException
, nvExceptions
.ClientException
) as e
: #TODO remove
625 error_value
=-vimconn
.HTTP_Bad_Request
626 error_text
= type(e
).__name
__ + ": "+ (str(e
) if len(e
.args
)==0 else str(e
.args
[0]))
629 print "delete_tenant_image " + error_text
630 #if reaching here is because an exception
631 return error_value
, error_text
633 def new_tenant_vminstance(self
,name
,description
,start
,image_id
,flavor_id
,net_list
):
634 '''Adds a VM instance to VIM
636 start: indicates if VM must start or boot in pause mode. Ignored
637 image_id,flavor_id: iamge and flavor uuid
638 net_list: list of interfaces, each one is a dictionary with:
640 net_id: network uuid to connect
641 vpci: virtual vcpi to assign, ignored because openstack lack #TODO
642 model: interface model, ignored #TODO
643 mac_address: used for SR-IOV ifaces #TODO for other types
644 use: 'data', 'bridge', 'mgmt'
645 type: 'virtual', 'PF', 'VF', 'VFnotShared'
646 vim_id: filled/added by this function
647 #TODO ip, security groups
648 Returns >=0, the instance identifier
652 print "osconnector: Creating VM into VIM"
653 print " image %s flavor %s nics=%s" %(image_id
, flavor_id
,net_list
)
657 self
._reload
_connection
()
660 if not net
.get("net_id"): #skip non connected iface
662 if net
["type"]=="virtual":
663 net_list_vim
.append({'net-id': net
["net_id"]})
665 metadata_vpci
[ net
["net_id"] ] = [[ net
["vpci"], "" ]]
666 elif net
["type"]=="PF":
667 print "new_tenant_vminstance: Warning, can not connect a passthrough interface "
668 #TODO insert this when openstack consider passthrough ports as openstack neutron ports
671 if "VF" not in metadata_vpci
:
672 metadata_vpci
["VF"]=[]
673 metadata_vpci
["VF"].append([ net
["vpci"], "" ])
675 "network_id": net
["net_id"],
676 "name": net
.get("name"),
677 "binding:vnic_type": "direct",
678 "admin_state_up": True
680 if not port_dict
["name"]:
681 port_dict
["name"] = name
682 if net
.get("mac_address"):
683 port_dict
["mac_address"]=net
["mac_address"]
684 #TODO: manage having SRIOV without vlan tag
685 #if net["type"] == "VFnotShared"
686 # port_dict["vlan"]=0
687 new_port
= self
.neutron
.create_port({"port": port_dict
})
688 net
["mac_adress"] = new_port
["port"]["mac_address"]
689 net
["vim_id"] = new_port
["port"]["id"]
690 net
["ip"] = new_port
["port"].get("fixed_ips",[{}])[0].get("ip_address")
691 net_list_vim
.append({"port-id": new_port
["port"]["id"]})
693 metadata
= {"pci_assignement": json
.dumps(metadata_vpci
)}
695 print "name '%s' image_id '%s'flavor_id '%s' net_list_vim '%s' description '%s' metadata %s"\
696 % (name
, image_id
, flavor_id
, str(net_list_vim
), description
, str(metadata
))
698 security_groups
= self
.config
.get('security_groups')
699 if type(security_groups
) is str:
700 security_groups
= ( security_groups
, )
701 server
= self
.nova
.servers
.create(name
, image_id
, flavor_id
, nics
=net_list_vim
, meta
=metadata
,
702 security_groups
= security_groups
,
703 availability_zone
= self
.config
.get('availability_zone'),
704 key_name
= self
.config
.get('keypair'),
705 ) #, description=description)
708 print "DONE :-)", server
710 # #TODO server.add_floating_ip("10.95.87.209")
711 # #To look for a free floating_ip
712 # free_floating_ip = None
713 # for floating_ip in self.neutron.list_floatingips().get("floatingips", () ):
714 # if not floating_ip["port_id"]:
715 # free_floating_ip = floating_ip["floating_ip_address"]
717 # if free_floating_ip:
718 # server.add_floating_ip(free_floating_ip)
722 # except nvExceptions.NotFound as e:
723 # error_value=-vimconn.HTTP_Not_Found
724 # error_text= "vm instance %s not found" % vm_id
725 except (ksExceptions
.ClientException
, nvExceptions
.ClientException
, ConnectionError
, TypeError) as e
:
726 error_value
=-vimconn
.HTTP_Bad_Request
727 error_text
= type(e
).__name
__ + ": "+ (str(e
) if len(e
.args
)==0 else str(e
.args
[0]))
728 except neClient
.exceptions
.ConnectionFailed
as e
:
729 error_value
=-vimconn
.HTTP_Bad_Request
730 error_text
= type(e
).__name
__ + ": "+ (str(e
) if len(e
.args
)==0 else str(e
.args
[0]))
731 #TODO insert exception vimconn.HTTP_Unauthorized
732 #if reaching here is because an exception
734 print "new_tenant_vminstance Exception",e
, error_text
735 return error_value
, error_text
737 def get_tenant_vminstance(self
,vm_id
):
738 '''Returns the VM instance information from VIM'''
740 print "osconnector: Getting VM from VIM"
742 self
._reload
_connection
()
743 server
= self
.nova
.servers
.find(id=vm_id
)
744 #TODO parse input and translate to VIM format (openmano_schemas.new_vminstance_response_schema)
745 return 1, {"server": server
.to_dict()}
746 except nvExceptions
.NotFound
as e
:
747 error_value
=-vimconn
.HTTP_Not_Found
748 error_text
= "vm instance %s not found" % vm_id
749 except (ksExceptions
.ClientException
, nvExceptions
.ClientException
) as e
:
750 error_value
=-vimconn
.HTTP_Bad_Request
751 error_text
= type(e
).__name
__ + ": "+ (str(e
) if len(e
.args
)==0 else str(e
.args
[0]))
752 #TODO insert exception vimconn.HTTP_Unauthorized
753 #if reaching here is because an exception
755 print "get_tenant_vminstance " + error_text
756 return error_value
, error_text
758 def get_tenant_vminstance_console(self
,vm_id
, console_type
="vnc"):
760 Get a console for the virtual machine
762 vm_id: uuid of the VM
763 console_type, can be:
764 "novnc" (by default), "xvpvnc" for VNC types,
765 "rdp-html5" for RDP types, "spice-html5" for SPICE types
766 Returns <0, text on error, for example not available
767 1, URL/text with the console parameters
770 print "osconnector: Getting VM CONSOLE from VIM"
772 self
._reload
_connection
()
773 server
= self
.nova
.servers
.find(id=vm_id
)
774 if console_type
== None or console_type
== "novnc":
775 console_dict
= server
.get_vnc_console("novnc")
776 elif console_type
== "xvpvnc":
777 console_dict
= server
.get_vnc_console(console_type
)
778 elif console_type
== "rdp-html5":
779 console_dict
= server
.get_rdp_console(console_type
)
780 elif console_type
== "spice-html5":
781 console_dict
= server
.get_spice_console(console_type
)
783 return -vimconn
.HTTP_Bad_Request
, "console type '%s' not allowed" % console_type
785 console_dict1
= console_dict
.get("console")
787 console_url
= console_dict1
.get("url")
790 protocol_index
= console_url
.find("//")
791 suffix_index
= console_url
[protocol_index
+2:].find("/") + protocol_index
+2
792 port_index
= console_url
[protocol_index
+2:suffix_index
].find(":") + protocol_index
+2
793 if protocol_index
< 0 or port_index
<0 or suffix_index
<0:
794 return -vimconn
.HTTP_Internal_Server_Error
, "Unexpected response from VIM"
795 console_dict
={"protocol": console_url
[0:protocol_index
],
796 "server": console_url
[protocol_index
+2:port_index
],
797 "port": console_url
[port_index
:suffix_index
],
798 "suffix": console_url
[suffix_index
+1:]
801 return 1, console_dict
802 return -vimconn
.HTTP_Internal_Server_Error
, "Unexpected response from VIM"
804 #TODO parse input and translate to VIM format (openmano_schemas.new_vminstance_response_schema)
805 return 1, {"server": server
.to_dict()}
806 except nvExceptions
.NotFound
as e
:
807 error_value
=-vimconn
.HTTP_Not_Found
808 error_text
= "vm instance %s not found" % vm_id
809 except (ksExceptions
.ClientException
, nvExceptions
.ClientException
, nvExceptions
.BadRequest
) as e
:
810 error_value
=-vimconn
.HTTP_Bad_Request
811 error_text
= type(e
).__name
__ + ": "+ (str(e
) if len(e
.args
)==0 else str(e
.args
[0]))
812 #TODO insert exception vimconn.HTTP_Unauthorized
813 #if reaching here is because an exception
815 print "get_tenant_vminstance " + error_text
816 return error_value
, error_text
819 def delete_tenant_vminstance(self
, vm_id
):
820 '''Removes a VM instance from VIM
821 Returns >0, the instance identifier
825 print "osconnector: Getting VM from VIM"
827 self
._reload
_connection
()
828 #delete VM ports attached to this networks before the virtual machine
829 ports
= self
.neutron
.list_ports(device_id
=vm_id
)
830 for p
in ports
['ports']:
832 self
.neutron
.delete_port(p
["id"])
833 except Exception as e
:
834 print "Error deleting port: " + type(e
).__name
__ + ": "+ str(e
)
835 self
.nova
.servers
.delete(vm_id
)
837 except nvExceptions
.NotFound
as e
:
838 error_value
=-vimconn
.HTTP_Not_Found
839 error_text
= (str(e
) if len(e
.args
)==0 else str(e
.args
[0]))
840 except (ksExceptions
.ClientException
, nvExceptions
.ClientException
) as e
:
841 error_value
=-vimconn
.HTTP_Bad_Request
842 error_text
= type(e
).__name
__ + ": "+ (str(e
) if len(e
.args
)==0 else str(e
.args
[0]))
843 #TODO insert exception vimconn.HTTP_Unauthorized
844 #if reaching here is because an exception
846 print "get_tenant_vminstance " + error_text
847 return error_value
, error_text
849 def refresh_tenant_vms_and_nets(self
, vmDict
, netDict
):
850 '''Refreshes the status of the dictionaries of VM instances and nets passed as arguments. It modifies the dictionaries
852 - result: 0 if all elements could be refreshed (even if its status didn't change)
853 n>0, the number of elements that couldn't be refreshed,
854 <0 if error (foreseen)
855 - error_msg: text with reference to possible errors
860 nets_unrefreshed
= []
862 print "osconnector refresh_tenant_vms and nets: Getting tenant VM instance information from VIM"
864 vmDict
[vm_id
] = {'error_msg':None, 'vim_info':None}
865 r
,c
= self
.get_tenant_vminstance(vm_id
)
867 print "osconnector refresh_tenant_vm. Error getting vm_id '%s' status: %s" % (vm_id
, c
)
868 if r
==-vimconn
.HTTP_Not_Found
:
869 vmDict
[vm_id
]['status'] = "DELETED"
871 vmDict
[vm_id
]['status'] = "VIM_ERROR"
872 vmDict
[vm_id
]['error_msg'] = c
873 vms_unrefreshed
.append(vm_id
)
876 vmDict
[vm_id
]['status'] = vmStatus2manoFormat
[ c
['server']['status'] ]
877 vmDict
[vm_id
]['vim_info'] = yaml
.safe_dump(c
['server'])
878 vmDict
[vm_id
]["interfaces"] = []
879 if c
['server'].get('fault'):
880 vmDict
[vm_id
]['error_msg'] = str(c
['server']['fault'])
883 self
._reload
_connection
()
884 port_dict
=self
.neutron
.list_ports(device_id
=vm_id
)
885 for port
in port_dict
["ports"]:
887 interface
['vim_info'] = yaml
.safe_dump(port
)
888 interface
["mac_address"] = port
.get("mac_address")
889 interface
["vim_net_id"] = port
["network_id"]
890 interface
["vim_interface_id"] = port
["id"]
892 #look for floating ip address
893 floating_ip_dict
= self
.neutron
.list_floatingips(port_id
=port
["id"])
894 if floating_ip_dict
.get("floatingips"):
895 ips
.append(floating_ip_dict
["floatingips"][0].get("floating_ip_address") )
897 for subnet
in port
["fixed_ips"]:
898 ips
.append(subnet
["ip_address"])
899 interface
["ip_address"] = ";".join(ips
)
900 vmDict
[vm_id
]["interfaces"].append(interface
)
901 except Exception as e
:
902 print type(e
).__name
__ + ": "+ (str(e
) if len(e
.args
)==0 else str(e
.args
[0]))
905 #error message at server.fault["message"]
906 except KeyError as e
:
907 print "osconnector refresh_tenant_elements KeyError %s getting vm_id '%s' status %s" % (str(e
), vm_id
, c
['server']['status'])
908 vmDict
[vm_id
]['status'] = "VIM_ERROR"
909 vmDict
[vm_id
]['error_msg'] = str(e
)
910 vms_unrefreshed
.append(vm_id
)
912 #print "VMs refreshed: %s" % str(vms_refreshed)
913 for net_id
in netDict
:
914 netDict
[net_id
]={'error_msg':None, 'vim_info':None}
915 r
,c
= self
.get_tenant_network(net_id
)
917 print "osconnector refresh_tenant_network. Error getting net_id '%s' status: %s" % (net_id
, c
)
918 if r
==-vimconn
.HTTP_Not_Found
:
919 netDict
[net_id
]['status'] = "DELETED" #TODO check exit status
921 netDict
[vm_id
]['status'] = "VIM_ERROR"
922 netDict
[vm_id
]['error_msg'] = c
923 nets_unrefreshed
.append(net_id
)
926 netDict
[net_id
]['status'] = netStatus2manoFormat
[ c
['status'] ]
927 if c
['status'] == "ACIVE" and not c
['admin_state_up']:
928 netDict
[net_id
]['status'] = 'DOWN'
929 netDict
[net_id
]['vim_info'] = yaml
.safe_dump(c
)
930 if c
.get('fault'): #TODO
931 netDict
[net_id
]['error_msg'] = str(c
['fault'])
932 except KeyError as e
:
933 print "osconnector refresh_tenant_elements KeyError %s getting vm_id '%s' status %s" % (str(e
), vm_id
, c
['network']['status'])
934 netDict
[net_id
]['status'] = "VIM_ERROR"
935 netDict
[net_id
]['error_msg'] = str(e
)
936 nets_unrefreshed
.append(net_id
)
938 #print "Nets refreshed: %s" % str(nets_refreshed)
941 if len(vms_unrefreshed
)+len(nets_unrefreshed
)>0:
942 error_msg
+= "VMs unrefreshed: " + str(vms_unrefreshed
) + "; nets unrefreshed: " + str(nets_unrefreshed
)
945 #return len(vms_unrefreshed)+len(nets_unrefreshed), error_msg, vms_refreshed, nets_refreshed
946 return len(vms_unrefreshed
)+len(nets_unrefreshed
), error_msg
948 def action_tenant_vminstance(self
, vm_id
, action_dict
):
949 '''Send and action over a VM instance from VIM
950 Returns the status'''
952 print "osconnector: Action over VM instance from VIM " + vm_id
954 self
._reload
_connection
()
955 server
= self
.nova
.servers
.find(id=vm_id
)
956 if "start" in action_dict
:
957 if action_dict
["start"]=="rebuild":
960 if server
.status
=="PAUSED":
962 elif server
.status
=="SUSPENDED":
964 elif server
.status
=="SHUTOFF":
966 elif "pause" in action_dict
:
968 elif "resume" in action_dict
:
970 elif "shutoff" in action_dict
or "shutdown" in action_dict
:
972 elif "forceOff" in action_dict
:
974 elif "terminate" in action_dict
:
976 elif "createImage" in action_dict
:
977 server
.create_image()
979 #"description":description_schema,
981 #"metadata":metadata_schema,
982 #"imageRef": id_schema,
983 #"disk": {"oneOf":[{"type": "null"}, {"type":"string"}] },
984 elif "rebuild" in action_dict
:
985 server
.rebuild(server
.image
['id'])
986 elif "reboot" in action_dict
:
987 server
.reboot() #reboot_type='SOFT'
988 elif "console" in action_dict
:
989 console_type
= action_dict
["console"]
990 if console_type
== None or console_type
== "novnc":
991 console_dict
= server
.get_vnc_console("novnc")
992 elif console_type
== "xvpvnc":
993 console_dict
= server
.get_vnc_console(console_type
)
994 elif console_type
== "rdp-html5":
995 console_dict
= server
.get_rdp_console(console_type
)
996 elif console_type
== "spice-html5":
997 console_dict
= server
.get_spice_console(console_type
)
999 return -vimconn
.HTTP_Bad_Request
, "console type '%s' not allowed" % console_type
1002 console_url
= console_dict
["console"]["url"]
1004 protocol_index
= console_url
.find("//")
1005 suffix_index
= console_url
[protocol_index
+2:].find("/") + protocol_index
+2
1006 port_index
= console_url
[protocol_index
+2:suffix_index
].find(":") + protocol_index
+2
1007 if protocol_index
< 0 or port_index
<0 or suffix_index
<0:
1008 print "action_tenant_vminstance, console: response", str(console_dict
)
1009 return -vimconn
.HTTP_Internal_Server_Error
, "Unexpected response from VIM"
1010 console_dict2
={"protocol": console_url
[0:protocol_index
],
1011 "server": console_url
[protocol_index
+2 : port_index
],
1012 "port": int(console_url
[port_index
+1 : suffix_index
]),
1013 "suffix": console_url
[suffix_index
+1:]
1016 return 1, console_dict2
1018 print "action_tenant_vminstance, console: response", str(console_dict
)
1019 return -vimconn
.HTTP_Internal_Server_Error
, "Unexpected response from VIM"
1022 except nvExceptions
.NotFound
as e
:
1023 error_value
=-vimconn
.HTTP_Not_Found
1024 error_text
= (str(e
) if len(e
.args
)==0 else str(e
.args
[0]))
1025 except (ksExceptions
.ClientException
, nvExceptions
.ClientException
) as e
:
1026 error_value
=-vimconn
.HTTP_Bad_Request
1027 error_text
= type(e
).__name
__ + ": "+ (str(e
) if len(e
.args
)==0 else str(e
.args
[0]))
1028 #TODO insert exception vimconn.HTTP_Unauthorized
1029 #if reaching here is because an exception
1031 print "action_tenant_vminstance " + error_text
1032 return error_value
, error_text
1034 def get_hosts_info(self
):
1035 '''Get the information of deployed hosts
1036 Returns the hosts content'''
1038 print "osconnector: Getting Host info from VIM"
1041 self
._reload
_connection
()
1042 hypervisors
= self
.nova
.hypervisors
.list()
1043 for hype
in hypervisors
:
1044 h_list
.append( hype
.to_dict() )
1045 return 1, {"hosts":h_list
}
1046 except nvExceptions
.NotFound
as e
:
1047 error_value
=-vimconn
.HTTP_Not_Found
1048 error_text
= (str(e
) if len(e
.args
)==0 else str(e
.args
[0]))
1049 except (ksExceptions
.ClientException
, nvExceptions
.ClientException
) as e
:
1050 error_value
=-vimconn
.HTTP_Bad_Request
1051 error_text
= type(e
).__name
__ + ": "+ (str(e
) if len(e
.args
)==0 else str(e
.args
[0]))
1052 #TODO insert exception vimconn.HTTP_Unauthorized
1053 #if reaching here is because an exception
1055 print "get_hosts_info " + error_text
1056 return error_value
, error_text
1058 def get_hosts(self
, vim_tenant
):
1059 '''Get the hosts and deployed instances
1060 Returns the hosts content'''
1061 r
, hype_dict
= self
.get_hosts_info()
1064 hypervisors
= hype_dict
["hosts"]
1066 servers
= self
.nova
.servers
.list()
1067 for hype
in hypervisors
:
1068 for server
in servers
:
1069 if server
.to_dict()['OS-EXT-SRV-ATTR:hypervisor_hostname']==hype
['hypervisor_hostname']:
1071 hype
['vm'].append(server
.id)
1073 hype
['vm'] = [server
.id]
1075 except nvExceptions
.NotFound
as e
:
1076 error_value
=-vimconn
.HTTP_Not_Found
1077 error_text
= (str(e
) if len(e
.args
)==0 else str(e
.args
[0]))
1078 except (ksExceptions
.ClientException
, nvExceptions
.ClientException
) as e
:
1079 error_value
=-vimconn
.HTTP_Bad_Request
1080 error_text
= type(e
).__name
__ + ": "+ (str(e
) if len(e
.args
)==0 else str(e
.args
[0]))
1081 #TODO insert exception vimconn.HTTP_Unauthorized
1082 #if reaching here is because an exception
1084 print "get_hosts " + error_text
1085 return error_value
, error_text
1087 def get_image_id_from_path(self
, path
):
1088 '''Get the image id from image path in the VIM database'''
1090 0,"Image not found" if there are no images with that path
1091 1,image-id if there is one image with that path
1092 <0,message if there was an error (Image not found, error contacting VIM, more than 1 image with that path, etc.)
1095 self
._reload
_connection
()
1096 images
= self
.nova
.images
.list()
1097 for image
in images
:
1098 if image
.metadata
.get("location")==path
:
1100 return 0, "image with location '%s' not found" % path
1101 except (ksExceptions
.ClientException
, nvExceptions
.ClientException
) as e
: #TODO remove
1102 error_value
=-vimconn
.HTTP_Bad_Request
1103 error_text
= type(e
).__name
__ + ": "+ (str(e
) if len(e
.args
)==0 else str(e
.args
[0]))
1105 print "get_image_id_from_path " + error_text
1106 #if reaching here is because an exception
1107 return error_value
, error_text