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
, exceptions
as nvExceptions
40 from keystoneauth1
.identity
import v2
, v3
41 from keystoneauth1
import session
42 import keystoneclient
.exceptions
as ksExceptions
43 import keystoneclient
.v3
.client
as ksClient_v3
44 import keystoneclient
.v2_0
.client
as ksClient_v2
45 from glanceclient
import client
as glClient
46 import glanceclient
.client
as gl1Client
47 import glanceclient
.exc
as gl1Exceptions
48 from cinderclient
import client
as cClient
49 from httplib
import HTTPException
50 from neutronclient
.neutron
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,
71 log_level
=None, config
={}, persistent_info
={}):
72 '''using common constructor parameters. In this case
73 'url' is the keystone authorization url,
74 'url_admin' is not use
76 api_version
= config
.get('APIversion')
77 if api_version
and api_version
not in ('v3.3', 'v2.0', '2', '3'):
78 raise vimconn
.vimconnException("Invalid value '{}' for config:APIversion. "
79 "Allowed values are 'v3.3', 'v2.0', '2' or '3'".format(api_version
))
80 vimconn
.vimconnector
.__init
__(self
, uuid
, name
, tenant_id
, tenant_name
, url
, url_admin
, user
, passwd
, log_level
,
83 self
.insecure
= self
.config
.get("insecure", False)
85 raise TypeError, 'url param can not be NoneType'
86 self
.persistent_info
= persistent_info
87 self
.session
= persistent_info
.get('session', {'reload_client': True})
88 self
.nova
= self
.session
.get('nova')
89 self
.neutron
= self
.session
.get('neutron')
90 self
.cinder
= self
.session
.get('cinder')
91 self
.glance
= self
.session
.get('glance')
92 self
.keystone
= self
.session
.get('keystone')
93 self
.api_version3
= self
.session
.get('api_version3')
95 self
.logger
= logging
.getLogger('openmano.vim.openstack')
97 self
.logger
.setLevel( getattr(logging
, log_level
) )
99 def __getitem__(self
, index
):
100 """Get individuals parameters.
102 if index
== 'project_domain_id':
103 return self
.config
.get("project_domain_id")
104 elif index
== 'user_domain_id':
105 return self
.config
.get("user_domain_id")
107 return vimconn
.vimconnector
.__getitem
__(self
, index
)
109 def __setitem__(self
, index
, value
):
110 """Set individuals parameters and it is marked as dirty so to force connection reload.
112 if index
== 'project_domain_id':
113 self
.config
["project_domain_id"] = value
114 elif index
== 'user_domain_id':
115 self
.config
["user_domain_id"] = value
117 vimconn
.vimconnector
.__setitem
__(self
, index
, value
)
118 self
.session
['reload_client'] = True
120 def _reload_connection(self
):
121 '''Called before any operation, it check if credentials has changed
122 Throw keystoneclient.apiclient.exceptions.AuthorizationFailure
124 #TODO control the timing and possible token timeout, but it seams that python client does this task for us :-)
125 if self
.session
['reload_client']:
126 if self
.config
.get('APIversion'):
127 self
.api_version3
= self
.config
['APIversion'] == 'v3.3' or self
.config
['APIversion'] == '3'
128 else: # get from ending auth_url that end with v3 or with v2.0
129 self
.api_version3
= self
.url
.split("/")[-1] == "v3"
130 self
.session
['api_version3'] = self
.api_version3
131 if self
.api_version3
:
132 auth
= v3
.Password(auth_url
=self
.url
,
134 password
=self
.passwd
,
135 project_name
=self
.tenant_name
,
136 project_id
=self
.tenant_id
,
137 project_domain_id
=self
.config
.get('project_domain_id', 'default'),
138 user_domain_id
=self
.config
.get('user_domain_id', 'default'))
140 auth
= v2
.Password(auth_url
=self
.url
,
142 password
=self
.passwd
,
143 tenant_name
=self
.tenant_name
,
144 tenant_id
=self
.tenant_id
)
145 sess
= session
.Session(auth
=auth
, verify
=not self
.insecure
)
146 if self
.api_version3
:
147 self
.keystone
= ksClient_v3
.Client(session
=sess
)
149 self
.keystone
= ksClient_v2
.Client(session
=sess
)
150 self
.session
['keystone'] = self
.keystone
151 self
.nova
= self
.session
['nova'] = nClient
.Client("2.1", session
=sess
)
152 self
.neutron
= self
.session
['neutron'] = neClient
.Client('2.0', session
=sess
)
153 self
.cinder
= self
.session
['cinder'] = cClient
.Client(2, session
=sess
)
154 self
.glance
= self
.session
['glance'] = glClient
.Client(2, session
=sess
)
155 self
.session
['reload_client'] = False
156 self
.persistent_info
['session'] = self
.session
158 def __net_os2mano(self
, net_list_dict
):
159 '''Transform the net openstack format to mano format
160 net_list_dict can be a list of dict or a single dict'''
161 if type(net_list_dict
) is dict:
162 net_list_
=(net_list_dict
,)
163 elif type(net_list_dict
) is list:
164 net_list_
=net_list_dict
166 raise TypeError("param net_list_dict must be a list or a dictionary")
167 for net
in net_list_
:
168 if net
.get('provider:network_type') == "vlan":
173 def _format_exception(self
, exception
):
174 '''Transform a keystone, nova, neutron exception into a vimconn exception'''
175 if isinstance(exception
, (HTTPException
, gl1Exceptions
.HTTPException
, gl1Exceptions
.CommunicationError
,
176 ConnectionError
, ksExceptions
.ConnectionError
, neExceptions
.ConnectionFailed
178 raise vimconn
.vimconnConnectionException(type(exception
).__name
__ + ": " + str(exception
))
179 elif isinstance(exception
, (nvExceptions
.ClientException
, ksExceptions
.ClientException
,
180 neExceptions
.NeutronException
, nvExceptions
.BadRequest
)):
181 raise vimconn
.vimconnUnexpectedResponse(type(exception
).__name
__ + ": " + str(exception
))
182 elif isinstance(exception
, (neExceptions
.NetworkNotFoundClient
, nvExceptions
.NotFound
)):
183 raise vimconn
.vimconnNotFoundException(type(exception
).__name
__ + ": " + str(exception
))
184 elif isinstance(exception
, nvExceptions
.Conflict
):
185 raise vimconn
.vimconnConflictException(type(exception
).__name
__ + ": " + str(exception
))
186 elif isinstance(exception
, vimconn
.vimconnException
):
189 self
.logger
.error("General Exception " + str(exception
), exc_info
=True)
190 raise vimconn
.vimconnConnectionException(type(exception
).__name
__ + ": " + str(exception
))
192 def get_tenant_list(self
, filter_dict
={}):
193 '''Obtain tenants of VIM
194 filter_dict can contain the following keys:
195 name: filter by tenant name
196 id: filter by tenant uuid/id
198 Returns the tenant list of dictionaries: [{'name':'<name>, 'id':'<id>, ...}, ...]
200 self
.logger
.debug("Getting tenants from VIM filter: '%s'", str(filter_dict
))
202 self
._reload
_connection
()
203 if self
.api_version3
:
204 project_class_list
= self
.keystone
.projects
.list(name
=filter_dict
.get("name"))
206 project_class_list
= self
.keystone
.tenants
.findall(**filter_dict
)
208 for project
in project_class_list
:
209 if filter_dict
.get('id') and filter_dict
["id"] != project
.id:
211 project_list
.append(project
.to_dict())
213 except (ksExceptions
.ConnectionError
, ksExceptions
.ClientException
, ConnectionError
) as e
:
214 self
._format
_exception
(e
)
216 def new_tenant(self
, tenant_name
, tenant_description
):
217 '''Adds a new tenant to openstack VIM. Returns the tenant identifier'''
218 self
.logger
.debug("Adding a new tenant name: %s", tenant_name
)
220 self
._reload
_connection
()
221 if self
.api_version3
:
222 project
= self
.keystone
.projects
.create(tenant_name
, self
.config
.get("project_domain_id", "default"),
223 description
=tenant_description
, is_domain
=False)
225 project
= self
.keystone
.tenants
.create(tenant_name
, tenant_description
)
227 except (ksExceptions
.ConnectionError
, ksExceptions
.ClientException
, ConnectionError
) as e
:
228 self
._format
_exception
(e
)
230 def delete_tenant(self
, tenant_id
):
231 '''Delete a tenant from openstack VIM. Returns the old tenant identifier'''
232 self
.logger
.debug("Deleting tenant %s from VIM", tenant_id
)
234 self
._reload
_connection
()
235 if self
.api_version3
:
236 self
.keystone
.projects
.delete(tenant_id
)
238 self
.keystone
.tenants
.delete(tenant_id
)
240 except (ksExceptions
.ConnectionError
, ksExceptions
.ClientException
, ConnectionError
) as e
:
241 self
._format
_exception
(e
)
243 def new_network(self
,net_name
, net_type
, ip_profile
=None, shared
=False, vlan
=None):
244 '''Adds a tenant network to VIM. Returns the network identifier'''
245 self
.logger
.debug("Adding a new network to VIM name '%s', type '%s'", net_name
, net_type
)
246 #self.logger.debug(">>>>>>>>>>>>>>>>>> IP profile %s", str(ip_profile))
249 self
._reload
_connection
()
250 network_dict
= {'name': net_name
, 'admin_state_up': True}
251 if net_type
=="data" or net_type
=="ptp":
252 if self
.config
.get('dataplane_physical_net') == None:
253 raise vimconn
.vimconnConflictException("You must provide a 'dataplane_physical_net' at config value before creating sriov network")
254 network_dict
["provider:physical_network"] = self
.config
['dataplane_physical_net'] #"physnet_sriov" #TODO physical
255 network_dict
["provider:network_type"] = "vlan"
257 network_dict
["provider:network_type"] = vlan
258 network_dict
["shared"]=shared
259 new_net
=self
.neutron
.create_network({'network':network_dict
})
261 #create subnetwork, even if there is no profile
264 if 'subnet_address' not in ip_profile
:
265 #Fake subnet is required
266 subnet_rand
= random
.randint(0, 255)
267 ip_profile
['subnet_address'] = "192.168.{}.0/24".format(subnet_rand
)
268 if 'ip_version' not in ip_profile
:
269 ip_profile
['ip_version'] = "IPv4"
270 subnet
= {"name":net_name
+"-subnet",
271 "network_id": new_net
["network"]["id"],
272 "ip_version": 4 if ip_profile
['ip_version']=="IPv4" else 6,
273 "cidr": ip_profile
['subnet_address']
275 # Gateway should be set to None if not needed. Otherwise openstack assigns one by default
276 subnet
['gateway_ip'] = ip_profile
.get('gateway_address')
277 if ip_profile
.get('dns_address'):
278 subnet
['dns_nameservers'] = ip_profile
['dns_address'].split(";")
279 if 'dhcp_enabled' in ip_profile
:
280 subnet
['enable_dhcp'] = False if ip_profile
['dhcp_enabled']=="false" else True
281 if 'dhcp_start_address' in ip_profile
:
282 subnet
['allocation_pools'] = []
283 subnet
['allocation_pools'].append(dict())
284 subnet
['allocation_pools'][0]['start'] = ip_profile
['dhcp_start_address']
285 if 'dhcp_count' in ip_profile
:
286 #parts = ip_profile['dhcp_start_address'].split('.')
287 #ip_int = (int(parts[0]) << 24) + (int(parts[1]) << 16) + (int(parts[2]) << 8) + int(parts[3])
288 ip_int
= int(netaddr
.IPAddress(ip_profile
['dhcp_start_address']))
289 ip_int
+= ip_profile
['dhcp_count'] - 1
290 ip_str
= str(netaddr
.IPAddress(ip_int
))
291 subnet
['allocation_pools'][0]['end'] = ip_str
292 #self.logger.debug(">>>>>>>>>>>>>>>>>> Subnet: %s", str(subnet))
293 self
.neutron
.create_subnet({"subnet": subnet
} )
294 return new_net
["network"]["id"]
295 except (neExceptions
.ConnectionFailed
, ksExceptions
.ClientException
, neExceptions
.NeutronException
, ConnectionError
) as e
:
297 self
.neutron
.delete_network(new_net
['network']['id'])
298 self
._format
_exception
(e
)
300 def get_network_list(self
, filter_dict
={}):
301 '''Obtain tenant networks of VIM
307 admin_state_up: boolean
309 Returns the network list of dictionaries
311 self
.logger
.debug("Getting network from VIM filter: '%s'", str(filter_dict
))
313 self
._reload
_connection
()
314 if self
.api_version3
and "tenant_id" in filter_dict
:
315 filter_dict
['project_id'] = filter_dict
.pop('tenant_id') #TODO check
316 net_dict
=self
.neutron
.list_networks(**filter_dict
)
317 net_list
=net_dict
["networks"]
318 self
.__net
_os
2mano
(net_list
)
320 except (neExceptions
.ConnectionFailed
, ksExceptions
.ClientException
, neExceptions
.NeutronException
, ConnectionError
) as e
:
321 self
._format
_exception
(e
)
323 def get_network(self
, net_id
):
324 '''Obtain details of network from VIM
325 Returns the network information from a network id'''
326 self
.logger
.debug(" Getting tenant network %s from VIM", net_id
)
327 filter_dict
={"id": net_id
}
328 net_list
= self
.get_network_list(filter_dict
)
330 raise vimconn
.vimconnNotFoundException("Network '{}' not found".format(net_id
))
331 elif len(net_list
)>1:
332 raise vimconn
.vimconnConflictException("Found more than one network with this criteria")
335 for subnet_id
in net
.get("subnets", () ):
337 subnet
= self
.neutron
.show_subnet(subnet_id
)
338 except Exception as e
:
339 self
.logger
.error("osconnector.get_network(): Error getting subnet %s %s" % (net_id
, str(e
)))
340 subnet
= {"id": subnet_id
, "fault": str(e
)}
341 subnets
.append(subnet
)
342 net
["subnets"] = subnets
343 net
["encapsulation"] = net
.get('provider:network_type')
344 net
["segmentation_id"] = net
.get('provider:segmentation_id')
347 def delete_network(self
, net_id
):
348 '''Deletes a tenant network from VIM. Returns the old network identifier'''
349 self
.logger
.debug("Deleting network '%s' from VIM", net_id
)
351 self
._reload
_connection
()
352 #delete VM ports attached to this networks before the network
353 ports
= self
.neutron
.list_ports(network_id
=net_id
)
354 for p
in ports
['ports']:
356 self
.neutron
.delete_port(p
["id"])
357 except Exception as e
:
358 self
.logger
.error("Error deleting port %s: %s", p
["id"], str(e
))
359 self
.neutron
.delete_network(net_id
)
361 except (neExceptions
.ConnectionFailed
, neExceptions
.NetworkNotFoundClient
, neExceptions
.NeutronException
,
362 ksExceptions
.ClientException
, neExceptions
.NeutronException
, ConnectionError
) as e
:
363 self
._format
_exception
(e
)
365 def refresh_nets_status(self
, net_list
):
366 '''Get the status of the networks
367 Params: the list of network identifiers
368 Returns a dictionary with:
369 net_id: #VIM id of this network
370 status: #Mandatory. Text with one of:
371 # DELETED (not found at vim)
372 # VIM_ERROR (Cannot connect to VIM, VIM response error, ...)
373 # OTHER (Vim reported other status not understood)
374 # ERROR (VIM indicates an ERROR status)
375 # ACTIVE, INACTIVE, DOWN (admin down),
376 # BUILD (on building process)
378 error_msg: #Text with VIM error message, if any. Or the VIM connection ERROR
379 vim_info: #Text with plain information obtained from vim (yaml.safe_dump)
383 for net_id
in net_list
:
386 net_vim
= self
.get_network(net_id
)
387 if net_vim
['status'] in netStatus2manoFormat
:
388 net
["status"] = netStatus2manoFormat
[ net_vim
['status'] ]
390 net
["status"] = "OTHER"
391 net
["error_msg"] = "VIM status reported " + net_vim
['status']
393 if net
['status'] == "ACTIVE" and not net_vim
['admin_state_up']:
394 net
['status'] = 'DOWN'
396 net
['vim_info'] = yaml
.safe_dump(net_vim
, default_flow_style
=True, width
=256)
397 except yaml
.representer
.RepresenterError
:
398 net
['vim_info'] = str(net_vim
)
399 if net_vim
.get('fault'): #TODO
400 net
['error_msg'] = str(net_vim
['fault'])
401 except vimconn
.vimconnNotFoundException
as e
:
402 self
.logger
.error("Exception getting net status: %s", str(e
))
403 net
['status'] = "DELETED"
404 net
['error_msg'] = str(e
)
405 except vimconn
.vimconnException
as e
:
406 self
.logger
.error("Exception getting net status: %s", str(e
))
407 net
['status'] = "VIM_ERROR"
408 net
['error_msg'] = str(e
)
409 net_dict
[net_id
] = net
412 def get_flavor(self
, flavor_id
):
413 '''Obtain flavor details from the VIM. Returns the flavor dict details'''
414 self
.logger
.debug("Getting flavor '%s'", flavor_id
)
416 self
._reload
_connection
()
417 flavor
= self
.nova
.flavors
.find(id=flavor_id
)
418 #TODO parse input and translate to VIM format (openmano_schemas.new_vminstance_response_schema)
419 return flavor
.to_dict()
420 except (nvExceptions
.NotFound
, nvExceptions
.ClientException
, ksExceptions
.ClientException
, ConnectionError
) as e
:
421 self
._format
_exception
(e
)
423 def get_flavor_id_from_data(self
, flavor_dict
):
424 """Obtain flavor id that match the flavor description
425 Returns the flavor_id or raises a vimconnNotFoundException
426 flavor_dict: contains the required ram, vcpus, disk
427 If 'use_existing_flavors' is set to True at config, the closer flavor that provides same or more ram, vcpus
428 and disk is returned. Otherwise a flavor with exactly same ram, vcpus and disk is returned or a
429 vimconnNotFoundException is raised
431 exact_match
= False if self
.config
.get('use_existing_flavors') else True
433 self
._reload
_connection
()
434 flavor_candidate_id
= None
435 flavor_candidate_data
= (10000, 10000, 10000)
436 flavor_target
= (flavor_dict
["ram"], flavor_dict
["vcpus"], flavor_dict
["disk"])
438 numas
= flavor_dict
.get("extended", {}).get("numas")
441 raise vimconn
.vimconnNotFoundException("Flavor with EPA still not implemted")
443 # raise vimconn.vimconnNotFoundException("Cannot find any flavor with more than one numa")
445 # numas = extended.get("numas")
446 for flavor
in self
.nova
.flavors
.list():
447 epa
= flavor
.get_keys()
451 flavor_data
= (flavor
.ram
, flavor
.vcpus
, flavor
.disk
)
452 if flavor_data
== flavor_target
:
454 elif not exact_match
and flavor_target
< flavor_data
< flavor_candidate_data
:
455 flavor_candidate_id
= flavor
.id
456 flavor_candidate_data
= flavor_data
457 if not exact_match
and flavor_candidate_id
:
458 return flavor_candidate_id
459 raise vimconn
.vimconnNotFoundException("Cannot find any flavor matching '{}'".format(str(flavor_dict
)))
460 except (nvExceptions
.NotFound
, nvExceptions
.ClientException
, ksExceptions
.ClientException
, ConnectionError
) as e
:
461 self
._format
_exception
(e
)
464 def new_flavor(self
, flavor_data
, change_name_if_used
=True):
465 '''Adds a tenant flavor to openstack VIM
466 if change_name_if_used is True, it will change name in case of conflict, because it is not supported name repetition
467 Returns the flavor identifier
469 self
.logger
.debug("Adding flavor '%s'", str(flavor_data
))
473 name
=flavor_data
['name']
474 while retry
<max_retries
:
477 self
._reload
_connection
()
478 if change_name_if_used
:
481 fl
=self
.nova
.flavors
.list()
483 fl_names
.append(f
.name
)
484 while name
in fl_names
:
486 name
= flavor_data
['name']+"-" + str(name_suffix
)
488 ram
= flavor_data
.get('ram',64)
489 vcpus
= flavor_data
.get('vcpus',1)
492 extended
= flavor_data
.get("extended")
494 numas
=extended
.get("numas")
496 numa_nodes
= len(numas
)
498 return -1, "Can not add flavor with more than one numa"
499 numa_properties
= {"hw:numa_nodes":str(numa_nodes
)}
500 numa_properties
["hw:mem_page_size"] = "large"
501 numa_properties
["hw:cpu_policy"] = "dedicated"
502 numa_properties
["hw:numa_mempolicy"] = "strict"
504 #overwrite ram and vcpus
505 ram
= numa
['memory']*1024
506 #See for reference: https://specs.openstack.org/openstack/nova-specs/specs/mitaka/implemented/virt-driver-cpu-thread-pinning.html
507 if 'paired-threads' in numa
:
508 vcpus
= numa
['paired-threads']*2
509 #cpu_thread_policy "require" implies that the compute node must have an STM architecture
510 numa_properties
["hw:cpu_thread_policy"] = "require"
511 numa_properties
["hw:cpu_policy"] = "dedicated"
512 elif 'cores' in numa
:
513 vcpus
= numa
['cores']
514 # cpu_thread_policy "prefer" implies that the host must not have an SMT architecture, or a non-SMT architecture will be emulated
515 numa_properties
["hw:cpu_thread_policy"] = "isolate"
516 numa_properties
["hw:cpu_policy"] = "dedicated"
517 elif 'threads' in numa
:
518 vcpus
= numa
['threads']
519 # cpu_thread_policy "prefer" implies that the host may or may not have an SMT architecture
520 numa_properties
["hw:cpu_thread_policy"] = "prefer"
521 numa_properties
["hw:cpu_policy"] = "dedicated"
522 # for interface in numa.get("interfaces",() ):
523 # if interface["dedicated"]=="yes":
524 # raise vimconn.vimconnException("Passthrough interfaces are not supported for the openstack connector", http_code=vimconn.HTTP_Service_Unavailable)
525 # #TODO, add the key 'pci_passthrough:alias"="<label at config>:<number ifaces>"' when a way to connect it is available
528 new_flavor
=self
.nova
.flavors
.create(name
,
531 flavor_data
.get('disk',1),
532 is_public
=flavor_data
.get('is_public', True)
536 new_flavor
.set_keys(numa_properties
)
538 except nvExceptions
.Conflict
as e
:
539 if change_name_if_used
and retry
< max_retries
:
541 self
._format
_exception
(e
)
542 #except nvExceptions.BadRequest as e:
543 except (ksExceptions
.ClientException
, nvExceptions
.ClientException
, ConnectionError
) as e
:
544 self
._format
_exception
(e
)
546 def delete_flavor(self
,flavor_id
):
547 '''Deletes a tenant flavor from openstack VIM. Returns the old flavor_id
550 self
._reload
_connection
()
551 self
.nova
.flavors
.delete(flavor_id
)
553 #except nvExceptions.BadRequest as e:
554 except (nvExceptions
.NotFound
, ksExceptions
.ClientException
, nvExceptions
.ClientException
, ConnectionError
) as e
:
555 self
._format
_exception
(e
)
557 def new_image(self
,image_dict
):
559 Adds a tenant image to VIM. imge_dict is a dictionary with:
561 disk_format: qcow2, vhd, vmdk, raw (by default), ...
562 location: path or URI
563 public: "yes" or "no"
564 metadata: metadata of the image
567 # ALF TODO: revise and change for the new method or session
568 #using version 1 of glance client
569 glancev1
= gl1Client
.Client('1',self
.glance_endpoint
, token
=self
.keystone
.auth_token
, **self
.k_creds
) #TODO check k_creds vs n_creds
572 while retry
<max_retries
:
575 self
._reload
_connection
()
576 #determine format http://docs.openstack.org/developer/glance/formats.html
577 if "disk_format" in image_dict
:
578 disk_format
=image_dict
["disk_format"]
579 else: #autodiscover based on extension
580 if image_dict
['location'][-6:]==".qcow2":
582 elif image_dict
['location'][-4:]==".vhd":
584 elif image_dict
['location'][-5:]==".vmdk":
586 elif image_dict
['location'][-4:]==".vdi":
588 elif image_dict
['location'][-4:]==".iso":
590 elif image_dict
['location'][-4:]==".aki":
592 elif image_dict
['location'][-4:]==".ari":
594 elif image_dict
['location'][-4:]==".ami":
598 self
.logger
.debug("new_image: '%s' loading from '%s'", image_dict
['name'], image_dict
['location'])
599 if image_dict
['location'][0:4]=="http":
600 new_image
= glancev1
.images
.create(name
=image_dict
['name'], is_public
=image_dict
.get('public',"yes")=="yes",
601 container_format
="bare", location
=image_dict
['location'], disk_format
=disk_format
)
603 with
open(image_dict
['location']) as fimage
:
604 new_image
= glancev1
.images
.create(name
=image_dict
['name'], is_public
=image_dict
.get('public',"yes")=="yes",
605 container_format
="bare", data
=fimage
, disk_format
=disk_format
)
606 #insert metadata. We cannot use 'new_image.properties.setdefault'
607 #because nova and glance are "INDEPENDENT" and we are using nova for reading metadata
608 new_image_nova
=self
.nova
.images
.find(id=new_image
.id)
609 new_image_nova
.metadata
.setdefault('location',image_dict
['location'])
610 metadata_to_load
= image_dict
.get('metadata')
612 for k
,v
in yaml
.load(metadata_to_load
).iteritems():
613 new_image_nova
.metadata
.setdefault(k
,v
)
615 except (nvExceptions
.Conflict
, ksExceptions
.ClientException
, nvExceptions
.ClientException
) as e
:
616 self
._format
_exception
(e
)
617 except (HTTPException
, gl1Exceptions
.HTTPException
, gl1Exceptions
.CommunicationError
, ConnectionError
) as e
:
618 if retry
==max_retries
:
620 self
._format
_exception
(e
)
621 except IOError as e
: #can not open the file
622 raise vimconn
.vimconnConnectionException(type(e
).__name
__ + ": " + str(e
)+ " for " + image_dict
['location'],
623 http_code
=vimconn
.HTTP_Bad_Request
)
625 def delete_image(self
, image_id
):
626 '''Deletes a tenant image from openstack VIM. Returns the old id
629 self
._reload
_connection
()
630 self
.nova
.images
.delete(image_id
)
632 except (nvExceptions
.NotFound
, ksExceptions
.ClientException
, nvExceptions
.ClientException
, gl1Exceptions
.CommunicationError
, ConnectionError
) as e
: #TODO remove
633 self
._format
_exception
(e
)
635 def get_image_id_from_path(self
, path
):
636 '''Get the image id from image path in the VIM database. Returns the image_id'''
638 self
._reload
_connection
()
639 images
= self
.nova
.images
.list()
641 if image
.metadata
.get("location")==path
:
643 raise vimconn
.vimconnNotFoundException("image with location '{}' not found".format( path
))
644 except (ksExceptions
.ClientException
, nvExceptions
.ClientException
, gl1Exceptions
.CommunicationError
, ConnectionError
) as e
:
645 self
._format
_exception
(e
)
647 def get_image_list(self
, filter_dict
={}):
648 '''Obtain tenant images from VIM
652 checksum: image checksum
653 Returns the image list of dictionaries:
654 [{<the fields at Filter_dict plus some VIM specific>}, ...]
657 self
.logger
.debug("Getting image list from VIM filter: '%s'", str(filter_dict
))
659 self
._reload
_connection
()
660 filter_dict_os
=filter_dict
.copy()
661 #First we filter by the available filter fields: name, id. The others are removed.
662 filter_dict_os
.pop('checksum',None)
663 image_list
=self
.nova
.images
.findall(**filter_dict_os
)
664 if len(image_list
)==0:
666 #Then we filter by the rest of filter fields: checksum
668 for image
in image_list
:
669 image_class
=self
.glance
.images
.get(image
.id)
670 if 'checksum' not in filter_dict
or image_class
['checksum']==filter_dict
.get('checksum'):
671 filtered_list
.append(image_class
.copy())
673 except (ksExceptions
.ClientException
, nvExceptions
.ClientException
, gl1Exceptions
.CommunicationError
, ConnectionError
) as e
:
674 self
._format
_exception
(e
)
676 def __wait_for_vm(self
, vm_id
, status
):
677 """wait until vm is in the desired status and return True.
678 If the VM gets in ERROR status, return false.
679 If the timeout is reached generate an exception"""
681 while elapsed_time
< server_timeout
:
682 vm_status
= self
.nova
.servers
.get(vm_id
).status
683 if vm_status
== status
:
685 if vm_status
== 'ERROR':
690 # if we exceeded the timeout rollback
691 if elapsed_time
>= server_timeout
:
692 raise vimconn
.vimconnException('Timeout waiting for instance ' + vm_id
+ ' to get ' + status
,
693 http_code
=vimconn
.HTTP_Request_Timeout
)
695 def new_vminstance(self
,name
,description
,start
,image_id
,flavor_id
,net_list
,cloud_config
=None,disk_list
=None):
696 '''Adds a VM instance to VIM
698 start: indicates if VM must start or boot in pause mode. Ignored
699 image_id,flavor_id: iamge and flavor uuid
700 net_list: list of interfaces, each one is a dictionary with:
702 net_id: network uuid to connect
703 vpci: virtual vcpi to assign, ignored because openstack lack #TODO
704 model: interface model, ignored #TODO
705 mac_address: used for SR-IOV ifaces #TODO for other types
706 use: 'data', 'bridge', 'mgmt'
707 type: 'virtual', 'PF', 'VF', 'VFnotShared'
708 vim_id: filled/added by this function
709 floating_ip: True/False (or it can be None)
710 #TODO ip, security groups
711 Returns the instance identifier
713 self
.logger
.debug("new_vminstance input: image='%s' flavor='%s' nics='%s'",image_id
, flavor_id
,str(net_list
))
718 external_network
=[] # list of external networks to be connected to instance, later on used to create floating_ip
719 no_secured_ports
= [] # List of port-is with port-security disabled
720 self
._reload
_connection
()
721 metadata_vpci
={} # For a specific neutron plugin
722 block_device_mapping
= None
724 if not net
.get("net_id"): #skip non connected iface
728 "network_id": net
["net_id"],
729 "name": net
.get("name"),
730 "admin_state_up": True
732 if net
["type"]=="virtual":
734 metadata_vpci
[ net
["net_id"] ] = [[ net
["vpci"], "" ]]
735 elif net
["type"]=="VF": # for VF
737 if "VF" not in metadata_vpci
:
738 metadata_vpci
["VF"]=[]
739 metadata_vpci
["VF"].append([ net
["vpci"], "" ])
740 port_dict
["binding:vnic_type"]="direct"
743 if "PF" not in metadata_vpci
:
744 metadata_vpci
["PF"]=[]
745 metadata_vpci
["PF"].append([ net
["vpci"], "" ])
746 port_dict
["binding:vnic_type"]="direct-physical"
747 if not port_dict
["name"]:
748 port_dict
["name"]=name
749 if net
.get("mac_address"):
750 port_dict
["mac_address"]=net
["mac_address"]
751 new_port
= self
.neutron
.create_port({"port": port_dict
})
752 net
["mac_adress"] = new_port
["port"]["mac_address"]
753 net
["vim_id"] = new_port
["port"]["id"]
754 # if try to use a network without subnetwork, it will return a emtpy list
755 fixed_ips
= new_port
["port"].get("fixed_ips")
757 net
["ip"] = fixed_ips
[0].get("ip_address")
760 net_list_vim
.append({"port-id": new_port
["port"]["id"]})
762 if net
.get('floating_ip', False):
763 net
['exit_on_floating_ip_error'] = True
764 external_network
.append(net
)
765 elif net
['use'] == 'mgmt' and self
.config
.get('use_floating_ip'):
766 net
['exit_on_floating_ip_error'] = False
767 external_network
.append(net
)
769 # If port security is disabled when the port has not yet been attached to the VM, then all vm traffic is dropped.
770 # As a workaround we wait until the VM is active and then disable the port-security
771 if net
.get("port_security") == False:
772 no_secured_ports
.append(new_port
["port"]["id"])
775 metadata
= {"pci_assignement": json
.dumps(metadata_vpci
)}
776 if len(metadata
["pci_assignement"]) >255:
777 #limit the metadata size
778 #metadata["pci_assignement"] = metadata["pci_assignement"][0:255]
779 self
.logger
.warn("Metadata deleted since it exceeds the expected length (255) ")
782 self
.logger
.debug("name '%s' image_id '%s'flavor_id '%s' net_list_vim '%s' description '%s' metadata %s",
783 name
, image_id
, flavor_id
, str(net_list_vim
), description
, str(metadata
))
785 security_groups
= self
.config
.get('security_groups')
786 if type(security_groups
) is str:
787 security_groups
= ( security_groups
, )
791 if isinstance(cloud_config
, dict):
792 if cloud_config
.get("user-data"):
793 userdata
=cloud_config
["user-data"]
794 if cloud_config
.get("boot-data-drive") != None:
795 config_drive
= cloud_config
["boot-data-drive"]
796 if cloud_config
.get("config-files") or cloud_config
.get("users") or cloud_config
.get("key-pairs"):
798 raise vimconn
.vimconnConflictException("Cloud-config cannot contain both 'userdata' and 'config-files'/'users'/'key-pairs'")
801 if cloud_config
.get("key-pairs"):
802 userdata_dict
["ssh-authorized-keys"] = cloud_config
["key-pairs"]
803 userdata_dict
["users"] = [{"default": None, "ssh-authorized-keys": cloud_config
["key-pairs"] }]
804 if cloud_config
.get("users"):
805 if "users" not in userdata_dict
:
806 userdata_dict
["users"] = [ "default" ]
807 for user
in cloud_config
["users"]:
809 "name" : user
["name"],
810 "sudo": "ALL = (ALL)NOPASSWD:ALL"
812 if "user-info" in user
:
813 user_info
["gecos"] = user
["user-info"]
814 if user
.get("key-pairs"):
815 user_info
["ssh-authorized-keys"] = user
["key-pairs"]
816 userdata_dict
["users"].append(user_info
)
818 if cloud_config
.get("config-files"):
819 userdata_dict
["write_files"] = []
820 for file in cloud_config
["config-files"]:
822 "path" : file["dest"],
823 "content": file["content"]
825 if file.get("encoding"):
826 file_info
["encoding"] = file["encoding"]
827 if file.get("permissions"):
828 file_info
["permissions"] = file["permissions"]
829 if file.get("owner"):
830 file_info
["owner"] = file["owner"]
831 userdata_dict
["write_files"].append(file_info
)
832 userdata
= "#cloud-config\n"
833 userdata
+= yaml
.safe_dump(userdata_dict
, indent
=4, default_flow_style
=False)
834 self
.logger
.debug("userdata: %s", userdata
)
835 elif isinstance(cloud_config
, str):
836 userdata
= cloud_config
838 #Create additional volumes in case these are present in disk_list
839 base_disk_index
= ord('b')
840 if disk_list
!= None:
841 block_device_mapping
= {}
842 for disk
in disk_list
:
843 if 'image_id' in disk
:
844 volume
= self
.cinder
.volumes
.create(size
= disk
['size'],name
= name
+ '_vd' +
845 chr(base_disk_index
), imageRef
= disk
['image_id'])
847 volume
= self
.cinder
.volumes
.create(size
=disk
['size'], name
=name
+ '_vd' +
848 chr(base_disk_index
))
849 block_device_mapping
['_vd' + chr(base_disk_index
)] = volume
.id
852 #wait until volumes are with status available
855 while keep_waiting
and elapsed_time
< volume_timeout
:
857 for volume_id
in block_device_mapping
.itervalues():
858 if self
.cinder
.volumes
.get(volume_id
).status
!= 'available':
864 #if we exceeded the timeout rollback
865 if elapsed_time
>= volume_timeout
:
866 #delete the volumes we just created
867 for volume_id
in block_device_mapping
.itervalues():
868 self
.cinder
.volumes
.delete(volume_id
)
870 #delete ports we just created
871 for net_item
in net_list_vim
:
872 if 'port-id' in net_item
:
873 self
.neutron
.delete_port(net_item
['port-id'])
875 raise vimconn
.vimconnException('Timeout creating volumes for instance ' + name
,
876 http_code
=vimconn
.HTTP_Request_Timeout
)
878 self
.logger
.debug("nova.servers.create({}, {}, {}, nics={}, meta={}, security_groups={}," \
879 "availability_zone={}, key_name={}, userdata={}, config_drive={}, " \
880 "block_device_mapping={})".format(name
, image_id
, flavor_id
, net_list_vim
,
881 metadata
, security_groups
, self
.config
.get('availability_zone'),
882 self
.config
.get('keypair'), userdata
, config_drive
, block_device_mapping
))
883 server
= self
.nova
.servers
.create(name
, image_id
, flavor_id
, nics
=net_list_vim
, meta
=metadata
,
884 security_groups
=security_groups
,
885 availability_zone
=self
.config
.get('availability_zone'),
886 key_name
=self
.config
.get('keypair'),
888 config_drive
=config_drive
,
889 block_device_mapping
=block_device_mapping
890 ) # , description=description)
892 # Previously mentioned workaround to wait until the VM is active and then disable the port-security
894 self
.__wait
_for
_vm
(server
.id, 'ACTIVE')
896 for port_id
in no_secured_ports
:
898 self
.neutron
.update_port(port_id
, {"port": {"port_security_enabled": False, "security_groups": None} })
900 except Exception as e
:
901 self
.logger
.error("It was not possible to disable port security for port {}".format(port_id
))
902 self
.delete_vminstance(server
.id)
905 #print "DONE :-)", server
907 floating_ips
= self
.neutron
.list_floatingips().get("floatingips", ())
910 self
.__wait
_for
_vm
(server
.id, 'ACTIVE')
912 for floating_network
in external_network
:
915 while(assigned
== False):
917 ip
= floating_ips
.pop(0)
918 if not ip
.get("port_id", False) and ip
.get('tenant_id') == server
.tenant_id
:
919 free_floating_ip
= ip
.get("floating_ip_address")
921 fix_ip
= floating_network
.get('ip')
922 server
.add_floating_ip(free_floating_ip
, fix_ip
)
924 except Exception as e
:
925 raise vimconn
.vimconnException(type(e
).__name
__ + ": Cannot create floating_ip "+ str(e
), http_code
=vimconn
.HTTP_Conflict
)
927 #Find the external network
928 external_nets
= list()
929 for net
in self
.neutron
.list_networks()['networks']:
930 if net
['router:external']:
931 external_nets
.append(net
)
933 if len(external_nets
) == 0:
934 raise vimconn
.vimconnException("Cannot create floating_ip automatically since no external "
935 "network is present",
936 http_code
=vimconn
.HTTP_Conflict
)
937 if len(external_nets
) > 1:
938 raise vimconn
.vimconnException("Cannot create floating_ip automatically since multiple "
939 "external networks are present",
940 http_code
=vimconn
.HTTP_Conflict
)
942 pool_id
= external_nets
[0].get('id')
943 param
= {'floatingip': {'floating_network_id': pool_id
, 'tenant_id': server
.tenant_id
}}
945 #self.logger.debug("Creating floating IP")
946 new_floating_ip
= self
.neutron
.create_floatingip(param
)
947 free_floating_ip
= new_floating_ip
['floatingip']['floating_ip_address']
948 fix_ip
= floating_network
.get('ip')
949 server
.add_floating_ip(free_floating_ip
, fix_ip
)
951 except Exception as e
:
952 raise vimconn
.vimconnException(type(e
).__name
__ + ": Cannot assign floating_ip "+ str(e
), http_code
=vimconn
.HTTP_Conflict
)
953 except Exception as e
:
954 if not floating_network
['exit_on_floating_ip_error']:
955 self
.logger
.warn("Cannot create floating_ip. %s", str(e
))
960 # except nvExceptions.NotFound as e:
961 # error_value=-vimconn.HTTP_Not_Found
962 # error_text= "vm instance %s not found" % vm_id
963 # except TypeError as e:
964 # raise vimconn.vimconnException(type(e).__name__ + ": "+ str(e), http_code=vimconn.HTTP_Bad_Request)
966 except Exception as e
:
967 # delete the volumes we just created
968 if block_device_mapping
:
969 for volume_id
in block_device_mapping
.itervalues():
970 self
.cinder
.volumes
.delete(volume_id
)
974 self
.delete_vminstance(server
.id)
976 # delete ports we just created
977 for net_item
in net_list_vim
:
978 if 'port-id' in net_item
:
979 self
.neutron
.delete_port(net_item
['port-id'])
981 self
._format
_exception
(e
)
983 def get_vminstance(self
,vm_id
):
984 '''Returns the VM instance information from VIM'''
985 #self.logger.debug("Getting VM from VIM")
987 self
._reload
_connection
()
988 server
= self
.nova
.servers
.find(id=vm_id
)
989 #TODO parse input and translate to VIM format (openmano_schemas.new_vminstance_response_schema)
990 return server
.to_dict()
991 except (ksExceptions
.ClientException
, nvExceptions
.ClientException
, nvExceptions
.NotFound
, ConnectionError
) as e
:
992 self
._format
_exception
(e
)
994 def get_vminstance_console(self
,vm_id
, console_type
="vnc"):
996 Get a console for the virtual machine
998 vm_id: uuid of the VM
999 console_type, can be:
1000 "novnc" (by default), "xvpvnc" for VNC types,
1001 "rdp-html5" for RDP types, "spice-html5" for SPICE types
1002 Returns dict with the console parameters:
1003 protocol: ssh, ftp, http, https, ...
1004 server: usually ip address
1005 port: the http, ssh, ... port
1006 suffix: extra text, e.g. the http path and query string
1008 self
.logger
.debug("Getting VM CONSOLE from VIM")
1010 self
._reload
_connection
()
1011 server
= self
.nova
.servers
.find(id=vm_id
)
1012 if console_type
== None or console_type
== "novnc":
1013 console_dict
= server
.get_vnc_console("novnc")
1014 elif console_type
== "xvpvnc":
1015 console_dict
= server
.get_vnc_console(console_type
)
1016 elif console_type
== "rdp-html5":
1017 console_dict
= server
.get_rdp_console(console_type
)
1018 elif console_type
== "spice-html5":
1019 console_dict
= server
.get_spice_console(console_type
)
1021 raise vimconn
.vimconnException("console type '{}' not allowed".format(console_type
), http_code
=vimconn
.HTTP_Bad_Request
)
1023 console_dict1
= console_dict
.get("console")
1025 console_url
= console_dict1
.get("url")
1028 protocol_index
= console_url
.find("//")
1029 suffix_index
= console_url
[protocol_index
+2:].find("/") + protocol_index
+2
1030 port_index
= console_url
[protocol_index
+2:suffix_index
].find(":") + protocol_index
+2
1031 if protocol_index
< 0 or port_index
<0 or suffix_index
<0:
1032 return -vimconn
.HTTP_Internal_Server_Error
, "Unexpected response from VIM"
1033 console_dict
={"protocol": console_url
[0:protocol_index
],
1034 "server": console_url
[protocol_index
+2:port_index
],
1035 "port": console_url
[port_index
:suffix_index
],
1036 "suffix": console_url
[suffix_index
+1:]
1040 raise vimconn
.vimconnUnexpectedResponse("Unexpected response from VIM")
1042 except (nvExceptions
.NotFound
, ksExceptions
.ClientException
, nvExceptions
.ClientException
, nvExceptions
.BadRequest
, ConnectionError
) as e
:
1043 self
._format
_exception
(e
)
1045 def delete_vminstance(self
, vm_id
):
1046 '''Removes a VM instance from VIM. Returns the old identifier
1048 #print "osconnector: Getting VM from VIM"
1050 self
._reload
_connection
()
1051 #delete VM ports attached to this networks before the virtual machine
1052 ports
= self
.neutron
.list_ports(device_id
=vm_id
)
1053 for p
in ports
['ports']:
1055 self
.neutron
.delete_port(p
["id"])
1056 except Exception as e
:
1057 self
.logger
.error("Error deleting port: " + type(e
).__name
__ + ": "+ str(e
))
1059 #commented because detaching the volumes makes the servers.delete not work properly ?!?
1060 #dettach volumes attached
1061 server
= self
.nova
.servers
.get(vm_id
)
1062 volumes_attached_dict
= server
._info
['os-extended-volumes:volumes_attached']
1063 #for volume in volumes_attached_dict:
1064 # self.cinder.volumes.detach(volume['id'])
1066 self
.nova
.servers
.delete(vm_id
)
1069 #Although having detached them should have them in active status
1070 #we ensure in this loop
1073 while keep_waiting
and elapsed_time
< volume_timeout
:
1074 keep_waiting
= False
1075 for volume
in volumes_attached_dict
:
1076 if self
.cinder
.volumes
.get(volume
['id']).status
!= 'available':
1079 self
.cinder
.volumes
.delete(volume
['id'])
1085 except (nvExceptions
.NotFound
, ksExceptions
.ClientException
, nvExceptions
.ClientException
, ConnectionError
) as e
:
1086 self
._format
_exception
(e
)
1087 #TODO insert exception vimconn.HTTP_Unauthorized
1088 #if reaching here is because an exception
1090 def refresh_vms_status(self
, vm_list
):
1091 '''Get the status of the virtual machines and their interfaces/ports
1092 Params: the list of VM identifiers
1093 Returns a dictionary with:
1094 vm_id: #VIM id of this Virtual Machine
1095 status: #Mandatory. Text with one of:
1096 # DELETED (not found at vim)
1097 # VIM_ERROR (Cannot connect to VIM, VIM response error, ...)
1098 # OTHER (Vim reported other status not understood)
1099 # ERROR (VIM indicates an ERROR status)
1100 # ACTIVE, PAUSED, SUSPENDED, INACTIVE (not running),
1101 # CREATING (on building process), ERROR
1102 # ACTIVE:NoMgmtIP (Active but any of its interface has an IP address
1104 error_msg: #Text with VIM error message, if any. Or the VIM connection ERROR
1105 vim_info: #Text with plain information obtained from vim (yaml.safe_dump)
1107 - vim_info: #Text with plain information obtained from vim (yaml.safe_dump)
1108 mac_address: #Text format XX:XX:XX:XX:XX:XX
1109 vim_net_id: #network id where this interface is connected
1110 vim_interface_id: #interface/port VIM id
1111 ip_address: #null, or text with IPv4, IPv6 address
1112 compute_node: #identification of compute node where PF,VF interface is allocated
1113 pci: #PCI address of the NIC that hosts the PF,VF
1114 vlan: #physical VLAN used for VF
1117 self
.logger
.debug("refresh_vms status: Getting tenant VM instance information from VIM")
1118 for vm_id
in vm_list
:
1121 vm_vim
= self
.get_vminstance(vm_id
)
1122 if vm_vim
['status'] in vmStatus2manoFormat
:
1123 vm
['status'] = vmStatus2manoFormat
[ vm_vim
['status'] ]
1125 vm
['status'] = "OTHER"
1126 vm
['error_msg'] = "VIM status reported " + vm_vim
['status']
1128 vm
['vim_info'] = yaml
.safe_dump(vm_vim
, default_flow_style
=True, width
=256)
1129 except yaml
.representer
.RepresenterError
:
1130 vm
['vim_info'] = str(vm_vim
)
1131 vm
["interfaces"] = []
1132 if vm_vim
.get('fault'):
1133 vm
['error_msg'] = str(vm_vim
['fault'])
1136 self
._reload
_connection
()
1137 port_dict
=self
.neutron
.list_ports(device_id
=vm_id
)
1138 for port
in port_dict
["ports"]:
1141 interface
['vim_info'] = yaml
.safe_dump(port
, default_flow_style
=True, width
=256)
1142 except yaml
.representer
.RepresenterError
:
1143 interface
['vim_info'] = str(port
)
1144 interface
["mac_address"] = port
.get("mac_address")
1145 interface
["vim_net_id"] = port
["network_id"]
1146 interface
["vim_interface_id"] = port
["id"]
1147 # check if OS-EXT-SRV-ATTR:host is there,
1148 # in case of non-admin credentials, it will be missing
1149 if vm_vim
.get('OS-EXT-SRV-ATTR:host'):
1150 interface
["compute_node"] = vm_vim
['OS-EXT-SRV-ATTR:host']
1151 interface
["pci"] = None
1153 # check if binding:profile is there,
1154 # in case of non-admin credentials, it will be missing
1155 if port
.get('binding:profile'):
1156 if port
['binding:profile'].get('pci_slot'):
1157 # TODO: At the moment sr-iov pci addresses are converted to PF pci addresses by setting the slot to 0x00
1158 # TODO: This is just a workaround valid for niantinc. Find a better way to do so
1159 # CHANGE DDDD:BB:SS.F to DDDD:BB:00.(F%2) assuming there are 2 ports per nic
1160 pci
= port
['binding:profile']['pci_slot']
1161 # interface["pci"] = pci[:-4] + "00." + str(int(pci[-1]) % 2)
1162 interface
["pci"] = pci
1163 interface
["vlan"] = None
1164 #if network is of type vlan and port is of type direct (sr-iov) then set vlan id
1165 network
= self
.neutron
.show_network(port
["network_id"])
1166 if network
['network'].get('provider:network_type') == 'vlan' and \
1167 port
.get("binding:vnic_type") == "direct":
1168 interface
["vlan"] = network
['network'].get('provider:segmentation_id')
1170 #look for floating ip address
1171 floating_ip_dict
= self
.neutron
.list_floatingips(port_id
=port
["id"])
1172 if floating_ip_dict
.get("floatingips"):
1173 ips
.append(floating_ip_dict
["floatingips"][0].get("floating_ip_address") )
1175 for subnet
in port
["fixed_ips"]:
1176 ips
.append(subnet
["ip_address"])
1177 interface
["ip_address"] = ";".join(ips
)
1178 vm
["interfaces"].append(interface
)
1179 except Exception as e
:
1180 self
.logger
.error("Error getting vm interface information " + type(e
).__name
__ + ": "+ str(e
))
1181 except vimconn
.vimconnNotFoundException
as e
:
1182 self
.logger
.error("Exception getting vm status: %s", str(e
))
1183 vm
['status'] = "DELETED"
1184 vm
['error_msg'] = str(e
)
1185 except vimconn
.vimconnException
as e
:
1186 self
.logger
.error("Exception getting vm status: %s", str(e
))
1187 vm
['status'] = "VIM_ERROR"
1188 vm
['error_msg'] = str(e
)
1192 def action_vminstance(self
, vm_id
, action_dict
):
1193 '''Send and action over a VM instance from VIM
1194 Returns the vm_id if the action was successfully sent to the VIM'''
1195 self
.logger
.debug("Action over VM '%s': %s", vm_id
, str(action_dict
))
1197 self
._reload
_connection
()
1198 server
= self
.nova
.servers
.find(id=vm_id
)
1199 if "start" in action_dict
:
1200 if action_dict
["start"]=="rebuild":
1203 if server
.status
=="PAUSED":
1205 elif server
.status
=="SUSPENDED":
1207 elif server
.status
=="SHUTOFF":
1209 elif "pause" in action_dict
:
1211 elif "resume" in action_dict
:
1213 elif "shutoff" in action_dict
or "shutdown" in action_dict
:
1215 elif "forceOff" in action_dict
:
1217 elif "terminate" in action_dict
:
1219 elif "createImage" in action_dict
:
1220 server
.create_image()
1221 #"path":path_schema,
1222 #"description":description_schema,
1223 #"name":name_schema,
1224 #"metadata":metadata_schema,
1225 #"imageRef": id_schema,
1226 #"disk": {"oneOf":[{"type": "null"}, {"type":"string"}] },
1227 elif "rebuild" in action_dict
:
1228 server
.rebuild(server
.image
['id'])
1229 elif "reboot" in action_dict
:
1230 server
.reboot() #reboot_type='SOFT'
1231 elif "console" in action_dict
:
1232 console_type
= action_dict
["console"]
1233 if console_type
== None or console_type
== "novnc":
1234 console_dict
= server
.get_vnc_console("novnc")
1235 elif console_type
== "xvpvnc":
1236 console_dict
= server
.get_vnc_console(console_type
)
1237 elif console_type
== "rdp-html5":
1238 console_dict
= server
.get_rdp_console(console_type
)
1239 elif console_type
== "spice-html5":
1240 console_dict
= server
.get_spice_console(console_type
)
1242 raise vimconn
.vimconnException("console type '{}' not allowed".format(console_type
),
1243 http_code
=vimconn
.HTTP_Bad_Request
)
1245 console_url
= console_dict
["console"]["url"]
1247 protocol_index
= console_url
.find("//")
1248 suffix_index
= console_url
[protocol_index
+2:].find("/") + protocol_index
+2
1249 port_index
= console_url
[protocol_index
+2:suffix_index
].find(":") + protocol_index
+2
1250 if protocol_index
< 0 or port_index
<0 or suffix_index
<0:
1251 raise vimconn
.vimconnException("Unexpected response from VIM " + str(console_dict
))
1252 console_dict2
={"protocol": console_url
[0:protocol_index
],
1253 "server": console_url
[protocol_index
+2 : port_index
],
1254 "port": int(console_url
[port_index
+1 : suffix_index
]),
1255 "suffix": console_url
[suffix_index
+1:]
1257 return console_dict2
1258 except Exception as e
:
1259 raise vimconn
.vimconnException("Unexpected response from VIM " + str(console_dict
))
1262 except (ksExceptions
.ClientException
, nvExceptions
.ClientException
, nvExceptions
.NotFound
, ConnectionError
) as e
:
1263 self
._format
_exception
(e
)
1264 #TODO insert exception vimconn.HTTP_Unauthorized
1268 def new_external_port(self
, port_data
):
1269 #TODO openstack if needed
1270 '''Adds a external port to VIM'''
1271 '''Returns the port identifier'''
1272 return -vimconn
.HTTP_Internal_Server_Error
, "osconnector.new_external_port() not implemented"
1274 def connect_port_network(self
, port_id
, network_id
, admin
=False):
1275 #TODO openstack if needed
1276 '''Connects a external port to a network'''
1277 '''Returns status code of the VIM response'''
1278 return -vimconn
.HTTP_Internal_Server_Error
, "osconnector.connect_port_network() not implemented"
1280 def new_user(self
, user_name
, user_passwd
, tenant_id
=None):
1281 '''Adds a new user to openstack VIM'''
1282 '''Returns the user identifier'''
1283 self
.logger
.debug("osconnector: Adding a new user to VIM")
1285 self
._reload
_connection
()
1286 user
=self
.keystone
.users
.create(user_name
, user_passwd
, tenant_id
=tenant_id
)
1287 #self.keystone.tenants.add_user(self.k_creds["username"], #role)
1289 except ksExceptions
.ConnectionError
as e
:
1290 error_value
=-vimconn
.HTTP_Bad_Request
1291 error_text
= type(e
).__name
__ + ": "+ (str(e
) if len(e
.args
)==0 else str(e
.args
[0]))
1292 except ksExceptions
.ClientException
as e
: #TODO remove
1293 error_value
=-vimconn
.HTTP_Bad_Request
1294 error_text
= type(e
).__name
__ + ": "+ (str(e
) if len(e
.args
)==0 else str(e
.args
[0]))
1295 #TODO insert exception vimconn.HTTP_Unauthorized
1296 #if reaching here is because an exception
1298 self
.logger
.debug("new_user " + error_text
)
1299 return error_value
, error_text
1301 def delete_user(self
, user_id
):
1302 '''Delete a user from openstack VIM'''
1303 '''Returns the user identifier'''
1305 print "osconnector: Deleting a user from VIM"
1307 self
._reload
_connection
()
1308 self
.keystone
.users
.delete(user_id
)
1310 except ksExceptions
.ConnectionError
as e
:
1311 error_value
=-vimconn
.HTTP_Bad_Request
1312 error_text
= type(e
).__name
__ + ": "+ (str(e
) if len(e
.args
)==0 else str(e
.args
[0]))
1313 except ksExceptions
.NotFound
as e
:
1314 error_value
=-vimconn
.HTTP_Not_Found
1315 error_text
= type(e
).__name
__ + ": "+ (str(e
) if len(e
.args
)==0 else str(e
.args
[0]))
1316 except ksExceptions
.ClientException
as e
: #TODO remove
1317 error_value
=-vimconn
.HTTP_Bad_Request
1318 error_text
= type(e
).__name
__ + ": "+ (str(e
) if len(e
.args
)==0 else str(e
.args
[0]))
1319 #TODO insert exception vimconn.HTTP_Unauthorized
1320 #if reaching here is because an exception
1322 print "delete_tenant " + error_text
1323 return error_value
, error_text
1325 def get_hosts_info(self
):
1326 '''Get the information of deployed hosts
1327 Returns the hosts content'''
1329 print "osconnector: Getting Host info from VIM"
1332 self
._reload
_connection
()
1333 hypervisors
= self
.nova
.hypervisors
.list()
1334 for hype
in hypervisors
:
1335 h_list
.append( hype
.to_dict() )
1336 return 1, {"hosts":h_list
}
1337 except nvExceptions
.NotFound
as e
:
1338 error_value
=-vimconn
.HTTP_Not_Found
1339 error_text
= (str(e
) if len(e
.args
)==0 else str(e
.args
[0]))
1340 except (ksExceptions
.ClientException
, nvExceptions
.ClientException
) as e
:
1341 error_value
=-vimconn
.HTTP_Bad_Request
1342 error_text
= type(e
).__name
__ + ": "+ (str(e
) if len(e
.args
)==0 else str(e
.args
[0]))
1343 #TODO insert exception vimconn.HTTP_Unauthorized
1344 #if reaching here is because an exception
1346 print "get_hosts_info " + error_text
1347 return error_value
, error_text
1349 def get_hosts(self
, vim_tenant
):
1350 '''Get the hosts and deployed instances
1351 Returns the hosts content'''
1352 r
, hype_dict
= self
.get_hosts_info()
1355 hypervisors
= hype_dict
["hosts"]
1357 servers
= self
.nova
.servers
.list()
1358 for hype
in hypervisors
:
1359 for server
in servers
:
1360 if server
.to_dict()['OS-EXT-SRV-ATTR:hypervisor_hostname']==hype
['hypervisor_hostname']:
1362 hype
['vm'].append(server
.id)
1364 hype
['vm'] = [server
.id]
1366 except nvExceptions
.NotFound
as e
:
1367 error_value
=-vimconn
.HTTP_Not_Found
1368 error_text
= (str(e
) if len(e
.args
)==0 else str(e
.args
[0]))
1369 except (ksExceptions
.ClientException
, nvExceptions
.ClientException
) as e
:
1370 error_value
=-vimconn
.HTTP_Bad_Request
1371 error_text
= type(e
).__name
__ + ": "+ (str(e
) if len(e
.args
)==0 else str(e
.args
[0]))
1372 #TODO insert exception vimconn.HTTP_Unauthorized
1373 #if reaching here is because an exception
1375 print "get_hosts " + error_text
1376 return error_value
, error_text