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