blob: 83ad940d8a3f1eaef36e81d9d5f45be625c471c9 [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
montesmorenocf227142017-01-12 12:24:21 +000099 if config.get('region_name'):
100 self.k_creds['region_name'] = config.get('region_name')
101 self.n_creds['region_name'] = config.get('region_name')
montesmoreno0c8def02016-12-22 12:16:23 +0000102
tierno7edb6752016-03-21 17:37:52 +0100103 self.reload_client = True
tierno73ad9e42016-09-12 18:11:11 +0200104 self.logger = logging.getLogger('openmano.vim.openstack')
tiernofe789902016-09-29 14:20:44 +0000105 if log_level:
106 self.logger.setLevel( getattr(logging, log_level) )
tierno7edb6752016-03-21 17:37:52 +0100107
108 def __setitem__(self,index, value):
109 '''Set individuals parameters
110 Throw TypeError, KeyError
111 '''
tierno392f2852016-05-13 12:28:55 +0200112 if index=='tenant_id':
tierno7edb6752016-03-21 17:37:52 +0100113 self.reload_client=True
tierno392f2852016-05-13 12:28:55 +0200114 self.tenant_id = value
ahmadsa95baa272016-11-30 09:14:11 +0500115 if self.osc_api_version == 'v3.3':
116 if value:
117 self.k_creds['project_id'] = value
118 self.n_creds['project_id'] = value
119 else:
120 del self.k_creds['project_id']
121 del self.n_creds['project_id']
tierno392f2852016-05-13 12:28:55 +0200122 else:
ahmadsa95baa272016-11-30 09:14:11 +0500123 if value:
124 self.k_creds['tenant_id'] = value
125 self.n_creds['tenant_id'] = value
126 else:
127 del self.k_creds['tenant_id']
128 del self.n_creds['tenant_id']
tierno392f2852016-05-13 12:28:55 +0200129 elif index=='tenant_name':
130 self.reload_client=True
131 self.tenant_name = value
ahmadsa95baa272016-11-30 09:14:11 +0500132 if self.osc_api_version == 'v3.3':
133 if value:
134 self.k_creds['project_name'] = value
135 self.n_creds['project_name'] = value
136 else:
137 del self.k_creds['project_name']
138 del self.n_creds['project_name']
tierno7edb6752016-03-21 17:37:52 +0100139 else:
ahmadsa95baa272016-11-30 09:14:11 +0500140 if value:
141 self.k_creds['tenant_name'] = value
142 self.n_creds['project_id'] = value
143 else:
144 del self.k_creds['tenant_name']
145 del self.n_creds['project_id']
tierno7edb6752016-03-21 17:37:52 +0100146 elif index=='user':
147 self.reload_client=True
148 self.user = value
149 if value:
150 self.k_creds['username'] = value
151 self.n_creds['username'] = value
152 else:
153 del self.k_creds['username']
154 del self.n_creds['username']
155 elif index=='passwd':
156 self.reload_client=True
157 self.passwd = value
158 if value:
159 self.k_creds['password'] = value
160 self.n_creds['api_key'] = value
161 else:
162 del self.k_creds['password']
163 del self.n_creds['api_key']
164 elif index=='url':
165 self.reload_client=True
166 self.url = value
167 if value:
168 self.k_creds['auth_url'] = value
169 self.n_creds['auth_url'] = value
170 else:
171 raise TypeError, 'url param can not be NoneType'
172 else:
173 vimconn.vimconnector.__setitem__(self,index, value)
174
175 def _reload_connection(self):
176 '''Called before any operation, it check if credentials has changed
177 Throw keystoneclient.apiclient.exceptions.AuthorizationFailure
178 '''
179 #TODO control the timing and possible token timeout, but it seams that python client does this task for us :-)
180 if self.reload_client:
181 #test valid params
182 if len(self.n_creds) <4:
183 raise ksExceptions.ClientException("Not enough parameters to connect to openstack")
ahmadsa95baa272016-11-30 09:14:11 +0500184 if self.osc_api_version == 'v3.3':
185 self.nova = nClient(APIVersion(version_str='2'), **self.n_creds)
montesmoreno0c8def02016-12-22 12:16:23 +0000186 #TODO To be updated for v3
187 #self.cinder = cClient.Client(**self.n_creds)
ahmadsa95baa272016-11-30 09:14:11 +0500188 self.keystone = ksClient.Client(**self.k_creds)
189 self.ne_endpoint=self.keystone.service_catalog.url_for(service_type='network', endpoint_type='publicURL')
190 self.neutron = neClient.Client(APIVersion(version_str='2'), endpoint_url=self.ne_endpoint, token=self.keystone.auth_token, **self.k_creds)
191 else:
192 self.nova = nClient_v2.Client('2', **self.n_creds)
montesmoreno0c8def02016-12-22 12:16:23 +0000193 self.cinder = cClient_v2.Client(**self.n_creds)
ahmadsa95baa272016-11-30 09:14:11 +0500194 self.keystone = ksClient_v2.Client(**self.k_creds)
195 self.ne_endpoint=self.keystone.service_catalog.url_for(service_type='network', endpoint_type='publicURL')
196 self.neutron = neClient_v2.Client('2.0', endpoint_url=self.ne_endpoint, token=self.keystone.auth_token, **self.k_creds)
tierno7edb6752016-03-21 17:37:52 +0100197 self.glance_endpoint = self.keystone.service_catalog.url_for(service_type='image', endpoint_type='publicURL')
198 self.glance = glClient.Client(self.glance_endpoint, token=self.keystone.auth_token, **self.k_creds) #TODO check k_creds vs n_creds
tierno7edb6752016-03-21 17:37:52 +0100199 self.reload_client = False
ahmadsa95baa272016-11-30 09:14:11 +0500200
tierno7edb6752016-03-21 17:37:52 +0100201 def __net_os2mano(self, net_list_dict):
202 '''Transform the net openstack format to mano format
203 net_list_dict can be a list of dict or a single dict'''
204 if type(net_list_dict) is dict:
205 net_list_=(net_list_dict,)
206 elif type(net_list_dict) is list:
207 net_list_=net_list_dict
208 else:
209 raise TypeError("param net_list_dict must be a list or a dictionary")
210 for net in net_list_:
211 if net.get('provider:network_type') == "vlan":
212 net['type']='data'
213 else:
214 net['type']='bridge'
tiernoae4a8d12016-07-08 12:30:39 +0200215
216
217
218 def _format_exception(self, exception):
219 '''Transform a keystone, nova, neutron exception into a vimconn exception'''
220 if isinstance(exception, (HTTPException, gl1Exceptions.HTTPException, gl1Exceptions.CommunicationError,
tierno8e995ce2016-09-22 08:13:00 +0000221 ConnectionError, ksExceptions.ConnectionError, neExceptions.ConnectionFailed
222 )):
tiernoae4a8d12016-07-08 12:30:39 +0200223 raise vimconn.vimconnConnectionException(type(exception).__name__ + ": " + str(exception))
224 elif isinstance(exception, (nvExceptions.ClientException, ksExceptions.ClientException,
225 neExceptions.NeutronException, nvExceptions.BadRequest)):
226 raise vimconn.vimconnUnexpectedResponse(type(exception).__name__ + ": " + str(exception))
227 elif isinstance(exception, (neExceptions.NetworkNotFoundClient, nvExceptions.NotFound)):
228 raise vimconn.vimconnNotFoundException(type(exception).__name__ + ": " + str(exception))
229 elif isinstance(exception, nvExceptions.Conflict):
230 raise vimconn.vimconnConflictException(type(exception).__name__ + ": " + str(exception))
231 else: # ()
232 raise vimconn.vimconnConnectionException(type(exception).__name__ + ": " + str(exception))
233
234 def get_tenant_list(self, filter_dict={}):
235 '''Obtain tenants of VIM
236 filter_dict can contain the following keys:
237 name: filter by tenant name
238 id: filter by tenant uuid/id
239 <other VIM specific>
240 Returns the tenant list of dictionaries: [{'name':'<name>, 'id':'<id>, ...}, ...]
241 '''
ahmadsa95baa272016-11-30 09:14:11 +0500242 self.logger.debug("Getting tenants from VIM filter: '%s'", str(filter_dict))
tiernoae4a8d12016-07-08 12:30:39 +0200243 try:
244 self._reload_connection()
montesmoreno0c8def02016-12-22 12:16:23 +0000245 if self.osc_api_version == 'v3.3':
ahmadsa95baa272016-11-30 09:14:11 +0500246 project_class_list=self.keystone.projects.findall(**filter_dict)
247 else:
248 project_class_list=self.keystone.tenants.findall(**filter_dict)
249 project_list=[]
250 for project in project_class_list:
251 project_list.append(project.to_dict())
252 return project_list
tierno8e995ce2016-09-22 08:13:00 +0000253 except (ksExceptions.ConnectionError, ksExceptions.ClientException, ConnectionError) as e:
tiernoae4a8d12016-07-08 12:30:39 +0200254 self._format_exception(e)
255
256 def new_tenant(self, tenant_name, tenant_description):
257 '''Adds a new tenant to openstack VIM. Returns the tenant identifier'''
258 self.logger.debug("Adding a new tenant name: %s", tenant_name)
259 try:
260 self._reload_connection()
ahmadsa95baa272016-11-30 09:14:11 +0500261 if self.osc_api_version == 'v3.3':
262 project=self.keystone.projects.create(tenant_name, tenant_description)
263 else:
264 project=self.keystone.tenants.create(tenant_name, tenant_description)
265 return project.id
tierno8e995ce2016-09-22 08:13:00 +0000266 except (ksExceptions.ConnectionError, ksExceptions.ClientException, ConnectionError) as e:
tiernoae4a8d12016-07-08 12:30:39 +0200267 self._format_exception(e)
268
269 def delete_tenant(self, tenant_id):
270 '''Delete a tenant from openstack VIM. Returns the old tenant identifier'''
271 self.logger.debug("Deleting tenant %s from VIM", tenant_id)
272 try:
273 self._reload_connection()
montesmoreno0c8def02016-12-22 12:16:23 +0000274 if self.osc_api_version == 'v3.3':
ahmadsa95baa272016-11-30 09:14:11 +0500275 self.keystone.projects.delete(tenant_id)
276 else:
277 self.keystone.tenants.delete(tenant_id)
tiernoae4a8d12016-07-08 12:30:39 +0200278 return tenant_id
tierno8e995ce2016-09-22 08:13:00 +0000279 except (ksExceptions.ConnectionError, ksExceptions.ClientException, ConnectionError) as e:
tiernoae4a8d12016-07-08 12:30:39 +0200280 self._format_exception(e)
ahmadsa95baa272016-11-30 09:14:11 +0500281
garciadeblas9f8456e2016-09-05 05:02:59 +0200282 def new_network(self,net_name, net_type, ip_profile=None, shared=False, vlan=None):
tiernoae4a8d12016-07-08 12:30:39 +0200283 '''Adds a tenant network to VIM. Returns the network identifier'''
284 self.logger.debug("Adding a new network to VIM name '%s', type '%s'", net_name, net_type)
garciadeblasedca7b32016-09-29 14:01:52 +0000285 #self.logger.debug(">>>>>>>>>>>>>>>>>> IP profile %s", str(ip_profile))
tierno7edb6752016-03-21 17:37:52 +0100286 try:
garciadeblasedca7b32016-09-29 14:01:52 +0000287 new_net = None
tierno7edb6752016-03-21 17:37:52 +0100288 self._reload_connection()
289 network_dict = {'name': net_name, 'admin_state_up': True}
290 if net_type=="data" or net_type=="ptp":
291 if self.config.get('dataplane_physical_net') == None:
tiernoae4a8d12016-07-08 12:30:39 +0200292 raise vimconn.vimconnConflictException("You must provide a 'dataplane_physical_net' at config value before creating sriov network")
tierno7edb6752016-03-21 17:37:52 +0100293 network_dict["provider:physical_network"] = self.config['dataplane_physical_net'] #"physnet_sriov" #TODO physical
294 network_dict["provider:network_type"] = "vlan"
295 if vlan!=None:
296 network_dict["provider:network_type"] = vlan
tiernoae4a8d12016-07-08 12:30:39 +0200297 network_dict["shared"]=shared
tierno7edb6752016-03-21 17:37:52 +0100298 new_net=self.neutron.create_network({'network':network_dict})
299 #print new_net
garciadeblas9f8456e2016-09-05 05:02:59 +0200300 #create subnetwork, even if there is no profile
301 if not ip_profile:
302 ip_profile = {}
303 if 'subnet_address' not in ip_profile:
304 #Fake subnet is required
305 ip_profile['subnet_address'] = "192.168.111.0/24"
306 if 'ip_version' not in ip_profile:
307 ip_profile['ip_version'] = "IPv4"
tierno7edb6752016-03-21 17:37:52 +0100308 subnet={"name":net_name+"-subnet",
309 "network_id": new_net["network"]["id"],
garciadeblas9f8456e2016-09-05 05:02:59 +0200310 "ip_version": 4 if ip_profile['ip_version']=="IPv4" else 6,
311 "cidr": ip_profile['subnet_address']
tierno7edb6752016-03-21 17:37:52 +0100312 }
garciadeblas9f8456e2016-09-05 05:02:59 +0200313 if 'gateway_address' in ip_profile:
314 subnet['gateway_ip'] = ip_profile['gateway_address']
garciadeblasedca7b32016-09-29 14:01:52 +0000315 if ip_profile.get('dns_address'):
garciadeblas9f8456e2016-09-05 05:02:59 +0200316 #TODO: manage dns_address as a list of addresses separated by commas
317 subnet['dns_nameservers'] = []
318 subnet['dns_nameservers'].append(ip_profile['dns_address'])
319 if 'dhcp_enabled' in ip_profile:
320 subnet['enable_dhcp'] = False if ip_profile['dhcp_enabled']=="false" else True
321 if 'dhcp_start_address' in ip_profile:
322 subnet['allocation_pools']=[]
323 subnet['allocation_pools'].append(dict())
324 subnet['allocation_pools'][0]['start'] = ip_profile['dhcp_start_address']
325 if 'dhcp_count' in ip_profile:
326 #parts = ip_profile['dhcp_start_address'].split('.')
327 #ip_int = (int(parts[0]) << 24) + (int(parts[1]) << 16) + (int(parts[2]) << 8) + int(parts[3])
328 ip_int = int(netaddr.IPAddress(ip_profile['dhcp_start_address']))
garciadeblas21d795b2016-09-29 17:31:46 +0200329 ip_int += ip_profile['dhcp_count'] - 1
garciadeblas9f8456e2016-09-05 05:02:59 +0200330 ip_str = str(netaddr.IPAddress(ip_int))
331 subnet['allocation_pools'][0]['end'] = ip_str
garciadeblasedca7b32016-09-29 14:01:52 +0000332 #self.logger.debug(">>>>>>>>>>>>>>>>>> Subnet: %s", str(subnet))
tierno7edb6752016-03-21 17:37:52 +0100333 self.neutron.create_subnet({"subnet": subnet} )
tiernoae4a8d12016-07-08 12:30:39 +0200334 return new_net["network"]["id"]
tierno8e995ce2016-09-22 08:13:00 +0000335 except (neExceptions.ConnectionFailed, ksExceptions.ClientException, neExceptions.NeutronException, ConnectionError) as e:
garciadeblasedca7b32016-09-29 14:01:52 +0000336 if new_net:
337 self.neutron.delete_network(new_net['network']['id'])
tiernoae4a8d12016-07-08 12:30:39 +0200338 self._format_exception(e)
tierno7edb6752016-03-21 17:37:52 +0100339
340 def get_network_list(self, filter_dict={}):
341 '''Obtain tenant networks of VIM
342 Filter_dict can be:
343 name: network name
344 id: network uuid
345 shared: boolean
346 tenant_id: tenant
347 admin_state_up: boolean
348 status: 'ACTIVE'
349 Returns the network list of dictionaries
350 '''
tiernoae4a8d12016-07-08 12:30:39 +0200351 self.logger.debug("Getting network from VIM filter: '%s'", str(filter_dict))
tierno7edb6752016-03-21 17:37:52 +0100352 try:
353 self._reload_connection()
montesmoreno0c8def02016-12-22 12:16:23 +0000354 if self.osc_api_version == 'v3.3' and "tenant_id" in filter_dict:
ahmadsa95baa272016-11-30 09:14:11 +0500355 filter_dict['project_id'] = filter_dict.pop('tenant_id')
tierno7edb6752016-03-21 17:37:52 +0100356 net_dict=self.neutron.list_networks(**filter_dict)
357 net_list=net_dict["networks"]
358 self.__net_os2mano(net_list)
tiernoae4a8d12016-07-08 12:30:39 +0200359 return net_list
tierno8e995ce2016-09-22 08:13:00 +0000360 except (neExceptions.ConnectionFailed, ksExceptions.ClientException, neExceptions.NeutronException, ConnectionError) as e:
tiernoae4a8d12016-07-08 12:30:39 +0200361 self._format_exception(e)
tierno7edb6752016-03-21 17:37:52 +0100362
tiernoae4a8d12016-07-08 12:30:39 +0200363 def get_network(self, net_id):
364 '''Obtain details of network from VIM
365 Returns the network information from a network id'''
366 self.logger.debug(" Getting tenant network %s from VIM", net_id)
tierno7edb6752016-03-21 17:37:52 +0100367 filter_dict={"id": net_id}
tiernoae4a8d12016-07-08 12:30:39 +0200368 net_list = self.get_network_list(filter_dict)
tierno7edb6752016-03-21 17:37:52 +0100369 if len(net_list)==0:
tiernoae4a8d12016-07-08 12:30:39 +0200370 raise vimconn.vimconnNotFoundException("Network '{}' not found".format(net_id))
tierno7edb6752016-03-21 17:37:52 +0100371 elif len(net_list)>1:
tiernoae4a8d12016-07-08 12:30:39 +0200372 raise vimconn.vimconnConflictException("Found more than one network with this criteria")
tierno7edb6752016-03-21 17:37:52 +0100373 net = net_list[0]
374 subnets=[]
375 for subnet_id in net.get("subnets", () ):
376 try:
377 subnet = self.neutron.show_subnet(subnet_id)
378 except Exception as e:
tiernoae4a8d12016-07-08 12:30:39 +0200379 self.logger.error("osconnector.get_network(): Error getting subnet %s %s" % (net_id, str(e)))
380 subnet = {"id": subnet_id, "fault": str(e)}
tierno7edb6752016-03-21 17:37:52 +0100381 subnets.append(subnet)
382 net["subnets"] = subnets
tiernoae4a8d12016-07-08 12:30:39 +0200383 return net
tierno7edb6752016-03-21 17:37:52 +0100384
tiernoae4a8d12016-07-08 12:30:39 +0200385 def delete_network(self, net_id):
386 '''Deletes a tenant network from VIM. Returns the old network identifier'''
387 self.logger.debug("Deleting network '%s' from VIM", net_id)
tierno7edb6752016-03-21 17:37:52 +0100388 try:
389 self._reload_connection()
390 #delete VM ports attached to this networks before the network
391 ports = self.neutron.list_ports(network_id=net_id)
392 for p in ports['ports']:
393 try:
394 self.neutron.delete_port(p["id"])
395 except Exception as e:
tiernoae4a8d12016-07-08 12:30:39 +0200396 self.logger.error("Error deleting port %s: %s", p["id"], str(e))
tierno7edb6752016-03-21 17:37:52 +0100397 self.neutron.delete_network(net_id)
tiernoae4a8d12016-07-08 12:30:39 +0200398 return net_id
399 except (neExceptions.ConnectionFailed, neExceptions.NetworkNotFoundClient, neExceptions.NeutronException,
tierno8e995ce2016-09-22 08:13:00 +0000400 ksExceptions.ClientException, neExceptions.NeutronException, ConnectionError) as e:
tiernoae4a8d12016-07-08 12:30:39 +0200401 self._format_exception(e)
tierno7edb6752016-03-21 17:37:52 +0100402
tiernoae4a8d12016-07-08 12:30:39 +0200403 def refresh_nets_status(self, net_list):
404 '''Get the status of the networks
405 Params: the list of network identifiers
406 Returns a dictionary with:
407 net_id: #VIM id of this network
408 status: #Mandatory. Text with one of:
409 # DELETED (not found at vim)
410 # VIM_ERROR (Cannot connect to VIM, VIM response error, ...)
411 # OTHER (Vim reported other status not understood)
412 # ERROR (VIM indicates an ERROR status)
413 # ACTIVE, INACTIVE, DOWN (admin down),
414 # BUILD (on building process)
415 #
416 error_msg: #Text with VIM error message, if any. Or the VIM connection ERROR
417 vim_info: #Text with plain information obtained from vim (yaml.safe_dump)
418
419 '''
420 net_dict={}
421 for net_id in net_list:
422 net = {}
423 try:
424 net_vim = self.get_network(net_id)
425 if net_vim['status'] in netStatus2manoFormat:
426 net["status"] = netStatus2manoFormat[ net_vim['status'] ]
427 else:
428 net["status"] = "OTHER"
429 net["error_msg"] = "VIM status reported " + net_vim['status']
430
tierno8e995ce2016-09-22 08:13:00 +0000431 if net['status'] == "ACTIVE" and not net_vim['admin_state_up']:
tiernoae4a8d12016-07-08 12:30:39 +0200432 net['status'] = 'DOWN'
tierno8e995ce2016-09-22 08:13:00 +0000433 try:
434 net['vim_info'] = yaml.safe_dump(net_vim, default_flow_style=True, width=256)
435 except yaml.representer.RepresenterError:
436 net['vim_info'] = str(net_vim)
tiernoae4a8d12016-07-08 12:30:39 +0200437 if net_vim.get('fault'): #TODO
438 net['error_msg'] = str(net_vim['fault'])
439 except vimconn.vimconnNotFoundException as e:
440 self.logger.error("Exception getting net status: %s", str(e))
441 net['status'] = "DELETED"
442 net['error_msg'] = str(e)
443 except vimconn.vimconnException as e:
444 self.logger.error("Exception getting net status: %s", str(e))
445 net['status'] = "VIM_ERROR"
446 net['error_msg'] = str(e)
447 net_dict[net_id] = net
448 return net_dict
449
450 def get_flavor(self, flavor_id):
451 '''Obtain flavor details from the VIM. Returns the flavor dict details'''
452 self.logger.debug("Getting flavor '%s'", flavor_id)
tierno7edb6752016-03-21 17:37:52 +0100453 try:
454 self._reload_connection()
455 flavor = self.nova.flavors.find(id=flavor_id)
456 #TODO parse input and translate to VIM format (openmano_schemas.new_vminstance_response_schema)
tiernoae4a8d12016-07-08 12:30:39 +0200457 return flavor.to_dict()
tierno8e995ce2016-09-22 08:13:00 +0000458 except (nvExceptions.NotFound, nvExceptions.ClientException, ksExceptions.ClientException, ConnectionError) as e:
tiernoae4a8d12016-07-08 12:30:39 +0200459 self._format_exception(e)
tierno7edb6752016-03-21 17:37:52 +0100460
tiernoae4a8d12016-07-08 12:30:39 +0200461 def new_flavor(self, flavor_data, change_name_if_used=True):
tierno7edb6752016-03-21 17:37:52 +0100462 '''Adds a tenant flavor to openstack VIM
tiernoae4a8d12016-07-08 12:30:39 +0200463 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 +0100464 Returns the flavor identifier
465 '''
tiernoae4a8d12016-07-08 12:30:39 +0200466 self.logger.debug("Adding flavor '%s'", str(flavor_data))
tierno7edb6752016-03-21 17:37:52 +0100467 retry=0
tiernoae4a8d12016-07-08 12:30:39 +0200468 max_retries=3
tierno7edb6752016-03-21 17:37:52 +0100469 name_suffix = 0
tiernoae4a8d12016-07-08 12:30:39 +0200470 name=flavor_data['name']
471 while retry<max_retries:
tierno7edb6752016-03-21 17:37:52 +0100472 retry+=1
473 try:
474 self._reload_connection()
475 if change_name_if_used:
476 #get used names
477 fl_names=[]
478 fl=self.nova.flavors.list()
479 for f in fl:
480 fl_names.append(f.name)
481 while name in fl_names:
482 name_suffix += 1
tiernoae4a8d12016-07-08 12:30:39 +0200483 name = flavor_data['name']+"-" + str(name_suffix)
tierno7edb6752016-03-21 17:37:52 +0100484
tiernoae4a8d12016-07-08 12:30:39 +0200485 ram = flavor_data.get('ram',64)
486 vcpus = flavor_data.get('vcpus',1)
tierno7edb6752016-03-21 17:37:52 +0100487 numa_properties=None
488
tiernoae4a8d12016-07-08 12:30:39 +0200489 extended = flavor_data.get("extended")
tierno7edb6752016-03-21 17:37:52 +0100490 if extended:
491 numas=extended.get("numas")
492 if numas:
493 numa_nodes = len(numas)
494 if numa_nodes > 1:
495 return -1, "Can not add flavor with more than one numa"
496 numa_properties = {"hw:numa_nodes":str(numa_nodes)}
497 numa_properties["hw:mem_page_size"] = "large"
498 numa_properties["hw:cpu_policy"] = "dedicated"
499 numa_properties["hw:numa_mempolicy"] = "strict"
500 for numa in numas:
501 #overwrite ram and vcpus
502 ram = numa['memory']*1024
503 if 'paired-threads' in numa:
504 vcpus = numa['paired-threads']*2
505 numa_properties["hw:cpu_threads_policy"] = "prefer"
506 elif 'cores' in numa:
507 vcpus = numa['cores']
508 #numa_properties["hw:cpu_threads_policy"] = "prefer"
509 elif 'threads' in numa:
510 vcpus = numa['threads']
511 numa_properties["hw:cpu_policy"] = "isolated"
512 for interface in numa.get("interfaces",() ):
513 if interface["dedicated"]=="yes":
tierno809a7802016-07-08 13:31:24 +0200514 raise vimconn.vimconnException("Passthrough interfaces are not supported for the openstack connector", http_code=vimconn.HTTP_Service_Unavailable)
tierno7edb6752016-03-21 17:37:52 +0100515 #TODO, add the key 'pci_passthrough:alias"="<label at config>:<number ifaces>"' when a way to connect it is available
516
517 #create flavor
518 new_flavor=self.nova.flavors.create(name,
519 ram,
520 vcpus,
tiernoae4a8d12016-07-08 12:30:39 +0200521 flavor_data.get('disk',1),
522 is_public=flavor_data.get('is_public', True)
tierno7edb6752016-03-21 17:37:52 +0100523 )
524 #add metadata
525 if numa_properties:
526 new_flavor.set_keys(numa_properties)
tiernoae4a8d12016-07-08 12:30:39 +0200527 return new_flavor.id
tierno7edb6752016-03-21 17:37:52 +0100528 except nvExceptions.Conflict as e:
tiernoae4a8d12016-07-08 12:30:39 +0200529 if change_name_if_used and retry < max_retries:
tierno7edb6752016-03-21 17:37:52 +0100530 continue
tiernoae4a8d12016-07-08 12:30:39 +0200531 self._format_exception(e)
tierno7edb6752016-03-21 17:37:52 +0100532 #except nvExceptions.BadRequest as e:
533 except (ksExceptions.ClientException, nvExceptions.ClientException, ConnectionError) as e:
tiernoae4a8d12016-07-08 12:30:39 +0200534 self._format_exception(e)
tierno7edb6752016-03-21 17:37:52 +0100535
tiernoae4a8d12016-07-08 12:30:39 +0200536 def delete_flavor(self,flavor_id):
537 '''Deletes a tenant flavor from openstack VIM. Returns the old flavor_id
tierno7edb6752016-03-21 17:37:52 +0100538 '''
tiernoae4a8d12016-07-08 12:30:39 +0200539 try:
540 self._reload_connection()
541 self.nova.flavors.delete(flavor_id)
542 return flavor_id
543 #except nvExceptions.BadRequest as e:
tierno8e995ce2016-09-22 08:13:00 +0000544 except (nvExceptions.NotFound, ksExceptions.ClientException, nvExceptions.ClientException, ConnectionError) as e:
tiernoae4a8d12016-07-08 12:30:39 +0200545 self._format_exception(e)
tierno7edb6752016-03-21 17:37:52 +0100546
tiernoae4a8d12016-07-08 12:30:39 +0200547 def new_image(self,image_dict):
tierno7edb6752016-03-21 17:37:52 +0100548 '''
tiernoae4a8d12016-07-08 12:30:39 +0200549 Adds a tenant image to VIM. imge_dict is a dictionary with:
550 name: name
551 disk_format: qcow2, vhd, vmdk, raw (by default), ...
552 location: path or URI
553 public: "yes" or "no"
554 metadata: metadata of the image
555 Returns the image_id
tierno7edb6752016-03-21 17:37:52 +0100556 '''
tierno7edb6752016-03-21 17:37:52 +0100557 #using version 1 of glance client
558 glancev1 = gl1Client.Client('1',self.glance_endpoint, token=self.keystone.auth_token, **self.k_creds) #TODO check k_creds vs n_creds
tiernoae4a8d12016-07-08 12:30:39 +0200559 retry=0
560 max_retries=3
561 while retry<max_retries:
tierno7edb6752016-03-21 17:37:52 +0100562 retry+=1
563 try:
564 self._reload_connection()
565 #determine format http://docs.openstack.org/developer/glance/formats.html
566 if "disk_format" in image_dict:
567 disk_format=image_dict["disk_format"]
568 else: #autodiscover base on extention
569 if image_dict['location'][-6:]==".qcow2":
570 disk_format="qcow2"
571 elif image_dict['location'][-4:]==".vhd":
572 disk_format="vhd"
573 elif image_dict['location'][-5:]==".vmdk":
574 disk_format="vmdk"
575 elif image_dict['location'][-4:]==".vdi":
576 disk_format="vdi"
577 elif image_dict['location'][-4:]==".iso":
578 disk_format="iso"
579 elif image_dict['location'][-4:]==".aki":
580 disk_format="aki"
581 elif image_dict['location'][-4:]==".ari":
582 disk_format="ari"
583 elif image_dict['location'][-4:]==".ami":
584 disk_format="ami"
585 else:
586 disk_format="raw"
tiernoae4a8d12016-07-08 12:30:39 +0200587 self.logger.debug("new_image: '%s' loading from '%s'", image_dict['name'], image_dict['location'])
tierno7edb6752016-03-21 17:37:52 +0100588 if image_dict['location'][0:4]=="http":
589 new_image = glancev1.images.create(name=image_dict['name'], is_public=image_dict.get('public',"yes")=="yes",
590 container_format="bare", location=image_dict['location'], disk_format=disk_format)
591 else: #local path
592 with open(image_dict['location']) as fimage:
593 new_image = glancev1.images.create(name=image_dict['name'], is_public=image_dict.get('public',"yes")=="yes",
594 container_format="bare", data=fimage, disk_format=disk_format)
595 #insert metadata. We cannot use 'new_image.properties.setdefault'
596 #because nova and glance are "INDEPENDENT" and we are using nova for reading metadata
597 new_image_nova=self.nova.images.find(id=new_image.id)
598 new_image_nova.metadata.setdefault('location',image_dict['location'])
599 metadata_to_load = image_dict.get('metadata')
600 if metadata_to_load:
601 for k,v in yaml.load(metadata_to_load).iteritems():
602 new_image_nova.metadata.setdefault(k,v)
tiernoae4a8d12016-07-08 12:30:39 +0200603 return new_image.id
604 except (nvExceptions.Conflict, ksExceptions.ClientException, nvExceptions.ClientException) as e:
605 self._format_exception(e)
tierno8e995ce2016-09-22 08:13:00 +0000606 except (HTTPException, gl1Exceptions.HTTPException, gl1Exceptions.CommunicationError, ConnectionError) as e:
tiernoae4a8d12016-07-08 12:30:39 +0200607 if retry==max_retries:
608 continue
609 self._format_exception(e)
tierno7edb6752016-03-21 17:37:52 +0100610 except IOError as e: #can not open the file
tiernoae4a8d12016-07-08 12:30:39 +0200611 raise vimconn.vimconnConnectionException(type(e).__name__ + ": " + str(e)+ " for " + image_dict['location'],
612 http_code=vimconn.HTTP_Bad_Request)
tierno7edb6752016-03-21 17:37:52 +0100613
tiernoae4a8d12016-07-08 12:30:39 +0200614 def delete_image(self, image_id):
615 '''Deletes a tenant image from openstack VIM. Returns the old id
tierno7edb6752016-03-21 17:37:52 +0100616 '''
tiernoae4a8d12016-07-08 12:30:39 +0200617 try:
618 self._reload_connection()
619 self.nova.images.delete(image_id)
620 return image_id
tierno8e995ce2016-09-22 08:13:00 +0000621 except (nvExceptions.NotFound, ksExceptions.ClientException, nvExceptions.ClientException, gl1Exceptions.CommunicationError, ConnectionError) as e: #TODO remove
tiernoae4a8d12016-07-08 12:30:39 +0200622 self._format_exception(e)
623
624 def get_image_id_from_path(self, path):
garciadeblasb69fa9f2016-09-28 12:04:10 +0200625 '''Get the image id from image path in the VIM database. Returns the image_id'''
tiernoae4a8d12016-07-08 12:30:39 +0200626 try:
627 self._reload_connection()
628 images = self.nova.images.list()
629 for image in images:
630 if image.metadata.get("location")==path:
631 return image.id
632 raise vimconn.vimconnNotFoundException("image with location '{}' not found".format( path))
tierno8e995ce2016-09-22 08:13:00 +0000633 except (ksExceptions.ClientException, nvExceptions.ClientException, gl1Exceptions.CommunicationError, ConnectionError) as e:
tiernoae4a8d12016-07-08 12:30:39 +0200634 self._format_exception(e)
tierno7edb6752016-03-21 17:37:52 +0100635
garciadeblasb69fa9f2016-09-28 12:04:10 +0200636 def get_image_list(self, filter_dict={}):
637 '''Obtain tenant images from VIM
638 Filter_dict can be:
639 id: image id
640 name: image name
641 checksum: image checksum
642 Returns the image list of dictionaries:
643 [{<the fields at Filter_dict plus some VIM specific>}, ...]
644 List can be empty
645 '''
646 self.logger.debug("Getting image list from VIM filter: '%s'", str(filter_dict))
647 try:
648 self._reload_connection()
649 filter_dict_os=filter_dict.copy()
650 #First we filter by the available filter fields: name, id. The others are removed.
651 filter_dict_os.pop('checksum',None)
652 image_list=self.nova.images.findall(**filter_dict_os)
653 if len(image_list)==0:
654 return []
655 #Then we filter by the rest of filter fields: checksum
656 filtered_list = []
657 for image in image_list:
garciadeblasbb6a1ed2016-09-30 14:02:09 +0000658 image_dict=self.glance.images.get(image.id)
garciadeblasb69fa9f2016-09-28 12:04:10 +0200659 if image_dict['checksum']==filter_dict.get('checksum'):
660 filtered_list.append(image)
661 return filtered_list
662 except (ksExceptions.ClientException, nvExceptions.ClientException, gl1Exceptions.CommunicationError, ConnectionError) as e:
663 self._format_exception(e)
664
montesmoreno0c8def02016-12-22 12:16:23 +0000665 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 +0100666 '''Adds a VM instance to VIM
667 Params:
668 start: indicates if VM must start or boot in pause mode. Ignored
669 image_id,flavor_id: iamge and flavor uuid
670 net_list: list of interfaces, each one is a dictionary with:
671 name:
672 net_id: network uuid to connect
673 vpci: virtual vcpi to assign, ignored because openstack lack #TODO
674 model: interface model, ignored #TODO
675 mac_address: used for SR-IOV ifaces #TODO for other types
676 use: 'data', 'bridge', 'mgmt'
677 type: 'virtual', 'PF', 'VF', 'VFnotShared'
678 vim_id: filled/added by this function
ahmadsaf853d452016-12-22 11:33:47 +0500679 floating_ip: True/False (or it can be None)
tierno7edb6752016-03-21 17:37:52 +0100680 #TODO ip, security groups
tiernoae4a8d12016-07-08 12:30:39 +0200681 Returns the instance identifier
tierno7edb6752016-03-21 17:37:52 +0100682 '''
tiernoae4a8d12016-07-08 12:30:39 +0200683 self.logger.debug("Creating VM image '%s' flavor '%s' nics='%s'",image_id, flavor_id,str(net_list))
tierno7edb6752016-03-21 17:37:52 +0100684 try:
tierno6e116232016-07-18 13:01:40 +0200685 metadata={}
tierno7edb6752016-03-21 17:37:52 +0100686 net_list_vim=[]
ahmadsaf853d452016-12-22 11:33:47 +0500687 external_network=[] #list of external networks to be connected to instance, later on used to create floating_ip
tierno7edb6752016-03-21 17:37:52 +0100688 self._reload_connection()
tiernoae4a8d12016-07-08 12:30:39 +0200689 metadata_vpci={} #For a specific neutron plugin
tierno7edb6752016-03-21 17:37:52 +0100690 for net in net_list:
691 if not net.get("net_id"): #skip non connected iface
692 continue
ahmadsaf853d452016-12-22 11:33:47 +0500693 if net["type"]=="virtual" or net["type"]=="VF":
tierno7edb6752016-03-21 17:37:52 +0100694 port_dict={
ahmadsaf853d452016-12-22 11:33:47 +0500695 "network_id": net["net_id"],
696 "name": net.get("name"),
697 "admin_state_up": True
698 }
699 if net["type"]=="virtual":
700 if "vpci" in net:
701 metadata_vpci[ net["net_id"] ] = [[ net["vpci"], "" ]]
702 else: # for VF
703 if "vpci" in net:
704 if "VF" not in metadata_vpci:
705 metadata_vpci["VF"]=[]
706 metadata_vpci["VF"].append([ net["vpci"], "" ])
707 port_dict["binding:vnic_type"]="direct"
tierno7edb6752016-03-21 17:37:52 +0100708 if not port_dict["name"]:
ahmadsaf853d452016-12-22 11:33:47 +0500709 port_dict["name"]=name
tierno7edb6752016-03-21 17:37:52 +0100710 if net.get("mac_address"):
711 port_dict["mac_address"]=net["mac_address"]
montesmorenocf227142017-01-12 12:24:21 +0000712 if net.get("port_security") == False:
montesmoreno2a1fc4e2017-01-09 16:46:04 +0000713 port_dict["port_security_enabled"]=net["port_security"]
tierno7edb6752016-03-21 17:37:52 +0100714 new_port = self.neutron.create_port({"port": port_dict })
715 net["mac_adress"] = new_port["port"]["mac_address"]
716 net["vim_id"] = new_port["port"]["id"]
ahmadsaf853d452016-12-22 11:33:47 +0500717 net["ip"] = new_port["port"].get("fixed_ips", [{}])[0].get("ip_address")
tierno7edb6752016-03-21 17:37:52 +0100718 net_list_vim.append({"port-id": new_port["port"]["id"]})
ahmadsaf853d452016-12-22 11:33:47 +0500719 else: # for PF
720 self.logger.warn("new_vminstance: Warning, can not connect a passthrough interface ")
721 #TODO insert this when openstack consider passthrough ports as openstack neutron ports
722 if net.get('floating_ip', False):
723 external_network.append(net)
724
tierno7edb6752016-03-21 17:37:52 +0100725 if metadata_vpci:
726 metadata = {"pci_assignement": json.dumps(metadata_vpci)}
tiernoafbced42016-07-23 01:43:53 +0200727 if len(metadata["pci_assignement"]) >255:
tierno6e116232016-07-18 13:01:40 +0200728 #limit the metadata size
729 #metadata["pci_assignement"] = metadata["pci_assignement"][0:255]
730 self.logger.warn("Metadata deleted since it exceeds the expected length (255) ")
731 metadata = {}
tierno7edb6752016-03-21 17:37:52 +0100732
tiernoae4a8d12016-07-08 12:30:39 +0200733 self.logger.debug("name '%s' image_id '%s'flavor_id '%s' net_list_vim '%s' description '%s' metadata %s",
734 name, image_id, flavor_id, str(net_list_vim), description, str(metadata))
tierno7edb6752016-03-21 17:37:52 +0100735
736 security_groups = self.config.get('security_groups')
737 if type(security_groups) is str:
738 security_groups = ( security_groups, )
tiernoa4e1a6e2016-08-31 14:19:40 +0200739 if isinstance(cloud_config, dict):
740 userdata="#cloud-config\nusers:\n"
741 #default user
742 if "key-pairs" in cloud_config:
743 userdata += " - default:\n ssh-authorized-keys:\n"
744 for key in cloud_config["key-pairs"]:
745 userdata += " - '{key}'\n".format(key=key)
746 for user in cloud_config.get("users",[]):
747 userdata += " - name: {name}\n sudo: ALL=(ALL) NOPASSWD:ALL\n".format(name=user["name"])
748 if "user-info" in user:
749 userdata += " gecos: {}'\n".format(user["user-info"])
750 if user.get("key-pairs"):
751 userdata += " ssh-authorized-keys:\n"
752 for key in user["key-pairs"]:
753 userdata += " - '{key}'\n".format(key=key)
754 self.logger.debug("userdata: %s", userdata)
755 elif isinstance(cloud_config, str):
756 userdata = cloud_config
757 else:
montesmoreno0c8def02016-12-22 12:16:23 +0000758 userdata=None
759
760 #Create additional volumes in case these are present in disk_list
761 block_device_mapping = None
762 base_disk_index = ord('b')
763 if disk_list != None:
764 block_device_mapping = dict()
765 for disk in disk_list:
766 if 'image_id' in disk:
767 volume = self.cinder.volumes.create(size = disk['size'],name = name + '_vd' +
768 chr(base_disk_index), imageRef = disk['image_id'])
769 else:
770 volume = self.cinder.volumes.create(size=disk['size'], name=name + '_vd' +
771 chr(base_disk_index))
772 block_device_mapping['_vd' + chr(base_disk_index)] = volume.id
773 base_disk_index += 1
774
775 #wait until volumes are with status available
776 keep_waiting = True
777 elapsed_time = 0
778 while keep_waiting and elapsed_time < volume_timeout:
779 keep_waiting = False
780 for volume_id in block_device_mapping.itervalues():
781 if self.cinder.volumes.get(volume_id).status != 'available':
782 keep_waiting = True
783 if keep_waiting:
784 time.sleep(1)
785 elapsed_time += 1
786
787 #if we exceeded the timeout rollback
788 if elapsed_time >= volume_timeout:
789 #delete the volumes we just created
790 for volume_id in block_device_mapping.itervalues():
791 self.cinder.volumes.delete(volume_id)
792
793 #delete ports we just created
794 for net_item in net_list_vim:
795 if 'port-id' in net_item:
montesmorenocf227142017-01-12 12:24:21 +0000796 self.neutron.delete_port(net_item['port-id'])
montesmoreno0c8def02016-12-22 12:16:23 +0000797
798 raise vimconn.vimconnException('Timeout creating volumes for instance ' + name,
799 http_code=vimconn.HTTP_Request_Timeout)
800
tierno7edb6752016-03-21 17:37:52 +0100801 server = self.nova.servers.create(name, image_id, flavor_id, nics=net_list_vim, meta=metadata,
montesmoreno0c8def02016-12-22 12:16:23 +0000802 security_groups=security_groups,
803 availability_zone=self.config.get('availability_zone'),
804 key_name=self.config.get('keypair'),
805 userdata=userdata,
806 block_device_mapping = block_device_mapping
807 ) # , description=description)
tiernoae4a8d12016-07-08 12:30:39 +0200808 #print "DONE :-)", server
ahmadsaf853d452016-12-22 11:33:47 +0500809 pool_id = None
810 floating_ips = self.neutron.list_floatingips().get("floatingips", ())
811 for floating_network in external_network:
montesmoreno2a1fc4e2017-01-09 16:46:04 +0000812 # wait until vm is active
813 elapsed_time = 0
814 while elapsed_time < server_timeout:
815 status = self.nova.servers.get(server.id).status
816 if status == 'ACTIVE':
817 break
818 time.sleep(1)
819 elapsed_time += 1
820
821 #if we exceeded the timeout rollback
822 if elapsed_time >= server_timeout:
823 self.delete_vminstance(server.id)
824 raise vimconn.vimconnException('Timeout creating instance ' + name,
825 http_code=vimconn.HTTP_Request_Timeout)
826
ahmadsaf853d452016-12-22 11:33:47 +0500827 assigned = False
828 while(assigned == False):
829 if floating_ips:
830 ip = floating_ips.pop(0)
montesmoreno2a1fc4e2017-01-09 16:46:04 +0000831 if not ip.get("port_id", False) and ip.get('tenant_id') == server.tenant_id:
ahmadsaf853d452016-12-22 11:33:47 +0500832 free_floating_ip = ip.get("floating_ip_address")
833 try:
834 fix_ip = floating_network.get('ip')
835 server.add_floating_ip(free_floating_ip, fix_ip)
836 assigned = True
837 except Exception as e:
838 self.delete_vminstance(server.id)
839 raise vimconn.vimconnException(type(e).__name__ + ": Cannot create floating_ip "+ str(e), http_code=vimconn.HTTP_Conflict)
840 else:
montesmoreno2a1fc4e2017-01-09 16:46:04 +0000841 #Find the external network
842 external_nets = list()
843 for net in self.neutron.list_networks()['networks']:
844 if net['router:external']:
845 external_nets.append(net)
846
847 if len(external_nets) == 0:
848 self.delete_vminstance(server.id)
849 raise vimconn.vimconnException("Cannot create floating_ip automatically since no external "
850 "network is present",
851 http_code=vimconn.HTTP_Conflict)
852 if len(external_nets) > 1:
853 self.delete_vminstance(server.id)
854 raise vimconn.vimconnException("Cannot create floating_ip automatically since multiple "
855 "external networks are present",
856 http_code=vimconn.HTTP_Conflict)
857
858 pool_id = external_nets[0].get('id')
859 param = {'floatingip': {'floating_network_id': pool_id, 'tenant_id': server.tenant_id}}
ahmadsaf853d452016-12-22 11:33:47 +0500860 try:
861 #self.logger.debug("Creating floating IP")
862 new_floating_ip = self.neutron.create_floatingip(param)
863 free_floating_ip = new_floating_ip['floatingip']['floating_ip_address']
864 fix_ip = floating_network.get('ip')
865 server.add_floating_ip(free_floating_ip, fix_ip)
866 assigned=True
867 except Exception as e:
868 self.delete_vminstance(server.id)
869 raise vimconn.vimconnException(type(e).__name__ + ": Cannot create floating_ip "+ str(e), http_code=vimconn.HTTP_Conflict)
tierno7edb6752016-03-21 17:37:52 +0100870
tiernoae4a8d12016-07-08 12:30:39 +0200871 return server.id
tierno7edb6752016-03-21 17:37:52 +0100872# except nvExceptions.NotFound as e:
873# error_value=-vimconn.HTTP_Not_Found
874# error_text= "vm instance %s not found" % vm_id
tierno8e995ce2016-09-22 08:13:00 +0000875 except (ksExceptions.ClientException, nvExceptions.ClientException, ConnectionError
876 ) as e:
montesmoreno2a1fc4e2017-01-09 16:46:04 +0000877 # delete the volumes we just created
878 if block_device_mapping != None:
879 for volume_id in block_device_mapping.itervalues():
880 self.cinder.volumes.delete(volume_id)
881
882 # delete ports we just created
883 for net_item in net_list_vim:
884 if 'port-id' in net_item:
montesmorenocf227142017-01-12 12:24:21 +0000885 self.neutron.delete_port(net_item['port-id'])
tiernoae4a8d12016-07-08 12:30:39 +0200886 self._format_exception(e)
887 except TypeError as e:
888 raise vimconn.vimconnException(type(e).__name__ + ": "+ str(e), http_code=vimconn.HTTP_Bad_Request)
tierno7edb6752016-03-21 17:37:52 +0100889
tiernoae4a8d12016-07-08 12:30:39 +0200890 def get_vminstance(self,vm_id):
tierno7edb6752016-03-21 17:37:52 +0100891 '''Returns the VM instance information from VIM'''
tiernoae4a8d12016-07-08 12:30:39 +0200892 #self.logger.debug("Getting VM from VIM")
tierno7edb6752016-03-21 17:37:52 +0100893 try:
894 self._reload_connection()
895 server = self.nova.servers.find(id=vm_id)
896 #TODO parse input and translate to VIM format (openmano_schemas.new_vminstance_response_schema)
tiernoae4a8d12016-07-08 12:30:39 +0200897 return server.to_dict()
tierno8e995ce2016-09-22 08:13:00 +0000898 except (ksExceptions.ClientException, nvExceptions.ClientException, nvExceptions.NotFound, ConnectionError) as e:
tiernoae4a8d12016-07-08 12:30:39 +0200899 self._format_exception(e)
900
901 def get_vminstance_console(self,vm_id, console_type="vnc"):
tierno7edb6752016-03-21 17:37:52 +0100902 '''
903 Get a console for the virtual machine
904 Params:
905 vm_id: uuid of the VM
906 console_type, can be:
907 "novnc" (by default), "xvpvnc" for VNC types,
908 "rdp-html5" for RDP types, "spice-html5" for SPICE types
tiernoae4a8d12016-07-08 12:30:39 +0200909 Returns dict with the console parameters:
910 protocol: ssh, ftp, http, https, ...
911 server: usually ip address
912 port: the http, ssh, ... port
913 suffix: extra text, e.g. the http path and query string
tierno7edb6752016-03-21 17:37:52 +0100914 '''
tiernoae4a8d12016-07-08 12:30:39 +0200915 self.logger.debug("Getting VM CONSOLE from VIM")
tierno7edb6752016-03-21 17:37:52 +0100916 try:
917 self._reload_connection()
918 server = self.nova.servers.find(id=vm_id)
919 if console_type == None or console_type == "novnc":
920 console_dict = server.get_vnc_console("novnc")
921 elif console_type == "xvpvnc":
922 console_dict = server.get_vnc_console(console_type)
923 elif console_type == "rdp-html5":
924 console_dict = server.get_rdp_console(console_type)
925 elif console_type == "spice-html5":
926 console_dict = server.get_spice_console(console_type)
927 else:
tiernoae4a8d12016-07-08 12:30:39 +0200928 raise vimconn.vimconnException("console type '{}' not allowed".format(console_type), http_code=vimconn.HTTP_Bad_Request)
tierno7edb6752016-03-21 17:37:52 +0100929
930 console_dict1 = console_dict.get("console")
931 if console_dict1:
932 console_url = console_dict1.get("url")
933 if console_url:
934 #parse console_url
935 protocol_index = console_url.find("//")
936 suffix_index = console_url[protocol_index+2:].find("/") + protocol_index+2
937 port_index = console_url[protocol_index+2:suffix_index].find(":") + protocol_index+2
938 if protocol_index < 0 or port_index<0 or suffix_index<0:
939 return -vimconn.HTTP_Internal_Server_Error, "Unexpected response from VIM"
940 console_dict={"protocol": console_url[0:protocol_index],
941 "server": console_url[protocol_index+2:port_index],
942 "port": console_url[port_index:suffix_index],
943 "suffix": console_url[suffix_index+1:]
944 }
945 protocol_index += 2
tiernoae4a8d12016-07-08 12:30:39 +0200946 return console_dict
947 raise vimconn.vimconnUnexpectedResponse("Unexpected response from VIM")
tierno7edb6752016-03-21 17:37:52 +0100948
tierno8e995ce2016-09-22 08:13:00 +0000949 except (nvExceptions.NotFound, ksExceptions.ClientException, nvExceptions.ClientException, nvExceptions.BadRequest, ConnectionError) as e:
tiernoae4a8d12016-07-08 12:30:39 +0200950 self._format_exception(e)
tierno7edb6752016-03-21 17:37:52 +0100951
tiernoae4a8d12016-07-08 12:30:39 +0200952 def delete_vminstance(self, vm_id):
953 '''Removes a VM instance from VIM. Returns the old identifier
tierno7edb6752016-03-21 17:37:52 +0100954 '''
tiernoae4a8d12016-07-08 12:30:39 +0200955 #print "osconnector: Getting VM from VIM"
tierno7edb6752016-03-21 17:37:52 +0100956 try:
957 self._reload_connection()
958 #delete VM ports attached to this networks before the virtual machine
959 ports = self.neutron.list_ports(device_id=vm_id)
960 for p in ports['ports']:
961 try:
962 self.neutron.delete_port(p["id"])
963 except Exception as e:
tiernoae4a8d12016-07-08 12:30:39 +0200964 self.logger.error("Error deleting port: " + type(e).__name__ + ": "+ str(e))
montesmoreno0c8def02016-12-22 12:16:23 +0000965
966 #commented because detaching the volumes makes the servers.delete not work properly ?!?
967 #dettach volumes attached
968 server = self.nova.servers.get(vm_id)
969 volumes_attached_dict = server._info['os-extended-volumes:volumes_attached']
970 #for volume in volumes_attached_dict:
971 # self.cinder.volumes.detach(volume['id'])
972
tierno7edb6752016-03-21 17:37:52 +0100973 self.nova.servers.delete(vm_id)
montesmoreno0c8def02016-12-22 12:16:23 +0000974
975 #delete volumes.
976 #Although having detached them should have them in active status
977 #we ensure in this loop
978 keep_waiting = True
979 elapsed_time = 0
980 while keep_waiting and elapsed_time < volume_timeout:
981 keep_waiting = False
982 for volume in volumes_attached_dict:
983 if self.cinder.volumes.get(volume['id']).status != 'available':
984 keep_waiting = True
985 else:
986 self.cinder.volumes.delete(volume['id'])
987 if keep_waiting:
988 time.sleep(1)
989 elapsed_time += 1
990
tiernoae4a8d12016-07-08 12:30:39 +0200991 return vm_id
tierno8e995ce2016-09-22 08:13:00 +0000992 except (nvExceptions.NotFound, ksExceptions.ClientException, nvExceptions.ClientException, ConnectionError) as e:
tiernoae4a8d12016-07-08 12:30:39 +0200993 self._format_exception(e)
tierno7edb6752016-03-21 17:37:52 +0100994 #TODO insert exception vimconn.HTTP_Unauthorized
995 #if reaching here is because an exception
tierno7edb6752016-03-21 17:37:52 +0100996
tiernoae4a8d12016-07-08 12:30:39 +0200997 def refresh_vms_status(self, vm_list):
998 '''Get the status of the virtual machines and their interfaces/ports
999 Params: the list of VM identifiers
1000 Returns a dictionary with:
1001 vm_id: #VIM id of this Virtual Machine
1002 status: #Mandatory. Text with one of:
1003 # DELETED (not found at vim)
1004 # VIM_ERROR (Cannot connect to VIM, VIM response error, ...)
1005 # OTHER (Vim reported other status not understood)
1006 # ERROR (VIM indicates an ERROR status)
1007 # ACTIVE, PAUSED, SUSPENDED, INACTIVE (not running),
1008 # CREATING (on building process), ERROR
1009 # ACTIVE:NoMgmtIP (Active but any of its interface has an IP address
1010 #
1011 error_msg: #Text with VIM error message, if any. Or the VIM connection ERROR
1012 vim_info: #Text with plain information obtained from vim (yaml.safe_dump)
1013 interfaces:
1014 - vim_info: #Text with plain information obtained from vim (yaml.safe_dump)
1015 mac_address: #Text format XX:XX:XX:XX:XX:XX
1016 vim_net_id: #network id where this interface is connected
1017 vim_interface_id: #interface/port VIM id
1018 ip_address: #null, or text with IPv4, IPv6 address
tierno7edb6752016-03-21 17:37:52 +01001019 '''
tiernoae4a8d12016-07-08 12:30:39 +02001020 vm_dict={}
1021 self.logger.debug("refresh_vms status: Getting tenant VM instance information from VIM")
1022 for vm_id in vm_list:
1023 vm={}
1024 try:
1025 vm_vim = self.get_vminstance(vm_id)
1026 if vm_vim['status'] in vmStatus2manoFormat:
1027 vm['status'] = vmStatus2manoFormat[ vm_vim['status'] ]
tierno7edb6752016-03-21 17:37:52 +01001028 else:
tiernoae4a8d12016-07-08 12:30:39 +02001029 vm['status'] = "OTHER"
1030 vm['error_msg'] = "VIM status reported " + vm_vim['status']
tierno8e995ce2016-09-22 08:13:00 +00001031 try:
1032 vm['vim_info'] = yaml.safe_dump(vm_vim, default_flow_style=True, width=256)
1033 except yaml.representer.RepresenterError:
1034 vm['vim_info'] = str(vm_vim)
tiernoae4a8d12016-07-08 12:30:39 +02001035 vm["interfaces"] = []
1036 if vm_vim.get('fault'):
1037 vm['error_msg'] = str(vm_vim['fault'])
1038 #get interfaces
tierno7edb6752016-03-21 17:37:52 +01001039 try:
tiernoae4a8d12016-07-08 12:30:39 +02001040 self._reload_connection()
1041 port_dict=self.neutron.list_ports(device_id=vm_id)
1042 for port in port_dict["ports"]:
1043 interface={}
tierno8e995ce2016-09-22 08:13:00 +00001044 try:
1045 interface['vim_info'] = yaml.safe_dump(port, default_flow_style=True, width=256)
1046 except yaml.representer.RepresenterError:
1047 interface['vim_info'] = str(port)
tiernoae4a8d12016-07-08 12:30:39 +02001048 interface["mac_address"] = port.get("mac_address")
1049 interface["vim_net_id"] = port["network_id"]
1050 interface["vim_interface_id"] = port["id"]
1051 ips=[]
1052 #look for floating ip address
1053 floating_ip_dict = self.neutron.list_floatingips(port_id=port["id"])
1054 if floating_ip_dict.get("floatingips"):
1055 ips.append(floating_ip_dict["floatingips"][0].get("floating_ip_address") )
tierno7edb6752016-03-21 17:37:52 +01001056
tiernoae4a8d12016-07-08 12:30:39 +02001057 for subnet in port["fixed_ips"]:
1058 ips.append(subnet["ip_address"])
1059 interface["ip_address"] = ";".join(ips)
1060 vm["interfaces"].append(interface)
1061 except Exception as e:
1062 self.logger.error("Error getting vm interface information " + type(e).__name__ + ": "+ str(e))
1063 except vimconn.vimconnNotFoundException as e:
1064 self.logger.error("Exception getting vm status: %s", str(e))
1065 vm['status'] = "DELETED"
1066 vm['error_msg'] = str(e)
1067 except vimconn.vimconnException as e:
1068 self.logger.error("Exception getting vm status: %s", str(e))
1069 vm['status'] = "VIM_ERROR"
1070 vm['error_msg'] = str(e)
1071 vm_dict[vm_id] = vm
1072 return vm_dict
tierno7edb6752016-03-21 17:37:52 +01001073
tiernoae4a8d12016-07-08 12:30:39 +02001074 def action_vminstance(self, vm_id, action_dict):
tierno7edb6752016-03-21 17:37:52 +01001075 '''Send and action over a VM instance from VIM
tiernoae4a8d12016-07-08 12:30:39 +02001076 Returns the vm_id if the action was successfully sent to the VIM'''
1077 self.logger.debug("Action over VM '%s': %s", vm_id, str(action_dict))
tierno7edb6752016-03-21 17:37:52 +01001078 try:
1079 self._reload_connection()
1080 server = self.nova.servers.find(id=vm_id)
1081 if "start" in action_dict:
1082 if action_dict["start"]=="rebuild":
1083 server.rebuild()
1084 else:
1085 if server.status=="PAUSED":
1086 server.unpause()
1087 elif server.status=="SUSPENDED":
1088 server.resume()
1089 elif server.status=="SHUTOFF":
1090 server.start()
1091 elif "pause" in action_dict:
1092 server.pause()
1093 elif "resume" in action_dict:
1094 server.resume()
1095 elif "shutoff" in action_dict or "shutdown" in action_dict:
1096 server.stop()
1097 elif "forceOff" in action_dict:
1098 server.stop() #TODO
1099 elif "terminate" in action_dict:
1100 server.delete()
1101 elif "createImage" in action_dict:
1102 server.create_image()
1103 #"path":path_schema,
1104 #"description":description_schema,
1105 #"name":name_schema,
1106 #"metadata":metadata_schema,
1107 #"imageRef": id_schema,
1108 #"disk": {"oneOf":[{"type": "null"}, {"type":"string"}] },
1109 elif "rebuild" in action_dict:
1110 server.rebuild(server.image['id'])
1111 elif "reboot" in action_dict:
1112 server.reboot() #reboot_type='SOFT'
1113 elif "console" in action_dict:
1114 console_type = action_dict["console"]
1115 if console_type == None or console_type == "novnc":
1116 console_dict = server.get_vnc_console("novnc")
1117 elif console_type == "xvpvnc":
1118 console_dict = server.get_vnc_console(console_type)
1119 elif console_type == "rdp-html5":
1120 console_dict = server.get_rdp_console(console_type)
1121 elif console_type == "spice-html5":
1122 console_dict = server.get_spice_console(console_type)
1123 else:
tiernoae4a8d12016-07-08 12:30:39 +02001124 raise vimconn.vimconnException("console type '{}' not allowed".format(console_type),
1125 http_code=vimconn.HTTP_Bad_Request)
tierno7edb6752016-03-21 17:37:52 +01001126 try:
1127 console_url = console_dict["console"]["url"]
1128 #parse console_url
1129 protocol_index = console_url.find("//")
1130 suffix_index = console_url[protocol_index+2:].find("/") + protocol_index+2
1131 port_index = console_url[protocol_index+2:suffix_index].find(":") + protocol_index+2
1132 if protocol_index < 0 or port_index<0 or suffix_index<0:
tiernoae4a8d12016-07-08 12:30:39 +02001133 raise vimconn.vimconnException("Unexpected response from VIM " + str(console_dict))
tierno7edb6752016-03-21 17:37:52 +01001134 console_dict2={"protocol": console_url[0:protocol_index],
1135 "server": console_url[protocol_index+2 : port_index],
1136 "port": int(console_url[port_index+1 : suffix_index]),
1137 "suffix": console_url[suffix_index+1:]
1138 }
tiernoae4a8d12016-07-08 12:30:39 +02001139 return console_dict2
1140 except Exception as e:
1141 raise vimconn.vimconnException("Unexpected response from VIM " + str(console_dict))
tierno7edb6752016-03-21 17:37:52 +01001142
tiernoae4a8d12016-07-08 12:30:39 +02001143 return vm_id
tierno8e995ce2016-09-22 08:13:00 +00001144 except (ksExceptions.ClientException, nvExceptions.ClientException, nvExceptions.NotFound, ConnectionError) as e:
tiernoae4a8d12016-07-08 12:30:39 +02001145 self._format_exception(e)
1146 #TODO insert exception vimconn.HTTP_Unauthorized
1147
1148#NOT USED FUNCTIONS
1149
1150 def new_external_port(self, port_data):
1151 #TODO openstack if needed
1152 '''Adds a external port to VIM'''
1153 '''Returns the port identifier'''
1154 return -vimconn.HTTP_Internal_Server_Error, "osconnector.new_external_port() not implemented"
1155
1156 def connect_port_network(self, port_id, network_id, admin=False):
1157 #TODO openstack if needed
1158 '''Connects a external port to a network'''
1159 '''Returns status code of the VIM response'''
1160 return -vimconn.HTTP_Internal_Server_Error, "osconnector.connect_port_network() not implemented"
1161
1162 def new_user(self, user_name, user_passwd, tenant_id=None):
1163 '''Adds a new user to openstack VIM'''
1164 '''Returns the user identifier'''
1165 self.logger.debug("osconnector: Adding a new user to VIM")
1166 try:
1167 self._reload_connection()
1168 user=self.keystone.users.create(user_name, user_passwd, tenant_id=tenant_id)
1169 #self.keystone.tenants.add_user(self.k_creds["username"], #role)
1170 return user.id
1171 except ksExceptions.ConnectionError as e:
1172 error_value=-vimconn.HTTP_Bad_Request
1173 error_text= type(e).__name__ + ": "+ (str(e) if len(e.args)==0 else str(e.args[0]))
1174 except ksExceptions.ClientException as e: #TODO remove
tierno7edb6752016-03-21 17:37:52 +01001175 error_value=-vimconn.HTTP_Bad_Request
1176 error_text= type(e).__name__ + ": "+ (str(e) if len(e.args)==0 else str(e.args[0]))
1177 #TODO insert exception vimconn.HTTP_Unauthorized
1178 #if reaching here is because an exception
1179 if self.debug:
tiernoae4a8d12016-07-08 12:30:39 +02001180 self.logger.debug("new_user " + error_text)
tierno7edb6752016-03-21 17:37:52 +01001181 return error_value, error_text
tiernoae4a8d12016-07-08 12:30:39 +02001182
1183 def delete_user(self, user_id):
1184 '''Delete a user from openstack VIM'''
1185 '''Returns the user identifier'''
1186 if self.debug:
1187 print "osconnector: Deleting a user from VIM"
1188 try:
1189 self._reload_connection()
1190 self.keystone.users.delete(user_id)
1191 return 1, user_id
1192 except ksExceptions.ConnectionError as e:
1193 error_value=-vimconn.HTTP_Bad_Request
1194 error_text= type(e).__name__ + ": "+ (str(e) if len(e.args)==0 else str(e.args[0]))
1195 except ksExceptions.NotFound as e:
1196 error_value=-vimconn.HTTP_Not_Found
1197 error_text= type(e).__name__ + ": "+ (str(e) if len(e.args)==0 else str(e.args[0]))
1198 except ksExceptions.ClientException as e: #TODO remove
1199 error_value=-vimconn.HTTP_Bad_Request
1200 error_text= type(e).__name__ + ": "+ (str(e) if len(e.args)==0 else str(e.args[0]))
1201 #TODO insert exception vimconn.HTTP_Unauthorized
1202 #if reaching here is because an exception
1203 if self.debug:
1204 print "delete_tenant " + error_text
1205 return error_value, error_text
1206
tierno7edb6752016-03-21 17:37:52 +01001207 def get_hosts_info(self):
1208 '''Get the information of deployed hosts
1209 Returns the hosts content'''
1210 if self.debug:
1211 print "osconnector: Getting Host info from VIM"
1212 try:
1213 h_list=[]
1214 self._reload_connection()
1215 hypervisors = self.nova.hypervisors.list()
1216 for hype in hypervisors:
1217 h_list.append( hype.to_dict() )
1218 return 1, {"hosts":h_list}
1219 except nvExceptions.NotFound as e:
1220 error_value=-vimconn.HTTP_Not_Found
1221 error_text= (str(e) if len(e.args)==0 else str(e.args[0]))
1222 except (ksExceptions.ClientException, nvExceptions.ClientException) as e:
1223 error_value=-vimconn.HTTP_Bad_Request
1224 error_text= type(e).__name__ + ": "+ (str(e) if len(e.args)==0 else str(e.args[0]))
1225 #TODO insert exception vimconn.HTTP_Unauthorized
1226 #if reaching here is because an exception
1227 if self.debug:
1228 print "get_hosts_info " + error_text
1229 return error_value, error_text
1230
1231 def get_hosts(self, vim_tenant):
1232 '''Get the hosts and deployed instances
1233 Returns the hosts content'''
1234 r, hype_dict = self.get_hosts_info()
1235 if r<0:
1236 return r, hype_dict
1237 hypervisors = hype_dict["hosts"]
1238 try:
1239 servers = self.nova.servers.list()
1240 for hype in hypervisors:
1241 for server in servers:
1242 if server.to_dict()['OS-EXT-SRV-ATTR:hypervisor_hostname']==hype['hypervisor_hostname']:
1243 if 'vm' in hype:
1244 hype['vm'].append(server.id)
1245 else:
1246 hype['vm'] = [server.id]
1247 return 1, hype_dict
1248 except nvExceptions.NotFound as e:
1249 error_value=-vimconn.HTTP_Not_Found
1250 error_text= (str(e) if len(e.args)==0 else str(e.args[0]))
1251 except (ksExceptions.ClientException, nvExceptions.ClientException) as e:
1252 error_value=-vimconn.HTTP_Bad_Request
1253 error_text= type(e).__name__ + ": "+ (str(e) if len(e.args)==0 else str(e.args[0]))
1254 #TODO insert exception vimconn.HTTP_Unauthorized
1255 #if reaching here is because an exception
1256 if self.debug:
1257 print "get_hosts " + error_text
1258 return error_value, error_text
1259
tierno7edb6752016-03-21 17:37:52 +01001260