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
.glancev1
= self
.session
.get('glancev1')
93 self
.keystone
= self
.session
.get('keystone')
94 self
.api_version3
= self
.session
.get('api_version3')
96 self
.logger
= logging
.getLogger('openmano.vim.openstack')
98 self
.logger
.setLevel( getattr(logging
, log_level
) )
100 def __getitem__(self
, index
):
101 """Get individuals parameters.
103 if index
== 'project_domain_id':
104 return self
.config
.get("project_domain_id")
105 elif index
== 'user_domain_id':
106 return self
.config
.get("user_domain_id")
108 return vimconn
.vimconnector
.__getitem
__(self
, index
)
110 def __setitem__(self
, index
, value
):
111 """Set individuals parameters and it is marked as dirty so to force connection reload.
113 if index
== 'project_domain_id':
114 self
.config
["project_domain_id"] = value
115 elif index
== 'user_domain_id':
116 self
.config
["user_domain_id"] = value
118 vimconn
.vimconnector
.__setitem
__(self
, index
, value
)
119 self
.session
['reload_client'] = True
121 def _reload_connection(self
):
122 '''Called before any operation, it check if credentials has changed
123 Throw keystoneclient.apiclient.exceptions.AuthorizationFailure
125 #TODO control the timing and possible token timeout, but it seams that python client does this task for us :-)
126 if self
.session
['reload_client']:
127 if self
.config
.get('APIversion'):
128 self
.api_version3
= self
.config
['APIversion'] == 'v3.3' or self
.config
['APIversion'] == '3'
129 else: # get from ending auth_url that end with v3 or with v2.0
130 self
.api_version3
= self
.url
.split("/")[-1] == "v3"
131 self
.session
['api_version3'] = self
.api_version3
132 if self
.api_version3
:
133 auth
= v3
.Password(auth_url
=self
.url
,
135 password
=self
.passwd
,
136 project_name
=self
.tenant_name
,
137 project_id
=self
.tenant_id
,
138 project_domain_id
=self
.config
.get('project_domain_id', 'default'),
139 user_domain_id
=self
.config
.get('user_domain_id', 'default'))
141 auth
= v2
.Password(auth_url
=self
.url
,
143 password
=self
.passwd
,
144 tenant_name
=self
.tenant_name
,
145 tenant_id
=self
.tenant_id
)
146 sess
= session
.Session(auth
=auth
, verify
=not self
.insecure
)
147 if self
.api_version3
:
148 self
.keystone
= ksClient_v3
.Client(session
=sess
)
150 self
.keystone
= ksClient_v2
.Client(session
=sess
)
151 self
.session
['keystone'] = self
.keystone
152 self
.nova
= self
.session
['nova'] = nClient
.Client("2.1", session
=sess
)
153 self
.neutron
= self
.session
['neutron'] = neClient
.Client('2.0', session
=sess
)
154 self
.cinder
= self
.session
['cinder'] = cClient
.Client(2, session
=sess
)
155 self
.glance
= self
.session
['glance'] = glClient
.Client(2, session
=sess
)
156 self
.glancev1
= self
.session
['glancev1'] = glClient
.Client('1', session
=sess
)
157 self
.session
['reload_client'] = False
158 self
.persistent_info
['session'] = self
.session
160 def __net_os2mano(self
, net_list_dict
):
161 '''Transform the net openstack format to mano format
162 net_list_dict can be a list of dict or a single dict'''
163 if type(net_list_dict
) is dict:
164 net_list_
=(net_list_dict
,)
165 elif type(net_list_dict
) is list:
166 net_list_
=net_list_dict
168 raise TypeError("param net_list_dict must be a list or a dictionary")
169 for net
in net_list_
:
170 if net
.get('provider:network_type') == "vlan":
175 def _format_exception(self
, exception
):
176 '''Transform a keystone, nova, neutron exception into a vimconn exception'''
177 if isinstance(exception
, (HTTPException
, gl1Exceptions
.HTTPException
, gl1Exceptions
.CommunicationError
,
178 ConnectionError
, ksExceptions
.ConnectionError
, neExceptions
.ConnectionFailed
180 raise vimconn
.vimconnConnectionException(type(exception
).__name
__ + ": " + str(exception
))
181 elif isinstance(exception
, (nvExceptions
.ClientException
, ksExceptions
.ClientException
,
182 neExceptions
.NeutronException
, nvExceptions
.BadRequest
)):
183 raise vimconn
.vimconnUnexpectedResponse(type(exception
).__name
__ + ": " + str(exception
))
184 elif isinstance(exception
, (neExceptions
.NetworkNotFoundClient
, nvExceptions
.NotFound
)):
185 raise vimconn
.vimconnNotFoundException(type(exception
).__name
__ + ": " + str(exception
))
186 elif isinstance(exception
, nvExceptions
.Conflict
):
187 raise vimconn
.vimconnConflictException(type(exception
).__name
__ + ": " + str(exception
))
188 elif isinstance(exception
, vimconn
.vimconnException
):
191 self
.logger
.error("General Exception " + str(exception
), exc_info
=True)
192 raise vimconn
.vimconnConnectionException(type(exception
).__name
__ + ": " + str(exception
))
194 def get_tenant_list(self
, filter_dict
={}):
195 '''Obtain tenants of VIM
196 filter_dict can contain the following keys:
197 name: filter by tenant name
198 id: filter by tenant uuid/id
200 Returns the tenant list of dictionaries: [{'name':'<name>, 'id':'<id>, ...}, ...]
202 self
.logger
.debug("Getting tenants from VIM filter: '%s'", str(filter_dict
))
204 self
._reload
_connection
()
205 if self
.api_version3
:
206 project_class_list
= self
.keystone
.projects
.list(name
=filter_dict
.get("name"))
208 project_class_list
= self
.keystone
.tenants
.findall(**filter_dict
)
210 for project
in project_class_list
:
211 if filter_dict
.get('id') and filter_dict
["id"] != project
.id:
213 project_list
.append(project
.to_dict())
215 except (ksExceptions
.ConnectionError
, ksExceptions
.ClientException
, ConnectionError
) as e
:
216 self
._format
_exception
(e
)
218 def new_tenant(self
, tenant_name
, tenant_description
):
219 '''Adds a new tenant to openstack VIM. Returns the tenant identifier'''
220 self
.logger
.debug("Adding a new tenant name: %s", tenant_name
)
222 self
._reload
_connection
()
223 if self
.api_version3
:
224 project
= self
.keystone
.projects
.create(tenant_name
, self
.config
.get("project_domain_id", "default"),
225 description
=tenant_description
, is_domain
=False)
227 project
= self
.keystone
.tenants
.create(tenant_name
, tenant_description
)
229 except (ksExceptions
.ConnectionError
, ksExceptions
.ClientException
, ConnectionError
) as e
:
230 self
._format
_exception
(e
)
232 def delete_tenant(self
, tenant_id
):
233 '''Delete a tenant from openstack VIM. Returns the old tenant identifier'''
234 self
.logger
.debug("Deleting tenant %s from VIM", tenant_id
)
236 self
._reload
_connection
()
237 if self
.api_version3
:
238 self
.keystone
.projects
.delete(tenant_id
)
240 self
.keystone
.tenants
.delete(tenant_id
)
242 except (ksExceptions
.ConnectionError
, ksExceptions
.ClientException
, ConnectionError
) as e
:
243 self
._format
_exception
(e
)
245 def new_network(self
,net_name
, net_type
, ip_profile
=None, shared
=False, vlan
=None):
246 '''Adds a tenant network to VIM. Returns the network identifier'''
247 self
.logger
.debug("Adding a new network to VIM name '%s', type '%s'", net_name
, net_type
)
248 #self.logger.debug(">>>>>>>>>>>>>>>>>> IP profile %s", str(ip_profile))
251 self
._reload
_connection
()
252 network_dict
= {'name': net_name
, 'admin_state_up': True}
253 if net_type
=="data" or net_type
=="ptp":
254 if self
.config
.get('dataplane_physical_net') == None:
255 raise vimconn
.vimconnConflictException("You must provide a 'dataplane_physical_net' at config value before creating sriov network")
256 network_dict
["provider:physical_network"] = self
.config
['dataplane_physical_net'] #"physnet_sriov" #TODO physical
257 network_dict
["provider:network_type"] = "vlan"
259 network_dict
["provider:network_type"] = vlan
260 network_dict
["shared"]=shared
261 new_net
=self
.neutron
.create_network({'network':network_dict
})
263 #create subnetwork, even if there is no profile
266 if 'subnet_address' not in ip_profile
:
267 #Fake subnet is required
268 subnet_rand
= random
.randint(0, 255)
269 ip_profile
['subnet_address'] = "192.168.{}.0/24".format(subnet_rand
)
270 if 'ip_version' not in ip_profile
:
271 ip_profile
['ip_version'] = "IPv4"
272 subnet
= {"name":net_name
+"-subnet",
273 "network_id": new_net
["network"]["id"],
274 "ip_version": 4 if ip_profile
['ip_version']=="IPv4" else 6,
275 "cidr": ip_profile
['subnet_address']
277 # Gateway should be set to None if not needed. Otherwise openstack assigns one by default
278 subnet
['gateway_ip'] = ip_profile
.get('gateway_address')
279 if ip_profile
.get('dns_address'):
280 subnet
['dns_nameservers'] = ip_profile
['dns_address'].split(";")
281 if 'dhcp_enabled' in ip_profile
:
282 subnet
['enable_dhcp'] = False if ip_profile
['dhcp_enabled']=="false" else True
283 if 'dhcp_start_address' in ip_profile
:
284 subnet
['allocation_pools'] = []
285 subnet
['allocation_pools'].append(dict())
286 subnet
['allocation_pools'][0]['start'] = ip_profile
['dhcp_start_address']
287 if 'dhcp_count' in ip_profile
:
288 #parts = ip_profile['dhcp_start_address'].split('.')
289 #ip_int = (int(parts[0]) << 24) + (int(parts[1]) << 16) + (int(parts[2]) << 8) + int(parts[3])
290 ip_int
= int(netaddr
.IPAddress(ip_profile
['dhcp_start_address']))
291 ip_int
+= ip_profile
['dhcp_count'] - 1
292 ip_str
= str(netaddr
.IPAddress(ip_int
))
293 subnet
['allocation_pools'][0]['end'] = ip_str
294 #self.logger.debug(">>>>>>>>>>>>>>>>>> Subnet: %s", str(subnet))
295 self
.neutron
.create_subnet({"subnet": subnet
} )
296 return new_net
["network"]["id"]
297 except (neExceptions
.ConnectionFailed
, ksExceptions
.ClientException
, neExceptions
.NeutronException
, ConnectionError
) as e
:
299 self
.neutron
.delete_network(new_net
['network']['id'])
300 self
._format
_exception
(e
)
302 def get_network_list(self
, filter_dict
={}):
303 '''Obtain tenant networks of VIM
309 admin_state_up: boolean
311 Returns the network list of dictionaries
313 self
.logger
.debug("Getting network from VIM filter: '%s'", str(filter_dict
))
315 self
._reload
_connection
()
316 if self
.api_version3
and "tenant_id" in filter_dict
:
317 filter_dict
['project_id'] = filter_dict
.pop('tenant_id') #TODO check
318 net_dict
=self
.neutron
.list_networks(**filter_dict
)
319 net_list
=net_dict
["networks"]
320 self
.__net
_os
2mano
(net_list
)
322 except (neExceptions
.ConnectionFailed
, ksExceptions
.ClientException
, neExceptions
.NeutronException
, ConnectionError
) as e
:
323 self
._format
_exception
(e
)
325 def get_network(self
, net_id
):
326 '''Obtain details of network from VIM
327 Returns the network information from a network id'''
328 self
.logger
.debug(" Getting tenant network %s from VIM", net_id
)
329 filter_dict
={"id": net_id
}
330 net_list
= self
.get_network_list(filter_dict
)
332 raise vimconn
.vimconnNotFoundException("Network '{}' not found".format(net_id
))
333 elif len(net_list
)>1:
334 raise vimconn
.vimconnConflictException("Found more than one network with this criteria")
337 for subnet_id
in net
.get("subnets", () ):
339 subnet
= self
.neutron
.show_subnet(subnet_id
)
340 except Exception as e
:
341 self
.logger
.error("osconnector.get_network(): Error getting subnet %s %s" % (net_id
, str(e
)))
342 subnet
= {"id": subnet_id
, "fault": str(e
)}
343 subnets
.append(subnet
)
344 net
["subnets"] = subnets
345 net
["encapsulation"] = net
.get('provider:network_type')
346 net
["segmentation_id"] = net
.get('provider:segmentation_id')
349 def delete_network(self
, net_id
):
350 '''Deletes a tenant network from VIM. Returns the old network identifier'''
351 self
.logger
.debug("Deleting network '%s' from VIM", net_id
)
353 self
._reload
_connection
()
354 #delete VM ports attached to this networks before the network
355 ports
= self
.neutron
.list_ports(network_id
=net_id
)
356 for p
in ports
['ports']:
358 self
.neutron
.delete_port(p
["id"])
359 except Exception as e
:
360 self
.logger
.error("Error deleting port %s: %s", p
["id"], str(e
))
361 self
.neutron
.delete_network(net_id
)
363 except (neExceptions
.ConnectionFailed
, neExceptions
.NetworkNotFoundClient
, neExceptions
.NeutronException
,
364 ksExceptions
.ClientException
, neExceptions
.NeutronException
, ConnectionError
) as e
:
365 self
._format
_exception
(e
)
367 def refresh_nets_status(self
, net_list
):
368 '''Get the status of the networks
369 Params: the list of network identifiers
370 Returns a dictionary with:
371 net_id: #VIM id of this network
372 status: #Mandatory. Text with one of:
373 # DELETED (not found at vim)
374 # VIM_ERROR (Cannot connect to VIM, VIM response error, ...)
375 # OTHER (Vim reported other status not understood)
376 # ERROR (VIM indicates an ERROR status)
377 # ACTIVE, INACTIVE, DOWN (admin down),
378 # BUILD (on building process)
380 error_msg: #Text with VIM error message, if any. Or the VIM connection ERROR
381 vim_info: #Text with plain information obtained from vim (yaml.safe_dump)
385 for net_id
in net_list
:
388 net_vim
= self
.get_network(net_id
)
389 if net_vim
['status'] in netStatus2manoFormat
:
390 net
["status"] = netStatus2manoFormat
[ net_vim
['status'] ]
392 net
["status"] = "OTHER"
393 net
["error_msg"] = "VIM status reported " + net_vim
['status']
395 if net
['status'] == "ACTIVE" and not net_vim
['admin_state_up']:
396 net
['status'] = 'DOWN'
398 net
['vim_info'] = yaml
.safe_dump(net_vim
, default_flow_style
=True, width
=256)
399 except yaml
.representer
.RepresenterError
:
400 net
['vim_info'] = str(net_vim
)
401 if net_vim
.get('fault'): #TODO
402 net
['error_msg'] = str(net_vim
['fault'])
403 except vimconn
.vimconnNotFoundException
as e
:
404 self
.logger
.error("Exception getting net status: %s", str(e
))
405 net
['status'] = "DELETED"
406 net
['error_msg'] = str(e
)
407 except vimconn
.vimconnException
as e
:
408 self
.logger
.error("Exception getting net status: %s", str(e
))
409 net
['status'] = "VIM_ERROR"
410 net
['error_msg'] = str(e
)
411 net_dict
[net_id
] = net
414 def get_flavor(self
, flavor_id
):
415 '''Obtain flavor details from the VIM. Returns the flavor dict details'''
416 self
.logger
.debug("Getting flavor '%s'", flavor_id
)
418 self
._reload
_connection
()
419 flavor
= self
.nova
.flavors
.find(id=flavor_id
)
420 #TODO parse input and translate to VIM format (openmano_schemas.new_vminstance_response_schema)
421 return flavor
.to_dict()
422 except (nvExceptions
.NotFound
, nvExceptions
.ClientException
, ksExceptions
.ClientException
, ConnectionError
) as e
:
423 self
._format
_exception
(e
)
425 def get_flavor_id_from_data(self
, flavor_dict
):
426 """Obtain flavor id that match the flavor description
427 Returns the flavor_id or raises a vimconnNotFoundException
428 flavor_dict: contains the required ram, vcpus, disk
429 If 'use_existing_flavors' is set to True at config, the closer flavor that provides same or more ram, vcpus
430 and disk is returned. Otherwise a flavor with exactly same ram, vcpus and disk is returned or a
431 vimconnNotFoundException is raised
433 exact_match
= False if self
.config
.get('use_existing_flavors') else True
435 self
._reload
_connection
()
436 flavor_candidate_id
= None
437 flavor_candidate_data
= (10000, 10000, 10000)
438 flavor_target
= (flavor_dict
["ram"], flavor_dict
["vcpus"], flavor_dict
["disk"])
440 numas
= flavor_dict
.get("extended", {}).get("numas")
443 raise vimconn
.vimconnNotFoundException("Flavor with EPA still not implemted")
445 # raise vimconn.vimconnNotFoundException("Cannot find any flavor with more than one numa")
447 # numas = extended.get("numas")
448 for flavor
in self
.nova
.flavors
.list():
449 epa
= flavor
.get_keys()
453 flavor_data
= (flavor
.ram
, flavor
.vcpus
, flavor
.disk
)
454 if flavor_data
== flavor_target
:
456 elif not exact_match
and flavor_target
< flavor_data
< flavor_candidate_data
:
457 flavor_candidate_id
= flavor
.id
458 flavor_candidate_data
= flavor_data
459 if not exact_match
and flavor_candidate_id
:
460 return flavor_candidate_id
461 raise vimconn
.vimconnNotFoundException("Cannot find any flavor matching '{}'".format(str(flavor_dict
)))
462 except (nvExceptions
.NotFound
, nvExceptions
.ClientException
, ksExceptions
.ClientException
, ConnectionError
) as e
:
463 self
._format
_exception
(e
)
466 def new_flavor(self
, flavor_data
, change_name_if_used
=True):
467 '''Adds a tenant flavor to openstack VIM
468 if change_name_if_used is True, it will change name in case of conflict, because it is not supported name repetition
469 Returns the flavor identifier
471 self
.logger
.debug("Adding flavor '%s'", str(flavor_data
))
475 name
=flavor_data
['name']
476 while retry
<max_retries
:
479 self
._reload
_connection
()
480 if change_name_if_used
:
483 fl
=self
.nova
.flavors
.list()
485 fl_names
.append(f
.name
)
486 while name
in fl_names
:
488 name
= flavor_data
['name']+"-" + str(name_suffix
)
490 ram
= flavor_data
.get('ram',64)
491 vcpus
= flavor_data
.get('vcpus',1)
494 extended
= flavor_data
.get("extended")
496 numas
=extended
.get("numas")
498 numa_nodes
= len(numas
)
500 return -1, "Can not add flavor with more than one numa"
501 numa_properties
= {"hw:numa_nodes":str(numa_nodes
)}
502 numa_properties
["hw:mem_page_size"] = "large"
503 numa_properties
["hw:cpu_policy"] = "dedicated"
504 numa_properties
["hw:numa_mempolicy"] = "strict"
506 #overwrite ram and vcpus
507 ram
= numa
['memory']*1024
508 #See for reference: https://specs.openstack.org/openstack/nova-specs/specs/mitaka/implemented/virt-driver-cpu-thread-pinning.html
509 if 'paired-threads' in numa
:
510 vcpus
= numa
['paired-threads']*2
511 #cpu_thread_policy "require" implies that the compute node must have an STM architecture
512 numa_properties
["hw:cpu_thread_policy"] = "require"
513 numa_properties
["hw:cpu_policy"] = "dedicated"
514 elif 'cores' in numa
:
515 vcpus
= numa
['cores']
516 # cpu_thread_policy "prefer" implies that the host must not have an SMT architecture, or a non-SMT architecture will be emulated
517 numa_properties
["hw:cpu_thread_policy"] = "isolate"
518 numa_properties
["hw:cpu_policy"] = "dedicated"
519 elif 'threads' in numa
:
520 vcpus
= numa
['threads']
521 # cpu_thread_policy "prefer" implies that the host may or may not have an SMT architecture
522 numa_properties
["hw:cpu_thread_policy"] = "prefer"
523 numa_properties
["hw:cpu_policy"] = "dedicated"
524 # for interface in numa.get("interfaces",() ):
525 # if interface["dedicated"]=="yes":
526 # raise vimconn.vimconnException("Passthrough interfaces are not supported for the openstack connector", http_code=vimconn.HTTP_Service_Unavailable)
527 # #TODO, add the key 'pci_passthrough:alias"="<label at config>:<number ifaces>"' when a way to connect it is available
530 new_flavor
=self
.nova
.flavors
.create(name
,
533 flavor_data
.get('disk',1),
534 is_public
=flavor_data
.get('is_public', True)
538 new_flavor
.set_keys(numa_properties
)
540 except nvExceptions
.Conflict
as e
:
541 if change_name_if_used
and retry
< max_retries
:
543 self
._format
_exception
(e
)
544 #except nvExceptions.BadRequest as e:
545 except (ksExceptions
.ClientException
, nvExceptions
.ClientException
, ConnectionError
) as e
:
546 self
._format
_exception
(e
)
548 def delete_flavor(self
,flavor_id
):
549 '''Deletes a tenant flavor from openstack VIM. Returns the old flavor_id
552 self
._reload
_connection
()
553 self
.nova
.flavors
.delete(flavor_id
)
555 #except nvExceptions.BadRequest as e:
556 except (nvExceptions
.NotFound
, ksExceptions
.ClientException
, nvExceptions
.ClientException
, ConnectionError
) as e
:
557 self
._format
_exception
(e
)
559 def new_image(self
,image_dict
):
561 Adds a tenant image to VIM. imge_dict is a dictionary with:
563 disk_format: qcow2, vhd, vmdk, raw (by default), ...
564 location: path or URI
565 public: "yes" or "no"
566 metadata: metadata of the image
571 while retry
<max_retries
:
574 self
._reload
_connection
()
575 #determine format http://docs.openstack.org/developer/glance/formats.html
576 if "disk_format" in image_dict
:
577 disk_format
=image_dict
["disk_format"]
578 else: #autodiscover based on extension
579 if image_dict
['location'][-6:]==".qcow2":
581 elif image_dict
['location'][-4:]==".vhd":
583 elif image_dict
['location'][-5:]==".vmdk":
585 elif image_dict
['location'][-4:]==".vdi":
587 elif image_dict
['location'][-4:]==".iso":
589 elif image_dict
['location'][-4:]==".aki":
591 elif image_dict
['location'][-4:]==".ari":
593 elif image_dict
['location'][-4:]==".ami":
597 self
.logger
.debug("new_image: '%s' loading from '%s'", image_dict
['name'], image_dict
['location'])
598 if image_dict
['location'][0:4]=="http":
599 new_image
= self
.glancev1
.images
.create(name
=image_dict
['name'], is_public
=image_dict
.get('public',"yes")=="yes",
600 container_format
="bare", location
=image_dict
['location'], disk_format
=disk_format
)
602 with
open(image_dict
['location']) as fimage
:
603 new_image
= self
.glancev1
.images
.create(name
=image_dict
['name'], is_public
=image_dict
.get('public',"yes")=="yes",
604 container_format
="bare", data
=fimage
, disk_format
=disk_format
)
605 #insert metadata. We cannot use 'new_image.properties.setdefault'
606 #because nova and glance are "INDEPENDENT" and we are using nova for reading metadata
607 new_image_nova
=self
.nova
.images
.find(id=new_image
.id)
608 new_image_nova
.metadata
.setdefault('location',image_dict
['location'])
609 metadata_to_load
= image_dict
.get('metadata')
611 for k
,v
in yaml
.load(metadata_to_load
).iteritems():
612 new_image_nova
.metadata
.setdefault(k
,v
)
614 except (nvExceptions
.Conflict
, ksExceptions
.ClientException
, nvExceptions
.ClientException
) as e
:
615 self
._format
_exception
(e
)
616 except (HTTPException
, gl1Exceptions
.HTTPException
, gl1Exceptions
.CommunicationError
, ConnectionError
) as e
:
617 if retry
==max_retries
:
619 self
._format
_exception
(e
)
620 except IOError as e
: #can not open the file
621 raise vimconn
.vimconnConnectionException(type(e
).__name
__ + ": " + str(e
)+ " for " + image_dict
['location'],
622 http_code
=vimconn
.HTTP_Bad_Request
)
624 def delete_image(self
, image_id
):
625 '''Deletes a tenant image from openstack VIM. Returns the old id
628 self
._reload
_connection
()
629 self
.nova
.images
.delete(image_id
)
631 except (nvExceptions
.NotFound
, ksExceptions
.ClientException
, nvExceptions
.ClientException
, gl1Exceptions
.CommunicationError
, ConnectionError
) as e
: #TODO remove
632 self
._format
_exception
(e
)
634 def get_image_id_from_path(self
, path
):
635 '''Get the image id from image path in the VIM database. Returns the image_id'''
637 self
._reload
_connection
()
638 images
= self
.nova
.images
.list()
640 if image
.metadata
.get("location")==path
:
642 raise vimconn
.vimconnNotFoundException("image with location '{}' not found".format( path
))
643 except (ksExceptions
.ClientException
, nvExceptions
.ClientException
, gl1Exceptions
.CommunicationError
, ConnectionError
) as e
:
644 self
._format
_exception
(e
)
646 def get_image_list(self
, filter_dict
={}):
647 '''Obtain tenant images from VIM
651 checksum: image checksum
652 Returns the image list of dictionaries:
653 [{<the fields at Filter_dict plus some VIM specific>}, ...]
656 self
.logger
.debug("Getting image list from VIM filter: '%s'", str(filter_dict
))
658 self
._reload
_connection
()
659 filter_dict_os
=filter_dict
.copy()
660 #First we filter by the available filter fields: name, id. The others are removed.
661 filter_dict_os
.pop('checksum',None)
662 image_list
=self
.nova
.images
.findall(**filter_dict_os
)
663 if len(image_list
)==0:
665 #Then we filter by the rest of filter fields: checksum
667 for image
in image_list
:
668 image_class
=self
.glance
.images
.get(image
.id)
669 if 'checksum' not in filter_dict
or image_class
['checksum']==filter_dict
.get('checksum'):
670 filtered_list
.append(image_class
.copy())
672 except (ksExceptions
.ClientException
, nvExceptions
.ClientException
, gl1Exceptions
.CommunicationError
, ConnectionError
) as e
:
673 self
._format
_exception
(e
)
675 def __wait_for_vm(self
, vm_id
, status
):
676 """wait until vm is in the desired status and return True.
677 If the VM gets in ERROR status, return false.
678 If the timeout is reached generate an exception"""
680 while elapsed_time
< server_timeout
:
681 vm_status
= self
.nova
.servers
.get(vm_id
).status
682 if vm_status
== status
:
684 if vm_status
== 'ERROR':
689 # if we exceeded the timeout rollback
690 if elapsed_time
>= server_timeout
:
691 raise vimconn
.vimconnException('Timeout waiting for instance ' + vm_id
+ ' to get ' + status
,
692 http_code
=vimconn
.HTTP_Request_Timeout
)
694 def new_vminstance(self
,name
,description
,start
,image_id
,flavor_id
,net_list
,cloud_config
=None,disk_list
=None):
695 '''Adds a VM instance to VIM
697 start: indicates if VM must start or boot in pause mode. Ignored
698 image_id,flavor_id: iamge and flavor uuid
699 net_list: list of interfaces, each one is a dictionary with:
701 net_id: network uuid to connect
702 vpci: virtual vcpi to assign, ignored because openstack lack #TODO
703 model: interface model, ignored #TODO
704 mac_address: used for SR-IOV ifaces #TODO for other types
705 use: 'data', 'bridge', 'mgmt'
706 type: 'virtual', 'PF', 'VF', 'VFnotShared'
707 vim_id: filled/added by this function
708 floating_ip: True/False (or it can be None)
709 #TODO ip, security groups
710 Returns the instance identifier
712 self
.logger
.debug("new_vminstance input: image='%s' flavor='%s' nics='%s'",image_id
, flavor_id
,str(net_list
))
717 external_network
=[] # list of external networks to be connected to instance, later on used to create floating_ip
718 no_secured_ports
= [] # List of port-is with port-security disabled
719 self
._reload
_connection
()
720 metadata_vpci
={} # For a specific neutron plugin
721 block_device_mapping
= None
723 if not net
.get("net_id"): #skip non connected iface
727 "network_id": net
["net_id"],
728 "name": net
.get("name"),
729 "admin_state_up": True
731 if net
["type"]=="virtual":
733 metadata_vpci
[ net
["net_id"] ] = [[ net
["vpci"], "" ]]
734 elif net
["type"]=="VF": # for VF
736 if "VF" not in metadata_vpci
:
737 metadata_vpci
["VF"]=[]
738 metadata_vpci
["VF"].append([ net
["vpci"], "" ])
739 port_dict
["binding:vnic_type"]="direct"
742 if "PF" not in metadata_vpci
:
743 metadata_vpci
["PF"]=[]
744 metadata_vpci
["PF"].append([ net
["vpci"], "" ])
745 port_dict
["binding:vnic_type"]="direct-physical"
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 new_port
= self
.neutron
.create_port({"port": port_dict
})
751 net
["mac_adress"] = new_port
["port"]["mac_address"]
752 net
["vim_id"] = new_port
["port"]["id"]
753 # if try to use a network without subnetwork, it will return a emtpy list
754 fixed_ips
= new_port
["port"].get("fixed_ips")
756 net
["ip"] = fixed_ips
[0].get("ip_address")
759 net_list_vim
.append({"port-id": new_port
["port"]["id"]})
761 if net
.get('floating_ip', False):
762 net
['exit_on_floating_ip_error'] = True
763 external_network
.append(net
)
764 elif net
['use'] == 'mgmt' and self
.config
.get('use_floating_ip'):
765 net
['exit_on_floating_ip_error'] = False
766 external_network
.append(net
)
768 # If port security is disabled when the port has not yet been attached to the VM, then all vm traffic is dropped.
769 # As a workaround we wait until the VM is active and then disable the port-security
770 if net
.get("port_security") == False:
771 no_secured_ports
.append(new_port
["port"]["id"])
774 metadata
= {"pci_assignement": json
.dumps(metadata_vpci
)}
775 if len(metadata
["pci_assignement"]) >255:
776 #limit the metadata size
777 #metadata["pci_assignement"] = metadata["pci_assignement"][0:255]
778 self
.logger
.warn("Metadata deleted since it exceeds the expected length (255) ")
781 self
.logger
.debug("name '%s' image_id '%s'flavor_id '%s' net_list_vim '%s' description '%s' metadata %s",
782 name
, image_id
, flavor_id
, str(net_list_vim
), description
, str(metadata
))
784 security_groups
= self
.config
.get('security_groups')
785 if type(security_groups
) is str:
786 security_groups
= ( security_groups
, )
790 if isinstance(cloud_config
, dict):
791 if cloud_config
.get("user-data"):
792 userdata
=cloud_config
["user-data"]
793 if cloud_config
.get("boot-data-drive") != None:
794 config_drive
= cloud_config
["boot-data-drive"]
795 if cloud_config
.get("config-files") or cloud_config
.get("users") or cloud_config
.get("key-pairs"):
797 raise vimconn
.vimconnConflictException("Cloud-config cannot contain both 'userdata' and 'config-files'/'users'/'key-pairs'")
800 if cloud_config
.get("key-pairs"):
801 userdata_dict
["ssh-authorized-keys"] = cloud_config
["key-pairs"]
802 userdata_dict
["users"] = [{"default": None, "ssh-authorized-keys": cloud_config
["key-pairs"] }]
803 if cloud_config
.get("users"):
804 if "users" not in userdata_dict
:
805 userdata_dict
["users"] = [ "default" ]
806 for user
in cloud_config
["users"]:
808 "name" : user
["name"],
809 "sudo": "ALL = (ALL)NOPASSWD:ALL"
811 if "user-info" in user
:
812 user_info
["gecos"] = user
["user-info"]
813 if user
.get("key-pairs"):
814 user_info
["ssh-authorized-keys"] = user
["key-pairs"]
815 userdata_dict
["users"].append(user_info
)
817 if cloud_config
.get("config-files"):
818 userdata_dict
["write_files"] = []
819 for file in cloud_config
["config-files"]:
821 "path" : file["dest"],
822 "content": file["content"]
824 if file.get("encoding"):
825 file_info
["encoding"] = file["encoding"]
826 if file.get("permissions"):
827 file_info
["permissions"] = file["permissions"]
828 if file.get("owner"):
829 file_info
["owner"] = file["owner"]
830 userdata_dict
["write_files"].append(file_info
)
831 userdata
= "#cloud-config\n"
832 userdata
+= yaml
.safe_dump(userdata_dict
, indent
=4, default_flow_style
=False)
833 self
.logger
.debug("userdata: %s", userdata
)
834 elif isinstance(cloud_config
, str):
835 userdata
= cloud_config
837 #Create additional volumes in case these are present in disk_list
838 base_disk_index
= ord('b')
839 if disk_list
!= None:
840 block_device_mapping
= {}
841 for disk
in disk_list
:
842 if 'image_id' in disk
:
843 volume
= self
.cinder
.volumes
.create(size
= disk
['size'],name
= name
+ '_vd' +
844 chr(base_disk_index
), imageRef
= disk
['image_id'])
846 volume
= self
.cinder
.volumes
.create(size
=disk
['size'], name
=name
+ '_vd' +
847 chr(base_disk_index
))
848 block_device_mapping
['_vd' + chr(base_disk_index
)] = volume
.id
851 #wait until volumes are with status available
854 while keep_waiting
and elapsed_time
< volume_timeout
:
856 for volume_id
in block_device_mapping
.itervalues():
857 if self
.cinder
.volumes
.get(volume_id
).status
!= 'available':
863 #if we exceeded the timeout rollback
864 if elapsed_time
>= volume_timeout
:
865 #delete the volumes we just created
866 for volume_id
in block_device_mapping
.itervalues():
867 self
.cinder
.volumes
.delete(volume_id
)
869 #delete ports we just created
870 for net_item
in net_list_vim
:
871 if 'port-id' in net_item
:
872 self
.neutron
.delete_port(net_item
['port-id'])
874 raise vimconn
.vimconnException('Timeout creating volumes for instance ' + name
,
875 http_code
=vimconn
.HTTP_Request_Timeout
)
877 self
.logger
.debug("nova.servers.create({}, {}, {}, nics={}, meta={}, security_groups={}," \
878 "availability_zone={}, key_name={}, userdata={}, config_drive={}, " \
879 "block_device_mapping={})".format(name
, image_id
, flavor_id
, net_list_vim
,
880 metadata
, security_groups
, self
.config
.get('availability_zone'),
881 self
.config
.get('keypair'), userdata
, config_drive
, block_device_mapping
))
882 server
= self
.nova
.servers
.create(name
, image_id
, flavor_id
, nics
=net_list_vim
, meta
=metadata
,
883 security_groups
=security_groups
,
884 availability_zone
=self
.config
.get('availability_zone'),
885 key_name
=self
.config
.get('keypair'),
887 config_drive
=config_drive
,
888 block_device_mapping
=block_device_mapping
889 ) # , description=description)
891 # Previously mentioned workaround to wait until the VM is active and then disable the port-security
893 self
.__wait
_for
_vm
(server
.id, 'ACTIVE')
895 for port_id
in no_secured_ports
:
897 self
.neutron
.update_port(port_id
, {"port": {"port_security_enabled": False, "security_groups": None} })
899 except Exception as e
:
900 self
.logger
.error("It was not possible to disable port security for port {}".format(port_id
))
901 self
.delete_vminstance(server
.id)
904 #print "DONE :-)", server
906 floating_ips
= self
.neutron
.list_floatingips().get("floatingips", ())
909 self
.__wait
_for
_vm
(server
.id, 'ACTIVE')
911 for floating_network
in external_network
:
914 while(assigned
== False):
916 ip
= floating_ips
.pop(0)
917 if not ip
.get("port_id", False) and ip
.get('tenant_id') == server
.tenant_id
:
918 free_floating_ip
= ip
.get("floating_ip_address")
920 fix_ip
= floating_network
.get('ip')
921 server
.add_floating_ip(free_floating_ip
, fix_ip
)
923 except Exception as e
:
924 raise vimconn
.vimconnException(type(e
).__name
__ + ": Cannot create floating_ip "+ str(e
), http_code
=vimconn
.HTTP_Conflict
)
926 #Find the external network
927 external_nets
= list()
928 for net
in self
.neutron
.list_networks()['networks']:
929 if net
['router:external']:
930 external_nets
.append(net
)
932 if len(external_nets
) == 0:
933 raise vimconn
.vimconnException("Cannot create floating_ip automatically since no external "
934 "network is present",
935 http_code
=vimconn
.HTTP_Conflict
)
936 if len(external_nets
) > 1:
937 raise vimconn
.vimconnException("Cannot create floating_ip automatically since multiple "
938 "external networks are present",
939 http_code
=vimconn
.HTTP_Conflict
)
941 pool_id
= external_nets
[0].get('id')
942 param
= {'floatingip': {'floating_network_id': pool_id
, 'tenant_id': server
.tenant_id
}}
944 #self.logger.debug("Creating floating IP")
945 new_floating_ip
= self
.neutron
.create_floatingip(param
)
946 free_floating_ip
= new_floating_ip
['floatingip']['floating_ip_address']
947 fix_ip
= floating_network
.get('ip')
948 server
.add_floating_ip(free_floating_ip
, fix_ip
)
950 except Exception as e
:
951 raise vimconn
.vimconnException(type(e
).__name
__ + ": Cannot assign floating_ip "+ str(e
), http_code
=vimconn
.HTTP_Conflict
)
952 except Exception as e
:
953 if not floating_network
['exit_on_floating_ip_error']:
954 self
.logger
.warn("Cannot create floating_ip. %s", str(e
))
959 # except nvExceptions.NotFound as e:
960 # error_value=-vimconn.HTTP_Not_Found
961 # error_text= "vm instance %s not found" % vm_id
962 # except TypeError as e:
963 # raise vimconn.vimconnException(type(e).__name__ + ": "+ str(e), http_code=vimconn.HTTP_Bad_Request)
965 except Exception as e
:
966 # delete the volumes we just created
967 if block_device_mapping
:
968 for volume_id
in block_device_mapping
.itervalues():
969 self
.cinder
.volumes
.delete(volume_id
)
973 self
.delete_vminstance(server
.id)
975 # delete ports we just created
976 for net_item
in net_list_vim
:
977 if 'port-id' in net_item
:
978 self
.neutron
.delete_port(net_item
['port-id'])
980 self
._format
_exception
(e
)
982 def get_vminstance(self
,vm_id
):
983 '''Returns the VM instance information from VIM'''
984 #self.logger.debug("Getting VM from VIM")
986 self
._reload
_connection
()
987 server
= self
.nova
.servers
.find(id=vm_id
)
988 #TODO parse input and translate to VIM format (openmano_schemas.new_vminstance_response_schema)
989 return server
.to_dict()
990 except (ksExceptions
.ClientException
, nvExceptions
.ClientException
, nvExceptions
.NotFound
, ConnectionError
) as e
:
991 self
._format
_exception
(e
)
993 def get_vminstance_console(self
,vm_id
, console_type
="vnc"):
995 Get a console for the virtual machine
997 vm_id: uuid of the VM
998 console_type, can be:
999 "novnc" (by default), "xvpvnc" for VNC types,
1000 "rdp-html5" for RDP types, "spice-html5" for SPICE types
1001 Returns dict with the console parameters:
1002 protocol: ssh, ftp, http, https, ...
1003 server: usually ip address
1004 port: the http, ssh, ... port
1005 suffix: extra text, e.g. the http path and query string
1007 self
.logger
.debug("Getting VM CONSOLE from VIM")
1009 self
._reload
_connection
()
1010 server
= self
.nova
.servers
.find(id=vm_id
)
1011 if console_type
== None or console_type
== "novnc":
1012 console_dict
= server
.get_vnc_console("novnc")
1013 elif console_type
== "xvpvnc":
1014 console_dict
= server
.get_vnc_console(console_type
)
1015 elif console_type
== "rdp-html5":
1016 console_dict
= server
.get_rdp_console(console_type
)
1017 elif console_type
== "spice-html5":
1018 console_dict
= server
.get_spice_console(console_type
)
1020 raise vimconn
.vimconnException("console type '{}' not allowed".format(console_type
), http_code
=vimconn
.HTTP_Bad_Request
)
1022 console_dict1
= console_dict
.get("console")
1024 console_url
= console_dict1
.get("url")
1027 protocol_index
= console_url
.find("//")
1028 suffix_index
= console_url
[protocol_index
+2:].find("/") + protocol_index
+2
1029 port_index
= console_url
[protocol_index
+2:suffix_index
].find(":") + protocol_index
+2
1030 if protocol_index
< 0 or port_index
<0 or suffix_index
<0:
1031 return -vimconn
.HTTP_Internal_Server_Error
, "Unexpected response from VIM"
1032 console_dict
={"protocol": console_url
[0:protocol_index
],
1033 "server": console_url
[protocol_index
+2:port_index
],
1034 "port": console_url
[port_index
:suffix_index
],
1035 "suffix": console_url
[suffix_index
+1:]
1039 raise vimconn
.vimconnUnexpectedResponse("Unexpected response from VIM")
1041 except (nvExceptions
.NotFound
, ksExceptions
.ClientException
, nvExceptions
.ClientException
, nvExceptions
.BadRequest
, ConnectionError
) as e
:
1042 self
._format
_exception
(e
)
1044 def delete_vminstance(self
, vm_id
):
1045 '''Removes a VM instance from VIM. Returns the old identifier
1047 #print "osconnector: Getting VM from VIM"
1049 self
._reload
_connection
()
1050 #delete VM ports attached to this networks before the virtual machine
1051 ports
= self
.neutron
.list_ports(device_id
=vm_id
)
1052 for p
in ports
['ports']:
1054 self
.neutron
.delete_port(p
["id"])
1055 except Exception as e
:
1056 self
.logger
.error("Error deleting port: " + type(e
).__name
__ + ": "+ str(e
))
1058 #commented because detaching the volumes makes the servers.delete not work properly ?!?
1059 #dettach volumes attached
1060 server
= self
.nova
.servers
.get(vm_id
)
1061 volumes_attached_dict
= server
._info
['os-extended-volumes:volumes_attached']
1062 #for volume in volumes_attached_dict:
1063 # self.cinder.volumes.detach(volume['id'])
1065 self
.nova
.servers
.delete(vm_id
)
1068 #Although having detached them should have them in active status
1069 #we ensure in this loop
1072 while keep_waiting
and elapsed_time
< volume_timeout
:
1073 keep_waiting
= False
1074 for volume
in volumes_attached_dict
:
1075 if self
.cinder
.volumes
.get(volume
['id']).status
!= 'available':
1078 self
.cinder
.volumes
.delete(volume
['id'])
1084 except (nvExceptions
.NotFound
, ksExceptions
.ClientException
, nvExceptions
.ClientException
, ConnectionError
) as e
:
1085 self
._format
_exception
(e
)
1086 #TODO insert exception vimconn.HTTP_Unauthorized
1087 #if reaching here is because an exception
1089 def refresh_vms_status(self
, vm_list
):
1090 '''Get the status of the virtual machines and their interfaces/ports
1091 Params: the list of VM identifiers
1092 Returns a dictionary with:
1093 vm_id: #VIM id of this Virtual Machine
1094 status: #Mandatory. Text with one of:
1095 # DELETED (not found at vim)
1096 # VIM_ERROR (Cannot connect to VIM, VIM response error, ...)
1097 # OTHER (Vim reported other status not understood)
1098 # ERROR (VIM indicates an ERROR status)
1099 # ACTIVE, PAUSED, SUSPENDED, INACTIVE (not running),
1100 # CREATING (on building process), ERROR
1101 # ACTIVE:NoMgmtIP (Active but any of its interface has an IP address
1103 error_msg: #Text with VIM error message, if any. Or the VIM connection ERROR
1104 vim_info: #Text with plain information obtained from vim (yaml.safe_dump)
1106 - vim_info: #Text with plain information obtained from vim (yaml.safe_dump)
1107 mac_address: #Text format XX:XX:XX:XX:XX:XX
1108 vim_net_id: #network id where this interface is connected
1109 vim_interface_id: #interface/port VIM id
1110 ip_address: #null, or text with IPv4, IPv6 address
1111 compute_node: #identification of compute node where PF,VF interface is allocated
1112 pci: #PCI address of the NIC that hosts the PF,VF
1113 vlan: #physical VLAN used for VF
1116 self
.logger
.debug("refresh_vms status: Getting tenant VM instance information from VIM")
1117 for vm_id
in vm_list
:
1120 vm_vim
= self
.get_vminstance(vm_id
)
1121 if vm_vim
['status'] in vmStatus2manoFormat
:
1122 vm
['status'] = vmStatus2manoFormat
[ vm_vim
['status'] ]
1124 vm
['status'] = "OTHER"
1125 vm
['error_msg'] = "VIM status reported " + vm_vim
['status']
1127 vm
['vim_info'] = yaml
.safe_dump(vm_vim
, default_flow_style
=True, width
=256)
1128 except yaml
.representer
.RepresenterError
:
1129 vm
['vim_info'] = str(vm_vim
)
1130 vm
["interfaces"] = []
1131 if vm_vim
.get('fault'):
1132 vm
['error_msg'] = str(vm_vim
['fault'])
1135 self
._reload
_connection
()
1136 port_dict
=self
.neutron
.list_ports(device_id
=vm_id
)
1137 for port
in port_dict
["ports"]:
1140 interface
['vim_info'] = yaml
.safe_dump(port
, default_flow_style
=True, width
=256)
1141 except yaml
.representer
.RepresenterError
:
1142 interface
['vim_info'] = str(port
)
1143 interface
["mac_address"] = port
.get("mac_address")
1144 interface
["vim_net_id"] = port
["network_id"]
1145 interface
["vim_interface_id"] = port
["id"]
1146 # check if OS-EXT-SRV-ATTR:host is there,
1147 # in case of non-admin credentials, it will be missing
1148 if vm_vim
.get('OS-EXT-SRV-ATTR:host'):
1149 interface
["compute_node"] = vm_vim
['OS-EXT-SRV-ATTR:host']
1150 interface
["pci"] = None
1152 # check if binding:profile is there,
1153 # in case of non-admin credentials, it will be missing
1154 if port
.get('binding:profile'):
1155 if port
['binding:profile'].get('pci_slot'):
1156 # TODO: At the moment sr-iov pci addresses are converted to PF pci addresses by setting the slot to 0x00
1157 # TODO: This is just a workaround valid for niantinc. Find a better way to do so
1158 # CHANGE DDDD:BB:SS.F to DDDD:BB:00.(F%2) assuming there are 2 ports per nic
1159 pci
= port
['binding:profile']['pci_slot']
1160 # interface["pci"] = pci[:-4] + "00." + str(int(pci[-1]) % 2)
1161 interface
["pci"] = pci
1162 interface
["vlan"] = None
1163 #if network is of type vlan and port is of type direct (sr-iov) then set vlan id
1164 network
= self
.neutron
.show_network(port
["network_id"])
1165 if network
['network'].get('provider:network_type') == 'vlan' and \
1166 port
.get("binding:vnic_type") == "direct":
1167 interface
["vlan"] = network
['network'].get('provider:segmentation_id')
1169 #look for floating ip address
1170 floating_ip_dict
= self
.neutron
.list_floatingips(port_id
=port
["id"])
1171 if floating_ip_dict
.get("floatingips"):
1172 ips
.append(floating_ip_dict
["floatingips"][0].get("floating_ip_address") )
1174 for subnet
in port
["fixed_ips"]:
1175 ips
.append(subnet
["ip_address"])
1176 interface
["ip_address"] = ";".join(ips
)
1177 vm
["interfaces"].append(interface
)
1178 except Exception as e
:
1179 self
.logger
.error("Error getting vm interface information " + type(e
).__name
__ + ": "+ str(e
))
1180 except vimconn
.vimconnNotFoundException
as e
:
1181 self
.logger
.error("Exception getting vm status: %s", str(e
))
1182 vm
['status'] = "DELETED"
1183 vm
['error_msg'] = str(e
)
1184 except vimconn
.vimconnException
as e
:
1185 self
.logger
.error("Exception getting vm status: %s", str(e
))
1186 vm
['status'] = "VIM_ERROR"
1187 vm
['error_msg'] = str(e
)
1191 def action_vminstance(self
, vm_id
, action_dict
):
1192 '''Send and action over a VM instance from VIM
1193 Returns the vm_id if the action was successfully sent to the VIM'''
1194 self
.logger
.debug("Action over VM '%s': %s", vm_id
, str(action_dict
))
1196 self
._reload
_connection
()
1197 server
= self
.nova
.servers
.find(id=vm_id
)
1198 if "start" in action_dict
:
1199 if action_dict
["start"]=="rebuild":
1202 if server
.status
=="PAUSED":
1204 elif server
.status
=="SUSPENDED":
1206 elif server
.status
=="SHUTOFF":
1208 elif "pause" in action_dict
:
1210 elif "resume" in action_dict
:
1212 elif "shutoff" in action_dict
or "shutdown" in action_dict
:
1214 elif "forceOff" in action_dict
:
1216 elif "terminate" in action_dict
:
1218 elif "createImage" in action_dict
:
1219 server
.create_image()
1220 #"path":path_schema,
1221 #"description":description_schema,
1222 #"name":name_schema,
1223 #"metadata":metadata_schema,
1224 #"imageRef": id_schema,
1225 #"disk": {"oneOf":[{"type": "null"}, {"type":"string"}] },
1226 elif "rebuild" in action_dict
:
1227 server
.rebuild(server
.image
['id'])
1228 elif "reboot" in action_dict
:
1229 server
.reboot() #reboot_type='SOFT'
1230 elif "console" in action_dict
:
1231 console_type
= action_dict
["console"]
1232 if console_type
== None or console_type
== "novnc":
1233 console_dict
= server
.get_vnc_console("novnc")
1234 elif console_type
== "xvpvnc":
1235 console_dict
= server
.get_vnc_console(console_type
)
1236 elif console_type
== "rdp-html5":
1237 console_dict
= server
.get_rdp_console(console_type
)
1238 elif console_type
== "spice-html5":
1239 console_dict
= server
.get_spice_console(console_type
)
1241 raise vimconn
.vimconnException("console type '{}' not allowed".format(console_type
),
1242 http_code
=vimconn
.HTTP_Bad_Request
)
1244 console_url
= console_dict
["console"]["url"]
1246 protocol_index
= console_url
.find("//")
1247 suffix_index
= console_url
[protocol_index
+2:].find("/") + protocol_index
+2
1248 port_index
= console_url
[protocol_index
+2:suffix_index
].find(":") + protocol_index
+2
1249 if protocol_index
< 0 or port_index
<0 or suffix_index
<0:
1250 raise vimconn
.vimconnException("Unexpected response from VIM " + str(console_dict
))
1251 console_dict2
={"protocol": console_url
[0:protocol_index
],
1252 "server": console_url
[protocol_index
+2 : port_index
],
1253 "port": int(console_url
[port_index
+1 : suffix_index
]),
1254 "suffix": console_url
[suffix_index
+1:]
1256 return console_dict2
1257 except Exception as e
:
1258 raise vimconn
.vimconnException("Unexpected response from VIM " + str(console_dict
))
1261 except (ksExceptions
.ClientException
, nvExceptions
.ClientException
, nvExceptions
.NotFound
, ConnectionError
) as e
:
1262 self
._format
_exception
(e
)
1263 #TODO insert exception vimconn.HTTP_Unauthorized
1267 def new_external_port(self
, port_data
):
1268 #TODO openstack if needed
1269 '''Adds a external port to VIM'''
1270 '''Returns the port identifier'''
1271 return -vimconn
.HTTP_Internal_Server_Error
, "osconnector.new_external_port() not implemented"
1273 def connect_port_network(self
, port_id
, network_id
, admin
=False):
1274 #TODO openstack if needed
1275 '''Connects a external port to a network'''
1276 '''Returns status code of the VIM response'''
1277 return -vimconn
.HTTP_Internal_Server_Error
, "osconnector.connect_port_network() not implemented"
1279 def new_user(self
, user_name
, user_passwd
, tenant_id
=None):
1280 '''Adds a new user to openstack VIM'''
1281 '''Returns the user identifier'''
1282 self
.logger
.debug("osconnector: Adding a new user to VIM")
1284 self
._reload
_connection
()
1285 user
=self
.keystone
.users
.create(user_name
, user_passwd
, tenant_id
=tenant_id
)
1286 #self.keystone.tenants.add_user(self.k_creds["username"], #role)
1288 except ksExceptions
.ConnectionError
as e
:
1289 error_value
=-vimconn
.HTTP_Bad_Request
1290 error_text
= type(e
).__name
__ + ": "+ (str(e
) if len(e
.args
)==0 else str(e
.args
[0]))
1291 except ksExceptions
.ClientException
as e
: #TODO remove
1292 error_value
=-vimconn
.HTTP_Bad_Request
1293 error_text
= type(e
).__name
__ + ": "+ (str(e
) if len(e
.args
)==0 else str(e
.args
[0]))
1294 #TODO insert exception vimconn.HTTP_Unauthorized
1295 #if reaching here is because an exception
1297 self
.logger
.debug("new_user " + error_text
)
1298 return error_value
, error_text
1300 def delete_user(self
, user_id
):
1301 '''Delete a user from openstack VIM'''
1302 '''Returns the user identifier'''
1304 print "osconnector: Deleting a user from VIM"
1306 self
._reload
_connection
()
1307 self
.keystone
.users
.delete(user_id
)
1309 except ksExceptions
.ConnectionError
as e
:
1310 error_value
=-vimconn
.HTTP_Bad_Request
1311 error_text
= type(e
).__name
__ + ": "+ (str(e
) if len(e
.args
)==0 else str(e
.args
[0]))
1312 except ksExceptions
.NotFound
as e
:
1313 error_value
=-vimconn
.HTTP_Not_Found
1314 error_text
= type(e
).__name
__ + ": "+ (str(e
) if len(e
.args
)==0 else str(e
.args
[0]))
1315 except ksExceptions
.ClientException
as e
: #TODO remove
1316 error_value
=-vimconn
.HTTP_Bad_Request
1317 error_text
= type(e
).__name
__ + ": "+ (str(e
) if len(e
.args
)==0 else str(e
.args
[0]))
1318 #TODO insert exception vimconn.HTTP_Unauthorized
1319 #if reaching here is because an exception
1321 print "delete_tenant " + error_text
1322 return error_value
, error_text
1324 def get_hosts_info(self
):
1325 '''Get the information of deployed hosts
1326 Returns the hosts content'''
1328 print "osconnector: Getting Host info from VIM"
1331 self
._reload
_connection
()
1332 hypervisors
= self
.nova
.hypervisors
.list()
1333 for hype
in hypervisors
:
1334 h_list
.append( hype
.to_dict() )
1335 return 1, {"hosts":h_list
}
1336 except nvExceptions
.NotFound
as e
:
1337 error_value
=-vimconn
.HTTP_Not_Found
1338 error_text
= (str(e
) if len(e
.args
)==0 else str(e
.args
[0]))
1339 except (ksExceptions
.ClientException
, nvExceptions
.ClientException
) as e
:
1340 error_value
=-vimconn
.HTTP_Bad_Request
1341 error_text
= type(e
).__name
__ + ": "+ (str(e
) if len(e
.args
)==0 else str(e
.args
[0]))
1342 #TODO insert exception vimconn.HTTP_Unauthorized
1343 #if reaching here is because an exception
1345 print "get_hosts_info " + error_text
1346 return error_value
, error_text
1348 def get_hosts(self
, vim_tenant
):
1349 '''Get the hosts and deployed instances
1350 Returns the hosts content'''
1351 r
, hype_dict
= self
.get_hosts_info()
1354 hypervisors
= hype_dict
["hosts"]
1356 servers
= self
.nova
.servers
.list()
1357 for hype
in hypervisors
:
1358 for server
in servers
:
1359 if server
.to_dict()['OS-EXT-SRV-ATTR:hypervisor_hostname']==hype
['hypervisor_hostname']:
1361 hype
['vm'].append(server
.id)
1363 hype
['vm'] = [server
.id]
1365 except nvExceptions
.NotFound
as e
:
1366 error_value
=-vimconn
.HTTP_Not_Found
1367 error_text
= (str(e
) if len(e
.args
)==0 else str(e
.args
[0]))
1368 except (ksExceptions
.ClientException
, nvExceptions
.ClientException
) as e
:
1369 error_value
=-vimconn
.HTTP_Bad_Request
1370 error_text
= type(e
).__name
__ + ": "+ (str(e
) if len(e
.args
)==0 else str(e
.args
[0]))
1371 #TODO insert exception vimconn.HTTP_Unauthorized
1372 #if reaching here is because an exception
1374 print "get_hosts " + error_text
1375 return error_value
, error_text