blob: bcb80ae1460974f5c9d69531d55e4e8e73e5d7f0 [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'
Pablo Montes Moreno6a7785b2017-07-03 10:44:30 +0200172
tiernoae4a8d12016-07-08 12:30:39 +0200173 def _format_exception(self, exception):
174 '''Transform a keystone, nova, neutron exception into a vimconn exception'''
175 if isinstance(exception, (HTTPException, gl1Exceptions.HTTPException, gl1Exceptions.CommunicationError,
tierno8e995ce2016-09-22 08:13:00 +0000176 ConnectionError, ksExceptions.ConnectionError, neExceptions.ConnectionFailed
177 )):
tiernoae4a8d12016-07-08 12:30:39 +0200178 raise vimconn.vimconnConnectionException(type(exception).__name__ + ": " + str(exception))
179 elif isinstance(exception, (nvExceptions.ClientException, ksExceptions.ClientException,
180 neExceptions.NeutronException, nvExceptions.BadRequest)):
181 raise vimconn.vimconnUnexpectedResponse(type(exception).__name__ + ": " + str(exception))
182 elif isinstance(exception, (neExceptions.NetworkNotFoundClient, nvExceptions.NotFound)):
183 raise vimconn.vimconnNotFoundException(type(exception).__name__ + ": " + str(exception))
184 elif isinstance(exception, nvExceptions.Conflict):
185 raise vimconn.vimconnConflictException(type(exception).__name__ + ": " + str(exception))
Pablo Montes Moreno6a7785b2017-07-03 10:44:30 +0200186 elif isinstance(exception, vimconn.vimconnException):
187 raise
tiernof716aea2017-06-21 18:01:40 +0200188 else: # ()
tiernob84cbdc2017-07-07 14:30:30 +0200189 self.logger.error("General Exception " + str(exception), exc_info=True)
tiernoae4a8d12016-07-08 12:30:39 +0200190 raise vimconn.vimconnConnectionException(type(exception).__name__ + ": " + str(exception))
191
192 def get_tenant_list(self, filter_dict={}):
193 '''Obtain tenants of VIM
194 filter_dict can contain the following keys:
195 name: filter by tenant name
196 id: filter by tenant uuid/id
197 <other VIM specific>
198 Returns the tenant list of dictionaries: [{'name':'<name>, 'id':'<id>, ...}, ...]
199 '''
ahmadsa95baa272016-11-30 09:14:11 +0500200 self.logger.debug("Getting tenants from VIM filter: '%s'", str(filter_dict))
tiernoae4a8d12016-07-08 12:30:39 +0200201 try:
202 self._reload_connection()
tiernof716aea2017-06-21 18:01:40 +0200203 if self.api_version3:
204 project_class_list = self.keystone.projects.list(name=filter_dict.get("name"))
ahmadsa95baa272016-11-30 09:14:11 +0500205 else:
tiernof716aea2017-06-21 18:01:40 +0200206 project_class_list = self.keystone.tenants.findall(**filter_dict)
ahmadsa95baa272016-11-30 09:14:11 +0500207 project_list=[]
208 for project in project_class_list:
tiernof716aea2017-06-21 18:01:40 +0200209 if filter_dict.get('id') and filter_dict["id"] != project.id:
210 continue
ahmadsa95baa272016-11-30 09:14:11 +0500211 project_list.append(project.to_dict())
212 return project_list
tiernof716aea2017-06-21 18:01:40 +0200213 except (ksExceptions.ConnectionError, ksExceptions.ClientException, ConnectionError) as e:
tiernoae4a8d12016-07-08 12:30:39 +0200214 self._format_exception(e)
215
216 def new_tenant(self, tenant_name, tenant_description):
217 '''Adds a new tenant to openstack VIM. Returns the tenant identifier'''
218 self.logger.debug("Adding a new tenant name: %s", tenant_name)
219 try:
220 self._reload_connection()
tiernof716aea2017-06-21 18:01:40 +0200221 if self.api_version3:
222 project = self.keystone.projects.create(tenant_name, self.config.get("project_domain_id", "default"),
223 description=tenant_description, is_domain=False)
ahmadsa95baa272016-11-30 09:14:11 +0500224 else:
tiernof716aea2017-06-21 18:01:40 +0200225 project = self.keystone.tenants.create(tenant_name, tenant_description)
ahmadsa95baa272016-11-30 09:14:11 +0500226 return project.id
tierno8e995ce2016-09-22 08:13:00 +0000227 except (ksExceptions.ConnectionError, ksExceptions.ClientException, ConnectionError) as e:
tiernoae4a8d12016-07-08 12:30:39 +0200228 self._format_exception(e)
229
230 def delete_tenant(self, tenant_id):
231 '''Delete a tenant from openstack VIM. Returns the old tenant identifier'''
232 self.logger.debug("Deleting tenant %s from VIM", tenant_id)
233 try:
234 self._reload_connection()
tiernof716aea2017-06-21 18:01:40 +0200235 if self.api_version3:
ahmadsa95baa272016-11-30 09:14:11 +0500236 self.keystone.projects.delete(tenant_id)
237 else:
238 self.keystone.tenants.delete(tenant_id)
tiernoae4a8d12016-07-08 12:30:39 +0200239 return tenant_id
tierno8e995ce2016-09-22 08:13:00 +0000240 except (ksExceptions.ConnectionError, ksExceptions.ClientException, ConnectionError) as e:
tiernoae4a8d12016-07-08 12:30:39 +0200241 self._format_exception(e)
ahmadsa95baa272016-11-30 09:14:11 +0500242
garciadeblas9f8456e2016-09-05 05:02:59 +0200243 def new_network(self,net_name, net_type, ip_profile=None, shared=False, vlan=None):
tiernoae4a8d12016-07-08 12:30:39 +0200244 '''Adds a tenant network to VIM. Returns the network identifier'''
245 self.logger.debug("Adding a new network to VIM name '%s', type '%s'", net_name, net_type)
garciadeblasedca7b32016-09-29 14:01:52 +0000246 #self.logger.debug(">>>>>>>>>>>>>>>>>> IP profile %s", str(ip_profile))
tierno7edb6752016-03-21 17:37:52 +0100247 try:
garciadeblasedca7b32016-09-29 14:01:52 +0000248 new_net = None
tierno7edb6752016-03-21 17:37:52 +0100249 self._reload_connection()
250 network_dict = {'name': net_name, 'admin_state_up': True}
251 if net_type=="data" or net_type=="ptp":
252 if self.config.get('dataplane_physical_net') == None:
tiernoae4a8d12016-07-08 12:30:39 +0200253 raise vimconn.vimconnConflictException("You must provide a 'dataplane_physical_net' at config value before creating sriov network")
tierno7edb6752016-03-21 17:37:52 +0100254 network_dict["provider:physical_network"] = self.config['dataplane_physical_net'] #"physnet_sriov" #TODO physical
255 network_dict["provider:network_type"] = "vlan"
256 if vlan!=None:
257 network_dict["provider:network_type"] = vlan
tiernoae4a8d12016-07-08 12:30:39 +0200258 network_dict["shared"]=shared
tierno7edb6752016-03-21 17:37:52 +0100259 new_net=self.neutron.create_network({'network':network_dict})
260 #print new_net
garciadeblas9f8456e2016-09-05 05:02:59 +0200261 #create subnetwork, even if there is no profile
262 if not ip_profile:
263 ip_profile = {}
264 if 'subnet_address' not in ip_profile:
garciadeblas2299e3b2017-01-26 14:35:55 +0000265 #Fake subnet is required
266 subnet_rand = random.randint(0, 255)
267 ip_profile['subnet_address'] = "192.168.{}.0/24".format(subnet_rand)
garciadeblas9f8456e2016-09-05 05:02:59 +0200268 if 'ip_version' not in ip_profile:
269 ip_profile['ip_version'] = "IPv4"
tiernoa1fb4462017-06-30 12:25:50 +0200270 subnet = {"name":net_name+"-subnet",
tierno7edb6752016-03-21 17:37:52 +0100271 "network_id": new_net["network"]["id"],
garciadeblas9f8456e2016-09-05 05:02:59 +0200272 "ip_version": 4 if ip_profile['ip_version']=="IPv4" else 6,
273 "cidr": ip_profile['subnet_address']
tierno7edb6752016-03-21 17:37:52 +0100274 }
tiernoa1fb4462017-06-30 12:25:50 +0200275 # Gateway should be set to None if not needed. Otherwise openstack assigns one by default
276 subnet['gateway_ip'] = ip_profile.get('gateway_address')
garciadeblasedca7b32016-09-29 14:01:52 +0000277 if ip_profile.get('dns_address'):
tierno455612d2017-05-30 16:40:10 +0200278 subnet['dns_nameservers'] = ip_profile['dns_address'].split(";")
garciadeblas9f8456e2016-09-05 05:02:59 +0200279 if 'dhcp_enabled' in ip_profile:
280 subnet['enable_dhcp'] = False if ip_profile['dhcp_enabled']=="false" else True
281 if 'dhcp_start_address' in ip_profile:
tiernoa1fb4462017-06-30 12:25:50 +0200282 subnet['allocation_pools'] = []
garciadeblas9f8456e2016-09-05 05:02:59 +0200283 subnet['allocation_pools'].append(dict())
284 subnet['allocation_pools'][0]['start'] = ip_profile['dhcp_start_address']
285 if 'dhcp_count' in ip_profile:
286 #parts = ip_profile['dhcp_start_address'].split('.')
287 #ip_int = (int(parts[0]) << 24) + (int(parts[1]) << 16) + (int(parts[2]) << 8) + int(parts[3])
288 ip_int = int(netaddr.IPAddress(ip_profile['dhcp_start_address']))
garciadeblas21d795b2016-09-29 17:31:46 +0200289 ip_int += ip_profile['dhcp_count'] - 1
garciadeblas9f8456e2016-09-05 05:02:59 +0200290 ip_str = str(netaddr.IPAddress(ip_int))
291 subnet['allocation_pools'][0]['end'] = ip_str
garciadeblasedca7b32016-09-29 14:01:52 +0000292 #self.logger.debug(">>>>>>>>>>>>>>>>>> Subnet: %s", str(subnet))
tierno7edb6752016-03-21 17:37:52 +0100293 self.neutron.create_subnet({"subnet": subnet} )
tiernoae4a8d12016-07-08 12:30:39 +0200294 return new_net["network"]["id"]
tierno8e995ce2016-09-22 08:13:00 +0000295 except (neExceptions.ConnectionFailed, ksExceptions.ClientException, neExceptions.NeutronException, ConnectionError) as e:
garciadeblasedca7b32016-09-29 14:01:52 +0000296 if new_net:
297 self.neutron.delete_network(new_net['network']['id'])
tiernoae4a8d12016-07-08 12:30:39 +0200298 self._format_exception(e)
tierno7edb6752016-03-21 17:37:52 +0100299
300 def get_network_list(self, filter_dict={}):
301 '''Obtain tenant networks of VIM
302 Filter_dict can be:
303 name: network name
304 id: network uuid
305 shared: boolean
306 tenant_id: tenant
307 admin_state_up: boolean
308 status: 'ACTIVE'
309 Returns the network list of dictionaries
310 '''
tiernoae4a8d12016-07-08 12:30:39 +0200311 self.logger.debug("Getting network from VIM filter: '%s'", str(filter_dict))
tierno7edb6752016-03-21 17:37:52 +0100312 try:
313 self._reload_connection()
tiernof716aea2017-06-21 18:01:40 +0200314 if self.api_version3 and "tenant_id" in filter_dict:
315 filter_dict['project_id'] = filter_dict.pop('tenant_id') #TODO check
tierno7edb6752016-03-21 17:37:52 +0100316 net_dict=self.neutron.list_networks(**filter_dict)
317 net_list=net_dict["networks"]
318 self.__net_os2mano(net_list)
tiernoae4a8d12016-07-08 12:30:39 +0200319 return net_list
tierno8e995ce2016-09-22 08:13:00 +0000320 except (neExceptions.ConnectionFailed, ksExceptions.ClientException, neExceptions.NeutronException, ConnectionError) as e:
tiernoae4a8d12016-07-08 12:30:39 +0200321 self._format_exception(e)
tierno7edb6752016-03-21 17:37:52 +0100322
tiernoae4a8d12016-07-08 12:30:39 +0200323 def get_network(self, net_id):
324 '''Obtain details of network from VIM
325 Returns the network information from a network id'''
326 self.logger.debug(" Getting tenant network %s from VIM", net_id)
tierno7edb6752016-03-21 17:37:52 +0100327 filter_dict={"id": net_id}
tiernoae4a8d12016-07-08 12:30:39 +0200328 net_list = self.get_network_list(filter_dict)
tierno7edb6752016-03-21 17:37:52 +0100329 if len(net_list)==0:
tiernoae4a8d12016-07-08 12:30:39 +0200330 raise vimconn.vimconnNotFoundException("Network '{}' not found".format(net_id))
tierno7edb6752016-03-21 17:37:52 +0100331 elif len(net_list)>1:
tiernoae4a8d12016-07-08 12:30:39 +0200332 raise vimconn.vimconnConflictException("Found more than one network with this criteria")
tierno7edb6752016-03-21 17:37:52 +0100333 net = net_list[0]
334 subnets=[]
335 for subnet_id in net.get("subnets", () ):
336 try:
337 subnet = self.neutron.show_subnet(subnet_id)
338 except Exception as e:
tiernoae4a8d12016-07-08 12:30:39 +0200339 self.logger.error("osconnector.get_network(): Error getting subnet %s %s" % (net_id, str(e)))
340 subnet = {"id": subnet_id, "fault": str(e)}
tierno7edb6752016-03-21 17:37:52 +0100341 subnets.append(subnet)
342 net["subnets"] = subnets
Pablo Montes Moreno51e553b2017-03-23 16:39:12 +0100343 net["encapsulation"] = net.get('provider:network_type')
Pablo Montes Moreno3fbff9b2017-03-08 11:28:15 +0100344 net["segmentation_id"] = net.get('provider:segmentation_id')
tiernoae4a8d12016-07-08 12:30:39 +0200345 return net
tierno7edb6752016-03-21 17:37:52 +0100346
tiernoae4a8d12016-07-08 12:30:39 +0200347 def delete_network(self, net_id):
348 '''Deletes a tenant network from VIM. Returns the old network identifier'''
349 self.logger.debug("Deleting network '%s' from VIM", net_id)
tierno7edb6752016-03-21 17:37:52 +0100350 try:
351 self._reload_connection()
352 #delete VM ports attached to this networks before the network
353 ports = self.neutron.list_ports(network_id=net_id)
354 for p in ports['ports']:
355 try:
356 self.neutron.delete_port(p["id"])
357 except Exception as e:
tiernoae4a8d12016-07-08 12:30:39 +0200358 self.logger.error("Error deleting port %s: %s", p["id"], str(e))
tierno7edb6752016-03-21 17:37:52 +0100359 self.neutron.delete_network(net_id)
tiernoae4a8d12016-07-08 12:30:39 +0200360 return net_id
361 except (neExceptions.ConnectionFailed, neExceptions.NetworkNotFoundClient, neExceptions.NeutronException,
tierno8e995ce2016-09-22 08:13:00 +0000362 ksExceptions.ClientException, neExceptions.NeutronException, ConnectionError) as e:
tiernoae4a8d12016-07-08 12:30:39 +0200363 self._format_exception(e)
tierno7edb6752016-03-21 17:37:52 +0100364
tiernoae4a8d12016-07-08 12:30:39 +0200365 def refresh_nets_status(self, net_list):
366 '''Get the status of the networks
367 Params: the list of network identifiers
368 Returns a dictionary with:
369 net_id: #VIM id of this network
370 status: #Mandatory. Text with one of:
371 # DELETED (not found at vim)
372 # VIM_ERROR (Cannot connect to VIM, VIM response error, ...)
373 # OTHER (Vim reported other status not understood)
374 # ERROR (VIM indicates an ERROR status)
375 # ACTIVE, INACTIVE, DOWN (admin down),
376 # BUILD (on building process)
377 #
378 error_msg: #Text with VIM error message, if any. Or the VIM connection ERROR
379 vim_info: #Text with plain information obtained from vim (yaml.safe_dump)
380
381 '''
382 net_dict={}
383 for net_id in net_list:
384 net = {}
385 try:
386 net_vim = self.get_network(net_id)
387 if net_vim['status'] in netStatus2manoFormat:
388 net["status"] = netStatus2manoFormat[ net_vim['status'] ]
389 else:
390 net["status"] = "OTHER"
391 net["error_msg"] = "VIM status reported " + net_vim['status']
392
tierno8e995ce2016-09-22 08:13:00 +0000393 if net['status'] == "ACTIVE" and not net_vim['admin_state_up']:
tiernoae4a8d12016-07-08 12:30:39 +0200394 net['status'] = 'DOWN'
tierno8e995ce2016-09-22 08:13:00 +0000395 try:
396 net['vim_info'] = yaml.safe_dump(net_vim, default_flow_style=True, width=256)
397 except yaml.representer.RepresenterError:
398 net['vim_info'] = str(net_vim)
tiernoae4a8d12016-07-08 12:30:39 +0200399 if net_vim.get('fault'): #TODO
400 net['error_msg'] = str(net_vim['fault'])
401 except vimconn.vimconnNotFoundException as e:
402 self.logger.error("Exception getting net status: %s", str(e))
403 net['status'] = "DELETED"
404 net['error_msg'] = str(e)
405 except vimconn.vimconnException as e:
406 self.logger.error("Exception getting net status: %s", str(e))
407 net['status'] = "VIM_ERROR"
408 net['error_msg'] = str(e)
409 net_dict[net_id] = net
410 return net_dict
411
412 def get_flavor(self, flavor_id):
413 '''Obtain flavor details from the VIM. Returns the flavor dict details'''
414 self.logger.debug("Getting flavor '%s'", flavor_id)
tierno7edb6752016-03-21 17:37:52 +0100415 try:
416 self._reload_connection()
417 flavor = self.nova.flavors.find(id=flavor_id)
418 #TODO parse input and translate to VIM format (openmano_schemas.new_vminstance_response_schema)
tiernoae4a8d12016-07-08 12:30:39 +0200419 return flavor.to_dict()
tierno8e995ce2016-09-22 08:13:00 +0000420 except (nvExceptions.NotFound, nvExceptions.ClientException, ksExceptions.ClientException, ConnectionError) as e:
tiernoae4a8d12016-07-08 12:30:39 +0200421 self._format_exception(e)
tierno7edb6752016-03-21 17:37:52 +0100422
tiernocf157a82017-01-30 14:07:06 +0100423 def get_flavor_id_from_data(self, flavor_dict):
424 """Obtain flavor id that match the flavor description
425 Returns the flavor_id or raises a vimconnNotFoundException
tiernoe26fc7a2017-05-30 14:43:03 +0200426 flavor_dict: contains the required ram, vcpus, disk
427 If 'use_existing_flavors' is set to True at config, the closer flavor that provides same or more ram, vcpus
428 and disk is returned. Otherwise a flavor with exactly same ram, vcpus and disk is returned or a
429 vimconnNotFoundException is raised
tiernocf157a82017-01-30 14:07:06 +0100430 """
tiernoe26fc7a2017-05-30 14:43:03 +0200431 exact_match = False if self.config.get('use_existing_flavors') else True
tiernocf157a82017-01-30 14:07:06 +0100432 try:
433 self._reload_connection()
tiernoe26fc7a2017-05-30 14:43:03 +0200434 flavor_candidate_id = None
435 flavor_candidate_data = (10000, 10000, 10000)
436 flavor_target = (flavor_dict["ram"], flavor_dict["vcpus"], flavor_dict["disk"])
437 # numa=None
438 numas = flavor_dict.get("extended", {}).get("numas")
tiernocf157a82017-01-30 14:07:06 +0100439 if numas:
440 #TODO
441 raise vimconn.vimconnNotFoundException("Flavor with EPA still not implemted")
442 # if len(numas) > 1:
443 # raise vimconn.vimconnNotFoundException("Cannot find any flavor with more than one numa")
444 # numa=numas[0]
445 # numas = extended.get("numas")
446 for flavor in self.nova.flavors.list():
447 epa = flavor.get_keys()
448 if epa:
449 continue
tiernoe26fc7a2017-05-30 14:43:03 +0200450 # TODO
451 flavor_data = (flavor.ram, flavor.vcpus, flavor.disk)
452 if flavor_data == flavor_target:
453 return flavor.id
454 elif not exact_match and flavor_target < flavor_data < flavor_candidate_data:
455 flavor_candidate_id = flavor.id
456 flavor_candidate_data = flavor_data
457 if not exact_match and flavor_candidate_id:
458 return flavor_candidate_id
tiernocf157a82017-01-30 14:07:06 +0100459 raise vimconn.vimconnNotFoundException("Cannot find any flavor matching '{}'".format(str(flavor_dict)))
460 except (nvExceptions.NotFound, nvExceptions.ClientException, ksExceptions.ClientException, ConnectionError) as e:
461 self._format_exception(e)
462
463
tiernoae4a8d12016-07-08 12:30:39 +0200464 def new_flavor(self, flavor_data, change_name_if_used=True):
tierno7edb6752016-03-21 17:37:52 +0100465 '''Adds a tenant flavor to openstack VIM
tiernoae4a8d12016-07-08 12:30:39 +0200466 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 +0100467 Returns the flavor identifier
468 '''
tiernoae4a8d12016-07-08 12:30:39 +0200469 self.logger.debug("Adding flavor '%s'", str(flavor_data))
tierno7edb6752016-03-21 17:37:52 +0100470 retry=0
tiernoae4a8d12016-07-08 12:30:39 +0200471 max_retries=3
tierno7edb6752016-03-21 17:37:52 +0100472 name_suffix = 0
tiernoae4a8d12016-07-08 12:30:39 +0200473 name=flavor_data['name']
474 while retry<max_retries:
tierno7edb6752016-03-21 17:37:52 +0100475 retry+=1
476 try:
477 self._reload_connection()
478 if change_name_if_used:
479 #get used names
480 fl_names=[]
481 fl=self.nova.flavors.list()
482 for f in fl:
483 fl_names.append(f.name)
484 while name in fl_names:
485 name_suffix += 1
tiernoae4a8d12016-07-08 12:30:39 +0200486 name = flavor_data['name']+"-" + str(name_suffix)
tierno7edb6752016-03-21 17:37:52 +0100487
tiernoae4a8d12016-07-08 12:30:39 +0200488 ram = flavor_data.get('ram',64)
489 vcpus = flavor_data.get('vcpus',1)
tierno7edb6752016-03-21 17:37:52 +0100490 numa_properties=None
491
tiernoae4a8d12016-07-08 12:30:39 +0200492 extended = flavor_data.get("extended")
tierno7edb6752016-03-21 17:37:52 +0100493 if extended:
494 numas=extended.get("numas")
495 if numas:
496 numa_nodes = len(numas)
497 if numa_nodes > 1:
498 return -1, "Can not add flavor with more than one numa"
499 numa_properties = {"hw:numa_nodes":str(numa_nodes)}
500 numa_properties["hw:mem_page_size"] = "large"
501 numa_properties["hw:cpu_policy"] = "dedicated"
502 numa_properties["hw:numa_mempolicy"] = "strict"
503 for numa in numas:
504 #overwrite ram and vcpus
505 ram = numa['memory']*1024
Pablo Montes Morenoea1d6232017-05-24 11:33:24 +0200506 #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 +0100507 if 'paired-threads' in numa:
508 vcpus = numa['paired-threads']*2
Pablo Montes Morenoea1d6232017-05-24 11:33:24 +0200509 #cpu_thread_policy "require" implies that the compute node must have an STM architecture
510 numa_properties["hw:cpu_thread_policy"] = "require"
511 numa_properties["hw:cpu_policy"] = "dedicated"
tierno7edb6752016-03-21 17:37:52 +0100512 elif 'cores' in numa:
513 vcpus = numa['cores']
Pablo Montes Morenoea1d6232017-05-24 11:33:24 +0200514 # cpu_thread_policy "prefer" implies that the host must not have an SMT architecture, or a non-SMT architecture will be emulated
515 numa_properties["hw:cpu_thread_policy"] = "isolate"
516 numa_properties["hw:cpu_policy"] = "dedicated"
tierno7edb6752016-03-21 17:37:52 +0100517 elif 'threads' in numa:
518 vcpus = numa['threads']
Pablo Montes Morenoea1d6232017-05-24 11:33:24 +0200519 # cpu_thread_policy "prefer" implies that the host may or may not have an SMT architecture
520 numa_properties["hw:cpu_thread_policy"] = "prefer"
521 numa_properties["hw:cpu_policy"] = "dedicated"
Pablo Montes Moreno3be0b2a2017-03-30 13:22:15 +0200522 # for interface in numa.get("interfaces",() ):
523 # if interface["dedicated"]=="yes":
524 # raise vimconn.vimconnException("Passthrough interfaces are not supported for the openstack connector", http_code=vimconn.HTTP_Service_Unavailable)
525 # #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 +0100526
527 #create flavor
528 new_flavor=self.nova.flavors.create(name,
529 ram,
530 vcpus,
tiernoae4a8d12016-07-08 12:30:39 +0200531 flavor_data.get('disk',1),
532 is_public=flavor_data.get('is_public', True)
tierno7edb6752016-03-21 17:37:52 +0100533 )
534 #add metadata
535 if numa_properties:
536 new_flavor.set_keys(numa_properties)
tiernoae4a8d12016-07-08 12:30:39 +0200537 return new_flavor.id
tierno7edb6752016-03-21 17:37:52 +0100538 except nvExceptions.Conflict as e:
tiernoae4a8d12016-07-08 12:30:39 +0200539 if change_name_if_used and retry < max_retries:
tierno7edb6752016-03-21 17:37:52 +0100540 continue
tiernoae4a8d12016-07-08 12:30:39 +0200541 self._format_exception(e)
tierno7edb6752016-03-21 17:37:52 +0100542 #except nvExceptions.BadRequest as e:
543 except (ksExceptions.ClientException, nvExceptions.ClientException, ConnectionError) as e:
tiernoae4a8d12016-07-08 12:30:39 +0200544 self._format_exception(e)
tierno7edb6752016-03-21 17:37:52 +0100545
tiernoae4a8d12016-07-08 12:30:39 +0200546 def delete_flavor(self,flavor_id):
547 '''Deletes a tenant flavor from openstack VIM. Returns the old flavor_id
tierno7edb6752016-03-21 17:37:52 +0100548 '''
tiernoae4a8d12016-07-08 12:30:39 +0200549 try:
550 self._reload_connection()
551 self.nova.flavors.delete(flavor_id)
552 return flavor_id
553 #except nvExceptions.BadRequest as e:
tierno8e995ce2016-09-22 08:13:00 +0000554 except (nvExceptions.NotFound, ksExceptions.ClientException, nvExceptions.ClientException, ConnectionError) as e:
tiernoae4a8d12016-07-08 12:30:39 +0200555 self._format_exception(e)
tierno7edb6752016-03-21 17:37:52 +0100556
tiernoae4a8d12016-07-08 12:30:39 +0200557 def new_image(self,image_dict):
tierno7edb6752016-03-21 17:37:52 +0100558 '''
tiernoae4a8d12016-07-08 12:30:39 +0200559 Adds a tenant image to VIM. imge_dict is a dictionary with:
560 name: name
561 disk_format: qcow2, vhd, vmdk, raw (by default), ...
562 location: path or URI
563 public: "yes" or "no"
564 metadata: metadata of the image
565 Returns the image_id
tierno7edb6752016-03-21 17:37:52 +0100566 '''
tiernob5cef372017-06-19 15:52:22 +0200567 # ALF TODO: revise and change for the new method or session
tierno7edb6752016-03-21 17:37:52 +0100568 #using version 1 of glance client
569 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 +0200570 retry=0
571 max_retries=3
572 while retry<max_retries:
tierno7edb6752016-03-21 17:37:52 +0100573 retry+=1
574 try:
575 self._reload_connection()
576 #determine format http://docs.openstack.org/developer/glance/formats.html
577 if "disk_format" in image_dict:
578 disk_format=image_dict["disk_format"]
garciadeblas14480452017-01-10 13:08:07 +0100579 else: #autodiscover based on extension
tierno7edb6752016-03-21 17:37:52 +0100580 if image_dict['location'][-6:]==".qcow2":
581 disk_format="qcow2"
582 elif image_dict['location'][-4:]==".vhd":
583 disk_format="vhd"
584 elif image_dict['location'][-5:]==".vmdk":
585 disk_format="vmdk"
586 elif image_dict['location'][-4:]==".vdi":
587 disk_format="vdi"
588 elif image_dict['location'][-4:]==".iso":
589 disk_format="iso"
590 elif image_dict['location'][-4:]==".aki":
591 disk_format="aki"
592 elif image_dict['location'][-4:]==".ari":
593 disk_format="ari"
594 elif image_dict['location'][-4:]==".ami":
595 disk_format="ami"
596 else:
597 disk_format="raw"
tiernoae4a8d12016-07-08 12:30:39 +0200598 self.logger.debug("new_image: '%s' loading from '%s'", image_dict['name'], image_dict['location'])
tierno7edb6752016-03-21 17:37:52 +0100599 if image_dict['location'][0:4]=="http":
600 new_image = glancev1.images.create(name=image_dict['name'], is_public=image_dict.get('public',"yes")=="yes",
601 container_format="bare", location=image_dict['location'], disk_format=disk_format)
602 else: #local path
603 with open(image_dict['location']) as fimage:
604 new_image = glancev1.images.create(name=image_dict['name'], is_public=image_dict.get('public',"yes")=="yes",
605 container_format="bare", data=fimage, disk_format=disk_format)
606 #insert metadata. We cannot use 'new_image.properties.setdefault'
607 #because nova and glance are "INDEPENDENT" and we are using nova for reading metadata
608 new_image_nova=self.nova.images.find(id=new_image.id)
609 new_image_nova.metadata.setdefault('location',image_dict['location'])
610 metadata_to_load = image_dict.get('metadata')
611 if metadata_to_load:
612 for k,v in yaml.load(metadata_to_load).iteritems():
613 new_image_nova.metadata.setdefault(k,v)
tiernoae4a8d12016-07-08 12:30:39 +0200614 return new_image.id
615 except (nvExceptions.Conflict, ksExceptions.ClientException, nvExceptions.ClientException) as e:
616 self._format_exception(e)
tierno8e995ce2016-09-22 08:13:00 +0000617 except (HTTPException, gl1Exceptions.HTTPException, gl1Exceptions.CommunicationError, ConnectionError) as e:
tiernoae4a8d12016-07-08 12:30:39 +0200618 if retry==max_retries:
619 continue
620 self._format_exception(e)
tierno7edb6752016-03-21 17:37:52 +0100621 except IOError as e: #can not open the file
tiernoae4a8d12016-07-08 12:30:39 +0200622 raise vimconn.vimconnConnectionException(type(e).__name__ + ": " + str(e)+ " for " + image_dict['location'],
623 http_code=vimconn.HTTP_Bad_Request)
tierno7edb6752016-03-21 17:37:52 +0100624
tiernoae4a8d12016-07-08 12:30:39 +0200625 def delete_image(self, image_id):
626 '''Deletes a tenant image from openstack VIM. Returns the old id
tierno7edb6752016-03-21 17:37:52 +0100627 '''
tiernoae4a8d12016-07-08 12:30:39 +0200628 try:
629 self._reload_connection()
630 self.nova.images.delete(image_id)
631 return image_id
tierno8e995ce2016-09-22 08:13:00 +0000632 except (nvExceptions.NotFound, ksExceptions.ClientException, nvExceptions.ClientException, gl1Exceptions.CommunicationError, ConnectionError) as e: #TODO remove
tiernoae4a8d12016-07-08 12:30:39 +0200633 self._format_exception(e)
634
635 def get_image_id_from_path(self, path):
garciadeblasb69fa9f2016-09-28 12:04:10 +0200636 '''Get the image id from image path in the VIM database. Returns the image_id'''
tiernoae4a8d12016-07-08 12:30:39 +0200637 try:
638 self._reload_connection()
639 images = self.nova.images.list()
640 for image in images:
641 if image.metadata.get("location")==path:
642 return image.id
643 raise vimconn.vimconnNotFoundException("image with location '{}' not found".format( path))
tierno8e995ce2016-09-22 08:13:00 +0000644 except (ksExceptions.ClientException, nvExceptions.ClientException, gl1Exceptions.CommunicationError, ConnectionError) as e:
tiernoae4a8d12016-07-08 12:30:39 +0200645 self._format_exception(e)
tierno7edb6752016-03-21 17:37:52 +0100646
garciadeblasb69fa9f2016-09-28 12:04:10 +0200647 def get_image_list(self, filter_dict={}):
648 '''Obtain tenant images from VIM
649 Filter_dict can be:
650 id: image id
651 name: image name
652 checksum: image checksum
653 Returns the image list of dictionaries:
654 [{<the fields at Filter_dict plus some VIM specific>}, ...]
655 List can be empty
656 '''
657 self.logger.debug("Getting image list from VIM filter: '%s'", str(filter_dict))
658 try:
659 self._reload_connection()
660 filter_dict_os=filter_dict.copy()
661 #First we filter by the available filter fields: name, id. The others are removed.
662 filter_dict_os.pop('checksum',None)
663 image_list=self.nova.images.findall(**filter_dict_os)
664 if len(image_list)==0:
665 return []
666 #Then we filter by the rest of filter fields: checksum
667 filtered_list = []
668 for image in image_list:
tierno4540ea52017-01-18 17:44:32 +0100669 image_class=self.glance.images.get(image.id)
670 if 'checksum' not in filter_dict or image_class['checksum']==filter_dict.get('checksum'):
671 filtered_list.append(image_class.copy())
garciadeblasb69fa9f2016-09-28 12:04:10 +0200672 return filtered_list
673 except (ksExceptions.ClientException, nvExceptions.ClientException, gl1Exceptions.CommunicationError, ConnectionError) as e:
674 self._format_exception(e)
675
Pablo Montes Moreno6a7785b2017-07-03 10:44:30 +0200676 def __wait_for_vm(self, vm_id, status):
677 """wait until vm is in the desired status and return True.
678 If the VM gets in ERROR status, return false.
679 If the timeout is reached generate an exception"""
680 elapsed_time = 0
681 while elapsed_time < server_timeout:
682 vm_status = self.nova.servers.get(vm_id).status
683 if vm_status == status:
684 return True
685 if vm_status == 'ERROR':
686 return False
687 time.sleep(1)
688 elapsed_time += 1
689
690 # if we exceeded the timeout rollback
691 if elapsed_time >= server_timeout:
692 raise vimconn.vimconnException('Timeout waiting for instance ' + vm_id + ' to get ' + status,
693 http_code=vimconn.HTTP_Request_Timeout)
694
montesmoreno0c8def02016-12-22 12:16:23 +0000695 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 +0100696 '''Adds a VM instance to VIM
697 Params:
698 start: indicates if VM must start or boot in pause mode. Ignored
699 image_id,flavor_id: iamge and flavor uuid
700 net_list: list of interfaces, each one is a dictionary with:
701 name:
702 net_id: network uuid to connect
703 vpci: virtual vcpi to assign, ignored because openstack lack #TODO
704 model: interface model, ignored #TODO
705 mac_address: used for SR-IOV ifaces #TODO for other types
706 use: 'data', 'bridge', 'mgmt'
707 type: 'virtual', 'PF', 'VF', 'VFnotShared'
708 vim_id: filled/added by this function
ahmadsaf853d452016-12-22 11:33:47 +0500709 floating_ip: True/False (or it can be None)
tierno7edb6752016-03-21 17:37:52 +0100710 #TODO ip, security groups
tiernoae4a8d12016-07-08 12:30:39 +0200711 Returns the instance identifier
tierno7edb6752016-03-21 17:37:52 +0100712 '''
tiernofa51c202017-01-27 14:58:17 +0100713 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 +0100714 try:
Pablo Montes Moreno6a7785b2017-07-03 10:44:30 +0200715 server = None
tierno6e116232016-07-18 13:01:40 +0200716 metadata={}
tierno7edb6752016-03-21 17:37:52 +0100717 net_list_vim=[]
Pablo Montes Moreno6a7785b2017-07-03 10:44:30 +0200718 external_network=[] # list of external networks to be connected to instance, later on used to create floating_ip
719 no_secured_ports = [] # List of port-is with port-security disabled
tierno7edb6752016-03-21 17:37:52 +0100720 self._reload_connection()
Pablo Montes Moreno6a7785b2017-07-03 10:44:30 +0200721 metadata_vpci={} # For a specific neutron plugin
tiernob84cbdc2017-07-07 14:30:30 +0200722 block_device_mapping = None
tierno7edb6752016-03-21 17:37:52 +0100723 for net in net_list:
724 if not net.get("net_id"): #skip non connected iface
725 continue
Pablo Montes Moreno3be0b2a2017-03-30 13:22:15 +0200726
727 port_dict={
728 "network_id": net["net_id"],
729 "name": net.get("name"),
730 "admin_state_up": True
731 }
732 if net["type"]=="virtual":
733 if "vpci" in net:
734 metadata_vpci[ net["net_id"] ] = [[ net["vpci"], "" ]]
735 elif net["type"]=="VF": # for VF
736 if "vpci" in net:
737 if "VF" not in metadata_vpci:
738 metadata_vpci["VF"]=[]
739 metadata_vpci["VF"].append([ net["vpci"], "" ])
740 port_dict["binding:vnic_type"]="direct"
Pablo Montes Moreno6a7785b2017-07-03 10:44:30 +0200741 else: # For PT
Pablo Montes Moreno3be0b2a2017-03-30 13:22:15 +0200742 if "vpci" in net:
743 if "PF" not in metadata_vpci:
744 metadata_vpci["PF"]=[]
745 metadata_vpci["PF"].append([ net["vpci"], "" ])
746 port_dict["binding:vnic_type"]="direct-physical"
747 if not port_dict["name"]:
748 port_dict["name"]=name
749 if net.get("mac_address"):
750 port_dict["mac_address"]=net["mac_address"]
Pablo Montes Moreno3be0b2a2017-03-30 13:22:15 +0200751 new_port = self.neutron.create_port({"port": port_dict })
752 net["mac_adress"] = new_port["port"]["mac_address"]
753 net["vim_id"] = new_port["port"]["id"]
tiernob84cbdc2017-07-07 14:30:30 +0200754 # if try to use a network without subnetwork, it will return a emtpy list
755 fixed_ips = new_port["port"].get("fixed_ips")
756 if fixed_ips:
757 net["ip"] = fixed_ips[0].get("ip_address")
758 else:
759 net["ip"] = None
Pablo Montes Moreno3be0b2a2017-03-30 13:22:15 +0200760 net_list_vim.append({"port-id": new_port["port"]["id"]})
761
ahmadsaf853d452016-12-22 11:33:47 +0500762 if net.get('floating_ip', False):
tiernof8383b82017-01-18 15:49:48 +0100763 net['exit_on_floating_ip_error'] = True
ahmadsaf853d452016-12-22 11:33:47 +0500764 external_network.append(net)
tiernof8383b82017-01-18 15:49:48 +0100765 elif net['use'] == 'mgmt' and self.config.get('use_floating_ip'):
766 net['exit_on_floating_ip_error'] = False
767 external_network.append(net)
768
Pablo Montes Moreno6a7785b2017-07-03 10:44:30 +0200769 # If port security is disabled when the port has not yet been attached to the VM, then all vm traffic is dropped.
770 # As a workaround we wait until the VM is active and then disable the port-security
771 if net.get("port_security") == False:
772 no_secured_ports.append(new_port["port"]["id"])
773
tierno7edb6752016-03-21 17:37:52 +0100774 if metadata_vpci:
775 metadata = {"pci_assignement": json.dumps(metadata_vpci)}
tiernoafbced42016-07-23 01:43:53 +0200776 if len(metadata["pci_assignement"]) >255:
tierno6e116232016-07-18 13:01:40 +0200777 #limit the metadata size
778 #metadata["pci_assignement"] = metadata["pci_assignement"][0:255]
779 self.logger.warn("Metadata deleted since it exceeds the expected length (255) ")
780 metadata = {}
tierno7edb6752016-03-21 17:37:52 +0100781
tiernoae4a8d12016-07-08 12:30:39 +0200782 self.logger.debug("name '%s' image_id '%s'flavor_id '%s' net_list_vim '%s' description '%s' metadata %s",
783 name, image_id, flavor_id, str(net_list_vim), description, str(metadata))
tierno7edb6752016-03-21 17:37:52 +0100784
785 security_groups = self.config.get('security_groups')
786 if type(security_groups) is str:
787 security_groups = ( security_groups, )
tierno36c0b172017-01-12 18:32:28 +0100788 #cloud config
789 userdata=None
790 config_drive = None
tiernoa4e1a6e2016-08-31 14:19:40 +0200791 if isinstance(cloud_config, dict):
tierno36c0b172017-01-12 18:32:28 +0100792 if cloud_config.get("user-data"):
793 userdata=cloud_config["user-data"]
794 if cloud_config.get("boot-data-drive") != None:
795 config_drive = cloud_config["boot-data-drive"]
796 if cloud_config.get("config-files") or cloud_config.get("users") or cloud_config.get("key-pairs"):
797 if userdata:
798 raise vimconn.vimconnConflictException("Cloud-config cannot contain both 'userdata' and 'config-files'/'users'/'key-pairs'")
799 userdata_dict={}
800 #default user
801 if cloud_config.get("key-pairs"):
802 userdata_dict["ssh-authorized-keys"] = cloud_config["key-pairs"]
803 userdata_dict["users"] = [{"default": None, "ssh-authorized-keys": cloud_config["key-pairs"] }]
804 if cloud_config.get("users"):
tierno01d0bf52017-01-25 14:27:20 +0100805 if "users" not in userdata_dict:
tierno36c0b172017-01-12 18:32:28 +0100806 userdata_dict["users"] = [ "default" ]
807 for user in cloud_config["users"]:
808 user_info = {
809 "name" : user["name"],
810 "sudo": "ALL = (ALL)NOPASSWD:ALL"
811 }
812 if "user-info" in user:
813 user_info["gecos"] = user["user-info"]
814 if user.get("key-pairs"):
815 user_info["ssh-authorized-keys"] = user["key-pairs"]
816 userdata_dict["users"].append(user_info)
817
818 if cloud_config.get("config-files"):
819 userdata_dict["write_files"] = []
820 for file in cloud_config["config-files"]:
821 file_info = {
822 "path" : file["dest"],
823 "content": file["content"]
824 }
825 if file.get("encoding"):
826 file_info["encoding"] = file["encoding"]
827 if file.get("permissions"):
828 file_info["permissions"] = file["permissions"]
829 if file.get("owner"):
830 file_info["owner"] = file["owner"]
831 userdata_dict["write_files"].append(file_info)
832 userdata = "#cloud-config\n"
833 userdata += yaml.safe_dump(userdata_dict, indent=4, default_flow_style=False)
tiernoa4e1a6e2016-08-31 14:19:40 +0200834 self.logger.debug("userdata: %s", userdata)
835 elif isinstance(cloud_config, str):
836 userdata = cloud_config
montesmoreno0c8def02016-12-22 12:16:23 +0000837
838 #Create additional volumes in case these are present in disk_list
montesmoreno0c8def02016-12-22 12:16:23 +0000839 base_disk_index = ord('b')
840 if disk_list != None:
tiernob84cbdc2017-07-07 14:30:30 +0200841 block_device_mapping = {}
montesmoreno0c8def02016-12-22 12:16:23 +0000842 for disk in disk_list:
843 if 'image_id' in disk:
844 volume = self.cinder.volumes.create(size = disk['size'],name = name + '_vd' +
845 chr(base_disk_index), imageRef = disk['image_id'])
846 else:
847 volume = self.cinder.volumes.create(size=disk['size'], name=name + '_vd' +
848 chr(base_disk_index))
849 block_device_mapping['_vd' + chr(base_disk_index)] = volume.id
850 base_disk_index += 1
851
852 #wait until volumes are with status available
853 keep_waiting = True
854 elapsed_time = 0
855 while keep_waiting and elapsed_time < volume_timeout:
856 keep_waiting = False
857 for volume_id in block_device_mapping.itervalues():
858 if self.cinder.volumes.get(volume_id).status != 'available':
859 keep_waiting = True
860 if keep_waiting:
861 time.sleep(1)
862 elapsed_time += 1
863
864 #if we exceeded the timeout rollback
865 if elapsed_time >= volume_timeout:
866 #delete the volumes we just created
867 for volume_id in block_device_mapping.itervalues():
868 self.cinder.volumes.delete(volume_id)
869
870 #delete ports we just created
871 for net_item in net_list_vim:
872 if 'port-id' in net_item:
montesmorenocf227142017-01-12 12:24:21 +0000873 self.neutron.delete_port(net_item['port-id'])
montesmoreno0c8def02016-12-22 12:16:23 +0000874
875 raise vimconn.vimconnException('Timeout creating volumes for instance ' + name,
876 http_code=vimconn.HTTP_Request_Timeout)
877
Pablo Montes Morenob7490b52017-06-20 10:49:58 +0200878 self.logger.debug("nova.servers.create({}, {}, {}, nics={}, meta={}, security_groups={}," \
879 "availability_zone={}, key_name={}, userdata={}, config_drive={}, " \
880 "block_device_mapping={})".format(name, image_id, flavor_id, net_list_vim,
881 metadata, security_groups, self.config.get('availability_zone'),
882 self.config.get('keypair'), userdata, config_drive, block_device_mapping))
tierno7edb6752016-03-21 17:37:52 +0100883 server = self.nova.servers.create(name, image_id, flavor_id, nics=net_list_vim, meta=metadata,
montesmoreno0c8def02016-12-22 12:16:23 +0000884 security_groups=security_groups,
885 availability_zone=self.config.get('availability_zone'),
886 key_name=self.config.get('keypair'),
887 userdata=userdata,
tiernob84cbdc2017-07-07 14:30:30 +0200888 config_drive=config_drive,
889 block_device_mapping=block_device_mapping
montesmoreno0c8def02016-12-22 12:16:23 +0000890 ) # , description=description)
Pablo Montes Moreno6a7785b2017-07-03 10:44:30 +0200891
892 # Previously mentioned workaround to wait until the VM is active and then disable the port-security
893 if no_secured_ports:
894 self.__wait_for_vm(server.id, 'ACTIVE')
895
896 for port_id in no_secured_ports:
897 try:
898 self.neutron.update_port(port_id, {"port": {"port_security_enabled": False, "security_groups": None} })
899
900 except Exception as e:
901 self.logger.error("It was not possible to disable port security for port {}".format(port_id))
902 self.delete_vminstance(server.id)
903 raise
904
tiernoae4a8d12016-07-08 12:30:39 +0200905 #print "DONE :-)", server
ahmadsaf853d452016-12-22 11:33:47 +0500906 pool_id = None
907 floating_ips = self.neutron.list_floatingips().get("floatingips", ())
Pablo Montes Moreno6a7785b2017-07-03 10:44:30 +0200908
909 if external_network:
910 self.__wait_for_vm(server.id, 'ACTIVE')
911
ahmadsaf853d452016-12-22 11:33:47 +0500912 for floating_network in external_network:
tiernof8383b82017-01-18 15:49:48 +0100913 try:
tiernof8383b82017-01-18 15:49:48 +0100914 assigned = False
915 while(assigned == False):
916 if floating_ips:
917 ip = floating_ips.pop(0)
918 if not ip.get("port_id", False) and ip.get('tenant_id') == server.tenant_id:
919 free_floating_ip = ip.get("floating_ip_address")
920 try:
921 fix_ip = floating_network.get('ip')
922 server.add_floating_ip(free_floating_ip, fix_ip)
923 assigned = True
924 except Exception as e:
925 raise vimconn.vimconnException(type(e).__name__ + ": Cannot create floating_ip "+ str(e), http_code=vimconn.HTTP_Conflict)
926 else:
927 #Find the external network
928 external_nets = list()
929 for net in self.neutron.list_networks()['networks']:
930 if net['router:external']:
931 external_nets.append(net)
932
933 if len(external_nets) == 0:
934 raise vimconn.vimconnException("Cannot create floating_ip automatically since no external "
935 "network is present",
936 http_code=vimconn.HTTP_Conflict)
937 if len(external_nets) > 1:
938 raise vimconn.vimconnException("Cannot create floating_ip automatically since multiple "
939 "external networks are present",
940 http_code=vimconn.HTTP_Conflict)
941
942 pool_id = external_nets[0].get('id')
943 param = {'floatingip': {'floating_network_id': pool_id, 'tenant_id': server.tenant_id}}
ahmadsaf853d452016-12-22 11:33:47 +0500944 try:
tiernof8383b82017-01-18 15:49:48 +0100945 #self.logger.debug("Creating floating IP")
946 new_floating_ip = self.neutron.create_floatingip(param)
947 free_floating_ip = new_floating_ip['floatingip']['floating_ip_address']
ahmadsaf853d452016-12-22 11:33:47 +0500948 fix_ip = floating_network.get('ip')
949 server.add_floating_ip(free_floating_ip, fix_ip)
tiernof8383b82017-01-18 15:49:48 +0100950 assigned=True
ahmadsaf853d452016-12-22 11:33:47 +0500951 except Exception as e:
tiernof8383b82017-01-18 15:49:48 +0100952 raise vimconn.vimconnException(type(e).__name__ + ": Cannot assign floating_ip "+ str(e), http_code=vimconn.HTTP_Conflict)
953 except Exception as e:
954 if not floating_network['exit_on_floating_ip_error']:
955 self.logger.warn("Cannot create floating_ip. %s", str(e))
956 continue
tiernof8383b82017-01-18 15:49:48 +0100957 raise
montesmoreno2a1fc4e2017-01-09 16:46:04 +0000958
tiernoae4a8d12016-07-08 12:30:39 +0200959 return server.id
tierno7edb6752016-03-21 17:37:52 +0100960# except nvExceptions.NotFound as e:
961# error_value=-vimconn.HTTP_Not_Found
962# error_text= "vm instance %s not found" % vm_id
Pablo Montes Moreno6a7785b2017-07-03 10:44:30 +0200963# except TypeError as e:
964# raise vimconn.vimconnException(type(e).__name__ + ": "+ str(e), http_code=vimconn.HTTP_Bad_Request)
965
966 except Exception as e:
montesmoreno2a1fc4e2017-01-09 16:46:04 +0000967 # delete the volumes we just created
tiernob84cbdc2017-07-07 14:30:30 +0200968 if block_device_mapping:
montesmoreno2a1fc4e2017-01-09 16:46:04 +0000969 for volume_id in block_device_mapping.itervalues():
970 self.cinder.volumes.delete(volume_id)
971
Pablo Montes Moreno6a7785b2017-07-03 10:44:30 +0200972 # Delete the VM
973 if server != None:
974 self.delete_vminstance(server.id)
975 else:
976 # delete ports we just created
977 for net_item in net_list_vim:
978 if 'port-id' in net_item:
979 self.neutron.delete_port(net_item['port-id'])
980
tiernoae4a8d12016-07-08 12:30:39 +0200981 self._format_exception(e)
tierno7edb6752016-03-21 17:37:52 +0100982
tiernoae4a8d12016-07-08 12:30:39 +0200983 def get_vminstance(self,vm_id):
tierno7edb6752016-03-21 17:37:52 +0100984 '''Returns the VM instance information from VIM'''
tiernoae4a8d12016-07-08 12:30:39 +0200985 #self.logger.debug("Getting VM from VIM")
tierno7edb6752016-03-21 17:37:52 +0100986 try:
987 self._reload_connection()
988 server = self.nova.servers.find(id=vm_id)
989 #TODO parse input and translate to VIM format (openmano_schemas.new_vminstance_response_schema)
tiernoae4a8d12016-07-08 12:30:39 +0200990 return server.to_dict()
tierno8e995ce2016-09-22 08:13:00 +0000991 except (ksExceptions.ClientException, nvExceptions.ClientException, nvExceptions.NotFound, ConnectionError) as e:
tiernoae4a8d12016-07-08 12:30:39 +0200992 self._format_exception(e)
993
994 def get_vminstance_console(self,vm_id, console_type="vnc"):
tierno7edb6752016-03-21 17:37:52 +0100995 '''
996 Get a console for the virtual machine
997 Params:
998 vm_id: uuid of the VM
999 console_type, can be:
1000 "novnc" (by default), "xvpvnc" for VNC types,
1001 "rdp-html5" for RDP types, "spice-html5" for SPICE types
tiernoae4a8d12016-07-08 12:30:39 +02001002 Returns dict with the console parameters:
1003 protocol: ssh, ftp, http, https, ...
1004 server: usually ip address
1005 port: the http, ssh, ... port
1006 suffix: extra text, e.g. the http path and query string
tierno7edb6752016-03-21 17:37:52 +01001007 '''
tiernoae4a8d12016-07-08 12:30:39 +02001008 self.logger.debug("Getting VM CONSOLE from VIM")
tierno7edb6752016-03-21 17:37:52 +01001009 try:
1010 self._reload_connection()
1011 server = self.nova.servers.find(id=vm_id)
1012 if console_type == None or console_type == "novnc":
1013 console_dict = server.get_vnc_console("novnc")
1014 elif console_type == "xvpvnc":
1015 console_dict = server.get_vnc_console(console_type)
1016 elif console_type == "rdp-html5":
1017 console_dict = server.get_rdp_console(console_type)
1018 elif console_type == "spice-html5":
1019 console_dict = server.get_spice_console(console_type)
1020 else:
tiernoae4a8d12016-07-08 12:30:39 +02001021 raise vimconn.vimconnException("console type '{}' not allowed".format(console_type), http_code=vimconn.HTTP_Bad_Request)
tierno7edb6752016-03-21 17:37:52 +01001022
1023 console_dict1 = console_dict.get("console")
1024 if console_dict1:
1025 console_url = console_dict1.get("url")
1026 if console_url:
1027 #parse console_url
1028 protocol_index = console_url.find("//")
1029 suffix_index = console_url[protocol_index+2:].find("/") + protocol_index+2
1030 port_index = console_url[protocol_index+2:suffix_index].find(":") + protocol_index+2
1031 if protocol_index < 0 or port_index<0 or suffix_index<0:
1032 return -vimconn.HTTP_Internal_Server_Error, "Unexpected response from VIM"
1033 console_dict={"protocol": console_url[0:protocol_index],
1034 "server": console_url[protocol_index+2:port_index],
1035 "port": console_url[port_index:suffix_index],
1036 "suffix": console_url[suffix_index+1:]
1037 }
1038 protocol_index += 2
tiernoae4a8d12016-07-08 12:30:39 +02001039 return console_dict
1040 raise vimconn.vimconnUnexpectedResponse("Unexpected response from VIM")
tierno7edb6752016-03-21 17:37:52 +01001041
tierno8e995ce2016-09-22 08:13:00 +00001042 except (nvExceptions.NotFound, ksExceptions.ClientException, nvExceptions.ClientException, nvExceptions.BadRequest, ConnectionError) as e:
tiernoae4a8d12016-07-08 12:30:39 +02001043 self._format_exception(e)
tierno7edb6752016-03-21 17:37:52 +01001044
tiernoae4a8d12016-07-08 12:30:39 +02001045 def delete_vminstance(self, vm_id):
1046 '''Removes a VM instance from VIM. Returns the old identifier
tierno7edb6752016-03-21 17:37:52 +01001047 '''
tiernoae4a8d12016-07-08 12:30:39 +02001048 #print "osconnector: Getting VM from VIM"
tierno7edb6752016-03-21 17:37:52 +01001049 try:
1050 self._reload_connection()
1051 #delete VM ports attached to this networks before the virtual machine
1052 ports = self.neutron.list_ports(device_id=vm_id)
1053 for p in ports['ports']:
1054 try:
1055 self.neutron.delete_port(p["id"])
1056 except Exception as e:
tiernoae4a8d12016-07-08 12:30:39 +02001057 self.logger.error("Error deleting port: " + type(e).__name__ + ": "+ str(e))
montesmoreno0c8def02016-12-22 12:16:23 +00001058
1059 #commented because detaching the volumes makes the servers.delete not work properly ?!?
1060 #dettach volumes attached
1061 server = self.nova.servers.get(vm_id)
1062 volumes_attached_dict = server._info['os-extended-volumes:volumes_attached']
1063 #for volume in volumes_attached_dict:
1064 # self.cinder.volumes.detach(volume['id'])
1065
tierno7edb6752016-03-21 17:37:52 +01001066 self.nova.servers.delete(vm_id)
montesmoreno0c8def02016-12-22 12:16:23 +00001067
1068 #delete volumes.
1069 #Although having detached them should have them in active status
1070 #we ensure in this loop
1071 keep_waiting = True
1072 elapsed_time = 0
1073 while keep_waiting and elapsed_time < volume_timeout:
1074 keep_waiting = False
1075 for volume in volumes_attached_dict:
1076 if self.cinder.volumes.get(volume['id']).status != 'available':
1077 keep_waiting = True
1078 else:
1079 self.cinder.volumes.delete(volume['id'])
1080 if keep_waiting:
1081 time.sleep(1)
1082 elapsed_time += 1
1083
tiernoae4a8d12016-07-08 12:30:39 +02001084 return vm_id
tierno8e995ce2016-09-22 08:13:00 +00001085 except (nvExceptions.NotFound, ksExceptions.ClientException, nvExceptions.ClientException, ConnectionError) as e:
tiernoae4a8d12016-07-08 12:30:39 +02001086 self._format_exception(e)
tierno7edb6752016-03-21 17:37:52 +01001087 #TODO insert exception vimconn.HTTP_Unauthorized
1088 #if reaching here is because an exception
tierno7edb6752016-03-21 17:37:52 +01001089
tiernoae4a8d12016-07-08 12:30:39 +02001090 def refresh_vms_status(self, vm_list):
1091 '''Get the status of the virtual machines and their interfaces/ports
1092 Params: the list of VM identifiers
1093 Returns a dictionary with:
1094 vm_id: #VIM id of this Virtual Machine
1095 status: #Mandatory. Text with one of:
1096 # DELETED (not found at vim)
1097 # VIM_ERROR (Cannot connect to VIM, VIM response error, ...)
1098 # OTHER (Vim reported other status not understood)
1099 # ERROR (VIM indicates an ERROR status)
1100 # ACTIVE, PAUSED, SUSPENDED, INACTIVE (not running),
1101 # CREATING (on building process), ERROR
1102 # ACTIVE:NoMgmtIP (Active but any of its interface has an IP address
1103 #
1104 error_msg: #Text with VIM error message, if any. Or the VIM connection ERROR
1105 vim_info: #Text with plain information obtained from vim (yaml.safe_dump)
1106 interfaces:
1107 - vim_info: #Text with plain information obtained from vim (yaml.safe_dump)
1108 mac_address: #Text format XX:XX:XX:XX:XX:XX
1109 vim_net_id: #network id where this interface is connected
1110 vim_interface_id: #interface/port VIM id
1111 ip_address: #null, or text with IPv4, IPv6 address
tierno867ffe92017-03-27 12:50:34 +02001112 compute_node: #identification of compute node where PF,VF interface is allocated
1113 pci: #PCI address of the NIC that hosts the PF,VF
1114 vlan: #physical VLAN used for VF
tierno7edb6752016-03-21 17:37:52 +01001115 '''
tiernoae4a8d12016-07-08 12:30:39 +02001116 vm_dict={}
1117 self.logger.debug("refresh_vms status: Getting tenant VM instance information from VIM")
1118 for vm_id in vm_list:
1119 vm={}
1120 try:
1121 vm_vim = self.get_vminstance(vm_id)
1122 if vm_vim['status'] in vmStatus2manoFormat:
1123 vm['status'] = vmStatus2manoFormat[ vm_vim['status'] ]
tierno7edb6752016-03-21 17:37:52 +01001124 else:
tiernoae4a8d12016-07-08 12:30:39 +02001125 vm['status'] = "OTHER"
1126 vm['error_msg'] = "VIM status reported " + vm_vim['status']
tierno8e995ce2016-09-22 08:13:00 +00001127 try:
1128 vm['vim_info'] = yaml.safe_dump(vm_vim, default_flow_style=True, width=256)
1129 except yaml.representer.RepresenterError:
1130 vm['vim_info'] = str(vm_vim)
tiernoae4a8d12016-07-08 12:30:39 +02001131 vm["interfaces"] = []
1132 if vm_vim.get('fault'):
1133 vm['error_msg'] = str(vm_vim['fault'])
1134 #get interfaces
tierno7edb6752016-03-21 17:37:52 +01001135 try:
tiernoae4a8d12016-07-08 12:30:39 +02001136 self._reload_connection()
1137 port_dict=self.neutron.list_ports(device_id=vm_id)
1138 for port in port_dict["ports"]:
1139 interface={}
tierno8e995ce2016-09-22 08:13:00 +00001140 try:
1141 interface['vim_info'] = yaml.safe_dump(port, default_flow_style=True, width=256)
1142 except yaml.representer.RepresenterError:
1143 interface['vim_info'] = str(port)
tiernoae4a8d12016-07-08 12:30:39 +02001144 interface["mac_address"] = port.get("mac_address")
1145 interface["vim_net_id"] = port["network_id"]
1146 interface["vim_interface_id"] = port["id"]
Mike Marchetti5b9da422017-05-02 15:35:47 -04001147 # check if OS-EXT-SRV-ATTR:host is there,
1148 # in case of non-admin credentials, it will be missing
1149 if vm_vim.get('OS-EXT-SRV-ATTR:host'):
1150 interface["compute_node"] = vm_vim['OS-EXT-SRV-ATTR:host']
tierno867ffe92017-03-27 12:50:34 +02001151 interface["pci"] = None
Mike Marchetti5b9da422017-05-02 15:35:47 -04001152
1153 # check if binding:profile is there,
1154 # in case of non-admin credentials, it will be missing
1155 if port.get('binding:profile'):
1156 if port['binding:profile'].get('pci_slot'):
1157 # TODO: At the moment sr-iov pci addresses are converted to PF pci addresses by setting the slot to 0x00
1158 # TODO: This is just a workaround valid for niantinc. Find a better way to do so
1159 # CHANGE DDDD:BB:SS.F to DDDD:BB:00.(F%2) assuming there are 2 ports per nic
1160 pci = port['binding:profile']['pci_slot']
1161 # interface["pci"] = pci[:-4] + "00." + str(int(pci[-1]) % 2)
1162 interface["pci"] = pci
tierno867ffe92017-03-27 12:50:34 +02001163 interface["vlan"] = None
Pablo Montes Moreno3be0b2a2017-03-30 13:22:15 +02001164 #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 +01001165 network = self.neutron.show_network(port["network_id"])
Pablo Montes Moreno3be0b2a2017-03-30 13:22:15 +02001166 if network['network'].get('provider:network_type') == 'vlan' and \
1167 port.get("binding:vnic_type") == "direct":
tierno867ffe92017-03-27 12:50:34 +02001168 interface["vlan"] = network['network'].get('provider:segmentation_id')
tiernoae4a8d12016-07-08 12:30:39 +02001169 ips=[]
1170 #look for floating ip address
1171 floating_ip_dict = self.neutron.list_floatingips(port_id=port["id"])
1172 if floating_ip_dict.get("floatingips"):
1173 ips.append(floating_ip_dict["floatingips"][0].get("floating_ip_address") )
tierno7edb6752016-03-21 17:37:52 +01001174
tiernoae4a8d12016-07-08 12:30:39 +02001175 for subnet in port["fixed_ips"]:
1176 ips.append(subnet["ip_address"])
1177 interface["ip_address"] = ";".join(ips)
1178 vm["interfaces"].append(interface)
1179 except Exception as e:
1180 self.logger.error("Error getting vm interface information " + type(e).__name__ + ": "+ str(e))
1181 except vimconn.vimconnNotFoundException as e:
1182 self.logger.error("Exception getting vm status: %s", str(e))
1183 vm['status'] = "DELETED"
1184 vm['error_msg'] = str(e)
1185 except vimconn.vimconnException as e:
1186 self.logger.error("Exception getting vm status: %s", str(e))
1187 vm['status'] = "VIM_ERROR"
1188 vm['error_msg'] = str(e)
1189 vm_dict[vm_id] = vm
1190 return vm_dict
tierno7edb6752016-03-21 17:37:52 +01001191
tiernoae4a8d12016-07-08 12:30:39 +02001192 def action_vminstance(self, vm_id, action_dict):
tierno7edb6752016-03-21 17:37:52 +01001193 '''Send and action over a VM instance from VIM
tiernoae4a8d12016-07-08 12:30:39 +02001194 Returns the vm_id if the action was successfully sent to the VIM'''
1195 self.logger.debug("Action over VM '%s': %s", vm_id, str(action_dict))
tierno7edb6752016-03-21 17:37:52 +01001196 try:
1197 self._reload_connection()
1198 server = self.nova.servers.find(id=vm_id)
1199 if "start" in action_dict:
1200 if action_dict["start"]=="rebuild":
1201 server.rebuild()
1202 else:
1203 if server.status=="PAUSED":
1204 server.unpause()
1205 elif server.status=="SUSPENDED":
1206 server.resume()
1207 elif server.status=="SHUTOFF":
1208 server.start()
1209 elif "pause" in action_dict:
1210 server.pause()
1211 elif "resume" in action_dict:
1212 server.resume()
1213 elif "shutoff" in action_dict or "shutdown" in action_dict:
1214 server.stop()
1215 elif "forceOff" in action_dict:
1216 server.stop() #TODO
1217 elif "terminate" in action_dict:
1218 server.delete()
1219 elif "createImage" in action_dict:
1220 server.create_image()
1221 #"path":path_schema,
1222 #"description":description_schema,
1223 #"name":name_schema,
1224 #"metadata":metadata_schema,
1225 #"imageRef": id_schema,
1226 #"disk": {"oneOf":[{"type": "null"}, {"type":"string"}] },
1227 elif "rebuild" in action_dict:
1228 server.rebuild(server.image['id'])
1229 elif "reboot" in action_dict:
1230 server.reboot() #reboot_type='SOFT'
1231 elif "console" in action_dict:
1232 console_type = action_dict["console"]
1233 if console_type == None or console_type == "novnc":
1234 console_dict = server.get_vnc_console("novnc")
1235 elif console_type == "xvpvnc":
1236 console_dict = server.get_vnc_console(console_type)
1237 elif console_type == "rdp-html5":
1238 console_dict = server.get_rdp_console(console_type)
1239 elif console_type == "spice-html5":
1240 console_dict = server.get_spice_console(console_type)
1241 else:
tiernoae4a8d12016-07-08 12:30:39 +02001242 raise vimconn.vimconnException("console type '{}' not allowed".format(console_type),
1243 http_code=vimconn.HTTP_Bad_Request)
tierno7edb6752016-03-21 17:37:52 +01001244 try:
1245 console_url = console_dict["console"]["url"]
1246 #parse console_url
1247 protocol_index = console_url.find("//")
1248 suffix_index = console_url[protocol_index+2:].find("/") + protocol_index+2
1249 port_index = console_url[protocol_index+2:suffix_index].find(":") + protocol_index+2
1250 if protocol_index < 0 or port_index<0 or suffix_index<0:
tiernoae4a8d12016-07-08 12:30:39 +02001251 raise vimconn.vimconnException("Unexpected response from VIM " + str(console_dict))
tierno7edb6752016-03-21 17:37:52 +01001252 console_dict2={"protocol": console_url[0:protocol_index],
1253 "server": console_url[protocol_index+2 : port_index],
1254 "port": int(console_url[port_index+1 : suffix_index]),
1255 "suffix": console_url[suffix_index+1:]
1256 }
tiernoae4a8d12016-07-08 12:30:39 +02001257 return console_dict2
1258 except Exception as e:
1259 raise vimconn.vimconnException("Unexpected response from VIM " + str(console_dict))
tierno7edb6752016-03-21 17:37:52 +01001260
tiernoae4a8d12016-07-08 12:30:39 +02001261 return vm_id
tierno8e995ce2016-09-22 08:13:00 +00001262 except (ksExceptions.ClientException, nvExceptions.ClientException, nvExceptions.NotFound, ConnectionError) as e:
tiernoae4a8d12016-07-08 12:30:39 +02001263 self._format_exception(e)
1264 #TODO insert exception vimconn.HTTP_Unauthorized
1265
1266#NOT USED FUNCTIONS
1267
1268 def new_external_port(self, port_data):
1269 #TODO openstack if needed
1270 '''Adds a external port to VIM'''
1271 '''Returns the port identifier'''
1272 return -vimconn.HTTP_Internal_Server_Error, "osconnector.new_external_port() not implemented"
1273
1274 def connect_port_network(self, port_id, network_id, admin=False):
1275 #TODO openstack if needed
1276 '''Connects a external port to a network'''
1277 '''Returns status code of the VIM response'''
1278 return -vimconn.HTTP_Internal_Server_Error, "osconnector.connect_port_network() not implemented"
1279
1280 def new_user(self, user_name, user_passwd, tenant_id=None):
1281 '''Adds a new user to openstack VIM'''
1282 '''Returns the user identifier'''
1283 self.logger.debug("osconnector: Adding a new user to VIM")
1284 try:
1285 self._reload_connection()
1286 user=self.keystone.users.create(user_name, user_passwd, tenant_id=tenant_id)
1287 #self.keystone.tenants.add_user(self.k_creds["username"], #role)
1288 return user.id
1289 except ksExceptions.ConnectionError as e:
1290 error_value=-vimconn.HTTP_Bad_Request
1291 error_text= type(e).__name__ + ": "+ (str(e) if len(e.args)==0 else str(e.args[0]))
1292 except ksExceptions.ClientException as e: #TODO remove
tierno7edb6752016-03-21 17:37:52 +01001293 error_value=-vimconn.HTTP_Bad_Request
1294 error_text= type(e).__name__ + ": "+ (str(e) if len(e.args)==0 else str(e.args[0]))
1295 #TODO insert exception vimconn.HTTP_Unauthorized
1296 #if reaching here is because an exception
1297 if self.debug:
tiernoae4a8d12016-07-08 12:30:39 +02001298 self.logger.debug("new_user " + error_text)
tierno7edb6752016-03-21 17:37:52 +01001299 return error_value, error_text
tiernoae4a8d12016-07-08 12:30:39 +02001300
1301 def delete_user(self, user_id):
1302 '''Delete a user from openstack VIM'''
1303 '''Returns the user identifier'''
1304 if self.debug:
1305 print "osconnector: Deleting a user from VIM"
1306 try:
1307 self._reload_connection()
1308 self.keystone.users.delete(user_id)
1309 return 1, user_id
1310 except ksExceptions.ConnectionError as e:
1311 error_value=-vimconn.HTTP_Bad_Request
1312 error_text= type(e).__name__ + ": "+ (str(e) if len(e.args)==0 else str(e.args[0]))
1313 except ksExceptions.NotFound as e:
1314 error_value=-vimconn.HTTP_Not_Found
1315 error_text= type(e).__name__ + ": "+ (str(e) if len(e.args)==0 else str(e.args[0]))
1316 except ksExceptions.ClientException as e: #TODO remove
1317 error_value=-vimconn.HTTP_Bad_Request
1318 error_text= type(e).__name__ + ": "+ (str(e) if len(e.args)==0 else str(e.args[0]))
1319 #TODO insert exception vimconn.HTTP_Unauthorized
1320 #if reaching here is because an exception
1321 if self.debug:
1322 print "delete_tenant " + error_text
1323 return error_value, error_text
1324
tierno7edb6752016-03-21 17:37:52 +01001325 def get_hosts_info(self):
1326 '''Get the information of deployed hosts
1327 Returns the hosts content'''
1328 if self.debug:
1329 print "osconnector: Getting Host info from VIM"
1330 try:
1331 h_list=[]
1332 self._reload_connection()
1333 hypervisors = self.nova.hypervisors.list()
1334 for hype in hypervisors:
1335 h_list.append( hype.to_dict() )
1336 return 1, {"hosts":h_list}
1337 except nvExceptions.NotFound as e:
1338 error_value=-vimconn.HTTP_Not_Found
1339 error_text= (str(e) if len(e.args)==0 else str(e.args[0]))
1340 except (ksExceptions.ClientException, nvExceptions.ClientException) as e:
1341 error_value=-vimconn.HTTP_Bad_Request
1342 error_text= type(e).__name__ + ": "+ (str(e) if len(e.args)==0 else str(e.args[0]))
1343 #TODO insert exception vimconn.HTTP_Unauthorized
1344 #if reaching here is because an exception
1345 if self.debug:
1346 print "get_hosts_info " + error_text
1347 return error_value, error_text
1348
1349 def get_hosts(self, vim_tenant):
1350 '''Get the hosts and deployed instances
1351 Returns the hosts content'''
1352 r, hype_dict = self.get_hosts_info()
1353 if r<0:
1354 return r, hype_dict
1355 hypervisors = hype_dict["hosts"]
1356 try:
1357 servers = self.nova.servers.list()
1358 for hype in hypervisors:
1359 for server in servers:
1360 if server.to_dict()['OS-EXT-SRV-ATTR:hypervisor_hostname']==hype['hypervisor_hostname']:
1361 if 'vm' in hype:
1362 hype['vm'].append(server.id)
1363 else:
1364 hype['vm'] = [server.id]
1365 return 1, hype_dict
1366 except nvExceptions.NotFound as e:
1367 error_value=-vimconn.HTTP_Not_Found
1368 error_text= (str(e) if len(e.args)==0 else str(e.args[0]))
1369 except (ksExceptions.ClientException, nvExceptions.ClientException) as e:
1370 error_value=-vimconn.HTTP_Bad_Request
1371 error_text= type(e).__name__ + ": "+ (str(e) if len(e.args)==0 else str(e.args[0]))
1372 #TODO insert exception vimconn.HTTP_Unauthorized
1373 #if reaching here is because an exception
1374 if self.debug:
1375 print "get_hosts " + error_text
1376 return error_value, error_text
1377
tierno7edb6752016-03-21 17:37:52 +01001378