blob: 2bce6cb86685f2447ba5786c98f5e956a76ff9d0 [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
tiernob5cef372017-06-19 15:52:22 +020039from novaclient import client as nClient, exceptions as nvExceptions
40from keystoneauth1.identity import v2, v3
41from keystoneauth1 import session
tierno7edb6752016-03-21 17:37:52 +010042import keystoneclient.exceptions as ksExceptions
tiernof716aea2017-06-21 18:01:40 +020043import keystoneclient.v3.client as ksClient_v3
44import keystoneclient.v2_0.client as ksClient_v2
tiernob5cef372017-06-19 15:52:22 +020045from glanceclient import client as glClient
tierno7edb6752016-03-21 17:37:52 +010046import glanceclient.client as gl1Client
47import glanceclient.exc as gl1Exceptions
tiernob5cef372017-06-19 15:52:22 +020048from cinderclient import client as cClient
tierno7edb6752016-03-21 17:37:52 +010049from httplib import HTTPException
tiernob5cef372017-06-19 15:52:22 +020050from neutronclient.neutron import client as neClient
tierno7edb6752016-03-21 17:37:52 +010051from neutronclient.common import exceptions as neExceptions
52from requests.exceptions import ConnectionError
53
54'''contain the openstack virtual machine status to openmano status'''
55vmStatus2manoFormat={'ACTIVE':'ACTIVE',
56 'PAUSED':'PAUSED',
57 'SUSPENDED': 'SUSPENDED',
58 'SHUTOFF':'INACTIVE',
59 'BUILD':'BUILD',
60 'ERROR':'ERROR','DELETED':'DELETED'
61 }
62netStatus2manoFormat={'ACTIVE':'ACTIVE','PAUSED':'PAUSED','INACTIVE':'INACTIVE','BUILD':'BUILD','ERROR':'ERROR','DELETED':'DELETED'
63 }
64
montesmoreno0c8def02016-12-22 12:16:23 +000065#global var to have a timeout creating and deleting volumes
66volume_timeout = 60
montesmoreno2a1fc4e2017-01-09 16:46:04 +000067server_timeout = 60
montesmoreno0c8def02016-12-22 12:16:23 +000068
tierno7edb6752016-03-21 17:37:52 +010069class vimconnector(vimconn.vimconnector):
tiernob3d36742017-03-03 23:51:05 +010070 def __init__(self, uuid, name, tenant_id, tenant_name, url, url_admin=None, user=None, passwd=None,
71 log_level=None, config={}, persistent_info={}):
ahmadsa96af9f42017-01-31 16:17:14 +050072 '''using common constructor parameters. In this case
tierno7edb6752016-03-21 17:37:52 +010073 'url' is the keystone authorization url,
74 'url_admin' is not use
75 '''
tiernof716aea2017-06-21 18:01:40 +020076 api_version = config.get('APIversion')
77 if api_version and api_version not in ('v3.3', 'v2.0', '2', '3'):
tiernob5cef372017-06-19 15:52:22 +020078 raise vimconn.vimconnException("Invalid value '{}' for config:APIversion. "
tiernof716aea2017-06-21 18:01:40 +020079 "Allowed values are 'v3.3', 'v2.0', '2' or '3'".format(api_version))
tiernob5cef372017-06-19 15:52:22 +020080 vimconn.vimconnector.__init__(self, uuid, name, tenant_id, tenant_name, url, url_admin, user, passwd, log_level,
81 config)
tiernob3d36742017-03-03 23:51:05 +010082
tiernob5cef372017-06-19 15:52:22 +020083 self.insecure = self.config.get("insecure", False)
tierno7edb6752016-03-21 17:37:52 +010084 if not url:
85 raise TypeError, 'url param can not be NoneType'
tiernob5cef372017-06-19 15:52:22 +020086 self.persistent_info = persistent_info
87 self.session = persistent_info.get('session', {'reload_client': True})
88 self.nova = self.session.get('nova')
89 self.neutron = self.session.get('neutron')
90 self.cinder = self.session.get('cinder')
91 self.glance = self.session.get('glance')
tiernof716aea2017-06-21 18:01:40 +020092 self.keystone = self.session.get('keystone')
93 self.api_version3 = self.session.get('api_version3')
montesmoreno0c8def02016-12-22 12:16:23 +000094
tierno73ad9e42016-09-12 18:11:11 +020095 self.logger = logging.getLogger('openmano.vim.openstack')
tiernofe789902016-09-29 14:20:44 +000096 if log_level:
97 self.logger.setLevel( getattr(logging, log_level) )
tiernof716aea2017-06-21 18:01:40 +020098
99 def __getitem__(self, index):
100 """Get individuals parameters.
101 Throw KeyError"""
102 if index == 'project_domain_id':
103 return self.config.get("project_domain_id")
104 elif index == 'user_domain_id':
105 return self.config.get("user_domain_id")
106 else:
tierno76a3c312017-06-29 16:42:15 +0200107 return vimconn.vimconnector.__getitem__(self, index)
tiernof716aea2017-06-21 18:01:40 +0200108
109 def __setitem__(self, index, value):
110 """Set individuals parameters and it is marked as dirty so to force connection reload.
111 Throw KeyError"""
112 if index == 'project_domain_id':
113 self.config["project_domain_id"] = value
114 elif index == 'user_domain_id':
115 self.config["user_domain_id"] = value
116 else:
117 vimconn.vimconnector.__setitem__(self, index, value)
tiernob5cef372017-06-19 15:52:22 +0200118 self.session['reload_client'] = True
tiernof716aea2017-06-21 18:01:40 +0200119
tierno7edb6752016-03-21 17:37:52 +0100120 def _reload_connection(self):
121 '''Called before any operation, it check if credentials has changed
122 Throw keystoneclient.apiclient.exceptions.AuthorizationFailure
123 '''
124 #TODO control the timing and possible token timeout, but it seams that python client does this task for us :-)
tiernob5cef372017-06-19 15:52:22 +0200125 if self.session['reload_client']:
tiernof716aea2017-06-21 18:01:40 +0200126 if self.config.get('APIversion'):
127 self.api_version3 = self.config['APIversion'] == 'v3.3' or self.config['APIversion'] == '3'
128 else: # get from ending auth_url that end with v3 or with v2.0
129 self.api_version3 = self.url.split("/")[-1] == "v3"
130 self.session['api_version3'] = self.api_version3
131 if self.api_version3:
132 auth = v3.Password(auth_url=self.url,
tiernob5cef372017-06-19 15:52:22 +0200133 username=self.user,
134 password=self.passwd,
135 project_name=self.tenant_name,
136 project_id=self.tenant_id,
137 project_domain_id=self.config.get('project_domain_id', 'default'),
138 user_domain_id=self.config.get('user_domain_id', 'default'))
ahmadsa95baa272016-11-30 09:14:11 +0500139 else:
tiernof716aea2017-06-21 18:01:40 +0200140 auth = v2.Password(auth_url=self.url,
tiernob5cef372017-06-19 15:52:22 +0200141 username=self.user,
142 password=self.passwd,
143 tenant_name=self.tenant_name,
144 tenant_id=self.tenant_id)
145 sess = session.Session(auth=auth, verify=not self.insecure)
tiernof716aea2017-06-21 18:01:40 +0200146 if self.api_version3:
147 self.keystone = ksClient_v3.Client(session=sess)
148 else:
149 self.keystone = ksClient_v2.Client(session=sess)
150 self.session['keystone'] = self.keystone
tiernob5cef372017-06-19 15:52:22 +0200151 self.nova = self.session['nova'] = nClient.Client("2.1", session=sess)
152 self.neutron = self.session['neutron'] = neClient.Client('2.0', session=sess)
153 self.cinder = self.session['cinder'] = cClient.Client(2, session=sess)
154 self.glance = self.session['glance'] = glClient.Client(2, session=sess)
155 self.session['reload_client'] = False
156 self.persistent_info['session'] = self.session
ahmadsa95baa272016-11-30 09:14:11 +0500157
tierno7edb6752016-03-21 17:37:52 +0100158 def __net_os2mano(self, net_list_dict):
159 '''Transform the net openstack format to mano format
160 net_list_dict can be a list of dict or a single dict'''
161 if type(net_list_dict) is dict:
162 net_list_=(net_list_dict,)
163 elif type(net_list_dict) is list:
164 net_list_=net_list_dict
165 else:
166 raise TypeError("param net_list_dict must be a list or a dictionary")
167 for net in net_list_:
168 if net.get('provider:network_type') == "vlan":
169 net['type']='data'
170 else:
171 net['type']='bridge'
tiernoae4a8d12016-07-08 12:30:39 +0200172
173
174
175 def _format_exception(self, exception):
176 '''Transform a keystone, nova, neutron exception into a vimconn exception'''
177 if isinstance(exception, (HTTPException, gl1Exceptions.HTTPException, gl1Exceptions.CommunicationError,
tierno8e995ce2016-09-22 08:13:00 +0000178 ConnectionError, ksExceptions.ConnectionError, neExceptions.ConnectionFailed
179 )):
tiernoae4a8d12016-07-08 12:30:39 +0200180 raise vimconn.vimconnConnectionException(type(exception).__name__ + ": " + str(exception))
181 elif isinstance(exception, (nvExceptions.ClientException, ksExceptions.ClientException,
182 neExceptions.NeutronException, nvExceptions.BadRequest)):
183 raise vimconn.vimconnUnexpectedResponse(type(exception).__name__ + ": " + str(exception))
184 elif isinstance(exception, (neExceptions.NetworkNotFoundClient, nvExceptions.NotFound)):
185 raise vimconn.vimconnNotFoundException(type(exception).__name__ + ": " + str(exception))
186 elif isinstance(exception, nvExceptions.Conflict):
187 raise vimconn.vimconnConflictException(type(exception).__name__ + ": " + str(exception))
tiernof716aea2017-06-21 18:01:40 +0200188 else: # ()
tiernoae4a8d12016-07-08 12:30:39 +0200189 raise vimconn.vimconnConnectionException(type(exception).__name__ + ": " + str(exception))
190
191 def get_tenant_list(self, filter_dict={}):
192 '''Obtain tenants of VIM
193 filter_dict can contain the following keys:
194 name: filter by tenant name
195 id: filter by tenant uuid/id
196 <other VIM specific>
197 Returns the tenant list of dictionaries: [{'name':'<name>, 'id':'<id>, ...}, ...]
198 '''
ahmadsa95baa272016-11-30 09:14:11 +0500199 self.logger.debug("Getting tenants from VIM filter: '%s'", str(filter_dict))
tiernoae4a8d12016-07-08 12:30:39 +0200200 try:
201 self._reload_connection()
tiernof716aea2017-06-21 18:01:40 +0200202 if self.api_version3:
203 project_class_list = self.keystone.projects.list(name=filter_dict.get("name"))
ahmadsa95baa272016-11-30 09:14:11 +0500204 else:
tiernof716aea2017-06-21 18:01:40 +0200205 project_class_list = self.keystone.tenants.findall(**filter_dict)
ahmadsa95baa272016-11-30 09:14:11 +0500206 project_list=[]
207 for project in project_class_list:
tiernof716aea2017-06-21 18:01:40 +0200208 if filter_dict.get('id') and filter_dict["id"] != project.id:
209 continue
ahmadsa95baa272016-11-30 09:14:11 +0500210 project_list.append(project.to_dict())
211 return project_list
tiernof716aea2017-06-21 18:01:40 +0200212 except (ksExceptions.ConnectionError, ksExceptions.ClientException, ConnectionError) as e:
tiernoae4a8d12016-07-08 12:30:39 +0200213 self._format_exception(e)
214
215 def new_tenant(self, tenant_name, tenant_description):
216 '''Adds a new tenant to openstack VIM. Returns the tenant identifier'''
217 self.logger.debug("Adding a new tenant name: %s", tenant_name)
218 try:
219 self._reload_connection()
tiernof716aea2017-06-21 18:01:40 +0200220 if self.api_version3:
221 project = self.keystone.projects.create(tenant_name, self.config.get("project_domain_id", "default"),
222 description=tenant_description, is_domain=False)
ahmadsa95baa272016-11-30 09:14:11 +0500223 else:
tiernof716aea2017-06-21 18:01:40 +0200224 project = self.keystone.tenants.create(tenant_name, tenant_description)
ahmadsa95baa272016-11-30 09:14:11 +0500225 return project.id
tierno8e995ce2016-09-22 08:13:00 +0000226 except (ksExceptions.ConnectionError, ksExceptions.ClientException, ConnectionError) as e:
tiernoae4a8d12016-07-08 12:30:39 +0200227 self._format_exception(e)
228
229 def delete_tenant(self, tenant_id):
230 '''Delete a tenant from openstack VIM. Returns the old tenant identifier'''
231 self.logger.debug("Deleting tenant %s from VIM", tenant_id)
232 try:
233 self._reload_connection()
tiernof716aea2017-06-21 18:01:40 +0200234 if self.api_version3:
ahmadsa95baa272016-11-30 09:14:11 +0500235 self.keystone.projects.delete(tenant_id)
236 else:
237 self.keystone.tenants.delete(tenant_id)
tiernoae4a8d12016-07-08 12:30:39 +0200238 return tenant_id
tierno8e995ce2016-09-22 08:13:00 +0000239 except (ksExceptions.ConnectionError, ksExceptions.ClientException, ConnectionError) as e:
tiernoae4a8d12016-07-08 12:30:39 +0200240 self._format_exception(e)
ahmadsa95baa272016-11-30 09:14:11 +0500241
garciadeblas9f8456e2016-09-05 05:02:59 +0200242 def new_network(self,net_name, net_type, ip_profile=None, shared=False, vlan=None):
tiernoae4a8d12016-07-08 12:30:39 +0200243 '''Adds a tenant network to VIM. Returns the network identifier'''
244 self.logger.debug("Adding a new network to VIM name '%s', type '%s'", net_name, net_type)
garciadeblasedca7b32016-09-29 14:01:52 +0000245 #self.logger.debug(">>>>>>>>>>>>>>>>>> IP profile %s", str(ip_profile))
tierno7edb6752016-03-21 17:37:52 +0100246 try:
garciadeblasedca7b32016-09-29 14:01:52 +0000247 new_net = None
tierno7edb6752016-03-21 17:37:52 +0100248 self._reload_connection()
249 network_dict = {'name': net_name, 'admin_state_up': True}
250 if net_type=="data" or net_type=="ptp":
251 if self.config.get('dataplane_physical_net') == None:
tiernoae4a8d12016-07-08 12:30:39 +0200252 raise vimconn.vimconnConflictException("You must provide a 'dataplane_physical_net' at config value before creating sriov network")
tierno7edb6752016-03-21 17:37:52 +0100253 network_dict["provider:physical_network"] = self.config['dataplane_physical_net'] #"physnet_sriov" #TODO physical
254 network_dict["provider:network_type"] = "vlan"
255 if vlan!=None:
256 network_dict["provider:network_type"] = vlan
tiernoae4a8d12016-07-08 12:30:39 +0200257 network_dict["shared"]=shared
tierno7edb6752016-03-21 17:37:52 +0100258 new_net=self.neutron.create_network({'network':network_dict})
259 #print new_net
garciadeblas9f8456e2016-09-05 05:02:59 +0200260 #create subnetwork, even if there is no profile
261 if not ip_profile:
262 ip_profile = {}
263 if 'subnet_address' not in ip_profile:
garciadeblas2299e3b2017-01-26 14:35:55 +0000264 #Fake subnet is required
265 subnet_rand = random.randint(0, 255)
266 ip_profile['subnet_address'] = "192.168.{}.0/24".format(subnet_rand)
garciadeblas9f8456e2016-09-05 05:02:59 +0200267 if 'ip_version' not in ip_profile:
268 ip_profile['ip_version'] = "IPv4"
tiernoa1fb4462017-06-30 12:25:50 +0200269 subnet = {"name":net_name+"-subnet",
tierno7edb6752016-03-21 17:37:52 +0100270 "network_id": new_net["network"]["id"],
garciadeblas9f8456e2016-09-05 05:02:59 +0200271 "ip_version": 4 if ip_profile['ip_version']=="IPv4" else 6,
272 "cidr": ip_profile['subnet_address']
tierno7edb6752016-03-21 17:37:52 +0100273 }
tiernoa1fb4462017-06-30 12:25:50 +0200274 # Gateway should be set to None if not needed. Otherwise openstack assigns one by default
275 subnet['gateway_ip'] = ip_profile.get('gateway_address')
garciadeblasedca7b32016-09-29 14:01:52 +0000276 if ip_profile.get('dns_address'):
tierno455612d2017-05-30 16:40:10 +0200277 subnet['dns_nameservers'] = ip_profile['dns_address'].split(";")
garciadeblas9f8456e2016-09-05 05:02:59 +0200278 if 'dhcp_enabled' in ip_profile:
279 subnet['enable_dhcp'] = False if ip_profile['dhcp_enabled']=="false" else True
280 if 'dhcp_start_address' in ip_profile:
tiernoa1fb4462017-06-30 12:25:50 +0200281 subnet['allocation_pools'] = []
garciadeblas9f8456e2016-09-05 05:02:59 +0200282 subnet['allocation_pools'].append(dict())
283 subnet['allocation_pools'][0]['start'] = ip_profile['dhcp_start_address']
284 if 'dhcp_count' in ip_profile:
285 #parts = ip_profile['dhcp_start_address'].split('.')
286 #ip_int = (int(parts[0]) << 24) + (int(parts[1]) << 16) + (int(parts[2]) << 8) + int(parts[3])
287 ip_int = int(netaddr.IPAddress(ip_profile['dhcp_start_address']))
garciadeblas21d795b2016-09-29 17:31:46 +0200288 ip_int += ip_profile['dhcp_count'] - 1
garciadeblas9f8456e2016-09-05 05:02:59 +0200289 ip_str = str(netaddr.IPAddress(ip_int))
290 subnet['allocation_pools'][0]['end'] = ip_str
garciadeblasedca7b32016-09-29 14:01:52 +0000291 #self.logger.debug(">>>>>>>>>>>>>>>>>> Subnet: %s", str(subnet))
tierno7edb6752016-03-21 17:37:52 +0100292 self.neutron.create_subnet({"subnet": subnet} )
tiernoae4a8d12016-07-08 12:30:39 +0200293 return new_net["network"]["id"]
tierno8e995ce2016-09-22 08:13:00 +0000294 except (neExceptions.ConnectionFailed, ksExceptions.ClientException, neExceptions.NeutronException, ConnectionError) as e:
garciadeblasedca7b32016-09-29 14:01:52 +0000295 if new_net:
296 self.neutron.delete_network(new_net['network']['id'])
tiernoae4a8d12016-07-08 12:30:39 +0200297 self._format_exception(e)
tierno7edb6752016-03-21 17:37:52 +0100298
299 def get_network_list(self, filter_dict={}):
300 '''Obtain tenant networks of VIM
301 Filter_dict can be:
302 name: network name
303 id: network uuid
304 shared: boolean
305 tenant_id: tenant
306 admin_state_up: boolean
307 status: 'ACTIVE'
308 Returns the network list of dictionaries
309 '''
tiernoae4a8d12016-07-08 12:30:39 +0200310 self.logger.debug("Getting network from VIM filter: '%s'", str(filter_dict))
tierno7edb6752016-03-21 17:37:52 +0100311 try:
312 self._reload_connection()
tiernof716aea2017-06-21 18:01:40 +0200313 if self.api_version3 and "tenant_id" in filter_dict:
314 filter_dict['project_id'] = filter_dict.pop('tenant_id') #TODO check
tierno7edb6752016-03-21 17:37:52 +0100315 net_dict=self.neutron.list_networks(**filter_dict)
316 net_list=net_dict["networks"]
317 self.__net_os2mano(net_list)
tiernoae4a8d12016-07-08 12:30:39 +0200318 return net_list
tierno8e995ce2016-09-22 08:13:00 +0000319 except (neExceptions.ConnectionFailed, ksExceptions.ClientException, neExceptions.NeutronException, ConnectionError) as e:
tiernoae4a8d12016-07-08 12:30:39 +0200320 self._format_exception(e)
tierno7edb6752016-03-21 17:37:52 +0100321
tiernoae4a8d12016-07-08 12:30:39 +0200322 def get_network(self, net_id):
323 '''Obtain details of network from VIM
324 Returns the network information from a network id'''
325 self.logger.debug(" Getting tenant network %s from VIM", net_id)
tierno7edb6752016-03-21 17:37:52 +0100326 filter_dict={"id": net_id}
tiernoae4a8d12016-07-08 12:30:39 +0200327 net_list = self.get_network_list(filter_dict)
tierno7edb6752016-03-21 17:37:52 +0100328 if len(net_list)==0:
tiernoae4a8d12016-07-08 12:30:39 +0200329 raise vimconn.vimconnNotFoundException("Network '{}' not found".format(net_id))
tierno7edb6752016-03-21 17:37:52 +0100330 elif len(net_list)>1:
tiernoae4a8d12016-07-08 12:30:39 +0200331 raise vimconn.vimconnConflictException("Found more than one network with this criteria")
tierno7edb6752016-03-21 17:37:52 +0100332 net = net_list[0]
333 subnets=[]
334 for subnet_id in net.get("subnets", () ):
335 try:
336 subnet = self.neutron.show_subnet(subnet_id)
337 except Exception as e:
tiernoae4a8d12016-07-08 12:30:39 +0200338 self.logger.error("osconnector.get_network(): Error getting subnet %s %s" % (net_id, str(e)))
339 subnet = {"id": subnet_id, "fault": str(e)}
tierno7edb6752016-03-21 17:37:52 +0100340 subnets.append(subnet)
341 net["subnets"] = subnets
Pablo Montes Moreno51e553b2017-03-23 16:39:12 +0100342 net["encapsulation"] = net.get('provider:network_type')
Pablo Montes Moreno3fbff9b2017-03-08 11:28:15 +0100343 net["segmentation_id"] = net.get('provider:segmentation_id')
tiernoae4a8d12016-07-08 12:30:39 +0200344 return net
tierno7edb6752016-03-21 17:37:52 +0100345
tiernoae4a8d12016-07-08 12:30:39 +0200346 def delete_network(self, net_id):
347 '''Deletes a tenant network from VIM. Returns the old network identifier'''
348 self.logger.debug("Deleting network '%s' from VIM", net_id)
tierno7edb6752016-03-21 17:37:52 +0100349 try:
350 self._reload_connection()
351 #delete VM ports attached to this networks before the network
352 ports = self.neutron.list_ports(network_id=net_id)
353 for p in ports['ports']:
354 try:
355 self.neutron.delete_port(p["id"])
356 except Exception as e:
tiernoae4a8d12016-07-08 12:30:39 +0200357 self.logger.error("Error deleting port %s: %s", p["id"], str(e))
tierno7edb6752016-03-21 17:37:52 +0100358 self.neutron.delete_network(net_id)
tiernoae4a8d12016-07-08 12:30:39 +0200359 return net_id
360 except (neExceptions.ConnectionFailed, neExceptions.NetworkNotFoundClient, neExceptions.NeutronException,
tierno8e995ce2016-09-22 08:13:00 +0000361 ksExceptions.ClientException, neExceptions.NeutronException, ConnectionError) as e:
tiernoae4a8d12016-07-08 12:30:39 +0200362 self._format_exception(e)
tierno7edb6752016-03-21 17:37:52 +0100363
tiernoae4a8d12016-07-08 12:30:39 +0200364 def refresh_nets_status(self, net_list):
365 '''Get the status of the networks
366 Params: the list of network identifiers
367 Returns a dictionary with:
368 net_id: #VIM id of this network
369 status: #Mandatory. Text with one of:
370 # DELETED (not found at vim)
371 # VIM_ERROR (Cannot connect to VIM, VIM response error, ...)
372 # OTHER (Vim reported other status not understood)
373 # ERROR (VIM indicates an ERROR status)
374 # ACTIVE, INACTIVE, DOWN (admin down),
375 # BUILD (on building process)
376 #
377 error_msg: #Text with VIM error message, if any. Or the VIM connection ERROR
378 vim_info: #Text with plain information obtained from vim (yaml.safe_dump)
379
380 '''
381 net_dict={}
382 for net_id in net_list:
383 net = {}
384 try:
385 net_vim = self.get_network(net_id)
386 if net_vim['status'] in netStatus2manoFormat:
387 net["status"] = netStatus2manoFormat[ net_vim['status'] ]
388 else:
389 net["status"] = "OTHER"
390 net["error_msg"] = "VIM status reported " + net_vim['status']
391
tierno8e995ce2016-09-22 08:13:00 +0000392 if net['status'] == "ACTIVE" and not net_vim['admin_state_up']:
tiernoae4a8d12016-07-08 12:30:39 +0200393 net['status'] = 'DOWN'
tierno8e995ce2016-09-22 08:13:00 +0000394 try:
395 net['vim_info'] = yaml.safe_dump(net_vim, default_flow_style=True, width=256)
396 except yaml.representer.RepresenterError:
397 net['vim_info'] = str(net_vim)
tiernoae4a8d12016-07-08 12:30:39 +0200398 if net_vim.get('fault'): #TODO
399 net['error_msg'] = str(net_vim['fault'])
400 except vimconn.vimconnNotFoundException as e:
401 self.logger.error("Exception getting net status: %s", str(e))
402 net['status'] = "DELETED"
403 net['error_msg'] = str(e)
404 except vimconn.vimconnException as e:
405 self.logger.error("Exception getting net status: %s", str(e))
406 net['status'] = "VIM_ERROR"
407 net['error_msg'] = str(e)
408 net_dict[net_id] = net
409 return net_dict
410
411 def get_flavor(self, flavor_id):
412 '''Obtain flavor details from the VIM. Returns the flavor dict details'''
413 self.logger.debug("Getting flavor '%s'", flavor_id)
tierno7edb6752016-03-21 17:37:52 +0100414 try:
415 self._reload_connection()
416 flavor = self.nova.flavors.find(id=flavor_id)
417 #TODO parse input and translate to VIM format (openmano_schemas.new_vminstance_response_schema)
tiernoae4a8d12016-07-08 12:30:39 +0200418 return flavor.to_dict()
tierno8e995ce2016-09-22 08:13:00 +0000419 except (nvExceptions.NotFound, nvExceptions.ClientException, ksExceptions.ClientException, ConnectionError) as e:
tiernoae4a8d12016-07-08 12:30:39 +0200420 self._format_exception(e)
tierno7edb6752016-03-21 17:37:52 +0100421
tiernocf157a82017-01-30 14:07:06 +0100422 def get_flavor_id_from_data(self, flavor_dict):
423 """Obtain flavor id that match the flavor description
424 Returns the flavor_id or raises a vimconnNotFoundException
tiernoe26fc7a2017-05-30 14:43:03 +0200425 flavor_dict: contains the required ram, vcpus, disk
426 If 'use_existing_flavors' is set to True at config, the closer flavor that provides same or more ram, vcpus
427 and disk is returned. Otherwise a flavor with exactly same ram, vcpus and disk is returned or a
428 vimconnNotFoundException is raised
tiernocf157a82017-01-30 14:07:06 +0100429 """
tiernoe26fc7a2017-05-30 14:43:03 +0200430 exact_match = False if self.config.get('use_existing_flavors') else True
tiernocf157a82017-01-30 14:07:06 +0100431 try:
432 self._reload_connection()
tiernoe26fc7a2017-05-30 14:43:03 +0200433 flavor_candidate_id = None
434 flavor_candidate_data = (10000, 10000, 10000)
435 flavor_target = (flavor_dict["ram"], flavor_dict["vcpus"], flavor_dict["disk"])
436 # numa=None
437 numas = flavor_dict.get("extended", {}).get("numas")
tiernocf157a82017-01-30 14:07:06 +0100438 if numas:
439 #TODO
440 raise vimconn.vimconnNotFoundException("Flavor with EPA still not implemted")
441 # if len(numas) > 1:
442 # raise vimconn.vimconnNotFoundException("Cannot find any flavor with more than one numa")
443 # numa=numas[0]
444 # numas = extended.get("numas")
445 for flavor in self.nova.flavors.list():
446 epa = flavor.get_keys()
447 if epa:
448 continue
tiernoe26fc7a2017-05-30 14:43:03 +0200449 # TODO
450 flavor_data = (flavor.ram, flavor.vcpus, flavor.disk)
451 if flavor_data == flavor_target:
452 return flavor.id
453 elif not exact_match and flavor_target < flavor_data < flavor_candidate_data:
454 flavor_candidate_id = flavor.id
455 flavor_candidate_data = flavor_data
456 if not exact_match and flavor_candidate_id:
457 return flavor_candidate_id
tiernocf157a82017-01-30 14:07:06 +0100458 raise vimconn.vimconnNotFoundException("Cannot find any flavor matching '{}'".format(str(flavor_dict)))
459 except (nvExceptions.NotFound, nvExceptions.ClientException, ksExceptions.ClientException, ConnectionError) as e:
460 self._format_exception(e)
461
462
tiernoae4a8d12016-07-08 12:30:39 +0200463 def new_flavor(self, flavor_data, change_name_if_used=True):
tierno7edb6752016-03-21 17:37:52 +0100464 '''Adds a tenant flavor to openstack VIM
tiernoae4a8d12016-07-08 12:30:39 +0200465 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 +0100466 Returns the flavor identifier
467 '''
tiernoae4a8d12016-07-08 12:30:39 +0200468 self.logger.debug("Adding flavor '%s'", str(flavor_data))
tierno7edb6752016-03-21 17:37:52 +0100469 retry=0
tiernoae4a8d12016-07-08 12:30:39 +0200470 max_retries=3
tierno7edb6752016-03-21 17:37:52 +0100471 name_suffix = 0
tiernoae4a8d12016-07-08 12:30:39 +0200472 name=flavor_data['name']
473 while retry<max_retries:
tierno7edb6752016-03-21 17:37:52 +0100474 retry+=1
475 try:
476 self._reload_connection()
477 if change_name_if_used:
478 #get used names
479 fl_names=[]
480 fl=self.nova.flavors.list()
481 for f in fl:
482 fl_names.append(f.name)
483 while name in fl_names:
484 name_suffix += 1
tiernoae4a8d12016-07-08 12:30:39 +0200485 name = flavor_data['name']+"-" + str(name_suffix)
tierno7edb6752016-03-21 17:37:52 +0100486
tiernoae4a8d12016-07-08 12:30:39 +0200487 ram = flavor_data.get('ram',64)
488 vcpus = flavor_data.get('vcpus',1)
tierno7edb6752016-03-21 17:37:52 +0100489 numa_properties=None
490
tiernoae4a8d12016-07-08 12:30:39 +0200491 extended = flavor_data.get("extended")
tierno7edb6752016-03-21 17:37:52 +0100492 if extended:
493 numas=extended.get("numas")
494 if numas:
495 numa_nodes = len(numas)
496 if numa_nodes > 1:
497 return -1, "Can not add flavor with more than one numa"
498 numa_properties = {"hw:numa_nodes":str(numa_nodes)}
499 numa_properties["hw:mem_page_size"] = "large"
500 numa_properties["hw:cpu_policy"] = "dedicated"
501 numa_properties["hw:numa_mempolicy"] = "strict"
502 for numa in numas:
503 #overwrite ram and vcpus
504 ram = numa['memory']*1024
Pablo Montes Morenoea1d6232017-05-24 11:33:24 +0200505 #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 +0100506 if 'paired-threads' in numa:
507 vcpus = numa['paired-threads']*2
Pablo Montes Morenoea1d6232017-05-24 11:33:24 +0200508 #cpu_thread_policy "require" implies that the compute node must have an STM architecture
509 numa_properties["hw:cpu_thread_policy"] = "require"
510 numa_properties["hw:cpu_policy"] = "dedicated"
tierno7edb6752016-03-21 17:37:52 +0100511 elif 'cores' in numa:
512 vcpus = numa['cores']
Pablo Montes Morenoea1d6232017-05-24 11:33:24 +0200513 # cpu_thread_policy "prefer" implies that the host must not have an SMT architecture, or a non-SMT architecture will be emulated
514 numa_properties["hw:cpu_thread_policy"] = "isolate"
515 numa_properties["hw:cpu_policy"] = "dedicated"
tierno7edb6752016-03-21 17:37:52 +0100516 elif 'threads' in numa:
517 vcpus = numa['threads']
Pablo Montes Morenoea1d6232017-05-24 11:33:24 +0200518 # cpu_thread_policy "prefer" implies that the host may or may not have an SMT architecture
519 numa_properties["hw:cpu_thread_policy"] = "prefer"
520 numa_properties["hw:cpu_policy"] = "dedicated"
Pablo Montes Moreno3be0b2a2017-03-30 13:22:15 +0200521 # for interface in numa.get("interfaces",() ):
522 # if interface["dedicated"]=="yes":
523 # raise vimconn.vimconnException("Passthrough interfaces are not supported for the openstack connector", http_code=vimconn.HTTP_Service_Unavailable)
524 # #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 +0100525
526 #create flavor
527 new_flavor=self.nova.flavors.create(name,
528 ram,
529 vcpus,
tiernoae4a8d12016-07-08 12:30:39 +0200530 flavor_data.get('disk',1),
531 is_public=flavor_data.get('is_public', True)
tierno7edb6752016-03-21 17:37:52 +0100532 )
533 #add metadata
534 if numa_properties:
535 new_flavor.set_keys(numa_properties)
tiernoae4a8d12016-07-08 12:30:39 +0200536 return new_flavor.id
tierno7edb6752016-03-21 17:37:52 +0100537 except nvExceptions.Conflict as e:
tiernoae4a8d12016-07-08 12:30:39 +0200538 if change_name_if_used and retry < max_retries:
tierno7edb6752016-03-21 17:37:52 +0100539 continue
tiernoae4a8d12016-07-08 12:30:39 +0200540 self._format_exception(e)
tierno7edb6752016-03-21 17:37:52 +0100541 #except nvExceptions.BadRequest as e:
542 except (ksExceptions.ClientException, nvExceptions.ClientException, ConnectionError) as e:
tiernoae4a8d12016-07-08 12:30:39 +0200543 self._format_exception(e)
tierno7edb6752016-03-21 17:37:52 +0100544
tiernoae4a8d12016-07-08 12:30:39 +0200545 def delete_flavor(self,flavor_id):
546 '''Deletes a tenant flavor from openstack VIM. Returns the old flavor_id
tierno7edb6752016-03-21 17:37:52 +0100547 '''
tiernoae4a8d12016-07-08 12:30:39 +0200548 try:
549 self._reload_connection()
550 self.nova.flavors.delete(flavor_id)
551 return flavor_id
552 #except nvExceptions.BadRequest as e:
tierno8e995ce2016-09-22 08:13:00 +0000553 except (nvExceptions.NotFound, ksExceptions.ClientException, nvExceptions.ClientException, ConnectionError) as e:
tiernoae4a8d12016-07-08 12:30:39 +0200554 self._format_exception(e)
tierno7edb6752016-03-21 17:37:52 +0100555
tiernoae4a8d12016-07-08 12:30:39 +0200556 def new_image(self,image_dict):
tierno7edb6752016-03-21 17:37:52 +0100557 '''
tiernoae4a8d12016-07-08 12:30:39 +0200558 Adds a tenant image to VIM. imge_dict is a dictionary with:
559 name: name
560 disk_format: qcow2, vhd, vmdk, raw (by default), ...
561 location: path or URI
562 public: "yes" or "no"
563 metadata: metadata of the image
564 Returns the image_id
tierno7edb6752016-03-21 17:37:52 +0100565 '''
tiernob5cef372017-06-19 15:52:22 +0200566 # ALF TODO: revise and change for the new method or session
tierno7edb6752016-03-21 17:37:52 +0100567 #using version 1 of glance client
568 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 +0200569 retry=0
570 max_retries=3
571 while retry<max_retries:
tierno7edb6752016-03-21 17:37:52 +0100572 retry+=1
573 try:
574 self._reload_connection()
575 #determine format http://docs.openstack.org/developer/glance/formats.html
576 if "disk_format" in image_dict:
577 disk_format=image_dict["disk_format"]
garciadeblas14480452017-01-10 13:08:07 +0100578 else: #autodiscover based on extension
tierno7edb6752016-03-21 17:37:52 +0100579 if image_dict['location'][-6:]==".qcow2":
580 disk_format="qcow2"
581 elif image_dict['location'][-4:]==".vhd":
582 disk_format="vhd"
583 elif image_dict['location'][-5:]==".vmdk":
584 disk_format="vmdk"
585 elif image_dict['location'][-4:]==".vdi":
586 disk_format="vdi"
587 elif image_dict['location'][-4:]==".iso":
588 disk_format="iso"
589 elif image_dict['location'][-4:]==".aki":
590 disk_format="aki"
591 elif image_dict['location'][-4:]==".ari":
592 disk_format="ari"
593 elif image_dict['location'][-4:]==".ami":
594 disk_format="ami"
595 else:
596 disk_format="raw"
tiernoae4a8d12016-07-08 12:30:39 +0200597 self.logger.debug("new_image: '%s' loading from '%s'", image_dict['name'], image_dict['location'])
tierno7edb6752016-03-21 17:37:52 +0100598 if image_dict['location'][0:4]=="http":
599 new_image = glancev1.images.create(name=image_dict['name'], is_public=image_dict.get('public',"yes")=="yes",
600 container_format="bare", location=image_dict['location'], disk_format=disk_format)
601 else: #local path
602 with open(image_dict['location']) as fimage:
603 new_image = glancev1.images.create(name=image_dict['name'], is_public=image_dict.get('public',"yes")=="yes",
604 container_format="bare", data=fimage, disk_format=disk_format)
605 #insert metadata. We cannot use 'new_image.properties.setdefault'
606 #because nova and glance are "INDEPENDENT" and we are using nova for reading metadata
607 new_image_nova=self.nova.images.find(id=new_image.id)
608 new_image_nova.metadata.setdefault('location',image_dict['location'])
609 metadata_to_load = image_dict.get('metadata')
610 if metadata_to_load:
611 for k,v in yaml.load(metadata_to_load).iteritems():
612 new_image_nova.metadata.setdefault(k,v)
tiernoae4a8d12016-07-08 12:30:39 +0200613 return new_image.id
614 except (nvExceptions.Conflict, ksExceptions.ClientException, nvExceptions.ClientException) as e:
615 self._format_exception(e)
tierno8e995ce2016-09-22 08:13:00 +0000616 except (HTTPException, gl1Exceptions.HTTPException, gl1Exceptions.CommunicationError, ConnectionError) as e:
tiernoae4a8d12016-07-08 12:30:39 +0200617 if retry==max_retries:
618 continue
619 self._format_exception(e)
tierno7edb6752016-03-21 17:37:52 +0100620 except IOError as e: #can not open the file
tiernoae4a8d12016-07-08 12:30:39 +0200621 raise vimconn.vimconnConnectionException(type(e).__name__ + ": " + str(e)+ " for " + image_dict['location'],
622 http_code=vimconn.HTTP_Bad_Request)
tierno7edb6752016-03-21 17:37:52 +0100623
tiernoae4a8d12016-07-08 12:30:39 +0200624 def delete_image(self, image_id):
625 '''Deletes a tenant image from openstack VIM. Returns the old id
tierno7edb6752016-03-21 17:37:52 +0100626 '''
tiernoae4a8d12016-07-08 12:30:39 +0200627 try:
628 self._reload_connection()
629 self.nova.images.delete(image_id)
630 return image_id
tierno8e995ce2016-09-22 08:13:00 +0000631 except (nvExceptions.NotFound, ksExceptions.ClientException, nvExceptions.ClientException, gl1Exceptions.CommunicationError, ConnectionError) as e: #TODO remove
tiernoae4a8d12016-07-08 12:30:39 +0200632 self._format_exception(e)
633
634 def get_image_id_from_path(self, path):
garciadeblasb69fa9f2016-09-28 12:04:10 +0200635 '''Get the image id from image path in the VIM database. Returns the image_id'''
tiernoae4a8d12016-07-08 12:30:39 +0200636 try:
637 self._reload_connection()
638 images = self.nova.images.list()
639 for image in images:
640 if image.metadata.get("location")==path:
641 return image.id
642 raise vimconn.vimconnNotFoundException("image with location '{}' not found".format( path))
tierno8e995ce2016-09-22 08:13:00 +0000643 except (ksExceptions.ClientException, nvExceptions.ClientException, gl1Exceptions.CommunicationError, ConnectionError) as e:
tiernoae4a8d12016-07-08 12:30:39 +0200644 self._format_exception(e)
tierno7edb6752016-03-21 17:37:52 +0100645
garciadeblasb69fa9f2016-09-28 12:04:10 +0200646 def get_image_list(self, filter_dict={}):
647 '''Obtain tenant images from VIM
648 Filter_dict can be:
649 id: image id
650 name: image name
651 checksum: image checksum
652 Returns the image list of dictionaries:
653 [{<the fields at Filter_dict plus some VIM specific>}, ...]
654 List can be empty
655 '''
656 self.logger.debug("Getting image list from VIM filter: '%s'", str(filter_dict))
657 try:
658 self._reload_connection()
659 filter_dict_os=filter_dict.copy()
660 #First we filter by the available filter fields: name, id. The others are removed.
661 filter_dict_os.pop('checksum',None)
662 image_list=self.nova.images.findall(**filter_dict_os)
663 if len(image_list)==0:
664 return []
665 #Then we filter by the rest of filter fields: checksum
666 filtered_list = []
667 for image in image_list:
tierno4540ea52017-01-18 17:44:32 +0100668 image_class=self.glance.images.get(image.id)
669 if 'checksum' not in filter_dict or image_class['checksum']==filter_dict.get('checksum'):
670 filtered_list.append(image_class.copy())
garciadeblasb69fa9f2016-09-28 12:04:10 +0200671 return filtered_list
672 except (ksExceptions.ClientException, nvExceptions.ClientException, gl1Exceptions.CommunicationError, ConnectionError) as e:
673 self._format_exception(e)
674
montesmoreno0c8def02016-12-22 12:16:23 +0000675 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 +0100676 '''Adds a VM instance to VIM
677 Params:
678 start: indicates if VM must start or boot in pause mode. Ignored
679 image_id,flavor_id: iamge and flavor uuid
680 net_list: list of interfaces, each one is a dictionary with:
681 name:
682 net_id: network uuid to connect
683 vpci: virtual vcpi to assign, ignored because openstack lack #TODO
684 model: interface model, ignored #TODO
685 mac_address: used for SR-IOV ifaces #TODO for other types
686 use: 'data', 'bridge', 'mgmt'
687 type: 'virtual', 'PF', 'VF', 'VFnotShared'
688 vim_id: filled/added by this function
ahmadsaf853d452016-12-22 11:33:47 +0500689 floating_ip: True/False (or it can be None)
tierno7edb6752016-03-21 17:37:52 +0100690 #TODO ip, security groups
tiernoae4a8d12016-07-08 12:30:39 +0200691 Returns the instance identifier
tierno7edb6752016-03-21 17:37:52 +0100692 '''
tiernofa51c202017-01-27 14:58:17 +0100693 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 +0100694 try:
tierno6e116232016-07-18 13:01:40 +0200695 metadata={}
tierno7edb6752016-03-21 17:37:52 +0100696 net_list_vim=[]
ahmadsaf853d452016-12-22 11:33:47 +0500697 external_network=[] #list of external networks to be connected to instance, later on used to create floating_ip
tierno7edb6752016-03-21 17:37:52 +0100698 self._reload_connection()
tiernoae4a8d12016-07-08 12:30:39 +0200699 metadata_vpci={} #For a specific neutron plugin
tierno7edb6752016-03-21 17:37:52 +0100700 for net in net_list:
701 if not net.get("net_id"): #skip non connected iface
702 continue
Pablo Montes Moreno3be0b2a2017-03-30 13:22:15 +0200703
704 port_dict={
705 "network_id": net["net_id"],
706 "name": net.get("name"),
707 "admin_state_up": True
708 }
709 if net["type"]=="virtual":
710 if "vpci" in net:
711 metadata_vpci[ net["net_id"] ] = [[ net["vpci"], "" ]]
712 elif net["type"]=="VF": # for VF
713 if "vpci" in net:
714 if "VF" not in metadata_vpci:
715 metadata_vpci["VF"]=[]
716 metadata_vpci["VF"].append([ net["vpci"], "" ])
717 port_dict["binding:vnic_type"]="direct"
718 else: #For PT
719 if "vpci" in net:
720 if "PF" not in metadata_vpci:
721 metadata_vpci["PF"]=[]
722 metadata_vpci["PF"].append([ net["vpci"], "" ])
723 port_dict["binding:vnic_type"]="direct-physical"
724 if not port_dict["name"]:
725 port_dict["name"]=name
726 if net.get("mac_address"):
727 port_dict["mac_address"]=net["mac_address"]
728 if net.get("port_security") == False:
729 port_dict["port_security_enabled"]=net["port_security"]
730 new_port = self.neutron.create_port({"port": port_dict })
731 net["mac_adress"] = new_port["port"]["mac_address"]
732 net["vim_id"] = new_port["port"]["id"]
733 net["ip"] = new_port["port"].get("fixed_ips", [{}])[0].get("ip_address")
734 net_list_vim.append({"port-id": new_port["port"]["id"]})
735
ahmadsaf853d452016-12-22 11:33:47 +0500736 if net.get('floating_ip', False):
tiernof8383b82017-01-18 15:49:48 +0100737 net['exit_on_floating_ip_error'] = True
ahmadsaf853d452016-12-22 11:33:47 +0500738 external_network.append(net)
tiernof8383b82017-01-18 15:49:48 +0100739 elif net['use'] == 'mgmt' and self.config.get('use_floating_ip'):
740 net['exit_on_floating_ip_error'] = False
741 external_network.append(net)
742
tierno7edb6752016-03-21 17:37:52 +0100743 if metadata_vpci:
744 metadata = {"pci_assignement": json.dumps(metadata_vpci)}
tiernoafbced42016-07-23 01:43:53 +0200745 if len(metadata["pci_assignement"]) >255:
tierno6e116232016-07-18 13:01:40 +0200746 #limit the metadata size
747 #metadata["pci_assignement"] = metadata["pci_assignement"][0:255]
748 self.logger.warn("Metadata deleted since it exceeds the expected length (255) ")
749 metadata = {}
tierno7edb6752016-03-21 17:37:52 +0100750
tiernoae4a8d12016-07-08 12:30:39 +0200751 self.logger.debug("name '%s' image_id '%s'flavor_id '%s' net_list_vim '%s' description '%s' metadata %s",
752 name, image_id, flavor_id, str(net_list_vim), description, str(metadata))
tierno7edb6752016-03-21 17:37:52 +0100753
754 security_groups = self.config.get('security_groups')
755 if type(security_groups) is str:
756 security_groups = ( security_groups, )
tierno36c0b172017-01-12 18:32:28 +0100757 #cloud config
758 userdata=None
759 config_drive = None
tiernoa4e1a6e2016-08-31 14:19:40 +0200760 if isinstance(cloud_config, dict):
tierno36c0b172017-01-12 18:32:28 +0100761 if cloud_config.get("user-data"):
762 userdata=cloud_config["user-data"]
763 if cloud_config.get("boot-data-drive") != None:
764 config_drive = cloud_config["boot-data-drive"]
765 if cloud_config.get("config-files") or cloud_config.get("users") or cloud_config.get("key-pairs"):
766 if userdata:
767 raise vimconn.vimconnConflictException("Cloud-config cannot contain both 'userdata' and 'config-files'/'users'/'key-pairs'")
768 userdata_dict={}
769 #default user
770 if cloud_config.get("key-pairs"):
771 userdata_dict["ssh-authorized-keys"] = cloud_config["key-pairs"]
772 userdata_dict["users"] = [{"default": None, "ssh-authorized-keys": cloud_config["key-pairs"] }]
773 if cloud_config.get("users"):
tierno01d0bf52017-01-25 14:27:20 +0100774 if "users" not in userdata_dict:
tierno36c0b172017-01-12 18:32:28 +0100775 userdata_dict["users"] = [ "default" ]
776 for user in cloud_config["users"]:
777 user_info = {
778 "name" : user["name"],
779 "sudo": "ALL = (ALL)NOPASSWD:ALL"
780 }
781 if "user-info" in user:
782 user_info["gecos"] = user["user-info"]
783 if user.get("key-pairs"):
784 user_info["ssh-authorized-keys"] = user["key-pairs"]
785 userdata_dict["users"].append(user_info)
786
787 if cloud_config.get("config-files"):
788 userdata_dict["write_files"] = []
789 for file in cloud_config["config-files"]:
790 file_info = {
791 "path" : file["dest"],
792 "content": file["content"]
793 }
794 if file.get("encoding"):
795 file_info["encoding"] = file["encoding"]
796 if file.get("permissions"):
797 file_info["permissions"] = file["permissions"]
798 if file.get("owner"):
799 file_info["owner"] = file["owner"]
800 userdata_dict["write_files"].append(file_info)
801 userdata = "#cloud-config\n"
802 userdata += yaml.safe_dump(userdata_dict, indent=4, default_flow_style=False)
tiernoa4e1a6e2016-08-31 14:19:40 +0200803 self.logger.debug("userdata: %s", userdata)
804 elif isinstance(cloud_config, str):
805 userdata = cloud_config
montesmoreno0c8def02016-12-22 12:16:23 +0000806
807 #Create additional volumes in case these are present in disk_list
808 block_device_mapping = None
809 base_disk_index = ord('b')
810 if disk_list != None:
811 block_device_mapping = dict()
812 for disk in disk_list:
813 if 'image_id' in disk:
814 volume = self.cinder.volumes.create(size = disk['size'],name = name + '_vd' +
815 chr(base_disk_index), imageRef = disk['image_id'])
816 else:
817 volume = self.cinder.volumes.create(size=disk['size'], name=name + '_vd' +
818 chr(base_disk_index))
819 block_device_mapping['_vd' + chr(base_disk_index)] = volume.id
820 base_disk_index += 1
821
822 #wait until volumes are with status available
823 keep_waiting = True
824 elapsed_time = 0
825 while keep_waiting and elapsed_time < volume_timeout:
826 keep_waiting = False
827 for volume_id in block_device_mapping.itervalues():
828 if self.cinder.volumes.get(volume_id).status != 'available':
829 keep_waiting = True
830 if keep_waiting:
831 time.sleep(1)
832 elapsed_time += 1
833
834 #if we exceeded the timeout rollback
835 if elapsed_time >= volume_timeout:
836 #delete the volumes we just created
837 for volume_id in block_device_mapping.itervalues():
838 self.cinder.volumes.delete(volume_id)
839
840 #delete ports we just created
841 for net_item in net_list_vim:
842 if 'port-id' in net_item:
montesmorenocf227142017-01-12 12:24:21 +0000843 self.neutron.delete_port(net_item['port-id'])
montesmoreno0c8def02016-12-22 12:16:23 +0000844
845 raise vimconn.vimconnException('Timeout creating volumes for instance ' + name,
846 http_code=vimconn.HTTP_Request_Timeout)
847
Pablo Montes Morenob7490b52017-06-20 10:49:58 +0200848 self.logger.debug("nova.servers.create({}, {}, {}, nics={}, meta={}, security_groups={}," \
849 "availability_zone={}, key_name={}, userdata={}, config_drive={}, " \
850 "block_device_mapping={})".format(name, image_id, flavor_id, net_list_vim,
851 metadata, security_groups, self.config.get('availability_zone'),
852 self.config.get('keypair'), userdata, config_drive, block_device_mapping))
tierno7edb6752016-03-21 17:37:52 +0100853 server = self.nova.servers.create(name, image_id, flavor_id, nics=net_list_vim, meta=metadata,
montesmoreno0c8def02016-12-22 12:16:23 +0000854 security_groups=security_groups,
855 availability_zone=self.config.get('availability_zone'),
856 key_name=self.config.get('keypair'),
857 userdata=userdata,
tierno36c0b172017-01-12 18:32:28 +0100858 config_drive = config_drive,
montesmoreno0c8def02016-12-22 12:16:23 +0000859 block_device_mapping = block_device_mapping
860 ) # , description=description)
tiernoae4a8d12016-07-08 12:30:39 +0200861 #print "DONE :-)", server
ahmadsaf853d452016-12-22 11:33:47 +0500862 pool_id = None
863 floating_ips = self.neutron.list_floatingips().get("floatingips", ())
864 for floating_network in external_network:
tiernof8383b82017-01-18 15:49:48 +0100865 try:
866 # wait until vm is active
867 elapsed_time = 0
868 while elapsed_time < server_timeout:
869 status = self.nova.servers.get(server.id).status
870 if status == 'ACTIVE':
871 break
872 time.sleep(1)
873 elapsed_time += 1
montesmoreno2a1fc4e2017-01-09 16:46:04 +0000874
tiernof8383b82017-01-18 15:49:48 +0100875 #if we exceeded the timeout rollback
876 if elapsed_time >= server_timeout:
877 raise vimconn.vimconnException('Timeout creating instance ' + name,
878 http_code=vimconn.HTTP_Request_Timeout)
montesmoreno2a1fc4e2017-01-09 16:46:04 +0000879
tiernof8383b82017-01-18 15:49:48 +0100880 assigned = False
881 while(assigned == False):
882 if floating_ips:
883 ip = floating_ips.pop(0)
884 if not ip.get("port_id", False) and ip.get('tenant_id') == server.tenant_id:
885 free_floating_ip = ip.get("floating_ip_address")
886 try:
887 fix_ip = floating_network.get('ip')
888 server.add_floating_ip(free_floating_ip, fix_ip)
889 assigned = True
890 except Exception as e:
891 raise vimconn.vimconnException(type(e).__name__ + ": Cannot create floating_ip "+ str(e), http_code=vimconn.HTTP_Conflict)
892 else:
893 #Find the external network
894 external_nets = list()
895 for net in self.neutron.list_networks()['networks']:
896 if net['router:external']:
897 external_nets.append(net)
898
899 if len(external_nets) == 0:
900 raise vimconn.vimconnException("Cannot create floating_ip automatically since no external "
901 "network is present",
902 http_code=vimconn.HTTP_Conflict)
903 if len(external_nets) > 1:
904 raise vimconn.vimconnException("Cannot create floating_ip automatically since multiple "
905 "external networks are present",
906 http_code=vimconn.HTTP_Conflict)
907
908 pool_id = external_nets[0].get('id')
909 param = {'floatingip': {'floating_network_id': pool_id, 'tenant_id': server.tenant_id}}
ahmadsaf853d452016-12-22 11:33:47 +0500910 try:
tiernof8383b82017-01-18 15:49:48 +0100911 #self.logger.debug("Creating floating IP")
912 new_floating_ip = self.neutron.create_floatingip(param)
913 free_floating_ip = new_floating_ip['floatingip']['floating_ip_address']
ahmadsaf853d452016-12-22 11:33:47 +0500914 fix_ip = floating_network.get('ip')
915 server.add_floating_ip(free_floating_ip, fix_ip)
tiernof8383b82017-01-18 15:49:48 +0100916 assigned=True
ahmadsaf853d452016-12-22 11:33:47 +0500917 except Exception as e:
tiernof8383b82017-01-18 15:49:48 +0100918 raise vimconn.vimconnException(type(e).__name__ + ": Cannot assign floating_ip "+ str(e), http_code=vimconn.HTTP_Conflict)
919 except Exception as e:
920 if not floating_network['exit_on_floating_ip_error']:
921 self.logger.warn("Cannot create floating_ip. %s", str(e))
922 continue
923 self.delete_vminstance(server.id)
924 raise
montesmoreno2a1fc4e2017-01-09 16:46:04 +0000925
tiernoae4a8d12016-07-08 12:30:39 +0200926 return server.id
tierno7edb6752016-03-21 17:37:52 +0100927# except nvExceptions.NotFound as e:
928# error_value=-vimconn.HTTP_Not_Found
929# error_text= "vm instance %s not found" % vm_id
tiernof8383b82017-01-18 15:49:48 +0100930 except (ksExceptions.ClientException, nvExceptions.ClientException, ConnectionError) as e:
montesmoreno2a1fc4e2017-01-09 16:46:04 +0000931 # delete the volumes we just created
932 if block_device_mapping != None:
933 for volume_id in block_device_mapping.itervalues():
934 self.cinder.volumes.delete(volume_id)
935
936 # delete ports we just created
937 for net_item in net_list_vim:
938 if 'port-id' in net_item:
montesmorenocf227142017-01-12 12:24:21 +0000939 self.neutron.delete_port(net_item['port-id'])
tiernoae4a8d12016-07-08 12:30:39 +0200940 self._format_exception(e)
941 except TypeError as e:
942 raise vimconn.vimconnException(type(e).__name__ + ": "+ str(e), http_code=vimconn.HTTP_Bad_Request)
tierno7edb6752016-03-21 17:37:52 +0100943
tiernoae4a8d12016-07-08 12:30:39 +0200944 def get_vminstance(self,vm_id):
tierno7edb6752016-03-21 17:37:52 +0100945 '''Returns the VM instance information from VIM'''
tiernoae4a8d12016-07-08 12:30:39 +0200946 #self.logger.debug("Getting VM from VIM")
tierno7edb6752016-03-21 17:37:52 +0100947 try:
948 self._reload_connection()
949 server = self.nova.servers.find(id=vm_id)
950 #TODO parse input and translate to VIM format (openmano_schemas.new_vminstance_response_schema)
tiernoae4a8d12016-07-08 12:30:39 +0200951 return server.to_dict()
tierno8e995ce2016-09-22 08:13:00 +0000952 except (ksExceptions.ClientException, nvExceptions.ClientException, nvExceptions.NotFound, ConnectionError) as e:
tiernoae4a8d12016-07-08 12:30:39 +0200953 self._format_exception(e)
954
955 def get_vminstance_console(self,vm_id, console_type="vnc"):
tierno7edb6752016-03-21 17:37:52 +0100956 '''
957 Get a console for the virtual machine
958 Params:
959 vm_id: uuid of the VM
960 console_type, can be:
961 "novnc" (by default), "xvpvnc" for VNC types,
962 "rdp-html5" for RDP types, "spice-html5" for SPICE types
tiernoae4a8d12016-07-08 12:30:39 +0200963 Returns dict with the console parameters:
964 protocol: ssh, ftp, http, https, ...
965 server: usually ip address
966 port: the http, ssh, ... port
967 suffix: extra text, e.g. the http path and query string
tierno7edb6752016-03-21 17:37:52 +0100968 '''
tiernoae4a8d12016-07-08 12:30:39 +0200969 self.logger.debug("Getting VM CONSOLE from VIM")
tierno7edb6752016-03-21 17:37:52 +0100970 try:
971 self._reload_connection()
972 server = self.nova.servers.find(id=vm_id)
973 if console_type == None or console_type == "novnc":
974 console_dict = server.get_vnc_console("novnc")
975 elif console_type == "xvpvnc":
976 console_dict = server.get_vnc_console(console_type)
977 elif console_type == "rdp-html5":
978 console_dict = server.get_rdp_console(console_type)
979 elif console_type == "spice-html5":
980 console_dict = server.get_spice_console(console_type)
981 else:
tiernoae4a8d12016-07-08 12:30:39 +0200982 raise vimconn.vimconnException("console type '{}' not allowed".format(console_type), http_code=vimconn.HTTP_Bad_Request)
tierno7edb6752016-03-21 17:37:52 +0100983
984 console_dict1 = console_dict.get("console")
985 if console_dict1:
986 console_url = console_dict1.get("url")
987 if console_url:
988 #parse console_url
989 protocol_index = console_url.find("//")
990 suffix_index = console_url[protocol_index+2:].find("/") + protocol_index+2
991 port_index = console_url[protocol_index+2:suffix_index].find(":") + protocol_index+2
992 if protocol_index < 0 or port_index<0 or suffix_index<0:
993 return -vimconn.HTTP_Internal_Server_Error, "Unexpected response from VIM"
994 console_dict={"protocol": console_url[0:protocol_index],
995 "server": console_url[protocol_index+2:port_index],
996 "port": console_url[port_index:suffix_index],
997 "suffix": console_url[suffix_index+1:]
998 }
999 protocol_index += 2
tiernoae4a8d12016-07-08 12:30:39 +02001000 return console_dict
1001 raise vimconn.vimconnUnexpectedResponse("Unexpected response from VIM")
tierno7edb6752016-03-21 17:37:52 +01001002
tierno8e995ce2016-09-22 08:13:00 +00001003 except (nvExceptions.NotFound, ksExceptions.ClientException, nvExceptions.ClientException, nvExceptions.BadRequest, ConnectionError) as e:
tiernoae4a8d12016-07-08 12:30:39 +02001004 self._format_exception(e)
tierno7edb6752016-03-21 17:37:52 +01001005
tiernoae4a8d12016-07-08 12:30:39 +02001006 def delete_vminstance(self, vm_id):
1007 '''Removes a VM instance from VIM. Returns the old identifier
tierno7edb6752016-03-21 17:37:52 +01001008 '''
tiernoae4a8d12016-07-08 12:30:39 +02001009 #print "osconnector: Getting VM from VIM"
tierno7edb6752016-03-21 17:37:52 +01001010 try:
1011 self._reload_connection()
1012 #delete VM ports attached to this networks before the virtual machine
1013 ports = self.neutron.list_ports(device_id=vm_id)
1014 for p in ports['ports']:
1015 try:
1016 self.neutron.delete_port(p["id"])
1017 except Exception as e:
tiernoae4a8d12016-07-08 12:30:39 +02001018 self.logger.error("Error deleting port: " + type(e).__name__ + ": "+ str(e))
montesmoreno0c8def02016-12-22 12:16:23 +00001019
1020 #commented because detaching the volumes makes the servers.delete not work properly ?!?
1021 #dettach volumes attached
1022 server = self.nova.servers.get(vm_id)
1023 volumes_attached_dict = server._info['os-extended-volumes:volumes_attached']
1024 #for volume in volumes_attached_dict:
1025 # self.cinder.volumes.detach(volume['id'])
1026
tierno7edb6752016-03-21 17:37:52 +01001027 self.nova.servers.delete(vm_id)
montesmoreno0c8def02016-12-22 12:16:23 +00001028
1029 #delete volumes.
1030 #Although having detached them should have them in active status
1031 #we ensure in this loop
1032 keep_waiting = True
1033 elapsed_time = 0
1034 while keep_waiting and elapsed_time < volume_timeout:
1035 keep_waiting = False
1036 for volume in volumes_attached_dict:
1037 if self.cinder.volumes.get(volume['id']).status != 'available':
1038 keep_waiting = True
1039 else:
1040 self.cinder.volumes.delete(volume['id'])
1041 if keep_waiting:
1042 time.sleep(1)
1043 elapsed_time += 1
1044
tiernoae4a8d12016-07-08 12:30:39 +02001045 return vm_id
tierno8e995ce2016-09-22 08:13:00 +00001046 except (nvExceptions.NotFound, ksExceptions.ClientException, nvExceptions.ClientException, ConnectionError) as e:
tiernoae4a8d12016-07-08 12:30:39 +02001047 self._format_exception(e)
tierno7edb6752016-03-21 17:37:52 +01001048 #TODO insert exception vimconn.HTTP_Unauthorized
1049 #if reaching here is because an exception
tierno7edb6752016-03-21 17:37:52 +01001050
tiernoae4a8d12016-07-08 12:30:39 +02001051 def refresh_vms_status(self, vm_list):
1052 '''Get the status of the virtual machines and their interfaces/ports
1053 Params: the list of VM identifiers
1054 Returns a dictionary with:
1055 vm_id: #VIM id of this Virtual Machine
1056 status: #Mandatory. Text with one of:
1057 # DELETED (not found at vim)
1058 # VIM_ERROR (Cannot connect to VIM, VIM response error, ...)
1059 # OTHER (Vim reported other status not understood)
1060 # ERROR (VIM indicates an ERROR status)
1061 # ACTIVE, PAUSED, SUSPENDED, INACTIVE (not running),
1062 # CREATING (on building process), ERROR
1063 # ACTIVE:NoMgmtIP (Active but any of its interface has an IP address
1064 #
1065 error_msg: #Text with VIM error message, if any. Or the VIM connection ERROR
1066 vim_info: #Text with plain information obtained from vim (yaml.safe_dump)
1067 interfaces:
1068 - vim_info: #Text with plain information obtained from vim (yaml.safe_dump)
1069 mac_address: #Text format XX:XX:XX:XX:XX:XX
1070 vim_net_id: #network id where this interface is connected
1071 vim_interface_id: #interface/port VIM id
1072 ip_address: #null, or text with IPv4, IPv6 address
tierno867ffe92017-03-27 12:50:34 +02001073 compute_node: #identification of compute node where PF,VF interface is allocated
1074 pci: #PCI address of the NIC that hosts the PF,VF
1075 vlan: #physical VLAN used for VF
tierno7edb6752016-03-21 17:37:52 +01001076 '''
tiernoae4a8d12016-07-08 12:30:39 +02001077 vm_dict={}
1078 self.logger.debug("refresh_vms status: Getting tenant VM instance information from VIM")
1079 for vm_id in vm_list:
1080 vm={}
1081 try:
1082 vm_vim = self.get_vminstance(vm_id)
1083 if vm_vim['status'] in vmStatus2manoFormat:
1084 vm['status'] = vmStatus2manoFormat[ vm_vim['status'] ]
tierno7edb6752016-03-21 17:37:52 +01001085 else:
tiernoae4a8d12016-07-08 12:30:39 +02001086 vm['status'] = "OTHER"
1087 vm['error_msg'] = "VIM status reported " + vm_vim['status']
tierno8e995ce2016-09-22 08:13:00 +00001088 try:
1089 vm['vim_info'] = yaml.safe_dump(vm_vim, default_flow_style=True, width=256)
1090 except yaml.representer.RepresenterError:
1091 vm['vim_info'] = str(vm_vim)
tiernoae4a8d12016-07-08 12:30:39 +02001092 vm["interfaces"] = []
1093 if vm_vim.get('fault'):
1094 vm['error_msg'] = str(vm_vim['fault'])
1095 #get interfaces
tierno7edb6752016-03-21 17:37:52 +01001096 try:
tiernoae4a8d12016-07-08 12:30:39 +02001097 self._reload_connection()
1098 port_dict=self.neutron.list_ports(device_id=vm_id)
1099 for port in port_dict["ports"]:
1100 interface={}
tierno8e995ce2016-09-22 08:13:00 +00001101 try:
1102 interface['vim_info'] = yaml.safe_dump(port, default_flow_style=True, width=256)
1103 except yaml.representer.RepresenterError:
1104 interface['vim_info'] = str(port)
tiernoae4a8d12016-07-08 12:30:39 +02001105 interface["mac_address"] = port.get("mac_address")
1106 interface["vim_net_id"] = port["network_id"]
1107 interface["vim_interface_id"] = port["id"]
Mike Marchetti5b9da422017-05-02 15:35:47 -04001108 # check if OS-EXT-SRV-ATTR:host is there,
1109 # in case of non-admin credentials, it will be missing
1110 if vm_vim.get('OS-EXT-SRV-ATTR:host'):
1111 interface["compute_node"] = vm_vim['OS-EXT-SRV-ATTR:host']
tierno867ffe92017-03-27 12:50:34 +02001112 interface["pci"] = None
Mike Marchetti5b9da422017-05-02 15:35:47 -04001113
1114 # check if binding:profile is there,
1115 # in case of non-admin credentials, it will be missing
1116 if port.get('binding:profile'):
1117 if port['binding:profile'].get('pci_slot'):
1118 # TODO: At the moment sr-iov pci addresses are converted to PF pci addresses by setting the slot to 0x00
1119 # TODO: This is just a workaround valid for niantinc. Find a better way to do so
1120 # CHANGE DDDD:BB:SS.F to DDDD:BB:00.(F%2) assuming there are 2 ports per nic
1121 pci = port['binding:profile']['pci_slot']
1122 # interface["pci"] = pci[:-4] + "00." + str(int(pci[-1]) % 2)
1123 interface["pci"] = pci
tierno867ffe92017-03-27 12:50:34 +02001124 interface["vlan"] = None
Pablo Montes Moreno3be0b2a2017-03-30 13:22:15 +02001125 #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 +01001126 network = self.neutron.show_network(port["network_id"])
Pablo Montes Moreno3be0b2a2017-03-30 13:22:15 +02001127 if network['network'].get('provider:network_type') == 'vlan' and \
1128 port.get("binding:vnic_type") == "direct":
tierno867ffe92017-03-27 12:50:34 +02001129 interface["vlan"] = network['network'].get('provider:segmentation_id')
tiernoae4a8d12016-07-08 12:30:39 +02001130 ips=[]
1131 #look for floating ip address
1132 floating_ip_dict = self.neutron.list_floatingips(port_id=port["id"])
1133 if floating_ip_dict.get("floatingips"):
1134 ips.append(floating_ip_dict["floatingips"][0].get("floating_ip_address") )
tierno7edb6752016-03-21 17:37:52 +01001135
tiernoae4a8d12016-07-08 12:30:39 +02001136 for subnet in port["fixed_ips"]:
1137 ips.append(subnet["ip_address"])
1138 interface["ip_address"] = ";".join(ips)
1139 vm["interfaces"].append(interface)
1140 except Exception as e:
1141 self.logger.error("Error getting vm interface information " + type(e).__name__ + ": "+ str(e))
1142 except vimconn.vimconnNotFoundException as e:
1143 self.logger.error("Exception getting vm status: %s", str(e))
1144 vm['status'] = "DELETED"
1145 vm['error_msg'] = str(e)
1146 except vimconn.vimconnException as e:
1147 self.logger.error("Exception getting vm status: %s", str(e))
1148 vm['status'] = "VIM_ERROR"
1149 vm['error_msg'] = str(e)
1150 vm_dict[vm_id] = vm
1151 return vm_dict
tierno7edb6752016-03-21 17:37:52 +01001152
tiernoae4a8d12016-07-08 12:30:39 +02001153 def action_vminstance(self, vm_id, action_dict):
tierno7edb6752016-03-21 17:37:52 +01001154 '''Send and action over a VM instance from VIM
tiernoae4a8d12016-07-08 12:30:39 +02001155 Returns the vm_id if the action was successfully sent to the VIM'''
1156 self.logger.debug("Action over VM '%s': %s", vm_id, str(action_dict))
tierno7edb6752016-03-21 17:37:52 +01001157 try:
1158 self._reload_connection()
1159 server = self.nova.servers.find(id=vm_id)
1160 if "start" in action_dict:
1161 if action_dict["start"]=="rebuild":
1162 server.rebuild()
1163 else:
1164 if server.status=="PAUSED":
1165 server.unpause()
1166 elif server.status=="SUSPENDED":
1167 server.resume()
1168 elif server.status=="SHUTOFF":
1169 server.start()
1170 elif "pause" in action_dict:
1171 server.pause()
1172 elif "resume" in action_dict:
1173 server.resume()
1174 elif "shutoff" in action_dict or "shutdown" in action_dict:
1175 server.stop()
1176 elif "forceOff" in action_dict:
1177 server.stop() #TODO
1178 elif "terminate" in action_dict:
1179 server.delete()
1180 elif "createImage" in action_dict:
1181 server.create_image()
1182 #"path":path_schema,
1183 #"description":description_schema,
1184 #"name":name_schema,
1185 #"metadata":metadata_schema,
1186 #"imageRef": id_schema,
1187 #"disk": {"oneOf":[{"type": "null"}, {"type":"string"}] },
1188 elif "rebuild" in action_dict:
1189 server.rebuild(server.image['id'])
1190 elif "reboot" in action_dict:
1191 server.reboot() #reboot_type='SOFT'
1192 elif "console" in action_dict:
1193 console_type = action_dict["console"]
1194 if console_type == None or console_type == "novnc":
1195 console_dict = server.get_vnc_console("novnc")
1196 elif console_type == "xvpvnc":
1197 console_dict = server.get_vnc_console(console_type)
1198 elif console_type == "rdp-html5":
1199 console_dict = server.get_rdp_console(console_type)
1200 elif console_type == "spice-html5":
1201 console_dict = server.get_spice_console(console_type)
1202 else:
tiernoae4a8d12016-07-08 12:30:39 +02001203 raise vimconn.vimconnException("console type '{}' not allowed".format(console_type),
1204 http_code=vimconn.HTTP_Bad_Request)
tierno7edb6752016-03-21 17:37:52 +01001205 try:
1206 console_url = console_dict["console"]["url"]
1207 #parse console_url
1208 protocol_index = console_url.find("//")
1209 suffix_index = console_url[protocol_index+2:].find("/") + protocol_index+2
1210 port_index = console_url[protocol_index+2:suffix_index].find(":") + protocol_index+2
1211 if protocol_index < 0 or port_index<0 or suffix_index<0:
tiernoae4a8d12016-07-08 12:30:39 +02001212 raise vimconn.vimconnException("Unexpected response from VIM " + str(console_dict))
tierno7edb6752016-03-21 17:37:52 +01001213 console_dict2={"protocol": console_url[0:protocol_index],
1214 "server": console_url[protocol_index+2 : port_index],
1215 "port": int(console_url[port_index+1 : suffix_index]),
1216 "suffix": console_url[suffix_index+1:]
1217 }
tiernoae4a8d12016-07-08 12:30:39 +02001218 return console_dict2
1219 except Exception as e:
1220 raise vimconn.vimconnException("Unexpected response from VIM " + str(console_dict))
tierno7edb6752016-03-21 17:37:52 +01001221
tiernoae4a8d12016-07-08 12:30:39 +02001222 return vm_id
tierno8e995ce2016-09-22 08:13:00 +00001223 except (ksExceptions.ClientException, nvExceptions.ClientException, nvExceptions.NotFound, ConnectionError) as e:
tiernoae4a8d12016-07-08 12:30:39 +02001224 self._format_exception(e)
1225 #TODO insert exception vimconn.HTTP_Unauthorized
1226
1227#NOT USED FUNCTIONS
1228
1229 def new_external_port(self, port_data):
1230 #TODO openstack if needed
1231 '''Adds a external port to VIM'''
1232 '''Returns the port identifier'''
1233 return -vimconn.HTTP_Internal_Server_Error, "osconnector.new_external_port() not implemented"
1234
1235 def connect_port_network(self, port_id, network_id, admin=False):
1236 #TODO openstack if needed
1237 '''Connects a external port to a network'''
1238 '''Returns status code of the VIM response'''
1239 return -vimconn.HTTP_Internal_Server_Error, "osconnector.connect_port_network() not implemented"
1240
1241 def new_user(self, user_name, user_passwd, tenant_id=None):
1242 '''Adds a new user to openstack VIM'''
1243 '''Returns the user identifier'''
1244 self.logger.debug("osconnector: Adding a new user to VIM")
1245 try:
1246 self._reload_connection()
1247 user=self.keystone.users.create(user_name, user_passwd, tenant_id=tenant_id)
1248 #self.keystone.tenants.add_user(self.k_creds["username"], #role)
1249 return user.id
1250 except ksExceptions.ConnectionError as e:
1251 error_value=-vimconn.HTTP_Bad_Request
1252 error_text= type(e).__name__ + ": "+ (str(e) if len(e.args)==0 else str(e.args[0]))
1253 except ksExceptions.ClientException as e: #TODO remove
tierno7edb6752016-03-21 17:37:52 +01001254 error_value=-vimconn.HTTP_Bad_Request
1255 error_text= type(e).__name__ + ": "+ (str(e) if len(e.args)==0 else str(e.args[0]))
1256 #TODO insert exception vimconn.HTTP_Unauthorized
1257 #if reaching here is because an exception
1258 if self.debug:
tiernoae4a8d12016-07-08 12:30:39 +02001259 self.logger.debug("new_user " + error_text)
tierno7edb6752016-03-21 17:37:52 +01001260 return error_value, error_text
tiernoae4a8d12016-07-08 12:30:39 +02001261
1262 def delete_user(self, user_id):
1263 '''Delete a user from openstack VIM'''
1264 '''Returns the user identifier'''
1265 if self.debug:
1266 print "osconnector: Deleting a user from VIM"
1267 try:
1268 self._reload_connection()
1269 self.keystone.users.delete(user_id)
1270 return 1, user_id
1271 except ksExceptions.ConnectionError as e:
1272 error_value=-vimconn.HTTP_Bad_Request
1273 error_text= type(e).__name__ + ": "+ (str(e) if len(e.args)==0 else str(e.args[0]))
1274 except ksExceptions.NotFound as e:
1275 error_value=-vimconn.HTTP_Not_Found
1276 error_text= type(e).__name__ + ": "+ (str(e) if len(e.args)==0 else str(e.args[0]))
1277 except ksExceptions.ClientException as e: #TODO remove
1278 error_value=-vimconn.HTTP_Bad_Request
1279 error_text= type(e).__name__ + ": "+ (str(e) if len(e.args)==0 else str(e.args[0]))
1280 #TODO insert exception vimconn.HTTP_Unauthorized
1281 #if reaching here is because an exception
1282 if self.debug:
1283 print "delete_tenant " + error_text
1284 return error_value, error_text
1285
tierno7edb6752016-03-21 17:37:52 +01001286 def get_hosts_info(self):
1287 '''Get the information of deployed hosts
1288 Returns the hosts content'''
1289 if self.debug:
1290 print "osconnector: Getting Host info from VIM"
1291 try:
1292 h_list=[]
1293 self._reload_connection()
1294 hypervisors = self.nova.hypervisors.list()
1295 for hype in hypervisors:
1296 h_list.append( hype.to_dict() )
1297 return 1, {"hosts":h_list}
1298 except nvExceptions.NotFound as e:
1299 error_value=-vimconn.HTTP_Not_Found
1300 error_text= (str(e) if len(e.args)==0 else str(e.args[0]))
1301 except (ksExceptions.ClientException, nvExceptions.ClientException) as e:
1302 error_value=-vimconn.HTTP_Bad_Request
1303 error_text= type(e).__name__ + ": "+ (str(e) if len(e.args)==0 else str(e.args[0]))
1304 #TODO insert exception vimconn.HTTP_Unauthorized
1305 #if reaching here is because an exception
1306 if self.debug:
1307 print "get_hosts_info " + error_text
1308 return error_value, error_text
1309
1310 def get_hosts(self, vim_tenant):
1311 '''Get the hosts and deployed instances
1312 Returns the hosts content'''
1313 r, hype_dict = self.get_hosts_info()
1314 if r<0:
1315 return r, hype_dict
1316 hypervisors = hype_dict["hosts"]
1317 try:
1318 servers = self.nova.servers.list()
1319 for hype in hypervisors:
1320 for server in servers:
1321 if server.to_dict()['OS-EXT-SRV-ATTR:hypervisor_hostname']==hype['hypervisor_hostname']:
1322 if 'vm' in hype:
1323 hype['vm'].append(server.id)
1324 else:
1325 hype['vm'] = [server.id]
1326 return 1, hype_dict
1327 except nvExceptions.NotFound as e:
1328 error_value=-vimconn.HTTP_Not_Found
1329 error_text= (str(e) if len(e.args)==0 else str(e.args[0]))
1330 except (ksExceptions.ClientException, nvExceptions.ClientException) as e:
1331 error_value=-vimconn.HTTP_Bad_Request
1332 error_text= type(e).__name__ + ": "+ (str(e) if len(e.args)==0 else str(e.args[0]))
1333 #TODO insert exception vimconn.HTTP_Unauthorized
1334 #if reaching here is because an exception
1335 if self.debug:
1336 print "get_hosts " + error_text
1337 return error_value, error_text
1338
tierno7edb6752016-03-21 17:37:52 +01001339