blob: 6c46dbb8d9b68f302da60db85b970d529ea90f37 [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'''
ahmadsa95baa272016-11-30 09:14:11 +050027__author__="Alfonso Tierno, Gerardo Garcia, xFlow Research"
28__date__ ="$24-nov-2016 09:10$"
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
tierno7edb6752016-03-21 17:37:52 +010035
ahmadsa95baa272016-11-30 09:14:11 +050036from novaclient import client as nClient_v2, exceptions as nvExceptions, api_versions as APIVersion
37import keystoneclient.v2_0.client as ksClient_v2
38from novaclient.v2.client import Client as nClient
39import keystoneclient.v3.client as ksClient
tierno7edb6752016-03-21 17:37:52 +010040import keystoneclient.exceptions as ksExceptions
41import glanceclient.v2.client as glClient
42import glanceclient.client as gl1Client
43import glanceclient.exc as gl1Exceptions
44from httplib import HTTPException
ahmadsa95baa272016-11-30 09:14:11 +050045from neutronclient.neutron import client as neClient_v2
46from neutronclient.v2_0 import client as neClient
tierno7edb6752016-03-21 17:37:52 +010047from neutronclient.common import exceptions as neExceptions
48from requests.exceptions import ConnectionError
49
50'''contain the openstack virtual machine status to openmano status'''
51vmStatus2manoFormat={'ACTIVE':'ACTIVE',
52 'PAUSED':'PAUSED',
53 'SUSPENDED': 'SUSPENDED',
54 'SHUTOFF':'INACTIVE',
55 'BUILD':'BUILD',
56 'ERROR':'ERROR','DELETED':'DELETED'
57 }
58netStatus2manoFormat={'ACTIVE':'ACTIVE','PAUSED':'PAUSED','INACTIVE':'INACTIVE','BUILD':'BUILD','ERROR':'ERROR','DELETED':'DELETED'
59 }
60
61class vimconnector(vimconn.vimconnector):
tiernofe789902016-09-29 14:20:44 +000062 def __init__(self, uuid, name, tenant_id, tenant_name, url, url_admin=None, user=None, passwd=None, log_level=None, config={}):
tierno7edb6752016-03-21 17:37:52 +010063 '''using common constructor parameters. In this case
64 'url' is the keystone authorization url,
65 'url_admin' is not use
66 '''
ahmadsa95baa272016-11-30 09:14:11 +050067 self.osc_api_version = 'v2.0'
68 if config.get('APIversion') == 'v3.3':
69 self.osc_api_version = 'v3.3'
tiernoae4a8d12016-07-08 12:30:39 +020070 vimconn.vimconnector.__init__(self, uuid, name, tenant_id, tenant_name, url, url_admin, user, passwd, log_level, config)
tierno7edb6752016-03-21 17:37:52 +010071
72 self.k_creds={}
73 self.n_creds={}
74 if not url:
75 raise TypeError, 'url param can not be NoneType'
76 self.k_creds['auth_url'] = url
77 self.n_creds['auth_url'] = url
tierno392f2852016-05-13 12:28:55 +020078 if tenant_name:
79 self.k_creds['tenant_name'] = tenant_name
80 self.n_creds['project_id'] = tenant_name
81 if tenant_id:
82 self.k_creds['tenant_id'] = tenant_id
83 self.n_creds['tenant_id'] = tenant_id
tierno7edb6752016-03-21 17:37:52 +010084 if user:
85 self.k_creds['username'] = user
86 self.n_creds['username'] = user
87 if passwd:
88 self.k_creds['password'] = passwd
89 self.n_creds['api_key'] = passwd
ahmadsa95baa272016-11-30 09:14:11 +050090 if self.osc_api_version == 'v3.3':
91 self.k_creds['project_name'] = tenant_name
92 self.k_creds['project_id'] = tenant_id
tierno7edb6752016-03-21 17:37:52 +010093 self.reload_client = True
tierno73ad9e42016-09-12 18:11:11 +020094 self.logger = logging.getLogger('openmano.vim.openstack')
tiernofe789902016-09-29 14:20:44 +000095 if log_level:
96 self.logger.setLevel( getattr(logging, log_level) )
tierno7edb6752016-03-21 17:37:52 +010097
98 def __setitem__(self,index, value):
99 '''Set individuals parameters
100 Throw TypeError, KeyError
101 '''
tierno392f2852016-05-13 12:28:55 +0200102 if index=='tenant_id':
tierno7edb6752016-03-21 17:37:52 +0100103 self.reload_client=True
tierno392f2852016-05-13 12:28:55 +0200104 self.tenant_id = value
ahmadsa95baa272016-11-30 09:14:11 +0500105 if self.osc_api_version == 'v3.3':
106 if value:
107 self.k_creds['project_id'] = value
108 self.n_creds['project_id'] = value
109 else:
110 del self.k_creds['project_id']
111 del self.n_creds['project_id']
tierno392f2852016-05-13 12:28:55 +0200112 else:
ahmadsa95baa272016-11-30 09:14:11 +0500113 if value:
114 self.k_creds['tenant_id'] = value
115 self.n_creds['tenant_id'] = value
116 else:
117 del self.k_creds['tenant_id']
118 del self.n_creds['tenant_id']
tierno392f2852016-05-13 12:28:55 +0200119 elif index=='tenant_name':
120 self.reload_client=True
121 self.tenant_name = value
ahmadsa95baa272016-11-30 09:14:11 +0500122 if self.osc_api_version == 'v3.3':
123 if value:
124 self.k_creds['project_name'] = value
125 self.n_creds['project_name'] = value
126 else:
127 del self.k_creds['project_name']
128 del self.n_creds['project_name']
tierno7edb6752016-03-21 17:37:52 +0100129 else:
ahmadsa95baa272016-11-30 09:14:11 +0500130 if value:
131 self.k_creds['tenant_name'] = value
132 self.n_creds['project_id'] = value
133 else:
134 del self.k_creds['tenant_name']
135 del self.n_creds['project_id']
tierno7edb6752016-03-21 17:37:52 +0100136 elif index=='user':
137 self.reload_client=True
138 self.user = value
139 if value:
140 self.k_creds['username'] = value
141 self.n_creds['username'] = value
142 else:
143 del self.k_creds['username']
144 del self.n_creds['username']
145 elif index=='passwd':
146 self.reload_client=True
147 self.passwd = value
148 if value:
149 self.k_creds['password'] = value
150 self.n_creds['api_key'] = value
151 else:
152 del self.k_creds['password']
153 del self.n_creds['api_key']
154 elif index=='url':
155 self.reload_client=True
156 self.url = value
157 if value:
158 self.k_creds['auth_url'] = value
159 self.n_creds['auth_url'] = value
160 else:
161 raise TypeError, 'url param can not be NoneType'
162 else:
163 vimconn.vimconnector.__setitem__(self,index, value)
164
165 def _reload_connection(self):
166 '''Called before any operation, it check if credentials has changed
167 Throw keystoneclient.apiclient.exceptions.AuthorizationFailure
168 '''
169 #TODO control the timing and possible token timeout, but it seams that python client does this task for us :-)
170 if self.reload_client:
171 #test valid params
172 if len(self.n_creds) <4:
173 raise ksExceptions.ClientException("Not enough parameters to connect to openstack")
ahmadsa95baa272016-11-30 09:14:11 +0500174 if self.osc_api_version == 'v3.3':
175 self.nova = nClient(APIVersion(version_str='2'), **self.n_creds)
176 self.keystone = ksClient.Client(**self.k_creds)
177 self.ne_endpoint=self.keystone.service_catalog.url_for(service_type='network', endpoint_type='publicURL')
178 self.neutron = neClient.Client(APIVersion(version_str='2'), endpoint_url=self.ne_endpoint, token=self.keystone.auth_token, **self.k_creds)
179 else:
180 self.nova = nClient_v2.Client('2', **self.n_creds)
181 self.keystone = ksClient_v2.Client(**self.k_creds)
182 self.ne_endpoint=self.keystone.service_catalog.url_for(service_type='network', endpoint_type='publicURL')
183 self.neutron = neClient_v2.Client('2.0', endpoint_url=self.ne_endpoint, token=self.keystone.auth_token, **self.k_creds)
tierno7edb6752016-03-21 17:37:52 +0100184 self.glance_endpoint = self.keystone.service_catalog.url_for(service_type='image', endpoint_type='publicURL')
185 self.glance = glClient.Client(self.glance_endpoint, token=self.keystone.auth_token, **self.k_creds) #TODO check k_creds vs n_creds
tierno7edb6752016-03-21 17:37:52 +0100186 self.reload_client = False
ahmadsa95baa272016-11-30 09:14:11 +0500187
tierno7edb6752016-03-21 17:37:52 +0100188 def __net_os2mano(self, net_list_dict):
189 '''Transform the net openstack format to mano format
190 net_list_dict can be a list of dict or a single dict'''
191 if type(net_list_dict) is dict:
192 net_list_=(net_list_dict,)
193 elif type(net_list_dict) is list:
194 net_list_=net_list_dict
195 else:
196 raise TypeError("param net_list_dict must be a list or a dictionary")
197 for net in net_list_:
198 if net.get('provider:network_type') == "vlan":
199 net['type']='data'
200 else:
201 net['type']='bridge'
tiernoae4a8d12016-07-08 12:30:39 +0200202
203
204
205 def _format_exception(self, exception):
206 '''Transform a keystone, nova, neutron exception into a vimconn exception'''
207 if isinstance(exception, (HTTPException, gl1Exceptions.HTTPException, gl1Exceptions.CommunicationError,
tierno8e995ce2016-09-22 08:13:00 +0000208 ConnectionError, ksExceptions.ConnectionError, neExceptions.ConnectionFailed
209 )):
tiernoae4a8d12016-07-08 12:30:39 +0200210 raise vimconn.vimconnConnectionException(type(exception).__name__ + ": " + str(exception))
211 elif isinstance(exception, (nvExceptions.ClientException, ksExceptions.ClientException,
212 neExceptions.NeutronException, nvExceptions.BadRequest)):
213 raise vimconn.vimconnUnexpectedResponse(type(exception).__name__ + ": " + str(exception))
214 elif isinstance(exception, (neExceptions.NetworkNotFoundClient, nvExceptions.NotFound)):
215 raise vimconn.vimconnNotFoundException(type(exception).__name__ + ": " + str(exception))
216 elif isinstance(exception, nvExceptions.Conflict):
217 raise vimconn.vimconnConflictException(type(exception).__name__ + ": " + str(exception))
218 else: # ()
219 raise vimconn.vimconnConnectionException(type(exception).__name__ + ": " + str(exception))
220
221 def get_tenant_list(self, filter_dict={}):
222 '''Obtain tenants of VIM
223 filter_dict can contain the following keys:
224 name: filter by tenant name
225 id: filter by tenant uuid/id
226 <other VIM specific>
227 Returns the tenant list of dictionaries: [{'name':'<name>, 'id':'<id>, ...}, ...]
228 '''
ahmadsa95baa272016-11-30 09:14:11 +0500229 self.logger.debug("Getting tenants from VIM filter: '%s'", str(filter_dict))
tiernoae4a8d12016-07-08 12:30:39 +0200230 try:
231 self._reload_connection()
ahmadsa95baa272016-11-30 09:14:11 +0500232 if osc_api_version == 'v3.3':
233 project_class_list=self.keystone.projects.findall(**filter_dict)
234 else:
235 project_class_list=self.keystone.tenants.findall(**filter_dict)
236 project_list=[]
237 for project in project_class_list:
238 project_list.append(project.to_dict())
239 return project_list
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)
242
243 def new_tenant(self, tenant_name, tenant_description):
244 '''Adds a new tenant to openstack VIM. Returns the tenant identifier'''
245 self.logger.debug("Adding a new tenant name: %s", tenant_name)
246 try:
247 self._reload_connection()
ahmadsa95baa272016-11-30 09:14:11 +0500248 if self.osc_api_version == 'v3.3':
249 project=self.keystone.projects.create(tenant_name, tenant_description)
250 else:
251 project=self.keystone.tenants.create(tenant_name, tenant_description)
252 return project.id
tierno8e995ce2016-09-22 08:13:00 +0000253 except (ksExceptions.ConnectionError, ksExceptions.ClientException, ConnectionError) as e:
tiernoae4a8d12016-07-08 12:30:39 +0200254 self._format_exception(e)
255
256 def delete_tenant(self, tenant_id):
257 '''Delete a tenant from openstack VIM. Returns the old tenant identifier'''
258 self.logger.debug("Deleting tenant %s from VIM", tenant_id)
259 try:
260 self._reload_connection()
ahmadsa95baa272016-11-30 09:14:11 +0500261 if osc_api_version == 'v3.3':
262 self.keystone.projects.delete(tenant_id)
263 else:
264 self.keystone.tenants.delete(tenant_id)
tiernoae4a8d12016-07-08 12:30:39 +0200265 return tenant_id
tierno8e995ce2016-09-22 08:13:00 +0000266 except (ksExceptions.ConnectionError, ksExceptions.ClientException, ConnectionError) as e:
tiernoae4a8d12016-07-08 12:30:39 +0200267 self._format_exception(e)
ahmadsa95baa272016-11-30 09:14:11 +0500268
garciadeblas9f8456e2016-09-05 05:02:59 +0200269 def new_network(self,net_name, net_type, ip_profile=None, shared=False, vlan=None):
tiernoae4a8d12016-07-08 12:30:39 +0200270 '''Adds a tenant network to VIM. Returns the network identifier'''
271 self.logger.debug("Adding a new network to VIM name '%s', type '%s'", net_name, net_type)
garciadeblasedca7b32016-09-29 14:01:52 +0000272 #self.logger.debug(">>>>>>>>>>>>>>>>>> IP profile %s", str(ip_profile))
tierno7edb6752016-03-21 17:37:52 +0100273 try:
garciadeblasedca7b32016-09-29 14:01:52 +0000274 new_net = None
tierno7edb6752016-03-21 17:37:52 +0100275 self._reload_connection()
276 network_dict = {'name': net_name, 'admin_state_up': True}
277 if net_type=="data" or net_type=="ptp":
278 if self.config.get('dataplane_physical_net') == None:
tiernoae4a8d12016-07-08 12:30:39 +0200279 raise vimconn.vimconnConflictException("You must provide a 'dataplane_physical_net' at config value before creating sriov network")
tierno7edb6752016-03-21 17:37:52 +0100280 network_dict["provider:physical_network"] = self.config['dataplane_physical_net'] #"physnet_sriov" #TODO physical
281 network_dict["provider:network_type"] = "vlan"
282 if vlan!=None:
283 network_dict["provider:network_type"] = vlan
tiernoae4a8d12016-07-08 12:30:39 +0200284 network_dict["shared"]=shared
tierno7edb6752016-03-21 17:37:52 +0100285 new_net=self.neutron.create_network({'network':network_dict})
286 #print new_net
garciadeblas9f8456e2016-09-05 05:02:59 +0200287 #create subnetwork, even if there is no profile
288 if not ip_profile:
289 ip_profile = {}
290 if 'subnet_address' not in ip_profile:
291 #Fake subnet is required
292 ip_profile['subnet_address'] = "192.168.111.0/24"
293 if 'ip_version' not in ip_profile:
294 ip_profile['ip_version'] = "IPv4"
tierno7edb6752016-03-21 17:37:52 +0100295 subnet={"name":net_name+"-subnet",
296 "network_id": new_net["network"]["id"],
garciadeblas9f8456e2016-09-05 05:02:59 +0200297 "ip_version": 4 if ip_profile['ip_version']=="IPv4" else 6,
298 "cidr": ip_profile['subnet_address']
tierno7edb6752016-03-21 17:37:52 +0100299 }
garciadeblas9f8456e2016-09-05 05:02:59 +0200300 if 'gateway_address' in ip_profile:
301 subnet['gateway_ip'] = ip_profile['gateway_address']
garciadeblasedca7b32016-09-29 14:01:52 +0000302 if ip_profile.get('dns_address'):
garciadeblas9f8456e2016-09-05 05:02:59 +0200303 #TODO: manage dns_address as a list of addresses separated by commas
304 subnet['dns_nameservers'] = []
305 subnet['dns_nameservers'].append(ip_profile['dns_address'])
306 if 'dhcp_enabled' in ip_profile:
307 subnet['enable_dhcp'] = False if ip_profile['dhcp_enabled']=="false" else True
308 if 'dhcp_start_address' in ip_profile:
309 subnet['allocation_pools']=[]
310 subnet['allocation_pools'].append(dict())
311 subnet['allocation_pools'][0]['start'] = ip_profile['dhcp_start_address']
312 if 'dhcp_count' in ip_profile:
313 #parts = ip_profile['dhcp_start_address'].split('.')
314 #ip_int = (int(parts[0]) << 24) + (int(parts[1]) << 16) + (int(parts[2]) << 8) + int(parts[3])
315 ip_int = int(netaddr.IPAddress(ip_profile['dhcp_start_address']))
garciadeblas21d795b2016-09-29 17:31:46 +0200316 ip_int += ip_profile['dhcp_count'] - 1
garciadeblas9f8456e2016-09-05 05:02:59 +0200317 ip_str = str(netaddr.IPAddress(ip_int))
318 subnet['allocation_pools'][0]['end'] = ip_str
garciadeblasedca7b32016-09-29 14:01:52 +0000319 #self.logger.debug(">>>>>>>>>>>>>>>>>> Subnet: %s", str(subnet))
tierno7edb6752016-03-21 17:37:52 +0100320 self.neutron.create_subnet({"subnet": subnet} )
tiernoae4a8d12016-07-08 12:30:39 +0200321 return new_net["network"]["id"]
tierno8e995ce2016-09-22 08:13:00 +0000322 except (neExceptions.ConnectionFailed, ksExceptions.ClientException, neExceptions.NeutronException, ConnectionError) as e:
garciadeblasedca7b32016-09-29 14:01:52 +0000323 if new_net:
324 self.neutron.delete_network(new_net['network']['id'])
tiernoae4a8d12016-07-08 12:30:39 +0200325 self._format_exception(e)
tierno7edb6752016-03-21 17:37:52 +0100326
327 def get_network_list(self, filter_dict={}):
328 '''Obtain tenant networks of VIM
329 Filter_dict can be:
330 name: network name
331 id: network uuid
332 shared: boolean
333 tenant_id: tenant
334 admin_state_up: boolean
335 status: 'ACTIVE'
336 Returns the network list of dictionaries
337 '''
tiernoae4a8d12016-07-08 12:30:39 +0200338 self.logger.debug("Getting network from VIM filter: '%s'", str(filter_dict))
tierno7edb6752016-03-21 17:37:52 +0100339 try:
340 self._reload_connection()
ahmadsa95baa272016-11-30 09:14:11 +0500341 if osc_api_version == 'v3.3' and "tenant_id" in filter_dict:
342 filter_dict['project_id'] = filter_dict.pop('tenant_id')
tierno7edb6752016-03-21 17:37:52 +0100343 net_dict=self.neutron.list_networks(**filter_dict)
344 net_list=net_dict["networks"]
345 self.__net_os2mano(net_list)
tiernoae4a8d12016-07-08 12:30:39 +0200346 return net_list
tierno8e995ce2016-09-22 08:13:00 +0000347 except (neExceptions.ConnectionFailed, ksExceptions.ClientException, neExceptions.NeutronException, ConnectionError) as e:
tiernoae4a8d12016-07-08 12:30:39 +0200348 self._format_exception(e)
tierno7edb6752016-03-21 17:37:52 +0100349
tiernoae4a8d12016-07-08 12:30:39 +0200350 def get_network(self, net_id):
351 '''Obtain details of network from VIM
352 Returns the network information from a network id'''
353 self.logger.debug(" Getting tenant network %s from VIM", net_id)
tierno7edb6752016-03-21 17:37:52 +0100354 filter_dict={"id": net_id}
tiernoae4a8d12016-07-08 12:30:39 +0200355 net_list = self.get_network_list(filter_dict)
tierno7edb6752016-03-21 17:37:52 +0100356 if len(net_list)==0:
tiernoae4a8d12016-07-08 12:30:39 +0200357 raise vimconn.vimconnNotFoundException("Network '{}' not found".format(net_id))
tierno7edb6752016-03-21 17:37:52 +0100358 elif len(net_list)>1:
tiernoae4a8d12016-07-08 12:30:39 +0200359 raise vimconn.vimconnConflictException("Found more than one network with this criteria")
tierno7edb6752016-03-21 17:37:52 +0100360 net = net_list[0]
361 subnets=[]
362 for subnet_id in net.get("subnets", () ):
363 try:
364 subnet = self.neutron.show_subnet(subnet_id)
365 except Exception as e:
tiernoae4a8d12016-07-08 12:30:39 +0200366 self.logger.error("osconnector.get_network(): Error getting subnet %s %s" % (net_id, str(e)))
367 subnet = {"id": subnet_id, "fault": str(e)}
tierno7edb6752016-03-21 17:37:52 +0100368 subnets.append(subnet)
369 net["subnets"] = subnets
tiernoae4a8d12016-07-08 12:30:39 +0200370 return net
tierno7edb6752016-03-21 17:37:52 +0100371
tiernoae4a8d12016-07-08 12:30:39 +0200372 def delete_network(self, net_id):
373 '''Deletes a tenant network from VIM. Returns the old network identifier'''
374 self.logger.debug("Deleting network '%s' from VIM", net_id)
tierno7edb6752016-03-21 17:37:52 +0100375 try:
376 self._reload_connection()
377 #delete VM ports attached to this networks before the network
378 ports = self.neutron.list_ports(network_id=net_id)
379 for p in ports['ports']:
380 try:
381 self.neutron.delete_port(p["id"])
382 except Exception as e:
tiernoae4a8d12016-07-08 12:30:39 +0200383 self.logger.error("Error deleting port %s: %s", p["id"], str(e))
tierno7edb6752016-03-21 17:37:52 +0100384 self.neutron.delete_network(net_id)
tiernoae4a8d12016-07-08 12:30:39 +0200385 return net_id
386 except (neExceptions.ConnectionFailed, neExceptions.NetworkNotFoundClient, neExceptions.NeutronException,
tierno8e995ce2016-09-22 08:13:00 +0000387 ksExceptions.ClientException, neExceptions.NeutronException, ConnectionError) as e:
tiernoae4a8d12016-07-08 12:30:39 +0200388 self._format_exception(e)
tierno7edb6752016-03-21 17:37:52 +0100389
tiernoae4a8d12016-07-08 12:30:39 +0200390 def refresh_nets_status(self, net_list):
391 '''Get the status of the networks
392 Params: the list of network identifiers
393 Returns a dictionary with:
394 net_id: #VIM id of this network
395 status: #Mandatory. Text with one of:
396 # DELETED (not found at vim)
397 # VIM_ERROR (Cannot connect to VIM, VIM response error, ...)
398 # OTHER (Vim reported other status not understood)
399 # ERROR (VIM indicates an ERROR status)
400 # ACTIVE, INACTIVE, DOWN (admin down),
401 # BUILD (on building process)
402 #
403 error_msg: #Text with VIM error message, if any. Or the VIM connection ERROR
404 vim_info: #Text with plain information obtained from vim (yaml.safe_dump)
405
406 '''
407 net_dict={}
408 for net_id in net_list:
409 net = {}
410 try:
411 net_vim = self.get_network(net_id)
412 if net_vim['status'] in netStatus2manoFormat:
413 net["status"] = netStatus2manoFormat[ net_vim['status'] ]
414 else:
415 net["status"] = "OTHER"
416 net["error_msg"] = "VIM status reported " + net_vim['status']
417
tierno8e995ce2016-09-22 08:13:00 +0000418 if net['status'] == "ACTIVE" and not net_vim['admin_state_up']:
tiernoae4a8d12016-07-08 12:30:39 +0200419 net['status'] = 'DOWN'
tierno8e995ce2016-09-22 08:13:00 +0000420 try:
421 net['vim_info'] = yaml.safe_dump(net_vim, default_flow_style=True, width=256)
422 except yaml.representer.RepresenterError:
423 net['vim_info'] = str(net_vim)
tiernoae4a8d12016-07-08 12:30:39 +0200424 if net_vim.get('fault'): #TODO
425 net['error_msg'] = str(net_vim['fault'])
426 except vimconn.vimconnNotFoundException as e:
427 self.logger.error("Exception getting net status: %s", str(e))
428 net['status'] = "DELETED"
429 net['error_msg'] = str(e)
430 except vimconn.vimconnException as e:
431 self.logger.error("Exception getting net status: %s", str(e))
432 net['status'] = "VIM_ERROR"
433 net['error_msg'] = str(e)
434 net_dict[net_id] = net
435 return net_dict
436
437 def get_flavor(self, flavor_id):
438 '''Obtain flavor details from the VIM. Returns the flavor dict details'''
439 self.logger.debug("Getting flavor '%s'", flavor_id)
tierno7edb6752016-03-21 17:37:52 +0100440 try:
441 self._reload_connection()
442 flavor = self.nova.flavors.find(id=flavor_id)
443 #TODO parse input and translate to VIM format (openmano_schemas.new_vminstance_response_schema)
tiernoae4a8d12016-07-08 12:30:39 +0200444 return flavor.to_dict()
tierno8e995ce2016-09-22 08:13:00 +0000445 except (nvExceptions.NotFound, nvExceptions.ClientException, ksExceptions.ClientException, ConnectionError) as e:
tiernoae4a8d12016-07-08 12:30:39 +0200446 self._format_exception(e)
tierno7edb6752016-03-21 17:37:52 +0100447
tiernoae4a8d12016-07-08 12:30:39 +0200448 def new_flavor(self, flavor_data, change_name_if_used=True):
tierno7edb6752016-03-21 17:37:52 +0100449 '''Adds a tenant flavor to openstack VIM
tiernoae4a8d12016-07-08 12:30:39 +0200450 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 +0100451 Returns the flavor identifier
452 '''
tiernoae4a8d12016-07-08 12:30:39 +0200453 self.logger.debug("Adding flavor '%s'", str(flavor_data))
tierno7edb6752016-03-21 17:37:52 +0100454 retry=0
tiernoae4a8d12016-07-08 12:30:39 +0200455 max_retries=3
tierno7edb6752016-03-21 17:37:52 +0100456 name_suffix = 0
tiernoae4a8d12016-07-08 12:30:39 +0200457 name=flavor_data['name']
458 while retry<max_retries:
tierno7edb6752016-03-21 17:37:52 +0100459 retry+=1
460 try:
461 self._reload_connection()
462 if change_name_if_used:
463 #get used names
464 fl_names=[]
465 fl=self.nova.flavors.list()
466 for f in fl:
467 fl_names.append(f.name)
468 while name in fl_names:
469 name_suffix += 1
tiernoae4a8d12016-07-08 12:30:39 +0200470 name = flavor_data['name']+"-" + str(name_suffix)
tierno7edb6752016-03-21 17:37:52 +0100471
tiernoae4a8d12016-07-08 12:30:39 +0200472 ram = flavor_data.get('ram',64)
473 vcpus = flavor_data.get('vcpus',1)
tierno7edb6752016-03-21 17:37:52 +0100474 numa_properties=None
475
tiernoae4a8d12016-07-08 12:30:39 +0200476 extended = flavor_data.get("extended")
tierno7edb6752016-03-21 17:37:52 +0100477 if extended:
478 numas=extended.get("numas")
479 if numas:
480 numa_nodes = len(numas)
481 if numa_nodes > 1:
482 return -1, "Can not add flavor with more than one numa"
483 numa_properties = {"hw:numa_nodes":str(numa_nodes)}
484 numa_properties["hw:mem_page_size"] = "large"
485 numa_properties["hw:cpu_policy"] = "dedicated"
486 numa_properties["hw:numa_mempolicy"] = "strict"
487 for numa in numas:
488 #overwrite ram and vcpus
489 ram = numa['memory']*1024
490 if 'paired-threads' in numa:
491 vcpus = numa['paired-threads']*2
492 numa_properties["hw:cpu_threads_policy"] = "prefer"
493 elif 'cores' in numa:
494 vcpus = numa['cores']
495 #numa_properties["hw:cpu_threads_policy"] = "prefer"
496 elif 'threads' in numa:
497 vcpus = numa['threads']
498 numa_properties["hw:cpu_policy"] = "isolated"
499 for interface in numa.get("interfaces",() ):
500 if interface["dedicated"]=="yes":
tierno809a7802016-07-08 13:31:24 +0200501 raise vimconn.vimconnException("Passthrough interfaces are not supported for the openstack connector", http_code=vimconn.HTTP_Service_Unavailable)
tierno7edb6752016-03-21 17:37:52 +0100502 #TODO, add the key 'pci_passthrough:alias"="<label at config>:<number ifaces>"' when a way to connect it is available
503
504 #create flavor
505 new_flavor=self.nova.flavors.create(name,
506 ram,
507 vcpus,
tiernoae4a8d12016-07-08 12:30:39 +0200508 flavor_data.get('disk',1),
509 is_public=flavor_data.get('is_public', True)
tierno7edb6752016-03-21 17:37:52 +0100510 )
511 #add metadata
512 if numa_properties:
513 new_flavor.set_keys(numa_properties)
tiernoae4a8d12016-07-08 12:30:39 +0200514 return new_flavor.id
tierno7edb6752016-03-21 17:37:52 +0100515 except nvExceptions.Conflict as e:
tiernoae4a8d12016-07-08 12:30:39 +0200516 if change_name_if_used and retry < max_retries:
tierno7edb6752016-03-21 17:37:52 +0100517 continue
tiernoae4a8d12016-07-08 12:30:39 +0200518 self._format_exception(e)
tierno7edb6752016-03-21 17:37:52 +0100519 #except nvExceptions.BadRequest as e:
520 except (ksExceptions.ClientException, nvExceptions.ClientException, ConnectionError) as e:
tiernoae4a8d12016-07-08 12:30:39 +0200521 self._format_exception(e)
tierno7edb6752016-03-21 17:37:52 +0100522
tiernoae4a8d12016-07-08 12:30:39 +0200523 def delete_flavor(self,flavor_id):
524 '''Deletes a tenant flavor from openstack VIM. Returns the old flavor_id
tierno7edb6752016-03-21 17:37:52 +0100525 '''
tiernoae4a8d12016-07-08 12:30:39 +0200526 try:
527 self._reload_connection()
528 self.nova.flavors.delete(flavor_id)
529 return flavor_id
530 #except nvExceptions.BadRequest as e:
tierno8e995ce2016-09-22 08:13:00 +0000531 except (nvExceptions.NotFound, ksExceptions.ClientException, nvExceptions.ClientException, ConnectionError) as e:
tiernoae4a8d12016-07-08 12:30:39 +0200532 self._format_exception(e)
tierno7edb6752016-03-21 17:37:52 +0100533
tiernoae4a8d12016-07-08 12:30:39 +0200534 def new_image(self,image_dict):
tierno7edb6752016-03-21 17:37:52 +0100535 '''
tiernoae4a8d12016-07-08 12:30:39 +0200536 Adds a tenant image to VIM. imge_dict is a dictionary with:
537 name: name
538 disk_format: qcow2, vhd, vmdk, raw (by default), ...
539 location: path or URI
540 public: "yes" or "no"
541 metadata: metadata of the image
542 Returns the image_id
tierno7edb6752016-03-21 17:37:52 +0100543 '''
tierno7edb6752016-03-21 17:37:52 +0100544 #using version 1 of glance client
545 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 +0200546 retry=0
547 max_retries=3
548 while retry<max_retries:
tierno7edb6752016-03-21 17:37:52 +0100549 retry+=1
550 try:
551 self._reload_connection()
552 #determine format http://docs.openstack.org/developer/glance/formats.html
553 if "disk_format" in image_dict:
554 disk_format=image_dict["disk_format"]
555 else: #autodiscover base on extention
556 if image_dict['location'][-6:]==".qcow2":
557 disk_format="qcow2"
558 elif image_dict['location'][-4:]==".vhd":
559 disk_format="vhd"
560 elif image_dict['location'][-5:]==".vmdk":
561 disk_format="vmdk"
562 elif image_dict['location'][-4:]==".vdi":
563 disk_format="vdi"
564 elif image_dict['location'][-4:]==".iso":
565 disk_format="iso"
566 elif image_dict['location'][-4:]==".aki":
567 disk_format="aki"
568 elif image_dict['location'][-4:]==".ari":
569 disk_format="ari"
570 elif image_dict['location'][-4:]==".ami":
571 disk_format="ami"
572 else:
573 disk_format="raw"
tiernoae4a8d12016-07-08 12:30:39 +0200574 self.logger.debug("new_image: '%s' loading from '%s'", image_dict['name'], image_dict['location'])
tierno7edb6752016-03-21 17:37:52 +0100575 if image_dict['location'][0:4]=="http":
576 new_image = glancev1.images.create(name=image_dict['name'], is_public=image_dict.get('public',"yes")=="yes",
577 container_format="bare", location=image_dict['location'], disk_format=disk_format)
578 else: #local path
579 with open(image_dict['location']) as fimage:
580 new_image = glancev1.images.create(name=image_dict['name'], is_public=image_dict.get('public',"yes")=="yes",
581 container_format="bare", data=fimage, disk_format=disk_format)
582 #insert metadata. We cannot use 'new_image.properties.setdefault'
583 #because nova and glance are "INDEPENDENT" and we are using nova for reading metadata
584 new_image_nova=self.nova.images.find(id=new_image.id)
585 new_image_nova.metadata.setdefault('location',image_dict['location'])
586 metadata_to_load = image_dict.get('metadata')
587 if metadata_to_load:
588 for k,v in yaml.load(metadata_to_load).iteritems():
589 new_image_nova.metadata.setdefault(k,v)
tiernoae4a8d12016-07-08 12:30:39 +0200590 return new_image.id
591 except (nvExceptions.Conflict, ksExceptions.ClientException, nvExceptions.ClientException) as e:
592 self._format_exception(e)
tierno8e995ce2016-09-22 08:13:00 +0000593 except (HTTPException, gl1Exceptions.HTTPException, gl1Exceptions.CommunicationError, ConnectionError) as e:
tiernoae4a8d12016-07-08 12:30:39 +0200594 if retry==max_retries:
595 continue
596 self._format_exception(e)
tierno7edb6752016-03-21 17:37:52 +0100597 except IOError as e: #can not open the file
tiernoae4a8d12016-07-08 12:30:39 +0200598 raise vimconn.vimconnConnectionException(type(e).__name__ + ": " + str(e)+ " for " + image_dict['location'],
599 http_code=vimconn.HTTP_Bad_Request)
tierno7edb6752016-03-21 17:37:52 +0100600
tiernoae4a8d12016-07-08 12:30:39 +0200601 def delete_image(self, image_id):
602 '''Deletes a tenant image from openstack VIM. Returns the old id
tierno7edb6752016-03-21 17:37:52 +0100603 '''
tiernoae4a8d12016-07-08 12:30:39 +0200604 try:
605 self._reload_connection()
606 self.nova.images.delete(image_id)
607 return image_id
tierno8e995ce2016-09-22 08:13:00 +0000608 except (nvExceptions.NotFound, ksExceptions.ClientException, nvExceptions.ClientException, gl1Exceptions.CommunicationError, ConnectionError) as e: #TODO remove
tiernoae4a8d12016-07-08 12:30:39 +0200609 self._format_exception(e)
610
611 def get_image_id_from_path(self, path):
garciadeblasb69fa9f2016-09-28 12:04:10 +0200612 '''Get the image id from image path in the VIM database. Returns the image_id'''
tiernoae4a8d12016-07-08 12:30:39 +0200613 try:
614 self._reload_connection()
615 images = self.nova.images.list()
616 for image in images:
617 if image.metadata.get("location")==path:
618 return image.id
619 raise vimconn.vimconnNotFoundException("image with location '{}' not found".format( path))
tierno8e995ce2016-09-22 08:13:00 +0000620 except (ksExceptions.ClientException, nvExceptions.ClientException, gl1Exceptions.CommunicationError, ConnectionError) as e:
tiernoae4a8d12016-07-08 12:30:39 +0200621 self._format_exception(e)
tierno7edb6752016-03-21 17:37:52 +0100622
garciadeblasb69fa9f2016-09-28 12:04:10 +0200623 def get_image_list(self, filter_dict={}):
624 '''Obtain tenant images from VIM
625 Filter_dict can be:
626 id: image id
627 name: image name
628 checksum: image checksum
629 Returns the image list of dictionaries:
630 [{<the fields at Filter_dict plus some VIM specific>}, ...]
631 List can be empty
632 '''
633 self.logger.debug("Getting image list from VIM filter: '%s'", str(filter_dict))
634 try:
635 self._reload_connection()
636 filter_dict_os=filter_dict.copy()
637 #First we filter by the available filter fields: name, id. The others are removed.
638 filter_dict_os.pop('checksum',None)
639 image_list=self.nova.images.findall(**filter_dict_os)
640 if len(image_list)==0:
641 return []
642 #Then we filter by the rest of filter fields: checksum
643 filtered_list = []
644 for image in image_list:
garciadeblasbb6a1ed2016-09-30 14:02:09 +0000645 image_dict=self.glance.images.get(image.id)
garciadeblasb69fa9f2016-09-28 12:04:10 +0200646 if image_dict['checksum']==filter_dict.get('checksum'):
647 filtered_list.append(image)
648 return filtered_list
649 except (ksExceptions.ClientException, nvExceptions.ClientException, gl1Exceptions.CommunicationError, ConnectionError) as e:
650 self._format_exception(e)
651
tiernoa4e1a6e2016-08-31 14:19:40 +0200652 def new_vminstance(self,name,description,start,image_id,flavor_id,net_list,cloud_config=None):
tierno7edb6752016-03-21 17:37:52 +0100653 '''Adds a VM instance to VIM
654 Params:
655 start: indicates if VM must start or boot in pause mode. Ignored
656 image_id,flavor_id: iamge and flavor uuid
657 net_list: list of interfaces, each one is a dictionary with:
658 name:
659 net_id: network uuid to connect
660 vpci: virtual vcpi to assign, ignored because openstack lack #TODO
661 model: interface model, ignored #TODO
662 mac_address: used for SR-IOV ifaces #TODO for other types
663 use: 'data', 'bridge', 'mgmt'
664 type: 'virtual', 'PF', 'VF', 'VFnotShared'
665 vim_id: filled/added by this function
ahmadsaf853d452016-12-22 11:33:47 +0500666 floating_ip: True/False (or it can be None)
tierno7edb6752016-03-21 17:37:52 +0100667 #TODO ip, security groups
tiernoae4a8d12016-07-08 12:30:39 +0200668 Returns the instance identifier
tierno7edb6752016-03-21 17:37:52 +0100669 '''
tiernoae4a8d12016-07-08 12:30:39 +0200670 self.logger.debug("Creating VM image '%s' flavor '%s' nics='%s'",image_id, flavor_id,str(net_list))
tierno7edb6752016-03-21 17:37:52 +0100671 try:
tierno6e116232016-07-18 13:01:40 +0200672 metadata={}
tierno7edb6752016-03-21 17:37:52 +0100673 net_list_vim=[]
ahmadsaf853d452016-12-22 11:33:47 +0500674 external_network=[] #list of external networks to be connected to instance, later on used to create floating_ip
tierno7edb6752016-03-21 17:37:52 +0100675 self._reload_connection()
tiernoae4a8d12016-07-08 12:30:39 +0200676 metadata_vpci={} #For a specific neutron plugin
tierno7edb6752016-03-21 17:37:52 +0100677 for net in net_list:
678 if not net.get("net_id"): #skip non connected iface
679 continue
ahmadsaf853d452016-12-22 11:33:47 +0500680 if net["type"]=="virtual" or net["type"]=="VF":
tierno7edb6752016-03-21 17:37:52 +0100681 port_dict={
ahmadsaf853d452016-12-22 11:33:47 +0500682 "network_id": net["net_id"],
683 "name": net.get("name"),
684 "admin_state_up": True
685 }
686 if net["type"]=="virtual":
687 if "vpci" in net:
688 metadata_vpci[ net["net_id"] ] = [[ net["vpci"], "" ]]
689 else: # for VF
690 if "vpci" in net:
691 if "VF" not in metadata_vpci:
692 metadata_vpci["VF"]=[]
693 metadata_vpci["VF"].append([ net["vpci"], "" ])
694 port_dict["binding:vnic_type"]="direct"
tierno7edb6752016-03-21 17:37:52 +0100695 if not port_dict["name"]:
ahmadsaf853d452016-12-22 11:33:47 +0500696 port_dict["name"]=name
tierno7edb6752016-03-21 17:37:52 +0100697 if net.get("mac_address"):
698 port_dict["mac_address"]=net["mac_address"]
tierno7edb6752016-03-21 17:37:52 +0100699 new_port = self.neutron.create_port({"port": port_dict })
700 net["mac_adress"] = new_port["port"]["mac_address"]
701 net["vim_id"] = new_port["port"]["id"]
ahmadsaf853d452016-12-22 11:33:47 +0500702 net["ip"] = new_port["port"].get("fixed_ips", [{}])[0].get("ip_address")
tierno7edb6752016-03-21 17:37:52 +0100703 net_list_vim.append({"port-id": new_port["port"]["id"]})
ahmadsaf853d452016-12-22 11:33:47 +0500704 else: # for PF
705 self.logger.warn("new_vminstance: Warning, can not connect a passthrough interface ")
706 #TODO insert this when openstack consider passthrough ports as openstack neutron ports
707 if net.get('floating_ip', False):
708 external_network.append(net)
709
tierno7edb6752016-03-21 17:37:52 +0100710 if metadata_vpci:
711 metadata = {"pci_assignement": json.dumps(metadata_vpci)}
tiernoafbced42016-07-23 01:43:53 +0200712 if len(metadata["pci_assignement"]) >255:
tierno6e116232016-07-18 13:01:40 +0200713 #limit the metadata size
714 #metadata["pci_assignement"] = metadata["pci_assignement"][0:255]
715 self.logger.warn("Metadata deleted since it exceeds the expected length (255) ")
716 metadata = {}
tierno7edb6752016-03-21 17:37:52 +0100717
tiernoae4a8d12016-07-08 12:30:39 +0200718 self.logger.debug("name '%s' image_id '%s'flavor_id '%s' net_list_vim '%s' description '%s' metadata %s",
719 name, image_id, flavor_id, str(net_list_vim), description, str(metadata))
tierno7edb6752016-03-21 17:37:52 +0100720
721 security_groups = self.config.get('security_groups')
722 if type(security_groups) is str:
723 security_groups = ( security_groups, )
tiernoa4e1a6e2016-08-31 14:19:40 +0200724 if isinstance(cloud_config, dict):
725 userdata="#cloud-config\nusers:\n"
726 #default user
727 if "key-pairs" in cloud_config:
728 userdata += " - default:\n ssh-authorized-keys:\n"
729 for key in cloud_config["key-pairs"]:
730 userdata += " - '{key}'\n".format(key=key)
731 for user in cloud_config.get("users",[]):
732 userdata += " - name: {name}\n sudo: ALL=(ALL) NOPASSWD:ALL\n".format(name=user["name"])
733 if "user-info" in user:
734 userdata += " gecos: {}'\n".format(user["user-info"])
735 if user.get("key-pairs"):
736 userdata += " ssh-authorized-keys:\n"
737 for key in user["key-pairs"]:
738 userdata += " - '{key}'\n".format(key=key)
739 self.logger.debug("userdata: %s", userdata)
740 elif isinstance(cloud_config, str):
741 userdata = cloud_config
742 else:
743 userdata=None
744
tierno7edb6752016-03-21 17:37:52 +0100745 server = self.nova.servers.create(name, image_id, flavor_id, nics=net_list_vim, meta=metadata,
746 security_groups = security_groups,
747 availability_zone = self.config.get('availability_zone'),
748 key_name = self.config.get('keypair'),
tiernoa4e1a6e2016-08-31 14:19:40 +0200749 userdata=userdata
tierno7edb6752016-03-21 17:37:52 +0100750 ) #, description=description)
751
752
tiernoae4a8d12016-07-08 12:30:39 +0200753 #print "DONE :-)", server
tierno7edb6752016-03-21 17:37:52 +0100754
ahmadsaf853d452016-12-22 11:33:47 +0500755 pool_id = None
756 floating_ips = self.neutron.list_floatingips().get("floatingips", ())
757 for floating_network in external_network:
758 assigned = False
759 while(assigned == False):
760 if floating_ips:
761 ip = floating_ips.pop(0)
762 if not ip.get("port_id", False):
763 free_floating_ip = ip.get("floating_ip_address")
764 try:
765 fix_ip = floating_network.get('ip')
766 server.add_floating_ip(free_floating_ip, fix_ip)
767 assigned = True
768 except Exception as e:
769 self.delete_vminstance(server.id)
770 raise vimconn.vimconnException(type(e).__name__ + ": Cannot create floating_ip "+ str(e), http_code=vimconn.HTTP_Conflict)
771 else:
772 pool_id = floating_network.get('net_id')
773 param = {'floatingip': {'floating_network_id': pool_id}}
774 try:
775 #self.logger.debug("Creating floating IP")
776 new_floating_ip = self.neutron.create_floatingip(param)
777 free_floating_ip = new_floating_ip['floatingip']['floating_ip_address']
778 fix_ip = floating_network.get('ip')
779 server.add_floating_ip(free_floating_ip, fix_ip)
780 assigned=True
781 except Exception as e:
782 self.delete_vminstance(server.id)
783 raise vimconn.vimconnException(type(e).__name__ + ": Cannot create floating_ip "+ str(e), http_code=vimconn.HTTP_Conflict)
tierno7edb6752016-03-21 17:37:52 +0100784
tiernoae4a8d12016-07-08 12:30:39 +0200785 return server.id
tierno7edb6752016-03-21 17:37:52 +0100786# except nvExceptions.NotFound as e:
787# error_value=-vimconn.HTTP_Not_Found
788# error_text= "vm instance %s not found" % vm_id
tierno8e995ce2016-09-22 08:13:00 +0000789 except (ksExceptions.ClientException, nvExceptions.ClientException, ConnectionError
790 ) as e:
tiernoae4a8d12016-07-08 12:30:39 +0200791 self._format_exception(e)
792 except TypeError as e:
793 raise vimconn.vimconnException(type(e).__name__ + ": "+ str(e), http_code=vimconn.HTTP_Bad_Request)
tierno7edb6752016-03-21 17:37:52 +0100794
tiernoae4a8d12016-07-08 12:30:39 +0200795 def get_vminstance(self,vm_id):
tierno7edb6752016-03-21 17:37:52 +0100796 '''Returns the VM instance information from VIM'''
tiernoae4a8d12016-07-08 12:30:39 +0200797 #self.logger.debug("Getting VM from VIM")
tierno7edb6752016-03-21 17:37:52 +0100798 try:
799 self._reload_connection()
800 server = self.nova.servers.find(id=vm_id)
801 #TODO parse input and translate to VIM format (openmano_schemas.new_vminstance_response_schema)
tiernoae4a8d12016-07-08 12:30:39 +0200802 return server.to_dict()
tierno8e995ce2016-09-22 08:13:00 +0000803 except (ksExceptions.ClientException, nvExceptions.ClientException, nvExceptions.NotFound, ConnectionError) as e:
tiernoae4a8d12016-07-08 12:30:39 +0200804 self._format_exception(e)
805
806 def get_vminstance_console(self,vm_id, console_type="vnc"):
tierno7edb6752016-03-21 17:37:52 +0100807 '''
808 Get a console for the virtual machine
809 Params:
810 vm_id: uuid of the VM
811 console_type, can be:
812 "novnc" (by default), "xvpvnc" for VNC types,
813 "rdp-html5" for RDP types, "spice-html5" for SPICE types
tiernoae4a8d12016-07-08 12:30:39 +0200814 Returns dict with the console parameters:
815 protocol: ssh, ftp, http, https, ...
816 server: usually ip address
817 port: the http, ssh, ... port
818 suffix: extra text, e.g. the http path and query string
tierno7edb6752016-03-21 17:37:52 +0100819 '''
tiernoae4a8d12016-07-08 12:30:39 +0200820 self.logger.debug("Getting VM CONSOLE from VIM")
tierno7edb6752016-03-21 17:37:52 +0100821 try:
822 self._reload_connection()
823 server = self.nova.servers.find(id=vm_id)
824 if console_type == None or console_type == "novnc":
825 console_dict = server.get_vnc_console("novnc")
826 elif console_type == "xvpvnc":
827 console_dict = server.get_vnc_console(console_type)
828 elif console_type == "rdp-html5":
829 console_dict = server.get_rdp_console(console_type)
830 elif console_type == "spice-html5":
831 console_dict = server.get_spice_console(console_type)
832 else:
tiernoae4a8d12016-07-08 12:30:39 +0200833 raise vimconn.vimconnException("console type '{}' not allowed".format(console_type), http_code=vimconn.HTTP_Bad_Request)
tierno7edb6752016-03-21 17:37:52 +0100834
835 console_dict1 = console_dict.get("console")
836 if console_dict1:
837 console_url = console_dict1.get("url")
838 if console_url:
839 #parse console_url
840 protocol_index = console_url.find("//")
841 suffix_index = console_url[protocol_index+2:].find("/") + protocol_index+2
842 port_index = console_url[protocol_index+2:suffix_index].find(":") + protocol_index+2
843 if protocol_index < 0 or port_index<0 or suffix_index<0:
844 return -vimconn.HTTP_Internal_Server_Error, "Unexpected response from VIM"
845 console_dict={"protocol": console_url[0:protocol_index],
846 "server": console_url[protocol_index+2:port_index],
847 "port": console_url[port_index:suffix_index],
848 "suffix": console_url[suffix_index+1:]
849 }
850 protocol_index += 2
tiernoae4a8d12016-07-08 12:30:39 +0200851 return console_dict
852 raise vimconn.vimconnUnexpectedResponse("Unexpected response from VIM")
tierno7edb6752016-03-21 17:37:52 +0100853
tierno8e995ce2016-09-22 08:13:00 +0000854 except (nvExceptions.NotFound, ksExceptions.ClientException, nvExceptions.ClientException, nvExceptions.BadRequest, ConnectionError) as e:
tiernoae4a8d12016-07-08 12:30:39 +0200855 self._format_exception(e)
tierno7edb6752016-03-21 17:37:52 +0100856
tiernoae4a8d12016-07-08 12:30:39 +0200857 def delete_vminstance(self, vm_id):
858 '''Removes a VM instance from VIM. Returns the old identifier
tierno7edb6752016-03-21 17:37:52 +0100859 '''
tiernoae4a8d12016-07-08 12:30:39 +0200860 #print "osconnector: Getting VM from VIM"
tierno7edb6752016-03-21 17:37:52 +0100861 try:
862 self._reload_connection()
863 #delete VM ports attached to this networks before the virtual machine
864 ports = self.neutron.list_ports(device_id=vm_id)
865 for p in ports['ports']:
866 try:
867 self.neutron.delete_port(p["id"])
868 except Exception as e:
tiernoae4a8d12016-07-08 12:30:39 +0200869 self.logger.error("Error deleting port: " + type(e).__name__ + ": "+ str(e))
tierno7edb6752016-03-21 17:37:52 +0100870 self.nova.servers.delete(vm_id)
tiernoae4a8d12016-07-08 12:30:39 +0200871 return vm_id
tierno8e995ce2016-09-22 08:13:00 +0000872 except (nvExceptions.NotFound, ksExceptions.ClientException, nvExceptions.ClientException, ConnectionError) as e:
tiernoae4a8d12016-07-08 12:30:39 +0200873 self._format_exception(e)
tierno7edb6752016-03-21 17:37:52 +0100874 #TODO insert exception vimconn.HTTP_Unauthorized
875 #if reaching here is because an exception
tierno7edb6752016-03-21 17:37:52 +0100876
tiernoae4a8d12016-07-08 12:30:39 +0200877 def refresh_vms_status(self, vm_list):
878 '''Get the status of the virtual machines and their interfaces/ports
879 Params: the list of VM identifiers
880 Returns a dictionary with:
881 vm_id: #VIM id of this Virtual Machine
882 status: #Mandatory. Text with one of:
883 # DELETED (not found at vim)
884 # VIM_ERROR (Cannot connect to VIM, VIM response error, ...)
885 # OTHER (Vim reported other status not understood)
886 # ERROR (VIM indicates an ERROR status)
887 # ACTIVE, PAUSED, SUSPENDED, INACTIVE (not running),
888 # CREATING (on building process), ERROR
889 # ACTIVE:NoMgmtIP (Active but any of its interface has an IP address
890 #
891 error_msg: #Text with VIM error message, if any. Or the VIM connection ERROR
892 vim_info: #Text with plain information obtained from vim (yaml.safe_dump)
893 interfaces:
894 - vim_info: #Text with plain information obtained from vim (yaml.safe_dump)
895 mac_address: #Text format XX:XX:XX:XX:XX:XX
896 vim_net_id: #network id where this interface is connected
897 vim_interface_id: #interface/port VIM id
898 ip_address: #null, or text with IPv4, IPv6 address
tierno7edb6752016-03-21 17:37:52 +0100899 '''
tiernoae4a8d12016-07-08 12:30:39 +0200900 vm_dict={}
901 self.logger.debug("refresh_vms status: Getting tenant VM instance information from VIM")
902 for vm_id in vm_list:
903 vm={}
904 try:
905 vm_vim = self.get_vminstance(vm_id)
906 if vm_vim['status'] in vmStatus2manoFormat:
907 vm['status'] = vmStatus2manoFormat[ vm_vim['status'] ]
tierno7edb6752016-03-21 17:37:52 +0100908 else:
tiernoae4a8d12016-07-08 12:30:39 +0200909 vm['status'] = "OTHER"
910 vm['error_msg'] = "VIM status reported " + vm_vim['status']
tierno8e995ce2016-09-22 08:13:00 +0000911 try:
912 vm['vim_info'] = yaml.safe_dump(vm_vim, default_flow_style=True, width=256)
913 except yaml.representer.RepresenterError:
914 vm['vim_info'] = str(vm_vim)
tiernoae4a8d12016-07-08 12:30:39 +0200915 vm["interfaces"] = []
916 if vm_vim.get('fault'):
917 vm['error_msg'] = str(vm_vim['fault'])
918 #get interfaces
tierno7edb6752016-03-21 17:37:52 +0100919 try:
tiernoae4a8d12016-07-08 12:30:39 +0200920 self._reload_connection()
921 port_dict=self.neutron.list_ports(device_id=vm_id)
922 for port in port_dict["ports"]:
923 interface={}
tierno8e995ce2016-09-22 08:13:00 +0000924 try:
925 interface['vim_info'] = yaml.safe_dump(port, default_flow_style=True, width=256)
926 except yaml.representer.RepresenterError:
927 interface['vim_info'] = str(port)
tiernoae4a8d12016-07-08 12:30:39 +0200928 interface["mac_address"] = port.get("mac_address")
929 interface["vim_net_id"] = port["network_id"]
930 interface["vim_interface_id"] = port["id"]
931 ips=[]
932 #look for floating ip address
933 floating_ip_dict = self.neutron.list_floatingips(port_id=port["id"])
934 if floating_ip_dict.get("floatingips"):
935 ips.append(floating_ip_dict["floatingips"][0].get("floating_ip_address") )
tierno7edb6752016-03-21 17:37:52 +0100936
tiernoae4a8d12016-07-08 12:30:39 +0200937 for subnet in port["fixed_ips"]:
938 ips.append(subnet["ip_address"])
939 interface["ip_address"] = ";".join(ips)
940 vm["interfaces"].append(interface)
941 except Exception as e:
942 self.logger.error("Error getting vm interface information " + type(e).__name__ + ": "+ str(e))
943 except vimconn.vimconnNotFoundException as e:
944 self.logger.error("Exception getting vm status: %s", str(e))
945 vm['status'] = "DELETED"
946 vm['error_msg'] = str(e)
947 except vimconn.vimconnException as e:
948 self.logger.error("Exception getting vm status: %s", str(e))
949 vm['status'] = "VIM_ERROR"
950 vm['error_msg'] = str(e)
951 vm_dict[vm_id] = vm
952 return vm_dict
tierno7edb6752016-03-21 17:37:52 +0100953
tiernoae4a8d12016-07-08 12:30:39 +0200954 def action_vminstance(self, vm_id, action_dict):
tierno7edb6752016-03-21 17:37:52 +0100955 '''Send and action over a VM instance from VIM
tiernoae4a8d12016-07-08 12:30:39 +0200956 Returns the vm_id if the action was successfully sent to the VIM'''
957 self.logger.debug("Action over VM '%s': %s", vm_id, str(action_dict))
tierno7edb6752016-03-21 17:37:52 +0100958 try:
959 self._reload_connection()
960 server = self.nova.servers.find(id=vm_id)
961 if "start" in action_dict:
962 if action_dict["start"]=="rebuild":
963 server.rebuild()
964 else:
965 if server.status=="PAUSED":
966 server.unpause()
967 elif server.status=="SUSPENDED":
968 server.resume()
969 elif server.status=="SHUTOFF":
970 server.start()
971 elif "pause" in action_dict:
972 server.pause()
973 elif "resume" in action_dict:
974 server.resume()
975 elif "shutoff" in action_dict or "shutdown" in action_dict:
976 server.stop()
977 elif "forceOff" in action_dict:
978 server.stop() #TODO
979 elif "terminate" in action_dict:
980 server.delete()
981 elif "createImage" in action_dict:
982 server.create_image()
983 #"path":path_schema,
984 #"description":description_schema,
985 #"name":name_schema,
986 #"metadata":metadata_schema,
987 #"imageRef": id_schema,
988 #"disk": {"oneOf":[{"type": "null"}, {"type":"string"}] },
989 elif "rebuild" in action_dict:
990 server.rebuild(server.image['id'])
991 elif "reboot" in action_dict:
992 server.reboot() #reboot_type='SOFT'
993 elif "console" in action_dict:
994 console_type = action_dict["console"]
995 if console_type == None or console_type == "novnc":
996 console_dict = server.get_vnc_console("novnc")
997 elif console_type == "xvpvnc":
998 console_dict = server.get_vnc_console(console_type)
999 elif console_type == "rdp-html5":
1000 console_dict = server.get_rdp_console(console_type)
1001 elif console_type == "spice-html5":
1002 console_dict = server.get_spice_console(console_type)
1003 else:
tiernoae4a8d12016-07-08 12:30:39 +02001004 raise vimconn.vimconnException("console type '{}' not allowed".format(console_type),
1005 http_code=vimconn.HTTP_Bad_Request)
tierno7edb6752016-03-21 17:37:52 +01001006 try:
1007 console_url = console_dict["console"]["url"]
1008 #parse console_url
1009 protocol_index = console_url.find("//")
1010 suffix_index = console_url[protocol_index+2:].find("/") + protocol_index+2
1011 port_index = console_url[protocol_index+2:suffix_index].find(":") + protocol_index+2
1012 if protocol_index < 0 or port_index<0 or suffix_index<0:
tiernoae4a8d12016-07-08 12:30:39 +02001013 raise vimconn.vimconnException("Unexpected response from VIM " + str(console_dict))
tierno7edb6752016-03-21 17:37:52 +01001014 console_dict2={"protocol": console_url[0:protocol_index],
1015 "server": console_url[protocol_index+2 : port_index],
1016 "port": int(console_url[port_index+1 : suffix_index]),
1017 "suffix": console_url[suffix_index+1:]
1018 }
tiernoae4a8d12016-07-08 12:30:39 +02001019 return console_dict2
1020 except Exception as e:
1021 raise vimconn.vimconnException("Unexpected response from VIM " + str(console_dict))
tierno7edb6752016-03-21 17:37:52 +01001022
tiernoae4a8d12016-07-08 12:30:39 +02001023 return vm_id
tierno8e995ce2016-09-22 08:13:00 +00001024 except (ksExceptions.ClientException, nvExceptions.ClientException, nvExceptions.NotFound, ConnectionError) as e:
tiernoae4a8d12016-07-08 12:30:39 +02001025 self._format_exception(e)
1026 #TODO insert exception vimconn.HTTP_Unauthorized
1027
1028#NOT USED FUNCTIONS
1029
1030 def new_external_port(self, port_data):
1031 #TODO openstack if needed
1032 '''Adds a external port to VIM'''
1033 '''Returns the port identifier'''
1034 return -vimconn.HTTP_Internal_Server_Error, "osconnector.new_external_port() not implemented"
1035
1036 def connect_port_network(self, port_id, network_id, admin=False):
1037 #TODO openstack if needed
1038 '''Connects a external port to a network'''
1039 '''Returns status code of the VIM response'''
1040 return -vimconn.HTTP_Internal_Server_Error, "osconnector.connect_port_network() not implemented"
1041
1042 def new_user(self, user_name, user_passwd, tenant_id=None):
1043 '''Adds a new user to openstack VIM'''
1044 '''Returns the user identifier'''
1045 self.logger.debug("osconnector: Adding a new user to VIM")
1046 try:
1047 self._reload_connection()
1048 user=self.keystone.users.create(user_name, user_passwd, tenant_id=tenant_id)
1049 #self.keystone.tenants.add_user(self.k_creds["username"], #role)
1050 return user.id
1051 except ksExceptions.ConnectionError as e:
1052 error_value=-vimconn.HTTP_Bad_Request
1053 error_text= type(e).__name__ + ": "+ (str(e) if len(e.args)==0 else str(e.args[0]))
1054 except ksExceptions.ClientException as e: #TODO remove
tierno7edb6752016-03-21 17:37:52 +01001055 error_value=-vimconn.HTTP_Bad_Request
1056 error_text= type(e).__name__ + ": "+ (str(e) if len(e.args)==0 else str(e.args[0]))
1057 #TODO insert exception vimconn.HTTP_Unauthorized
1058 #if reaching here is because an exception
1059 if self.debug:
tiernoae4a8d12016-07-08 12:30:39 +02001060 self.logger.debug("new_user " + error_text)
tierno7edb6752016-03-21 17:37:52 +01001061 return error_value, error_text
tiernoae4a8d12016-07-08 12:30:39 +02001062
1063 def delete_user(self, user_id):
1064 '''Delete a user from openstack VIM'''
1065 '''Returns the user identifier'''
1066 if self.debug:
1067 print "osconnector: Deleting a user from VIM"
1068 try:
1069 self._reload_connection()
1070 self.keystone.users.delete(user_id)
1071 return 1, user_id
1072 except ksExceptions.ConnectionError as e:
1073 error_value=-vimconn.HTTP_Bad_Request
1074 error_text= type(e).__name__ + ": "+ (str(e) if len(e.args)==0 else str(e.args[0]))
1075 except ksExceptions.NotFound as e:
1076 error_value=-vimconn.HTTP_Not_Found
1077 error_text= type(e).__name__ + ": "+ (str(e) if len(e.args)==0 else str(e.args[0]))
1078 except ksExceptions.ClientException as e: #TODO remove
1079 error_value=-vimconn.HTTP_Bad_Request
1080 error_text= type(e).__name__ + ": "+ (str(e) if len(e.args)==0 else str(e.args[0]))
1081 #TODO insert exception vimconn.HTTP_Unauthorized
1082 #if reaching here is because an exception
1083 if self.debug:
1084 print "delete_tenant " + error_text
1085 return error_value, error_text
1086
tierno7edb6752016-03-21 17:37:52 +01001087 def get_hosts_info(self):
1088 '''Get the information of deployed hosts
1089 Returns the hosts content'''
1090 if self.debug:
1091 print "osconnector: Getting Host info from VIM"
1092 try:
1093 h_list=[]
1094 self._reload_connection()
1095 hypervisors = self.nova.hypervisors.list()
1096 for hype in hypervisors:
1097 h_list.append( hype.to_dict() )
1098 return 1, {"hosts":h_list}
1099 except nvExceptions.NotFound as e:
1100 error_value=-vimconn.HTTP_Not_Found
1101 error_text= (str(e) if len(e.args)==0 else str(e.args[0]))
1102 except (ksExceptions.ClientException, nvExceptions.ClientException) as e:
1103 error_value=-vimconn.HTTP_Bad_Request
1104 error_text= type(e).__name__ + ": "+ (str(e) if len(e.args)==0 else str(e.args[0]))
1105 #TODO insert exception vimconn.HTTP_Unauthorized
1106 #if reaching here is because an exception
1107 if self.debug:
1108 print "get_hosts_info " + error_text
1109 return error_value, error_text
1110
1111 def get_hosts(self, vim_tenant):
1112 '''Get the hosts and deployed instances
1113 Returns the hosts content'''
1114 r, hype_dict = self.get_hosts_info()
1115 if r<0:
1116 return r, hype_dict
1117 hypervisors = hype_dict["hosts"]
1118 try:
1119 servers = self.nova.servers.list()
1120 for hype in hypervisors:
1121 for server in servers:
1122 if server.to_dict()['OS-EXT-SRV-ATTR:hypervisor_hostname']==hype['hypervisor_hostname']:
1123 if 'vm' in hype:
1124 hype['vm'].append(server.id)
1125 else:
1126 hype['vm'] = [server.id]
1127 return 1, hype_dict
1128 except nvExceptions.NotFound as e:
1129 error_value=-vimconn.HTTP_Not_Found
1130 error_text= (str(e) if len(e.args)==0 else str(e.args[0]))
1131 except (ksExceptions.ClientException, nvExceptions.ClientException) as e:
1132 error_value=-vimconn.HTTP_Bad_Request
1133 error_text= type(e).__name__ + ": "+ (str(e) if len(e.args)==0 else str(e.args[0]))
1134 #TODO insert exception vimconn.HTTP_Unauthorized
1135 #if reaching here is because an exception
1136 if self.debug:
1137 print "get_hosts " + error_text
1138 return error_value, error_text
1139
tierno7edb6752016-03-21 17:37:52 +01001140