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