blob: 195d7bbc14ee2998e8aed00fd799f2aa9de1f371 [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
tierno36c0b172017-01-12 18:32:28 +010036import yaml
garciadeblas2299e3b2017-01-26 14:35:55 +000037import random
tierno7edb6752016-03-21 17:37:52 +010038
ahmadsa96af9f42017-01-31 16:17:14 +050039from novaclient import client as nClient_v2, exceptions as nvExceptions
40from novaclient import api_versions
ahmadsa95baa272016-11-30 09:14:11 +050041import keystoneclient.v2_0.client as ksClient_v2
42from novaclient.v2.client import Client as nClient
43import keystoneclient.v3.client as ksClient
tierno7edb6752016-03-21 17:37:52 +010044import keystoneclient.exceptions as ksExceptions
45import glanceclient.v2.client as glClient
46import glanceclient.client as gl1Client
47import glanceclient.exc as gl1Exceptions
montesmoreno0c8def02016-12-22 12:16:23 +000048import cinderclient.v2.client as cClient_v2
tierno7edb6752016-03-21 17:37:52 +010049from httplib import HTTPException
ahmadsa95baa272016-11-30 09:14:11 +050050from neutronclient.neutron import client as neClient_v2
51from neutronclient.v2_0 import client as neClient
tierno7edb6752016-03-21 17:37:52 +010052from neutronclient.common import exceptions as neExceptions
53from requests.exceptions import ConnectionError
54
55'''contain the openstack virtual machine status to openmano status'''
56vmStatus2manoFormat={'ACTIVE':'ACTIVE',
57 'PAUSED':'PAUSED',
58 'SUSPENDED': 'SUSPENDED',
59 'SHUTOFF':'INACTIVE',
60 'BUILD':'BUILD',
61 'ERROR':'ERROR','DELETED':'DELETED'
62 }
63netStatus2manoFormat={'ACTIVE':'ACTIVE','PAUSED':'PAUSED','INACTIVE':'INACTIVE','BUILD':'BUILD','ERROR':'ERROR','DELETED':'DELETED'
64 }
65
montesmoreno0c8def02016-12-22 12:16:23 +000066#global var to have a timeout creating and deleting volumes
67volume_timeout = 60
montesmoreno2a1fc4e2017-01-09 16:46:04 +000068server_timeout = 60
montesmoreno0c8def02016-12-22 12:16:23 +000069
tierno7edb6752016-03-21 17:37:52 +010070class vimconnector(vimconn.vimconnector):
tiernob3d36742017-03-03 23:51:05 +010071 def __init__(self, uuid, name, tenant_id, tenant_name, url, url_admin=None, user=None, passwd=None,
72 log_level=None, config={}, persistent_info={}):
ahmadsa96af9f42017-01-31 16:17:14 +050073 '''using common constructor parameters. In this case
tierno7edb6752016-03-21 17:37:52 +010074 'url' is the keystone authorization url,
75 'url_admin' is not use
76 '''
ahmadsa95baa272016-11-30 09:14:11 +050077 self.osc_api_version = 'v2.0'
78 if config.get('APIversion') == 'v3.3':
79 self.osc_api_version = 'v3.3'
tiernoae4a8d12016-07-08 12:30:39 +020080 vimconn.vimconnector.__init__(self, uuid, name, tenant_id, tenant_name, url, url_admin, user, passwd, log_level, config)
tiernob3d36742017-03-03 23:51:05 +010081
82 self.persistent_info = persistent_info
tierno7edb6752016-03-21 17:37:52 +010083 self.k_creds={}
84 self.n_creds={}
tiernoc75a5dc2017-01-18 15:53:44 +010085 if self.config.get("insecure"):
86 self.k_creds["insecure"] = True
87 self.n_creds["insecure"] = True
tierno7edb6752016-03-21 17:37:52 +010088 if not url:
89 raise TypeError, 'url param can not be NoneType'
90 self.k_creds['auth_url'] = url
91 self.n_creds['auth_url'] = url
tierno392f2852016-05-13 12:28:55 +020092 if tenant_name:
93 self.k_creds['tenant_name'] = tenant_name
94 self.n_creds['project_id'] = tenant_name
95 if tenant_id:
96 self.k_creds['tenant_id'] = tenant_id
97 self.n_creds['tenant_id'] = tenant_id
tierno7edb6752016-03-21 17:37:52 +010098 if user:
99 self.k_creds['username'] = user
100 self.n_creds['username'] = user
101 if passwd:
102 self.k_creds['password'] = passwd
103 self.n_creds['api_key'] = passwd
ahmadsa95baa272016-11-30 09:14:11 +0500104 if self.osc_api_version == 'v3.3':
105 self.k_creds['project_name'] = tenant_name
106 self.k_creds['project_id'] = tenant_id
montesmorenocf227142017-01-12 12:24:21 +0000107 if config.get('region_name'):
108 self.k_creds['region_name'] = config.get('region_name')
109 self.n_creds['region_name'] = config.get('region_name')
montesmoreno0c8def02016-12-22 12:16:23 +0000110
tierno7edb6752016-03-21 17:37:52 +0100111 self.reload_client = True
tierno73ad9e42016-09-12 18:11:11 +0200112 self.logger = logging.getLogger('openmano.vim.openstack')
tiernofe789902016-09-29 14:20:44 +0000113 if log_level:
114 self.logger.setLevel( getattr(logging, log_level) )
tierno7edb6752016-03-21 17:37:52 +0100115
116 def __setitem__(self,index, value):
117 '''Set individuals parameters
118 Throw TypeError, KeyError
119 '''
tierno392f2852016-05-13 12:28:55 +0200120 if index=='tenant_id':
tierno7edb6752016-03-21 17:37:52 +0100121 self.reload_client=True
tierno392f2852016-05-13 12:28:55 +0200122 self.tenant_id = value
ahmadsa95baa272016-11-30 09:14:11 +0500123 if self.osc_api_version == 'v3.3':
124 if value:
125 self.k_creds['project_id'] = value
126 self.n_creds['project_id'] = value
127 else:
128 del self.k_creds['project_id']
129 del self.n_creds['project_id']
tierno392f2852016-05-13 12:28:55 +0200130 else:
ahmadsa95baa272016-11-30 09:14:11 +0500131 if value:
132 self.k_creds['tenant_id'] = value
133 self.n_creds['tenant_id'] = value
134 else:
135 del self.k_creds['tenant_id']
136 del self.n_creds['tenant_id']
tierno392f2852016-05-13 12:28:55 +0200137 elif index=='tenant_name':
138 self.reload_client=True
139 self.tenant_name = value
ahmadsa95baa272016-11-30 09:14:11 +0500140 if self.osc_api_version == 'v3.3':
141 if value:
142 self.k_creds['project_name'] = value
143 self.n_creds['project_name'] = value
144 else:
145 del self.k_creds['project_name']
146 del self.n_creds['project_name']
tierno7edb6752016-03-21 17:37:52 +0100147 else:
ahmadsa95baa272016-11-30 09:14:11 +0500148 if value:
149 self.k_creds['tenant_name'] = value
150 self.n_creds['project_id'] = value
151 else:
152 del self.k_creds['tenant_name']
153 del self.n_creds['project_id']
tierno7edb6752016-03-21 17:37:52 +0100154 elif index=='user':
155 self.reload_client=True
156 self.user = value
157 if value:
158 self.k_creds['username'] = value
159 self.n_creds['username'] = value
160 else:
161 del self.k_creds['username']
162 del self.n_creds['username']
163 elif index=='passwd':
164 self.reload_client=True
165 self.passwd = value
166 if value:
167 self.k_creds['password'] = value
168 self.n_creds['api_key'] = value
169 else:
170 del self.k_creds['password']
171 del self.n_creds['api_key']
172 elif index=='url':
173 self.reload_client=True
174 self.url = value
175 if value:
176 self.k_creds['auth_url'] = value
177 self.n_creds['auth_url'] = value
178 else:
179 raise TypeError, 'url param can not be NoneType'
180 else:
181 vimconn.vimconnector.__setitem__(self,index, value)
182
183 def _reload_connection(self):
184 '''Called before any operation, it check if credentials has changed
185 Throw keystoneclient.apiclient.exceptions.AuthorizationFailure
186 '''
187 #TODO control the timing and possible token timeout, but it seams that python client does this task for us :-)
188 if self.reload_client:
189 #test valid params
190 if len(self.n_creds) <4:
191 raise ksExceptions.ClientException("Not enough parameters to connect to openstack")
ahmadsa95baa272016-11-30 09:14:11 +0500192 if self.osc_api_version == 'v3.3':
ahmadsa96af9f42017-01-31 16:17:14 +0500193 self.nova = nClient(api_version=api_versions.APIVersion(version_str='2.0'), **self.n_creds)
montesmoreno0c8def02016-12-22 12:16:23 +0000194 #TODO To be updated for v3
195 #self.cinder = cClient.Client(**self.n_creds)
ahmadsa95baa272016-11-30 09:14:11 +0500196 self.keystone = ksClient.Client(**self.k_creds)
197 self.ne_endpoint=self.keystone.service_catalog.url_for(service_type='network', endpoint_type='publicURL')
ahmadsa96af9f42017-01-31 16:17:14 +0500198 self.neutron = neClient.Client(api_version=api_versions.APIVersion(version_str='2.0'), endpoint_url=self.ne_endpoint, token=self.keystone.auth_token, **self.k_creds)
ahmadsa95baa272016-11-30 09:14:11 +0500199 else:
ahmadsa96af9f42017-01-31 16:17:14 +0500200 self.nova = nClient_v2.Client(version='2', **self.n_creds)
montesmoreno0c8def02016-12-22 12:16:23 +0000201 self.cinder = cClient_v2.Client(**self.n_creds)
ahmadsa95baa272016-11-30 09:14:11 +0500202 self.keystone = ksClient_v2.Client(**self.k_creds)
203 self.ne_endpoint=self.keystone.service_catalog.url_for(service_type='network', endpoint_type='publicURL')
204 self.neutron = neClient_v2.Client('2.0', endpoint_url=self.ne_endpoint, token=self.keystone.auth_token, **self.k_creds)
tierno7edb6752016-03-21 17:37:52 +0100205 self.glance_endpoint = self.keystone.service_catalog.url_for(service_type='image', endpoint_type='publicURL')
206 self.glance = glClient.Client(self.glance_endpoint, token=self.keystone.auth_token, **self.k_creds) #TODO check k_creds vs n_creds
tierno7edb6752016-03-21 17:37:52 +0100207 self.reload_client = False
ahmadsa95baa272016-11-30 09:14:11 +0500208
tierno7edb6752016-03-21 17:37:52 +0100209 def __net_os2mano(self, net_list_dict):
210 '''Transform the net openstack format to mano format
211 net_list_dict can be a list of dict or a single dict'''
212 if type(net_list_dict) is dict:
213 net_list_=(net_list_dict,)
214 elif type(net_list_dict) is list:
215 net_list_=net_list_dict
216 else:
217 raise TypeError("param net_list_dict must be a list or a dictionary")
218 for net in net_list_:
219 if net.get('provider:network_type') == "vlan":
220 net['type']='data'
221 else:
222 net['type']='bridge'
tiernoae4a8d12016-07-08 12:30:39 +0200223
224
225
226 def _format_exception(self, exception):
227 '''Transform a keystone, nova, neutron exception into a vimconn exception'''
228 if isinstance(exception, (HTTPException, gl1Exceptions.HTTPException, gl1Exceptions.CommunicationError,
tierno8e995ce2016-09-22 08:13:00 +0000229 ConnectionError, ksExceptions.ConnectionError, neExceptions.ConnectionFailed
230 )):
tiernoae4a8d12016-07-08 12:30:39 +0200231 raise vimconn.vimconnConnectionException(type(exception).__name__ + ": " + str(exception))
232 elif isinstance(exception, (nvExceptions.ClientException, ksExceptions.ClientException,
233 neExceptions.NeutronException, nvExceptions.BadRequest)):
234 raise vimconn.vimconnUnexpectedResponse(type(exception).__name__ + ": " + str(exception))
235 elif isinstance(exception, (neExceptions.NetworkNotFoundClient, nvExceptions.NotFound)):
236 raise vimconn.vimconnNotFoundException(type(exception).__name__ + ": " + str(exception))
237 elif isinstance(exception, nvExceptions.Conflict):
238 raise vimconn.vimconnConflictException(type(exception).__name__ + ": " + str(exception))
239 else: # ()
240 raise vimconn.vimconnConnectionException(type(exception).__name__ + ": " + str(exception))
241
242 def get_tenant_list(self, filter_dict={}):
243 '''Obtain tenants of VIM
244 filter_dict can contain the following keys:
245 name: filter by tenant name
246 id: filter by tenant uuid/id
247 <other VIM specific>
248 Returns the tenant list of dictionaries: [{'name':'<name>, 'id':'<id>, ...}, ...]
249 '''
ahmadsa95baa272016-11-30 09:14:11 +0500250 self.logger.debug("Getting tenants from VIM filter: '%s'", str(filter_dict))
tiernoae4a8d12016-07-08 12:30:39 +0200251 try:
252 self._reload_connection()
montesmoreno0c8def02016-12-22 12:16:23 +0000253 if self.osc_api_version == 'v3.3':
ahmadsa95baa272016-11-30 09:14:11 +0500254 project_class_list=self.keystone.projects.findall(**filter_dict)
255 else:
256 project_class_list=self.keystone.tenants.findall(**filter_dict)
257 project_list=[]
258 for project in project_class_list:
259 project_list.append(project.to_dict())
260 return project_list
tierno8e995ce2016-09-22 08:13:00 +0000261 except (ksExceptions.ConnectionError, ksExceptions.ClientException, ConnectionError) as e:
tiernoae4a8d12016-07-08 12:30:39 +0200262 self._format_exception(e)
263
264 def new_tenant(self, tenant_name, tenant_description):
265 '''Adds a new tenant to openstack VIM. Returns the tenant identifier'''
266 self.logger.debug("Adding a new tenant name: %s", tenant_name)
267 try:
268 self._reload_connection()
ahmadsa95baa272016-11-30 09:14:11 +0500269 if self.osc_api_version == 'v3.3':
270 project=self.keystone.projects.create(tenant_name, tenant_description)
271 else:
272 project=self.keystone.tenants.create(tenant_name, tenant_description)
273 return project.id
tierno8e995ce2016-09-22 08:13:00 +0000274 except (ksExceptions.ConnectionError, ksExceptions.ClientException, ConnectionError) as e:
tiernoae4a8d12016-07-08 12:30:39 +0200275 self._format_exception(e)
276
277 def delete_tenant(self, tenant_id):
278 '''Delete a tenant from openstack VIM. Returns the old tenant identifier'''
279 self.logger.debug("Deleting tenant %s from VIM", tenant_id)
280 try:
281 self._reload_connection()
montesmoreno0c8def02016-12-22 12:16:23 +0000282 if self.osc_api_version == 'v3.3':
ahmadsa95baa272016-11-30 09:14:11 +0500283 self.keystone.projects.delete(tenant_id)
284 else:
285 self.keystone.tenants.delete(tenant_id)
tiernoae4a8d12016-07-08 12:30:39 +0200286 return tenant_id
tierno8e995ce2016-09-22 08:13:00 +0000287 except (ksExceptions.ConnectionError, ksExceptions.ClientException, ConnectionError) as e:
tiernoae4a8d12016-07-08 12:30:39 +0200288 self._format_exception(e)
ahmadsa95baa272016-11-30 09:14:11 +0500289
garciadeblas9f8456e2016-09-05 05:02:59 +0200290 def new_network(self,net_name, net_type, ip_profile=None, shared=False, vlan=None):
tiernoae4a8d12016-07-08 12:30:39 +0200291 '''Adds a tenant network to VIM. Returns the network identifier'''
292 self.logger.debug("Adding a new network to VIM name '%s', type '%s'", net_name, net_type)
garciadeblasedca7b32016-09-29 14:01:52 +0000293 #self.logger.debug(">>>>>>>>>>>>>>>>>> IP profile %s", str(ip_profile))
tierno7edb6752016-03-21 17:37:52 +0100294 try:
garciadeblasedca7b32016-09-29 14:01:52 +0000295 new_net = None
tierno7edb6752016-03-21 17:37:52 +0100296 self._reload_connection()
297 network_dict = {'name': net_name, 'admin_state_up': True}
298 if net_type=="data" or net_type=="ptp":
299 if self.config.get('dataplane_physical_net') == None:
tiernoae4a8d12016-07-08 12:30:39 +0200300 raise vimconn.vimconnConflictException("You must provide a 'dataplane_physical_net' at config value before creating sriov network")
tierno7edb6752016-03-21 17:37:52 +0100301 network_dict["provider:physical_network"] = self.config['dataplane_physical_net'] #"physnet_sriov" #TODO physical
302 network_dict["provider:network_type"] = "vlan"
303 if vlan!=None:
304 network_dict["provider:network_type"] = vlan
tiernoae4a8d12016-07-08 12:30:39 +0200305 network_dict["shared"]=shared
tierno7edb6752016-03-21 17:37:52 +0100306 new_net=self.neutron.create_network({'network':network_dict})
307 #print new_net
garciadeblas9f8456e2016-09-05 05:02:59 +0200308 #create subnetwork, even if there is no profile
309 if not ip_profile:
310 ip_profile = {}
311 if 'subnet_address' not in ip_profile:
garciadeblas2299e3b2017-01-26 14:35:55 +0000312 #Fake subnet is required
313 subnet_rand = random.randint(0, 255)
314 ip_profile['subnet_address'] = "192.168.{}.0/24".format(subnet_rand)
garciadeblas9f8456e2016-09-05 05:02:59 +0200315 if 'ip_version' not in ip_profile:
316 ip_profile['ip_version'] = "IPv4"
tierno7edb6752016-03-21 17:37:52 +0100317 subnet={"name":net_name+"-subnet",
318 "network_id": new_net["network"]["id"],
garciadeblas9f8456e2016-09-05 05:02:59 +0200319 "ip_version": 4 if ip_profile['ip_version']=="IPv4" else 6,
320 "cidr": ip_profile['subnet_address']
tierno7edb6752016-03-21 17:37:52 +0100321 }
garciadeblas9f8456e2016-09-05 05:02:59 +0200322 if 'gateway_address' in ip_profile:
323 subnet['gateway_ip'] = ip_profile['gateway_address']
garciadeblasedca7b32016-09-29 14:01:52 +0000324 if ip_profile.get('dns_address'):
tierno455612d2017-05-30 16:40:10 +0200325 subnet['dns_nameservers'] = ip_profile['dns_address'].split(";")
garciadeblas9f8456e2016-09-05 05:02:59 +0200326 if 'dhcp_enabled' in ip_profile:
327 subnet['enable_dhcp'] = False if ip_profile['dhcp_enabled']=="false" else True
328 if 'dhcp_start_address' in ip_profile:
329 subnet['allocation_pools']=[]
330 subnet['allocation_pools'].append(dict())
331 subnet['allocation_pools'][0]['start'] = ip_profile['dhcp_start_address']
332 if 'dhcp_count' in ip_profile:
333 #parts = ip_profile['dhcp_start_address'].split('.')
334 #ip_int = (int(parts[0]) << 24) + (int(parts[1]) << 16) + (int(parts[2]) << 8) + int(parts[3])
335 ip_int = int(netaddr.IPAddress(ip_profile['dhcp_start_address']))
garciadeblas21d795b2016-09-29 17:31:46 +0200336 ip_int += ip_profile['dhcp_count'] - 1
garciadeblas9f8456e2016-09-05 05:02:59 +0200337 ip_str = str(netaddr.IPAddress(ip_int))
338 subnet['allocation_pools'][0]['end'] = ip_str
garciadeblasedca7b32016-09-29 14:01:52 +0000339 #self.logger.debug(">>>>>>>>>>>>>>>>>> Subnet: %s", str(subnet))
tierno7edb6752016-03-21 17:37:52 +0100340 self.neutron.create_subnet({"subnet": subnet} )
tiernoae4a8d12016-07-08 12:30:39 +0200341 return new_net["network"]["id"]
tierno8e995ce2016-09-22 08:13:00 +0000342 except (neExceptions.ConnectionFailed, ksExceptions.ClientException, neExceptions.NeutronException, ConnectionError) as e:
garciadeblasedca7b32016-09-29 14:01:52 +0000343 if new_net:
344 self.neutron.delete_network(new_net['network']['id'])
tiernoae4a8d12016-07-08 12:30:39 +0200345 self._format_exception(e)
tierno7edb6752016-03-21 17:37:52 +0100346
347 def get_network_list(self, filter_dict={}):
348 '''Obtain tenant networks of VIM
349 Filter_dict can be:
350 name: network name
351 id: network uuid
352 shared: boolean
353 tenant_id: tenant
354 admin_state_up: boolean
355 status: 'ACTIVE'
356 Returns the network list of dictionaries
357 '''
tiernoae4a8d12016-07-08 12:30:39 +0200358 self.logger.debug("Getting network from VIM filter: '%s'", str(filter_dict))
tierno7edb6752016-03-21 17:37:52 +0100359 try:
360 self._reload_connection()
montesmoreno0c8def02016-12-22 12:16:23 +0000361 if self.osc_api_version == 'v3.3' and "tenant_id" in filter_dict:
ahmadsa95baa272016-11-30 09:14:11 +0500362 filter_dict['project_id'] = filter_dict.pop('tenant_id')
tierno7edb6752016-03-21 17:37:52 +0100363 net_dict=self.neutron.list_networks(**filter_dict)
364 net_list=net_dict["networks"]
365 self.__net_os2mano(net_list)
tiernoae4a8d12016-07-08 12:30:39 +0200366 return net_list
tierno8e995ce2016-09-22 08:13:00 +0000367 except (neExceptions.ConnectionFailed, ksExceptions.ClientException, neExceptions.NeutronException, ConnectionError) as e:
tiernoae4a8d12016-07-08 12:30:39 +0200368 self._format_exception(e)
tierno7edb6752016-03-21 17:37:52 +0100369
tiernoae4a8d12016-07-08 12:30:39 +0200370 def get_network(self, net_id):
371 '''Obtain details of network from VIM
372 Returns the network information from a network id'''
373 self.logger.debug(" Getting tenant network %s from VIM", net_id)
tierno7edb6752016-03-21 17:37:52 +0100374 filter_dict={"id": net_id}
tiernoae4a8d12016-07-08 12:30:39 +0200375 net_list = self.get_network_list(filter_dict)
tierno7edb6752016-03-21 17:37:52 +0100376 if len(net_list)==0:
tiernoae4a8d12016-07-08 12:30:39 +0200377 raise vimconn.vimconnNotFoundException("Network '{}' not found".format(net_id))
tierno7edb6752016-03-21 17:37:52 +0100378 elif len(net_list)>1:
tiernoae4a8d12016-07-08 12:30:39 +0200379 raise vimconn.vimconnConflictException("Found more than one network with this criteria")
tierno7edb6752016-03-21 17:37:52 +0100380 net = net_list[0]
381 subnets=[]
382 for subnet_id in net.get("subnets", () ):
383 try:
384 subnet = self.neutron.show_subnet(subnet_id)
385 except Exception as e:
tiernoae4a8d12016-07-08 12:30:39 +0200386 self.logger.error("osconnector.get_network(): Error getting subnet %s %s" % (net_id, str(e)))
387 subnet = {"id": subnet_id, "fault": str(e)}
tierno7edb6752016-03-21 17:37:52 +0100388 subnets.append(subnet)
389 net["subnets"] = subnets
Pablo Montes Moreno51e553b2017-03-23 16:39:12 +0100390 net["encapsulation"] = net.get('provider:network_type')
Pablo Montes Moreno3fbff9b2017-03-08 11:28:15 +0100391 net["segmentation_id"] = net.get('provider:segmentation_id')
tiernoae4a8d12016-07-08 12:30:39 +0200392 return net
tierno7edb6752016-03-21 17:37:52 +0100393
tiernoae4a8d12016-07-08 12:30:39 +0200394 def delete_network(self, net_id):
395 '''Deletes a tenant network from VIM. Returns the old network identifier'''
396 self.logger.debug("Deleting network '%s' from VIM", net_id)
tierno7edb6752016-03-21 17:37:52 +0100397 try:
398 self._reload_connection()
399 #delete VM ports attached to this networks before the network
400 ports = self.neutron.list_ports(network_id=net_id)
401 for p in ports['ports']:
402 try:
403 self.neutron.delete_port(p["id"])
404 except Exception as e:
tiernoae4a8d12016-07-08 12:30:39 +0200405 self.logger.error("Error deleting port %s: %s", p["id"], str(e))
tierno7edb6752016-03-21 17:37:52 +0100406 self.neutron.delete_network(net_id)
tiernoae4a8d12016-07-08 12:30:39 +0200407 return net_id
408 except (neExceptions.ConnectionFailed, neExceptions.NetworkNotFoundClient, neExceptions.NeutronException,
tierno8e995ce2016-09-22 08:13:00 +0000409 ksExceptions.ClientException, neExceptions.NeutronException, ConnectionError) as e:
tiernoae4a8d12016-07-08 12:30:39 +0200410 self._format_exception(e)
tierno7edb6752016-03-21 17:37:52 +0100411
tiernoae4a8d12016-07-08 12:30:39 +0200412 def refresh_nets_status(self, net_list):
413 '''Get the status of the networks
414 Params: the list of network identifiers
415 Returns a dictionary with:
416 net_id: #VIM id of this network
417 status: #Mandatory. Text with one of:
418 # DELETED (not found at vim)
419 # VIM_ERROR (Cannot connect to VIM, VIM response error, ...)
420 # OTHER (Vim reported other status not understood)
421 # ERROR (VIM indicates an ERROR status)
422 # ACTIVE, INACTIVE, DOWN (admin down),
423 # BUILD (on building process)
424 #
425 error_msg: #Text with VIM error message, if any. Or the VIM connection ERROR
426 vim_info: #Text with plain information obtained from vim (yaml.safe_dump)
427
428 '''
429 net_dict={}
430 for net_id in net_list:
431 net = {}
432 try:
433 net_vim = self.get_network(net_id)
434 if net_vim['status'] in netStatus2manoFormat:
435 net["status"] = netStatus2manoFormat[ net_vim['status'] ]
436 else:
437 net["status"] = "OTHER"
438 net["error_msg"] = "VIM status reported " + net_vim['status']
439
tierno8e995ce2016-09-22 08:13:00 +0000440 if net['status'] == "ACTIVE" and not net_vim['admin_state_up']:
tiernoae4a8d12016-07-08 12:30:39 +0200441 net['status'] = 'DOWN'
tierno8e995ce2016-09-22 08:13:00 +0000442 try:
443 net['vim_info'] = yaml.safe_dump(net_vim, default_flow_style=True, width=256)
444 except yaml.representer.RepresenterError:
445 net['vim_info'] = str(net_vim)
tiernoae4a8d12016-07-08 12:30:39 +0200446 if net_vim.get('fault'): #TODO
447 net['error_msg'] = str(net_vim['fault'])
448 except vimconn.vimconnNotFoundException as e:
449 self.logger.error("Exception getting net status: %s", str(e))
450 net['status'] = "DELETED"
451 net['error_msg'] = str(e)
452 except vimconn.vimconnException as e:
453 self.logger.error("Exception getting net status: %s", str(e))
454 net['status'] = "VIM_ERROR"
455 net['error_msg'] = str(e)
456 net_dict[net_id] = net
457 return net_dict
458
459 def get_flavor(self, flavor_id):
460 '''Obtain flavor details from the VIM. Returns the flavor dict details'''
461 self.logger.debug("Getting flavor '%s'", flavor_id)
tierno7edb6752016-03-21 17:37:52 +0100462 try:
463 self._reload_connection()
464 flavor = self.nova.flavors.find(id=flavor_id)
465 #TODO parse input and translate to VIM format (openmano_schemas.new_vminstance_response_schema)
tiernoae4a8d12016-07-08 12:30:39 +0200466 return flavor.to_dict()
tierno8e995ce2016-09-22 08:13:00 +0000467 except (nvExceptions.NotFound, nvExceptions.ClientException, ksExceptions.ClientException, ConnectionError) as e:
tiernoae4a8d12016-07-08 12:30:39 +0200468 self._format_exception(e)
tierno7edb6752016-03-21 17:37:52 +0100469
tiernocf157a82017-01-30 14:07:06 +0100470 def get_flavor_id_from_data(self, flavor_dict):
471 """Obtain flavor id that match the flavor description
472 Returns the flavor_id or raises a vimconnNotFoundException
tiernoe26fc7a2017-05-30 14:43:03 +0200473 flavor_dict: contains the required ram, vcpus, disk
474 If 'use_existing_flavors' is set to True at config, the closer flavor that provides same or more ram, vcpus
475 and disk is returned. Otherwise a flavor with exactly same ram, vcpus and disk is returned or a
476 vimconnNotFoundException is raised
tiernocf157a82017-01-30 14:07:06 +0100477 """
tiernoe26fc7a2017-05-30 14:43:03 +0200478 exact_match = False if self.config.get('use_existing_flavors') else True
tiernocf157a82017-01-30 14:07:06 +0100479 try:
480 self._reload_connection()
tiernoe26fc7a2017-05-30 14:43:03 +0200481 flavor_candidate_id = None
482 flavor_candidate_data = (10000, 10000, 10000)
483 flavor_target = (flavor_dict["ram"], flavor_dict["vcpus"], flavor_dict["disk"])
484 # numa=None
485 numas = flavor_dict.get("extended", {}).get("numas")
tiernocf157a82017-01-30 14:07:06 +0100486 if numas:
487 #TODO
488 raise vimconn.vimconnNotFoundException("Flavor with EPA still not implemted")
489 # if len(numas) > 1:
490 # raise vimconn.vimconnNotFoundException("Cannot find any flavor with more than one numa")
491 # numa=numas[0]
492 # numas = extended.get("numas")
493 for flavor in self.nova.flavors.list():
494 epa = flavor.get_keys()
495 if epa:
496 continue
tiernoe26fc7a2017-05-30 14:43:03 +0200497 # TODO
498 flavor_data = (flavor.ram, flavor.vcpus, flavor.disk)
499 if flavor_data == flavor_target:
500 return flavor.id
501 elif not exact_match and flavor_target < flavor_data < flavor_candidate_data:
502 flavor_candidate_id = flavor.id
503 flavor_candidate_data = flavor_data
504 if not exact_match and flavor_candidate_id:
505 return flavor_candidate_id
tiernocf157a82017-01-30 14:07:06 +0100506 raise vimconn.vimconnNotFoundException("Cannot find any flavor matching '{}'".format(str(flavor_dict)))
507 except (nvExceptions.NotFound, nvExceptions.ClientException, ksExceptions.ClientException, ConnectionError) as e:
508 self._format_exception(e)
509
510
tiernoae4a8d12016-07-08 12:30:39 +0200511 def new_flavor(self, flavor_data, change_name_if_used=True):
tierno7edb6752016-03-21 17:37:52 +0100512 '''Adds a tenant flavor to openstack VIM
tiernoae4a8d12016-07-08 12:30:39 +0200513 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 +0100514 Returns the flavor identifier
515 '''
tiernoae4a8d12016-07-08 12:30:39 +0200516 self.logger.debug("Adding flavor '%s'", str(flavor_data))
tierno7edb6752016-03-21 17:37:52 +0100517 retry=0
tiernoae4a8d12016-07-08 12:30:39 +0200518 max_retries=3
tierno7edb6752016-03-21 17:37:52 +0100519 name_suffix = 0
tiernoae4a8d12016-07-08 12:30:39 +0200520 name=flavor_data['name']
521 while retry<max_retries:
tierno7edb6752016-03-21 17:37:52 +0100522 retry+=1
523 try:
524 self._reload_connection()
525 if change_name_if_used:
526 #get used names
527 fl_names=[]
528 fl=self.nova.flavors.list()
529 for f in fl:
530 fl_names.append(f.name)
531 while name in fl_names:
532 name_suffix += 1
tiernoae4a8d12016-07-08 12:30:39 +0200533 name = flavor_data['name']+"-" + str(name_suffix)
tierno7edb6752016-03-21 17:37:52 +0100534
tiernoae4a8d12016-07-08 12:30:39 +0200535 ram = flavor_data.get('ram',64)
536 vcpus = flavor_data.get('vcpus',1)
tierno7edb6752016-03-21 17:37:52 +0100537 numa_properties=None
538
tiernoae4a8d12016-07-08 12:30:39 +0200539 extended = flavor_data.get("extended")
tierno7edb6752016-03-21 17:37:52 +0100540 if extended:
541 numas=extended.get("numas")
542 if numas:
543 numa_nodes = len(numas)
544 if numa_nodes > 1:
545 return -1, "Can not add flavor with more than one numa"
546 numa_properties = {"hw:numa_nodes":str(numa_nodes)}
547 numa_properties["hw:mem_page_size"] = "large"
548 numa_properties["hw:cpu_policy"] = "dedicated"
549 numa_properties["hw:numa_mempolicy"] = "strict"
550 for numa in numas:
551 #overwrite ram and vcpus
552 ram = numa['memory']*1024
Pablo Montes Morenoea1d6232017-05-24 11:33:24 +0200553 #See for reference: https://specs.openstack.org/openstack/nova-specs/specs/mitaka/implemented/virt-driver-cpu-thread-pinning.html
tierno7edb6752016-03-21 17:37:52 +0100554 if 'paired-threads' in numa:
555 vcpus = numa['paired-threads']*2
Pablo Montes Morenoea1d6232017-05-24 11:33:24 +0200556 #cpu_thread_policy "require" implies that the compute node must have an STM architecture
557 numa_properties["hw:cpu_thread_policy"] = "require"
558 numa_properties["hw:cpu_policy"] = "dedicated"
tierno7edb6752016-03-21 17:37:52 +0100559 elif 'cores' in numa:
560 vcpus = numa['cores']
Pablo Montes Morenoea1d6232017-05-24 11:33:24 +0200561 # cpu_thread_policy "prefer" implies that the host must not have an SMT architecture, or a non-SMT architecture will be emulated
562 numa_properties["hw:cpu_thread_policy"] = "isolate"
563 numa_properties["hw:cpu_policy"] = "dedicated"
tierno7edb6752016-03-21 17:37:52 +0100564 elif 'threads' in numa:
565 vcpus = numa['threads']
Pablo Montes Morenoea1d6232017-05-24 11:33:24 +0200566 # cpu_thread_policy "prefer" implies that the host may or may not have an SMT architecture
567 numa_properties["hw:cpu_thread_policy"] = "prefer"
568 numa_properties["hw:cpu_policy"] = "dedicated"
Pablo Montes Moreno3be0b2a2017-03-30 13:22:15 +0200569 # for interface in numa.get("interfaces",() ):
570 # if interface["dedicated"]=="yes":
571 # raise vimconn.vimconnException("Passthrough interfaces are not supported for the openstack connector", http_code=vimconn.HTTP_Service_Unavailable)
572 # #TODO, add the key 'pci_passthrough:alias"="<label at config>:<number ifaces>"' when a way to connect it is available
tierno7edb6752016-03-21 17:37:52 +0100573
574 #create flavor
575 new_flavor=self.nova.flavors.create(name,
576 ram,
577 vcpus,
tiernoae4a8d12016-07-08 12:30:39 +0200578 flavor_data.get('disk',1),
579 is_public=flavor_data.get('is_public', True)
tierno7edb6752016-03-21 17:37:52 +0100580 )
581 #add metadata
582 if numa_properties:
583 new_flavor.set_keys(numa_properties)
tiernoae4a8d12016-07-08 12:30:39 +0200584 return new_flavor.id
tierno7edb6752016-03-21 17:37:52 +0100585 except nvExceptions.Conflict as e:
tiernoae4a8d12016-07-08 12:30:39 +0200586 if change_name_if_used and retry < max_retries:
tierno7edb6752016-03-21 17:37:52 +0100587 continue
tiernoae4a8d12016-07-08 12:30:39 +0200588 self._format_exception(e)
tierno7edb6752016-03-21 17:37:52 +0100589 #except nvExceptions.BadRequest as e:
590 except (ksExceptions.ClientException, nvExceptions.ClientException, ConnectionError) as e:
tiernoae4a8d12016-07-08 12:30:39 +0200591 self._format_exception(e)
tierno7edb6752016-03-21 17:37:52 +0100592
tiernoae4a8d12016-07-08 12:30:39 +0200593 def delete_flavor(self,flavor_id):
594 '''Deletes a tenant flavor from openstack VIM. Returns the old flavor_id
tierno7edb6752016-03-21 17:37:52 +0100595 '''
tiernoae4a8d12016-07-08 12:30:39 +0200596 try:
597 self._reload_connection()
598 self.nova.flavors.delete(flavor_id)
599 return flavor_id
600 #except nvExceptions.BadRequest as e:
tierno8e995ce2016-09-22 08:13:00 +0000601 except (nvExceptions.NotFound, ksExceptions.ClientException, nvExceptions.ClientException, ConnectionError) as e:
tiernoae4a8d12016-07-08 12:30:39 +0200602 self._format_exception(e)
tierno7edb6752016-03-21 17:37:52 +0100603
tiernoae4a8d12016-07-08 12:30:39 +0200604 def new_image(self,image_dict):
tierno7edb6752016-03-21 17:37:52 +0100605 '''
tiernoae4a8d12016-07-08 12:30:39 +0200606 Adds a tenant image to VIM. imge_dict is a dictionary with:
607 name: name
608 disk_format: qcow2, vhd, vmdk, raw (by default), ...
609 location: path or URI
610 public: "yes" or "no"
611 metadata: metadata of the image
612 Returns the image_id
tierno7edb6752016-03-21 17:37:52 +0100613 '''
tierno7edb6752016-03-21 17:37:52 +0100614 #using version 1 of glance client
615 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 +0200616 retry=0
617 max_retries=3
618 while retry<max_retries:
tierno7edb6752016-03-21 17:37:52 +0100619 retry+=1
620 try:
621 self._reload_connection()
622 #determine format http://docs.openstack.org/developer/glance/formats.html
623 if "disk_format" in image_dict:
624 disk_format=image_dict["disk_format"]
garciadeblas14480452017-01-10 13:08:07 +0100625 else: #autodiscover based on extension
tierno7edb6752016-03-21 17:37:52 +0100626 if image_dict['location'][-6:]==".qcow2":
627 disk_format="qcow2"
628 elif image_dict['location'][-4:]==".vhd":
629 disk_format="vhd"
630 elif image_dict['location'][-5:]==".vmdk":
631 disk_format="vmdk"
632 elif image_dict['location'][-4:]==".vdi":
633 disk_format="vdi"
634 elif image_dict['location'][-4:]==".iso":
635 disk_format="iso"
636 elif image_dict['location'][-4:]==".aki":
637 disk_format="aki"
638 elif image_dict['location'][-4:]==".ari":
639 disk_format="ari"
640 elif image_dict['location'][-4:]==".ami":
641 disk_format="ami"
642 else:
643 disk_format="raw"
tiernoae4a8d12016-07-08 12:30:39 +0200644 self.logger.debug("new_image: '%s' loading from '%s'", image_dict['name'], image_dict['location'])
tierno7edb6752016-03-21 17:37:52 +0100645 if image_dict['location'][0:4]=="http":
646 new_image = glancev1.images.create(name=image_dict['name'], is_public=image_dict.get('public',"yes")=="yes",
647 container_format="bare", location=image_dict['location'], disk_format=disk_format)
648 else: #local path
649 with open(image_dict['location']) as fimage:
650 new_image = glancev1.images.create(name=image_dict['name'], is_public=image_dict.get('public',"yes")=="yes",
651 container_format="bare", data=fimage, disk_format=disk_format)
652 #insert metadata. We cannot use 'new_image.properties.setdefault'
653 #because nova and glance are "INDEPENDENT" and we are using nova for reading metadata
654 new_image_nova=self.nova.images.find(id=new_image.id)
655 new_image_nova.metadata.setdefault('location',image_dict['location'])
656 metadata_to_load = image_dict.get('metadata')
657 if metadata_to_load:
658 for k,v in yaml.load(metadata_to_load).iteritems():
659 new_image_nova.metadata.setdefault(k,v)
tiernoae4a8d12016-07-08 12:30:39 +0200660 return new_image.id
661 except (nvExceptions.Conflict, ksExceptions.ClientException, nvExceptions.ClientException) as e:
662 self._format_exception(e)
tierno8e995ce2016-09-22 08:13:00 +0000663 except (HTTPException, gl1Exceptions.HTTPException, gl1Exceptions.CommunicationError, ConnectionError) as e:
tiernoae4a8d12016-07-08 12:30:39 +0200664 if retry==max_retries:
665 continue
666 self._format_exception(e)
tierno7edb6752016-03-21 17:37:52 +0100667 except IOError as e: #can not open the file
tiernoae4a8d12016-07-08 12:30:39 +0200668 raise vimconn.vimconnConnectionException(type(e).__name__ + ": " + str(e)+ " for " + image_dict['location'],
669 http_code=vimconn.HTTP_Bad_Request)
tierno7edb6752016-03-21 17:37:52 +0100670
tiernoae4a8d12016-07-08 12:30:39 +0200671 def delete_image(self, image_id):
672 '''Deletes a tenant image from openstack VIM. Returns the old id
tierno7edb6752016-03-21 17:37:52 +0100673 '''
tiernoae4a8d12016-07-08 12:30:39 +0200674 try:
675 self._reload_connection()
676 self.nova.images.delete(image_id)
677 return image_id
tierno8e995ce2016-09-22 08:13:00 +0000678 except (nvExceptions.NotFound, ksExceptions.ClientException, nvExceptions.ClientException, gl1Exceptions.CommunicationError, ConnectionError) as e: #TODO remove
tiernoae4a8d12016-07-08 12:30:39 +0200679 self._format_exception(e)
680
681 def get_image_id_from_path(self, path):
garciadeblasb69fa9f2016-09-28 12:04:10 +0200682 '''Get the image id from image path in the VIM database. Returns the image_id'''
tiernoae4a8d12016-07-08 12:30:39 +0200683 try:
684 self._reload_connection()
685 images = self.nova.images.list()
686 for image in images:
687 if image.metadata.get("location")==path:
688 return image.id
689 raise vimconn.vimconnNotFoundException("image with location '{}' not found".format( path))
tierno8e995ce2016-09-22 08:13:00 +0000690 except (ksExceptions.ClientException, nvExceptions.ClientException, gl1Exceptions.CommunicationError, ConnectionError) as e:
tiernoae4a8d12016-07-08 12:30:39 +0200691 self._format_exception(e)
tierno7edb6752016-03-21 17:37:52 +0100692
garciadeblasb69fa9f2016-09-28 12:04:10 +0200693 def get_image_list(self, filter_dict={}):
694 '''Obtain tenant images from VIM
695 Filter_dict can be:
696 id: image id
697 name: image name
698 checksum: image checksum
699 Returns the image list of dictionaries:
700 [{<the fields at Filter_dict plus some VIM specific>}, ...]
701 List can be empty
702 '''
703 self.logger.debug("Getting image list from VIM filter: '%s'", str(filter_dict))
704 try:
705 self._reload_connection()
706 filter_dict_os=filter_dict.copy()
707 #First we filter by the available filter fields: name, id. The others are removed.
708 filter_dict_os.pop('checksum',None)
709 image_list=self.nova.images.findall(**filter_dict_os)
710 if len(image_list)==0:
711 return []
712 #Then we filter by the rest of filter fields: checksum
713 filtered_list = []
714 for image in image_list:
tierno4540ea52017-01-18 17:44:32 +0100715 image_class=self.glance.images.get(image.id)
716 if 'checksum' not in filter_dict or image_class['checksum']==filter_dict.get('checksum'):
717 filtered_list.append(image_class.copy())
garciadeblasb69fa9f2016-09-28 12:04:10 +0200718 return filtered_list
719 except (ksExceptions.ClientException, nvExceptions.ClientException, gl1Exceptions.CommunicationError, ConnectionError) as e:
720 self._format_exception(e)
721
montesmoreno0c8def02016-12-22 12:16:23 +0000722 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 +0100723 '''Adds a VM instance to VIM
724 Params:
725 start: indicates if VM must start or boot in pause mode. Ignored
726 image_id,flavor_id: iamge and flavor uuid
727 net_list: list of interfaces, each one is a dictionary with:
728 name:
729 net_id: network uuid to connect
730 vpci: virtual vcpi to assign, ignored because openstack lack #TODO
731 model: interface model, ignored #TODO
732 mac_address: used for SR-IOV ifaces #TODO for other types
733 use: 'data', 'bridge', 'mgmt'
734 type: 'virtual', 'PF', 'VF', 'VFnotShared'
735 vim_id: filled/added by this function
ahmadsaf853d452016-12-22 11:33:47 +0500736 floating_ip: True/False (or it can be None)
tierno7edb6752016-03-21 17:37:52 +0100737 #TODO ip, security groups
tiernoae4a8d12016-07-08 12:30:39 +0200738 Returns the instance identifier
tierno7edb6752016-03-21 17:37:52 +0100739 '''
tiernofa51c202017-01-27 14:58:17 +0100740 self.logger.debug("new_vminstance input: image='%s' flavor='%s' nics='%s'",image_id, flavor_id,str(net_list))
tierno7edb6752016-03-21 17:37:52 +0100741 try:
tierno6e116232016-07-18 13:01:40 +0200742 metadata={}
tierno7edb6752016-03-21 17:37:52 +0100743 net_list_vim=[]
ahmadsaf853d452016-12-22 11:33:47 +0500744 external_network=[] #list of external networks to be connected to instance, later on used to create floating_ip
tierno7edb6752016-03-21 17:37:52 +0100745 self._reload_connection()
tiernoae4a8d12016-07-08 12:30:39 +0200746 metadata_vpci={} #For a specific neutron plugin
tierno7edb6752016-03-21 17:37:52 +0100747 for net in net_list:
748 if not net.get("net_id"): #skip non connected iface
749 continue
Pablo Montes Moreno3be0b2a2017-03-30 13:22:15 +0200750
751 port_dict={
752 "network_id": net["net_id"],
753 "name": net.get("name"),
754 "admin_state_up": True
755 }
756 if net["type"]=="virtual":
757 if "vpci" in net:
758 metadata_vpci[ net["net_id"] ] = [[ net["vpci"], "" ]]
759 elif net["type"]=="VF": # for VF
760 if "vpci" in net:
761 if "VF" not in metadata_vpci:
762 metadata_vpci["VF"]=[]
763 metadata_vpci["VF"].append([ net["vpci"], "" ])
764 port_dict["binding:vnic_type"]="direct"
765 else: #For PT
766 if "vpci" in net:
767 if "PF" not in metadata_vpci:
768 metadata_vpci["PF"]=[]
769 metadata_vpci["PF"].append([ net["vpci"], "" ])
770 port_dict["binding:vnic_type"]="direct-physical"
771 if not port_dict["name"]:
772 port_dict["name"]=name
773 if net.get("mac_address"):
774 port_dict["mac_address"]=net["mac_address"]
775 if net.get("port_security") == False:
776 port_dict["port_security_enabled"]=net["port_security"]
777 new_port = self.neutron.create_port({"port": port_dict })
778 net["mac_adress"] = new_port["port"]["mac_address"]
779 net["vim_id"] = new_port["port"]["id"]
780 net["ip"] = new_port["port"].get("fixed_ips", [{}])[0].get("ip_address")
781 net_list_vim.append({"port-id": new_port["port"]["id"]})
782
ahmadsaf853d452016-12-22 11:33:47 +0500783 if net.get('floating_ip', False):
tiernof8383b82017-01-18 15:49:48 +0100784 net['exit_on_floating_ip_error'] = True
ahmadsaf853d452016-12-22 11:33:47 +0500785 external_network.append(net)
tiernof8383b82017-01-18 15:49:48 +0100786 elif net['use'] == 'mgmt' and self.config.get('use_floating_ip'):
787 net['exit_on_floating_ip_error'] = False
788 external_network.append(net)
789
tierno7edb6752016-03-21 17:37:52 +0100790 if metadata_vpci:
791 metadata = {"pci_assignement": json.dumps(metadata_vpci)}
tiernoafbced42016-07-23 01:43:53 +0200792 if len(metadata["pci_assignement"]) >255:
tierno6e116232016-07-18 13:01:40 +0200793 #limit the metadata size
794 #metadata["pci_assignement"] = metadata["pci_assignement"][0:255]
795 self.logger.warn("Metadata deleted since it exceeds the expected length (255) ")
796 metadata = {}
tierno7edb6752016-03-21 17:37:52 +0100797
tiernoae4a8d12016-07-08 12:30:39 +0200798 self.logger.debug("name '%s' image_id '%s'flavor_id '%s' net_list_vim '%s' description '%s' metadata %s",
799 name, image_id, flavor_id, str(net_list_vim), description, str(metadata))
tierno7edb6752016-03-21 17:37:52 +0100800
801 security_groups = self.config.get('security_groups')
802 if type(security_groups) is str:
803 security_groups = ( security_groups, )
tierno36c0b172017-01-12 18:32:28 +0100804 #cloud config
805 userdata=None
806 config_drive = None
tiernoa4e1a6e2016-08-31 14:19:40 +0200807 if isinstance(cloud_config, dict):
tierno36c0b172017-01-12 18:32:28 +0100808 if cloud_config.get("user-data"):
809 userdata=cloud_config["user-data"]
810 if cloud_config.get("boot-data-drive") != None:
811 config_drive = cloud_config["boot-data-drive"]
812 if cloud_config.get("config-files") or cloud_config.get("users") or cloud_config.get("key-pairs"):
813 if userdata:
814 raise vimconn.vimconnConflictException("Cloud-config cannot contain both 'userdata' and 'config-files'/'users'/'key-pairs'")
815 userdata_dict={}
816 #default user
817 if cloud_config.get("key-pairs"):
818 userdata_dict["ssh-authorized-keys"] = cloud_config["key-pairs"]
819 userdata_dict["users"] = [{"default": None, "ssh-authorized-keys": cloud_config["key-pairs"] }]
820 if cloud_config.get("users"):
tierno01d0bf52017-01-25 14:27:20 +0100821 if "users" not in userdata_dict:
tierno36c0b172017-01-12 18:32:28 +0100822 userdata_dict["users"] = [ "default" ]
823 for user in cloud_config["users"]:
824 user_info = {
825 "name" : user["name"],
826 "sudo": "ALL = (ALL)NOPASSWD:ALL"
827 }
828 if "user-info" in user:
829 user_info["gecos"] = user["user-info"]
830 if user.get("key-pairs"):
831 user_info["ssh-authorized-keys"] = user["key-pairs"]
832 userdata_dict["users"].append(user_info)
833
834 if cloud_config.get("config-files"):
835 userdata_dict["write_files"] = []
836 for file in cloud_config["config-files"]:
837 file_info = {
838 "path" : file["dest"],
839 "content": file["content"]
840 }
841 if file.get("encoding"):
842 file_info["encoding"] = file["encoding"]
843 if file.get("permissions"):
844 file_info["permissions"] = file["permissions"]
845 if file.get("owner"):
846 file_info["owner"] = file["owner"]
847 userdata_dict["write_files"].append(file_info)
848 userdata = "#cloud-config\n"
849 userdata += yaml.safe_dump(userdata_dict, indent=4, default_flow_style=False)
tiernoa4e1a6e2016-08-31 14:19:40 +0200850 self.logger.debug("userdata: %s", userdata)
851 elif isinstance(cloud_config, str):
852 userdata = cloud_config
montesmoreno0c8def02016-12-22 12:16:23 +0000853
854 #Create additional volumes in case these are present in disk_list
855 block_device_mapping = None
856 base_disk_index = ord('b')
857 if disk_list != None:
858 block_device_mapping = dict()
859 for disk in disk_list:
860 if 'image_id' in disk:
861 volume = self.cinder.volumes.create(size = disk['size'],name = name + '_vd' +
862 chr(base_disk_index), imageRef = disk['image_id'])
863 else:
864 volume = self.cinder.volumes.create(size=disk['size'], name=name + '_vd' +
865 chr(base_disk_index))
866 block_device_mapping['_vd' + chr(base_disk_index)] = volume.id
867 base_disk_index += 1
868
869 #wait until volumes are with status available
870 keep_waiting = True
871 elapsed_time = 0
872 while keep_waiting and elapsed_time < volume_timeout:
873 keep_waiting = False
874 for volume_id in block_device_mapping.itervalues():
875 if self.cinder.volumes.get(volume_id).status != 'available':
876 keep_waiting = True
877 if keep_waiting:
878 time.sleep(1)
879 elapsed_time += 1
880
881 #if we exceeded the timeout rollback
882 if elapsed_time >= volume_timeout:
883 #delete the volumes we just created
884 for volume_id in block_device_mapping.itervalues():
885 self.cinder.volumes.delete(volume_id)
886
887 #delete ports we just created
888 for net_item in net_list_vim:
889 if 'port-id' in net_item:
montesmorenocf227142017-01-12 12:24:21 +0000890 self.neutron.delete_port(net_item['port-id'])
montesmoreno0c8def02016-12-22 12:16:23 +0000891
892 raise vimconn.vimconnException('Timeout creating volumes for instance ' + name,
893 http_code=vimconn.HTTP_Request_Timeout)
894
Pablo Montes Morenob7490b52017-06-20 10:49:58 +0200895 self.logger.debug("nova.servers.create({}, {}, {}, nics={}, meta={}, security_groups={}," \
896 "availability_zone={}, key_name={}, userdata={}, config_drive={}, " \
897 "block_device_mapping={})".format(name, image_id, flavor_id, net_list_vim,
898 metadata, security_groups, self.config.get('availability_zone'),
899 self.config.get('keypair'), userdata, config_drive, block_device_mapping))
tierno7edb6752016-03-21 17:37:52 +0100900 server = self.nova.servers.create(name, image_id, flavor_id, nics=net_list_vim, meta=metadata,
montesmoreno0c8def02016-12-22 12:16:23 +0000901 security_groups=security_groups,
902 availability_zone=self.config.get('availability_zone'),
903 key_name=self.config.get('keypair'),
904 userdata=userdata,
tierno36c0b172017-01-12 18:32:28 +0100905 config_drive = config_drive,
montesmoreno0c8def02016-12-22 12:16:23 +0000906 block_device_mapping = block_device_mapping
907 ) # , description=description)
tiernoae4a8d12016-07-08 12:30:39 +0200908 #print "DONE :-)", server
ahmadsaf853d452016-12-22 11:33:47 +0500909 pool_id = None
910 floating_ips = self.neutron.list_floatingips().get("floatingips", ())
911 for floating_network in external_network:
tiernof8383b82017-01-18 15:49:48 +0100912 try:
913 # wait until vm is active
914 elapsed_time = 0
915 while elapsed_time < server_timeout:
916 status = self.nova.servers.get(server.id).status
917 if status == 'ACTIVE':
918 break
919 time.sleep(1)
920 elapsed_time += 1
montesmoreno2a1fc4e2017-01-09 16:46:04 +0000921
tiernof8383b82017-01-18 15:49:48 +0100922 #if we exceeded the timeout rollback
923 if elapsed_time >= server_timeout:
924 raise vimconn.vimconnException('Timeout creating instance ' + name,
925 http_code=vimconn.HTTP_Request_Timeout)
montesmoreno2a1fc4e2017-01-09 16:46:04 +0000926
tiernof8383b82017-01-18 15:49:48 +0100927 assigned = False
928 while(assigned == False):
929 if floating_ips:
930 ip = floating_ips.pop(0)
931 if not ip.get("port_id", False) and ip.get('tenant_id') == server.tenant_id:
932 free_floating_ip = ip.get("floating_ip_address")
933 try:
934 fix_ip = floating_network.get('ip')
935 server.add_floating_ip(free_floating_ip, fix_ip)
936 assigned = True
937 except Exception as e:
938 raise vimconn.vimconnException(type(e).__name__ + ": Cannot create floating_ip "+ str(e), http_code=vimconn.HTTP_Conflict)
939 else:
940 #Find the external network
941 external_nets = list()
942 for net in self.neutron.list_networks()['networks']:
943 if net['router:external']:
944 external_nets.append(net)
945
946 if len(external_nets) == 0:
947 raise vimconn.vimconnException("Cannot create floating_ip automatically since no external "
948 "network is present",
949 http_code=vimconn.HTTP_Conflict)
950 if len(external_nets) > 1:
951 raise vimconn.vimconnException("Cannot create floating_ip automatically since multiple "
952 "external networks are present",
953 http_code=vimconn.HTTP_Conflict)
954
955 pool_id = external_nets[0].get('id')
956 param = {'floatingip': {'floating_network_id': pool_id, 'tenant_id': server.tenant_id}}
ahmadsaf853d452016-12-22 11:33:47 +0500957 try:
tiernof8383b82017-01-18 15:49:48 +0100958 #self.logger.debug("Creating floating IP")
959 new_floating_ip = self.neutron.create_floatingip(param)
960 free_floating_ip = new_floating_ip['floatingip']['floating_ip_address']
ahmadsaf853d452016-12-22 11:33:47 +0500961 fix_ip = floating_network.get('ip')
962 server.add_floating_ip(free_floating_ip, fix_ip)
tiernof8383b82017-01-18 15:49:48 +0100963 assigned=True
ahmadsaf853d452016-12-22 11:33:47 +0500964 except Exception as e:
tiernof8383b82017-01-18 15:49:48 +0100965 raise vimconn.vimconnException(type(e).__name__ + ": Cannot assign floating_ip "+ str(e), http_code=vimconn.HTTP_Conflict)
966 except Exception as e:
967 if not floating_network['exit_on_floating_ip_error']:
968 self.logger.warn("Cannot create floating_ip. %s", str(e))
969 continue
970 self.delete_vminstance(server.id)
971 raise
montesmoreno2a1fc4e2017-01-09 16:46:04 +0000972
tiernoae4a8d12016-07-08 12:30:39 +0200973 return server.id
tierno7edb6752016-03-21 17:37:52 +0100974# except nvExceptions.NotFound as e:
975# error_value=-vimconn.HTTP_Not_Found
976# error_text= "vm instance %s not found" % vm_id
tiernof8383b82017-01-18 15:49:48 +0100977 except (ksExceptions.ClientException, nvExceptions.ClientException, ConnectionError) as e:
montesmoreno2a1fc4e2017-01-09 16:46:04 +0000978 # delete the volumes we just created
979 if block_device_mapping != None:
980 for volume_id in block_device_mapping.itervalues():
981 self.cinder.volumes.delete(volume_id)
982
983 # delete ports we just created
984 for net_item in net_list_vim:
985 if 'port-id' in net_item:
montesmorenocf227142017-01-12 12:24:21 +0000986 self.neutron.delete_port(net_item['port-id'])
tiernoae4a8d12016-07-08 12:30:39 +0200987 self._format_exception(e)
988 except TypeError as e:
989 raise vimconn.vimconnException(type(e).__name__ + ": "+ str(e), http_code=vimconn.HTTP_Bad_Request)
tierno7edb6752016-03-21 17:37:52 +0100990
tiernoae4a8d12016-07-08 12:30:39 +0200991 def get_vminstance(self,vm_id):
tierno7edb6752016-03-21 17:37:52 +0100992 '''Returns the VM instance information from VIM'''
tiernoae4a8d12016-07-08 12:30:39 +0200993 #self.logger.debug("Getting VM from VIM")
tierno7edb6752016-03-21 17:37:52 +0100994 try:
995 self._reload_connection()
996 server = self.nova.servers.find(id=vm_id)
997 #TODO parse input and translate to VIM format (openmano_schemas.new_vminstance_response_schema)
tiernoae4a8d12016-07-08 12:30:39 +0200998 return server.to_dict()
tierno8e995ce2016-09-22 08:13:00 +0000999 except (ksExceptions.ClientException, nvExceptions.ClientException, nvExceptions.NotFound, ConnectionError) as e:
tiernoae4a8d12016-07-08 12:30:39 +02001000 self._format_exception(e)
1001
1002 def get_vminstance_console(self,vm_id, console_type="vnc"):
tierno7edb6752016-03-21 17:37:52 +01001003 '''
1004 Get a console for the virtual machine
1005 Params:
1006 vm_id: uuid of the VM
1007 console_type, can be:
1008 "novnc" (by default), "xvpvnc" for VNC types,
1009 "rdp-html5" for RDP types, "spice-html5" for SPICE types
tiernoae4a8d12016-07-08 12:30:39 +02001010 Returns dict with the console parameters:
1011 protocol: ssh, ftp, http, https, ...
1012 server: usually ip address
1013 port: the http, ssh, ... port
1014 suffix: extra text, e.g. the http path and query string
tierno7edb6752016-03-21 17:37:52 +01001015 '''
tiernoae4a8d12016-07-08 12:30:39 +02001016 self.logger.debug("Getting VM CONSOLE from VIM")
tierno7edb6752016-03-21 17:37:52 +01001017 try:
1018 self._reload_connection()
1019 server = self.nova.servers.find(id=vm_id)
1020 if console_type == None or console_type == "novnc":
1021 console_dict = server.get_vnc_console("novnc")
1022 elif console_type == "xvpvnc":
1023 console_dict = server.get_vnc_console(console_type)
1024 elif console_type == "rdp-html5":
1025 console_dict = server.get_rdp_console(console_type)
1026 elif console_type == "spice-html5":
1027 console_dict = server.get_spice_console(console_type)
1028 else:
tiernoae4a8d12016-07-08 12:30:39 +02001029 raise vimconn.vimconnException("console type '{}' not allowed".format(console_type), http_code=vimconn.HTTP_Bad_Request)
tierno7edb6752016-03-21 17:37:52 +01001030
1031 console_dict1 = console_dict.get("console")
1032 if console_dict1:
1033 console_url = console_dict1.get("url")
1034 if console_url:
1035 #parse console_url
1036 protocol_index = console_url.find("//")
1037 suffix_index = console_url[protocol_index+2:].find("/") + protocol_index+2
1038 port_index = console_url[protocol_index+2:suffix_index].find(":") + protocol_index+2
1039 if protocol_index < 0 or port_index<0 or suffix_index<0:
1040 return -vimconn.HTTP_Internal_Server_Error, "Unexpected response from VIM"
1041 console_dict={"protocol": console_url[0:protocol_index],
1042 "server": console_url[protocol_index+2:port_index],
1043 "port": console_url[port_index:suffix_index],
1044 "suffix": console_url[suffix_index+1:]
1045 }
1046 protocol_index += 2
tiernoae4a8d12016-07-08 12:30:39 +02001047 return console_dict
1048 raise vimconn.vimconnUnexpectedResponse("Unexpected response from VIM")
tierno7edb6752016-03-21 17:37:52 +01001049
tierno8e995ce2016-09-22 08:13:00 +00001050 except (nvExceptions.NotFound, ksExceptions.ClientException, nvExceptions.ClientException, nvExceptions.BadRequest, ConnectionError) as e:
tiernoae4a8d12016-07-08 12:30:39 +02001051 self._format_exception(e)
tierno7edb6752016-03-21 17:37:52 +01001052
tiernoae4a8d12016-07-08 12:30:39 +02001053 def delete_vminstance(self, vm_id):
1054 '''Removes a VM instance from VIM. Returns the old identifier
tierno7edb6752016-03-21 17:37:52 +01001055 '''
tiernoae4a8d12016-07-08 12:30:39 +02001056 #print "osconnector: Getting VM from VIM"
tierno7edb6752016-03-21 17:37:52 +01001057 try:
1058 self._reload_connection()
1059 #delete VM ports attached to this networks before the virtual machine
1060 ports = self.neutron.list_ports(device_id=vm_id)
1061 for p in ports['ports']:
1062 try:
1063 self.neutron.delete_port(p["id"])
1064 except Exception as e:
tiernoae4a8d12016-07-08 12:30:39 +02001065 self.logger.error("Error deleting port: " + type(e).__name__ + ": "+ str(e))
montesmoreno0c8def02016-12-22 12:16:23 +00001066
1067 #commented because detaching the volumes makes the servers.delete not work properly ?!?
1068 #dettach volumes attached
1069 server = self.nova.servers.get(vm_id)
1070 volumes_attached_dict = server._info['os-extended-volumes:volumes_attached']
1071 #for volume in volumes_attached_dict:
1072 # self.cinder.volumes.detach(volume['id'])
1073
tierno7edb6752016-03-21 17:37:52 +01001074 self.nova.servers.delete(vm_id)
montesmoreno0c8def02016-12-22 12:16:23 +00001075
1076 #delete volumes.
1077 #Although having detached them should have them in active status
1078 #we ensure in this loop
1079 keep_waiting = True
1080 elapsed_time = 0
1081 while keep_waiting and elapsed_time < volume_timeout:
1082 keep_waiting = False
1083 for volume in volumes_attached_dict:
1084 if self.cinder.volumes.get(volume['id']).status != 'available':
1085 keep_waiting = True
1086 else:
1087 self.cinder.volumes.delete(volume['id'])
1088 if keep_waiting:
1089 time.sleep(1)
1090 elapsed_time += 1
1091
tiernoae4a8d12016-07-08 12:30:39 +02001092 return vm_id
tierno8e995ce2016-09-22 08:13:00 +00001093 except (nvExceptions.NotFound, ksExceptions.ClientException, nvExceptions.ClientException, ConnectionError) as e:
tiernoae4a8d12016-07-08 12:30:39 +02001094 self._format_exception(e)
tierno7edb6752016-03-21 17:37:52 +01001095 #TODO insert exception vimconn.HTTP_Unauthorized
1096 #if reaching here is because an exception
tierno7edb6752016-03-21 17:37:52 +01001097
tiernoae4a8d12016-07-08 12:30:39 +02001098 def refresh_vms_status(self, vm_list):
1099 '''Get the status of the virtual machines and their interfaces/ports
1100 Params: the list of VM identifiers
1101 Returns a dictionary with:
1102 vm_id: #VIM id of this Virtual Machine
1103 status: #Mandatory. Text with one of:
1104 # DELETED (not found at vim)
1105 # VIM_ERROR (Cannot connect to VIM, VIM response error, ...)
1106 # OTHER (Vim reported other status not understood)
1107 # ERROR (VIM indicates an ERROR status)
1108 # ACTIVE, PAUSED, SUSPENDED, INACTIVE (not running),
1109 # CREATING (on building process), ERROR
1110 # ACTIVE:NoMgmtIP (Active but any of its interface has an IP address
1111 #
1112 error_msg: #Text with VIM error message, if any. Or the VIM connection ERROR
1113 vim_info: #Text with plain information obtained from vim (yaml.safe_dump)
1114 interfaces:
1115 - vim_info: #Text with plain information obtained from vim (yaml.safe_dump)
1116 mac_address: #Text format XX:XX:XX:XX:XX:XX
1117 vim_net_id: #network id where this interface is connected
1118 vim_interface_id: #interface/port VIM id
1119 ip_address: #null, or text with IPv4, IPv6 address
tierno867ffe92017-03-27 12:50:34 +02001120 compute_node: #identification of compute node where PF,VF interface is allocated
1121 pci: #PCI address of the NIC that hosts the PF,VF
1122 vlan: #physical VLAN used for VF
tierno7edb6752016-03-21 17:37:52 +01001123 '''
tiernoae4a8d12016-07-08 12:30:39 +02001124 vm_dict={}
1125 self.logger.debug("refresh_vms status: Getting tenant VM instance information from VIM")
1126 for vm_id in vm_list:
1127 vm={}
1128 try:
1129 vm_vim = self.get_vminstance(vm_id)
1130 if vm_vim['status'] in vmStatus2manoFormat:
1131 vm['status'] = vmStatus2manoFormat[ vm_vim['status'] ]
tierno7edb6752016-03-21 17:37:52 +01001132 else:
tiernoae4a8d12016-07-08 12:30:39 +02001133 vm['status'] = "OTHER"
1134 vm['error_msg'] = "VIM status reported " + vm_vim['status']
tierno8e995ce2016-09-22 08:13:00 +00001135 try:
1136 vm['vim_info'] = yaml.safe_dump(vm_vim, default_flow_style=True, width=256)
1137 except yaml.representer.RepresenterError:
1138 vm['vim_info'] = str(vm_vim)
tiernoae4a8d12016-07-08 12:30:39 +02001139 vm["interfaces"] = []
1140 if vm_vim.get('fault'):
1141 vm['error_msg'] = str(vm_vim['fault'])
1142 #get interfaces
tierno7edb6752016-03-21 17:37:52 +01001143 try:
tiernoae4a8d12016-07-08 12:30:39 +02001144 self._reload_connection()
1145 port_dict=self.neutron.list_ports(device_id=vm_id)
1146 for port in port_dict["ports"]:
1147 interface={}
tierno8e995ce2016-09-22 08:13:00 +00001148 try:
1149 interface['vim_info'] = yaml.safe_dump(port, default_flow_style=True, width=256)
1150 except yaml.representer.RepresenterError:
1151 interface['vim_info'] = str(port)
tiernoae4a8d12016-07-08 12:30:39 +02001152 interface["mac_address"] = port.get("mac_address")
1153 interface["vim_net_id"] = port["network_id"]
1154 interface["vim_interface_id"] = port["id"]
Mike Marchetti5b9da422017-05-02 15:35:47 -04001155 # check if OS-EXT-SRV-ATTR:host is there,
1156 # in case of non-admin credentials, it will be missing
1157 if vm_vim.get('OS-EXT-SRV-ATTR:host'):
1158 interface["compute_node"] = vm_vim['OS-EXT-SRV-ATTR:host']
tierno867ffe92017-03-27 12:50:34 +02001159 interface["pci"] = None
Mike Marchetti5b9da422017-05-02 15:35:47 -04001160
1161 # check if binding:profile is there,
1162 # in case of non-admin credentials, it will be missing
1163 if port.get('binding:profile'):
1164 if port['binding:profile'].get('pci_slot'):
1165 # TODO: At the moment sr-iov pci addresses are converted to PF pci addresses by setting the slot to 0x00
1166 # TODO: This is just a workaround valid for niantinc. Find a better way to do so
1167 # CHANGE DDDD:BB:SS.F to DDDD:BB:00.(F%2) assuming there are 2 ports per nic
1168 pci = port['binding:profile']['pci_slot']
1169 # interface["pci"] = pci[:-4] + "00." + str(int(pci[-1]) % 2)
1170 interface["pci"] = pci
tierno867ffe92017-03-27 12:50:34 +02001171 interface["vlan"] = None
Pablo Montes Moreno3be0b2a2017-03-30 13:22:15 +02001172 #if network is of type vlan and port is of type direct (sr-iov) then set vlan id
Pablo Montes Moreno51e553b2017-03-23 16:39:12 +01001173 network = self.neutron.show_network(port["network_id"])
Pablo Montes Moreno3be0b2a2017-03-30 13:22:15 +02001174 if network['network'].get('provider:network_type') == 'vlan' and \
1175 port.get("binding:vnic_type") == "direct":
tierno867ffe92017-03-27 12:50:34 +02001176 interface["vlan"] = network['network'].get('provider:segmentation_id')
tiernoae4a8d12016-07-08 12:30:39 +02001177 ips=[]
1178 #look for floating ip address
1179 floating_ip_dict = self.neutron.list_floatingips(port_id=port["id"])
1180 if floating_ip_dict.get("floatingips"):
1181 ips.append(floating_ip_dict["floatingips"][0].get("floating_ip_address") )
tierno7edb6752016-03-21 17:37:52 +01001182
tiernoae4a8d12016-07-08 12:30:39 +02001183 for subnet in port["fixed_ips"]:
1184 ips.append(subnet["ip_address"])
1185 interface["ip_address"] = ";".join(ips)
1186 vm["interfaces"].append(interface)
1187 except Exception as e:
1188 self.logger.error("Error getting vm interface information " + type(e).__name__ + ": "+ str(e))
1189 except vimconn.vimconnNotFoundException as e:
1190 self.logger.error("Exception getting vm status: %s", str(e))
1191 vm['status'] = "DELETED"
1192 vm['error_msg'] = str(e)
1193 except vimconn.vimconnException as e:
1194 self.logger.error("Exception getting vm status: %s", str(e))
1195 vm['status'] = "VIM_ERROR"
1196 vm['error_msg'] = str(e)
1197 vm_dict[vm_id] = vm
1198 return vm_dict
tierno7edb6752016-03-21 17:37:52 +01001199
tiernoae4a8d12016-07-08 12:30:39 +02001200 def action_vminstance(self, vm_id, action_dict):
tierno7edb6752016-03-21 17:37:52 +01001201 '''Send and action over a VM instance from VIM
tiernoae4a8d12016-07-08 12:30:39 +02001202 Returns the vm_id if the action was successfully sent to the VIM'''
1203 self.logger.debug("Action over VM '%s': %s", vm_id, str(action_dict))
tierno7edb6752016-03-21 17:37:52 +01001204 try:
1205 self._reload_connection()
1206 server = self.nova.servers.find(id=vm_id)
1207 if "start" in action_dict:
1208 if action_dict["start"]=="rebuild":
1209 server.rebuild()
1210 else:
1211 if server.status=="PAUSED":
1212 server.unpause()
1213 elif server.status=="SUSPENDED":
1214 server.resume()
1215 elif server.status=="SHUTOFF":
1216 server.start()
1217 elif "pause" in action_dict:
1218 server.pause()
1219 elif "resume" in action_dict:
1220 server.resume()
1221 elif "shutoff" in action_dict or "shutdown" in action_dict:
1222 server.stop()
1223 elif "forceOff" in action_dict:
1224 server.stop() #TODO
1225 elif "terminate" in action_dict:
1226 server.delete()
1227 elif "createImage" in action_dict:
1228 server.create_image()
1229 #"path":path_schema,
1230 #"description":description_schema,
1231 #"name":name_schema,
1232 #"metadata":metadata_schema,
1233 #"imageRef": id_schema,
1234 #"disk": {"oneOf":[{"type": "null"}, {"type":"string"}] },
1235 elif "rebuild" in action_dict:
1236 server.rebuild(server.image['id'])
1237 elif "reboot" in action_dict:
1238 server.reboot() #reboot_type='SOFT'
1239 elif "console" in action_dict:
1240 console_type = action_dict["console"]
1241 if console_type == None or console_type == "novnc":
1242 console_dict = server.get_vnc_console("novnc")
1243 elif console_type == "xvpvnc":
1244 console_dict = server.get_vnc_console(console_type)
1245 elif console_type == "rdp-html5":
1246 console_dict = server.get_rdp_console(console_type)
1247 elif console_type == "spice-html5":
1248 console_dict = server.get_spice_console(console_type)
1249 else:
tiernoae4a8d12016-07-08 12:30:39 +02001250 raise vimconn.vimconnException("console type '{}' not allowed".format(console_type),
1251 http_code=vimconn.HTTP_Bad_Request)
tierno7edb6752016-03-21 17:37:52 +01001252 try:
1253 console_url = console_dict["console"]["url"]
1254 #parse console_url
1255 protocol_index = console_url.find("//")
1256 suffix_index = console_url[protocol_index+2:].find("/") + protocol_index+2
1257 port_index = console_url[protocol_index+2:suffix_index].find(":") + protocol_index+2
1258 if protocol_index < 0 or port_index<0 or suffix_index<0:
tiernoae4a8d12016-07-08 12:30:39 +02001259 raise vimconn.vimconnException("Unexpected response from VIM " + str(console_dict))
tierno7edb6752016-03-21 17:37:52 +01001260 console_dict2={"protocol": console_url[0:protocol_index],
1261 "server": console_url[protocol_index+2 : port_index],
1262 "port": int(console_url[port_index+1 : suffix_index]),
1263 "suffix": console_url[suffix_index+1:]
1264 }
tiernoae4a8d12016-07-08 12:30:39 +02001265 return console_dict2
1266 except Exception as e:
1267 raise vimconn.vimconnException("Unexpected response from VIM " + str(console_dict))
tierno7edb6752016-03-21 17:37:52 +01001268
tiernoae4a8d12016-07-08 12:30:39 +02001269 return vm_id
tierno8e995ce2016-09-22 08:13:00 +00001270 except (ksExceptions.ClientException, nvExceptions.ClientException, nvExceptions.NotFound, ConnectionError) as e:
tiernoae4a8d12016-07-08 12:30:39 +02001271 self._format_exception(e)
1272 #TODO insert exception vimconn.HTTP_Unauthorized
1273
1274#NOT USED FUNCTIONS
1275
1276 def new_external_port(self, port_data):
1277 #TODO openstack if needed
1278 '''Adds a external port to VIM'''
1279 '''Returns the port identifier'''
1280 return -vimconn.HTTP_Internal_Server_Error, "osconnector.new_external_port() not implemented"
1281
1282 def connect_port_network(self, port_id, network_id, admin=False):
1283 #TODO openstack if needed
1284 '''Connects a external port to a network'''
1285 '''Returns status code of the VIM response'''
1286 return -vimconn.HTTP_Internal_Server_Error, "osconnector.connect_port_network() not implemented"
1287
1288 def new_user(self, user_name, user_passwd, tenant_id=None):
1289 '''Adds a new user to openstack VIM'''
1290 '''Returns the user identifier'''
1291 self.logger.debug("osconnector: Adding a new user to VIM")
1292 try:
1293 self._reload_connection()
1294 user=self.keystone.users.create(user_name, user_passwd, tenant_id=tenant_id)
1295 #self.keystone.tenants.add_user(self.k_creds["username"], #role)
1296 return user.id
1297 except ksExceptions.ConnectionError as e:
1298 error_value=-vimconn.HTTP_Bad_Request
1299 error_text= type(e).__name__ + ": "+ (str(e) if len(e.args)==0 else str(e.args[0]))
1300 except ksExceptions.ClientException as e: #TODO remove
tierno7edb6752016-03-21 17:37:52 +01001301 error_value=-vimconn.HTTP_Bad_Request
1302 error_text= type(e).__name__ + ": "+ (str(e) if len(e.args)==0 else str(e.args[0]))
1303 #TODO insert exception vimconn.HTTP_Unauthorized
1304 #if reaching here is because an exception
1305 if self.debug:
tiernoae4a8d12016-07-08 12:30:39 +02001306 self.logger.debug("new_user " + error_text)
tierno7edb6752016-03-21 17:37:52 +01001307 return error_value, error_text
tiernoae4a8d12016-07-08 12:30:39 +02001308
1309 def delete_user(self, user_id):
1310 '''Delete a user from openstack VIM'''
1311 '''Returns the user identifier'''
1312 if self.debug:
1313 print "osconnector: Deleting a user from VIM"
1314 try:
1315 self._reload_connection()
1316 self.keystone.users.delete(user_id)
1317 return 1, user_id
1318 except ksExceptions.ConnectionError as e:
1319 error_value=-vimconn.HTTP_Bad_Request
1320 error_text= type(e).__name__ + ": "+ (str(e) if len(e.args)==0 else str(e.args[0]))
1321 except ksExceptions.NotFound as e:
1322 error_value=-vimconn.HTTP_Not_Found
1323 error_text= type(e).__name__ + ": "+ (str(e) if len(e.args)==0 else str(e.args[0]))
1324 except ksExceptions.ClientException as e: #TODO remove
1325 error_value=-vimconn.HTTP_Bad_Request
1326 error_text= type(e).__name__ + ": "+ (str(e) if len(e.args)==0 else str(e.args[0]))
1327 #TODO insert exception vimconn.HTTP_Unauthorized
1328 #if reaching here is because an exception
1329 if self.debug:
1330 print "delete_tenant " + error_text
1331 return error_value, error_text
1332
tierno7edb6752016-03-21 17:37:52 +01001333 def get_hosts_info(self):
1334 '''Get the information of deployed hosts
1335 Returns the hosts content'''
1336 if self.debug:
1337 print "osconnector: Getting Host info from VIM"
1338 try:
1339 h_list=[]
1340 self._reload_connection()
1341 hypervisors = self.nova.hypervisors.list()
1342 for hype in hypervisors:
1343 h_list.append( hype.to_dict() )
1344 return 1, {"hosts":h_list}
1345 except nvExceptions.NotFound as e:
1346 error_value=-vimconn.HTTP_Not_Found
1347 error_text= (str(e) if len(e.args)==0 else str(e.args[0]))
1348 except (ksExceptions.ClientException, nvExceptions.ClientException) as e:
1349 error_value=-vimconn.HTTP_Bad_Request
1350 error_text= type(e).__name__ + ": "+ (str(e) if len(e.args)==0 else str(e.args[0]))
1351 #TODO insert exception vimconn.HTTP_Unauthorized
1352 #if reaching here is because an exception
1353 if self.debug:
1354 print "get_hosts_info " + error_text
1355 return error_value, error_text
1356
1357 def get_hosts(self, vim_tenant):
1358 '''Get the hosts and deployed instances
1359 Returns the hosts content'''
1360 r, hype_dict = self.get_hosts_info()
1361 if r<0:
1362 return r, hype_dict
1363 hypervisors = hype_dict["hosts"]
1364 try:
1365 servers = self.nova.servers.list()
1366 for hype in hypervisors:
1367 for server in servers:
1368 if server.to_dict()['OS-EXT-SRV-ATTR:hypervisor_hostname']==hype['hypervisor_hostname']:
1369 if 'vm' in hype:
1370 hype['vm'].append(server.id)
1371 else:
1372 hype['vm'] = [server.id]
1373 return 1, hype_dict
1374 except nvExceptions.NotFound as e:
1375 error_value=-vimconn.HTTP_Not_Found
1376 error_text= (str(e) if len(e.args)==0 else str(e.args[0]))
1377 except (ksExceptions.ClientException, nvExceptions.ClientException) as e:
1378 error_value=-vimconn.HTTP_Bad_Request
1379 error_text= type(e).__name__ + ": "+ (str(e) if len(e.args)==0 else str(e.args[0]))
1380 #TODO insert exception vimconn.HTTP_Unauthorized
1381 #if reaching here is because an exception
1382 if self.debug:
1383 print "get_hosts " + error_text
1384 return error_value, error_text
1385
tierno7edb6752016-03-21 17:37:52 +01001386