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, Pablo Montes, xFlow Research"
28 __date__
="$22-jun-2014 11:19:29$"
39 from novaclient
import client
as nClient_v2
, exceptions
as nvExceptions
, api_versions
as APIVersion
40 import keystoneclient
.v2_0
.client
as ksClient_v2
41 from novaclient
.v2
.client
import Client
as nClient
42 import keystoneclient
.v3
.client
as ksClient
43 import keystoneclient
.exceptions
as ksExceptions
44 import glanceclient
.v2
.client
as glClient
45 import glanceclient
.client
as gl1Client
46 import glanceclient
.exc
as gl1Exceptions
47 import cinderclient
.v2
.client
as cClient_v2
48 from httplib
import HTTPException
49 from neutronclient
.neutron
import client
as neClient_v2
50 from neutronclient
.v2_0
import client
as neClient
51 from neutronclient
.common
import exceptions
as neExceptions
52 from requests
.exceptions
import ConnectionError
54 '''contain the openstack virtual machine status to openmano status'''
55 vmStatus2manoFormat
={'ACTIVE':'ACTIVE',
57 'SUSPENDED': 'SUSPENDED',
60 'ERROR':'ERROR','DELETED':'DELETED'
62 netStatus2manoFormat
={'ACTIVE':'ACTIVE','PAUSED':'PAUSED','INACTIVE':'INACTIVE','BUILD':'BUILD','ERROR':'ERROR','DELETED':'DELETED'
65 #global var to have a timeout creating and deleting volumes
69 class vimconnector(vimconn
.vimconnector
):
70 def __init__(self
, uuid
, name
, tenant_id
, tenant_name
, url
, url_admin
=None, user
=None, passwd
=None, log_level
=None, config
={}):
71 '''using common constructor parameters. In this case
72 'url' is the keystone authorization url,
73 'url_admin' is not use
75 self
.osc_api_version
= 'v2.0'
76 if config
.get('APIversion') == 'v3.3':
77 self
.osc_api_version
= 'v3.3'
78 vimconn
.vimconnector
.__init
__(self
, uuid
, name
, tenant_id
, tenant_name
, url
, url_admin
, user
, passwd
, log_level
, config
)
82 if self
.config
.get("insecure"):
83 self
.k_creds
["insecure"] = True
84 self
.n_creds
["insecure"] = True
86 raise TypeError, 'url param can not be NoneType'
87 self
.k_creds
['auth_url'] = url
88 self
.n_creds
['auth_url'] = url
90 self
.k_creds
['tenant_name'] = tenant_name
91 self
.n_creds
['project_id'] = tenant_name
93 self
.k_creds
['tenant_id'] = tenant_id
94 self
.n_creds
['tenant_id'] = tenant_id
96 self
.k_creds
['username'] = user
97 self
.n_creds
['username'] = user
99 self
.k_creds
['password'] = passwd
100 self
.n_creds
['api_key'] = passwd
101 if self
.osc_api_version
== 'v3.3':
102 self
.k_creds
['project_name'] = tenant_name
103 self
.k_creds
['project_id'] = tenant_id
104 if config
.get('region_name'):
105 self
.k_creds
['region_name'] = config
.get('region_name')
106 self
.n_creds
['region_name'] = config
.get('region_name')
108 self
.reload_client
= True
109 self
.logger
= logging
.getLogger('openmano.vim.openstack')
111 self
.logger
.setLevel( getattr(logging
, log_level
) )
113 def __setitem__(self
,index
, value
):
114 '''Set individuals parameters
115 Throw TypeError, KeyError
117 if index
=='tenant_id':
118 self
.reload_client
=True
119 self
.tenant_id
= value
120 if self
.osc_api_version
== 'v3.3':
122 self
.k_creds
['project_id'] = value
123 self
.n_creds
['project_id'] = value
125 del self
.k_creds
['project_id']
126 del self
.n_creds
['project_id']
129 self
.k_creds
['tenant_id'] = value
130 self
.n_creds
['tenant_id'] = value
132 del self
.k_creds
['tenant_id']
133 del self
.n_creds
['tenant_id']
134 elif index
=='tenant_name':
135 self
.reload_client
=True
136 self
.tenant_name
= value
137 if self
.osc_api_version
== 'v3.3':
139 self
.k_creds
['project_name'] = value
140 self
.n_creds
['project_name'] = value
142 del self
.k_creds
['project_name']
143 del self
.n_creds
['project_name']
146 self
.k_creds
['tenant_name'] = value
147 self
.n_creds
['project_id'] = value
149 del self
.k_creds
['tenant_name']
150 del self
.n_creds
['project_id']
152 self
.reload_client
=True
155 self
.k_creds
['username'] = value
156 self
.n_creds
['username'] = value
158 del self
.k_creds
['username']
159 del self
.n_creds
['username']
160 elif index
=='passwd':
161 self
.reload_client
=True
164 self
.k_creds
['password'] = value
165 self
.n_creds
['api_key'] = value
167 del self
.k_creds
['password']
168 del self
.n_creds
['api_key']
170 self
.reload_client
=True
173 self
.k_creds
['auth_url'] = value
174 self
.n_creds
['auth_url'] = value
176 raise TypeError, 'url param can not be NoneType'
178 vimconn
.vimconnector
.__setitem
__(self
,index
, value
)
180 def _reload_connection(self
):
181 '''Called before any operation, it check if credentials has changed
182 Throw keystoneclient.apiclient.exceptions.AuthorizationFailure
184 #TODO control the timing and possible token timeout, but it seams that python client does this task for us :-)
185 if self
.reload_client
:
187 if len(self
.n_creds
) <4:
188 raise ksExceptions
.ClientException("Not enough parameters to connect to openstack")
189 if self
.osc_api_version
== 'v3.3':
190 self
.nova
= nClient(APIVersion(version_str
='2'), **self
.n_creds
)
191 #TODO To be updated for v3
192 #self.cinder = cClient.Client(**self.n_creds)
193 self
.keystone
= ksClient
.Client(**self
.k_creds
)
194 self
.ne_endpoint
=self
.keystone
.service_catalog
.url_for(service_type
='network', endpoint_type
='publicURL')
195 self
.neutron
= neClient
.Client(APIVersion(version_str
='2'), endpoint_url
=self
.ne_endpoint
, token
=self
.keystone
.auth_token
, **self
.k_creds
)
197 self
.nova
= nClient_v2
.Client('2', **self
.n_creds
)
198 self
.cinder
= cClient_v2
.Client(**self
.n_creds
)
199 self
.keystone
= ksClient_v2
.Client(**self
.k_creds
)
200 self
.ne_endpoint
=self
.keystone
.service_catalog
.url_for(service_type
='network', endpoint_type
='publicURL')
201 self
.neutron
= neClient_v2
.Client('2.0', endpoint_url
=self
.ne_endpoint
, token
=self
.keystone
.auth_token
, **self
.k_creds
)
202 self
.glance_endpoint
= self
.keystone
.service_catalog
.url_for(service_type
='image', endpoint_type
='publicURL')
203 self
.glance
= glClient
.Client(self
.glance_endpoint
, token
=self
.keystone
.auth_token
, **self
.k_creds
) #TODO check k_creds vs n_creds
204 self
.reload_client
= False
206 def __net_os2mano(self
, net_list_dict
):
207 '''Transform the net openstack format to mano format
208 net_list_dict can be a list of dict or a single dict'''
209 if type(net_list_dict
) is dict:
210 net_list_
=(net_list_dict
,)
211 elif type(net_list_dict
) is list:
212 net_list_
=net_list_dict
214 raise TypeError("param net_list_dict must be a list or a dictionary")
215 for net
in net_list_
:
216 if net
.get('provider:network_type') == "vlan":
223 def _format_exception(self
, exception
):
224 '''Transform a keystone, nova, neutron exception into a vimconn exception'''
225 if isinstance(exception
, (HTTPException
, gl1Exceptions
.HTTPException
, gl1Exceptions
.CommunicationError
,
226 ConnectionError
, ksExceptions
.ConnectionError
, neExceptions
.ConnectionFailed
228 raise vimconn
.vimconnConnectionException(type(exception
).__name
__ + ": " + str(exception
))
229 elif isinstance(exception
, (nvExceptions
.ClientException
, ksExceptions
.ClientException
,
230 neExceptions
.NeutronException
, nvExceptions
.BadRequest
)):
231 raise vimconn
.vimconnUnexpectedResponse(type(exception
).__name
__ + ": " + str(exception
))
232 elif isinstance(exception
, (neExceptions
.NetworkNotFoundClient
, nvExceptions
.NotFound
)):
233 raise vimconn
.vimconnNotFoundException(type(exception
).__name
__ + ": " + str(exception
))
234 elif isinstance(exception
, nvExceptions
.Conflict
):
235 raise vimconn
.vimconnConflictException(type(exception
).__name
__ + ": " + str(exception
))
237 raise vimconn
.vimconnConnectionException(type(exception
).__name
__ + ": " + str(exception
))
239 def get_tenant_list(self
, filter_dict
={}):
240 '''Obtain tenants of VIM
241 filter_dict can contain the following keys:
242 name: filter by tenant name
243 id: filter by tenant uuid/id
245 Returns the tenant list of dictionaries: [{'name':'<name>, 'id':'<id>, ...}, ...]
247 self
.logger
.debug("Getting tenants from VIM filter: '%s'", str(filter_dict
))
249 self
._reload
_connection
()
250 if self
.osc_api_version
== 'v3.3':
251 project_class_list
=self
.keystone
.projects
.findall(**filter_dict
)
253 project_class_list
=self
.keystone
.tenants
.findall(**filter_dict
)
255 for project
in project_class_list
:
256 project_list
.append(project
.to_dict())
258 except (ksExceptions
.ConnectionError
, ksExceptions
.ClientException
, ConnectionError
) as e
:
259 self
._format
_exception
(e
)
261 def new_tenant(self
, tenant_name
, tenant_description
):
262 '''Adds a new tenant to openstack VIM. Returns the tenant identifier'''
263 self
.logger
.debug("Adding a new tenant name: %s", tenant_name
)
265 self
._reload
_connection
()
266 if self
.osc_api_version
== 'v3.3':
267 project
=self
.keystone
.projects
.create(tenant_name
, tenant_description
)
269 project
=self
.keystone
.tenants
.create(tenant_name
, tenant_description
)
271 except (ksExceptions
.ConnectionError
, ksExceptions
.ClientException
, ConnectionError
) as e
:
272 self
._format
_exception
(e
)
274 def delete_tenant(self
, tenant_id
):
275 '''Delete a tenant from openstack VIM. Returns the old tenant identifier'''
276 self
.logger
.debug("Deleting tenant %s from VIM", tenant_id
)
278 self
._reload
_connection
()
279 if self
.osc_api_version
== 'v3.3':
280 self
.keystone
.projects
.delete(tenant_id
)
282 self
.keystone
.tenants
.delete(tenant_id
)
284 except (ksExceptions
.ConnectionError
, ksExceptions
.ClientException
, ConnectionError
) as e
:
285 self
._format
_exception
(e
)
287 def new_network(self
,net_name
, net_type
, ip_profile
=None, shared
=False, vlan
=None):
288 '''Adds a tenant network to VIM. Returns the network identifier'''
289 self
.logger
.debug("Adding a new network to VIM name '%s', type '%s'", net_name
, net_type
)
290 #self.logger.debug(">>>>>>>>>>>>>>>>>> IP profile %s", str(ip_profile))
293 self
._reload
_connection
()
294 network_dict
= {'name': net_name
, 'admin_state_up': True}
295 if net_type
=="data" or net_type
=="ptp":
296 if self
.config
.get('dataplane_physical_net') == None:
297 raise vimconn
.vimconnConflictException("You must provide a 'dataplane_physical_net' at config value before creating sriov network")
298 network_dict
["provider:physical_network"] = self
.config
['dataplane_physical_net'] #"physnet_sriov" #TODO physical
299 network_dict
["provider:network_type"] = "vlan"
301 network_dict
["provider:network_type"] = vlan
302 network_dict
["shared"]=shared
303 new_net
=self
.neutron
.create_network({'network':network_dict
})
305 #create subnetwork, even if there is no profile
308 if 'subnet_address' not in ip_profile
:
309 #Fake subnet is required
310 subnet_rand
= random
.randint(0, 255)
311 ip_profile
['subnet_address'] = "192.168.{}.0/24".format(subnet_rand
)
312 if 'ip_version' not in ip_profile
:
313 ip_profile
['ip_version'] = "IPv4"
314 subnet
={"name":net_name
+"-subnet",
315 "network_id": new_net
["network"]["id"],
316 "ip_version": 4 if ip_profile
['ip_version']=="IPv4" else 6,
317 "cidr": ip_profile
['subnet_address']
319 if 'gateway_address' in ip_profile
:
320 subnet
['gateway_ip'] = ip_profile
['gateway_address']
321 if ip_profile
.get('dns_address'):
322 #TODO: manage dns_address as a list of addresses separated by commas
323 subnet
['dns_nameservers'] = []
324 subnet
['dns_nameservers'].append(ip_profile
['dns_address'])
325 if 'dhcp_enabled' in ip_profile
:
326 subnet
['enable_dhcp'] = False if ip_profile
['dhcp_enabled']=="false" else True
327 if 'dhcp_start_address' in ip_profile
:
328 subnet
['allocation_pools']=[]
329 subnet
['allocation_pools'].append(dict())
330 subnet
['allocation_pools'][0]['start'] = ip_profile
['dhcp_start_address']
331 if 'dhcp_count' in ip_profile
:
332 #parts = ip_profile['dhcp_start_address'].split('.')
333 #ip_int = (int(parts[0]) << 24) + (int(parts[1]) << 16) + (int(parts[2]) << 8) + int(parts[3])
334 ip_int
= int(netaddr
.IPAddress(ip_profile
['dhcp_start_address']))
335 ip_int
+= ip_profile
['dhcp_count'] - 1
336 ip_str
= str(netaddr
.IPAddress(ip_int
))
337 subnet
['allocation_pools'][0]['end'] = ip_str
338 #self.logger.debug(">>>>>>>>>>>>>>>>>> Subnet: %s", str(subnet))
339 self
.neutron
.create_subnet({"subnet": subnet
} )
340 return new_net
["network"]["id"]
341 except (neExceptions
.ConnectionFailed
, ksExceptions
.ClientException
, neExceptions
.NeutronException
, ConnectionError
) as e
:
343 self
.neutron
.delete_network(new_net
['network']['id'])
344 self
._format
_exception
(e
)
346 def get_network_list(self
, filter_dict
={}):
347 '''Obtain tenant networks of VIM
353 admin_state_up: boolean
355 Returns the network list of dictionaries
357 self
.logger
.debug("Getting network from VIM filter: '%s'", str(filter_dict
))
359 self
._reload
_connection
()
360 if self
.osc_api_version
== 'v3.3' and "tenant_id" in filter_dict
:
361 filter_dict
['project_id'] = filter_dict
.pop('tenant_id')
362 net_dict
=self
.neutron
.list_networks(**filter_dict
)
363 net_list
=net_dict
["networks"]
364 self
.__net
_os
2mano
(net_list
)
366 except (neExceptions
.ConnectionFailed
, ksExceptions
.ClientException
, neExceptions
.NeutronException
, ConnectionError
) as e
:
367 self
._format
_exception
(e
)
369 def get_network(self
, net_id
):
370 '''Obtain details of network from VIM
371 Returns the network information from a network id'''
372 self
.logger
.debug(" Getting tenant network %s from VIM", net_id
)
373 filter_dict
={"id": net_id
}
374 net_list
= self
.get_network_list(filter_dict
)
376 raise vimconn
.vimconnNotFoundException("Network '{}' not found".format(net_id
))
377 elif len(net_list
)>1:
378 raise vimconn
.vimconnConflictException("Found more than one network with this criteria")
381 for subnet_id
in net
.get("subnets", () ):
383 subnet
= self
.neutron
.show_subnet(subnet_id
)
384 except Exception as e
:
385 self
.logger
.error("osconnector.get_network(): Error getting subnet %s %s" % (net_id
, str(e
)))
386 subnet
= {"id": subnet_id
, "fault": str(e
)}
387 subnets
.append(subnet
)
388 net
["subnets"] = subnets
391 def delete_network(self
, net_id
):
392 '''Deletes a tenant network from VIM. Returns the old network identifier'''
393 self
.logger
.debug("Deleting network '%s' from VIM", net_id
)
395 self
._reload
_connection
()
396 #delete VM ports attached to this networks before the network
397 ports
= self
.neutron
.list_ports(network_id
=net_id
)
398 for p
in ports
['ports']:
400 self
.neutron
.delete_port(p
["id"])
401 except Exception as e
:
402 self
.logger
.error("Error deleting port %s: %s", p
["id"], str(e
))
403 self
.neutron
.delete_network(net_id
)
405 except (neExceptions
.ConnectionFailed
, neExceptions
.NetworkNotFoundClient
, neExceptions
.NeutronException
,
406 ksExceptions
.ClientException
, neExceptions
.NeutronException
, ConnectionError
) as e
:
407 self
._format
_exception
(e
)
409 def refresh_nets_status(self
, net_list
):
410 '''Get the status of the networks
411 Params: the list of network identifiers
412 Returns a dictionary with:
413 net_id: #VIM id of this network
414 status: #Mandatory. Text with one of:
415 # DELETED (not found at vim)
416 # VIM_ERROR (Cannot connect to VIM, VIM response error, ...)
417 # OTHER (Vim reported other status not understood)
418 # ERROR (VIM indicates an ERROR status)
419 # ACTIVE, INACTIVE, DOWN (admin down),
420 # BUILD (on building process)
422 error_msg: #Text with VIM error message, if any. Or the VIM connection ERROR
423 vim_info: #Text with plain information obtained from vim (yaml.safe_dump)
427 for net_id
in net_list
:
430 net_vim
= self
.get_network(net_id
)
431 if net_vim
['status'] in netStatus2manoFormat
:
432 net
["status"] = netStatus2manoFormat
[ net_vim
['status'] ]
434 net
["status"] = "OTHER"
435 net
["error_msg"] = "VIM status reported " + net_vim
['status']
437 if net
['status'] == "ACTIVE" and not net_vim
['admin_state_up']:
438 net
['status'] = 'DOWN'
440 net
['vim_info'] = yaml
.safe_dump(net_vim
, default_flow_style
=True, width
=256)
441 except yaml
.representer
.RepresenterError
:
442 net
['vim_info'] = str(net_vim
)
443 if net_vim
.get('fault'): #TODO
444 net
['error_msg'] = str(net_vim
['fault'])
445 except vimconn
.vimconnNotFoundException
as e
:
446 self
.logger
.error("Exception getting net status: %s", str(e
))
447 net
['status'] = "DELETED"
448 net
['error_msg'] = str(e
)
449 except vimconn
.vimconnException
as e
:
450 self
.logger
.error("Exception getting net status: %s", str(e
))
451 net
['status'] = "VIM_ERROR"
452 net
['error_msg'] = str(e
)
453 net_dict
[net_id
] = net
456 def get_flavor(self
, flavor_id
):
457 '''Obtain flavor details from the VIM. Returns the flavor dict details'''
458 self
.logger
.debug("Getting flavor '%s'", flavor_id
)
460 self
._reload
_connection
()
461 flavor
= self
.nova
.flavors
.find(id=flavor_id
)
462 #TODO parse input and translate to VIM format (openmano_schemas.new_vminstance_response_schema)
463 return flavor
.to_dict()
464 except (nvExceptions
.NotFound
, nvExceptions
.ClientException
, ksExceptions
.ClientException
, ConnectionError
) as e
:
465 self
._format
_exception
(e
)
467 def get_flavor_id_from_data(self
, flavor_dict
):
468 """Obtain flavor id that match the flavor description
469 Returns the flavor_id or raises a vimconnNotFoundException
472 self
._reload
_connection
()
474 numas
= flavor_dict
.get("extended",{}).get("numas")
477 raise vimconn
.vimconnNotFoundException("Flavor with EPA still not implemted")
479 # raise vimconn.vimconnNotFoundException("Cannot find any flavor with more than one numa")
481 # numas = extended.get("numas")
482 for flavor
in self
.nova
.flavors
.list():
483 epa
= flavor
.get_keys()
487 if flavor
.ram
!= flavor_dict
["ram"]:
489 if flavor
.vcpus
!= flavor_dict
["vcpus"]:
491 if flavor
.disk
!= flavor_dict
["disk"]:
494 raise vimconn
.vimconnNotFoundException("Cannot find any flavor matching '{}'".format(str(flavor_dict
)))
495 except (nvExceptions
.NotFound
, nvExceptions
.ClientException
, ksExceptions
.ClientException
, ConnectionError
) as e
:
496 self
._format
_exception
(e
)
499 def new_flavor(self
, flavor_data
, change_name_if_used
=True):
500 '''Adds a tenant flavor to openstack VIM
501 if change_name_if_used is True, it will change name in case of conflict, because it is not supported name repetition
502 Returns the flavor identifier
504 self
.logger
.debug("Adding flavor '%s'", str(flavor_data
))
508 name
=flavor_data
['name']
509 while retry
<max_retries
:
512 self
._reload
_connection
()
513 if change_name_if_used
:
516 fl
=self
.nova
.flavors
.list()
518 fl_names
.append(f
.name
)
519 while name
in fl_names
:
521 name
= flavor_data
['name']+"-" + str(name_suffix
)
523 ram
= flavor_data
.get('ram',64)
524 vcpus
= flavor_data
.get('vcpus',1)
527 extended
= flavor_data
.get("extended")
529 numas
=extended
.get("numas")
531 numa_nodes
= len(numas
)
533 return -1, "Can not add flavor with more than one numa"
534 numa_properties
= {"hw:numa_nodes":str(numa_nodes
)}
535 numa_properties
["hw:mem_page_size"] = "large"
536 numa_properties
["hw:cpu_policy"] = "dedicated"
537 numa_properties
["hw:numa_mempolicy"] = "strict"
539 #overwrite ram and vcpus
540 ram
= numa
['memory']*1024
541 if 'paired-threads' in numa
:
542 vcpus
= numa
['paired-threads']*2
543 numa_properties
["hw:cpu_threads_policy"] = "prefer"
544 elif 'cores' in numa
:
545 vcpus
= numa
['cores']
546 #numa_properties["hw:cpu_threads_policy"] = "prefer"
547 elif 'threads' in numa
:
548 vcpus
= numa
['threads']
549 numa_properties
["hw:cpu_policy"] = "isolated"
550 for interface
in numa
.get("interfaces",() ):
551 if interface
["dedicated"]=="yes":
552 raise vimconn
.vimconnException("Passthrough interfaces are not supported for the openstack connector", http_code
=vimconn
.HTTP_Service_Unavailable
)
553 #TODO, add the key 'pci_passthrough:alias"="<label at config>:<number ifaces>"' when a way to connect it is available
556 new_flavor
=self
.nova
.flavors
.create(name
,
559 flavor_data
.get('disk',1),
560 is_public
=flavor_data
.get('is_public', True)
564 new_flavor
.set_keys(numa_properties
)
566 except nvExceptions
.Conflict
as e
:
567 if change_name_if_used
and retry
< max_retries
:
569 self
._format
_exception
(e
)
570 #except nvExceptions.BadRequest as e:
571 except (ksExceptions
.ClientException
, nvExceptions
.ClientException
, ConnectionError
) as e
:
572 self
._format
_exception
(e
)
574 def delete_flavor(self
,flavor_id
):
575 '''Deletes a tenant flavor from openstack VIM. Returns the old flavor_id
578 self
._reload
_connection
()
579 self
.nova
.flavors
.delete(flavor_id
)
581 #except nvExceptions.BadRequest as e:
582 except (nvExceptions
.NotFound
, ksExceptions
.ClientException
, nvExceptions
.ClientException
, ConnectionError
) as e
:
583 self
._format
_exception
(e
)
585 def new_image(self
,image_dict
):
587 Adds a tenant image to VIM. imge_dict is a dictionary with:
589 disk_format: qcow2, vhd, vmdk, raw (by default), ...
590 location: path or URI
591 public: "yes" or "no"
592 metadata: metadata of the image
595 #using version 1 of glance client
596 glancev1
= gl1Client
.Client('1',self
.glance_endpoint
, token
=self
.keystone
.auth_token
, **self
.k_creds
) #TODO check k_creds vs n_creds
599 while retry
<max_retries
:
602 self
._reload
_connection
()
603 #determine format http://docs.openstack.org/developer/glance/formats.html
604 if "disk_format" in image_dict
:
605 disk_format
=image_dict
["disk_format"]
606 else: #autodiscover based on extension
607 if image_dict
['location'][-6:]==".qcow2":
609 elif image_dict
['location'][-4:]==".vhd":
611 elif image_dict
['location'][-5:]==".vmdk":
613 elif image_dict
['location'][-4:]==".vdi":
615 elif image_dict
['location'][-4:]==".iso":
617 elif image_dict
['location'][-4:]==".aki":
619 elif image_dict
['location'][-4:]==".ari":
621 elif image_dict
['location'][-4:]==".ami":
625 self
.logger
.debug("new_image: '%s' loading from '%s'", image_dict
['name'], image_dict
['location'])
626 if image_dict
['location'][0:4]=="http":
627 new_image
= glancev1
.images
.create(name
=image_dict
['name'], is_public
=image_dict
.get('public',"yes")=="yes",
628 container_format
="bare", location
=image_dict
['location'], disk_format
=disk_format
)
630 with
open(image_dict
['location']) as fimage
:
631 new_image
= glancev1
.images
.create(name
=image_dict
['name'], is_public
=image_dict
.get('public',"yes")=="yes",
632 container_format
="bare", data
=fimage
, disk_format
=disk_format
)
633 #insert metadata. We cannot use 'new_image.properties.setdefault'
634 #because nova and glance are "INDEPENDENT" and we are using nova for reading metadata
635 new_image_nova
=self
.nova
.images
.find(id=new_image
.id)
636 new_image_nova
.metadata
.setdefault('location',image_dict
['location'])
637 metadata_to_load
= image_dict
.get('metadata')
639 for k
,v
in yaml
.load(metadata_to_load
).iteritems():
640 new_image_nova
.metadata
.setdefault(k
,v
)
642 except (nvExceptions
.Conflict
, ksExceptions
.ClientException
, nvExceptions
.ClientException
) as e
:
643 self
._format
_exception
(e
)
644 except (HTTPException
, gl1Exceptions
.HTTPException
, gl1Exceptions
.CommunicationError
, ConnectionError
) as e
:
645 if retry
==max_retries
:
647 self
._format
_exception
(e
)
648 except IOError as e
: #can not open the file
649 raise vimconn
.vimconnConnectionException(type(e
).__name
__ + ": " + str(e
)+ " for " + image_dict
['location'],
650 http_code
=vimconn
.HTTP_Bad_Request
)
652 def delete_image(self
, image_id
):
653 '''Deletes a tenant image from openstack VIM. Returns the old id
656 self
._reload
_connection
()
657 self
.nova
.images
.delete(image_id
)
659 except (nvExceptions
.NotFound
, ksExceptions
.ClientException
, nvExceptions
.ClientException
, gl1Exceptions
.CommunicationError
, ConnectionError
) as e
: #TODO remove
660 self
._format
_exception
(e
)
662 def get_image_id_from_path(self
, path
):
663 '''Get the image id from image path in the VIM database. Returns the image_id'''
665 self
._reload
_connection
()
666 images
= self
.nova
.images
.list()
668 if image
.metadata
.get("location")==path
:
670 raise vimconn
.vimconnNotFoundException("image with location '{}' not found".format( path
))
671 except (ksExceptions
.ClientException
, nvExceptions
.ClientException
, gl1Exceptions
.CommunicationError
, ConnectionError
) as e
:
672 self
._format
_exception
(e
)
674 def get_image_list(self
, filter_dict
={}):
675 '''Obtain tenant images from VIM
679 checksum: image checksum
680 Returns the image list of dictionaries:
681 [{<the fields at Filter_dict plus some VIM specific>}, ...]
684 self
.logger
.debug("Getting image list from VIM filter: '%s'", str(filter_dict
))
686 self
._reload
_connection
()
687 filter_dict_os
=filter_dict
.copy()
688 #First we filter by the available filter fields: name, id. The others are removed.
689 filter_dict_os
.pop('checksum',None)
690 image_list
=self
.nova
.images
.findall(**filter_dict_os
)
691 if len(image_list
)==0:
693 #Then we filter by the rest of filter fields: checksum
695 for image
in image_list
:
696 image_class
=self
.glance
.images
.get(image
.id)
697 if 'checksum' not in filter_dict
or image_class
['checksum']==filter_dict
.get('checksum'):
698 filtered_list
.append(image_class
.copy())
700 except (ksExceptions
.ClientException
, nvExceptions
.ClientException
, gl1Exceptions
.CommunicationError
, ConnectionError
) as e
:
701 self
._format
_exception
(e
)
703 def new_vminstance(self
,name
,description
,start
,image_id
,flavor_id
,net_list
,cloud_config
=None,disk_list
=None):
704 '''Adds a VM instance to VIM
706 start: indicates if VM must start or boot in pause mode. Ignored
707 image_id,flavor_id: iamge and flavor uuid
708 net_list: list of interfaces, each one is a dictionary with:
710 net_id: network uuid to connect
711 vpci: virtual vcpi to assign, ignored because openstack lack #TODO
712 model: interface model, ignored #TODO
713 mac_address: used for SR-IOV ifaces #TODO for other types
714 use: 'data', 'bridge', 'mgmt'
715 type: 'virtual', 'PF', 'VF', 'VFnotShared'
716 vim_id: filled/added by this function
717 floating_ip: True/False (or it can be None)
718 #TODO ip, security groups
719 Returns the instance identifier
721 self
.logger
.debug("new_vminstance input: image='%s' flavor='%s' nics='%s'",image_id
, flavor_id
,str(net_list
))
725 external_network
=[] #list of external networks to be connected to instance, later on used to create floating_ip
726 self
._reload
_connection
()
727 metadata_vpci
={} #For a specific neutron plugin
729 if not net
.get("net_id"): #skip non connected iface
731 if net
["type"]=="virtual" or net
["type"]=="VF":
733 "network_id": net
["net_id"],
734 "name": net
.get("name"),
735 "admin_state_up": True
737 if net
["type"]=="virtual":
739 metadata_vpci
[ net
["net_id"] ] = [[ net
["vpci"], "" ]]
742 if "VF" not in metadata_vpci
:
743 metadata_vpci
["VF"]=[]
744 metadata_vpci
["VF"].append([ net
["vpci"], "" ])
745 port_dict
["binding:vnic_type"]="direct"
746 if not port_dict
["name"]:
747 port_dict
["name"]=name
748 if net
.get("mac_address"):
749 port_dict
["mac_address"]=net
["mac_address"]
750 if net
.get("port_security") == False:
751 port_dict
["port_security_enabled"]=net
["port_security"]
752 new_port
= self
.neutron
.create_port({"port": port_dict
})
753 net
["mac_adress"] = new_port
["port"]["mac_address"]
754 net
["vim_id"] = new_port
["port"]["id"]
755 net
["ip"] = new_port
["port"].get("fixed_ips", [{}])[0].get("ip_address")
756 net_list_vim
.append({"port-id": new_port
["port"]["id"]})
758 self
.logger
.warn("new_vminstance: Warning, can not connect a passthrough interface ")
759 #TODO insert this when openstack consider passthrough ports as openstack neutron ports
760 if net
.get('floating_ip', False):
761 net
['exit_on_floating_ip_error'] = True
762 external_network
.append(net
)
763 elif net
['use'] == 'mgmt' and self
.config
.get('use_floating_ip'):
764 net
['exit_on_floating_ip_error'] = False
765 external_network
.append(net
)
768 metadata
= {"pci_assignement": json
.dumps(metadata_vpci
)}
769 if len(metadata
["pci_assignement"]) >255:
770 #limit the metadata size
771 #metadata["pci_assignement"] = metadata["pci_assignement"][0:255]
772 self
.logger
.warn("Metadata deleted since it exceeds the expected length (255) ")
775 self
.logger
.debug("name '%s' image_id '%s'flavor_id '%s' net_list_vim '%s' description '%s' metadata %s",
776 name
, image_id
, flavor_id
, str(net_list_vim
), description
, str(metadata
))
778 security_groups
= self
.config
.get('security_groups')
779 if type(security_groups
) is str:
780 security_groups
= ( security_groups
, )
784 if isinstance(cloud_config
, dict):
785 if cloud_config
.get("user-data"):
786 userdata
=cloud_config
["user-data"]
787 if cloud_config
.get("boot-data-drive") != None:
788 config_drive
= cloud_config
["boot-data-drive"]
789 if cloud_config
.get("config-files") or cloud_config
.get("users") or cloud_config
.get("key-pairs"):
791 raise vimconn
.vimconnConflictException("Cloud-config cannot contain both 'userdata' and 'config-files'/'users'/'key-pairs'")
794 if cloud_config
.get("key-pairs"):
795 userdata_dict
["ssh-authorized-keys"] = cloud_config
["key-pairs"]
796 userdata_dict
["users"] = [{"default": None, "ssh-authorized-keys": cloud_config
["key-pairs"] }]
797 if cloud_config
.get("users"):
798 if "users" not in userdata_dict
:
799 userdata_dict
["users"] = [ "default" ]
800 for user
in cloud_config
["users"]:
802 "name" : user
["name"],
803 "sudo": "ALL = (ALL)NOPASSWD:ALL"
805 if "user-info" in user
:
806 user_info
["gecos"] = user
["user-info"]
807 if user
.get("key-pairs"):
808 user_info
["ssh-authorized-keys"] = user
["key-pairs"]
809 userdata_dict
["users"].append(user_info
)
811 if cloud_config
.get("config-files"):
812 userdata_dict
["write_files"] = []
813 for file in cloud_config
["config-files"]:
815 "path" : file["dest"],
816 "content": file["content"]
818 if file.get("encoding"):
819 file_info
["encoding"] = file["encoding"]
820 if file.get("permissions"):
821 file_info
["permissions"] = file["permissions"]
822 if file.get("owner"):
823 file_info
["owner"] = file["owner"]
824 userdata_dict
["write_files"].append(file_info
)
825 userdata
= "#cloud-config\n"
826 userdata
+= yaml
.safe_dump(userdata_dict
, indent
=4, default_flow_style
=False)
827 self
.logger
.debug("userdata: %s", userdata
)
828 elif isinstance(cloud_config
, str):
829 userdata
= cloud_config
831 #Create additional volumes in case these are present in disk_list
832 block_device_mapping
= None
833 base_disk_index
= ord('b')
834 if disk_list
!= None:
835 block_device_mapping
= dict()
836 for disk
in disk_list
:
837 if 'image_id' in disk
:
838 volume
= self
.cinder
.volumes
.create(size
= disk
['size'],name
= name
+ '_vd' +
839 chr(base_disk_index
), imageRef
= disk
['image_id'])
841 volume
= self
.cinder
.volumes
.create(size
=disk
['size'], name
=name
+ '_vd' +
842 chr(base_disk_index
))
843 block_device_mapping
['_vd' + chr(base_disk_index
)] = volume
.id
846 #wait until volumes are with status available
849 while keep_waiting
and elapsed_time
< volume_timeout
:
851 for volume_id
in block_device_mapping
.itervalues():
852 if self
.cinder
.volumes
.get(volume_id
).status
!= 'available':
858 #if we exceeded the timeout rollback
859 if elapsed_time
>= volume_timeout
:
860 #delete the volumes we just created
861 for volume_id
in block_device_mapping
.itervalues():
862 self
.cinder
.volumes
.delete(volume_id
)
864 #delete ports we just created
865 for net_item
in net_list_vim
:
866 if 'port-id' in net_item
:
867 self
.neutron
.delete_port(net_item
['port-id'])
869 raise vimconn
.vimconnException('Timeout creating volumes for instance ' + name
,
870 http_code
=vimconn
.HTTP_Request_Timeout
)
872 server
= self
.nova
.servers
.create(name
, image_id
, flavor_id
, nics
=net_list_vim
, meta
=metadata
,
873 security_groups
=security_groups
,
874 availability_zone
=self
.config
.get('availability_zone'),
875 key_name
=self
.config
.get('keypair'),
877 config_drive
= config_drive
,
878 block_device_mapping
= block_device_mapping
879 ) # , description=description)
880 #print "DONE :-)", server
882 floating_ips
= self
.neutron
.list_floatingips().get("floatingips", ())
883 for floating_network
in external_network
:
885 # wait until vm is active
887 while elapsed_time
< server_timeout
:
888 status
= self
.nova
.servers
.get(server
.id).status
889 if status
== 'ACTIVE':
894 #if we exceeded the timeout rollback
895 if elapsed_time
>= server_timeout
:
896 raise vimconn
.vimconnException('Timeout creating instance ' + name
,
897 http_code
=vimconn
.HTTP_Request_Timeout
)
900 while(assigned
== False):
902 ip
= floating_ips
.pop(0)
903 if not ip
.get("port_id", False) and ip
.get('tenant_id') == server
.tenant_id
:
904 free_floating_ip
= ip
.get("floating_ip_address")
906 fix_ip
= floating_network
.get('ip')
907 server
.add_floating_ip(free_floating_ip
, fix_ip
)
909 except Exception as e
:
910 raise vimconn
.vimconnException(type(e
).__name
__ + ": Cannot create floating_ip "+ str(e
), http_code
=vimconn
.HTTP_Conflict
)
912 #Find the external network
913 external_nets
= list()
914 for net
in self
.neutron
.list_networks()['networks']:
915 if net
['router:external']:
916 external_nets
.append(net
)
918 if len(external_nets
) == 0:
919 raise vimconn
.vimconnException("Cannot create floating_ip automatically since no external "
920 "network is present",
921 http_code
=vimconn
.HTTP_Conflict
)
922 if len(external_nets
) > 1:
923 raise vimconn
.vimconnException("Cannot create floating_ip automatically since multiple "
924 "external networks are present",
925 http_code
=vimconn
.HTTP_Conflict
)
927 pool_id
= external_nets
[0].get('id')
928 param
= {'floatingip': {'floating_network_id': pool_id
, 'tenant_id': server
.tenant_id
}}
930 #self.logger.debug("Creating floating IP")
931 new_floating_ip
= self
.neutron
.create_floatingip(param
)
932 free_floating_ip
= new_floating_ip
['floatingip']['floating_ip_address']
933 fix_ip
= floating_network
.get('ip')
934 server
.add_floating_ip(free_floating_ip
, fix_ip
)
936 except Exception as e
:
937 raise vimconn
.vimconnException(type(e
).__name
__ + ": Cannot assign floating_ip "+ str(e
), http_code
=vimconn
.HTTP_Conflict
)
938 except Exception as e
:
939 if not floating_network
['exit_on_floating_ip_error']:
940 self
.logger
.warn("Cannot create floating_ip. %s", str(e
))
942 self
.delete_vminstance(server
.id)
946 # except nvExceptions.NotFound as e:
947 # error_value=-vimconn.HTTP_Not_Found
948 # error_text= "vm instance %s not found" % vm_id
949 except (ksExceptions
.ClientException
, nvExceptions
.ClientException
, ConnectionError
) as e
:
950 # delete the volumes we just created
951 if block_device_mapping
!= None:
952 for volume_id
in block_device_mapping
.itervalues():
953 self
.cinder
.volumes
.delete(volume_id
)
955 # delete ports we just created
956 for net_item
in net_list_vim
:
957 if 'port-id' in net_item
:
958 self
.neutron
.delete_port(net_item
['port-id'])
959 self
._format
_exception
(e
)
960 except TypeError as e
:
961 raise vimconn
.vimconnException(type(e
).__name
__ + ": "+ str(e
), http_code
=vimconn
.HTTP_Bad_Request
)
963 def get_vminstance(self
,vm_id
):
964 '''Returns the VM instance information from VIM'''
965 #self.logger.debug("Getting VM from VIM")
967 self
._reload
_connection
()
968 server
= self
.nova
.servers
.find(id=vm_id
)
969 #TODO parse input and translate to VIM format (openmano_schemas.new_vminstance_response_schema)
970 return server
.to_dict()
971 except (ksExceptions
.ClientException
, nvExceptions
.ClientException
, nvExceptions
.NotFound
, ConnectionError
) as e
:
972 self
._format
_exception
(e
)
974 def get_vminstance_console(self
,vm_id
, console_type
="vnc"):
976 Get a console for the virtual machine
978 vm_id: uuid of the VM
979 console_type, can be:
980 "novnc" (by default), "xvpvnc" for VNC types,
981 "rdp-html5" for RDP types, "spice-html5" for SPICE types
982 Returns dict with the console parameters:
983 protocol: ssh, ftp, http, https, ...
984 server: usually ip address
985 port: the http, ssh, ... port
986 suffix: extra text, e.g. the http path and query string
988 self
.logger
.debug("Getting VM CONSOLE from VIM")
990 self
._reload
_connection
()
991 server
= self
.nova
.servers
.find(id=vm_id
)
992 if console_type
== None or console_type
== "novnc":
993 console_dict
= server
.get_vnc_console("novnc")
994 elif console_type
== "xvpvnc":
995 console_dict
= server
.get_vnc_console(console_type
)
996 elif console_type
== "rdp-html5":
997 console_dict
= server
.get_rdp_console(console_type
)
998 elif console_type
== "spice-html5":
999 console_dict
= server
.get_spice_console(console_type
)
1001 raise vimconn
.vimconnException("console type '{}' not allowed".format(console_type
), http_code
=vimconn
.HTTP_Bad_Request
)
1003 console_dict1
= console_dict
.get("console")
1005 console_url
= console_dict1
.get("url")
1008 protocol_index
= console_url
.find("//")
1009 suffix_index
= console_url
[protocol_index
+2:].find("/") + protocol_index
+2
1010 port_index
= console_url
[protocol_index
+2:suffix_index
].find(":") + protocol_index
+2
1011 if protocol_index
< 0 or port_index
<0 or suffix_index
<0:
1012 return -vimconn
.HTTP_Internal_Server_Error
, "Unexpected response from VIM"
1013 console_dict
={"protocol": console_url
[0:protocol_index
],
1014 "server": console_url
[protocol_index
+2:port_index
],
1015 "port": console_url
[port_index
:suffix_index
],
1016 "suffix": console_url
[suffix_index
+1:]
1020 raise vimconn
.vimconnUnexpectedResponse("Unexpected response from VIM")
1022 except (nvExceptions
.NotFound
, ksExceptions
.ClientException
, nvExceptions
.ClientException
, nvExceptions
.BadRequest
, ConnectionError
) as e
:
1023 self
._format
_exception
(e
)
1025 def delete_vminstance(self
, vm_id
):
1026 '''Removes a VM instance from VIM. Returns the old identifier
1028 #print "osconnector: Getting VM from VIM"
1030 self
._reload
_connection
()
1031 #delete VM ports attached to this networks before the virtual machine
1032 ports
= self
.neutron
.list_ports(device_id
=vm_id
)
1033 for p
in ports
['ports']:
1035 self
.neutron
.delete_port(p
["id"])
1036 except Exception as e
:
1037 self
.logger
.error("Error deleting port: " + type(e
).__name
__ + ": "+ str(e
))
1039 #commented because detaching the volumes makes the servers.delete not work properly ?!?
1040 #dettach volumes attached
1041 server
= self
.nova
.servers
.get(vm_id
)
1042 volumes_attached_dict
= server
._info
['os-extended-volumes:volumes_attached']
1043 #for volume in volumes_attached_dict:
1044 # self.cinder.volumes.detach(volume['id'])
1046 self
.nova
.servers
.delete(vm_id
)
1049 #Although having detached them should have them in active status
1050 #we ensure in this loop
1053 while keep_waiting
and elapsed_time
< volume_timeout
:
1054 keep_waiting
= False
1055 for volume
in volumes_attached_dict
:
1056 if self
.cinder
.volumes
.get(volume
['id']).status
!= 'available':
1059 self
.cinder
.volumes
.delete(volume
['id'])
1065 except (nvExceptions
.NotFound
, ksExceptions
.ClientException
, nvExceptions
.ClientException
, ConnectionError
) as e
:
1066 self
._format
_exception
(e
)
1067 #TODO insert exception vimconn.HTTP_Unauthorized
1068 #if reaching here is because an exception
1070 def refresh_vms_status(self
, vm_list
):
1071 '''Get the status of the virtual machines and their interfaces/ports
1072 Params: the list of VM identifiers
1073 Returns a dictionary with:
1074 vm_id: #VIM id of this Virtual Machine
1075 status: #Mandatory. Text with one of:
1076 # DELETED (not found at vim)
1077 # VIM_ERROR (Cannot connect to VIM, VIM response error, ...)
1078 # OTHER (Vim reported other status not understood)
1079 # ERROR (VIM indicates an ERROR status)
1080 # ACTIVE, PAUSED, SUSPENDED, INACTIVE (not running),
1081 # CREATING (on building process), ERROR
1082 # ACTIVE:NoMgmtIP (Active but any of its interface has an IP address
1084 error_msg: #Text with VIM error message, if any. Or the VIM connection ERROR
1085 vim_info: #Text with plain information obtained from vim (yaml.safe_dump)
1087 - vim_info: #Text with plain information obtained from vim (yaml.safe_dump)
1088 mac_address: #Text format XX:XX:XX:XX:XX:XX
1089 vim_net_id: #network id where this interface is connected
1090 vim_interface_id: #interface/port VIM id
1091 ip_address: #null, or text with IPv4, IPv6 address
1094 self
.logger
.debug("refresh_vms status: Getting tenant VM instance information from VIM")
1095 for vm_id
in vm_list
:
1098 vm_vim
= self
.get_vminstance(vm_id
)
1099 if vm_vim
['status'] in vmStatus2manoFormat
:
1100 vm
['status'] = vmStatus2manoFormat
[ vm_vim
['status'] ]
1102 vm
['status'] = "OTHER"
1103 vm
['error_msg'] = "VIM status reported " + vm_vim
['status']
1105 vm
['vim_info'] = yaml
.safe_dump(vm_vim
, default_flow_style
=True, width
=256)
1106 except yaml
.representer
.RepresenterError
:
1107 vm
['vim_info'] = str(vm_vim
)
1108 vm
["interfaces"] = []
1109 if vm_vim
.get('fault'):
1110 vm
['error_msg'] = str(vm_vim
['fault'])
1113 self
._reload
_connection
()
1114 port_dict
=self
.neutron
.list_ports(device_id
=vm_id
)
1115 for port
in port_dict
["ports"]:
1118 interface
['vim_info'] = yaml
.safe_dump(port
, default_flow_style
=True, width
=256)
1119 except yaml
.representer
.RepresenterError
:
1120 interface
['vim_info'] = str(port
)
1121 interface
["mac_address"] = port
.get("mac_address")
1122 interface
["vim_net_id"] = port
["network_id"]
1123 interface
["vim_interface_id"] = port
["id"]
1125 #look for floating ip address
1126 floating_ip_dict
= self
.neutron
.list_floatingips(port_id
=port
["id"])
1127 if floating_ip_dict
.get("floatingips"):
1128 ips
.append(floating_ip_dict
["floatingips"][0].get("floating_ip_address") )
1130 for subnet
in port
["fixed_ips"]:
1131 ips
.append(subnet
["ip_address"])
1132 interface
["ip_address"] = ";".join(ips
)
1133 vm
["interfaces"].append(interface
)
1134 except Exception as e
:
1135 self
.logger
.error("Error getting vm interface information " + type(e
).__name
__ + ": "+ str(e
))
1136 except vimconn
.vimconnNotFoundException
as e
:
1137 self
.logger
.error("Exception getting vm status: %s", str(e
))
1138 vm
['status'] = "DELETED"
1139 vm
['error_msg'] = str(e
)
1140 except vimconn
.vimconnException
as e
:
1141 self
.logger
.error("Exception getting vm status: %s", str(e
))
1142 vm
['status'] = "VIM_ERROR"
1143 vm
['error_msg'] = str(e
)
1147 def action_vminstance(self
, vm_id
, action_dict
):
1148 '''Send and action over a VM instance from VIM
1149 Returns the vm_id if the action was successfully sent to the VIM'''
1150 self
.logger
.debug("Action over VM '%s': %s", vm_id
, str(action_dict
))
1152 self
._reload
_connection
()
1153 server
= self
.nova
.servers
.find(id=vm_id
)
1154 if "start" in action_dict
:
1155 if action_dict
["start"]=="rebuild":
1158 if server
.status
=="PAUSED":
1160 elif server
.status
=="SUSPENDED":
1162 elif server
.status
=="SHUTOFF":
1164 elif "pause" in action_dict
:
1166 elif "resume" in action_dict
:
1168 elif "shutoff" in action_dict
or "shutdown" in action_dict
:
1170 elif "forceOff" in action_dict
:
1172 elif "terminate" in action_dict
:
1174 elif "createImage" in action_dict
:
1175 server
.create_image()
1176 #"path":path_schema,
1177 #"description":description_schema,
1178 #"name":name_schema,
1179 #"metadata":metadata_schema,
1180 #"imageRef": id_schema,
1181 #"disk": {"oneOf":[{"type": "null"}, {"type":"string"}] },
1182 elif "rebuild" in action_dict
:
1183 server
.rebuild(server
.image
['id'])
1184 elif "reboot" in action_dict
:
1185 server
.reboot() #reboot_type='SOFT'
1186 elif "console" in action_dict
:
1187 console_type
= action_dict
["console"]
1188 if console_type
== None or console_type
== "novnc":
1189 console_dict
= server
.get_vnc_console("novnc")
1190 elif console_type
== "xvpvnc":
1191 console_dict
= server
.get_vnc_console(console_type
)
1192 elif console_type
== "rdp-html5":
1193 console_dict
= server
.get_rdp_console(console_type
)
1194 elif console_type
== "spice-html5":
1195 console_dict
= server
.get_spice_console(console_type
)
1197 raise vimconn
.vimconnException("console type '{}' not allowed".format(console_type
),
1198 http_code
=vimconn
.HTTP_Bad_Request
)
1200 console_url
= console_dict
["console"]["url"]
1202 protocol_index
= console_url
.find("//")
1203 suffix_index
= console_url
[protocol_index
+2:].find("/") + protocol_index
+2
1204 port_index
= console_url
[protocol_index
+2:suffix_index
].find(":") + protocol_index
+2
1205 if protocol_index
< 0 or port_index
<0 or suffix_index
<0:
1206 raise vimconn
.vimconnException("Unexpected response from VIM " + str(console_dict
))
1207 console_dict2
={"protocol": console_url
[0:protocol_index
],
1208 "server": console_url
[protocol_index
+2 : port_index
],
1209 "port": int(console_url
[port_index
+1 : suffix_index
]),
1210 "suffix": console_url
[suffix_index
+1:]
1212 return console_dict2
1213 except Exception as e
:
1214 raise vimconn
.vimconnException("Unexpected response from VIM " + str(console_dict
))
1217 except (ksExceptions
.ClientException
, nvExceptions
.ClientException
, nvExceptions
.NotFound
, ConnectionError
) as e
:
1218 self
._format
_exception
(e
)
1219 #TODO insert exception vimconn.HTTP_Unauthorized
1223 def new_external_port(self
, port_data
):
1224 #TODO openstack if needed
1225 '''Adds a external port to VIM'''
1226 '''Returns the port identifier'''
1227 return -vimconn
.HTTP_Internal_Server_Error
, "osconnector.new_external_port() not implemented"
1229 def connect_port_network(self
, port_id
, network_id
, admin
=False):
1230 #TODO openstack if needed
1231 '''Connects a external port to a network'''
1232 '''Returns status code of the VIM response'''
1233 return -vimconn
.HTTP_Internal_Server_Error
, "osconnector.connect_port_network() not implemented"
1235 def new_user(self
, user_name
, user_passwd
, tenant_id
=None):
1236 '''Adds a new user to openstack VIM'''
1237 '''Returns the user identifier'''
1238 self
.logger
.debug("osconnector: Adding a new user to VIM")
1240 self
._reload
_connection
()
1241 user
=self
.keystone
.users
.create(user_name
, user_passwd
, tenant_id
=tenant_id
)
1242 #self.keystone.tenants.add_user(self.k_creds["username"], #role)
1244 except ksExceptions
.ConnectionError
as e
:
1245 error_value
=-vimconn
.HTTP_Bad_Request
1246 error_text
= type(e
).__name
__ + ": "+ (str(e
) if len(e
.args
)==0 else str(e
.args
[0]))
1247 except ksExceptions
.ClientException
as e
: #TODO remove
1248 error_value
=-vimconn
.HTTP_Bad_Request
1249 error_text
= type(e
).__name
__ + ": "+ (str(e
) if len(e
.args
)==0 else str(e
.args
[0]))
1250 #TODO insert exception vimconn.HTTP_Unauthorized
1251 #if reaching here is because an exception
1253 self
.logger
.debug("new_user " + error_text
)
1254 return error_value
, error_text
1256 def delete_user(self
, user_id
):
1257 '''Delete a user from openstack VIM'''
1258 '''Returns the user identifier'''
1260 print "osconnector: Deleting a user from VIM"
1262 self
._reload
_connection
()
1263 self
.keystone
.users
.delete(user_id
)
1265 except ksExceptions
.ConnectionError
as e
:
1266 error_value
=-vimconn
.HTTP_Bad_Request
1267 error_text
= type(e
).__name
__ + ": "+ (str(e
) if len(e
.args
)==0 else str(e
.args
[0]))
1268 except ksExceptions
.NotFound
as e
:
1269 error_value
=-vimconn
.HTTP_Not_Found
1270 error_text
= type(e
).__name
__ + ": "+ (str(e
) if len(e
.args
)==0 else str(e
.args
[0]))
1271 except ksExceptions
.ClientException
as e
: #TODO remove
1272 error_value
=-vimconn
.HTTP_Bad_Request
1273 error_text
= type(e
).__name
__ + ": "+ (str(e
) if len(e
.args
)==0 else str(e
.args
[0]))
1274 #TODO insert exception vimconn.HTTP_Unauthorized
1275 #if reaching here is because an exception
1277 print "delete_tenant " + error_text
1278 return error_value
, error_text
1280 def get_hosts_info(self
):
1281 '''Get the information of deployed hosts
1282 Returns the hosts content'''
1284 print "osconnector: Getting Host info from VIM"
1287 self
._reload
_connection
()
1288 hypervisors
= self
.nova
.hypervisors
.list()
1289 for hype
in hypervisors
:
1290 h_list
.append( hype
.to_dict() )
1291 return 1, {"hosts":h_list
}
1292 except nvExceptions
.NotFound
as e
:
1293 error_value
=-vimconn
.HTTP_Not_Found
1294 error_text
= (str(e
) if len(e
.args
)==0 else str(e
.args
[0]))
1295 except (ksExceptions
.ClientException
, nvExceptions
.ClientException
) as e
:
1296 error_value
=-vimconn
.HTTP_Bad_Request
1297 error_text
= type(e
).__name
__ + ": "+ (str(e
) if len(e
.args
)==0 else str(e
.args
[0]))
1298 #TODO insert exception vimconn.HTTP_Unauthorized
1299 #if reaching here is because an exception
1301 print "get_hosts_info " + error_text
1302 return error_value
, error_text
1304 def get_hosts(self
, vim_tenant
):
1305 '''Get the hosts and deployed instances
1306 Returns the hosts content'''
1307 r
, hype_dict
= self
.get_hosts_info()
1310 hypervisors
= hype_dict
["hosts"]
1312 servers
= self
.nova
.servers
.list()
1313 for hype
in hypervisors
:
1314 for server
in servers
:
1315 if server
.to_dict()['OS-EXT-SRV-ATTR:hypervisor_hostname']==hype
['hypervisor_hostname']:
1317 hype
['vm'].append(server
.id)
1319 hype
['vm'] = [server
.id]
1321 except nvExceptions
.NotFound
as e
:
1322 error_value
=-vimconn
.HTTP_Not_Found
1323 error_text
= (str(e
) if len(e
.args
)==0 else str(e
.args
[0]))
1324 except (ksExceptions
.ClientException
, nvExceptions
.ClientException
) as e
:
1325 error_value
=-vimconn
.HTTP_Bad_Request
1326 error_text
= type(e
).__name
__ + ": "+ (str(e
) if len(e
.args
)==0 else str(e
.args
[0]))
1327 #TODO insert exception vimconn.HTTP_Unauthorized
1328 #if reaching here is because an exception
1330 print "get_hosts " + error_text
1331 return error_value
, error_text