blob: c5b4ce1f4679598965c16fa7c2c03fa66501ce26 [file] [log] [blame]
tierno7edb6752016-03-21 17:37:52 +01001# -*- 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'''
25osconnector implements all the methods to interact with openstack using the python-client.
26'''
montesmoreno0c8def02016-12-22 12:16:23 +000027__author__="Alfonso Tierno, Gerardo Garcia, Pablo Montes, xFlow Research"
28__date__ ="$22-jun-2014 11:19:29$"
tierno7edb6752016-03-21 17:37:52 +010029
30import vimconn
31import json
32import yaml
tiernoae4a8d12016-07-08 12:30:39 +020033import logging
garciadeblas9f8456e2016-09-05 05:02:59 +020034import netaddr
montesmoreno0c8def02016-12-22 12:16:23 +000035import time
tierno7edb6752016-03-21 17:37:52 +010036
ahmadsa95baa272016-11-30 09:14:11 +050037from novaclient import client as nClient_v2, exceptions as nvExceptions, api_versions as APIVersion
38import keystoneclient.v2_0.client as ksClient_v2
39from novaclient.v2.client import Client as nClient
40import keystoneclient.v3.client as ksClient
tierno7edb6752016-03-21 17:37:52 +010041import keystoneclient.exceptions as ksExceptions
42import glanceclient.v2.client as glClient
43import glanceclient.client as gl1Client
44import glanceclient.exc as gl1Exceptions
montesmoreno0c8def02016-12-22 12:16:23 +000045import cinderclient.v2.client as cClient_v2
tierno7edb6752016-03-21 17:37:52 +010046from httplib import HTTPException
ahmadsa95baa272016-11-30 09:14:11 +050047from neutronclient.neutron import client as neClient_v2
48from neutronclient.v2_0 import client as neClient
tierno7edb6752016-03-21 17:37:52 +010049from neutronclient.common import exceptions as neExceptions
50from requests.exceptions import ConnectionError
51
52'''contain the openstack virtual machine status to openmano status'''
53vmStatus2manoFormat={'ACTIVE':'ACTIVE',
54 'PAUSED':'PAUSED',
55 'SUSPENDED': 'SUSPENDED',
56 'SHUTOFF':'INACTIVE',
57 'BUILD':'BUILD',
58 'ERROR':'ERROR','DELETED':'DELETED'
59 }
60netStatus2manoFormat={'ACTIVE':'ACTIVE','PAUSED':'PAUSED','INACTIVE':'INACTIVE','BUILD':'BUILD','ERROR':'ERROR','DELETED':'DELETED'
61 }
62
montesmoreno0c8def02016-12-22 12:16:23 +000063#global var to have a timeout creating and deleting volumes
64volume_timeout = 60
montesmoreno2a1fc4e2017-01-09 16:46:04 +000065server_timeout = 60
montesmoreno0c8def02016-12-22 12:16:23 +000066
tierno7edb6752016-03-21 17:37:52 +010067class vimconnector(vimconn.vimconnector):
tiernofe789902016-09-29 14:20:44 +000068 def __init__(self, uuid, name, tenant_id, tenant_name, url, url_admin=None, user=None, passwd=None, log_level=None, config={}):
tierno7edb6752016-03-21 17:37:52 +010069 '''using common constructor parameters. In this case
70 'url' is the keystone authorization url,
71 'url_admin' is not use
72 '''
ahmadsa95baa272016-11-30 09:14:11 +050073 self.osc_api_version = 'v2.0'
74 if config.get('APIversion') == 'v3.3':
75 self.osc_api_version = 'v3.3'
tiernoae4a8d12016-07-08 12:30:39 +020076 vimconn.vimconnector.__init__(self, uuid, name, tenant_id, tenant_name, url, url_admin, user, passwd, log_level, config)
tierno7edb6752016-03-21 17:37:52 +010077
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
tierno392f2852016-05-13 12:28:55 +020084 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
tierno7edb6752016-03-21 17:37:52 +010090 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
ahmadsa95baa272016-11-30 09:14:11 +050096 if self.osc_api_version == 'v3.3':
97 self.k_creds['project_name'] = tenant_name
98 self.k_creds['project_id'] = tenant_id
montesmoreno0c8def02016-12-22 12:16:23 +000099
tierno7edb6752016-03-21 17:37:52 +0100100 self.reload_client = True
tierno73ad9e42016-09-12 18:11:11 +0200101 self.logger = logging.getLogger('openmano.vim.openstack')
tiernofe789902016-09-29 14:20:44 +0000102 if log_level:
103 self.logger.setLevel( getattr(logging, log_level) )
tierno7edb6752016-03-21 17:37:52 +0100104
105 def __setitem__(self,index, value):
106 '''Set individuals parameters
107 Throw TypeError, KeyError
108 '''
tierno392f2852016-05-13 12:28:55 +0200109 if index=='tenant_id':
tierno7edb6752016-03-21 17:37:52 +0100110 self.reload_client=True
tierno392f2852016-05-13 12:28:55 +0200111 self.tenant_id = value
ahmadsa95baa272016-11-30 09:14:11 +0500112 if self.osc_api_version == 'v3.3':
113 if value:
114 self.k_creds['project_id'] = value
115 self.n_creds['project_id'] = value
116 else:
117 del self.k_creds['project_id']
118 del self.n_creds['project_id']
tierno392f2852016-05-13 12:28:55 +0200119 else:
ahmadsa95baa272016-11-30 09:14:11 +0500120 if value:
121 self.k_creds['tenant_id'] = value
122 self.n_creds['tenant_id'] = value
123 else:
124 del self.k_creds['tenant_id']
125 del self.n_creds['tenant_id']
tierno392f2852016-05-13 12:28:55 +0200126 elif index=='tenant_name':
127 self.reload_client=True
128 self.tenant_name = value
ahmadsa95baa272016-11-30 09:14:11 +0500129 if self.osc_api_version == 'v3.3':
130 if value:
131 self.k_creds['project_name'] = value
132 self.n_creds['project_name'] = value
133 else:
134 del self.k_creds['project_name']
135 del self.n_creds['project_name']
tierno7edb6752016-03-21 17:37:52 +0100136 else:
ahmadsa95baa272016-11-30 09:14:11 +0500137 if value:
138 self.k_creds['tenant_name'] = value
139 self.n_creds['project_id'] = value
140 else:
141 del self.k_creds['tenant_name']
142 del self.n_creds['project_id']
tierno7edb6752016-03-21 17:37:52 +0100143 elif index=='user':
144 self.reload_client=True
145 self.user = value
146 if value:
147 self.k_creds['username'] = value
148 self.n_creds['username'] = value
149 else:
150 del self.k_creds['username']
151 del self.n_creds['username']
152 elif index=='passwd':
153 self.reload_client=True
154 self.passwd = value
155 if value:
156 self.k_creds['password'] = value
157 self.n_creds['api_key'] = value
158 else:
159 del self.k_creds['password']
160 del self.n_creds['api_key']
161 elif index=='url':
162 self.reload_client=True
163 self.url = value
164 if value:
165 self.k_creds['auth_url'] = value
166 self.n_creds['auth_url'] = value
167 else:
168 raise TypeError, 'url param can not be NoneType'
169 else:
170 vimconn.vimconnector.__setitem__(self,index, value)
171
172 def _reload_connection(self):
173 '''Called before any operation, it check if credentials has changed
174 Throw keystoneclient.apiclient.exceptions.AuthorizationFailure
175 '''
176 #TODO control the timing and possible token timeout, but it seams that python client does this task for us :-)
177 if self.reload_client:
178 #test valid params
179 if len(self.n_creds) <4:
180 raise ksExceptions.ClientException("Not enough parameters to connect to openstack")
ahmadsa95baa272016-11-30 09:14:11 +0500181 if self.osc_api_version == 'v3.3':
182 self.nova = nClient(APIVersion(version_str='2'), **self.n_creds)
montesmoreno0c8def02016-12-22 12:16:23 +0000183 #TODO To be updated for v3
184 #self.cinder = cClient.Client(**self.n_creds)
ahmadsa95baa272016-11-30 09:14:11 +0500185 self.keystone = ksClient.Client(**self.k_creds)
186 self.ne_endpoint=self.keystone.service_catalog.url_for(service_type='network', endpoint_type='publicURL')
187 self.neutron = neClient.Client(APIVersion(version_str='2'), endpoint_url=self.ne_endpoint, token=self.keystone.auth_token, **self.k_creds)
188 else:
189 self.nova = nClient_v2.Client('2', **self.n_creds)
montesmoreno0c8def02016-12-22 12:16:23 +0000190 self.cinder = cClient_v2.Client(**self.n_creds)
ahmadsa95baa272016-11-30 09:14:11 +0500191 self.keystone = ksClient_v2.Client(**self.k_creds)
192 self.ne_endpoint=self.keystone.service_catalog.url_for(service_type='network', endpoint_type='publicURL')
193 self.neutron = neClient_v2.Client('2.0', endpoint_url=self.ne_endpoint, token=self.keystone.auth_token, **self.k_creds)
tierno7edb6752016-03-21 17:37:52 +0100194 self.glance_endpoint = self.keystone.service_catalog.url_for(service_type='image', endpoint_type='publicURL')
195 self.glance = glClient.Client(self.glance_endpoint, token=self.keystone.auth_token, **self.k_creds) #TODO check k_creds vs n_creds
tierno7edb6752016-03-21 17:37:52 +0100196 self.reload_client = False
ahmadsa95baa272016-11-30 09:14:11 +0500197
tierno7edb6752016-03-21 17:37:52 +0100198 def __net_os2mano(self, net_list_dict):
199 '''Transform the net openstack format to mano format
200 net_list_dict can be a list of dict or a single dict'''
201 if type(net_list_dict) is dict:
202 net_list_=(net_list_dict,)
203 elif type(net_list_dict) is list:
204 net_list_=net_list_dict
205 else:
206 raise TypeError("param net_list_dict must be a list or a dictionary")
207 for net in net_list_:
208 if net.get('provider:network_type') == "vlan":
209 net['type']='data'
210 else:
211 net['type']='bridge'
tiernoae4a8d12016-07-08 12:30:39 +0200212
213
214
215 def _format_exception(self, exception):
216 '''Transform a keystone, nova, neutron exception into a vimconn exception'''
217 if isinstance(exception, (HTTPException, gl1Exceptions.HTTPException, gl1Exceptions.CommunicationError,
tierno8e995ce2016-09-22 08:13:00 +0000218 ConnectionError, ksExceptions.ConnectionError, neExceptions.ConnectionFailed
219 )):
tiernoae4a8d12016-07-08 12:30:39 +0200220 raise vimconn.vimconnConnectionException(type(exception).__name__ + ": " + str(exception))
221 elif isinstance(exception, (nvExceptions.ClientException, ksExceptions.ClientException,
222 neExceptions.NeutronException, nvExceptions.BadRequest)):
223 raise vimconn.vimconnUnexpectedResponse(type(exception).__name__ + ": " + str(exception))
224 elif isinstance(exception, (neExceptions.NetworkNotFoundClient, nvExceptions.NotFound)):
225 raise vimconn.vimconnNotFoundException(type(exception).__name__ + ": " + str(exception))
226 elif isinstance(exception, nvExceptions.Conflict):
227 raise vimconn.vimconnConflictException(type(exception).__name__ + ": " + str(exception))
228 else: # ()
229 raise vimconn.vimconnConnectionException(type(exception).__name__ + ": " + str(exception))
230
231 def get_tenant_list(self, filter_dict={}):
232 '''Obtain tenants of VIM
233 filter_dict can contain the following keys:
234 name: filter by tenant name
235 id: filter by tenant uuid/id
236 <other VIM specific>
237 Returns the tenant list of dictionaries: [{'name':'<name>, 'id':'<id>, ...}, ...]
238 '''
ahmadsa95baa272016-11-30 09:14:11 +0500239 self.logger.debug("Getting tenants from VIM filter: '%s'", str(filter_dict))
tiernoae4a8d12016-07-08 12:30:39 +0200240 try:
241 self._reload_connection()
montesmoreno0c8def02016-12-22 12:16:23 +0000242 if self.osc_api_version == 'v3.3':
ahmadsa95baa272016-11-30 09:14:11 +0500243 project_class_list=self.keystone.projects.findall(**filter_dict)
244 else:
245 project_class_list=self.keystone.tenants.findall(**filter_dict)
246 project_list=[]
247 for project in project_class_list:
248 project_list.append(project.to_dict())
249 return project_list
tierno8e995ce2016-09-22 08:13:00 +0000250 except (ksExceptions.ConnectionError, ksExceptions.ClientException, ConnectionError) as e:
tiernoae4a8d12016-07-08 12:30:39 +0200251 self._format_exception(e)
252
253 def new_tenant(self, tenant_name, tenant_description):
254 '''Adds a new tenant to openstack VIM. Returns the tenant identifier'''
255 self.logger.debug("Adding a new tenant name: %s", tenant_name)
256 try:
257 self._reload_connection()
ahmadsa95baa272016-11-30 09:14:11 +0500258 if self.osc_api_version == 'v3.3':
259 project=self.keystone.projects.create(tenant_name, tenant_description)
260 else:
261 project=self.keystone.tenants.create(tenant_name, tenant_description)
262 return project.id
tierno8e995ce2016-09-22 08:13:00 +0000263 except (ksExceptions.ConnectionError, ksExceptions.ClientException, ConnectionError) as e:
tiernoae4a8d12016-07-08 12:30:39 +0200264 self._format_exception(e)
265
266 def delete_tenant(self, tenant_id):
267 '''Delete a tenant from openstack VIM. Returns the old tenant identifier'''
268 self.logger.debug("Deleting tenant %s from VIM", tenant_id)
269 try:
270 self._reload_connection()
montesmoreno0c8def02016-12-22 12:16:23 +0000271 if self.osc_api_version == 'v3.3':
ahmadsa95baa272016-11-30 09:14:11 +0500272 self.keystone.projects.delete(tenant_id)
273 else:
274 self.keystone.tenants.delete(tenant_id)
tiernoae4a8d12016-07-08 12:30:39 +0200275 return tenant_id
tierno8e995ce2016-09-22 08:13:00 +0000276 except (ksExceptions.ConnectionError, ksExceptions.ClientException, ConnectionError) as e:
tiernoae4a8d12016-07-08 12:30:39 +0200277 self._format_exception(e)
ahmadsa95baa272016-11-30 09:14:11 +0500278
garciadeblas9f8456e2016-09-05 05:02:59 +0200279 def new_network(self,net_name, net_type, ip_profile=None, shared=False, vlan=None):
tiernoae4a8d12016-07-08 12:30:39 +0200280 '''Adds a tenant network to VIM. Returns the network identifier'''
281 self.logger.debug("Adding a new network to VIM name '%s', type '%s'", net_name, net_type)
garciadeblasedca7b32016-09-29 14:01:52 +0000282 #self.logger.debug(">>>>>>>>>>>>>>>>>> IP profile %s", str(ip_profile))
tierno7edb6752016-03-21 17:37:52 +0100283 try:
garciadeblasedca7b32016-09-29 14:01:52 +0000284 new_net = None
tierno7edb6752016-03-21 17:37:52 +0100285 self._reload_connection()
286 network_dict = {'name': net_name, 'admin_state_up': True}
287 if net_type=="data" or net_type=="ptp":
288 if self.config.get('dataplane_physical_net') == None:
tiernoae4a8d12016-07-08 12:30:39 +0200289 raise vimconn.vimconnConflictException("You must provide a 'dataplane_physical_net' at config value before creating sriov network")
tierno7edb6752016-03-21 17:37:52 +0100290 network_dict["provider:physical_network"] = self.config['dataplane_physical_net'] #"physnet_sriov" #TODO physical
291 network_dict["provider:network_type"] = "vlan"
292 if vlan!=None:
293 network_dict["provider:network_type"] = vlan
tiernoae4a8d12016-07-08 12:30:39 +0200294 network_dict["shared"]=shared
tierno7edb6752016-03-21 17:37:52 +0100295 new_net=self.neutron.create_network({'network':network_dict})
296 #print new_net
garciadeblas9f8456e2016-09-05 05:02:59 +0200297 #create subnetwork, even if there is no profile
298 if not ip_profile:
299 ip_profile = {}
300 if 'subnet_address' not in ip_profile:
301 #Fake subnet is required
302 ip_profile['subnet_address'] = "192.168.111.0/24"
303 if 'ip_version' not in ip_profile:
304 ip_profile['ip_version'] = "IPv4"
tierno7edb6752016-03-21 17:37:52 +0100305 subnet={"name":net_name+"-subnet",
306 "network_id": new_net["network"]["id"],
garciadeblas9f8456e2016-09-05 05:02:59 +0200307 "ip_version": 4 if ip_profile['ip_version']=="IPv4" else 6,
308 "cidr": ip_profile['subnet_address']
tierno7edb6752016-03-21 17:37:52 +0100309 }
garciadeblas9f8456e2016-09-05 05:02:59 +0200310 if 'gateway_address' in ip_profile:
311 subnet['gateway_ip'] = ip_profile['gateway_address']
garciadeblasedca7b32016-09-29 14:01:52 +0000312 if ip_profile.get('dns_address'):
garciadeblas9f8456e2016-09-05 05:02:59 +0200313 #TODO: manage dns_address as a list of addresses separated by commas
314 subnet['dns_nameservers'] = []
315 subnet['dns_nameservers'].append(ip_profile['dns_address'])
316 if 'dhcp_enabled' in ip_profile:
317 subnet['enable_dhcp'] = False if ip_profile['dhcp_enabled']=="false" else True
318 if 'dhcp_start_address' in ip_profile:
319 subnet['allocation_pools']=[]
320 subnet['allocation_pools'].append(dict())
321 subnet['allocation_pools'][0]['start'] = ip_profile['dhcp_start_address']
322 if 'dhcp_count' in ip_profile:
323 #parts = ip_profile['dhcp_start_address'].split('.')
324 #ip_int = (int(parts[0]) << 24) + (int(parts[1]) << 16) + (int(parts[2]) << 8) + int(parts[3])
325 ip_int = int(netaddr.IPAddress(ip_profile['dhcp_start_address']))
garciadeblas21d795b2016-09-29 17:31:46 +0200326 ip_int += ip_profile['dhcp_count'] - 1
garciadeblas9f8456e2016-09-05 05:02:59 +0200327 ip_str = str(netaddr.IPAddress(ip_int))
328 subnet['allocation_pools'][0]['end'] = ip_str
garciadeblasedca7b32016-09-29 14:01:52 +0000329 #self.logger.debug(">>>>>>>>>>>>>>>>>> Subnet: %s", str(subnet))
tierno7edb6752016-03-21 17:37:52 +0100330 self.neutron.create_subnet({"subnet": subnet} )
tiernoae4a8d12016-07-08 12:30:39 +0200331 return new_net["network"]["id"]
tierno8e995ce2016-09-22 08:13:00 +0000332 except (neExceptions.ConnectionFailed, ksExceptions.ClientException, neExceptions.NeutronException, ConnectionError) as e:
garciadeblasedca7b32016-09-29 14:01:52 +0000333 if new_net:
334 self.neutron.delete_network(new_net['network']['id'])
tiernoae4a8d12016-07-08 12:30:39 +0200335 self._format_exception(e)
tierno7edb6752016-03-21 17:37:52 +0100336
337 def get_network_list(self, filter_dict={}):
338 '''Obtain tenant networks of VIM
339 Filter_dict can be:
340 name: network name
341 id: network uuid
342 shared: boolean
343 tenant_id: tenant
344 admin_state_up: boolean
345 status: 'ACTIVE'
346 Returns the network list of dictionaries
347 '''
tiernoae4a8d12016-07-08 12:30:39 +0200348 self.logger.debug("Getting network from VIM filter: '%s'", str(filter_dict))
tierno7edb6752016-03-21 17:37:52 +0100349 try:
350 self._reload_connection()
montesmoreno0c8def02016-12-22 12:16:23 +0000351 if self.osc_api_version == 'v3.3' and "tenant_id" in filter_dict:
ahmadsa95baa272016-11-30 09:14:11 +0500352 filter_dict['project_id'] = filter_dict.pop('tenant_id')
tierno7edb6752016-03-21 17:37:52 +0100353 net_dict=self.neutron.list_networks(**filter_dict)
354 net_list=net_dict["networks"]
355 self.__net_os2mano(net_list)
tiernoae4a8d12016-07-08 12:30:39 +0200356 return net_list
tierno8e995ce2016-09-22 08:13:00 +0000357 except (neExceptions.ConnectionFailed, ksExceptions.ClientException, neExceptions.NeutronException, ConnectionError) as e:
tiernoae4a8d12016-07-08 12:30:39 +0200358 self._format_exception(e)
tierno7edb6752016-03-21 17:37:52 +0100359
tiernoae4a8d12016-07-08 12:30:39 +0200360 def get_network(self, net_id):
361 '''Obtain details of network from VIM
362 Returns the network information from a network id'''
363 self.logger.debug(" Getting tenant network %s from VIM", net_id)
tierno7edb6752016-03-21 17:37:52 +0100364 filter_dict={"id": net_id}
tiernoae4a8d12016-07-08 12:30:39 +0200365 net_list = self.get_network_list(filter_dict)
tierno7edb6752016-03-21 17:37:52 +0100366 if len(net_list)==0:
tiernoae4a8d12016-07-08 12:30:39 +0200367 raise vimconn.vimconnNotFoundException("Network '{}' not found".format(net_id))
tierno7edb6752016-03-21 17:37:52 +0100368 elif len(net_list)>1:
tiernoae4a8d12016-07-08 12:30:39 +0200369 raise vimconn.vimconnConflictException("Found more than one network with this criteria")
tierno7edb6752016-03-21 17:37:52 +0100370 net = net_list[0]
371 subnets=[]
372 for subnet_id in net.get("subnets", () ):
373 try:
374 subnet = self.neutron.show_subnet(subnet_id)
375 except Exception as e:
tiernoae4a8d12016-07-08 12:30:39 +0200376 self.logger.error("osconnector.get_network(): Error getting subnet %s %s" % (net_id, str(e)))
377 subnet = {"id": subnet_id, "fault": str(e)}
tierno7edb6752016-03-21 17:37:52 +0100378 subnets.append(subnet)
379 net["subnets"] = subnets
tiernoae4a8d12016-07-08 12:30:39 +0200380 return net
tierno7edb6752016-03-21 17:37:52 +0100381
tiernoae4a8d12016-07-08 12:30:39 +0200382 def delete_network(self, net_id):
383 '''Deletes a tenant network from VIM. Returns the old network identifier'''
384 self.logger.debug("Deleting network '%s' from VIM", net_id)
tierno7edb6752016-03-21 17:37:52 +0100385 try:
386 self._reload_connection()
387 #delete VM ports attached to this networks before the network
388 ports = self.neutron.list_ports(network_id=net_id)
389 for p in ports['ports']:
390 try:
391 self.neutron.delete_port(p["id"])
392 except Exception as e:
tiernoae4a8d12016-07-08 12:30:39 +0200393 self.logger.error("Error deleting port %s: %s", p["id"], str(e))
tierno7edb6752016-03-21 17:37:52 +0100394 self.neutron.delete_network(net_id)
tiernoae4a8d12016-07-08 12:30:39 +0200395 return net_id
396 except (neExceptions.ConnectionFailed, neExceptions.NetworkNotFoundClient, neExceptions.NeutronException,
tierno8e995ce2016-09-22 08:13:00 +0000397 ksExceptions.ClientException, neExceptions.NeutronException, ConnectionError) as e:
tiernoae4a8d12016-07-08 12:30:39 +0200398 self._format_exception(e)
tierno7edb6752016-03-21 17:37:52 +0100399
tiernoae4a8d12016-07-08 12:30:39 +0200400 def refresh_nets_status(self, net_list):
401 '''Get the status of the networks
402 Params: the list of network identifiers
403 Returns a dictionary with:
404 net_id: #VIM id of this network
405 status: #Mandatory. Text with one of:
406 # DELETED (not found at vim)
407 # VIM_ERROR (Cannot connect to VIM, VIM response error, ...)
408 # OTHER (Vim reported other status not understood)
409 # ERROR (VIM indicates an ERROR status)
410 # ACTIVE, INACTIVE, DOWN (admin down),
411 # BUILD (on building process)
412 #
413 error_msg: #Text with VIM error message, if any. Or the VIM connection ERROR
414 vim_info: #Text with plain information obtained from vim (yaml.safe_dump)
415
416 '''
417 net_dict={}
418 for net_id in net_list:
419 net = {}
420 try:
421 net_vim = self.get_network(net_id)
422 if net_vim['status'] in netStatus2manoFormat:
423 net["status"] = netStatus2manoFormat[ net_vim['status'] ]
424 else:
425 net["status"] = "OTHER"
426 net["error_msg"] = "VIM status reported " + net_vim['status']
427
tierno8e995ce2016-09-22 08:13:00 +0000428 if net['status'] == "ACTIVE" and not net_vim['admin_state_up']:
tiernoae4a8d12016-07-08 12:30:39 +0200429 net['status'] = 'DOWN'
tierno8e995ce2016-09-22 08:13:00 +0000430 try:
431 net['vim_info'] = yaml.safe_dump(net_vim, default_flow_style=True, width=256)
432 except yaml.representer.RepresenterError:
433 net['vim_info'] = str(net_vim)
tiernoae4a8d12016-07-08 12:30:39 +0200434 if net_vim.get('fault'): #TODO
435 net['error_msg'] = str(net_vim['fault'])
436 except vimconn.vimconnNotFoundException as e:
437 self.logger.error("Exception getting net status: %s", str(e))
438 net['status'] = "DELETED"
439 net['error_msg'] = str(e)
440 except vimconn.vimconnException as e:
441 self.logger.error("Exception getting net status: %s", str(e))
442 net['status'] = "VIM_ERROR"
443 net['error_msg'] = str(e)
444 net_dict[net_id] = net
445 return net_dict
446
447 def get_flavor(self, flavor_id):
448 '''Obtain flavor details from the VIM. Returns the flavor dict details'''
449 self.logger.debug("Getting flavor '%s'", flavor_id)
tierno7edb6752016-03-21 17:37:52 +0100450 try:
451 self._reload_connection()
452 flavor = self.nova.flavors.find(id=flavor_id)
453 #TODO parse input and translate to VIM format (openmano_schemas.new_vminstance_response_schema)
tiernoae4a8d12016-07-08 12:30:39 +0200454 return flavor.to_dict()
tierno8e995ce2016-09-22 08:13:00 +0000455 except (nvExceptions.NotFound, nvExceptions.ClientException, ksExceptions.ClientException, ConnectionError) as e:
tiernoae4a8d12016-07-08 12:30:39 +0200456 self._format_exception(e)
tierno7edb6752016-03-21 17:37:52 +0100457
tiernoae4a8d12016-07-08 12:30:39 +0200458 def new_flavor(self, flavor_data, change_name_if_used=True):
tierno7edb6752016-03-21 17:37:52 +0100459 '''Adds a tenant flavor to openstack VIM
tiernoae4a8d12016-07-08 12:30:39 +0200460 if change_name_if_used is True, it will change name in case of conflict, because it is not supported name repetition
tierno7edb6752016-03-21 17:37:52 +0100461 Returns the flavor identifier
462 '''
tiernoae4a8d12016-07-08 12:30:39 +0200463 self.logger.debug("Adding flavor '%s'", str(flavor_data))
tierno7edb6752016-03-21 17:37:52 +0100464 retry=0
tiernoae4a8d12016-07-08 12:30:39 +0200465 max_retries=3
tierno7edb6752016-03-21 17:37:52 +0100466 name_suffix = 0
tiernoae4a8d12016-07-08 12:30:39 +0200467 name=flavor_data['name']
468 while retry<max_retries:
tierno7edb6752016-03-21 17:37:52 +0100469 retry+=1
470 try:
471 self._reload_connection()
472 if change_name_if_used:
473 #get used names
474 fl_names=[]
475 fl=self.nova.flavors.list()
476 for f in fl:
477 fl_names.append(f.name)
478 while name in fl_names:
479 name_suffix += 1
tiernoae4a8d12016-07-08 12:30:39 +0200480 name = flavor_data['name']+"-" + str(name_suffix)
tierno7edb6752016-03-21 17:37:52 +0100481
tiernoae4a8d12016-07-08 12:30:39 +0200482 ram = flavor_data.get('ram',64)
483 vcpus = flavor_data.get('vcpus',1)
tierno7edb6752016-03-21 17:37:52 +0100484 numa_properties=None
485
tiernoae4a8d12016-07-08 12:30:39 +0200486 extended = flavor_data.get("extended")
tierno7edb6752016-03-21 17:37:52 +0100487 if extended:
488 numas=extended.get("numas")
489 if numas:
490 numa_nodes = len(numas)
491 if numa_nodes > 1:
492 return -1, "Can not add flavor with more than one numa"
493 numa_properties = {"hw:numa_nodes":str(numa_nodes)}
494 numa_properties["hw:mem_page_size"] = "large"
495 numa_properties["hw:cpu_policy"] = "dedicated"
496 numa_properties["hw:numa_mempolicy"] = "strict"
497 for numa in numas:
498 #overwrite ram and vcpus
499 ram = numa['memory']*1024
500 if 'paired-threads' in numa:
501 vcpus = numa['paired-threads']*2
502 numa_properties["hw:cpu_threads_policy"] = "prefer"
503 elif 'cores' in numa:
504 vcpus = numa['cores']
505 #numa_properties["hw:cpu_threads_policy"] = "prefer"
506 elif 'threads' in numa:
507 vcpus = numa['threads']
508 numa_properties["hw:cpu_policy"] = "isolated"
509 for interface in numa.get("interfaces",() ):
510 if interface["dedicated"]=="yes":
tierno809a7802016-07-08 13:31:24 +0200511 raise vimconn.vimconnException("Passthrough interfaces are not supported for the openstack connector", http_code=vimconn.HTTP_Service_Unavailable)
tierno7edb6752016-03-21 17:37:52 +0100512 #TODO, add the key 'pci_passthrough:alias"="<label at config>:<number ifaces>"' when a way to connect it is available
513
514 #create flavor
515 new_flavor=self.nova.flavors.create(name,
516 ram,
517 vcpus,
tiernoae4a8d12016-07-08 12:30:39 +0200518 flavor_data.get('disk',1),
519 is_public=flavor_data.get('is_public', True)
tierno7edb6752016-03-21 17:37:52 +0100520 )
521 #add metadata
522 if numa_properties:
523 new_flavor.set_keys(numa_properties)
tiernoae4a8d12016-07-08 12:30:39 +0200524 return new_flavor.id
tierno7edb6752016-03-21 17:37:52 +0100525 except nvExceptions.Conflict as e:
tiernoae4a8d12016-07-08 12:30:39 +0200526 if change_name_if_used and retry < max_retries:
tierno7edb6752016-03-21 17:37:52 +0100527 continue
tiernoae4a8d12016-07-08 12:30:39 +0200528 self._format_exception(e)
tierno7edb6752016-03-21 17:37:52 +0100529 #except nvExceptions.BadRequest as e:
530 except (ksExceptions.ClientException, nvExceptions.ClientException, ConnectionError) as e:
tiernoae4a8d12016-07-08 12:30:39 +0200531 self._format_exception(e)
tierno7edb6752016-03-21 17:37:52 +0100532
tiernoae4a8d12016-07-08 12:30:39 +0200533 def delete_flavor(self,flavor_id):
534 '''Deletes a tenant flavor from openstack VIM. Returns the old flavor_id
tierno7edb6752016-03-21 17:37:52 +0100535 '''
tiernoae4a8d12016-07-08 12:30:39 +0200536 try:
537 self._reload_connection()
538 self.nova.flavors.delete(flavor_id)
539 return flavor_id
540 #except nvExceptions.BadRequest as e:
tierno8e995ce2016-09-22 08:13:00 +0000541 except (nvExceptions.NotFound, ksExceptions.ClientException, nvExceptions.ClientException, ConnectionError) as e:
tiernoae4a8d12016-07-08 12:30:39 +0200542 self._format_exception(e)
tierno7edb6752016-03-21 17:37:52 +0100543
tiernoae4a8d12016-07-08 12:30:39 +0200544 def new_image(self,image_dict):
tierno7edb6752016-03-21 17:37:52 +0100545 '''
tiernoae4a8d12016-07-08 12:30:39 +0200546 Adds a tenant image to VIM. imge_dict is a dictionary with:
547 name: name
548 disk_format: qcow2, vhd, vmdk, raw (by default), ...
549 location: path or URI
550 public: "yes" or "no"
551 metadata: metadata of the image
552 Returns the image_id
tierno7edb6752016-03-21 17:37:52 +0100553 '''
tierno7edb6752016-03-21 17:37:52 +0100554 #using version 1 of glance client
555 glancev1 = gl1Client.Client('1',self.glance_endpoint, token=self.keystone.auth_token, **self.k_creds) #TODO check k_creds vs n_creds
tiernoae4a8d12016-07-08 12:30:39 +0200556 retry=0
557 max_retries=3
558 while retry<max_retries:
tierno7edb6752016-03-21 17:37:52 +0100559 retry+=1
560 try:
561 self._reload_connection()
562 #determine format http://docs.openstack.org/developer/glance/formats.html
563 if "disk_format" in image_dict:
564 disk_format=image_dict["disk_format"]
565 else: #autodiscover base on extention
566 if image_dict['location'][-6:]==".qcow2":
567 disk_format="qcow2"
568 elif image_dict['location'][-4:]==".vhd":
569 disk_format="vhd"
570 elif image_dict['location'][-5:]==".vmdk":
571 disk_format="vmdk"
572 elif image_dict['location'][-4:]==".vdi":
573 disk_format="vdi"
574 elif image_dict['location'][-4:]==".iso":
575 disk_format="iso"
576 elif image_dict['location'][-4:]==".aki":
577 disk_format="aki"
578 elif image_dict['location'][-4:]==".ari":
579 disk_format="ari"
580 elif image_dict['location'][-4:]==".ami":
581 disk_format="ami"
582 else:
583 disk_format="raw"
tiernoae4a8d12016-07-08 12:30:39 +0200584 self.logger.debug("new_image: '%s' loading from '%s'", image_dict['name'], image_dict['location'])
tierno7edb6752016-03-21 17:37:52 +0100585 if image_dict['location'][0:4]=="http":
586 new_image = glancev1.images.create(name=image_dict['name'], is_public=image_dict.get('public',"yes")=="yes",
587 container_format="bare", location=image_dict['location'], disk_format=disk_format)
588 else: #local path
589 with open(image_dict['location']) as fimage:
590 new_image = glancev1.images.create(name=image_dict['name'], is_public=image_dict.get('public',"yes")=="yes",
591 container_format="bare", data=fimage, disk_format=disk_format)
592 #insert metadata. We cannot use 'new_image.properties.setdefault'
593 #because nova and glance are "INDEPENDENT" and we are using nova for reading metadata
594 new_image_nova=self.nova.images.find(id=new_image.id)
595 new_image_nova.metadata.setdefault('location',image_dict['location'])
596 metadata_to_load = image_dict.get('metadata')
597 if metadata_to_load:
598 for k,v in yaml.load(metadata_to_load).iteritems():
599 new_image_nova.metadata.setdefault(k,v)
tiernoae4a8d12016-07-08 12:30:39 +0200600 return new_image.id
601 except (nvExceptions.Conflict, ksExceptions.ClientException, nvExceptions.ClientException) as e:
602 self._format_exception(e)
tierno8e995ce2016-09-22 08:13:00 +0000603 except (HTTPException, gl1Exceptions.HTTPException, gl1Exceptions.CommunicationError, ConnectionError) as e:
tiernoae4a8d12016-07-08 12:30:39 +0200604 if retry==max_retries:
605 continue
606 self._format_exception(e)
tierno7edb6752016-03-21 17:37:52 +0100607 except IOError as e: #can not open the file
tiernoae4a8d12016-07-08 12:30:39 +0200608 raise vimconn.vimconnConnectionException(type(e).__name__ + ": " + str(e)+ " for " + image_dict['location'],
609 http_code=vimconn.HTTP_Bad_Request)
tierno7edb6752016-03-21 17:37:52 +0100610
tiernoae4a8d12016-07-08 12:30:39 +0200611 def delete_image(self, image_id):
612 '''Deletes a tenant image from openstack VIM. Returns the old id
tierno7edb6752016-03-21 17:37:52 +0100613 '''
tiernoae4a8d12016-07-08 12:30:39 +0200614 try:
615 self._reload_connection()
616 self.nova.images.delete(image_id)
617 return image_id
tierno8e995ce2016-09-22 08:13:00 +0000618 except (nvExceptions.NotFound, ksExceptions.ClientException, nvExceptions.ClientException, gl1Exceptions.CommunicationError, ConnectionError) as e: #TODO remove
tiernoae4a8d12016-07-08 12:30:39 +0200619 self._format_exception(e)
620
621 def get_image_id_from_path(self, path):
garciadeblasb69fa9f2016-09-28 12:04:10 +0200622 '''Get the image id from image path in the VIM database. Returns the image_id'''
tiernoae4a8d12016-07-08 12:30:39 +0200623 try:
624 self._reload_connection()
625 images = self.nova.images.list()
626 for image in images:
627 if image.metadata.get("location")==path:
628 return image.id
629 raise vimconn.vimconnNotFoundException("image with location '{}' not found".format( path))
tierno8e995ce2016-09-22 08:13:00 +0000630 except (ksExceptions.ClientException, nvExceptions.ClientException, gl1Exceptions.CommunicationError, ConnectionError) as e:
tiernoae4a8d12016-07-08 12:30:39 +0200631 self._format_exception(e)
tierno7edb6752016-03-21 17:37:52 +0100632
garciadeblasb69fa9f2016-09-28 12:04:10 +0200633 def get_image_list(self, filter_dict={}):
634 '''Obtain tenant images from VIM
635 Filter_dict can be:
636 id: image id
637 name: image name
638 checksum: image checksum
639 Returns the image list of dictionaries:
640 [{<the fields at Filter_dict plus some VIM specific>}, ...]
641 List can be empty
642 '''
643 self.logger.debug("Getting image list from VIM filter: '%s'", str(filter_dict))
644 try:
645 self._reload_connection()
646 filter_dict_os=filter_dict.copy()
647 #First we filter by the available filter fields: name, id. The others are removed.
648 filter_dict_os.pop('checksum',None)
649 image_list=self.nova.images.findall(**filter_dict_os)
650 if len(image_list)==0:
651 return []
652 #Then we filter by the rest of filter fields: checksum
653 filtered_list = []
654 for image in image_list:
garciadeblasbb6a1ed2016-09-30 14:02:09 +0000655 image_dict=self.glance.images.get(image.id)
garciadeblasb69fa9f2016-09-28 12:04:10 +0200656 if image_dict['checksum']==filter_dict.get('checksum'):
657 filtered_list.append(image)
658 return filtered_list
659 except (ksExceptions.ClientException, nvExceptions.ClientException, gl1Exceptions.CommunicationError, ConnectionError) as e:
660 self._format_exception(e)
661
montesmoreno0c8def02016-12-22 12:16:23 +0000662 def new_vminstance(self,name,description,start,image_id,flavor_id,net_list,cloud_config=None,disk_list=None):
tierno7edb6752016-03-21 17:37:52 +0100663 '''Adds a VM instance to VIM
664 Params:
665 start: indicates if VM must start or boot in pause mode. Ignored
666 image_id,flavor_id: iamge and flavor uuid
667 net_list: list of interfaces, each one is a dictionary with:
668 name:
669 net_id: network uuid to connect
670 vpci: virtual vcpi to assign, ignored because openstack lack #TODO
671 model: interface model, ignored #TODO
672 mac_address: used for SR-IOV ifaces #TODO for other types
673 use: 'data', 'bridge', 'mgmt'
674 type: 'virtual', 'PF', 'VF', 'VFnotShared'
675 vim_id: filled/added by this function
ahmadsaf853d452016-12-22 11:33:47 +0500676 floating_ip: True/False (or it can be None)
tierno7edb6752016-03-21 17:37:52 +0100677 #TODO ip, security groups
tiernoae4a8d12016-07-08 12:30:39 +0200678 Returns the instance identifier
tierno7edb6752016-03-21 17:37:52 +0100679 '''
tiernoae4a8d12016-07-08 12:30:39 +0200680 self.logger.debug("Creating VM image '%s' flavor '%s' nics='%s'",image_id, flavor_id,str(net_list))
tierno7edb6752016-03-21 17:37:52 +0100681 try:
tierno6e116232016-07-18 13:01:40 +0200682 metadata={}
tierno7edb6752016-03-21 17:37:52 +0100683 net_list_vim=[]
ahmadsaf853d452016-12-22 11:33:47 +0500684 external_network=[] #list of external networks to be connected to instance, later on used to create floating_ip
tierno7edb6752016-03-21 17:37:52 +0100685 self._reload_connection()
tiernoae4a8d12016-07-08 12:30:39 +0200686 metadata_vpci={} #For a specific neutron plugin
tierno7edb6752016-03-21 17:37:52 +0100687 for net in net_list:
688 if not net.get("net_id"): #skip non connected iface
689 continue
ahmadsaf853d452016-12-22 11:33:47 +0500690 if net["type"]=="virtual" or net["type"]=="VF":
tierno7edb6752016-03-21 17:37:52 +0100691 port_dict={
ahmadsaf853d452016-12-22 11:33:47 +0500692 "network_id": net["net_id"],
693 "name": net.get("name"),
694 "admin_state_up": True
695 }
696 if net["type"]=="virtual":
697 if "vpci" in net:
698 metadata_vpci[ net["net_id"] ] = [[ net["vpci"], "" ]]
699 else: # for VF
700 if "vpci" in net:
701 if "VF" not in metadata_vpci:
702 metadata_vpci["VF"]=[]
703 metadata_vpci["VF"].append([ net["vpci"], "" ])
704 port_dict["binding:vnic_type"]="direct"
tierno7edb6752016-03-21 17:37:52 +0100705 if not port_dict["name"]:
ahmadsaf853d452016-12-22 11:33:47 +0500706 port_dict["name"]=name
tierno7edb6752016-03-21 17:37:52 +0100707 if net.get("mac_address"):
708 port_dict["mac_address"]=net["mac_address"]
montesmoreno2a1fc4e2017-01-09 16:46:04 +0000709 if "port_security" in net:
710 port_dict["port_security_enabled"]=net["port_security"]
tierno7edb6752016-03-21 17:37:52 +0100711 new_port = self.neutron.create_port({"port": port_dict })
712 net["mac_adress"] = new_port["port"]["mac_address"]
713 net["vim_id"] = new_port["port"]["id"]
ahmadsaf853d452016-12-22 11:33:47 +0500714 net["ip"] = new_port["port"].get("fixed_ips", [{}])[0].get("ip_address")
tierno7edb6752016-03-21 17:37:52 +0100715 net_list_vim.append({"port-id": new_port["port"]["id"]})
ahmadsaf853d452016-12-22 11:33:47 +0500716 else: # for PF
717 self.logger.warn("new_vminstance: Warning, can not connect a passthrough interface ")
718 #TODO insert this when openstack consider passthrough ports as openstack neutron ports
719 if net.get('floating_ip', False):
720 external_network.append(net)
721
tierno7edb6752016-03-21 17:37:52 +0100722 if metadata_vpci:
723 metadata = {"pci_assignement": json.dumps(metadata_vpci)}
tiernoafbced42016-07-23 01:43:53 +0200724 if len(metadata["pci_assignement"]) >255:
tierno6e116232016-07-18 13:01:40 +0200725 #limit the metadata size
726 #metadata["pci_assignement"] = metadata["pci_assignement"][0:255]
727 self.logger.warn("Metadata deleted since it exceeds the expected length (255) ")
728 metadata = {}
tierno7edb6752016-03-21 17:37:52 +0100729
tiernoae4a8d12016-07-08 12:30:39 +0200730 self.logger.debug("name '%s' image_id '%s'flavor_id '%s' net_list_vim '%s' description '%s' metadata %s",
731 name, image_id, flavor_id, str(net_list_vim), description, str(metadata))
tierno7edb6752016-03-21 17:37:52 +0100732
733 security_groups = self.config.get('security_groups')
734 if type(security_groups) is str:
735 security_groups = ( security_groups, )
tiernoa4e1a6e2016-08-31 14:19:40 +0200736 if isinstance(cloud_config, dict):
737 userdata="#cloud-config\nusers:\n"
738 #default user
739 if "key-pairs" in cloud_config:
740 userdata += " - default:\n ssh-authorized-keys:\n"
741 for key in cloud_config["key-pairs"]:
742 userdata += " - '{key}'\n".format(key=key)
743 for user in cloud_config.get("users",[]):
744 userdata += " - name: {name}\n sudo: ALL=(ALL) NOPASSWD:ALL\n".format(name=user["name"])
745 if "user-info" in user:
746 userdata += " gecos: {}'\n".format(user["user-info"])
747 if user.get("key-pairs"):
748 userdata += " ssh-authorized-keys:\n"
749 for key in user["key-pairs"]:
750 userdata += " - '{key}'\n".format(key=key)
751 self.logger.debug("userdata: %s", userdata)
752 elif isinstance(cloud_config, str):
753 userdata = cloud_config
754 else:
montesmoreno0c8def02016-12-22 12:16:23 +0000755 userdata=None
756
757 #Create additional volumes in case these are present in disk_list
758 block_device_mapping = None
759 base_disk_index = ord('b')
760 if disk_list != None:
761 block_device_mapping = dict()
762 for disk in disk_list:
763 if 'image_id' in disk:
764 volume = self.cinder.volumes.create(size = disk['size'],name = name + '_vd' +
765 chr(base_disk_index), imageRef = disk['image_id'])
766 else:
767 volume = self.cinder.volumes.create(size=disk['size'], name=name + '_vd' +
768 chr(base_disk_index))
769 block_device_mapping['_vd' + chr(base_disk_index)] = volume.id
770 base_disk_index += 1
771
772 #wait until volumes are with status available
773 keep_waiting = True
774 elapsed_time = 0
775 while keep_waiting and elapsed_time < volume_timeout:
776 keep_waiting = False
777 for volume_id in block_device_mapping.itervalues():
778 if self.cinder.volumes.get(volume_id).status != 'available':
779 keep_waiting = True
780 if keep_waiting:
781 time.sleep(1)
782 elapsed_time += 1
783
784 #if we exceeded the timeout rollback
785 if elapsed_time >= volume_timeout:
786 #delete the volumes we just created
787 for volume_id in block_device_mapping.itervalues():
788 self.cinder.volumes.delete(volume_id)
789
790 #delete ports we just created
791 for net_item in net_list_vim:
792 if 'port-id' in net_item:
793 self.neutron.delete_port(net_item['port_id'])
794
795 raise vimconn.vimconnException('Timeout creating volumes for instance ' + name,
796 http_code=vimconn.HTTP_Request_Timeout)
797
tierno7edb6752016-03-21 17:37:52 +0100798 server = self.nova.servers.create(name, image_id, flavor_id, nics=net_list_vim, meta=metadata,
montesmoreno0c8def02016-12-22 12:16:23 +0000799 security_groups=security_groups,
800 availability_zone=self.config.get('availability_zone'),
801 key_name=self.config.get('keypair'),
802 userdata=userdata,
803 block_device_mapping = block_device_mapping
804 ) # , description=description)
tiernoae4a8d12016-07-08 12:30:39 +0200805 #print "DONE :-)", server
tierno7edb6752016-03-21 17:37:52 +0100806
ahmadsaf853d452016-12-22 11:33:47 +0500807 pool_id = None
808 floating_ips = self.neutron.list_floatingips().get("floatingips", ())
809 for floating_network in external_network:
montesmoreno2a1fc4e2017-01-09 16:46:04 +0000810 # wait until vm is active
811 elapsed_time = 0
812 while elapsed_time < server_timeout:
813 status = self.nova.servers.get(server.id).status
814 if status == 'ACTIVE':
815 break
816 time.sleep(1)
817 elapsed_time += 1
818
819 #if we exceeded the timeout rollback
820 if elapsed_time >= server_timeout:
821 self.delete_vminstance(server.id)
822 raise vimconn.vimconnException('Timeout creating instance ' + name,
823 http_code=vimconn.HTTP_Request_Timeout)
824
ahmadsaf853d452016-12-22 11:33:47 +0500825 assigned = False
826 while(assigned == False):
827 if floating_ips:
828 ip = floating_ips.pop(0)
montesmoreno2a1fc4e2017-01-09 16:46:04 +0000829 if not ip.get("port_id", False) and ip.get('tenant_id') == server.tenant_id:
ahmadsaf853d452016-12-22 11:33:47 +0500830 free_floating_ip = ip.get("floating_ip_address")
831 try:
832 fix_ip = floating_network.get('ip')
833 server.add_floating_ip(free_floating_ip, fix_ip)
834 assigned = True
835 except Exception as e:
836 self.delete_vminstance(server.id)
837 raise vimconn.vimconnException(type(e).__name__ + ": Cannot create floating_ip "+ str(e), http_code=vimconn.HTTP_Conflict)
838 else:
montesmoreno2a1fc4e2017-01-09 16:46:04 +0000839 #Find the external network
840 external_nets = list()
841 for net in self.neutron.list_networks()['networks']:
842 if net['router:external']:
843 external_nets.append(net)
844
845 if len(external_nets) == 0:
846 self.delete_vminstance(server.id)
847 raise vimconn.vimconnException("Cannot create floating_ip automatically since no external "
848 "network is present",
849 http_code=vimconn.HTTP_Conflict)
850 if len(external_nets) > 1:
851 self.delete_vminstance(server.id)
852 raise vimconn.vimconnException("Cannot create floating_ip automatically since multiple "
853 "external networks are present",
854 http_code=vimconn.HTTP_Conflict)
855
856 pool_id = external_nets[0].get('id')
857 param = {'floatingip': {'floating_network_id': pool_id, 'tenant_id': server.tenant_id}}
ahmadsaf853d452016-12-22 11:33:47 +0500858 try:
859 #self.logger.debug("Creating floating IP")
860 new_floating_ip = self.neutron.create_floatingip(param)
861 free_floating_ip = new_floating_ip['floatingip']['floating_ip_address']
862 fix_ip = floating_network.get('ip')
863 server.add_floating_ip(free_floating_ip, fix_ip)
864 assigned=True
865 except Exception as e:
866 self.delete_vminstance(server.id)
867 raise vimconn.vimconnException(type(e).__name__ + ": Cannot create floating_ip "+ str(e), http_code=vimconn.HTTP_Conflict)
tierno7edb6752016-03-21 17:37:52 +0100868
tiernoae4a8d12016-07-08 12:30:39 +0200869 return server.id
tierno7edb6752016-03-21 17:37:52 +0100870# except nvExceptions.NotFound as e:
871# error_value=-vimconn.HTTP_Not_Found
872# error_text= "vm instance %s not found" % vm_id
tierno8e995ce2016-09-22 08:13:00 +0000873 except (ksExceptions.ClientException, nvExceptions.ClientException, ConnectionError
874 ) as e:
montesmoreno2a1fc4e2017-01-09 16:46:04 +0000875 # delete the volumes we just created
876 if block_device_mapping != None:
877 for volume_id in block_device_mapping.itervalues():
878 self.cinder.volumes.delete(volume_id)
879
880 # delete ports we just created
881 for net_item in net_list_vim:
882 if 'port-id' in net_item:
883 self.neutron.delete_port(net_item['port_id'])
tiernoae4a8d12016-07-08 12:30:39 +0200884 self._format_exception(e)
885 except TypeError as e:
886 raise vimconn.vimconnException(type(e).__name__ + ": "+ str(e), http_code=vimconn.HTTP_Bad_Request)
tierno7edb6752016-03-21 17:37:52 +0100887
tiernoae4a8d12016-07-08 12:30:39 +0200888 def get_vminstance(self,vm_id):
tierno7edb6752016-03-21 17:37:52 +0100889 '''Returns the VM instance information from VIM'''
tiernoae4a8d12016-07-08 12:30:39 +0200890 #self.logger.debug("Getting VM from VIM")
tierno7edb6752016-03-21 17:37:52 +0100891 try:
892 self._reload_connection()
893 server = self.nova.servers.find(id=vm_id)
894 #TODO parse input and translate to VIM format (openmano_schemas.new_vminstance_response_schema)
tiernoae4a8d12016-07-08 12:30:39 +0200895 return server.to_dict()
tierno8e995ce2016-09-22 08:13:00 +0000896 except (ksExceptions.ClientException, nvExceptions.ClientException, nvExceptions.NotFound, ConnectionError) as e:
tiernoae4a8d12016-07-08 12:30:39 +0200897 self._format_exception(e)
898
899 def get_vminstance_console(self,vm_id, console_type="vnc"):
tierno7edb6752016-03-21 17:37:52 +0100900 '''
901 Get a console for the virtual machine
902 Params:
903 vm_id: uuid of the VM
904 console_type, can be:
905 "novnc" (by default), "xvpvnc" for VNC types,
906 "rdp-html5" for RDP types, "spice-html5" for SPICE types
tiernoae4a8d12016-07-08 12:30:39 +0200907 Returns dict with the console parameters:
908 protocol: ssh, ftp, http, https, ...
909 server: usually ip address
910 port: the http, ssh, ... port
911 suffix: extra text, e.g. the http path and query string
tierno7edb6752016-03-21 17:37:52 +0100912 '''
tiernoae4a8d12016-07-08 12:30:39 +0200913 self.logger.debug("Getting VM CONSOLE from VIM")
tierno7edb6752016-03-21 17:37:52 +0100914 try:
915 self._reload_connection()
916 server = self.nova.servers.find(id=vm_id)
917 if console_type == None or console_type == "novnc":
918 console_dict = server.get_vnc_console("novnc")
919 elif console_type == "xvpvnc":
920 console_dict = server.get_vnc_console(console_type)
921 elif console_type == "rdp-html5":
922 console_dict = server.get_rdp_console(console_type)
923 elif console_type == "spice-html5":
924 console_dict = server.get_spice_console(console_type)
925 else:
tiernoae4a8d12016-07-08 12:30:39 +0200926 raise vimconn.vimconnException("console type '{}' not allowed".format(console_type), http_code=vimconn.HTTP_Bad_Request)
tierno7edb6752016-03-21 17:37:52 +0100927
928 console_dict1 = console_dict.get("console")
929 if console_dict1:
930 console_url = console_dict1.get("url")
931 if console_url:
932 #parse console_url
933 protocol_index = console_url.find("//")
934 suffix_index = console_url[protocol_index+2:].find("/") + protocol_index+2
935 port_index = console_url[protocol_index+2:suffix_index].find(":") + protocol_index+2
936 if protocol_index < 0 or port_index<0 or suffix_index<0:
937 return -vimconn.HTTP_Internal_Server_Error, "Unexpected response from VIM"
938 console_dict={"protocol": console_url[0:protocol_index],
939 "server": console_url[protocol_index+2:port_index],
940 "port": console_url[port_index:suffix_index],
941 "suffix": console_url[suffix_index+1:]
942 }
943 protocol_index += 2
tiernoae4a8d12016-07-08 12:30:39 +0200944 return console_dict
945 raise vimconn.vimconnUnexpectedResponse("Unexpected response from VIM")
tierno7edb6752016-03-21 17:37:52 +0100946
tierno8e995ce2016-09-22 08:13:00 +0000947 except (nvExceptions.NotFound, ksExceptions.ClientException, nvExceptions.ClientException, nvExceptions.BadRequest, ConnectionError) as e:
tiernoae4a8d12016-07-08 12:30:39 +0200948 self._format_exception(e)
tierno7edb6752016-03-21 17:37:52 +0100949
tiernoae4a8d12016-07-08 12:30:39 +0200950 def delete_vminstance(self, vm_id):
951 '''Removes a VM instance from VIM. Returns the old identifier
tierno7edb6752016-03-21 17:37:52 +0100952 '''
tiernoae4a8d12016-07-08 12:30:39 +0200953 #print "osconnector: Getting VM from VIM"
tierno7edb6752016-03-21 17:37:52 +0100954 try:
955 self._reload_connection()
956 #delete VM ports attached to this networks before the virtual machine
957 ports = self.neutron.list_ports(device_id=vm_id)
958 for p in ports['ports']:
959 try:
960 self.neutron.delete_port(p["id"])
961 except Exception as e:
tiernoae4a8d12016-07-08 12:30:39 +0200962 self.logger.error("Error deleting port: " + type(e).__name__ + ": "+ str(e))
montesmoreno0c8def02016-12-22 12:16:23 +0000963
964 #commented because detaching the volumes makes the servers.delete not work properly ?!?
965 #dettach volumes attached
966 server = self.nova.servers.get(vm_id)
967 volumes_attached_dict = server._info['os-extended-volumes:volumes_attached']
968 #for volume in volumes_attached_dict:
969 # self.cinder.volumes.detach(volume['id'])
970
tierno7edb6752016-03-21 17:37:52 +0100971 self.nova.servers.delete(vm_id)
montesmoreno0c8def02016-12-22 12:16:23 +0000972
973 #delete volumes.
974 #Although having detached them should have them in active status
975 #we ensure in this loop
976 keep_waiting = True
977 elapsed_time = 0
978 while keep_waiting and elapsed_time < volume_timeout:
979 keep_waiting = False
980 for volume in volumes_attached_dict:
981 if self.cinder.volumes.get(volume['id']).status != 'available':
982 keep_waiting = True
983 else:
984 self.cinder.volumes.delete(volume['id'])
985 if keep_waiting:
986 time.sleep(1)
987 elapsed_time += 1
988
tiernoae4a8d12016-07-08 12:30:39 +0200989 return vm_id
tierno8e995ce2016-09-22 08:13:00 +0000990 except (nvExceptions.NotFound, ksExceptions.ClientException, nvExceptions.ClientException, ConnectionError) as e:
tiernoae4a8d12016-07-08 12:30:39 +0200991 self._format_exception(e)
tierno7edb6752016-03-21 17:37:52 +0100992 #TODO insert exception vimconn.HTTP_Unauthorized
993 #if reaching here is because an exception
tierno7edb6752016-03-21 17:37:52 +0100994
tiernoae4a8d12016-07-08 12:30:39 +0200995 def refresh_vms_status(self, vm_list):
996 '''Get the status of the virtual machines and their interfaces/ports
997 Params: the list of VM identifiers
998 Returns a dictionary with:
999 vm_id: #VIM id of this Virtual Machine
1000 status: #Mandatory. Text with one of:
1001 # DELETED (not found at vim)
1002 # VIM_ERROR (Cannot connect to VIM, VIM response error, ...)
1003 # OTHER (Vim reported other status not understood)
1004 # ERROR (VIM indicates an ERROR status)
1005 # ACTIVE, PAUSED, SUSPENDED, INACTIVE (not running),
1006 # CREATING (on building process), ERROR
1007 # ACTIVE:NoMgmtIP (Active but any of its interface has an IP address
1008 #
1009 error_msg: #Text with VIM error message, if any. Or the VIM connection ERROR
1010 vim_info: #Text with plain information obtained from vim (yaml.safe_dump)
1011 interfaces:
1012 - vim_info: #Text with plain information obtained from vim (yaml.safe_dump)
1013 mac_address: #Text format XX:XX:XX:XX:XX:XX
1014 vim_net_id: #network id where this interface is connected
1015 vim_interface_id: #interface/port VIM id
1016 ip_address: #null, or text with IPv4, IPv6 address
tierno7edb6752016-03-21 17:37:52 +01001017 '''
tiernoae4a8d12016-07-08 12:30:39 +02001018 vm_dict={}
1019 self.logger.debug("refresh_vms status: Getting tenant VM instance information from VIM")
1020 for vm_id in vm_list:
1021 vm={}
1022 try:
1023 vm_vim = self.get_vminstance(vm_id)
1024 if vm_vim['status'] in vmStatus2manoFormat:
1025 vm['status'] = vmStatus2manoFormat[ vm_vim['status'] ]
tierno7edb6752016-03-21 17:37:52 +01001026 else:
tiernoae4a8d12016-07-08 12:30:39 +02001027 vm['status'] = "OTHER"
1028 vm['error_msg'] = "VIM status reported " + vm_vim['status']
tierno8e995ce2016-09-22 08:13:00 +00001029 try:
1030 vm['vim_info'] = yaml.safe_dump(vm_vim, default_flow_style=True, width=256)
1031 except yaml.representer.RepresenterError:
1032 vm['vim_info'] = str(vm_vim)
tiernoae4a8d12016-07-08 12:30:39 +02001033 vm["interfaces"] = []
1034 if vm_vim.get('fault'):
1035 vm['error_msg'] = str(vm_vim['fault'])
1036 #get interfaces
tierno7edb6752016-03-21 17:37:52 +01001037 try:
tiernoae4a8d12016-07-08 12:30:39 +02001038 self._reload_connection()
1039 port_dict=self.neutron.list_ports(device_id=vm_id)
1040 for port in port_dict["ports"]:
1041 interface={}
tierno8e995ce2016-09-22 08:13:00 +00001042 try:
1043 interface['vim_info'] = yaml.safe_dump(port, default_flow_style=True, width=256)
1044 except yaml.representer.RepresenterError:
1045 interface['vim_info'] = str(port)
tiernoae4a8d12016-07-08 12:30:39 +02001046 interface["mac_address"] = port.get("mac_address")
1047 interface["vim_net_id"] = port["network_id"]
1048 interface["vim_interface_id"] = port["id"]
1049 ips=[]
1050 #look for floating ip address
1051 floating_ip_dict = self.neutron.list_floatingips(port_id=port["id"])
1052 if floating_ip_dict.get("floatingips"):
1053 ips.append(floating_ip_dict["floatingips"][0].get("floating_ip_address") )
tierno7edb6752016-03-21 17:37:52 +01001054
tiernoae4a8d12016-07-08 12:30:39 +02001055 for subnet in port["fixed_ips"]:
1056 ips.append(subnet["ip_address"])
1057 interface["ip_address"] = ";".join(ips)
1058 vm["interfaces"].append(interface)
1059 except Exception as e:
1060 self.logger.error("Error getting vm interface information " + type(e).__name__ + ": "+ str(e))
1061 except vimconn.vimconnNotFoundException as e:
1062 self.logger.error("Exception getting vm status: %s", str(e))
1063 vm['status'] = "DELETED"
1064 vm['error_msg'] = str(e)
1065 except vimconn.vimconnException as e:
1066 self.logger.error("Exception getting vm status: %s", str(e))
1067 vm['status'] = "VIM_ERROR"
1068 vm['error_msg'] = str(e)
1069 vm_dict[vm_id] = vm
1070 return vm_dict
tierno7edb6752016-03-21 17:37:52 +01001071
tiernoae4a8d12016-07-08 12:30:39 +02001072 def action_vminstance(self, vm_id, action_dict):
tierno7edb6752016-03-21 17:37:52 +01001073 '''Send and action over a VM instance from VIM
tiernoae4a8d12016-07-08 12:30:39 +02001074 Returns the vm_id if the action was successfully sent to the VIM'''
1075 self.logger.debug("Action over VM '%s': %s", vm_id, str(action_dict))
tierno7edb6752016-03-21 17:37:52 +01001076 try:
1077 self._reload_connection()
1078 server = self.nova.servers.find(id=vm_id)
1079 if "start" in action_dict:
1080 if action_dict["start"]=="rebuild":
1081 server.rebuild()
1082 else:
1083 if server.status=="PAUSED":
1084 server.unpause()
1085 elif server.status=="SUSPENDED":
1086 server.resume()
1087 elif server.status=="SHUTOFF":
1088 server.start()
1089 elif "pause" in action_dict:
1090 server.pause()
1091 elif "resume" in action_dict:
1092 server.resume()
1093 elif "shutoff" in action_dict or "shutdown" in action_dict:
1094 server.stop()
1095 elif "forceOff" in action_dict:
1096 server.stop() #TODO
1097 elif "terminate" in action_dict:
1098 server.delete()
1099 elif "createImage" in action_dict:
1100 server.create_image()
1101 #"path":path_schema,
1102 #"description":description_schema,
1103 #"name":name_schema,
1104 #"metadata":metadata_schema,
1105 #"imageRef": id_schema,
1106 #"disk": {"oneOf":[{"type": "null"}, {"type":"string"}] },
1107 elif "rebuild" in action_dict:
1108 server.rebuild(server.image['id'])
1109 elif "reboot" in action_dict:
1110 server.reboot() #reboot_type='SOFT'
1111 elif "console" in action_dict:
1112 console_type = action_dict["console"]
1113 if console_type == None or console_type == "novnc":
1114 console_dict = server.get_vnc_console("novnc")
1115 elif console_type == "xvpvnc":
1116 console_dict = server.get_vnc_console(console_type)
1117 elif console_type == "rdp-html5":
1118 console_dict = server.get_rdp_console(console_type)
1119 elif console_type == "spice-html5":
1120 console_dict = server.get_spice_console(console_type)
1121 else:
tiernoae4a8d12016-07-08 12:30:39 +02001122 raise vimconn.vimconnException("console type '{}' not allowed".format(console_type),
1123 http_code=vimconn.HTTP_Bad_Request)
tierno7edb6752016-03-21 17:37:52 +01001124 try:
1125 console_url = console_dict["console"]["url"]
1126 #parse console_url
1127 protocol_index = console_url.find("//")
1128 suffix_index = console_url[protocol_index+2:].find("/") + protocol_index+2
1129 port_index = console_url[protocol_index+2:suffix_index].find(":") + protocol_index+2
1130 if protocol_index < 0 or port_index<0 or suffix_index<0:
tiernoae4a8d12016-07-08 12:30:39 +02001131 raise vimconn.vimconnException("Unexpected response from VIM " + str(console_dict))
tierno7edb6752016-03-21 17:37:52 +01001132 console_dict2={"protocol": console_url[0:protocol_index],
1133 "server": console_url[protocol_index+2 : port_index],
1134 "port": int(console_url[port_index+1 : suffix_index]),
1135 "suffix": console_url[suffix_index+1:]
1136 }
tiernoae4a8d12016-07-08 12:30:39 +02001137 return console_dict2
1138 except Exception as e:
1139 raise vimconn.vimconnException("Unexpected response from VIM " + str(console_dict))
tierno7edb6752016-03-21 17:37:52 +01001140
tiernoae4a8d12016-07-08 12:30:39 +02001141 return vm_id
tierno8e995ce2016-09-22 08:13:00 +00001142 except (ksExceptions.ClientException, nvExceptions.ClientException, nvExceptions.NotFound, ConnectionError) as e:
tiernoae4a8d12016-07-08 12:30:39 +02001143 self._format_exception(e)
1144 #TODO insert exception vimconn.HTTP_Unauthorized
1145
1146#NOT USED FUNCTIONS
1147
1148 def new_external_port(self, port_data):
1149 #TODO openstack if needed
1150 '''Adds a external port to VIM'''
1151 '''Returns the port identifier'''
1152 return -vimconn.HTTP_Internal_Server_Error, "osconnector.new_external_port() not implemented"
1153
1154 def connect_port_network(self, port_id, network_id, admin=False):
1155 #TODO openstack if needed
1156 '''Connects a external port to a network'''
1157 '''Returns status code of the VIM response'''
1158 return -vimconn.HTTP_Internal_Server_Error, "osconnector.connect_port_network() not implemented"
1159
1160 def new_user(self, user_name, user_passwd, tenant_id=None):
1161 '''Adds a new user to openstack VIM'''
1162 '''Returns the user identifier'''
1163 self.logger.debug("osconnector: Adding a new user to VIM")
1164 try:
1165 self._reload_connection()
1166 user=self.keystone.users.create(user_name, user_passwd, tenant_id=tenant_id)
1167 #self.keystone.tenants.add_user(self.k_creds["username"], #role)
1168 return user.id
1169 except ksExceptions.ConnectionError as e:
1170 error_value=-vimconn.HTTP_Bad_Request
1171 error_text= type(e).__name__ + ": "+ (str(e) if len(e.args)==0 else str(e.args[0]))
1172 except ksExceptions.ClientException as e: #TODO remove
tierno7edb6752016-03-21 17:37:52 +01001173 error_value=-vimconn.HTTP_Bad_Request
1174 error_text= type(e).__name__ + ": "+ (str(e) if len(e.args)==0 else str(e.args[0]))
1175 #TODO insert exception vimconn.HTTP_Unauthorized
1176 #if reaching here is because an exception
1177 if self.debug:
tiernoae4a8d12016-07-08 12:30:39 +02001178 self.logger.debug("new_user " + error_text)
tierno7edb6752016-03-21 17:37:52 +01001179 return error_value, error_text
tiernoae4a8d12016-07-08 12:30:39 +02001180
1181 def delete_user(self, user_id):
1182 '''Delete a user from openstack VIM'''
1183 '''Returns the user identifier'''
1184 if self.debug:
1185 print "osconnector: Deleting a user from VIM"
1186 try:
1187 self._reload_connection()
1188 self.keystone.users.delete(user_id)
1189 return 1, user_id
1190 except ksExceptions.ConnectionError as e:
1191 error_value=-vimconn.HTTP_Bad_Request
1192 error_text= type(e).__name__ + ": "+ (str(e) if len(e.args)==0 else str(e.args[0]))
1193 except ksExceptions.NotFound as e:
1194 error_value=-vimconn.HTTP_Not_Found
1195 error_text= type(e).__name__ + ": "+ (str(e) if len(e.args)==0 else str(e.args[0]))
1196 except ksExceptions.ClientException as e: #TODO remove
1197 error_value=-vimconn.HTTP_Bad_Request
1198 error_text= type(e).__name__ + ": "+ (str(e) if len(e.args)==0 else str(e.args[0]))
1199 #TODO insert exception vimconn.HTTP_Unauthorized
1200 #if reaching here is because an exception
1201 if self.debug:
1202 print "delete_tenant " + error_text
1203 return error_value, error_text
1204
tierno7edb6752016-03-21 17:37:52 +01001205 def get_hosts_info(self):
1206 '''Get the information of deployed hosts
1207 Returns the hosts content'''
1208 if self.debug:
1209 print "osconnector: Getting Host info from VIM"
1210 try:
1211 h_list=[]
1212 self._reload_connection()
1213 hypervisors = self.nova.hypervisors.list()
1214 for hype in hypervisors:
1215 h_list.append( hype.to_dict() )
1216 return 1, {"hosts":h_list}
1217 except nvExceptions.NotFound as e:
1218 error_value=-vimconn.HTTP_Not_Found
1219 error_text= (str(e) if len(e.args)==0 else str(e.args[0]))
1220 except (ksExceptions.ClientException, nvExceptions.ClientException) as e:
1221 error_value=-vimconn.HTTP_Bad_Request
1222 error_text= type(e).__name__ + ": "+ (str(e) if len(e.args)==0 else str(e.args[0]))
1223 #TODO insert exception vimconn.HTTP_Unauthorized
1224 #if reaching here is because an exception
1225 if self.debug:
1226 print "get_hosts_info " + error_text
1227 return error_value, error_text
1228
1229 def get_hosts(self, vim_tenant):
1230 '''Get the hosts and deployed instances
1231 Returns the hosts content'''
1232 r, hype_dict = self.get_hosts_info()
1233 if r<0:
1234 return r, hype_dict
1235 hypervisors = hype_dict["hosts"]
1236 try:
1237 servers = self.nova.servers.list()
1238 for hype in hypervisors:
1239 for server in servers:
1240 if server.to_dict()['OS-EXT-SRV-ATTR:hypervisor_hostname']==hype['hypervisor_hostname']:
1241 if 'vm' in hype:
1242 hype['vm'].append(server.id)
1243 else:
1244 hype['vm'] = [server.id]
1245 return 1, hype_dict
1246 except nvExceptions.NotFound as e:
1247 error_value=-vimconn.HTTP_Not_Found
1248 error_text= (str(e) if len(e.args)==0 else str(e.args[0]))
1249 except (ksExceptions.ClientException, nvExceptions.ClientException) as e:
1250 error_value=-vimconn.HTTP_Bad_Request
1251 error_text= type(e).__name__ + ": "+ (str(e) if len(e.args)==0 else str(e.args[0]))
1252 #TODO insert exception vimconn.HTTP_Unauthorized
1253 #if reaching here is because an exception
1254 if self.debug:
1255 print "get_hosts " + error_text
1256 return error_value, error_text
1257
tierno7edb6752016-03-21 17:37:52 +01001258