Proper error message shown when no image could be found at VIM
[osm/RO.git] / vimconn_openstack.py
1 # -*- coding: utf-8 -*-
2
3 ##
4 # Copyright 2015 Telefónica Investigación y Desarrollo, S.A.U.
5 # This file is part of openmano
6 # All Rights Reserved.
7 #
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
11 #
12 # http://www.apache.org/licenses/LICENSE-2.0
13 #
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
18 # under the License.
19 #
20 # For those usages not covered by the Apache License, Version 2.0 please
21 # contact with: nfvlabs@tid.es
22 ##
23
24 '''
25 osconnector implements all the methods to interact with openstack using the python-client.
26 '''
27 __author__="Alfonso Tierno, Gerardo Garcia, Pablo Montes, xFlow Research"
28 __date__ ="$22-jun-2014 11:19:29$"
29
30 import vimconn
31 import json
32 import yaml
33 import logging
34 import netaddr
35 import time
36 import yaml
37 import random
38
39 from novaclient import client as nClient_v2, exceptions as nvExceptions, api_versions as APIVersion
40 import keystoneclient.v2_0.client as ksClient_v2
41 from novaclient.v2.client import Client as nClient
42 import keystoneclient.v3.client as ksClient
43 import keystoneclient.exceptions as ksExceptions
44 import glanceclient.v2.client as glClient
45 import glanceclient.client as gl1Client
46 import glanceclient.exc as gl1Exceptions
47 import cinderclient.v2.client as cClient_v2
48 from httplib import HTTPException
49 from neutronclient.neutron import client as neClient_v2
50 from neutronclient.v2_0 import client as neClient
51 from neutronclient.common import exceptions as neExceptions
52 from requests.exceptions import ConnectionError
53
54 '''contain the openstack virtual machine status to openmano status'''
55 vmStatus2manoFormat={'ACTIVE':'ACTIVE',
56 'PAUSED':'PAUSED',
57 'SUSPENDED': 'SUSPENDED',
58 'SHUTOFF':'INACTIVE',
59 'BUILD':'BUILD',
60 'ERROR':'ERROR','DELETED':'DELETED'
61 }
62 netStatus2manoFormat={'ACTIVE':'ACTIVE','PAUSED':'PAUSED','INACTIVE':'INACTIVE','BUILD':'BUILD','ERROR':'ERROR','DELETED':'DELETED'
63 }
64
65 #global var to have a timeout creating and deleting volumes
66 volume_timeout = 60
67 server_timeout = 60
68
69 class vimconnector(vimconn.vimconnector):
70 def __init__(self, uuid, name, tenant_id, tenant_name, url, url_admin=None, user=None, passwd=None, log_level=None, config={}):
71 '''using common constructor parameters. In this case
72 'url' is the keystone authorization url,
73 'url_admin' is not use
74 '''
75 self.osc_api_version = 'v2.0'
76 if config.get('APIversion') == 'v3.3':
77 self.osc_api_version = 'v3.3'
78 vimconn.vimconnector.__init__(self, uuid, name, tenant_id, tenant_name, url, url_admin, user, passwd, log_level, config)
79
80 self.k_creds={}
81 self.n_creds={}
82 if self.config.get("insecure"):
83 self.k_creds["insecure"] = True
84 self.n_creds["insecure"] = True
85 if not url:
86 raise TypeError, 'url param can not be NoneType'
87 self.k_creds['auth_url'] = url
88 self.n_creds['auth_url'] = url
89 if tenant_name:
90 self.k_creds['tenant_name'] = tenant_name
91 self.n_creds['project_id'] = tenant_name
92 if tenant_id:
93 self.k_creds['tenant_id'] = tenant_id
94 self.n_creds['tenant_id'] = tenant_id
95 if user:
96 self.k_creds['username'] = user
97 self.n_creds['username'] = user
98 if passwd:
99 self.k_creds['password'] = passwd
100 self.n_creds['api_key'] = passwd
101 if self.osc_api_version == 'v3.3':
102 self.k_creds['project_name'] = tenant_name
103 self.k_creds['project_id'] = tenant_id
104 if config.get('region_name'):
105 self.k_creds['region_name'] = config.get('region_name')
106 self.n_creds['region_name'] = config.get('region_name')
107
108 self.reload_client = True
109 self.logger = logging.getLogger('openmano.vim.openstack')
110 if log_level:
111 self.logger.setLevel( getattr(logging, log_level) )
112
113 def __setitem__(self,index, value):
114 '''Set individuals parameters
115 Throw TypeError, KeyError
116 '''
117 if index=='tenant_id':
118 self.reload_client=True
119 self.tenant_id = value
120 if self.osc_api_version == 'v3.3':
121 if value:
122 self.k_creds['project_id'] = value
123 self.n_creds['project_id'] = value
124 else:
125 del self.k_creds['project_id']
126 del self.n_creds['project_id']
127 else:
128 if value:
129 self.k_creds['tenant_id'] = value
130 self.n_creds['tenant_id'] = value
131 else:
132 del self.k_creds['tenant_id']
133 del self.n_creds['tenant_id']
134 elif index=='tenant_name':
135 self.reload_client=True
136 self.tenant_name = value
137 if self.osc_api_version == 'v3.3':
138 if value:
139 self.k_creds['project_name'] = value
140 self.n_creds['project_name'] = value
141 else:
142 del self.k_creds['project_name']
143 del self.n_creds['project_name']
144 else:
145 if value:
146 self.k_creds['tenant_name'] = value
147 self.n_creds['project_id'] = value
148 else:
149 del self.k_creds['tenant_name']
150 del self.n_creds['project_id']
151 elif index=='user':
152 self.reload_client=True
153 self.user = value
154 if value:
155 self.k_creds['username'] = value
156 self.n_creds['username'] = value
157 else:
158 del self.k_creds['username']
159 del self.n_creds['username']
160 elif index=='passwd':
161 self.reload_client=True
162 self.passwd = value
163 if value:
164 self.k_creds['password'] = value
165 self.n_creds['api_key'] = value
166 else:
167 del self.k_creds['password']
168 del self.n_creds['api_key']
169 elif index=='url':
170 self.reload_client=True
171 self.url = value
172 if value:
173 self.k_creds['auth_url'] = value
174 self.n_creds['auth_url'] = value
175 else:
176 raise TypeError, 'url param can not be NoneType'
177 else:
178 vimconn.vimconnector.__setitem__(self,index, value)
179
180 def _reload_connection(self):
181 '''Called before any operation, it check if credentials has changed
182 Throw keystoneclient.apiclient.exceptions.AuthorizationFailure
183 '''
184 #TODO control the timing and possible token timeout, but it seams that python client does this task for us :-)
185 if self.reload_client:
186 #test valid params
187 if len(self.n_creds) <4:
188 raise ksExceptions.ClientException("Not enough parameters to connect to openstack")
189 if self.osc_api_version == 'v3.3':
190 self.nova = nClient(APIVersion(version_str='2'), **self.n_creds)
191 #TODO To be updated for v3
192 #self.cinder = cClient.Client(**self.n_creds)
193 self.keystone = ksClient.Client(**self.k_creds)
194 self.ne_endpoint=self.keystone.service_catalog.url_for(service_type='network', endpoint_type='publicURL')
195 self.neutron = neClient.Client(APIVersion(version_str='2'), endpoint_url=self.ne_endpoint, token=self.keystone.auth_token, **self.k_creds)
196 else:
197 self.nova = nClient_v2.Client('2', **self.n_creds)
198 self.cinder = cClient_v2.Client(**self.n_creds)
199 self.keystone = ksClient_v2.Client(**self.k_creds)
200 self.ne_endpoint=self.keystone.service_catalog.url_for(service_type='network', endpoint_type='publicURL')
201 self.neutron = neClient_v2.Client('2.0', endpoint_url=self.ne_endpoint, token=self.keystone.auth_token, **self.k_creds)
202 self.glance_endpoint = self.keystone.service_catalog.url_for(service_type='image', endpoint_type='publicURL')
203 self.glance = glClient.Client(self.glance_endpoint, token=self.keystone.auth_token, **self.k_creds) #TODO check k_creds vs n_creds
204 self.reload_client = False
205
206 def __net_os2mano(self, net_list_dict):
207 '''Transform the net openstack format to mano format
208 net_list_dict can be a list of dict or a single dict'''
209 if type(net_list_dict) is dict:
210 net_list_=(net_list_dict,)
211 elif type(net_list_dict) is list:
212 net_list_=net_list_dict
213 else:
214 raise TypeError("param net_list_dict must be a list or a dictionary")
215 for net in net_list_:
216 if net.get('provider:network_type') == "vlan":
217 net['type']='data'
218 else:
219 net['type']='bridge'
220
221
222
223 def _format_exception(self, exception):
224 '''Transform a keystone, nova, neutron exception into a vimconn exception'''
225 if isinstance(exception, (HTTPException, gl1Exceptions.HTTPException, gl1Exceptions.CommunicationError,
226 ConnectionError, ksExceptions.ConnectionError, neExceptions.ConnectionFailed
227 )):
228 raise vimconn.vimconnConnectionException(type(exception).__name__ + ": " + str(exception))
229 elif isinstance(exception, (nvExceptions.ClientException, ksExceptions.ClientException,
230 neExceptions.NeutronException, nvExceptions.BadRequest)):
231 raise vimconn.vimconnUnexpectedResponse(type(exception).__name__ + ": " + str(exception))
232 elif isinstance(exception, (neExceptions.NetworkNotFoundClient, nvExceptions.NotFound)):
233 raise vimconn.vimconnNotFoundException(type(exception).__name__ + ": " + str(exception))
234 elif isinstance(exception, nvExceptions.Conflict):
235 raise vimconn.vimconnConflictException(type(exception).__name__ + ": " + str(exception))
236 else: # ()
237 raise vimconn.vimconnConnectionException(type(exception).__name__ + ": " + str(exception))
238
239 def get_tenant_list(self, filter_dict={}):
240 '''Obtain tenants of VIM
241 filter_dict can contain the following keys:
242 name: filter by tenant name
243 id: filter by tenant uuid/id
244 <other VIM specific>
245 Returns the tenant list of dictionaries: [{'name':'<name>, 'id':'<id>, ...}, ...]
246 '''
247 self.logger.debug("Getting tenants from VIM filter: '%s'", str(filter_dict))
248 try:
249 self._reload_connection()
250 if self.osc_api_version == 'v3.3':
251 project_class_list=self.keystone.projects.findall(**filter_dict)
252 else:
253 project_class_list=self.keystone.tenants.findall(**filter_dict)
254 project_list=[]
255 for project in project_class_list:
256 project_list.append(project.to_dict())
257 return project_list
258 except (ksExceptions.ConnectionError, ksExceptions.ClientException, ConnectionError) as e:
259 self._format_exception(e)
260
261 def new_tenant(self, tenant_name, tenant_description):
262 '''Adds a new tenant to openstack VIM. Returns the tenant identifier'''
263 self.logger.debug("Adding a new tenant name: %s", tenant_name)
264 try:
265 self._reload_connection()
266 if self.osc_api_version == 'v3.3':
267 project=self.keystone.projects.create(tenant_name, tenant_description)
268 else:
269 project=self.keystone.tenants.create(tenant_name, tenant_description)
270 return project.id
271 except (ksExceptions.ConnectionError, ksExceptions.ClientException, ConnectionError) as e:
272 self._format_exception(e)
273
274 def delete_tenant(self, tenant_id):
275 '''Delete a tenant from openstack VIM. Returns the old tenant identifier'''
276 self.logger.debug("Deleting tenant %s from VIM", tenant_id)
277 try:
278 self._reload_connection()
279 if self.osc_api_version == 'v3.3':
280 self.keystone.projects.delete(tenant_id)
281 else:
282 self.keystone.tenants.delete(tenant_id)
283 return tenant_id
284 except (ksExceptions.ConnectionError, ksExceptions.ClientException, ConnectionError) as e:
285 self._format_exception(e)
286
287 def new_network(self,net_name, net_type, ip_profile=None, shared=False, vlan=None):
288 '''Adds a tenant network to VIM. Returns the network identifier'''
289 self.logger.debug("Adding a new network to VIM name '%s', type '%s'", net_name, net_type)
290 #self.logger.debug(">>>>>>>>>>>>>>>>>> IP profile %s", str(ip_profile))
291 try:
292 new_net = None
293 self._reload_connection()
294 network_dict = {'name': net_name, 'admin_state_up': True}
295 if net_type=="data" or net_type=="ptp":
296 if self.config.get('dataplane_physical_net') == None:
297 raise vimconn.vimconnConflictException("You must provide a 'dataplane_physical_net' at config value before creating sriov network")
298 network_dict["provider:physical_network"] = self.config['dataplane_physical_net'] #"physnet_sriov" #TODO physical
299 network_dict["provider:network_type"] = "vlan"
300 if vlan!=None:
301 network_dict["provider:network_type"] = vlan
302 network_dict["shared"]=shared
303 new_net=self.neutron.create_network({'network':network_dict})
304 #print new_net
305 #create subnetwork, even if there is no profile
306 if not ip_profile:
307 ip_profile = {}
308 if 'subnet_address' not in ip_profile:
309 #Fake subnet is required
310 subnet_rand = random.randint(0, 255)
311 ip_profile['subnet_address'] = "192.168.{}.0/24".format(subnet_rand)
312 if 'ip_version' not in ip_profile:
313 ip_profile['ip_version'] = "IPv4"
314 subnet={"name":net_name+"-subnet",
315 "network_id": new_net["network"]["id"],
316 "ip_version": 4 if ip_profile['ip_version']=="IPv4" else 6,
317 "cidr": ip_profile['subnet_address']
318 }
319 if 'gateway_address' in ip_profile:
320 subnet['gateway_ip'] = ip_profile['gateway_address']
321 if ip_profile.get('dns_address'):
322 #TODO: manage dns_address as a list of addresses separated by commas
323 subnet['dns_nameservers'] = []
324 subnet['dns_nameservers'].append(ip_profile['dns_address'])
325 if 'dhcp_enabled' in ip_profile:
326 subnet['enable_dhcp'] = False if ip_profile['dhcp_enabled']=="false" else True
327 if 'dhcp_start_address' in ip_profile:
328 subnet['allocation_pools']=[]
329 subnet['allocation_pools'].append(dict())
330 subnet['allocation_pools'][0]['start'] = ip_profile['dhcp_start_address']
331 if 'dhcp_count' in ip_profile:
332 #parts = ip_profile['dhcp_start_address'].split('.')
333 #ip_int = (int(parts[0]) << 24) + (int(parts[1]) << 16) + (int(parts[2]) << 8) + int(parts[3])
334 ip_int = int(netaddr.IPAddress(ip_profile['dhcp_start_address']))
335 ip_int += ip_profile['dhcp_count'] - 1
336 ip_str = str(netaddr.IPAddress(ip_int))
337 subnet['allocation_pools'][0]['end'] = ip_str
338 #self.logger.debug(">>>>>>>>>>>>>>>>>> Subnet: %s", str(subnet))
339 self.neutron.create_subnet({"subnet": subnet} )
340 return new_net["network"]["id"]
341 except (neExceptions.ConnectionFailed, ksExceptions.ClientException, neExceptions.NeutronException, ConnectionError) as e:
342 if new_net:
343 self.neutron.delete_network(new_net['network']['id'])
344 self._format_exception(e)
345
346 def get_network_list(self, filter_dict={}):
347 '''Obtain tenant networks of VIM
348 Filter_dict can be:
349 name: network name
350 id: network uuid
351 shared: boolean
352 tenant_id: tenant
353 admin_state_up: boolean
354 status: 'ACTIVE'
355 Returns the network list of dictionaries
356 '''
357 self.logger.debug("Getting network from VIM filter: '%s'", str(filter_dict))
358 try:
359 self._reload_connection()
360 if self.osc_api_version == 'v3.3' and "tenant_id" in filter_dict:
361 filter_dict['project_id'] = filter_dict.pop('tenant_id')
362 net_dict=self.neutron.list_networks(**filter_dict)
363 net_list=net_dict["networks"]
364 self.__net_os2mano(net_list)
365 return net_list
366 except (neExceptions.ConnectionFailed, ksExceptions.ClientException, neExceptions.NeutronException, ConnectionError) as e:
367 self._format_exception(e)
368
369 def get_network(self, net_id):
370 '''Obtain details of network from VIM
371 Returns the network information from a network id'''
372 self.logger.debug(" Getting tenant network %s from VIM", net_id)
373 filter_dict={"id": net_id}
374 net_list = self.get_network_list(filter_dict)
375 if len(net_list)==0:
376 raise vimconn.vimconnNotFoundException("Network '{}' not found".format(net_id))
377 elif len(net_list)>1:
378 raise vimconn.vimconnConflictException("Found more than one network with this criteria")
379 net = net_list[0]
380 subnets=[]
381 for subnet_id in net.get("subnets", () ):
382 try:
383 subnet = self.neutron.show_subnet(subnet_id)
384 except Exception as e:
385 self.logger.error("osconnector.get_network(): Error getting subnet %s %s" % (net_id, str(e)))
386 subnet = {"id": subnet_id, "fault": str(e)}
387 subnets.append(subnet)
388 net["subnets"] = subnets
389 return net
390
391 def delete_network(self, net_id):
392 '''Deletes a tenant network from VIM. Returns the old network identifier'''
393 self.logger.debug("Deleting network '%s' from VIM", net_id)
394 try:
395 self._reload_connection()
396 #delete VM ports attached to this networks before the network
397 ports = self.neutron.list_ports(network_id=net_id)
398 for p in ports['ports']:
399 try:
400 self.neutron.delete_port(p["id"])
401 except Exception as e:
402 self.logger.error("Error deleting port %s: %s", p["id"], str(e))
403 self.neutron.delete_network(net_id)
404 return net_id
405 except (neExceptions.ConnectionFailed, neExceptions.NetworkNotFoundClient, neExceptions.NeutronException,
406 ksExceptions.ClientException, neExceptions.NeutronException, ConnectionError) as e:
407 self._format_exception(e)
408
409 def refresh_nets_status(self, net_list):
410 '''Get the status of the networks
411 Params: the list of network identifiers
412 Returns a dictionary with:
413 net_id: #VIM id of this network
414 status: #Mandatory. Text with one of:
415 # DELETED (not found at vim)
416 # VIM_ERROR (Cannot connect to VIM, VIM response error, ...)
417 # OTHER (Vim reported other status not understood)
418 # ERROR (VIM indicates an ERROR status)
419 # ACTIVE, INACTIVE, DOWN (admin down),
420 # BUILD (on building process)
421 #
422 error_msg: #Text with VIM error message, if any. Or the VIM connection ERROR
423 vim_info: #Text with plain information obtained from vim (yaml.safe_dump)
424
425 '''
426 net_dict={}
427 for net_id in net_list:
428 net = {}
429 try:
430 net_vim = self.get_network(net_id)
431 if net_vim['status'] in netStatus2manoFormat:
432 net["status"] = netStatus2manoFormat[ net_vim['status'] ]
433 else:
434 net["status"] = "OTHER"
435 net["error_msg"] = "VIM status reported " + net_vim['status']
436
437 if net['status'] == "ACTIVE" and not net_vim['admin_state_up']:
438 net['status'] = 'DOWN'
439 try:
440 net['vim_info'] = yaml.safe_dump(net_vim, default_flow_style=True, width=256)
441 except yaml.representer.RepresenterError:
442 net['vim_info'] = str(net_vim)
443 if net_vim.get('fault'): #TODO
444 net['error_msg'] = str(net_vim['fault'])
445 except vimconn.vimconnNotFoundException as e:
446 self.logger.error("Exception getting net status: %s", str(e))
447 net['status'] = "DELETED"
448 net['error_msg'] = str(e)
449 except vimconn.vimconnException as e:
450 self.logger.error("Exception getting net status: %s", str(e))
451 net['status'] = "VIM_ERROR"
452 net['error_msg'] = str(e)
453 net_dict[net_id] = net
454 return net_dict
455
456 def get_flavor(self, flavor_id):
457 '''Obtain flavor details from the VIM. Returns the flavor dict details'''
458 self.logger.debug("Getting flavor '%s'", flavor_id)
459 try:
460 self._reload_connection()
461 flavor = self.nova.flavors.find(id=flavor_id)
462 #TODO parse input and translate to VIM format (openmano_schemas.new_vminstance_response_schema)
463 return flavor.to_dict()
464 except (nvExceptions.NotFound, nvExceptions.ClientException, ksExceptions.ClientException, ConnectionError) as e:
465 self._format_exception(e)
466
467 def get_flavor_id_from_data(self, flavor_dict):
468 """Obtain flavor id that match the flavor description
469 Returns the flavor_id or raises a vimconnNotFoundException
470 """
471 try:
472 self._reload_connection()
473 numa=None
474 numas = flavor_dict.get("extended",{}).get("numas")
475 if numas:
476 #TODO
477 raise vimconn.vimconnNotFoundException("Flavor with EPA still not implemted")
478 # if len(numas) > 1:
479 # raise vimconn.vimconnNotFoundException("Cannot find any flavor with more than one numa")
480 # numa=numas[0]
481 # numas = extended.get("numas")
482 for flavor in self.nova.flavors.list():
483 epa = flavor.get_keys()
484 if epa:
485 continue
486 #TODO
487 if flavor.ram != flavor_dict["ram"]:
488 continue
489 if flavor.vcpus != flavor_dict["vcpus"]:
490 continue
491 if flavor.disk != flavor_dict["disk"]:
492 continue
493 return flavor.id
494 raise vimconn.vimconnNotFoundException("Cannot find any flavor matching '{}'".format(str(flavor_dict)))
495 except (nvExceptions.NotFound, nvExceptions.ClientException, ksExceptions.ClientException, ConnectionError) as e:
496 self._format_exception(e)
497
498
499 def new_flavor(self, flavor_data, change_name_if_used=True):
500 '''Adds a tenant flavor to openstack VIM
501 if change_name_if_used is True, it will change name in case of conflict, because it is not supported name repetition
502 Returns the flavor identifier
503 '''
504 self.logger.debug("Adding flavor '%s'", str(flavor_data))
505 retry=0
506 max_retries=3
507 name_suffix = 0
508 name=flavor_data['name']
509 while retry<max_retries:
510 retry+=1
511 try:
512 self._reload_connection()
513 if change_name_if_used:
514 #get used names
515 fl_names=[]
516 fl=self.nova.flavors.list()
517 for f in fl:
518 fl_names.append(f.name)
519 while name in fl_names:
520 name_suffix += 1
521 name = flavor_data['name']+"-" + str(name_suffix)
522
523 ram = flavor_data.get('ram',64)
524 vcpus = flavor_data.get('vcpus',1)
525 numa_properties=None
526
527 extended = flavor_data.get("extended")
528 if extended:
529 numas=extended.get("numas")
530 if numas:
531 numa_nodes = len(numas)
532 if numa_nodes > 1:
533 return -1, "Can not add flavor with more than one numa"
534 numa_properties = {"hw:numa_nodes":str(numa_nodes)}
535 numa_properties["hw:mem_page_size"] = "large"
536 numa_properties["hw:cpu_policy"] = "dedicated"
537 numa_properties["hw:numa_mempolicy"] = "strict"
538 for numa in numas:
539 #overwrite ram and vcpus
540 ram = numa['memory']*1024
541 if 'paired-threads' in numa:
542 vcpus = numa['paired-threads']*2
543 numa_properties["hw:cpu_threads_policy"] = "prefer"
544 elif 'cores' in numa:
545 vcpus = numa['cores']
546 #numa_properties["hw:cpu_threads_policy"] = "prefer"
547 elif 'threads' in numa:
548 vcpus = numa['threads']
549 numa_properties["hw:cpu_policy"] = "isolated"
550 for interface in numa.get("interfaces",() ):
551 if interface["dedicated"]=="yes":
552 raise vimconn.vimconnException("Passthrough interfaces are not supported for the openstack connector", http_code=vimconn.HTTP_Service_Unavailable)
553 #TODO, add the key 'pci_passthrough:alias"="<label at config>:<number ifaces>"' when a way to connect it is available
554
555 #create flavor
556 new_flavor=self.nova.flavors.create(name,
557 ram,
558 vcpus,
559 flavor_data.get('disk',1),
560 is_public=flavor_data.get('is_public', True)
561 )
562 #add metadata
563 if numa_properties:
564 new_flavor.set_keys(numa_properties)
565 return new_flavor.id
566 except nvExceptions.Conflict as e:
567 if change_name_if_used and retry < max_retries:
568 continue
569 self._format_exception(e)
570 #except nvExceptions.BadRequest as e:
571 except (ksExceptions.ClientException, nvExceptions.ClientException, ConnectionError) as e:
572 self._format_exception(e)
573
574 def delete_flavor(self,flavor_id):
575 '''Deletes a tenant flavor from openstack VIM. Returns the old flavor_id
576 '''
577 try:
578 self._reload_connection()
579 self.nova.flavors.delete(flavor_id)
580 return flavor_id
581 #except nvExceptions.BadRequest as e:
582 except (nvExceptions.NotFound, ksExceptions.ClientException, nvExceptions.ClientException, ConnectionError) as e:
583 self._format_exception(e)
584
585 def new_image(self,image_dict):
586 '''
587 Adds a tenant image to VIM. imge_dict is a dictionary with:
588 name: name
589 disk_format: qcow2, vhd, vmdk, raw (by default), ...
590 location: path or URI
591 public: "yes" or "no"
592 metadata: metadata of the image
593 Returns the image_id
594 '''
595 #using version 1 of glance client
596 glancev1 = gl1Client.Client('1',self.glance_endpoint, token=self.keystone.auth_token, **self.k_creds) #TODO check k_creds vs n_creds
597 retry=0
598 max_retries=3
599 while retry<max_retries:
600 retry+=1
601 try:
602 self._reload_connection()
603 #determine format http://docs.openstack.org/developer/glance/formats.html
604 if "disk_format" in image_dict:
605 disk_format=image_dict["disk_format"]
606 else: #autodiscover based on extension
607 if image_dict['location'][-6:]==".qcow2":
608 disk_format="qcow2"
609 elif image_dict['location'][-4:]==".vhd":
610 disk_format="vhd"
611 elif image_dict['location'][-5:]==".vmdk":
612 disk_format="vmdk"
613 elif image_dict['location'][-4:]==".vdi":
614 disk_format="vdi"
615 elif image_dict['location'][-4:]==".iso":
616 disk_format="iso"
617 elif image_dict['location'][-4:]==".aki":
618 disk_format="aki"
619 elif image_dict['location'][-4:]==".ari":
620 disk_format="ari"
621 elif image_dict['location'][-4:]==".ami":
622 disk_format="ami"
623 else:
624 disk_format="raw"
625 self.logger.debug("new_image: '%s' loading from '%s'", image_dict['name'], image_dict['location'])
626 if image_dict['location'][0:4]=="http":
627 new_image = glancev1.images.create(name=image_dict['name'], is_public=image_dict.get('public',"yes")=="yes",
628 container_format="bare", location=image_dict['location'], disk_format=disk_format)
629 else: #local path
630 with open(image_dict['location']) as fimage:
631 new_image = glancev1.images.create(name=image_dict['name'], is_public=image_dict.get('public',"yes")=="yes",
632 container_format="bare", data=fimage, disk_format=disk_format)
633 #insert metadata. We cannot use 'new_image.properties.setdefault'
634 #because nova and glance are "INDEPENDENT" and we are using nova for reading metadata
635 new_image_nova=self.nova.images.find(id=new_image.id)
636 new_image_nova.metadata.setdefault('location',image_dict['location'])
637 metadata_to_load = image_dict.get('metadata')
638 if metadata_to_load:
639 for k,v in yaml.load(metadata_to_load).iteritems():
640 new_image_nova.metadata.setdefault(k,v)
641 return new_image.id
642 except (nvExceptions.Conflict, ksExceptions.ClientException, nvExceptions.ClientException) as e:
643 self._format_exception(e)
644 except (HTTPException, gl1Exceptions.HTTPException, gl1Exceptions.CommunicationError, ConnectionError) as e:
645 if retry==max_retries:
646 continue
647 self._format_exception(e)
648 except IOError as e: #can not open the file
649 raise vimconn.vimconnConnectionException(type(e).__name__ + ": " + str(e)+ " for " + image_dict['location'],
650 http_code=vimconn.HTTP_Bad_Request)
651
652 def delete_image(self, image_id):
653 '''Deletes a tenant image from openstack VIM. Returns the old id
654 '''
655 try:
656 self._reload_connection()
657 self.nova.images.delete(image_id)
658 return image_id
659 except (nvExceptions.NotFound, ksExceptions.ClientException, nvExceptions.ClientException, gl1Exceptions.CommunicationError, ConnectionError) as e: #TODO remove
660 self._format_exception(e)
661
662 def get_image_id_from_path(self, path):
663 '''Get the image id from image path in the VIM database. Returns the image_id'''
664 try:
665 self._reload_connection()
666 images = self.nova.images.list()
667 for image in images:
668 if image.metadata.get("location")==path:
669 return image.id
670 raise vimconn.vimconnNotFoundException("image with location '{}' not found".format( path))
671 except (ksExceptions.ClientException, nvExceptions.ClientException, gl1Exceptions.CommunicationError, ConnectionError) as e:
672 self._format_exception(e)
673
674 def get_image_list(self, filter_dict={}):
675 '''Obtain tenant images from VIM
676 Filter_dict can be:
677 id: image id
678 name: image name
679 checksum: image checksum
680 Returns the image list of dictionaries:
681 [{<the fields at Filter_dict plus some VIM specific>}, ...]
682 List can be empty
683 '''
684 self.logger.debug("Getting image list from VIM filter: '%s'", str(filter_dict))
685 try:
686 self._reload_connection()
687 filter_dict_os=filter_dict.copy()
688 #First we filter by the available filter fields: name, id. The others are removed.
689 filter_dict_os.pop('checksum',None)
690 image_list=self.nova.images.findall(**filter_dict_os)
691 if len(image_list)==0:
692 return []
693 #Then we filter by the rest of filter fields: checksum
694 filtered_list = []
695 for image in image_list:
696 image_class=self.glance.images.get(image.id)
697 if 'checksum' not in filter_dict or image_class['checksum']==filter_dict.get('checksum'):
698 filtered_list.append(image_class.copy())
699 return filtered_list
700 except (ksExceptions.ClientException, nvExceptions.ClientException, gl1Exceptions.CommunicationError, ConnectionError) as e:
701 self._format_exception(e)
702
703 def new_vminstance(self,name,description,start,image_id,flavor_id,net_list,cloud_config=None,disk_list=None):
704 '''Adds a VM instance to VIM
705 Params:
706 start: indicates if VM must start or boot in pause mode. Ignored
707 image_id,flavor_id: iamge and flavor uuid
708 net_list: list of interfaces, each one is a dictionary with:
709 name:
710 net_id: network uuid to connect
711 vpci: virtual vcpi to assign, ignored because openstack lack #TODO
712 model: interface model, ignored #TODO
713 mac_address: used for SR-IOV ifaces #TODO for other types
714 use: 'data', 'bridge', 'mgmt'
715 type: 'virtual', 'PF', 'VF', 'VFnotShared'
716 vim_id: filled/added by this function
717 floating_ip: True/False (or it can be None)
718 #TODO ip, security groups
719 Returns the instance identifier
720 '''
721 self.logger.debug("new_vminstance input: image='%s' flavor='%s' nics='%s'",image_id, flavor_id,str(net_list))
722 try:
723 metadata={}
724 net_list_vim=[]
725 external_network=[] #list of external networks to be connected to instance, later on used to create floating_ip
726 self._reload_connection()
727 metadata_vpci={} #For a specific neutron plugin
728 for net in net_list:
729 if not net.get("net_id"): #skip non connected iface
730 continue
731 if net["type"]=="virtual" or net["type"]=="VF":
732 port_dict={
733 "network_id": net["net_id"],
734 "name": net.get("name"),
735 "admin_state_up": True
736 }
737 if net["type"]=="virtual":
738 if "vpci" in net:
739 metadata_vpci[ net["net_id"] ] = [[ net["vpci"], "" ]]
740 else: # for VF
741 if "vpci" in net:
742 if "VF" not in metadata_vpci:
743 metadata_vpci["VF"]=[]
744 metadata_vpci["VF"].append([ net["vpci"], "" ])
745 port_dict["binding:vnic_type"]="direct"
746 if not port_dict["name"]:
747 port_dict["name"]=name
748 if net.get("mac_address"):
749 port_dict["mac_address"]=net["mac_address"]
750 if net.get("port_security") == False:
751 port_dict["port_security_enabled"]=net["port_security"]
752 new_port = self.neutron.create_port({"port": port_dict })
753 net["mac_adress"] = new_port["port"]["mac_address"]
754 net["vim_id"] = new_port["port"]["id"]
755 net["ip"] = new_port["port"].get("fixed_ips", [{}])[0].get("ip_address")
756 net_list_vim.append({"port-id": new_port["port"]["id"]})
757 else: # for PF
758 self.logger.warn("new_vminstance: Warning, can not connect a passthrough interface ")
759 #TODO insert this when openstack consider passthrough ports as openstack neutron ports
760 if net.get('floating_ip', False):
761 net['exit_on_floating_ip_error'] = True
762 external_network.append(net)
763 elif net['use'] == 'mgmt' and self.config.get('use_floating_ip'):
764 net['exit_on_floating_ip_error'] = False
765 external_network.append(net)
766
767 if metadata_vpci:
768 metadata = {"pci_assignement": json.dumps(metadata_vpci)}
769 if len(metadata["pci_assignement"]) >255:
770 #limit the metadata size
771 #metadata["pci_assignement"] = metadata["pci_assignement"][0:255]
772 self.logger.warn("Metadata deleted since it exceeds the expected length (255) ")
773 metadata = {}
774
775 self.logger.debug("name '%s' image_id '%s'flavor_id '%s' net_list_vim '%s' description '%s' metadata %s",
776 name, image_id, flavor_id, str(net_list_vim), description, str(metadata))
777
778 security_groups = self.config.get('security_groups')
779 if type(security_groups) is str:
780 security_groups = ( security_groups, )
781 #cloud config
782 userdata=None
783 config_drive = None
784 if isinstance(cloud_config, dict):
785 if cloud_config.get("user-data"):
786 userdata=cloud_config["user-data"]
787 if cloud_config.get("boot-data-drive") != None:
788 config_drive = cloud_config["boot-data-drive"]
789 if cloud_config.get("config-files") or cloud_config.get("users") or cloud_config.get("key-pairs"):
790 if userdata:
791 raise vimconn.vimconnConflictException("Cloud-config cannot contain both 'userdata' and 'config-files'/'users'/'key-pairs'")
792 userdata_dict={}
793 #default user
794 if cloud_config.get("key-pairs"):
795 userdata_dict["ssh-authorized-keys"] = cloud_config["key-pairs"]
796 userdata_dict["users"] = [{"default": None, "ssh-authorized-keys": cloud_config["key-pairs"] }]
797 if cloud_config.get("users"):
798 if "users" not in userdata_dict:
799 userdata_dict["users"] = [ "default" ]
800 for user in cloud_config["users"]:
801 user_info = {
802 "name" : user["name"],
803 "sudo": "ALL = (ALL)NOPASSWD:ALL"
804 }
805 if "user-info" in user:
806 user_info["gecos"] = user["user-info"]
807 if user.get("key-pairs"):
808 user_info["ssh-authorized-keys"] = user["key-pairs"]
809 userdata_dict["users"].append(user_info)
810
811 if cloud_config.get("config-files"):
812 userdata_dict["write_files"] = []
813 for file in cloud_config["config-files"]:
814 file_info = {
815 "path" : file["dest"],
816 "content": file["content"]
817 }
818 if file.get("encoding"):
819 file_info["encoding"] = file["encoding"]
820 if file.get("permissions"):
821 file_info["permissions"] = file["permissions"]
822 if file.get("owner"):
823 file_info["owner"] = file["owner"]
824 userdata_dict["write_files"].append(file_info)
825 userdata = "#cloud-config\n"
826 userdata += yaml.safe_dump(userdata_dict, indent=4, default_flow_style=False)
827 self.logger.debug("userdata: %s", userdata)
828 elif isinstance(cloud_config, str):
829 userdata = cloud_config
830
831 #Create additional volumes in case these are present in disk_list
832 block_device_mapping = None
833 base_disk_index = ord('b')
834 if disk_list != None:
835 block_device_mapping = dict()
836 for disk in disk_list:
837 if 'image_id' in disk:
838 volume = self.cinder.volumes.create(size = disk['size'],name = name + '_vd' +
839 chr(base_disk_index), imageRef = disk['image_id'])
840 else:
841 volume = self.cinder.volumes.create(size=disk['size'], name=name + '_vd' +
842 chr(base_disk_index))
843 block_device_mapping['_vd' + chr(base_disk_index)] = volume.id
844 base_disk_index += 1
845
846 #wait until volumes are with status available
847 keep_waiting = True
848 elapsed_time = 0
849 while keep_waiting and elapsed_time < volume_timeout:
850 keep_waiting = False
851 for volume_id in block_device_mapping.itervalues():
852 if self.cinder.volumes.get(volume_id).status != 'available':
853 keep_waiting = True
854 if keep_waiting:
855 time.sleep(1)
856 elapsed_time += 1
857
858 #if we exceeded the timeout rollback
859 if elapsed_time >= volume_timeout:
860 #delete the volumes we just created
861 for volume_id in block_device_mapping.itervalues():
862 self.cinder.volumes.delete(volume_id)
863
864 #delete ports we just created
865 for net_item in net_list_vim:
866 if 'port-id' in net_item:
867 self.neutron.delete_port(net_item['port-id'])
868
869 raise vimconn.vimconnException('Timeout creating volumes for instance ' + name,
870 http_code=vimconn.HTTP_Request_Timeout)
871
872 server = self.nova.servers.create(name, image_id, flavor_id, nics=net_list_vim, meta=metadata,
873 security_groups=security_groups,
874 availability_zone=self.config.get('availability_zone'),
875 key_name=self.config.get('keypair'),
876 userdata=userdata,
877 config_drive = config_drive,
878 block_device_mapping = block_device_mapping
879 ) # , description=description)
880 #print "DONE :-)", server
881 pool_id = None
882 floating_ips = self.neutron.list_floatingips().get("floatingips", ())
883 for floating_network in external_network:
884 try:
885 # wait until vm is active
886 elapsed_time = 0
887 while elapsed_time < server_timeout:
888 status = self.nova.servers.get(server.id).status
889 if status == 'ACTIVE':
890 break
891 time.sleep(1)
892 elapsed_time += 1
893
894 #if we exceeded the timeout rollback
895 if elapsed_time >= server_timeout:
896 raise vimconn.vimconnException('Timeout creating instance ' + name,
897 http_code=vimconn.HTTP_Request_Timeout)
898
899 assigned = False
900 while(assigned == False):
901 if floating_ips:
902 ip = floating_ips.pop(0)
903 if not ip.get("port_id", False) and ip.get('tenant_id') == server.tenant_id:
904 free_floating_ip = ip.get("floating_ip_address")
905 try:
906 fix_ip = floating_network.get('ip')
907 server.add_floating_ip(free_floating_ip, fix_ip)
908 assigned = True
909 except Exception as e:
910 raise vimconn.vimconnException(type(e).__name__ + ": Cannot create floating_ip "+ str(e), http_code=vimconn.HTTP_Conflict)
911 else:
912 #Find the external network
913 external_nets = list()
914 for net in self.neutron.list_networks()['networks']:
915 if net['router:external']:
916 external_nets.append(net)
917
918 if len(external_nets) == 0:
919 raise vimconn.vimconnException("Cannot create floating_ip automatically since no external "
920 "network is present",
921 http_code=vimconn.HTTP_Conflict)
922 if len(external_nets) > 1:
923 raise vimconn.vimconnException("Cannot create floating_ip automatically since multiple "
924 "external networks are present",
925 http_code=vimconn.HTTP_Conflict)
926
927 pool_id = external_nets[0].get('id')
928 param = {'floatingip': {'floating_network_id': pool_id, 'tenant_id': server.tenant_id}}
929 try:
930 #self.logger.debug("Creating floating IP")
931 new_floating_ip = self.neutron.create_floatingip(param)
932 free_floating_ip = new_floating_ip['floatingip']['floating_ip_address']
933 fix_ip = floating_network.get('ip')
934 server.add_floating_ip(free_floating_ip, fix_ip)
935 assigned=True
936 except Exception as e:
937 raise vimconn.vimconnException(type(e).__name__ + ": Cannot assign floating_ip "+ str(e), http_code=vimconn.HTTP_Conflict)
938 except Exception as e:
939 if not floating_network['exit_on_floating_ip_error']:
940 self.logger.warn("Cannot create floating_ip. %s", str(e))
941 continue
942 self.delete_vminstance(server.id)
943 raise
944
945 return server.id
946 # except nvExceptions.NotFound as e:
947 # error_value=-vimconn.HTTP_Not_Found
948 # error_text= "vm instance %s not found" % vm_id
949 except (ksExceptions.ClientException, nvExceptions.ClientException, ConnectionError) as e:
950 # delete the volumes we just created
951 if block_device_mapping != None:
952 for volume_id in block_device_mapping.itervalues():
953 self.cinder.volumes.delete(volume_id)
954
955 # delete ports we just created
956 for net_item in net_list_vim:
957 if 'port-id' in net_item:
958 self.neutron.delete_port(net_item['port-id'])
959 self._format_exception(e)
960 except TypeError as e:
961 raise vimconn.vimconnException(type(e).__name__ + ": "+ str(e), http_code=vimconn.HTTP_Bad_Request)
962
963 def get_vminstance(self,vm_id):
964 '''Returns the VM instance information from VIM'''
965 #self.logger.debug("Getting VM from VIM")
966 try:
967 self._reload_connection()
968 server = self.nova.servers.find(id=vm_id)
969 #TODO parse input and translate to VIM format (openmano_schemas.new_vminstance_response_schema)
970 return server.to_dict()
971 except (ksExceptions.ClientException, nvExceptions.ClientException, nvExceptions.NotFound, ConnectionError) as e:
972 self._format_exception(e)
973
974 def get_vminstance_console(self,vm_id, console_type="vnc"):
975 '''
976 Get a console for the virtual machine
977 Params:
978 vm_id: uuid of the VM
979 console_type, can be:
980 "novnc" (by default), "xvpvnc" for VNC types,
981 "rdp-html5" for RDP types, "spice-html5" for SPICE types
982 Returns dict with the console parameters:
983 protocol: ssh, ftp, http, https, ...
984 server: usually ip address
985 port: the http, ssh, ... port
986 suffix: extra text, e.g. the http path and query string
987 '''
988 self.logger.debug("Getting VM CONSOLE from VIM")
989 try:
990 self._reload_connection()
991 server = self.nova.servers.find(id=vm_id)
992 if console_type == None or console_type == "novnc":
993 console_dict = server.get_vnc_console("novnc")
994 elif console_type == "xvpvnc":
995 console_dict = server.get_vnc_console(console_type)
996 elif console_type == "rdp-html5":
997 console_dict = server.get_rdp_console(console_type)
998 elif console_type == "spice-html5":
999 console_dict = server.get_spice_console(console_type)
1000 else:
1001 raise vimconn.vimconnException("console type '{}' not allowed".format(console_type), http_code=vimconn.HTTP_Bad_Request)
1002
1003 console_dict1 = console_dict.get("console")
1004 if console_dict1:
1005 console_url = console_dict1.get("url")
1006 if console_url:
1007 #parse console_url
1008 protocol_index = console_url.find("//")
1009 suffix_index = console_url[protocol_index+2:].find("/") + protocol_index+2
1010 port_index = console_url[protocol_index+2:suffix_index].find(":") + protocol_index+2
1011 if protocol_index < 0 or port_index<0 or suffix_index<0:
1012 return -vimconn.HTTP_Internal_Server_Error, "Unexpected response from VIM"
1013 console_dict={"protocol": console_url[0:protocol_index],
1014 "server": console_url[protocol_index+2:port_index],
1015 "port": console_url[port_index:suffix_index],
1016 "suffix": console_url[suffix_index+1:]
1017 }
1018 protocol_index += 2
1019 return console_dict
1020 raise vimconn.vimconnUnexpectedResponse("Unexpected response from VIM")
1021
1022 except (nvExceptions.NotFound, ksExceptions.ClientException, nvExceptions.ClientException, nvExceptions.BadRequest, ConnectionError) as e:
1023 self._format_exception(e)
1024
1025 def delete_vminstance(self, vm_id):
1026 '''Removes a VM instance from VIM. Returns the old identifier
1027 '''
1028 #print "osconnector: Getting VM from VIM"
1029 try:
1030 self._reload_connection()
1031 #delete VM ports attached to this networks before the virtual machine
1032 ports = self.neutron.list_ports(device_id=vm_id)
1033 for p in ports['ports']:
1034 try:
1035 self.neutron.delete_port(p["id"])
1036 except Exception as e:
1037 self.logger.error("Error deleting port: " + type(e).__name__ + ": "+ str(e))
1038
1039 #commented because detaching the volumes makes the servers.delete not work properly ?!?
1040 #dettach volumes attached
1041 server = self.nova.servers.get(vm_id)
1042 volumes_attached_dict = server._info['os-extended-volumes:volumes_attached']
1043 #for volume in volumes_attached_dict:
1044 # self.cinder.volumes.detach(volume['id'])
1045
1046 self.nova.servers.delete(vm_id)
1047
1048 #delete volumes.
1049 #Although having detached them should have them in active status
1050 #we ensure in this loop
1051 keep_waiting = True
1052 elapsed_time = 0
1053 while keep_waiting and elapsed_time < volume_timeout:
1054 keep_waiting = False
1055 for volume in volumes_attached_dict:
1056 if self.cinder.volumes.get(volume['id']).status != 'available':
1057 keep_waiting = True
1058 else:
1059 self.cinder.volumes.delete(volume['id'])
1060 if keep_waiting:
1061 time.sleep(1)
1062 elapsed_time += 1
1063
1064 return vm_id
1065 except (nvExceptions.NotFound, ksExceptions.ClientException, nvExceptions.ClientException, ConnectionError) as e:
1066 self._format_exception(e)
1067 #TODO insert exception vimconn.HTTP_Unauthorized
1068 #if reaching here is because an exception
1069
1070 def refresh_vms_status(self, vm_list):
1071 '''Get the status of the virtual machines and their interfaces/ports
1072 Params: the list of VM identifiers
1073 Returns a dictionary with:
1074 vm_id: #VIM id of this Virtual Machine
1075 status: #Mandatory. Text with one of:
1076 # DELETED (not found at vim)
1077 # VIM_ERROR (Cannot connect to VIM, VIM response error, ...)
1078 # OTHER (Vim reported other status not understood)
1079 # ERROR (VIM indicates an ERROR status)
1080 # ACTIVE, PAUSED, SUSPENDED, INACTIVE (not running),
1081 # CREATING (on building process), ERROR
1082 # ACTIVE:NoMgmtIP (Active but any of its interface has an IP address
1083 #
1084 error_msg: #Text with VIM error message, if any. Or the VIM connection ERROR
1085 vim_info: #Text with plain information obtained from vim (yaml.safe_dump)
1086 interfaces:
1087 - vim_info: #Text with plain information obtained from vim (yaml.safe_dump)
1088 mac_address: #Text format XX:XX:XX:XX:XX:XX
1089 vim_net_id: #network id where this interface is connected
1090 vim_interface_id: #interface/port VIM id
1091 ip_address: #null, or text with IPv4, IPv6 address
1092 '''
1093 vm_dict={}
1094 self.logger.debug("refresh_vms status: Getting tenant VM instance information from VIM")
1095 for vm_id in vm_list:
1096 vm={}
1097 try:
1098 vm_vim = self.get_vminstance(vm_id)
1099 if vm_vim['status'] in vmStatus2manoFormat:
1100 vm['status'] = vmStatus2manoFormat[ vm_vim['status'] ]
1101 else:
1102 vm['status'] = "OTHER"
1103 vm['error_msg'] = "VIM status reported " + vm_vim['status']
1104 try:
1105 vm['vim_info'] = yaml.safe_dump(vm_vim, default_flow_style=True, width=256)
1106 except yaml.representer.RepresenterError:
1107 vm['vim_info'] = str(vm_vim)
1108 vm["interfaces"] = []
1109 if vm_vim.get('fault'):
1110 vm['error_msg'] = str(vm_vim['fault'])
1111 #get interfaces
1112 try:
1113 self._reload_connection()
1114 port_dict=self.neutron.list_ports(device_id=vm_id)
1115 for port in port_dict["ports"]:
1116 interface={}
1117 try:
1118 interface['vim_info'] = yaml.safe_dump(port, default_flow_style=True, width=256)
1119 except yaml.representer.RepresenterError:
1120 interface['vim_info'] = str(port)
1121 interface["mac_address"] = port.get("mac_address")
1122 interface["vim_net_id"] = port["network_id"]
1123 interface["vim_interface_id"] = port["id"]
1124 ips=[]
1125 #look for floating ip address
1126 floating_ip_dict = self.neutron.list_floatingips(port_id=port["id"])
1127 if floating_ip_dict.get("floatingips"):
1128 ips.append(floating_ip_dict["floatingips"][0].get("floating_ip_address") )
1129
1130 for subnet in port["fixed_ips"]:
1131 ips.append(subnet["ip_address"])
1132 interface["ip_address"] = ";".join(ips)
1133 vm["interfaces"].append(interface)
1134 except Exception as e:
1135 self.logger.error("Error getting vm interface information " + type(e).__name__ + ": "+ str(e))
1136 except vimconn.vimconnNotFoundException as e:
1137 self.logger.error("Exception getting vm status: %s", str(e))
1138 vm['status'] = "DELETED"
1139 vm['error_msg'] = str(e)
1140 except vimconn.vimconnException as e:
1141 self.logger.error("Exception getting vm status: %s", str(e))
1142 vm['status'] = "VIM_ERROR"
1143 vm['error_msg'] = str(e)
1144 vm_dict[vm_id] = vm
1145 return vm_dict
1146
1147 def action_vminstance(self, vm_id, action_dict):
1148 '''Send and action over a VM instance from VIM
1149 Returns the vm_id if the action was successfully sent to the VIM'''
1150 self.logger.debug("Action over VM '%s': %s", vm_id, str(action_dict))
1151 try:
1152 self._reload_connection()
1153 server = self.nova.servers.find(id=vm_id)
1154 if "start" in action_dict:
1155 if action_dict["start"]=="rebuild":
1156 server.rebuild()
1157 else:
1158 if server.status=="PAUSED":
1159 server.unpause()
1160 elif server.status=="SUSPENDED":
1161 server.resume()
1162 elif server.status=="SHUTOFF":
1163 server.start()
1164 elif "pause" in action_dict:
1165 server.pause()
1166 elif "resume" in action_dict:
1167 server.resume()
1168 elif "shutoff" in action_dict or "shutdown" in action_dict:
1169 server.stop()
1170 elif "forceOff" in action_dict:
1171 server.stop() #TODO
1172 elif "terminate" in action_dict:
1173 server.delete()
1174 elif "createImage" in action_dict:
1175 server.create_image()
1176 #"path":path_schema,
1177 #"description":description_schema,
1178 #"name":name_schema,
1179 #"metadata":metadata_schema,
1180 #"imageRef": id_schema,
1181 #"disk": {"oneOf":[{"type": "null"}, {"type":"string"}] },
1182 elif "rebuild" in action_dict:
1183 server.rebuild(server.image['id'])
1184 elif "reboot" in action_dict:
1185 server.reboot() #reboot_type='SOFT'
1186 elif "console" in action_dict:
1187 console_type = action_dict["console"]
1188 if console_type == None or console_type == "novnc":
1189 console_dict = server.get_vnc_console("novnc")
1190 elif console_type == "xvpvnc":
1191 console_dict = server.get_vnc_console(console_type)
1192 elif console_type == "rdp-html5":
1193 console_dict = server.get_rdp_console(console_type)
1194 elif console_type == "spice-html5":
1195 console_dict = server.get_spice_console(console_type)
1196 else:
1197 raise vimconn.vimconnException("console type '{}' not allowed".format(console_type),
1198 http_code=vimconn.HTTP_Bad_Request)
1199 try:
1200 console_url = console_dict["console"]["url"]
1201 #parse console_url
1202 protocol_index = console_url.find("//")
1203 suffix_index = console_url[protocol_index+2:].find("/") + protocol_index+2
1204 port_index = console_url[protocol_index+2:suffix_index].find(":") + protocol_index+2
1205 if protocol_index < 0 or port_index<0 or suffix_index<0:
1206 raise vimconn.vimconnException("Unexpected response from VIM " + str(console_dict))
1207 console_dict2={"protocol": console_url[0:protocol_index],
1208 "server": console_url[protocol_index+2 : port_index],
1209 "port": int(console_url[port_index+1 : suffix_index]),
1210 "suffix": console_url[suffix_index+1:]
1211 }
1212 return console_dict2
1213 except Exception as e:
1214 raise vimconn.vimconnException("Unexpected response from VIM " + str(console_dict))
1215
1216 return vm_id
1217 except (ksExceptions.ClientException, nvExceptions.ClientException, nvExceptions.NotFound, ConnectionError) as e:
1218 self._format_exception(e)
1219 #TODO insert exception vimconn.HTTP_Unauthorized
1220
1221 #NOT USED FUNCTIONS
1222
1223 def new_external_port(self, port_data):
1224 #TODO openstack if needed
1225 '''Adds a external port to VIM'''
1226 '''Returns the port identifier'''
1227 return -vimconn.HTTP_Internal_Server_Error, "osconnector.new_external_port() not implemented"
1228
1229 def connect_port_network(self, port_id, network_id, admin=False):
1230 #TODO openstack if needed
1231 '''Connects a external port to a network'''
1232 '''Returns status code of the VIM response'''
1233 return -vimconn.HTTP_Internal_Server_Error, "osconnector.connect_port_network() not implemented"
1234
1235 def new_user(self, user_name, user_passwd, tenant_id=None):
1236 '''Adds a new user to openstack VIM'''
1237 '''Returns the user identifier'''
1238 self.logger.debug("osconnector: Adding a new user to VIM")
1239 try:
1240 self._reload_connection()
1241 user=self.keystone.users.create(user_name, user_passwd, tenant_id=tenant_id)
1242 #self.keystone.tenants.add_user(self.k_creds["username"], #role)
1243 return user.id
1244 except ksExceptions.ConnectionError as e:
1245 error_value=-vimconn.HTTP_Bad_Request
1246 error_text= type(e).__name__ + ": "+ (str(e) if len(e.args)==0 else str(e.args[0]))
1247 except ksExceptions.ClientException as e: #TODO remove
1248 error_value=-vimconn.HTTP_Bad_Request
1249 error_text= type(e).__name__ + ": "+ (str(e) if len(e.args)==0 else str(e.args[0]))
1250 #TODO insert exception vimconn.HTTP_Unauthorized
1251 #if reaching here is because an exception
1252 if self.debug:
1253 self.logger.debug("new_user " + error_text)
1254 return error_value, error_text
1255
1256 def delete_user(self, user_id):
1257 '''Delete a user from openstack VIM'''
1258 '''Returns the user identifier'''
1259 if self.debug:
1260 print "osconnector: Deleting a user from VIM"
1261 try:
1262 self._reload_connection()
1263 self.keystone.users.delete(user_id)
1264 return 1, user_id
1265 except ksExceptions.ConnectionError as e:
1266 error_value=-vimconn.HTTP_Bad_Request
1267 error_text= type(e).__name__ + ": "+ (str(e) if len(e.args)==0 else str(e.args[0]))
1268 except ksExceptions.NotFound as e:
1269 error_value=-vimconn.HTTP_Not_Found
1270 error_text= type(e).__name__ + ": "+ (str(e) if len(e.args)==0 else str(e.args[0]))
1271 except ksExceptions.ClientException as e: #TODO remove
1272 error_value=-vimconn.HTTP_Bad_Request
1273 error_text= type(e).__name__ + ": "+ (str(e) if len(e.args)==0 else str(e.args[0]))
1274 #TODO insert exception vimconn.HTTP_Unauthorized
1275 #if reaching here is because an exception
1276 if self.debug:
1277 print "delete_tenant " + error_text
1278 return error_value, error_text
1279
1280 def get_hosts_info(self):
1281 '''Get the information of deployed hosts
1282 Returns the hosts content'''
1283 if self.debug:
1284 print "osconnector: Getting Host info from VIM"
1285 try:
1286 h_list=[]
1287 self._reload_connection()
1288 hypervisors = self.nova.hypervisors.list()
1289 for hype in hypervisors:
1290 h_list.append( hype.to_dict() )
1291 return 1, {"hosts":h_list}
1292 except nvExceptions.NotFound as e:
1293 error_value=-vimconn.HTTP_Not_Found
1294 error_text= (str(e) if len(e.args)==0 else str(e.args[0]))
1295 except (ksExceptions.ClientException, nvExceptions.ClientException) as e:
1296 error_value=-vimconn.HTTP_Bad_Request
1297 error_text= type(e).__name__ + ": "+ (str(e) if len(e.args)==0 else str(e.args[0]))
1298 #TODO insert exception vimconn.HTTP_Unauthorized
1299 #if reaching here is because an exception
1300 if self.debug:
1301 print "get_hosts_info " + error_text
1302 return error_value, error_text
1303
1304 def get_hosts(self, vim_tenant):
1305 '''Get the hosts and deployed instances
1306 Returns the hosts content'''
1307 r, hype_dict = self.get_hosts_info()
1308 if r<0:
1309 return r, hype_dict
1310 hypervisors = hype_dict["hosts"]
1311 try:
1312 servers = self.nova.servers.list()
1313 for hype in hypervisors:
1314 for server in servers:
1315 if server.to_dict()['OS-EXT-SRV-ATTR:hypervisor_hostname']==hype['hypervisor_hostname']:
1316 if 'vm' in hype:
1317 hype['vm'].append(server.id)
1318 else:
1319 hype['vm'] = [server.id]
1320 return 1, hype_dict
1321 except nvExceptions.NotFound as e:
1322 error_value=-vimconn.HTTP_Not_Found
1323 error_text= (str(e) if len(e.args)==0 else str(e.args[0]))
1324 except (ksExceptions.ClientException, nvExceptions.ClientException) as e:
1325 error_value=-vimconn.HTTP_Bad_Request
1326 error_text= type(e).__name__ + ": "+ (str(e) if len(e.args)==0 else str(e.args[0]))
1327 #TODO insert exception vimconn.HTTP_Unauthorized
1328 #if reaching here is because an exception
1329 if self.debug:
1330 print "get_hosts " + error_text
1331 return error_value, error_text
1332
1333