blob: 0305c059455ea6017549d83b8ed11694224c5eb4 [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
garciadeblas05a1a612017-07-23 20:26:28 +020067server_timeout = 300
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')
tiernob39d49e2017-08-02 14:02:15 +020092 self.glancev1 = self.session.get('glancev1')
tiernof716aea2017-06-21 18:01:40 +020093 self.keystone = self.session.get('keystone')
94 self.api_version3 = self.session.get('api_version3')
montesmoreno0c8def02016-12-22 12:16:23 +000095
tierno73ad9e42016-09-12 18:11:11 +020096 self.logger = logging.getLogger('openmano.vim.openstack')
tiernofe789902016-09-29 14:20:44 +000097 if log_level:
98 self.logger.setLevel( getattr(logging, log_level) )
tiernof716aea2017-06-21 18:01:40 +020099
100 def __getitem__(self, index):
101 """Get individuals parameters.
102 Throw KeyError"""
103 if index == 'project_domain_id':
104 return self.config.get("project_domain_id")
105 elif index == 'user_domain_id':
106 return self.config.get("user_domain_id")
107 else:
tierno76a3c312017-06-29 16:42:15 +0200108 return vimconn.vimconnector.__getitem__(self, index)
tiernof716aea2017-06-21 18:01:40 +0200109
110 def __setitem__(self, index, value):
111 """Set individuals parameters and it is marked as dirty so to force connection reload.
112 Throw KeyError"""
113 if index == 'project_domain_id':
114 self.config["project_domain_id"] = value
115 elif index == 'user_domain_id':
116 self.config["user_domain_id"] = value
117 else:
118 vimconn.vimconnector.__setitem__(self, index, value)
tiernob5cef372017-06-19 15:52:22 +0200119 self.session['reload_client'] = True
tiernof716aea2017-06-21 18:01:40 +0200120
tierno7edb6752016-03-21 17:37:52 +0100121 def _reload_connection(self):
122 '''Called before any operation, it check if credentials has changed
123 Throw keystoneclient.apiclient.exceptions.AuthorizationFailure
124 '''
125 #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 +0200126 if self.session['reload_client']:
tiernof716aea2017-06-21 18:01:40 +0200127 if self.config.get('APIversion'):
128 self.api_version3 = self.config['APIversion'] == 'v3.3' or self.config['APIversion'] == '3'
129 else: # get from ending auth_url that end with v3 or with v2.0
130 self.api_version3 = self.url.split("/")[-1] == "v3"
131 self.session['api_version3'] = self.api_version3
132 if self.api_version3:
133 auth = v3.Password(auth_url=self.url,
tiernob5cef372017-06-19 15:52:22 +0200134 username=self.user,
135 password=self.passwd,
136 project_name=self.tenant_name,
137 project_id=self.tenant_id,
138 project_domain_id=self.config.get('project_domain_id', 'default'),
139 user_domain_id=self.config.get('user_domain_id', 'default'))
ahmadsa95baa272016-11-30 09:14:11 +0500140 else:
tiernof716aea2017-06-21 18:01:40 +0200141 auth = v2.Password(auth_url=self.url,
tiernob5cef372017-06-19 15:52:22 +0200142 username=self.user,
143 password=self.passwd,
144 tenant_name=self.tenant_name,
145 tenant_id=self.tenant_id)
146 sess = session.Session(auth=auth, verify=not self.insecure)
tiernof716aea2017-06-21 18:01:40 +0200147 if self.api_version3:
148 self.keystone = ksClient_v3.Client(session=sess)
149 else:
150 self.keystone = ksClient_v2.Client(session=sess)
151 self.session['keystone'] = self.keystone
tiernob5cef372017-06-19 15:52:22 +0200152 self.nova = self.session['nova'] = nClient.Client("2.1", session=sess)
153 self.neutron = self.session['neutron'] = neClient.Client('2.0', session=sess)
154 self.cinder = self.session['cinder'] = cClient.Client(2, session=sess)
155 self.glance = self.session['glance'] = glClient.Client(2, session=sess)
tiernob39d49e2017-08-02 14:02:15 +0200156 self.glancev1 = self.session['glancev1'] = glClient.Client('1', session=sess)
tiernob5cef372017-06-19 15:52:22 +0200157 self.session['reload_client'] = False
158 self.persistent_info['session'] = self.session
ahmadsa95baa272016-11-30 09:14:11 +0500159
tierno7edb6752016-03-21 17:37:52 +0100160 def __net_os2mano(self, net_list_dict):
161 '''Transform the net openstack format to mano format
162 net_list_dict can be a list of dict or a single dict'''
163 if type(net_list_dict) is dict:
164 net_list_=(net_list_dict,)
165 elif type(net_list_dict) is list:
166 net_list_=net_list_dict
167 else:
168 raise TypeError("param net_list_dict must be a list or a dictionary")
169 for net in net_list_:
170 if net.get('provider:network_type') == "vlan":
171 net['type']='data'
172 else:
173 net['type']='bridge'
Pablo Montes Moreno6a7785b2017-07-03 10:44:30 +0200174
tiernoae4a8d12016-07-08 12:30:39 +0200175 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))
Pablo Montes Moreno6a7785b2017-07-03 10:44:30 +0200188 elif isinstance(exception, vimconn.vimconnException):
189 raise
tiernof716aea2017-06-21 18:01:40 +0200190 else: # ()
tiernob84cbdc2017-07-07 14:30:30 +0200191 self.logger.error("General Exception " + str(exception), exc_info=True)
tiernoae4a8d12016-07-08 12:30:39 +0200192 raise vimconn.vimconnConnectionException(type(exception).__name__ + ": " + str(exception))
193
194 def get_tenant_list(self, filter_dict={}):
195 '''Obtain tenants of VIM
196 filter_dict can contain the following keys:
197 name: filter by tenant name
198 id: filter by tenant uuid/id
199 <other VIM specific>
200 Returns the tenant list of dictionaries: [{'name':'<name>, 'id':'<id>, ...}, ...]
201 '''
ahmadsa95baa272016-11-30 09:14:11 +0500202 self.logger.debug("Getting tenants from VIM filter: '%s'", str(filter_dict))
tiernoae4a8d12016-07-08 12:30:39 +0200203 try:
204 self._reload_connection()
tiernof716aea2017-06-21 18:01:40 +0200205 if self.api_version3:
206 project_class_list = self.keystone.projects.list(name=filter_dict.get("name"))
ahmadsa95baa272016-11-30 09:14:11 +0500207 else:
tiernof716aea2017-06-21 18:01:40 +0200208 project_class_list = self.keystone.tenants.findall(**filter_dict)
ahmadsa95baa272016-11-30 09:14:11 +0500209 project_list=[]
210 for project in project_class_list:
tiernof716aea2017-06-21 18:01:40 +0200211 if filter_dict.get('id') and filter_dict["id"] != project.id:
212 continue
ahmadsa95baa272016-11-30 09:14:11 +0500213 project_list.append(project.to_dict())
214 return project_list
tiernof716aea2017-06-21 18:01:40 +0200215 except (ksExceptions.ConnectionError, ksExceptions.ClientException, ConnectionError) as e:
tiernoae4a8d12016-07-08 12:30:39 +0200216 self._format_exception(e)
217
218 def new_tenant(self, tenant_name, tenant_description):
219 '''Adds a new tenant to openstack VIM. Returns the tenant identifier'''
220 self.logger.debug("Adding a new tenant name: %s", tenant_name)
221 try:
222 self._reload_connection()
tiernof716aea2017-06-21 18:01:40 +0200223 if self.api_version3:
224 project = self.keystone.projects.create(tenant_name, self.config.get("project_domain_id", "default"),
225 description=tenant_description, is_domain=False)
ahmadsa95baa272016-11-30 09:14:11 +0500226 else:
tiernof716aea2017-06-21 18:01:40 +0200227 project = self.keystone.tenants.create(tenant_name, tenant_description)
ahmadsa95baa272016-11-30 09:14:11 +0500228 return project.id
tierno8e995ce2016-09-22 08:13:00 +0000229 except (ksExceptions.ConnectionError, ksExceptions.ClientException, ConnectionError) as e:
tiernoae4a8d12016-07-08 12:30:39 +0200230 self._format_exception(e)
231
232 def delete_tenant(self, tenant_id):
233 '''Delete a tenant from openstack VIM. Returns the old tenant identifier'''
234 self.logger.debug("Deleting tenant %s from VIM", tenant_id)
235 try:
236 self._reload_connection()
tiernof716aea2017-06-21 18:01:40 +0200237 if self.api_version3:
ahmadsa95baa272016-11-30 09:14:11 +0500238 self.keystone.projects.delete(tenant_id)
239 else:
240 self.keystone.tenants.delete(tenant_id)
tiernoae4a8d12016-07-08 12:30:39 +0200241 return tenant_id
tierno8e995ce2016-09-22 08:13:00 +0000242 except (ksExceptions.ConnectionError, ksExceptions.ClientException, ConnectionError) as e:
tiernoae4a8d12016-07-08 12:30:39 +0200243 self._format_exception(e)
ahmadsa95baa272016-11-30 09:14:11 +0500244
garciadeblas9f8456e2016-09-05 05:02:59 +0200245 def new_network(self,net_name, net_type, ip_profile=None, shared=False, vlan=None):
tiernoae4a8d12016-07-08 12:30:39 +0200246 '''Adds a tenant network to VIM. Returns the network identifier'''
247 self.logger.debug("Adding a new network to VIM name '%s', type '%s'", net_name, net_type)
garciadeblasedca7b32016-09-29 14:01:52 +0000248 #self.logger.debug(">>>>>>>>>>>>>>>>>> IP profile %s", str(ip_profile))
tierno7edb6752016-03-21 17:37:52 +0100249 try:
garciadeblasedca7b32016-09-29 14:01:52 +0000250 new_net = None
tierno7edb6752016-03-21 17:37:52 +0100251 self._reload_connection()
252 network_dict = {'name': net_name, 'admin_state_up': True}
253 if net_type=="data" or net_type=="ptp":
254 if self.config.get('dataplane_physical_net') == None:
tiernoae4a8d12016-07-08 12:30:39 +0200255 raise vimconn.vimconnConflictException("You must provide a 'dataplane_physical_net' at config value before creating sriov network")
tierno7edb6752016-03-21 17:37:52 +0100256 network_dict["provider:physical_network"] = self.config['dataplane_physical_net'] #"physnet_sriov" #TODO physical
257 network_dict["provider:network_type"] = "vlan"
258 if vlan!=None:
259 network_dict["provider:network_type"] = vlan
tiernoae4a8d12016-07-08 12:30:39 +0200260 network_dict["shared"]=shared
tierno7edb6752016-03-21 17:37:52 +0100261 new_net=self.neutron.create_network({'network':network_dict})
262 #print new_net
garciadeblas9f8456e2016-09-05 05:02:59 +0200263 #create subnetwork, even if there is no profile
264 if not ip_profile:
265 ip_profile = {}
266 if 'subnet_address' not in ip_profile:
garciadeblas2299e3b2017-01-26 14:35:55 +0000267 #Fake subnet is required
268 subnet_rand = random.randint(0, 255)
269 ip_profile['subnet_address'] = "192.168.{}.0/24".format(subnet_rand)
garciadeblas9f8456e2016-09-05 05:02:59 +0200270 if 'ip_version' not in ip_profile:
271 ip_profile['ip_version'] = "IPv4"
tiernoa1fb4462017-06-30 12:25:50 +0200272 subnet = {"name":net_name+"-subnet",
tierno7edb6752016-03-21 17:37:52 +0100273 "network_id": new_net["network"]["id"],
garciadeblas9f8456e2016-09-05 05:02:59 +0200274 "ip_version": 4 if ip_profile['ip_version']=="IPv4" else 6,
275 "cidr": ip_profile['subnet_address']
tierno7edb6752016-03-21 17:37:52 +0100276 }
tiernoa1fb4462017-06-30 12:25:50 +0200277 # Gateway should be set to None if not needed. Otherwise openstack assigns one by default
278 subnet['gateway_ip'] = ip_profile.get('gateway_address')
garciadeblasedca7b32016-09-29 14:01:52 +0000279 if ip_profile.get('dns_address'):
tierno455612d2017-05-30 16:40:10 +0200280 subnet['dns_nameservers'] = ip_profile['dns_address'].split(";")
garciadeblas9f8456e2016-09-05 05:02:59 +0200281 if 'dhcp_enabled' in ip_profile:
282 subnet['enable_dhcp'] = False if ip_profile['dhcp_enabled']=="false" else True
283 if 'dhcp_start_address' in ip_profile:
tiernoa1fb4462017-06-30 12:25:50 +0200284 subnet['allocation_pools'] = []
garciadeblas9f8456e2016-09-05 05:02:59 +0200285 subnet['allocation_pools'].append(dict())
286 subnet['allocation_pools'][0]['start'] = ip_profile['dhcp_start_address']
287 if 'dhcp_count' in ip_profile:
288 #parts = ip_profile['dhcp_start_address'].split('.')
289 #ip_int = (int(parts[0]) << 24) + (int(parts[1]) << 16) + (int(parts[2]) << 8) + int(parts[3])
290 ip_int = int(netaddr.IPAddress(ip_profile['dhcp_start_address']))
garciadeblas21d795b2016-09-29 17:31:46 +0200291 ip_int += ip_profile['dhcp_count'] - 1
garciadeblas9f8456e2016-09-05 05:02:59 +0200292 ip_str = str(netaddr.IPAddress(ip_int))
293 subnet['allocation_pools'][0]['end'] = ip_str
garciadeblasedca7b32016-09-29 14:01:52 +0000294 #self.logger.debug(">>>>>>>>>>>>>>>>>> Subnet: %s", str(subnet))
tierno7edb6752016-03-21 17:37:52 +0100295 self.neutron.create_subnet({"subnet": subnet} )
tiernoae4a8d12016-07-08 12:30:39 +0200296 return new_net["network"]["id"]
tierno8e995ce2016-09-22 08:13:00 +0000297 except (neExceptions.ConnectionFailed, ksExceptions.ClientException, neExceptions.NeutronException, ConnectionError) as e:
garciadeblasedca7b32016-09-29 14:01:52 +0000298 if new_net:
299 self.neutron.delete_network(new_net['network']['id'])
tiernoae4a8d12016-07-08 12:30:39 +0200300 self._format_exception(e)
tierno7edb6752016-03-21 17:37:52 +0100301
302 def get_network_list(self, filter_dict={}):
303 '''Obtain tenant networks of VIM
304 Filter_dict can be:
305 name: network name
306 id: network uuid
307 shared: boolean
308 tenant_id: tenant
309 admin_state_up: boolean
310 status: 'ACTIVE'
311 Returns the network list of dictionaries
312 '''
tiernoae4a8d12016-07-08 12:30:39 +0200313 self.logger.debug("Getting network from VIM filter: '%s'", str(filter_dict))
tierno7edb6752016-03-21 17:37:52 +0100314 try:
315 self._reload_connection()
tiernof716aea2017-06-21 18:01:40 +0200316 if self.api_version3 and "tenant_id" in filter_dict:
317 filter_dict['project_id'] = filter_dict.pop('tenant_id') #TODO check
tierno7edb6752016-03-21 17:37:52 +0100318 net_dict=self.neutron.list_networks(**filter_dict)
319 net_list=net_dict["networks"]
320 self.__net_os2mano(net_list)
tiernoae4a8d12016-07-08 12:30:39 +0200321 return net_list
tierno8e995ce2016-09-22 08:13:00 +0000322 except (neExceptions.ConnectionFailed, ksExceptions.ClientException, neExceptions.NeutronException, ConnectionError) as e:
tiernoae4a8d12016-07-08 12:30:39 +0200323 self._format_exception(e)
tierno7edb6752016-03-21 17:37:52 +0100324
tiernoae4a8d12016-07-08 12:30:39 +0200325 def get_network(self, net_id):
326 '''Obtain details of network from VIM
327 Returns the network information from a network id'''
328 self.logger.debug(" Getting tenant network %s from VIM", net_id)
tierno7edb6752016-03-21 17:37:52 +0100329 filter_dict={"id": net_id}
tiernoae4a8d12016-07-08 12:30:39 +0200330 net_list = self.get_network_list(filter_dict)
tierno7edb6752016-03-21 17:37:52 +0100331 if len(net_list)==0:
tiernoae4a8d12016-07-08 12:30:39 +0200332 raise vimconn.vimconnNotFoundException("Network '{}' not found".format(net_id))
tierno7edb6752016-03-21 17:37:52 +0100333 elif len(net_list)>1:
tiernoae4a8d12016-07-08 12:30:39 +0200334 raise vimconn.vimconnConflictException("Found more than one network with this criteria")
tierno7edb6752016-03-21 17:37:52 +0100335 net = net_list[0]
336 subnets=[]
337 for subnet_id in net.get("subnets", () ):
338 try:
339 subnet = self.neutron.show_subnet(subnet_id)
340 except Exception as e:
tiernoae4a8d12016-07-08 12:30:39 +0200341 self.logger.error("osconnector.get_network(): Error getting subnet %s %s" % (net_id, str(e)))
342 subnet = {"id": subnet_id, "fault": str(e)}
tierno7edb6752016-03-21 17:37:52 +0100343 subnets.append(subnet)
344 net["subnets"] = subnets
Pablo Montes Moreno51e553b2017-03-23 16:39:12 +0100345 net["encapsulation"] = net.get('provider:network_type')
Pablo Montes Moreno3fbff9b2017-03-08 11:28:15 +0100346 net["segmentation_id"] = net.get('provider:segmentation_id')
tiernoae4a8d12016-07-08 12:30:39 +0200347 return net
tierno7edb6752016-03-21 17:37:52 +0100348
tiernoae4a8d12016-07-08 12:30:39 +0200349 def delete_network(self, net_id):
350 '''Deletes a tenant network from VIM. Returns the old network identifier'''
351 self.logger.debug("Deleting network '%s' from VIM", net_id)
tierno7edb6752016-03-21 17:37:52 +0100352 try:
353 self._reload_connection()
354 #delete VM ports attached to this networks before the network
355 ports = self.neutron.list_ports(network_id=net_id)
356 for p in ports['ports']:
357 try:
358 self.neutron.delete_port(p["id"])
359 except Exception as e:
tiernoae4a8d12016-07-08 12:30:39 +0200360 self.logger.error("Error deleting port %s: %s", p["id"], str(e))
tierno7edb6752016-03-21 17:37:52 +0100361 self.neutron.delete_network(net_id)
tiernoae4a8d12016-07-08 12:30:39 +0200362 return net_id
363 except (neExceptions.ConnectionFailed, neExceptions.NetworkNotFoundClient, neExceptions.NeutronException,
tierno8e995ce2016-09-22 08:13:00 +0000364 ksExceptions.ClientException, neExceptions.NeutronException, ConnectionError) as e:
tiernoae4a8d12016-07-08 12:30:39 +0200365 self._format_exception(e)
tierno7edb6752016-03-21 17:37:52 +0100366
tiernoae4a8d12016-07-08 12:30:39 +0200367 def refresh_nets_status(self, net_list):
368 '''Get the status of the networks
369 Params: the list of network identifiers
370 Returns a dictionary with:
371 net_id: #VIM id of this network
372 status: #Mandatory. Text with one of:
373 # DELETED (not found at vim)
374 # VIM_ERROR (Cannot connect to VIM, VIM response error, ...)
375 # OTHER (Vim reported other status not understood)
376 # ERROR (VIM indicates an ERROR status)
377 # ACTIVE, INACTIVE, DOWN (admin down),
378 # BUILD (on building process)
379 #
380 error_msg: #Text with VIM error message, if any. Or the VIM connection ERROR
381 vim_info: #Text with plain information obtained from vim (yaml.safe_dump)
382
383 '''
384 net_dict={}
385 for net_id in net_list:
386 net = {}
387 try:
388 net_vim = self.get_network(net_id)
389 if net_vim['status'] in netStatus2manoFormat:
390 net["status"] = netStatus2manoFormat[ net_vim['status'] ]
391 else:
392 net["status"] = "OTHER"
393 net["error_msg"] = "VIM status reported " + net_vim['status']
394
tierno8e995ce2016-09-22 08:13:00 +0000395 if net['status'] == "ACTIVE" and not net_vim['admin_state_up']:
tiernoae4a8d12016-07-08 12:30:39 +0200396 net['status'] = 'DOWN'
tierno8e995ce2016-09-22 08:13:00 +0000397 try:
398 net['vim_info'] = yaml.safe_dump(net_vim, default_flow_style=True, width=256)
399 except yaml.representer.RepresenterError:
400 net['vim_info'] = str(net_vim)
tiernoae4a8d12016-07-08 12:30:39 +0200401 if net_vim.get('fault'): #TODO
402 net['error_msg'] = str(net_vim['fault'])
403 except vimconn.vimconnNotFoundException as e:
404 self.logger.error("Exception getting net status: %s", str(e))
405 net['status'] = "DELETED"
406 net['error_msg'] = str(e)
407 except vimconn.vimconnException as e:
408 self.logger.error("Exception getting net status: %s", str(e))
409 net['status'] = "VIM_ERROR"
410 net['error_msg'] = str(e)
411 net_dict[net_id] = net
412 return net_dict
413
414 def get_flavor(self, flavor_id):
415 '''Obtain flavor details from the VIM. Returns the flavor dict details'''
416 self.logger.debug("Getting flavor '%s'", flavor_id)
tierno7edb6752016-03-21 17:37:52 +0100417 try:
418 self._reload_connection()
419 flavor = self.nova.flavors.find(id=flavor_id)
420 #TODO parse input and translate to VIM format (openmano_schemas.new_vminstance_response_schema)
tiernoae4a8d12016-07-08 12:30:39 +0200421 return flavor.to_dict()
tierno8e995ce2016-09-22 08:13:00 +0000422 except (nvExceptions.NotFound, nvExceptions.ClientException, ksExceptions.ClientException, ConnectionError) as e:
tiernoae4a8d12016-07-08 12:30:39 +0200423 self._format_exception(e)
tierno7edb6752016-03-21 17:37:52 +0100424
tiernocf157a82017-01-30 14:07:06 +0100425 def get_flavor_id_from_data(self, flavor_dict):
426 """Obtain flavor id that match the flavor description
427 Returns the flavor_id or raises a vimconnNotFoundException
tiernoe26fc7a2017-05-30 14:43:03 +0200428 flavor_dict: contains the required ram, vcpus, disk
429 If 'use_existing_flavors' is set to True at config, the closer flavor that provides same or more ram, vcpus
430 and disk is returned. Otherwise a flavor with exactly same ram, vcpus and disk is returned or a
431 vimconnNotFoundException is raised
tiernocf157a82017-01-30 14:07:06 +0100432 """
tiernoe26fc7a2017-05-30 14:43:03 +0200433 exact_match = False if self.config.get('use_existing_flavors') else True
tiernocf157a82017-01-30 14:07:06 +0100434 try:
435 self._reload_connection()
tiernoe26fc7a2017-05-30 14:43:03 +0200436 flavor_candidate_id = None
437 flavor_candidate_data = (10000, 10000, 10000)
438 flavor_target = (flavor_dict["ram"], flavor_dict["vcpus"], flavor_dict["disk"])
439 # numa=None
440 numas = flavor_dict.get("extended", {}).get("numas")
tiernocf157a82017-01-30 14:07:06 +0100441 if numas:
442 #TODO
443 raise vimconn.vimconnNotFoundException("Flavor with EPA still not implemted")
444 # if len(numas) > 1:
445 # raise vimconn.vimconnNotFoundException("Cannot find any flavor with more than one numa")
446 # numa=numas[0]
447 # numas = extended.get("numas")
448 for flavor in self.nova.flavors.list():
449 epa = flavor.get_keys()
450 if epa:
451 continue
tiernoe26fc7a2017-05-30 14:43:03 +0200452 # TODO
453 flavor_data = (flavor.ram, flavor.vcpus, flavor.disk)
454 if flavor_data == flavor_target:
455 return flavor.id
456 elif not exact_match and flavor_target < flavor_data < flavor_candidate_data:
457 flavor_candidate_id = flavor.id
458 flavor_candidate_data = flavor_data
459 if not exact_match and flavor_candidate_id:
460 return flavor_candidate_id
tiernocf157a82017-01-30 14:07:06 +0100461 raise vimconn.vimconnNotFoundException("Cannot find any flavor matching '{}'".format(str(flavor_dict)))
462 except (nvExceptions.NotFound, nvExceptions.ClientException, ksExceptions.ClientException, ConnectionError) as e:
463 self._format_exception(e)
464
465
tiernoae4a8d12016-07-08 12:30:39 +0200466 def new_flavor(self, flavor_data, change_name_if_used=True):
tierno7edb6752016-03-21 17:37:52 +0100467 '''Adds a tenant flavor to openstack VIM
tiernoae4a8d12016-07-08 12:30:39 +0200468 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 +0100469 Returns the flavor identifier
470 '''
tiernoae4a8d12016-07-08 12:30:39 +0200471 self.logger.debug("Adding flavor '%s'", str(flavor_data))
tierno7edb6752016-03-21 17:37:52 +0100472 retry=0
tiernoae4a8d12016-07-08 12:30:39 +0200473 max_retries=3
tierno7edb6752016-03-21 17:37:52 +0100474 name_suffix = 0
tiernoae4a8d12016-07-08 12:30:39 +0200475 name=flavor_data['name']
476 while retry<max_retries:
tierno7edb6752016-03-21 17:37:52 +0100477 retry+=1
478 try:
479 self._reload_connection()
480 if change_name_if_used:
481 #get used names
482 fl_names=[]
483 fl=self.nova.flavors.list()
484 for f in fl:
485 fl_names.append(f.name)
486 while name in fl_names:
487 name_suffix += 1
tiernoae4a8d12016-07-08 12:30:39 +0200488 name = flavor_data['name']+"-" + str(name_suffix)
tierno7edb6752016-03-21 17:37:52 +0100489
tiernoae4a8d12016-07-08 12:30:39 +0200490 ram = flavor_data.get('ram',64)
491 vcpus = flavor_data.get('vcpus',1)
tierno7edb6752016-03-21 17:37:52 +0100492 numa_properties=None
493
tiernoae4a8d12016-07-08 12:30:39 +0200494 extended = flavor_data.get("extended")
tierno7edb6752016-03-21 17:37:52 +0100495 if extended:
496 numas=extended.get("numas")
497 if numas:
498 numa_nodes = len(numas)
499 if numa_nodes > 1:
500 return -1, "Can not add flavor with more than one numa"
501 numa_properties = {"hw:numa_nodes":str(numa_nodes)}
502 numa_properties["hw:mem_page_size"] = "large"
503 numa_properties["hw:cpu_policy"] = "dedicated"
504 numa_properties["hw:numa_mempolicy"] = "strict"
505 for numa in numas:
506 #overwrite ram and vcpus
507 ram = numa['memory']*1024
Pablo Montes Morenoea1d6232017-05-24 11:33:24 +0200508 #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 +0100509 if 'paired-threads' in numa:
510 vcpus = numa['paired-threads']*2
Pablo Montes Morenoea1d6232017-05-24 11:33:24 +0200511 #cpu_thread_policy "require" implies that the compute node must have an STM architecture
512 numa_properties["hw:cpu_thread_policy"] = "require"
513 numa_properties["hw:cpu_policy"] = "dedicated"
tierno7edb6752016-03-21 17:37:52 +0100514 elif 'cores' in numa:
515 vcpus = numa['cores']
Pablo Montes Morenoea1d6232017-05-24 11:33:24 +0200516 # cpu_thread_policy "prefer" implies that the host must not have an SMT architecture, or a non-SMT architecture will be emulated
517 numa_properties["hw:cpu_thread_policy"] = "isolate"
518 numa_properties["hw:cpu_policy"] = "dedicated"
tierno7edb6752016-03-21 17:37:52 +0100519 elif 'threads' in numa:
520 vcpus = numa['threads']
Pablo Montes Morenoea1d6232017-05-24 11:33:24 +0200521 # cpu_thread_policy "prefer" implies that the host may or may not have an SMT architecture
522 numa_properties["hw:cpu_thread_policy"] = "prefer"
523 numa_properties["hw:cpu_policy"] = "dedicated"
Pablo Montes Moreno3be0b2a2017-03-30 13:22:15 +0200524 # for interface in numa.get("interfaces",() ):
525 # if interface["dedicated"]=="yes":
526 # raise vimconn.vimconnException("Passthrough interfaces are not supported for the openstack connector", http_code=vimconn.HTTP_Service_Unavailable)
527 # #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 +0100528
529 #create flavor
530 new_flavor=self.nova.flavors.create(name,
531 ram,
532 vcpus,
tiernoae4a8d12016-07-08 12:30:39 +0200533 flavor_data.get('disk',1),
534 is_public=flavor_data.get('is_public', True)
tierno7edb6752016-03-21 17:37:52 +0100535 )
536 #add metadata
537 if numa_properties:
538 new_flavor.set_keys(numa_properties)
tiernoae4a8d12016-07-08 12:30:39 +0200539 return new_flavor.id
tierno7edb6752016-03-21 17:37:52 +0100540 except nvExceptions.Conflict as e:
tiernoae4a8d12016-07-08 12:30:39 +0200541 if change_name_if_used and retry < max_retries:
tierno7edb6752016-03-21 17:37:52 +0100542 continue
tiernoae4a8d12016-07-08 12:30:39 +0200543 self._format_exception(e)
tierno7edb6752016-03-21 17:37:52 +0100544 #except nvExceptions.BadRequest as e:
545 except (ksExceptions.ClientException, nvExceptions.ClientException, ConnectionError) as e:
tiernoae4a8d12016-07-08 12:30:39 +0200546 self._format_exception(e)
tierno7edb6752016-03-21 17:37:52 +0100547
tiernoae4a8d12016-07-08 12:30:39 +0200548 def delete_flavor(self,flavor_id):
549 '''Deletes a tenant flavor from openstack VIM. Returns the old flavor_id
tierno7edb6752016-03-21 17:37:52 +0100550 '''
tiernoae4a8d12016-07-08 12:30:39 +0200551 try:
552 self._reload_connection()
553 self.nova.flavors.delete(flavor_id)
554 return flavor_id
555 #except nvExceptions.BadRequest as e:
tierno8e995ce2016-09-22 08:13:00 +0000556 except (nvExceptions.NotFound, ksExceptions.ClientException, nvExceptions.ClientException, ConnectionError) as e:
tiernoae4a8d12016-07-08 12:30:39 +0200557 self._format_exception(e)
tierno7edb6752016-03-21 17:37:52 +0100558
tiernoae4a8d12016-07-08 12:30:39 +0200559 def new_image(self,image_dict):
tierno7edb6752016-03-21 17:37:52 +0100560 '''
tiernoae4a8d12016-07-08 12:30:39 +0200561 Adds a tenant image to VIM. imge_dict is a dictionary with:
562 name: name
563 disk_format: qcow2, vhd, vmdk, raw (by default), ...
564 location: path or URI
565 public: "yes" or "no"
566 metadata: metadata of the image
567 Returns the image_id
tierno7edb6752016-03-21 17:37:52 +0100568 '''
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":
tiernob39d49e2017-08-02 14:02:15 +0200599 new_image = self.glancev1.images.create(name=image_dict['name'], is_public=image_dict.get('public',"yes")=="yes",
tierno7edb6752016-03-21 17:37:52 +0100600 container_format="bare", location=image_dict['location'], disk_format=disk_format)
601 else: #local path
602 with open(image_dict['location']) as fimage:
tiernob39d49e2017-08-02 14:02:15 +0200603 new_image = self.glancev1.images.create(name=image_dict['name'], is_public=image_dict.get('public',"yes")=="yes",
tierno7edb6752016-03-21 17:37:52 +0100604 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
Pablo Montes Moreno6a7785b2017-07-03 10:44:30 +0200675 def __wait_for_vm(self, vm_id, status):
676 """wait until vm is in the desired status and return True.
677 If the VM gets in ERROR status, return false.
678 If the timeout is reached generate an exception"""
679 elapsed_time = 0
680 while elapsed_time < server_timeout:
681 vm_status = self.nova.servers.get(vm_id).status
682 if vm_status == status:
683 return True
684 if vm_status == 'ERROR':
685 return False
686 time.sleep(1)
687 elapsed_time += 1
688
689 # if we exceeded the timeout rollback
690 if elapsed_time >= server_timeout:
691 raise vimconn.vimconnException('Timeout waiting for instance ' + vm_id + ' to get ' + status,
692 http_code=vimconn.HTTP_Request_Timeout)
693
montesmoreno0c8def02016-12-22 12:16:23 +0000694 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 +0100695 '''Adds a VM instance to VIM
696 Params:
697 start: indicates if VM must start or boot in pause mode. Ignored
698 image_id,flavor_id: iamge and flavor uuid
699 net_list: list of interfaces, each one is a dictionary with:
700 name:
701 net_id: network uuid to connect
702 vpci: virtual vcpi to assign, ignored because openstack lack #TODO
703 model: interface model, ignored #TODO
704 mac_address: used for SR-IOV ifaces #TODO for other types
705 use: 'data', 'bridge', 'mgmt'
706 type: 'virtual', 'PF', 'VF', 'VFnotShared'
707 vim_id: filled/added by this function
ahmadsaf853d452016-12-22 11:33:47 +0500708 floating_ip: True/False (or it can be None)
tierno7edb6752016-03-21 17:37:52 +0100709 #TODO ip, security groups
tiernoae4a8d12016-07-08 12:30:39 +0200710 Returns the instance identifier
tierno7edb6752016-03-21 17:37:52 +0100711 '''
tiernofa51c202017-01-27 14:58:17 +0100712 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 +0100713 try:
Pablo Montes Moreno6a7785b2017-07-03 10:44:30 +0200714 server = None
tierno6e116232016-07-18 13:01:40 +0200715 metadata={}
tierno7edb6752016-03-21 17:37:52 +0100716 net_list_vim=[]
Pablo Montes Moreno6a7785b2017-07-03 10:44:30 +0200717 external_network=[] # list of external networks to be connected to instance, later on used to create floating_ip
718 no_secured_ports = [] # List of port-is with port-security disabled
tierno7edb6752016-03-21 17:37:52 +0100719 self._reload_connection()
Pablo Montes Moreno6a7785b2017-07-03 10:44:30 +0200720 metadata_vpci={} # For a specific neutron plugin
tiernob84cbdc2017-07-07 14:30:30 +0200721 block_device_mapping = None
tierno7edb6752016-03-21 17:37:52 +0100722 for net in net_list:
723 if not net.get("net_id"): #skip non connected iface
724 continue
Pablo Montes Moreno3be0b2a2017-03-30 13:22:15 +0200725
726 port_dict={
727 "network_id": net["net_id"],
728 "name": net.get("name"),
729 "admin_state_up": True
730 }
731 if net["type"]=="virtual":
732 if "vpci" in net:
733 metadata_vpci[ net["net_id"] ] = [[ net["vpci"], "" ]]
734 elif net["type"]=="VF": # for VF
735 if "vpci" in net:
736 if "VF" not in metadata_vpci:
737 metadata_vpci["VF"]=[]
738 metadata_vpci["VF"].append([ net["vpci"], "" ])
739 port_dict["binding:vnic_type"]="direct"
Pablo Montes Moreno6a7785b2017-07-03 10:44:30 +0200740 else: # For PT
Pablo Montes Moreno3be0b2a2017-03-30 13:22:15 +0200741 if "vpci" in net:
742 if "PF" not in metadata_vpci:
743 metadata_vpci["PF"]=[]
744 metadata_vpci["PF"].append([ net["vpci"], "" ])
745 port_dict["binding:vnic_type"]="direct-physical"
746 if not port_dict["name"]:
747 port_dict["name"]=name
748 if net.get("mac_address"):
749 port_dict["mac_address"]=net["mac_address"]
Pablo Montes Moreno3be0b2a2017-03-30 13:22:15 +0200750 new_port = self.neutron.create_port({"port": port_dict })
751 net["mac_adress"] = new_port["port"]["mac_address"]
752 net["vim_id"] = new_port["port"]["id"]
tiernob84cbdc2017-07-07 14:30:30 +0200753 # if try to use a network without subnetwork, it will return a emtpy list
754 fixed_ips = new_port["port"].get("fixed_ips")
755 if fixed_ips:
756 net["ip"] = fixed_ips[0].get("ip_address")
757 else:
758 net["ip"] = None
Pablo Montes Moreno3be0b2a2017-03-30 13:22:15 +0200759 net_list_vim.append({"port-id": new_port["port"]["id"]})
760
ahmadsaf853d452016-12-22 11:33:47 +0500761 if net.get('floating_ip', False):
tiernof8383b82017-01-18 15:49:48 +0100762 net['exit_on_floating_ip_error'] = True
ahmadsaf853d452016-12-22 11:33:47 +0500763 external_network.append(net)
tiernof8383b82017-01-18 15:49:48 +0100764 elif net['use'] == 'mgmt' and self.config.get('use_floating_ip'):
765 net['exit_on_floating_ip_error'] = False
766 external_network.append(net)
767
Pablo Montes Moreno6a7785b2017-07-03 10:44:30 +0200768 # If port security is disabled when the port has not yet been attached to the VM, then all vm traffic is dropped.
769 # As a workaround we wait until the VM is active and then disable the port-security
770 if net.get("port_security") == False:
771 no_secured_ports.append(new_port["port"]["id"])
772
tierno7edb6752016-03-21 17:37:52 +0100773 if metadata_vpci:
774 metadata = {"pci_assignement": json.dumps(metadata_vpci)}
tiernoafbced42016-07-23 01:43:53 +0200775 if len(metadata["pci_assignement"]) >255:
tierno6e116232016-07-18 13:01:40 +0200776 #limit the metadata size
777 #metadata["pci_assignement"] = metadata["pci_assignement"][0:255]
778 self.logger.warn("Metadata deleted since it exceeds the expected length (255) ")
779 metadata = {}
tierno7edb6752016-03-21 17:37:52 +0100780
tiernoae4a8d12016-07-08 12:30:39 +0200781 self.logger.debug("name '%s' image_id '%s'flavor_id '%s' net_list_vim '%s' description '%s' metadata %s",
782 name, image_id, flavor_id, str(net_list_vim), description, str(metadata))
tierno7edb6752016-03-21 17:37:52 +0100783
784 security_groups = self.config.get('security_groups')
785 if type(security_groups) is str:
786 security_groups = ( security_groups, )
tierno36c0b172017-01-12 18:32:28 +0100787 #cloud config
788 userdata=None
789 config_drive = None
tiernoa4e1a6e2016-08-31 14:19:40 +0200790 if isinstance(cloud_config, dict):
tierno36c0b172017-01-12 18:32:28 +0100791 if cloud_config.get("user-data"):
792 userdata=cloud_config["user-data"]
793 if cloud_config.get("boot-data-drive") != None:
794 config_drive = cloud_config["boot-data-drive"]
795 if cloud_config.get("config-files") or cloud_config.get("users") or cloud_config.get("key-pairs"):
796 if userdata:
797 raise vimconn.vimconnConflictException("Cloud-config cannot contain both 'userdata' and 'config-files'/'users'/'key-pairs'")
798 userdata_dict={}
799 #default user
800 if cloud_config.get("key-pairs"):
801 userdata_dict["ssh-authorized-keys"] = cloud_config["key-pairs"]
802 userdata_dict["users"] = [{"default": None, "ssh-authorized-keys": cloud_config["key-pairs"] }]
803 if cloud_config.get("users"):
tierno01d0bf52017-01-25 14:27:20 +0100804 if "users" not in userdata_dict:
tierno36c0b172017-01-12 18:32:28 +0100805 userdata_dict["users"] = [ "default" ]
806 for user in cloud_config["users"]:
807 user_info = {
808 "name" : user["name"],
809 "sudo": "ALL = (ALL)NOPASSWD:ALL"
810 }
811 if "user-info" in user:
812 user_info["gecos"] = user["user-info"]
813 if user.get("key-pairs"):
814 user_info["ssh-authorized-keys"] = user["key-pairs"]
815 userdata_dict["users"].append(user_info)
816
817 if cloud_config.get("config-files"):
818 userdata_dict["write_files"] = []
819 for file in cloud_config["config-files"]:
820 file_info = {
821 "path" : file["dest"],
822 "content": file["content"]
823 }
824 if file.get("encoding"):
825 file_info["encoding"] = file["encoding"]
826 if file.get("permissions"):
827 file_info["permissions"] = file["permissions"]
828 if file.get("owner"):
829 file_info["owner"] = file["owner"]
830 userdata_dict["write_files"].append(file_info)
831 userdata = "#cloud-config\n"
832 userdata += yaml.safe_dump(userdata_dict, indent=4, default_flow_style=False)
tiernoa4e1a6e2016-08-31 14:19:40 +0200833 self.logger.debug("userdata: %s", userdata)
834 elif isinstance(cloud_config, str):
835 userdata = cloud_config
montesmoreno0c8def02016-12-22 12:16:23 +0000836
837 #Create additional volumes in case these are present in disk_list
montesmoreno0c8def02016-12-22 12:16:23 +0000838 base_disk_index = ord('b')
839 if disk_list != None:
tiernob84cbdc2017-07-07 14:30:30 +0200840 block_device_mapping = {}
montesmoreno0c8def02016-12-22 12:16:23 +0000841 for disk in disk_list:
842 if 'image_id' in disk:
843 volume = self.cinder.volumes.create(size = disk['size'],name = name + '_vd' +
844 chr(base_disk_index), imageRef = disk['image_id'])
845 else:
846 volume = self.cinder.volumes.create(size=disk['size'], name=name + '_vd' +
847 chr(base_disk_index))
848 block_device_mapping['_vd' + chr(base_disk_index)] = volume.id
849 base_disk_index += 1
850
851 #wait until volumes are with status available
852 keep_waiting = True
853 elapsed_time = 0
854 while keep_waiting and elapsed_time < volume_timeout:
855 keep_waiting = False
856 for volume_id in block_device_mapping.itervalues():
857 if self.cinder.volumes.get(volume_id).status != 'available':
858 keep_waiting = True
859 if keep_waiting:
860 time.sleep(1)
861 elapsed_time += 1
862
863 #if we exceeded the timeout rollback
864 if elapsed_time >= volume_timeout:
865 #delete the volumes we just created
866 for volume_id in block_device_mapping.itervalues():
867 self.cinder.volumes.delete(volume_id)
868
869 #delete ports we just created
870 for net_item in net_list_vim:
871 if 'port-id' in net_item:
montesmorenocf227142017-01-12 12:24:21 +0000872 self.neutron.delete_port(net_item['port-id'])
montesmoreno0c8def02016-12-22 12:16:23 +0000873
874 raise vimconn.vimconnException('Timeout creating volumes for instance ' + name,
875 http_code=vimconn.HTTP_Request_Timeout)
876
Pablo Montes Morenob7490b52017-06-20 10:49:58 +0200877 self.logger.debug("nova.servers.create({}, {}, {}, nics={}, meta={}, security_groups={}," \
878 "availability_zone={}, key_name={}, userdata={}, config_drive={}, " \
879 "block_device_mapping={})".format(name, image_id, flavor_id, net_list_vim,
880 metadata, security_groups, self.config.get('availability_zone'),
881 self.config.get('keypair'), userdata, config_drive, block_device_mapping))
tierno7edb6752016-03-21 17:37:52 +0100882 server = self.nova.servers.create(name, image_id, flavor_id, nics=net_list_vim, meta=metadata,
montesmoreno0c8def02016-12-22 12:16:23 +0000883 security_groups=security_groups,
884 availability_zone=self.config.get('availability_zone'),
885 key_name=self.config.get('keypair'),
886 userdata=userdata,
tiernob84cbdc2017-07-07 14:30:30 +0200887 config_drive=config_drive,
888 block_device_mapping=block_device_mapping
montesmoreno0c8def02016-12-22 12:16:23 +0000889 ) # , description=description)
Pablo Montes Moreno6a7785b2017-07-03 10:44:30 +0200890
891 # Previously mentioned workaround to wait until the VM is active and then disable the port-security
892 if no_secured_ports:
893 self.__wait_for_vm(server.id, 'ACTIVE')
894
895 for port_id in no_secured_ports:
896 try:
897 self.neutron.update_port(port_id, {"port": {"port_security_enabled": False, "security_groups": None} })
898
899 except Exception as e:
900 self.logger.error("It was not possible to disable port security for port {}".format(port_id))
901 self.delete_vminstance(server.id)
902 raise
903
tiernoae4a8d12016-07-08 12:30:39 +0200904 #print "DONE :-)", server
ahmadsaf853d452016-12-22 11:33:47 +0500905 pool_id = None
906 floating_ips = self.neutron.list_floatingips().get("floatingips", ())
Pablo Montes Moreno6a7785b2017-07-03 10:44:30 +0200907
908 if external_network:
909 self.__wait_for_vm(server.id, 'ACTIVE')
910
ahmadsaf853d452016-12-22 11:33:47 +0500911 for floating_network in external_network:
tiernof8383b82017-01-18 15:49:48 +0100912 try:
tiernof8383b82017-01-18 15:49:48 +0100913 assigned = False
914 while(assigned == False):
915 if floating_ips:
916 ip = floating_ips.pop(0)
917 if not ip.get("port_id", False) and ip.get('tenant_id') == server.tenant_id:
918 free_floating_ip = ip.get("floating_ip_address")
919 try:
920 fix_ip = floating_network.get('ip')
921 server.add_floating_ip(free_floating_ip, fix_ip)
922 assigned = True
923 except Exception as e:
924 raise vimconn.vimconnException(type(e).__name__ + ": Cannot create floating_ip "+ str(e), http_code=vimconn.HTTP_Conflict)
925 else:
926 #Find the external network
927 external_nets = list()
928 for net in self.neutron.list_networks()['networks']:
929 if net['router:external']:
930 external_nets.append(net)
931
932 if len(external_nets) == 0:
933 raise vimconn.vimconnException("Cannot create floating_ip automatically since no external "
934 "network is present",
935 http_code=vimconn.HTTP_Conflict)
936 if len(external_nets) > 1:
937 raise vimconn.vimconnException("Cannot create floating_ip automatically since multiple "
938 "external networks are present",
939 http_code=vimconn.HTTP_Conflict)
940
941 pool_id = external_nets[0].get('id')
942 param = {'floatingip': {'floating_network_id': pool_id, 'tenant_id': server.tenant_id}}
ahmadsaf853d452016-12-22 11:33:47 +0500943 try:
tiernof8383b82017-01-18 15:49:48 +0100944 #self.logger.debug("Creating floating IP")
945 new_floating_ip = self.neutron.create_floatingip(param)
946 free_floating_ip = new_floating_ip['floatingip']['floating_ip_address']
ahmadsaf853d452016-12-22 11:33:47 +0500947 fix_ip = floating_network.get('ip')
948 server.add_floating_ip(free_floating_ip, fix_ip)
tiernof8383b82017-01-18 15:49:48 +0100949 assigned=True
ahmadsaf853d452016-12-22 11:33:47 +0500950 except Exception as e:
tiernof8383b82017-01-18 15:49:48 +0100951 raise vimconn.vimconnException(type(e).__name__ + ": Cannot assign floating_ip "+ str(e), http_code=vimconn.HTTP_Conflict)
952 except Exception as e:
953 if not floating_network['exit_on_floating_ip_error']:
954 self.logger.warn("Cannot create floating_ip. %s", str(e))
955 continue
tiernof8383b82017-01-18 15:49:48 +0100956 raise
montesmoreno2a1fc4e2017-01-09 16:46:04 +0000957
tiernoae4a8d12016-07-08 12:30:39 +0200958 return server.id
tierno7edb6752016-03-21 17:37:52 +0100959# except nvExceptions.NotFound as e:
960# error_value=-vimconn.HTTP_Not_Found
961# error_text= "vm instance %s not found" % vm_id
Pablo Montes Moreno6a7785b2017-07-03 10:44:30 +0200962# except TypeError as e:
963# raise vimconn.vimconnException(type(e).__name__ + ": "+ str(e), http_code=vimconn.HTTP_Bad_Request)
964
965 except Exception as e:
montesmoreno2a1fc4e2017-01-09 16:46:04 +0000966 # delete the volumes we just created
tiernob84cbdc2017-07-07 14:30:30 +0200967 if block_device_mapping:
montesmoreno2a1fc4e2017-01-09 16:46:04 +0000968 for volume_id in block_device_mapping.itervalues():
969 self.cinder.volumes.delete(volume_id)
970
Pablo Montes Moreno6a7785b2017-07-03 10:44:30 +0200971 # Delete the VM
972 if server != None:
973 self.delete_vminstance(server.id)
974 else:
975 # delete ports we just created
976 for net_item in net_list_vim:
977 if 'port-id' in net_item:
978 self.neutron.delete_port(net_item['port-id'])
979
tiernoae4a8d12016-07-08 12:30:39 +0200980 self._format_exception(e)
tierno7edb6752016-03-21 17:37:52 +0100981
tiernoae4a8d12016-07-08 12:30:39 +0200982 def get_vminstance(self,vm_id):
tierno7edb6752016-03-21 17:37:52 +0100983 '''Returns the VM instance information from VIM'''
tiernoae4a8d12016-07-08 12:30:39 +0200984 #self.logger.debug("Getting VM from VIM")
tierno7edb6752016-03-21 17:37:52 +0100985 try:
986 self._reload_connection()
987 server = self.nova.servers.find(id=vm_id)
988 #TODO parse input and translate to VIM format (openmano_schemas.new_vminstance_response_schema)
tiernoae4a8d12016-07-08 12:30:39 +0200989 return server.to_dict()
tierno8e995ce2016-09-22 08:13:00 +0000990 except (ksExceptions.ClientException, nvExceptions.ClientException, nvExceptions.NotFound, ConnectionError) as e:
tiernoae4a8d12016-07-08 12:30:39 +0200991 self._format_exception(e)
992
993 def get_vminstance_console(self,vm_id, console_type="vnc"):
tierno7edb6752016-03-21 17:37:52 +0100994 '''
995 Get a console for the virtual machine
996 Params:
997 vm_id: uuid of the VM
998 console_type, can be:
999 "novnc" (by default), "xvpvnc" for VNC types,
1000 "rdp-html5" for RDP types, "spice-html5" for SPICE types
tiernoae4a8d12016-07-08 12:30:39 +02001001 Returns dict with the console parameters:
1002 protocol: ssh, ftp, http, https, ...
1003 server: usually ip address
1004 port: the http, ssh, ... port
1005 suffix: extra text, e.g. the http path and query string
tierno7edb6752016-03-21 17:37:52 +01001006 '''
tiernoae4a8d12016-07-08 12:30:39 +02001007 self.logger.debug("Getting VM CONSOLE from VIM")
tierno7edb6752016-03-21 17:37:52 +01001008 try:
1009 self._reload_connection()
1010 server = self.nova.servers.find(id=vm_id)
1011 if console_type == None or console_type == "novnc":
1012 console_dict = server.get_vnc_console("novnc")
1013 elif console_type == "xvpvnc":
1014 console_dict = server.get_vnc_console(console_type)
1015 elif console_type == "rdp-html5":
1016 console_dict = server.get_rdp_console(console_type)
1017 elif console_type == "spice-html5":
1018 console_dict = server.get_spice_console(console_type)
1019 else:
tiernoae4a8d12016-07-08 12:30:39 +02001020 raise vimconn.vimconnException("console type '{}' not allowed".format(console_type), http_code=vimconn.HTTP_Bad_Request)
tierno7edb6752016-03-21 17:37:52 +01001021
1022 console_dict1 = console_dict.get("console")
1023 if console_dict1:
1024 console_url = console_dict1.get("url")
1025 if console_url:
1026 #parse console_url
1027 protocol_index = console_url.find("//")
1028 suffix_index = console_url[protocol_index+2:].find("/") + protocol_index+2
1029 port_index = console_url[protocol_index+2:suffix_index].find(":") + protocol_index+2
1030 if protocol_index < 0 or port_index<0 or suffix_index<0:
1031 return -vimconn.HTTP_Internal_Server_Error, "Unexpected response from VIM"
1032 console_dict={"protocol": console_url[0:protocol_index],
1033 "server": console_url[protocol_index+2:port_index],
1034 "port": console_url[port_index:suffix_index],
1035 "suffix": console_url[suffix_index+1:]
1036 }
1037 protocol_index += 2
tiernoae4a8d12016-07-08 12:30:39 +02001038 return console_dict
1039 raise vimconn.vimconnUnexpectedResponse("Unexpected response from VIM")
tierno7edb6752016-03-21 17:37:52 +01001040
tierno8e995ce2016-09-22 08:13:00 +00001041 except (nvExceptions.NotFound, ksExceptions.ClientException, nvExceptions.ClientException, nvExceptions.BadRequest, ConnectionError) as e:
tiernoae4a8d12016-07-08 12:30:39 +02001042 self._format_exception(e)
tierno7edb6752016-03-21 17:37:52 +01001043
tiernoae4a8d12016-07-08 12:30:39 +02001044 def delete_vminstance(self, vm_id):
1045 '''Removes a VM instance from VIM. Returns the old identifier
tierno7edb6752016-03-21 17:37:52 +01001046 '''
tiernoae4a8d12016-07-08 12:30:39 +02001047 #print "osconnector: Getting VM from VIM"
tierno7edb6752016-03-21 17:37:52 +01001048 try:
1049 self._reload_connection()
1050 #delete VM ports attached to this networks before the virtual machine
1051 ports = self.neutron.list_ports(device_id=vm_id)
1052 for p in ports['ports']:
1053 try:
1054 self.neutron.delete_port(p["id"])
1055 except Exception as e:
tiernoae4a8d12016-07-08 12:30:39 +02001056 self.logger.error("Error deleting port: " + type(e).__name__ + ": "+ str(e))
montesmoreno0c8def02016-12-22 12:16:23 +00001057
1058 #commented because detaching the volumes makes the servers.delete not work properly ?!?
1059 #dettach volumes attached
1060 server = self.nova.servers.get(vm_id)
1061 volumes_attached_dict = server._info['os-extended-volumes:volumes_attached']
1062 #for volume in volumes_attached_dict:
1063 # self.cinder.volumes.detach(volume['id'])
1064
tierno7edb6752016-03-21 17:37:52 +01001065 self.nova.servers.delete(vm_id)
montesmoreno0c8def02016-12-22 12:16:23 +00001066
1067 #delete volumes.
1068 #Although having detached them should have them in active status
1069 #we ensure in this loop
1070 keep_waiting = True
1071 elapsed_time = 0
1072 while keep_waiting and elapsed_time < volume_timeout:
1073 keep_waiting = False
1074 for volume in volumes_attached_dict:
1075 if self.cinder.volumes.get(volume['id']).status != 'available':
1076 keep_waiting = True
1077 else:
1078 self.cinder.volumes.delete(volume['id'])
1079 if keep_waiting:
1080 time.sleep(1)
1081 elapsed_time += 1
1082
tiernoae4a8d12016-07-08 12:30:39 +02001083 return vm_id
tierno8e995ce2016-09-22 08:13:00 +00001084 except (nvExceptions.NotFound, ksExceptions.ClientException, nvExceptions.ClientException, ConnectionError) as e:
tiernoae4a8d12016-07-08 12:30:39 +02001085 self._format_exception(e)
tierno7edb6752016-03-21 17:37:52 +01001086 #TODO insert exception vimconn.HTTP_Unauthorized
1087 #if reaching here is because an exception
tierno7edb6752016-03-21 17:37:52 +01001088
tiernoae4a8d12016-07-08 12:30:39 +02001089 def refresh_vms_status(self, vm_list):
1090 '''Get the status of the virtual machines and their interfaces/ports
1091 Params: the list of VM identifiers
1092 Returns a dictionary with:
1093 vm_id: #VIM id of this Virtual Machine
1094 status: #Mandatory. Text with one of:
1095 # DELETED (not found at vim)
1096 # VIM_ERROR (Cannot connect to VIM, VIM response error, ...)
1097 # OTHER (Vim reported other status not understood)
1098 # ERROR (VIM indicates an ERROR status)
1099 # ACTIVE, PAUSED, SUSPENDED, INACTIVE (not running),
1100 # CREATING (on building process), ERROR
1101 # ACTIVE:NoMgmtIP (Active but any of its interface has an IP address
1102 #
1103 error_msg: #Text with VIM error message, if any. Or the VIM connection ERROR
1104 vim_info: #Text with plain information obtained from vim (yaml.safe_dump)
1105 interfaces:
1106 - vim_info: #Text with plain information obtained from vim (yaml.safe_dump)
1107 mac_address: #Text format XX:XX:XX:XX:XX:XX
1108 vim_net_id: #network id where this interface is connected
1109 vim_interface_id: #interface/port VIM id
1110 ip_address: #null, or text with IPv4, IPv6 address
tierno867ffe92017-03-27 12:50:34 +02001111 compute_node: #identification of compute node where PF,VF interface is allocated
1112 pci: #PCI address of the NIC that hosts the PF,VF
1113 vlan: #physical VLAN used for VF
tierno7edb6752016-03-21 17:37:52 +01001114 '''
tiernoae4a8d12016-07-08 12:30:39 +02001115 vm_dict={}
1116 self.logger.debug("refresh_vms status: Getting tenant VM instance information from VIM")
1117 for vm_id in vm_list:
1118 vm={}
1119 try:
1120 vm_vim = self.get_vminstance(vm_id)
1121 if vm_vim['status'] in vmStatus2manoFormat:
1122 vm['status'] = vmStatus2manoFormat[ vm_vim['status'] ]
tierno7edb6752016-03-21 17:37:52 +01001123 else:
tiernoae4a8d12016-07-08 12:30:39 +02001124 vm['status'] = "OTHER"
1125 vm['error_msg'] = "VIM status reported " + vm_vim['status']
tierno8e995ce2016-09-22 08:13:00 +00001126 try:
1127 vm['vim_info'] = yaml.safe_dump(vm_vim, default_flow_style=True, width=256)
1128 except yaml.representer.RepresenterError:
1129 vm['vim_info'] = str(vm_vim)
tiernoae4a8d12016-07-08 12:30:39 +02001130 vm["interfaces"] = []
1131 if vm_vim.get('fault'):
1132 vm['error_msg'] = str(vm_vim['fault'])
1133 #get interfaces
tierno7edb6752016-03-21 17:37:52 +01001134 try:
tiernoae4a8d12016-07-08 12:30:39 +02001135 self._reload_connection()
1136 port_dict=self.neutron.list_ports(device_id=vm_id)
1137 for port in port_dict["ports"]:
1138 interface={}
tierno8e995ce2016-09-22 08:13:00 +00001139 try:
1140 interface['vim_info'] = yaml.safe_dump(port, default_flow_style=True, width=256)
1141 except yaml.representer.RepresenterError:
1142 interface['vim_info'] = str(port)
tiernoae4a8d12016-07-08 12:30:39 +02001143 interface["mac_address"] = port.get("mac_address")
1144 interface["vim_net_id"] = port["network_id"]
1145 interface["vim_interface_id"] = port["id"]
Mike Marchetti5b9da422017-05-02 15:35:47 -04001146 # check if OS-EXT-SRV-ATTR:host is there,
1147 # in case of non-admin credentials, it will be missing
1148 if vm_vim.get('OS-EXT-SRV-ATTR:host'):
1149 interface["compute_node"] = vm_vim['OS-EXT-SRV-ATTR:host']
tierno867ffe92017-03-27 12:50:34 +02001150 interface["pci"] = None
Mike Marchetti5b9da422017-05-02 15:35:47 -04001151
1152 # check if binding:profile is there,
1153 # in case of non-admin credentials, it will be missing
1154 if port.get('binding:profile'):
1155 if port['binding:profile'].get('pci_slot'):
1156 # TODO: At the moment sr-iov pci addresses are converted to PF pci addresses by setting the slot to 0x00
1157 # TODO: This is just a workaround valid for niantinc. Find a better way to do so
1158 # CHANGE DDDD:BB:SS.F to DDDD:BB:00.(F%2) assuming there are 2 ports per nic
1159 pci = port['binding:profile']['pci_slot']
1160 # interface["pci"] = pci[:-4] + "00." + str(int(pci[-1]) % 2)
1161 interface["pci"] = pci
tierno867ffe92017-03-27 12:50:34 +02001162 interface["vlan"] = None
Pablo Montes Moreno3be0b2a2017-03-30 13:22:15 +02001163 #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 +01001164 network = self.neutron.show_network(port["network_id"])
Pablo Montes Moreno3be0b2a2017-03-30 13:22:15 +02001165 if network['network'].get('provider:network_type') == 'vlan' and \
1166 port.get("binding:vnic_type") == "direct":
tierno867ffe92017-03-27 12:50:34 +02001167 interface["vlan"] = network['network'].get('provider:segmentation_id')
tiernoae4a8d12016-07-08 12:30:39 +02001168 ips=[]
1169 #look for floating ip address
1170 floating_ip_dict = self.neutron.list_floatingips(port_id=port["id"])
1171 if floating_ip_dict.get("floatingips"):
1172 ips.append(floating_ip_dict["floatingips"][0].get("floating_ip_address") )
tierno7edb6752016-03-21 17:37:52 +01001173
tiernoae4a8d12016-07-08 12:30:39 +02001174 for subnet in port["fixed_ips"]:
1175 ips.append(subnet["ip_address"])
1176 interface["ip_address"] = ";".join(ips)
1177 vm["interfaces"].append(interface)
1178 except Exception as e:
1179 self.logger.error("Error getting vm interface information " + type(e).__name__ + ": "+ str(e))
1180 except vimconn.vimconnNotFoundException as e:
1181 self.logger.error("Exception getting vm status: %s", str(e))
1182 vm['status'] = "DELETED"
1183 vm['error_msg'] = str(e)
1184 except vimconn.vimconnException as e:
1185 self.logger.error("Exception getting vm status: %s", str(e))
1186 vm['status'] = "VIM_ERROR"
1187 vm['error_msg'] = str(e)
1188 vm_dict[vm_id] = vm
1189 return vm_dict
tierno7edb6752016-03-21 17:37:52 +01001190
tiernoae4a8d12016-07-08 12:30:39 +02001191 def action_vminstance(self, vm_id, action_dict):
tierno7edb6752016-03-21 17:37:52 +01001192 '''Send and action over a VM instance from VIM
tiernoae4a8d12016-07-08 12:30:39 +02001193 Returns the vm_id if the action was successfully sent to the VIM'''
1194 self.logger.debug("Action over VM '%s': %s", vm_id, str(action_dict))
tierno7edb6752016-03-21 17:37:52 +01001195 try:
1196 self._reload_connection()
1197 server = self.nova.servers.find(id=vm_id)
1198 if "start" in action_dict:
1199 if action_dict["start"]=="rebuild":
1200 server.rebuild()
1201 else:
1202 if server.status=="PAUSED":
1203 server.unpause()
1204 elif server.status=="SUSPENDED":
1205 server.resume()
1206 elif server.status=="SHUTOFF":
1207 server.start()
1208 elif "pause" in action_dict:
1209 server.pause()
1210 elif "resume" in action_dict:
1211 server.resume()
1212 elif "shutoff" in action_dict or "shutdown" in action_dict:
1213 server.stop()
1214 elif "forceOff" in action_dict:
1215 server.stop() #TODO
1216 elif "terminate" in action_dict:
1217 server.delete()
1218 elif "createImage" in action_dict:
1219 server.create_image()
1220 #"path":path_schema,
1221 #"description":description_schema,
1222 #"name":name_schema,
1223 #"metadata":metadata_schema,
1224 #"imageRef": id_schema,
1225 #"disk": {"oneOf":[{"type": "null"}, {"type":"string"}] },
1226 elif "rebuild" in action_dict:
1227 server.rebuild(server.image['id'])
1228 elif "reboot" in action_dict:
1229 server.reboot() #reboot_type='SOFT'
1230 elif "console" in action_dict:
1231 console_type = action_dict["console"]
1232 if console_type == None or console_type == "novnc":
1233 console_dict = server.get_vnc_console("novnc")
1234 elif console_type == "xvpvnc":
1235 console_dict = server.get_vnc_console(console_type)
1236 elif console_type == "rdp-html5":
1237 console_dict = server.get_rdp_console(console_type)
1238 elif console_type == "spice-html5":
1239 console_dict = server.get_spice_console(console_type)
1240 else:
tiernoae4a8d12016-07-08 12:30:39 +02001241 raise vimconn.vimconnException("console type '{}' not allowed".format(console_type),
1242 http_code=vimconn.HTTP_Bad_Request)
tierno7edb6752016-03-21 17:37:52 +01001243 try:
1244 console_url = console_dict["console"]["url"]
1245 #parse console_url
1246 protocol_index = console_url.find("//")
1247 suffix_index = console_url[protocol_index+2:].find("/") + protocol_index+2
1248 port_index = console_url[protocol_index+2:suffix_index].find(":") + protocol_index+2
1249 if protocol_index < 0 or port_index<0 or suffix_index<0:
tiernoae4a8d12016-07-08 12:30:39 +02001250 raise vimconn.vimconnException("Unexpected response from VIM " + str(console_dict))
tierno7edb6752016-03-21 17:37:52 +01001251 console_dict2={"protocol": console_url[0:protocol_index],
1252 "server": console_url[protocol_index+2 : port_index],
1253 "port": int(console_url[port_index+1 : suffix_index]),
1254 "suffix": console_url[suffix_index+1:]
1255 }
tiernoae4a8d12016-07-08 12:30:39 +02001256 return console_dict2
1257 except Exception as e:
1258 raise vimconn.vimconnException("Unexpected response from VIM " + str(console_dict))
tierno7edb6752016-03-21 17:37:52 +01001259
tiernoae4a8d12016-07-08 12:30:39 +02001260 return vm_id
tierno8e995ce2016-09-22 08:13:00 +00001261 except (ksExceptions.ClientException, nvExceptions.ClientException, nvExceptions.NotFound, ConnectionError) as e:
tiernoae4a8d12016-07-08 12:30:39 +02001262 self._format_exception(e)
1263 #TODO insert exception vimconn.HTTP_Unauthorized
1264
1265#NOT USED FUNCTIONS
1266
1267 def new_external_port(self, port_data):
1268 #TODO openstack if needed
1269 '''Adds a external port to VIM'''
1270 '''Returns the port identifier'''
1271 return -vimconn.HTTP_Internal_Server_Error, "osconnector.new_external_port() not implemented"
1272
1273 def connect_port_network(self, port_id, network_id, admin=False):
1274 #TODO openstack if needed
1275 '''Connects a external port to a network'''
1276 '''Returns status code of the VIM response'''
1277 return -vimconn.HTTP_Internal_Server_Error, "osconnector.connect_port_network() not implemented"
1278
1279 def new_user(self, user_name, user_passwd, tenant_id=None):
1280 '''Adds a new user to openstack VIM'''
1281 '''Returns the user identifier'''
1282 self.logger.debug("osconnector: Adding a new user to VIM")
1283 try:
1284 self._reload_connection()
1285 user=self.keystone.users.create(user_name, user_passwd, tenant_id=tenant_id)
1286 #self.keystone.tenants.add_user(self.k_creds["username"], #role)
1287 return user.id
1288 except ksExceptions.ConnectionError as e:
1289 error_value=-vimconn.HTTP_Bad_Request
1290 error_text= type(e).__name__ + ": "+ (str(e) if len(e.args)==0 else str(e.args[0]))
1291 except ksExceptions.ClientException as e: #TODO remove
tierno7edb6752016-03-21 17:37:52 +01001292 error_value=-vimconn.HTTP_Bad_Request
1293 error_text= type(e).__name__ + ": "+ (str(e) if len(e.args)==0 else str(e.args[0]))
1294 #TODO insert exception vimconn.HTTP_Unauthorized
1295 #if reaching here is because an exception
1296 if self.debug:
tiernoae4a8d12016-07-08 12:30:39 +02001297 self.logger.debug("new_user " + error_text)
tierno7edb6752016-03-21 17:37:52 +01001298 return error_value, error_text
tiernoae4a8d12016-07-08 12:30:39 +02001299
1300 def delete_user(self, user_id):
1301 '''Delete a user from openstack VIM'''
1302 '''Returns the user identifier'''
1303 if self.debug:
1304 print "osconnector: Deleting a user from VIM"
1305 try:
1306 self._reload_connection()
1307 self.keystone.users.delete(user_id)
1308 return 1, user_id
1309 except ksExceptions.ConnectionError as e:
1310 error_value=-vimconn.HTTP_Bad_Request
1311 error_text= type(e).__name__ + ": "+ (str(e) if len(e.args)==0 else str(e.args[0]))
1312 except ksExceptions.NotFound as e:
1313 error_value=-vimconn.HTTP_Not_Found
1314 error_text= type(e).__name__ + ": "+ (str(e) if len(e.args)==0 else str(e.args[0]))
1315 except ksExceptions.ClientException as e: #TODO remove
1316 error_value=-vimconn.HTTP_Bad_Request
1317 error_text= type(e).__name__ + ": "+ (str(e) if len(e.args)==0 else str(e.args[0]))
1318 #TODO insert exception vimconn.HTTP_Unauthorized
1319 #if reaching here is because an exception
1320 if self.debug:
1321 print "delete_tenant " + error_text
1322 return error_value, error_text
1323
tierno7edb6752016-03-21 17:37:52 +01001324 def get_hosts_info(self):
1325 '''Get the information of deployed hosts
1326 Returns the hosts content'''
1327 if self.debug:
1328 print "osconnector: Getting Host info from VIM"
1329 try:
1330 h_list=[]
1331 self._reload_connection()
1332 hypervisors = self.nova.hypervisors.list()
1333 for hype in hypervisors:
1334 h_list.append( hype.to_dict() )
1335 return 1, {"hosts":h_list}
1336 except nvExceptions.NotFound as e:
1337 error_value=-vimconn.HTTP_Not_Found
1338 error_text= (str(e) if len(e.args)==0 else str(e.args[0]))
1339 except (ksExceptions.ClientException, nvExceptions.ClientException) as e:
1340 error_value=-vimconn.HTTP_Bad_Request
1341 error_text= type(e).__name__ + ": "+ (str(e) if len(e.args)==0 else str(e.args[0]))
1342 #TODO insert exception vimconn.HTTP_Unauthorized
1343 #if reaching here is because an exception
1344 if self.debug:
1345 print "get_hosts_info " + error_text
1346 return error_value, error_text
1347
1348 def get_hosts(self, vim_tenant):
1349 '''Get the hosts and deployed instances
1350 Returns the hosts content'''
1351 r, hype_dict = self.get_hosts_info()
1352 if r<0:
1353 return r, hype_dict
1354 hypervisors = hype_dict["hosts"]
1355 try:
1356 servers = self.nova.servers.list()
1357 for hype in hypervisors:
1358 for server in servers:
1359 if server.to_dict()['OS-EXT-SRV-ATTR:hypervisor_hostname']==hype['hypervisor_hostname']:
1360 if 'vm' in hype:
1361 hype['vm'].append(server.id)
1362 else:
1363 hype['vm'] = [server.id]
1364 return 1, hype_dict
1365 except nvExceptions.NotFound as e:
1366 error_value=-vimconn.HTTP_Not_Found
1367 error_text= (str(e) if len(e.args)==0 else str(e.args[0]))
1368 except (ksExceptions.ClientException, nvExceptions.ClientException) as e:
1369 error_value=-vimconn.HTTP_Bad_Request
1370 error_text= type(e).__name__ + ": "+ (str(e) if len(e.args)==0 else str(e.args[0]))
1371 #TODO insert exception vimconn.HTTP_Unauthorized
1372 #if reaching here is because an exception
1373 if self.debug:
1374 print "get_hosts " + error_text
1375 return error_value, error_text
1376
tierno7edb6752016-03-21 17:37:52 +01001377