blob: b280da89cc1276787af797dcd2d0a4dab3117224 [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
kate721d79b2017-06-24 04:21:38 -070038import re
tierno7edb6752016-03-21 17:37:52 +010039
tierno73cd9c32017-06-19 15:52:22 +020040from novaclient import client as nClient, exceptions as nvExceptions
41from keystoneauth1.identity import v2, v3
42from keystoneauth1 import session
tierno7edb6752016-03-21 17:37:52 +010043import keystoneclient.exceptions as ksExceptions
tiernod42ccea2017-06-21 18:01:40 +020044import keystoneclient.v3.client as ksClient_v3
45import keystoneclient.v2_0.client as ksClient_v2
tierno73cd9c32017-06-19 15:52:22 +020046from glanceclient import client as glClient
tierno7edb6752016-03-21 17:37:52 +010047import glanceclient.client as gl1Client
48import glanceclient.exc as gl1Exceptions
tierno73cd9c32017-06-19 15:52:22 +020049from cinderclient import client as cClient
tierno7edb6752016-03-21 17:37:52 +010050from httplib import HTTPException
tierno73cd9c32017-06-19 15:52:22 +020051from neutronclient.neutron import client as neClient
tierno7edb6752016-03-21 17:37:52 +010052from neutronclient.common import exceptions as neExceptions
53from requests.exceptions import ConnectionError
54
55'''contain the openstack virtual machine status to openmano status'''
56vmStatus2manoFormat={'ACTIVE':'ACTIVE',
57 'PAUSED':'PAUSED',
58 'SUSPENDED': 'SUSPENDED',
59 'SHUTOFF':'INACTIVE',
60 'BUILD':'BUILD',
61 'ERROR':'ERROR','DELETED':'DELETED'
62 }
63netStatus2manoFormat={'ACTIVE':'ACTIVE','PAUSED':'PAUSED','INACTIVE':'INACTIVE','BUILD':'BUILD','ERROR':'ERROR','DELETED':'DELETED'
64 }
65
montesmoreno0c8def02016-12-22 12:16:23 +000066#global var to have a timeout creating and deleting volumes
67volume_timeout = 60
montesmoreno2a1fc4e2017-01-09 16:46:04 +000068server_timeout = 60
montesmoreno0c8def02016-12-22 12:16:23 +000069
tierno7edb6752016-03-21 17:37:52 +010070class vimconnector(vimconn.vimconnector):
tiernob3d36742017-03-03 23:51:05 +010071 def __init__(self, uuid, name, tenant_id, tenant_name, url, url_admin=None, user=None, passwd=None,
72 log_level=None, config={}, persistent_info={}):
ahmadsa96af9f42017-01-31 16:17:14 +050073 '''using common constructor parameters. In this case
tierno7edb6752016-03-21 17:37:52 +010074 'url' is the keystone authorization url,
75 'url_admin' is not use
76 '''
tiernod42ccea2017-06-21 18:01:40 +020077 api_version = config.get('APIversion')
78 if api_version and api_version not in ('v3.3', 'v2.0', '2', '3'):
tierno73cd9c32017-06-19 15:52:22 +020079 raise vimconn.vimconnException("Invalid value '{}' for config:APIversion. "
tiernod42ccea2017-06-21 18:01:40 +020080 "Allowed values are 'v3.3', 'v2.0', '2' or '3'".format(api_version))
kate721d79b2017-06-24 04:21:38 -070081 vim_type = config.get('vim_type')
82 if vim_type and vim_type not in ('vio', 'VIO'):
83 raise vimconn.vimconnException("Invalid value '{}' for config:vim_type."
84 "Allowed values are 'vio' or 'VIO'".format(vim_type))
85
86 if config.get('dataplane_net_vlan_range') is not None:
87 #validate vlan ranges provided by user
88 self._validate_vlan_ranges(config.get('dataplane_net_vlan_range'))
89
tierno73cd9c32017-06-19 15:52:22 +020090 vimconn.vimconnector.__init__(self, uuid, name, tenant_id, tenant_name, url, url_admin, user, passwd, log_level,
91 config)
tiernob3d36742017-03-03 23:51:05 +010092
tierno73cd9c32017-06-19 15:52:22 +020093 self.insecure = self.config.get("insecure", False)
tierno7edb6752016-03-21 17:37:52 +010094 if not url:
95 raise TypeError, 'url param can not be NoneType'
tierno73cd9c32017-06-19 15:52:22 +020096 self.persistent_info = persistent_info
97 self.session = persistent_info.get('session', {'reload_client': True})
98 self.nova = self.session.get('nova')
99 self.neutron = self.session.get('neutron')
100 self.cinder = self.session.get('cinder')
101 self.glance = self.session.get('glance')
kate721d79b2017-06-24 04:21:38 -0700102 self.glancev1 = self.session.get('glancev1')
tiernod42ccea2017-06-21 18:01:40 +0200103 self.keystone = self.session.get('keystone')
104 self.api_version3 = self.session.get('api_version3')
kate721d79b2017-06-24 04:21:38 -0700105 self.vim_type = self.config.get("vim_type")
106 if self.vim_type:
107 self.vim_type = self.vim_type.upper()
108 if self.config.get("use_internal_endpoint"):
109 self.endpoint_type = "internalURL"
110 else:
111 self.endpoint_type = None
montesmoreno0c8def02016-12-22 12:16:23 +0000112
tierno73ad9e42016-09-12 18:11:11 +0200113 self.logger = logging.getLogger('openmano.vim.openstack')
kate721d79b2017-06-24 04:21:38 -0700114
115 ####### VIO Specific Changes #########
116 if self.vim_type == "VIO":
117 self.logger = logging.getLogger('openmano.vim.vio')
118
tiernofe789902016-09-29 14:20:44 +0000119 if log_level:
kate721d79b2017-06-24 04:21:38 -0700120 self.logger.setLevel(getattr(logging, log_level))
tiernod42ccea2017-06-21 18:01:40 +0200121
122 def __getitem__(self, index):
123 """Get individuals parameters.
124 Throw KeyError"""
125 if index == 'project_domain_id':
126 return self.config.get("project_domain_id")
127 elif index == 'user_domain_id':
128 return self.config.get("user_domain_id")
129 else:
tierno40a96022017-06-29 16:42:15 +0200130 return vimconn.vimconnector.__getitem__(self, index)
tiernod42ccea2017-06-21 18:01:40 +0200131
132 def __setitem__(self, index, value):
133 """Set individuals parameters and it is marked as dirty so to force connection reload.
134 Throw KeyError"""
135 if index == 'project_domain_id':
136 self.config["project_domain_id"] = value
137 elif index == 'user_domain_id':
138 self.config["user_domain_id"] = value
139 else:
140 vimconn.vimconnector.__setitem__(self, index, value)
tierno73cd9c32017-06-19 15:52:22 +0200141 self.session['reload_client'] = True
tiernod42ccea2017-06-21 18:01:40 +0200142
tierno7edb6752016-03-21 17:37:52 +0100143 def _reload_connection(self):
144 '''Called before any operation, it check if credentials has changed
145 Throw keystoneclient.apiclient.exceptions.AuthorizationFailure
146 '''
147 #TODO control the timing and possible token timeout, but it seams that python client does this task for us :-)
tierno73cd9c32017-06-19 15:52:22 +0200148 if self.session['reload_client']:
tiernod42ccea2017-06-21 18:01:40 +0200149 if self.config.get('APIversion'):
150 self.api_version3 = self.config['APIversion'] == 'v3.3' or self.config['APIversion'] == '3'
151 else: # get from ending auth_url that end with v3 or with v2.0
152 self.api_version3 = self.url.split("/")[-1] == "v3"
153 self.session['api_version3'] = self.api_version3
154 if self.api_version3:
155 auth = v3.Password(auth_url=self.url,
tierno73cd9c32017-06-19 15:52:22 +0200156 username=self.user,
157 password=self.passwd,
158 project_name=self.tenant_name,
159 project_id=self.tenant_id,
160 project_domain_id=self.config.get('project_domain_id', 'default'),
161 user_domain_id=self.config.get('user_domain_id', 'default'))
ahmadsa95baa272016-11-30 09:14:11 +0500162 else:
tiernod42ccea2017-06-21 18:01:40 +0200163 auth = v2.Password(auth_url=self.url,
tierno73cd9c32017-06-19 15:52:22 +0200164 username=self.user,
165 password=self.passwd,
166 tenant_name=self.tenant_name,
167 tenant_id=self.tenant_id)
168 sess = session.Session(auth=auth, verify=not self.insecure)
tiernod42ccea2017-06-21 18:01:40 +0200169 if self.api_version3:
kate721d79b2017-06-24 04:21:38 -0700170 self.keystone = ksClient_v3.Client(session=sess, endpoint_type=self.endpoint_type)
tiernod42ccea2017-06-21 18:01:40 +0200171 else:
kate721d79b2017-06-24 04:21:38 -0700172 self.keystone = ksClient_v2.Client(session=sess, endpoint_type=self.endpoint_type)
tiernod42ccea2017-06-21 18:01:40 +0200173 self.session['keystone'] = self.keystone
kate721d79b2017-06-24 04:21:38 -0700174 self.nova = self.session['nova'] = nClient.Client("2.1", session=sess, endpoint_type=self.endpoint_type)
175 self.neutron = self.session['neutron'] = neClient.Client('2.0', session=sess, endpoint_type=self.endpoint_type)
176 self.cinder = self.session['cinder'] = cClient.Client(2, session=sess, endpoint_type=self.endpoint_type)
177 if self.endpoint_type == "internalURL":
178 glance_service_id = self.keystone.services.list(name="glance")[0].id
179 glance_endpoint = self.keystone.endpoints.list(glance_service_id, interface="internal")[0].url
180 else:
181 glance_endpoint = None
182 self.glance = self.session['glance'] = glClient.Client(2, session=sess, endpoint=glance_endpoint)
183 #using version 1 of glance client in new_image()
184 self.glancev1 = self.session['glancev1'] = glClient.Client('1', session=sess,
185 endpoint=glance_endpoint)
tierno73cd9c32017-06-19 15:52:22 +0200186 self.session['reload_client'] = False
187 self.persistent_info['session'] = self.session
ahmadsa95baa272016-11-30 09:14:11 +0500188
tierno7edb6752016-03-21 17:37:52 +0100189 def __net_os2mano(self, net_list_dict):
190 '''Transform the net openstack format to mano format
191 net_list_dict can be a list of dict or a single dict'''
192 if type(net_list_dict) is dict:
193 net_list_=(net_list_dict,)
194 elif type(net_list_dict) is list:
195 net_list_=net_list_dict
196 else:
197 raise TypeError("param net_list_dict must be a list or a dictionary")
198 for net in net_list_:
199 if net.get('provider:network_type') == "vlan":
200 net['type']='data'
201 else:
202 net['type']='bridge'
kate721d79b2017-06-24 04:21:38 -0700203
tiernoae4a8d12016-07-08 12:30:39 +0200204 def _format_exception(self, exception):
205 '''Transform a keystone, nova, neutron exception into a vimconn exception'''
206 if isinstance(exception, (HTTPException, gl1Exceptions.HTTPException, gl1Exceptions.CommunicationError,
tierno8e995ce2016-09-22 08:13:00 +0000207 ConnectionError, ksExceptions.ConnectionError, neExceptions.ConnectionFailed
208 )):
tiernoae4a8d12016-07-08 12:30:39 +0200209 raise vimconn.vimconnConnectionException(type(exception).__name__ + ": " + str(exception))
210 elif isinstance(exception, (nvExceptions.ClientException, ksExceptions.ClientException,
211 neExceptions.NeutronException, nvExceptions.BadRequest)):
212 raise vimconn.vimconnUnexpectedResponse(type(exception).__name__ + ": " + str(exception))
213 elif isinstance(exception, (neExceptions.NetworkNotFoundClient, nvExceptions.NotFound)):
214 raise vimconn.vimconnNotFoundException(type(exception).__name__ + ": " + str(exception))
215 elif isinstance(exception, nvExceptions.Conflict):
216 raise vimconn.vimconnConflictException(type(exception).__name__ + ": " + str(exception))
tiernod42ccea2017-06-21 18:01:40 +0200217 else: # ()
tiernoae4a8d12016-07-08 12:30:39 +0200218 raise vimconn.vimconnConnectionException(type(exception).__name__ + ": " + str(exception))
219
220 def get_tenant_list(self, filter_dict={}):
221 '''Obtain tenants of VIM
222 filter_dict can contain the following keys:
223 name: filter by tenant name
224 id: filter by tenant uuid/id
225 <other VIM specific>
226 Returns the tenant list of dictionaries: [{'name':'<name>, 'id':'<id>, ...}, ...]
227 '''
ahmadsa95baa272016-11-30 09:14:11 +0500228 self.logger.debug("Getting tenants from VIM filter: '%s'", str(filter_dict))
tiernoae4a8d12016-07-08 12:30:39 +0200229 try:
230 self._reload_connection()
tiernod42ccea2017-06-21 18:01:40 +0200231 if self.api_version3:
232 project_class_list = self.keystone.projects.list(name=filter_dict.get("name"))
ahmadsa95baa272016-11-30 09:14:11 +0500233 else:
tiernod42ccea2017-06-21 18:01:40 +0200234 project_class_list = self.keystone.tenants.findall(**filter_dict)
ahmadsa95baa272016-11-30 09:14:11 +0500235 project_list=[]
236 for project in project_class_list:
tiernod42ccea2017-06-21 18:01:40 +0200237 if filter_dict.get('id') and filter_dict["id"] != project.id:
238 continue
ahmadsa95baa272016-11-30 09:14:11 +0500239 project_list.append(project.to_dict())
240 return project_list
tiernod42ccea2017-06-21 18:01:40 +0200241 except (ksExceptions.ConnectionError, ksExceptions.ClientException, ConnectionError) as e:
tiernoae4a8d12016-07-08 12:30:39 +0200242 self._format_exception(e)
243
244 def new_tenant(self, tenant_name, tenant_description):
245 '''Adds a new tenant to openstack VIM. Returns the tenant identifier'''
246 self.logger.debug("Adding a new tenant name: %s", tenant_name)
247 try:
248 self._reload_connection()
tiernod42ccea2017-06-21 18:01:40 +0200249 if self.api_version3:
250 project = self.keystone.projects.create(tenant_name, self.config.get("project_domain_id", "default"),
251 description=tenant_description, is_domain=False)
ahmadsa95baa272016-11-30 09:14:11 +0500252 else:
tiernod42ccea2017-06-21 18:01:40 +0200253 project = self.keystone.tenants.create(tenant_name, tenant_description)
ahmadsa95baa272016-11-30 09:14:11 +0500254 return project.id
tierno8e995ce2016-09-22 08:13:00 +0000255 except (ksExceptions.ConnectionError, ksExceptions.ClientException, ConnectionError) as e:
tiernoae4a8d12016-07-08 12:30:39 +0200256 self._format_exception(e)
257
258 def delete_tenant(self, tenant_id):
259 '''Delete a tenant from openstack VIM. Returns the old tenant identifier'''
260 self.logger.debug("Deleting tenant %s from VIM", tenant_id)
261 try:
262 self._reload_connection()
tiernod42ccea2017-06-21 18:01:40 +0200263 if self.api_version3:
ahmadsa95baa272016-11-30 09:14:11 +0500264 self.keystone.projects.delete(tenant_id)
265 else:
266 self.keystone.tenants.delete(tenant_id)
tiernoae4a8d12016-07-08 12:30:39 +0200267 return tenant_id
tierno8e995ce2016-09-22 08:13:00 +0000268 except (ksExceptions.ConnectionError, ksExceptions.ClientException, ConnectionError) as e:
tiernoae4a8d12016-07-08 12:30:39 +0200269 self._format_exception(e)
ahmadsa95baa272016-11-30 09:14:11 +0500270
garciadeblas9f8456e2016-09-05 05:02:59 +0200271 def new_network(self,net_name, net_type, ip_profile=None, shared=False, vlan=None):
tiernoae4a8d12016-07-08 12:30:39 +0200272 '''Adds a tenant network to VIM. Returns the network identifier'''
273 self.logger.debug("Adding a new network to VIM name '%s', type '%s'", net_name, net_type)
garciadeblasedca7b32016-09-29 14:01:52 +0000274 #self.logger.debug(">>>>>>>>>>>>>>>>>> IP profile %s", str(ip_profile))
tierno7edb6752016-03-21 17:37:52 +0100275 try:
garciadeblasedca7b32016-09-29 14:01:52 +0000276 new_net = None
tierno7edb6752016-03-21 17:37:52 +0100277 self._reload_connection()
278 network_dict = {'name': net_name, 'admin_state_up': True}
279 if net_type=="data" or net_type=="ptp":
280 if self.config.get('dataplane_physical_net') == None:
tiernoae4a8d12016-07-08 12:30:39 +0200281 raise vimconn.vimconnConflictException("You must provide a 'dataplane_physical_net' at config value before creating sriov network")
tierno7edb6752016-03-21 17:37:52 +0100282 network_dict["provider:physical_network"] = self.config['dataplane_physical_net'] #"physnet_sriov" #TODO physical
283 network_dict["provider:network_type"] = "vlan"
284 if vlan!=None:
285 network_dict["provider:network_type"] = vlan
kate721d79b2017-06-24 04:21:38 -0700286
287 ####### VIO Specific Changes #########
288 if self.vim_type == "VIO":
289 if vlan is not None:
290 network_dict["provider:segmentation_id"] = vlan
291 else:
292 if self.config.get('dataplane_net_vlan_range') is None:
293 raise vimconn.vimconnConflictException("You must provide "\
294 "'dataplane_net_vlan_range' in format [start_ID - end_ID]"\
295 "at config value before creating sriov network with vlan tag")
296
297 network_dict["provider:segmentation_id"] = self._genrate_vlanID()
298
tiernoae4a8d12016-07-08 12:30:39 +0200299 network_dict["shared"]=shared
tierno7edb6752016-03-21 17:37:52 +0100300 new_net=self.neutron.create_network({'network':network_dict})
301 #print new_net
garciadeblas9f8456e2016-09-05 05:02:59 +0200302 #create subnetwork, even if there is no profile
303 if not ip_profile:
304 ip_profile = {}
305 if 'subnet_address' not in ip_profile:
garciadeblas2299e3b2017-01-26 14:35:55 +0000306 #Fake subnet is required
307 subnet_rand = random.randint(0, 255)
308 ip_profile['subnet_address'] = "192.168.{}.0/24".format(subnet_rand)
garciadeblas9f8456e2016-09-05 05:02:59 +0200309 if 'ip_version' not in ip_profile:
310 ip_profile['ip_version'] = "IPv4"
tierno7edb6752016-03-21 17:37:52 +0100311 subnet={"name":net_name+"-subnet",
312 "network_id": new_net["network"]["id"],
garciadeblas9f8456e2016-09-05 05:02:59 +0200313 "ip_version": 4 if ip_profile['ip_version']=="IPv4" else 6,
314 "cidr": ip_profile['subnet_address']
tierno7edb6752016-03-21 17:37:52 +0100315 }
garciadeblas9f8456e2016-09-05 05:02:59 +0200316 if 'gateway_address' in ip_profile:
317 subnet['gateway_ip'] = ip_profile['gateway_address']
garciadeblasedca7b32016-09-29 14:01:52 +0000318 if ip_profile.get('dns_address'):
tierno455612d2017-05-30 16:40:10 +0200319 subnet['dns_nameservers'] = ip_profile['dns_address'].split(";")
garciadeblas9f8456e2016-09-05 05:02:59 +0200320 if 'dhcp_enabled' in ip_profile:
321 subnet['enable_dhcp'] = False if ip_profile['dhcp_enabled']=="false" else True
322 if 'dhcp_start_address' in ip_profile:
323 subnet['allocation_pools']=[]
324 subnet['allocation_pools'].append(dict())
325 subnet['allocation_pools'][0]['start'] = ip_profile['dhcp_start_address']
326 if 'dhcp_count' in ip_profile:
327 #parts = ip_profile['dhcp_start_address'].split('.')
328 #ip_int = (int(parts[0]) << 24) + (int(parts[1]) << 16) + (int(parts[2]) << 8) + int(parts[3])
329 ip_int = int(netaddr.IPAddress(ip_profile['dhcp_start_address']))
garciadeblas21d795b2016-09-29 17:31:46 +0200330 ip_int += ip_profile['dhcp_count'] - 1
garciadeblas9f8456e2016-09-05 05:02:59 +0200331 ip_str = str(netaddr.IPAddress(ip_int))
332 subnet['allocation_pools'][0]['end'] = ip_str
garciadeblasedca7b32016-09-29 14:01:52 +0000333 #self.logger.debug(">>>>>>>>>>>>>>>>>> Subnet: %s", str(subnet))
tierno7edb6752016-03-21 17:37:52 +0100334 self.neutron.create_subnet({"subnet": subnet} )
tiernoae4a8d12016-07-08 12:30:39 +0200335 return new_net["network"]["id"]
tierno8e995ce2016-09-22 08:13:00 +0000336 except (neExceptions.ConnectionFailed, ksExceptions.ClientException, neExceptions.NeutronException, ConnectionError) as e:
garciadeblasedca7b32016-09-29 14:01:52 +0000337 if new_net:
338 self.neutron.delete_network(new_net['network']['id'])
tiernoae4a8d12016-07-08 12:30:39 +0200339 self._format_exception(e)
tierno7edb6752016-03-21 17:37:52 +0100340
341 def get_network_list(self, filter_dict={}):
342 '''Obtain tenant networks of VIM
343 Filter_dict can be:
344 name: network name
345 id: network uuid
346 shared: boolean
347 tenant_id: tenant
348 admin_state_up: boolean
349 status: 'ACTIVE'
350 Returns the network list of dictionaries
351 '''
tiernoae4a8d12016-07-08 12:30:39 +0200352 self.logger.debug("Getting network from VIM filter: '%s'", str(filter_dict))
tierno7edb6752016-03-21 17:37:52 +0100353 try:
354 self._reload_connection()
tiernod42ccea2017-06-21 18:01:40 +0200355 if self.api_version3 and "tenant_id" in filter_dict:
356 filter_dict['project_id'] = filter_dict.pop('tenant_id') #TODO check
tierno7edb6752016-03-21 17:37:52 +0100357 net_dict=self.neutron.list_networks(**filter_dict)
358 net_list=net_dict["networks"]
359 self.__net_os2mano(net_list)
tiernoae4a8d12016-07-08 12:30:39 +0200360 return net_list
tierno8e995ce2016-09-22 08:13:00 +0000361 except (neExceptions.ConnectionFailed, ksExceptions.ClientException, neExceptions.NeutronException, ConnectionError) as e:
tiernoae4a8d12016-07-08 12:30:39 +0200362 self._format_exception(e)
tierno7edb6752016-03-21 17:37:52 +0100363
tiernoae4a8d12016-07-08 12:30:39 +0200364 def get_network(self, net_id):
365 '''Obtain details of network from VIM
366 Returns the network information from a network id'''
367 self.logger.debug(" Getting tenant network %s from VIM", net_id)
tierno7edb6752016-03-21 17:37:52 +0100368 filter_dict={"id": net_id}
tiernoae4a8d12016-07-08 12:30:39 +0200369 net_list = self.get_network_list(filter_dict)
tierno7edb6752016-03-21 17:37:52 +0100370 if len(net_list)==0:
tiernoae4a8d12016-07-08 12:30:39 +0200371 raise vimconn.vimconnNotFoundException("Network '{}' not found".format(net_id))
tierno7edb6752016-03-21 17:37:52 +0100372 elif len(net_list)>1:
tiernoae4a8d12016-07-08 12:30:39 +0200373 raise vimconn.vimconnConflictException("Found more than one network with this criteria")
tierno7edb6752016-03-21 17:37:52 +0100374 net = net_list[0]
375 subnets=[]
376 for subnet_id in net.get("subnets", () ):
377 try:
378 subnet = self.neutron.show_subnet(subnet_id)
379 except Exception as e:
tiernoae4a8d12016-07-08 12:30:39 +0200380 self.logger.error("osconnector.get_network(): Error getting subnet %s %s" % (net_id, str(e)))
381 subnet = {"id": subnet_id, "fault": str(e)}
tierno7edb6752016-03-21 17:37:52 +0100382 subnets.append(subnet)
383 net["subnets"] = subnets
Pablo Montes Moreno51e553b2017-03-23 16:39:12 +0100384 net["encapsulation"] = net.get('provider:network_type')
Pablo Montes Moreno3fbff9b2017-03-08 11:28:15 +0100385 net["segmentation_id"] = net.get('provider:segmentation_id')
tiernoae4a8d12016-07-08 12:30:39 +0200386 return net
tierno7edb6752016-03-21 17:37:52 +0100387
tiernoae4a8d12016-07-08 12:30:39 +0200388 def delete_network(self, net_id):
389 '''Deletes a tenant network from VIM. Returns the old network identifier'''
390 self.logger.debug("Deleting network '%s' from VIM", net_id)
tierno7edb6752016-03-21 17:37:52 +0100391 try:
392 self._reload_connection()
393 #delete VM ports attached to this networks before the network
394 ports = self.neutron.list_ports(network_id=net_id)
395 for p in ports['ports']:
396 try:
397 self.neutron.delete_port(p["id"])
398 except Exception as e:
tiernoae4a8d12016-07-08 12:30:39 +0200399 self.logger.error("Error deleting port %s: %s", p["id"], str(e))
tierno7edb6752016-03-21 17:37:52 +0100400 self.neutron.delete_network(net_id)
tiernoae4a8d12016-07-08 12:30:39 +0200401 return net_id
402 except (neExceptions.ConnectionFailed, neExceptions.NetworkNotFoundClient, neExceptions.NeutronException,
tierno8e995ce2016-09-22 08:13:00 +0000403 ksExceptions.ClientException, neExceptions.NeutronException, ConnectionError) as e:
tiernoae4a8d12016-07-08 12:30:39 +0200404 self._format_exception(e)
tierno7edb6752016-03-21 17:37:52 +0100405
tiernoae4a8d12016-07-08 12:30:39 +0200406 def refresh_nets_status(self, net_list):
407 '''Get the status of the networks
408 Params: the list of network identifiers
409 Returns a dictionary with:
410 net_id: #VIM id of this network
411 status: #Mandatory. Text with one of:
412 # DELETED (not found at vim)
413 # VIM_ERROR (Cannot connect to VIM, VIM response error, ...)
414 # OTHER (Vim reported other status not understood)
415 # ERROR (VIM indicates an ERROR status)
416 # ACTIVE, INACTIVE, DOWN (admin down),
417 # BUILD (on building process)
418 #
419 error_msg: #Text with VIM error message, if any. Or the VIM connection ERROR
420 vim_info: #Text with plain information obtained from vim (yaml.safe_dump)
421
422 '''
423 net_dict={}
424 for net_id in net_list:
425 net = {}
426 try:
427 net_vim = self.get_network(net_id)
428 if net_vim['status'] in netStatus2manoFormat:
429 net["status"] = netStatus2manoFormat[ net_vim['status'] ]
430 else:
431 net["status"] = "OTHER"
432 net["error_msg"] = "VIM status reported " + net_vim['status']
433
tierno8e995ce2016-09-22 08:13:00 +0000434 if net['status'] == "ACTIVE" and not net_vim['admin_state_up']:
tiernoae4a8d12016-07-08 12:30:39 +0200435 net['status'] = 'DOWN'
tierno8e995ce2016-09-22 08:13:00 +0000436 try:
437 net['vim_info'] = yaml.safe_dump(net_vim, default_flow_style=True, width=256)
438 except yaml.representer.RepresenterError:
439 net['vim_info'] = str(net_vim)
tiernoae4a8d12016-07-08 12:30:39 +0200440 if net_vim.get('fault'): #TODO
441 net['error_msg'] = str(net_vim['fault'])
442 except vimconn.vimconnNotFoundException as e:
443 self.logger.error("Exception getting net status: %s", str(e))
444 net['status'] = "DELETED"
445 net['error_msg'] = str(e)
446 except vimconn.vimconnException as e:
447 self.logger.error("Exception getting net status: %s", str(e))
448 net['status'] = "VIM_ERROR"
449 net['error_msg'] = str(e)
450 net_dict[net_id] = net
451 return net_dict
452
453 def get_flavor(self, flavor_id):
454 '''Obtain flavor details from the VIM. Returns the flavor dict details'''
455 self.logger.debug("Getting flavor '%s'", flavor_id)
tierno7edb6752016-03-21 17:37:52 +0100456 try:
457 self._reload_connection()
458 flavor = self.nova.flavors.find(id=flavor_id)
459 #TODO parse input and translate to VIM format (openmano_schemas.new_vminstance_response_schema)
tiernoae4a8d12016-07-08 12:30:39 +0200460 return flavor.to_dict()
tierno8e995ce2016-09-22 08:13:00 +0000461 except (nvExceptions.NotFound, nvExceptions.ClientException, ksExceptions.ClientException, ConnectionError) as e:
tiernoae4a8d12016-07-08 12:30:39 +0200462 self._format_exception(e)
tierno7edb6752016-03-21 17:37:52 +0100463
tiernocf157a82017-01-30 14:07:06 +0100464 def get_flavor_id_from_data(self, flavor_dict):
465 """Obtain flavor id that match the flavor description
466 Returns the flavor_id or raises a vimconnNotFoundException
tiernoe26fc7a2017-05-30 14:43:03 +0200467 flavor_dict: contains the required ram, vcpus, disk
468 If 'use_existing_flavors' is set to True at config, the closer flavor that provides same or more ram, vcpus
469 and disk is returned. Otherwise a flavor with exactly same ram, vcpus and disk is returned or a
470 vimconnNotFoundException is raised
tiernocf157a82017-01-30 14:07:06 +0100471 """
tiernoe26fc7a2017-05-30 14:43:03 +0200472 exact_match = False if self.config.get('use_existing_flavors') else True
tiernocf157a82017-01-30 14:07:06 +0100473 try:
474 self._reload_connection()
tiernoe26fc7a2017-05-30 14:43:03 +0200475 flavor_candidate_id = None
476 flavor_candidate_data = (10000, 10000, 10000)
477 flavor_target = (flavor_dict["ram"], flavor_dict["vcpus"], flavor_dict["disk"])
478 # numa=None
479 numas = flavor_dict.get("extended", {}).get("numas")
tiernocf157a82017-01-30 14:07:06 +0100480 if numas:
481 #TODO
482 raise vimconn.vimconnNotFoundException("Flavor with EPA still not implemted")
483 # if len(numas) > 1:
484 # raise vimconn.vimconnNotFoundException("Cannot find any flavor with more than one numa")
485 # numa=numas[0]
486 # numas = extended.get("numas")
487 for flavor in self.nova.flavors.list():
488 epa = flavor.get_keys()
489 if epa:
490 continue
tiernoe26fc7a2017-05-30 14:43:03 +0200491 # TODO
492 flavor_data = (flavor.ram, flavor.vcpus, flavor.disk)
493 if flavor_data == flavor_target:
494 return flavor.id
495 elif not exact_match and flavor_target < flavor_data < flavor_candidate_data:
496 flavor_candidate_id = flavor.id
497 flavor_candidate_data = flavor_data
498 if not exact_match and flavor_candidate_id:
499 return flavor_candidate_id
tiernocf157a82017-01-30 14:07:06 +0100500 raise vimconn.vimconnNotFoundException("Cannot find any flavor matching '{}'".format(str(flavor_dict)))
501 except (nvExceptions.NotFound, nvExceptions.ClientException, ksExceptions.ClientException, ConnectionError) as e:
502 self._format_exception(e)
503
504
tiernoae4a8d12016-07-08 12:30:39 +0200505 def new_flavor(self, flavor_data, change_name_if_used=True):
tierno7edb6752016-03-21 17:37:52 +0100506 '''Adds a tenant flavor to openstack VIM
tiernoae4a8d12016-07-08 12:30:39 +0200507 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 +0100508 Returns the flavor identifier
509 '''
tiernoae4a8d12016-07-08 12:30:39 +0200510 self.logger.debug("Adding flavor '%s'", str(flavor_data))
tierno7edb6752016-03-21 17:37:52 +0100511 retry=0
tiernoae4a8d12016-07-08 12:30:39 +0200512 max_retries=3
tierno7edb6752016-03-21 17:37:52 +0100513 name_suffix = 0
tiernoae4a8d12016-07-08 12:30:39 +0200514 name=flavor_data['name']
515 while retry<max_retries:
tierno7edb6752016-03-21 17:37:52 +0100516 retry+=1
517 try:
518 self._reload_connection()
519 if change_name_if_used:
520 #get used names
521 fl_names=[]
522 fl=self.nova.flavors.list()
523 for f in fl:
524 fl_names.append(f.name)
525 while name in fl_names:
526 name_suffix += 1
tiernoae4a8d12016-07-08 12:30:39 +0200527 name = flavor_data['name']+"-" + str(name_suffix)
kate721d79b2017-06-24 04:21:38 -0700528
tiernoae4a8d12016-07-08 12:30:39 +0200529 ram = flavor_data.get('ram',64)
530 vcpus = flavor_data.get('vcpus',1)
tierno7edb6752016-03-21 17:37:52 +0100531 numa_properties=None
532
tiernoae4a8d12016-07-08 12:30:39 +0200533 extended = flavor_data.get("extended")
tierno7edb6752016-03-21 17:37:52 +0100534 if extended:
535 numas=extended.get("numas")
536 if numas:
537 numa_nodes = len(numas)
538 if numa_nodes > 1:
539 return -1, "Can not add flavor with more than one numa"
540 numa_properties = {"hw:numa_nodes":str(numa_nodes)}
541 numa_properties["hw:mem_page_size"] = "large"
542 numa_properties["hw:cpu_policy"] = "dedicated"
543 numa_properties["hw:numa_mempolicy"] = "strict"
kate721d79b2017-06-24 04:21:38 -0700544 if self.vim_type == "VIO":
545 numa_properties["vmware:extra_config"] = '{"numa.nodeAffinity":"0"}'
546 numa_properties["vmware:latency_sensitivity_level"] = "high"
tierno7edb6752016-03-21 17:37:52 +0100547 for numa in numas:
548 #overwrite ram and vcpus
549 ram = numa['memory']*1024
Pablo Montes Morenoea1d6232017-05-24 11:33:24 +0200550 #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 +0100551 if 'paired-threads' in numa:
552 vcpus = numa['paired-threads']*2
Pablo Montes Morenoea1d6232017-05-24 11:33:24 +0200553 #cpu_thread_policy "require" implies that the compute node must have an STM architecture
554 numa_properties["hw:cpu_thread_policy"] = "require"
555 numa_properties["hw:cpu_policy"] = "dedicated"
tierno7edb6752016-03-21 17:37:52 +0100556 elif 'cores' in numa:
557 vcpus = numa['cores']
Pablo Montes Morenoea1d6232017-05-24 11:33:24 +0200558 # cpu_thread_policy "prefer" implies that the host must not have an SMT architecture, or a non-SMT architecture will be emulated
559 numa_properties["hw:cpu_thread_policy"] = "isolate"
560 numa_properties["hw:cpu_policy"] = "dedicated"
tierno7edb6752016-03-21 17:37:52 +0100561 elif 'threads' in numa:
562 vcpus = numa['threads']
Pablo Montes Morenoea1d6232017-05-24 11:33:24 +0200563 # cpu_thread_policy "prefer" implies that the host may or may not have an SMT architecture
564 numa_properties["hw:cpu_thread_policy"] = "prefer"
565 numa_properties["hw:cpu_policy"] = "dedicated"
Pablo Montes Moreno3be0b2a2017-03-30 13:22:15 +0200566 # for interface in numa.get("interfaces",() ):
567 # if interface["dedicated"]=="yes":
568 # raise vimconn.vimconnException("Passthrough interfaces are not supported for the openstack connector", http_code=vimconn.HTTP_Service_Unavailable)
569 # #TODO, add the key 'pci_passthrough:alias"="<label at config>:<number ifaces>"' when a way to connect it is available
kate721d79b2017-06-24 04:21:38 -0700570
571 #create flavor
572 new_flavor=self.nova.flavors.create(name,
573 ram,
574 vcpus,
tiernoae4a8d12016-07-08 12:30:39 +0200575 flavor_data.get('disk',1),
576 is_public=flavor_data.get('is_public', True)
kate721d79b2017-06-24 04:21:38 -0700577 )
tierno7edb6752016-03-21 17:37:52 +0100578 #add metadata
579 if numa_properties:
580 new_flavor.set_keys(numa_properties)
tiernoae4a8d12016-07-08 12:30:39 +0200581 return new_flavor.id
tierno7edb6752016-03-21 17:37:52 +0100582 except nvExceptions.Conflict as e:
tiernoae4a8d12016-07-08 12:30:39 +0200583 if change_name_if_used and retry < max_retries:
tierno7edb6752016-03-21 17:37:52 +0100584 continue
tiernoae4a8d12016-07-08 12:30:39 +0200585 self._format_exception(e)
tierno7edb6752016-03-21 17:37:52 +0100586 #except nvExceptions.BadRequest as e:
587 except (ksExceptions.ClientException, nvExceptions.ClientException, ConnectionError) as e:
tiernoae4a8d12016-07-08 12:30:39 +0200588 self._format_exception(e)
tierno7edb6752016-03-21 17:37:52 +0100589
tiernoae4a8d12016-07-08 12:30:39 +0200590 def delete_flavor(self,flavor_id):
591 '''Deletes a tenant flavor from openstack VIM. Returns the old flavor_id
tierno7edb6752016-03-21 17:37:52 +0100592 '''
tiernoae4a8d12016-07-08 12:30:39 +0200593 try:
594 self._reload_connection()
595 self.nova.flavors.delete(flavor_id)
596 return flavor_id
597 #except nvExceptions.BadRequest as e:
tierno8e995ce2016-09-22 08:13:00 +0000598 except (nvExceptions.NotFound, ksExceptions.ClientException, nvExceptions.ClientException, ConnectionError) as e:
tiernoae4a8d12016-07-08 12:30:39 +0200599 self._format_exception(e)
tierno7edb6752016-03-21 17:37:52 +0100600
tiernoae4a8d12016-07-08 12:30:39 +0200601 def new_image(self,image_dict):
tierno7edb6752016-03-21 17:37:52 +0100602 '''
tiernoae4a8d12016-07-08 12:30:39 +0200603 Adds a tenant image to VIM. imge_dict is a dictionary with:
604 name: name
605 disk_format: qcow2, vhd, vmdk, raw (by default), ...
606 location: path or URI
607 public: "yes" or "no"
608 metadata: metadata of the image
609 Returns the image_id
tierno7edb6752016-03-21 17:37:52 +0100610 '''
tierno73cd9c32017-06-19 15:52:22 +0200611 # ALF TODO: revise and change for the new method or session
tiernoae4a8d12016-07-08 12:30:39 +0200612 retry=0
613 max_retries=3
614 while retry<max_retries:
tierno7edb6752016-03-21 17:37:52 +0100615 retry+=1
616 try:
617 self._reload_connection()
618 #determine format http://docs.openstack.org/developer/glance/formats.html
619 if "disk_format" in image_dict:
620 disk_format=image_dict["disk_format"]
garciadeblas14480452017-01-10 13:08:07 +0100621 else: #autodiscover based on extension
tierno7edb6752016-03-21 17:37:52 +0100622 if image_dict['location'][-6:]==".qcow2":
623 disk_format="qcow2"
624 elif image_dict['location'][-4:]==".vhd":
625 disk_format="vhd"
626 elif image_dict['location'][-5:]==".vmdk":
627 disk_format="vmdk"
628 elif image_dict['location'][-4:]==".vdi":
629 disk_format="vdi"
630 elif image_dict['location'][-4:]==".iso":
631 disk_format="iso"
632 elif image_dict['location'][-4:]==".aki":
633 disk_format="aki"
634 elif image_dict['location'][-4:]==".ari":
635 disk_format="ari"
636 elif image_dict['location'][-4:]==".ami":
637 disk_format="ami"
638 else:
639 disk_format="raw"
tiernoae4a8d12016-07-08 12:30:39 +0200640 self.logger.debug("new_image: '%s' loading from '%s'", image_dict['name'], image_dict['location'])
tierno7edb6752016-03-21 17:37:52 +0100641 if image_dict['location'][0:4]=="http":
kate721d79b2017-06-24 04:21:38 -0700642 new_image = self.glancev1.images.create(name=image_dict['name'], is_public=image_dict.get('public',"yes")=="yes",
tierno7edb6752016-03-21 17:37:52 +0100643 container_format="bare", location=image_dict['location'], disk_format=disk_format)
644 else: #local path
645 with open(image_dict['location']) as fimage:
kate721d79b2017-06-24 04:21:38 -0700646 new_image = self.glancev1.images.create(name=image_dict['name'], is_public=image_dict.get('public',"yes")=="yes",
tierno7edb6752016-03-21 17:37:52 +0100647 container_format="bare", data=fimage, disk_format=disk_format)
648 #insert metadata. We cannot use 'new_image.properties.setdefault'
649 #because nova and glance are "INDEPENDENT" and we are using nova for reading metadata
650 new_image_nova=self.nova.images.find(id=new_image.id)
651 new_image_nova.metadata.setdefault('location',image_dict['location'])
652 metadata_to_load = image_dict.get('metadata')
653 if metadata_to_load:
654 for k,v in yaml.load(metadata_to_load).iteritems():
655 new_image_nova.metadata.setdefault(k,v)
tiernoae4a8d12016-07-08 12:30:39 +0200656 return new_image.id
657 except (nvExceptions.Conflict, ksExceptions.ClientException, nvExceptions.ClientException) as e:
658 self._format_exception(e)
tierno8e995ce2016-09-22 08:13:00 +0000659 except (HTTPException, gl1Exceptions.HTTPException, gl1Exceptions.CommunicationError, ConnectionError) as e:
tiernoae4a8d12016-07-08 12:30:39 +0200660 if retry==max_retries:
661 continue
662 self._format_exception(e)
tierno7edb6752016-03-21 17:37:52 +0100663 except IOError as e: #can not open the file
tiernoae4a8d12016-07-08 12:30:39 +0200664 raise vimconn.vimconnConnectionException(type(e).__name__ + ": " + str(e)+ " for " + image_dict['location'],
665 http_code=vimconn.HTTP_Bad_Request)
tierno7edb6752016-03-21 17:37:52 +0100666
tiernoae4a8d12016-07-08 12:30:39 +0200667 def delete_image(self, image_id):
668 '''Deletes a tenant image from openstack VIM. Returns the old id
tierno7edb6752016-03-21 17:37:52 +0100669 '''
tiernoae4a8d12016-07-08 12:30:39 +0200670 try:
671 self._reload_connection()
672 self.nova.images.delete(image_id)
673 return image_id
tierno8e995ce2016-09-22 08:13:00 +0000674 except (nvExceptions.NotFound, ksExceptions.ClientException, nvExceptions.ClientException, gl1Exceptions.CommunicationError, ConnectionError) as e: #TODO remove
tiernoae4a8d12016-07-08 12:30:39 +0200675 self._format_exception(e)
676
677 def get_image_id_from_path(self, path):
garciadeblasb69fa9f2016-09-28 12:04:10 +0200678 '''Get the image id from image path in the VIM database. Returns the image_id'''
tiernoae4a8d12016-07-08 12:30:39 +0200679 try:
680 self._reload_connection()
681 images = self.nova.images.list()
682 for image in images:
683 if image.metadata.get("location")==path:
684 return image.id
685 raise vimconn.vimconnNotFoundException("image with location '{}' not found".format( path))
tierno8e995ce2016-09-22 08:13:00 +0000686 except (ksExceptions.ClientException, nvExceptions.ClientException, gl1Exceptions.CommunicationError, ConnectionError) as e:
tiernoae4a8d12016-07-08 12:30:39 +0200687 self._format_exception(e)
tierno7edb6752016-03-21 17:37:52 +0100688
garciadeblasb69fa9f2016-09-28 12:04:10 +0200689 def get_image_list(self, filter_dict={}):
690 '''Obtain tenant images from VIM
691 Filter_dict can be:
692 id: image id
693 name: image name
694 checksum: image checksum
695 Returns the image list of dictionaries:
696 [{<the fields at Filter_dict plus some VIM specific>}, ...]
697 List can be empty
698 '''
699 self.logger.debug("Getting image list from VIM filter: '%s'", str(filter_dict))
700 try:
701 self._reload_connection()
702 filter_dict_os=filter_dict.copy()
703 #First we filter by the available filter fields: name, id. The others are removed.
704 filter_dict_os.pop('checksum',None)
705 image_list=self.nova.images.findall(**filter_dict_os)
706 if len(image_list)==0:
707 return []
708 #Then we filter by the rest of filter fields: checksum
709 filtered_list = []
710 for image in image_list:
tierno4540ea52017-01-18 17:44:32 +0100711 image_class=self.glance.images.get(image.id)
712 if 'checksum' not in filter_dict or image_class['checksum']==filter_dict.get('checksum'):
713 filtered_list.append(image_class.copy())
garciadeblasb69fa9f2016-09-28 12:04:10 +0200714 return filtered_list
715 except (ksExceptions.ClientException, nvExceptions.ClientException, gl1Exceptions.CommunicationError, ConnectionError) as e:
716 self._format_exception(e)
717
kate721d79b2017-06-24 04:21:38 -0700718
montesmoreno0c8def02016-12-22 12:16:23 +0000719 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 +0100720 '''Adds a VM instance to VIM
721 Params:
722 start: indicates if VM must start or boot in pause mode. Ignored
723 image_id,flavor_id: iamge and flavor uuid
724 net_list: list of interfaces, each one is a dictionary with:
725 name:
726 net_id: network uuid to connect
727 vpci: virtual vcpi to assign, ignored because openstack lack #TODO
728 model: interface model, ignored #TODO
729 mac_address: used for SR-IOV ifaces #TODO for other types
730 use: 'data', 'bridge', 'mgmt'
731 type: 'virtual', 'PF', 'VF', 'VFnotShared'
732 vim_id: filled/added by this function
ahmadsaf853d452016-12-22 11:33:47 +0500733 floating_ip: True/False (or it can be None)
tierno7edb6752016-03-21 17:37:52 +0100734 #TODO ip, security groups
tiernoae4a8d12016-07-08 12:30:39 +0200735 Returns the instance identifier
tierno7edb6752016-03-21 17:37:52 +0100736 '''
tiernofa51c202017-01-27 14:58:17 +0100737 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 +0100738 try:
tierno6e116232016-07-18 13:01:40 +0200739 metadata={}
tierno7edb6752016-03-21 17:37:52 +0100740 net_list_vim=[]
ahmadsaf853d452016-12-22 11:33:47 +0500741 external_network=[] #list of external networks to be connected to instance, later on used to create floating_ip
tierno7edb6752016-03-21 17:37:52 +0100742 self._reload_connection()
kate721d79b2017-06-24 04:21:38 -0700743 metadata_vpci = {} #For a specific neutron plugin
tierno7edb6752016-03-21 17:37:52 +0100744 for net in net_list:
745 if not net.get("net_id"): #skip non connected iface
746 continue
Pablo Montes Moreno3be0b2a2017-03-30 13:22:15 +0200747
748 port_dict={
kate721d79b2017-06-24 04:21:38 -0700749 "network_id": net["net_id"],
750 "name": net.get("name"),
751 "admin_state_up": True
752 }
Pablo Montes Moreno3be0b2a2017-03-30 13:22:15 +0200753 if net["type"]=="virtual":
754 if "vpci" in net:
755 metadata_vpci[ net["net_id"] ] = [[ net["vpci"], "" ]]
756 elif net["type"]=="VF": # for VF
757 if "vpci" in net:
758 if "VF" not in metadata_vpci:
759 metadata_vpci["VF"]=[]
760 metadata_vpci["VF"].append([ net["vpci"], "" ])
761 port_dict["binding:vnic_type"]="direct"
kate721d79b2017-06-24 04:21:38 -0700762 ########## VIO specific Changes #######
763 if self.vim_type == "VIO":
764 #Need to create port with port_security_enabled = False and no-security-groups
765 port_dict["port_security_enabled"]=False
766 port_dict["provider_security_groups"]=[]
767 port_dict["security_groups"]=[]
Pablo Montes Moreno3be0b2a2017-03-30 13:22:15 +0200768 else: #For PT
kate721d79b2017-06-24 04:21:38 -0700769 ########## VIO specific Changes #######
770 #Current VIO release does not support port with type 'direct-physical'
771 #So no need to create virtual port in case of PCI-device.
772 #Will update port_dict code when support gets added in next VIO release
773 if self.vim_type == "VIO":
774 raise vimconn.vimconnNotSupportedException("Current VIO release does not support full passthrough (PT)")
Pablo Montes Moreno3be0b2a2017-03-30 13:22:15 +0200775 if "vpci" in net:
776 if "PF" not in metadata_vpci:
777 metadata_vpci["PF"]=[]
778 metadata_vpci["PF"].append([ net["vpci"], "" ])
779 port_dict["binding:vnic_type"]="direct-physical"
780 if not port_dict["name"]:
781 port_dict["name"]=name
782 if net.get("mac_address"):
783 port_dict["mac_address"]=net["mac_address"]
784 if net.get("port_security") == False:
785 port_dict["port_security_enabled"]=net["port_security"]
kate721d79b2017-06-24 04:21:38 -0700786
Pablo Montes Moreno3be0b2a2017-03-30 13:22:15 +0200787 new_port = self.neutron.create_port({"port": port_dict })
kate721d79b2017-06-24 04:21:38 -0700788
Pablo Montes Moreno3be0b2a2017-03-30 13:22:15 +0200789 net["mac_adress"] = new_port["port"]["mac_address"]
790 net["vim_id"] = new_port["port"]["id"]
791 net["ip"] = new_port["port"].get("fixed_ips", [{}])[0].get("ip_address")
792 net_list_vim.append({"port-id": new_port["port"]["id"]})
793
ahmadsaf853d452016-12-22 11:33:47 +0500794 if net.get('floating_ip', False):
tiernof8383b82017-01-18 15:49:48 +0100795 net['exit_on_floating_ip_error'] = True
ahmadsaf853d452016-12-22 11:33:47 +0500796 external_network.append(net)
tiernof8383b82017-01-18 15:49:48 +0100797 elif net['use'] == 'mgmt' and self.config.get('use_floating_ip'):
798 net['exit_on_floating_ip_error'] = False
799 external_network.append(net)
800
tierno7edb6752016-03-21 17:37:52 +0100801 if metadata_vpci:
802 metadata = {"pci_assignement": json.dumps(metadata_vpci)}
tiernoafbced42016-07-23 01:43:53 +0200803 if len(metadata["pci_assignement"]) >255:
tierno6e116232016-07-18 13:01:40 +0200804 #limit the metadata size
805 #metadata["pci_assignement"] = metadata["pci_assignement"][0:255]
806 self.logger.warn("Metadata deleted since it exceeds the expected length (255) ")
807 metadata = {}
kate721d79b2017-06-24 04:21:38 -0700808
tiernoae4a8d12016-07-08 12:30:39 +0200809 self.logger.debug("name '%s' image_id '%s'flavor_id '%s' net_list_vim '%s' description '%s' metadata %s",
810 name, image_id, flavor_id, str(net_list_vim), description, str(metadata))
kate721d79b2017-06-24 04:21:38 -0700811
tierno7edb6752016-03-21 17:37:52 +0100812 security_groups = self.config.get('security_groups')
813 if type(security_groups) is str:
814 security_groups = ( security_groups, )
tierno36c0b172017-01-12 18:32:28 +0100815 #cloud config
816 userdata=None
817 config_drive = None
tiernoa4e1a6e2016-08-31 14:19:40 +0200818 if isinstance(cloud_config, dict):
tierno36c0b172017-01-12 18:32:28 +0100819 if cloud_config.get("user-data"):
820 userdata=cloud_config["user-data"]
821 if cloud_config.get("boot-data-drive") != None:
822 config_drive = cloud_config["boot-data-drive"]
823 if cloud_config.get("config-files") or cloud_config.get("users") or cloud_config.get("key-pairs"):
824 if userdata:
825 raise vimconn.vimconnConflictException("Cloud-config cannot contain both 'userdata' and 'config-files'/'users'/'key-pairs'")
826 userdata_dict={}
827 #default user
828 if cloud_config.get("key-pairs"):
829 userdata_dict["ssh-authorized-keys"] = cloud_config["key-pairs"]
830 userdata_dict["users"] = [{"default": None, "ssh-authorized-keys": cloud_config["key-pairs"] }]
831 if cloud_config.get("users"):
tierno01d0bf52017-01-25 14:27:20 +0100832 if "users" not in userdata_dict:
tierno36c0b172017-01-12 18:32:28 +0100833 userdata_dict["users"] = [ "default" ]
834 for user in cloud_config["users"]:
835 user_info = {
836 "name" : user["name"],
837 "sudo": "ALL = (ALL)NOPASSWD:ALL"
838 }
839 if "user-info" in user:
840 user_info["gecos"] = user["user-info"]
841 if user.get("key-pairs"):
842 user_info["ssh-authorized-keys"] = user["key-pairs"]
843 userdata_dict["users"].append(user_info)
844
845 if cloud_config.get("config-files"):
846 userdata_dict["write_files"] = []
847 for file in cloud_config["config-files"]:
848 file_info = {
849 "path" : file["dest"],
850 "content": file["content"]
851 }
852 if file.get("encoding"):
853 file_info["encoding"] = file["encoding"]
854 if file.get("permissions"):
855 file_info["permissions"] = file["permissions"]
856 if file.get("owner"):
857 file_info["owner"] = file["owner"]
858 userdata_dict["write_files"].append(file_info)
859 userdata = "#cloud-config\n"
860 userdata += yaml.safe_dump(userdata_dict, indent=4, default_flow_style=False)
tiernoa4e1a6e2016-08-31 14:19:40 +0200861 self.logger.debug("userdata: %s", userdata)
862 elif isinstance(cloud_config, str):
863 userdata = cloud_config
montesmoreno0c8def02016-12-22 12:16:23 +0000864
865 #Create additional volumes in case these are present in disk_list
866 block_device_mapping = None
867 base_disk_index = ord('b')
868 if disk_list != None:
869 block_device_mapping = dict()
870 for disk in disk_list:
871 if 'image_id' in disk:
872 volume = self.cinder.volumes.create(size = disk['size'],name = name + '_vd' +
873 chr(base_disk_index), imageRef = disk['image_id'])
874 else:
875 volume = self.cinder.volumes.create(size=disk['size'], name=name + '_vd' +
876 chr(base_disk_index))
877 block_device_mapping['_vd' + chr(base_disk_index)] = volume.id
878 base_disk_index += 1
879
880 #wait until volumes are with status available
881 keep_waiting = True
882 elapsed_time = 0
883 while keep_waiting and elapsed_time < volume_timeout:
884 keep_waiting = False
885 for volume_id in block_device_mapping.itervalues():
886 if self.cinder.volumes.get(volume_id).status != 'available':
887 keep_waiting = True
888 if keep_waiting:
889 time.sleep(1)
890 elapsed_time += 1
891
892 #if we exceeded the timeout rollback
893 if elapsed_time >= volume_timeout:
894 #delete the volumes we just created
895 for volume_id in block_device_mapping.itervalues():
896 self.cinder.volumes.delete(volume_id)
897
898 #delete ports we just created
899 for net_item in net_list_vim:
900 if 'port-id' in net_item:
montesmorenocf227142017-01-12 12:24:21 +0000901 self.neutron.delete_port(net_item['port-id'])
montesmoreno0c8def02016-12-22 12:16:23 +0000902
903 raise vimconn.vimconnException('Timeout creating volumes for instance ' + name,
904 http_code=vimconn.HTTP_Request_Timeout)
905
tierno7edb6752016-03-21 17:37:52 +0100906 server = self.nova.servers.create(name, image_id, flavor_id, nics=net_list_vim, meta=metadata,
montesmoreno0c8def02016-12-22 12:16:23 +0000907 security_groups=security_groups,
908 availability_zone=self.config.get('availability_zone'),
909 key_name=self.config.get('keypair'),
910 userdata=userdata,
tierno36c0b172017-01-12 18:32:28 +0100911 config_drive = config_drive,
montesmoreno0c8def02016-12-22 12:16:23 +0000912 block_device_mapping = block_device_mapping
913 ) # , description=description)
tiernoae4a8d12016-07-08 12:30:39 +0200914 #print "DONE :-)", server
ahmadsaf853d452016-12-22 11:33:47 +0500915 pool_id = None
916 floating_ips = self.neutron.list_floatingips().get("floatingips", ())
917 for floating_network in external_network:
tiernof8383b82017-01-18 15:49:48 +0100918 try:
919 # wait until vm is active
920 elapsed_time = 0
921 while elapsed_time < server_timeout:
922 status = self.nova.servers.get(server.id).status
923 if status == 'ACTIVE':
924 break
925 time.sleep(1)
926 elapsed_time += 1
montesmoreno2a1fc4e2017-01-09 16:46:04 +0000927
tiernof8383b82017-01-18 15:49:48 +0100928 #if we exceeded the timeout rollback
929 if elapsed_time >= server_timeout:
930 raise vimconn.vimconnException('Timeout creating instance ' + name,
931 http_code=vimconn.HTTP_Request_Timeout)
montesmoreno2a1fc4e2017-01-09 16:46:04 +0000932
tiernof8383b82017-01-18 15:49:48 +0100933 assigned = False
934 while(assigned == False):
935 if floating_ips:
936 ip = floating_ips.pop(0)
937 if not ip.get("port_id", False) and ip.get('tenant_id') == server.tenant_id:
938 free_floating_ip = ip.get("floating_ip_address")
939 try:
940 fix_ip = floating_network.get('ip')
941 server.add_floating_ip(free_floating_ip, fix_ip)
942 assigned = True
943 except Exception as e:
944 raise vimconn.vimconnException(type(e).__name__ + ": Cannot create floating_ip "+ str(e), http_code=vimconn.HTTP_Conflict)
945 else:
946 #Find the external network
947 external_nets = list()
948 for net in self.neutron.list_networks()['networks']:
949 if net['router:external']:
950 external_nets.append(net)
951
952 if len(external_nets) == 0:
953 raise vimconn.vimconnException("Cannot create floating_ip automatically since no external "
954 "network is present",
955 http_code=vimconn.HTTP_Conflict)
956 if len(external_nets) > 1:
957 raise vimconn.vimconnException("Cannot create floating_ip automatically since multiple "
958 "external networks are present",
959 http_code=vimconn.HTTP_Conflict)
960
961 pool_id = external_nets[0].get('id')
962 param = {'floatingip': {'floating_network_id': pool_id, 'tenant_id': server.tenant_id}}
ahmadsaf853d452016-12-22 11:33:47 +0500963 try:
tiernof8383b82017-01-18 15:49:48 +0100964 #self.logger.debug("Creating floating IP")
965 new_floating_ip = self.neutron.create_floatingip(param)
966 free_floating_ip = new_floating_ip['floatingip']['floating_ip_address']
ahmadsaf853d452016-12-22 11:33:47 +0500967 fix_ip = floating_network.get('ip')
968 server.add_floating_ip(free_floating_ip, fix_ip)
tiernof8383b82017-01-18 15:49:48 +0100969 assigned=True
ahmadsaf853d452016-12-22 11:33:47 +0500970 except Exception as e:
tiernof8383b82017-01-18 15:49:48 +0100971 raise vimconn.vimconnException(type(e).__name__ + ": Cannot assign floating_ip "+ str(e), http_code=vimconn.HTTP_Conflict)
972 except Exception as e:
973 if not floating_network['exit_on_floating_ip_error']:
974 self.logger.warn("Cannot create floating_ip. %s", str(e))
975 continue
976 self.delete_vminstance(server.id)
977 raise
montesmoreno2a1fc4e2017-01-09 16:46:04 +0000978
tiernoae4a8d12016-07-08 12:30:39 +0200979 return server.id
tierno7edb6752016-03-21 17:37:52 +0100980# except nvExceptions.NotFound as e:
981# error_value=-vimconn.HTTP_Not_Found
982# error_text= "vm instance %s not found" % vm_id
tiernof8383b82017-01-18 15:49:48 +0100983 except (ksExceptions.ClientException, nvExceptions.ClientException, ConnectionError) as e:
montesmoreno2a1fc4e2017-01-09 16:46:04 +0000984 # delete the volumes we just created
985 if block_device_mapping != None:
986 for volume_id in block_device_mapping.itervalues():
987 self.cinder.volumes.delete(volume_id)
988
989 # delete ports we just created
990 for net_item in net_list_vim:
991 if 'port-id' in net_item:
montesmorenocf227142017-01-12 12:24:21 +0000992 self.neutron.delete_port(net_item['port-id'])
tiernoae4a8d12016-07-08 12:30:39 +0200993 self._format_exception(e)
994 except TypeError as e:
995 raise vimconn.vimconnException(type(e).__name__ + ": "+ str(e), http_code=vimconn.HTTP_Bad_Request)
tierno7edb6752016-03-21 17:37:52 +0100996
tiernoae4a8d12016-07-08 12:30:39 +0200997 def get_vminstance(self,vm_id):
tierno7edb6752016-03-21 17:37:52 +0100998 '''Returns the VM instance information from VIM'''
tiernoae4a8d12016-07-08 12:30:39 +0200999 #self.logger.debug("Getting VM from VIM")
tierno7edb6752016-03-21 17:37:52 +01001000 try:
1001 self._reload_connection()
1002 server = self.nova.servers.find(id=vm_id)
1003 #TODO parse input and translate to VIM format (openmano_schemas.new_vminstance_response_schema)
tiernoae4a8d12016-07-08 12:30:39 +02001004 return server.to_dict()
tierno8e995ce2016-09-22 08:13:00 +00001005 except (ksExceptions.ClientException, nvExceptions.ClientException, nvExceptions.NotFound, ConnectionError) as e:
tiernoae4a8d12016-07-08 12:30:39 +02001006 self._format_exception(e)
1007
1008 def get_vminstance_console(self,vm_id, console_type="vnc"):
tierno7edb6752016-03-21 17:37:52 +01001009 '''
1010 Get a console for the virtual machine
1011 Params:
1012 vm_id: uuid of the VM
1013 console_type, can be:
1014 "novnc" (by default), "xvpvnc" for VNC types,
1015 "rdp-html5" for RDP types, "spice-html5" for SPICE types
tiernoae4a8d12016-07-08 12:30:39 +02001016 Returns dict with the console parameters:
1017 protocol: ssh, ftp, http, https, ...
1018 server: usually ip address
1019 port: the http, ssh, ... port
1020 suffix: extra text, e.g. the http path and query string
tierno7edb6752016-03-21 17:37:52 +01001021 '''
tiernoae4a8d12016-07-08 12:30:39 +02001022 self.logger.debug("Getting VM CONSOLE from VIM")
tierno7edb6752016-03-21 17:37:52 +01001023 try:
1024 self._reload_connection()
1025 server = self.nova.servers.find(id=vm_id)
1026 if console_type == None or console_type == "novnc":
1027 console_dict = server.get_vnc_console("novnc")
1028 elif console_type == "xvpvnc":
1029 console_dict = server.get_vnc_console(console_type)
1030 elif console_type == "rdp-html5":
1031 console_dict = server.get_rdp_console(console_type)
1032 elif console_type == "spice-html5":
1033 console_dict = server.get_spice_console(console_type)
1034 else:
tiernoae4a8d12016-07-08 12:30:39 +02001035 raise vimconn.vimconnException("console type '{}' not allowed".format(console_type), http_code=vimconn.HTTP_Bad_Request)
tierno7edb6752016-03-21 17:37:52 +01001036
1037 console_dict1 = console_dict.get("console")
1038 if console_dict1:
1039 console_url = console_dict1.get("url")
1040 if console_url:
1041 #parse console_url
1042 protocol_index = console_url.find("//")
1043 suffix_index = console_url[protocol_index+2:].find("/") + protocol_index+2
1044 port_index = console_url[protocol_index+2:suffix_index].find(":") + protocol_index+2
1045 if protocol_index < 0 or port_index<0 or suffix_index<0:
1046 return -vimconn.HTTP_Internal_Server_Error, "Unexpected response from VIM"
1047 console_dict={"protocol": console_url[0:protocol_index],
1048 "server": console_url[protocol_index+2:port_index],
1049 "port": console_url[port_index:suffix_index],
1050 "suffix": console_url[suffix_index+1:]
1051 }
1052 protocol_index += 2
tiernoae4a8d12016-07-08 12:30:39 +02001053 return console_dict
1054 raise vimconn.vimconnUnexpectedResponse("Unexpected response from VIM")
tierno7edb6752016-03-21 17:37:52 +01001055
tierno8e995ce2016-09-22 08:13:00 +00001056 except (nvExceptions.NotFound, ksExceptions.ClientException, nvExceptions.ClientException, nvExceptions.BadRequest, ConnectionError) as e:
tiernoae4a8d12016-07-08 12:30:39 +02001057 self._format_exception(e)
tierno7edb6752016-03-21 17:37:52 +01001058
tiernoae4a8d12016-07-08 12:30:39 +02001059 def delete_vminstance(self, vm_id):
1060 '''Removes a VM instance from VIM. Returns the old identifier
tierno7edb6752016-03-21 17:37:52 +01001061 '''
tiernoae4a8d12016-07-08 12:30:39 +02001062 #print "osconnector: Getting VM from VIM"
tierno7edb6752016-03-21 17:37:52 +01001063 try:
1064 self._reload_connection()
1065 #delete VM ports attached to this networks before the virtual machine
1066 ports = self.neutron.list_ports(device_id=vm_id)
1067 for p in ports['ports']:
1068 try:
1069 self.neutron.delete_port(p["id"])
1070 except Exception as e:
tiernoae4a8d12016-07-08 12:30:39 +02001071 self.logger.error("Error deleting port: " + type(e).__name__ + ": "+ str(e))
montesmoreno0c8def02016-12-22 12:16:23 +00001072
1073 #commented because detaching the volumes makes the servers.delete not work properly ?!?
1074 #dettach volumes attached
1075 server = self.nova.servers.get(vm_id)
1076 volumes_attached_dict = server._info['os-extended-volumes:volumes_attached']
1077 #for volume in volumes_attached_dict:
1078 # self.cinder.volumes.detach(volume['id'])
1079
tierno7edb6752016-03-21 17:37:52 +01001080 self.nova.servers.delete(vm_id)
montesmoreno0c8def02016-12-22 12:16:23 +00001081
1082 #delete volumes.
1083 #Although having detached them should have them in active status
1084 #we ensure in this loop
1085 keep_waiting = True
1086 elapsed_time = 0
1087 while keep_waiting and elapsed_time < volume_timeout:
1088 keep_waiting = False
1089 for volume in volumes_attached_dict:
1090 if self.cinder.volumes.get(volume['id']).status != 'available':
1091 keep_waiting = True
1092 else:
1093 self.cinder.volumes.delete(volume['id'])
1094 if keep_waiting:
1095 time.sleep(1)
1096 elapsed_time += 1
1097
tiernoae4a8d12016-07-08 12:30:39 +02001098 return vm_id
tierno8e995ce2016-09-22 08:13:00 +00001099 except (nvExceptions.NotFound, ksExceptions.ClientException, nvExceptions.ClientException, ConnectionError) as e:
tiernoae4a8d12016-07-08 12:30:39 +02001100 self._format_exception(e)
tierno7edb6752016-03-21 17:37:52 +01001101 #TODO insert exception vimconn.HTTP_Unauthorized
1102 #if reaching here is because an exception
tierno7edb6752016-03-21 17:37:52 +01001103
tiernoae4a8d12016-07-08 12:30:39 +02001104 def refresh_vms_status(self, vm_list):
1105 '''Get the status of the virtual machines and their interfaces/ports
1106 Params: the list of VM identifiers
1107 Returns a dictionary with:
1108 vm_id: #VIM id of this Virtual Machine
1109 status: #Mandatory. Text with one of:
1110 # DELETED (not found at vim)
1111 # VIM_ERROR (Cannot connect to VIM, VIM response error, ...)
1112 # OTHER (Vim reported other status not understood)
1113 # ERROR (VIM indicates an ERROR status)
1114 # ACTIVE, PAUSED, SUSPENDED, INACTIVE (not running),
1115 # CREATING (on building process), ERROR
1116 # ACTIVE:NoMgmtIP (Active but any of its interface has an IP address
1117 #
1118 error_msg: #Text with VIM error message, if any. Or the VIM connection ERROR
1119 vim_info: #Text with plain information obtained from vim (yaml.safe_dump)
1120 interfaces:
1121 - vim_info: #Text with plain information obtained from vim (yaml.safe_dump)
1122 mac_address: #Text format XX:XX:XX:XX:XX:XX
1123 vim_net_id: #network id where this interface is connected
1124 vim_interface_id: #interface/port VIM id
1125 ip_address: #null, or text with IPv4, IPv6 address
tierno867ffe92017-03-27 12:50:34 +02001126 compute_node: #identification of compute node where PF,VF interface is allocated
1127 pci: #PCI address of the NIC that hosts the PF,VF
1128 vlan: #physical VLAN used for VF
tierno7edb6752016-03-21 17:37:52 +01001129 '''
tiernoae4a8d12016-07-08 12:30:39 +02001130 vm_dict={}
1131 self.logger.debug("refresh_vms status: Getting tenant VM instance information from VIM")
1132 for vm_id in vm_list:
1133 vm={}
1134 try:
1135 vm_vim = self.get_vminstance(vm_id)
1136 if vm_vim['status'] in vmStatus2manoFormat:
1137 vm['status'] = vmStatus2manoFormat[ vm_vim['status'] ]
tierno7edb6752016-03-21 17:37:52 +01001138 else:
tiernoae4a8d12016-07-08 12:30:39 +02001139 vm['status'] = "OTHER"
1140 vm['error_msg'] = "VIM status reported " + vm_vim['status']
tierno8e995ce2016-09-22 08:13:00 +00001141 try:
1142 vm['vim_info'] = yaml.safe_dump(vm_vim, default_flow_style=True, width=256)
1143 except yaml.representer.RepresenterError:
1144 vm['vim_info'] = str(vm_vim)
tiernoae4a8d12016-07-08 12:30:39 +02001145 vm["interfaces"] = []
1146 if vm_vim.get('fault'):
1147 vm['error_msg'] = str(vm_vim['fault'])
1148 #get interfaces
tierno7edb6752016-03-21 17:37:52 +01001149 try:
tiernoae4a8d12016-07-08 12:30:39 +02001150 self._reload_connection()
1151 port_dict=self.neutron.list_ports(device_id=vm_id)
1152 for port in port_dict["ports"]:
1153 interface={}
tierno8e995ce2016-09-22 08:13:00 +00001154 try:
1155 interface['vim_info'] = yaml.safe_dump(port, default_flow_style=True, width=256)
1156 except yaml.representer.RepresenterError:
1157 interface['vim_info'] = str(port)
tiernoae4a8d12016-07-08 12:30:39 +02001158 interface["mac_address"] = port.get("mac_address")
1159 interface["vim_net_id"] = port["network_id"]
1160 interface["vim_interface_id"] = port["id"]
Mike Marchetti5b9da422017-05-02 15:35:47 -04001161 # check if OS-EXT-SRV-ATTR:host is there,
1162 # in case of non-admin credentials, it will be missing
1163 if vm_vim.get('OS-EXT-SRV-ATTR:host'):
1164 interface["compute_node"] = vm_vim['OS-EXT-SRV-ATTR:host']
tierno867ffe92017-03-27 12:50:34 +02001165 interface["pci"] = None
Mike Marchetti5b9da422017-05-02 15:35:47 -04001166
1167 # check if binding:profile is there,
1168 # in case of non-admin credentials, it will be missing
1169 if port.get('binding:profile'):
1170 if port['binding:profile'].get('pci_slot'):
1171 # TODO: At the moment sr-iov pci addresses are converted to PF pci addresses by setting the slot to 0x00
1172 # TODO: This is just a workaround valid for niantinc. Find a better way to do so
1173 # CHANGE DDDD:BB:SS.F to DDDD:BB:00.(F%2) assuming there are 2 ports per nic
1174 pci = port['binding:profile']['pci_slot']
1175 # interface["pci"] = pci[:-4] + "00." + str(int(pci[-1]) % 2)
1176 interface["pci"] = pci
tierno867ffe92017-03-27 12:50:34 +02001177 interface["vlan"] = None
Pablo Montes Moreno3be0b2a2017-03-30 13:22:15 +02001178 #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 +01001179 network = self.neutron.show_network(port["network_id"])
Pablo Montes Moreno3be0b2a2017-03-30 13:22:15 +02001180 if network['network'].get('provider:network_type') == 'vlan' and \
1181 port.get("binding:vnic_type") == "direct":
tierno867ffe92017-03-27 12:50:34 +02001182 interface["vlan"] = network['network'].get('provider:segmentation_id')
tiernoae4a8d12016-07-08 12:30:39 +02001183 ips=[]
1184 #look for floating ip address
1185 floating_ip_dict = self.neutron.list_floatingips(port_id=port["id"])
1186 if floating_ip_dict.get("floatingips"):
1187 ips.append(floating_ip_dict["floatingips"][0].get("floating_ip_address") )
tierno7edb6752016-03-21 17:37:52 +01001188
tiernoae4a8d12016-07-08 12:30:39 +02001189 for subnet in port["fixed_ips"]:
1190 ips.append(subnet["ip_address"])
1191 interface["ip_address"] = ";".join(ips)
1192 vm["interfaces"].append(interface)
1193 except Exception as e:
1194 self.logger.error("Error getting vm interface information " + type(e).__name__ + ": "+ str(e))
1195 except vimconn.vimconnNotFoundException as e:
1196 self.logger.error("Exception getting vm status: %s", str(e))
1197 vm['status'] = "DELETED"
1198 vm['error_msg'] = str(e)
1199 except vimconn.vimconnException as e:
1200 self.logger.error("Exception getting vm status: %s", str(e))
1201 vm['status'] = "VIM_ERROR"
1202 vm['error_msg'] = str(e)
1203 vm_dict[vm_id] = vm
1204 return vm_dict
tierno7edb6752016-03-21 17:37:52 +01001205
tiernoae4a8d12016-07-08 12:30:39 +02001206 def action_vminstance(self, vm_id, action_dict):
tierno7edb6752016-03-21 17:37:52 +01001207 '''Send and action over a VM instance from VIM
tiernoae4a8d12016-07-08 12:30:39 +02001208 Returns the vm_id if the action was successfully sent to the VIM'''
1209 self.logger.debug("Action over VM '%s': %s", vm_id, str(action_dict))
tierno7edb6752016-03-21 17:37:52 +01001210 try:
1211 self._reload_connection()
1212 server = self.nova.servers.find(id=vm_id)
1213 if "start" in action_dict:
1214 if action_dict["start"]=="rebuild":
1215 server.rebuild()
1216 else:
1217 if server.status=="PAUSED":
1218 server.unpause()
1219 elif server.status=="SUSPENDED":
1220 server.resume()
1221 elif server.status=="SHUTOFF":
1222 server.start()
1223 elif "pause" in action_dict:
1224 server.pause()
1225 elif "resume" in action_dict:
1226 server.resume()
1227 elif "shutoff" in action_dict or "shutdown" in action_dict:
1228 server.stop()
1229 elif "forceOff" in action_dict:
1230 server.stop() #TODO
1231 elif "terminate" in action_dict:
1232 server.delete()
1233 elif "createImage" in action_dict:
1234 server.create_image()
1235 #"path":path_schema,
1236 #"description":description_schema,
1237 #"name":name_schema,
1238 #"metadata":metadata_schema,
1239 #"imageRef": id_schema,
1240 #"disk": {"oneOf":[{"type": "null"}, {"type":"string"}] },
1241 elif "rebuild" in action_dict:
1242 server.rebuild(server.image['id'])
1243 elif "reboot" in action_dict:
1244 server.reboot() #reboot_type='SOFT'
1245 elif "console" in action_dict:
1246 console_type = action_dict["console"]
1247 if console_type == None or console_type == "novnc":
1248 console_dict = server.get_vnc_console("novnc")
1249 elif console_type == "xvpvnc":
1250 console_dict = server.get_vnc_console(console_type)
1251 elif console_type == "rdp-html5":
1252 console_dict = server.get_rdp_console(console_type)
1253 elif console_type == "spice-html5":
1254 console_dict = server.get_spice_console(console_type)
1255 else:
tiernoae4a8d12016-07-08 12:30:39 +02001256 raise vimconn.vimconnException("console type '{}' not allowed".format(console_type),
1257 http_code=vimconn.HTTP_Bad_Request)
tierno7edb6752016-03-21 17:37:52 +01001258 try:
1259 console_url = console_dict["console"]["url"]
1260 #parse console_url
1261 protocol_index = console_url.find("//")
1262 suffix_index = console_url[protocol_index+2:].find("/") + protocol_index+2
1263 port_index = console_url[protocol_index+2:suffix_index].find(":") + protocol_index+2
1264 if protocol_index < 0 or port_index<0 or suffix_index<0:
tiernoae4a8d12016-07-08 12:30:39 +02001265 raise vimconn.vimconnException("Unexpected response from VIM " + str(console_dict))
tierno7edb6752016-03-21 17:37:52 +01001266 console_dict2={"protocol": console_url[0:protocol_index],
1267 "server": console_url[protocol_index+2 : port_index],
1268 "port": int(console_url[port_index+1 : suffix_index]),
1269 "suffix": console_url[suffix_index+1:]
1270 }
tiernoae4a8d12016-07-08 12:30:39 +02001271 return console_dict2
1272 except Exception as e:
1273 raise vimconn.vimconnException("Unexpected response from VIM " + str(console_dict))
tierno7edb6752016-03-21 17:37:52 +01001274
tiernoae4a8d12016-07-08 12:30:39 +02001275 return vm_id
tierno8e995ce2016-09-22 08:13:00 +00001276 except (ksExceptions.ClientException, nvExceptions.ClientException, nvExceptions.NotFound, ConnectionError) as e:
tiernoae4a8d12016-07-08 12:30:39 +02001277 self._format_exception(e)
1278 #TODO insert exception vimconn.HTTP_Unauthorized
1279
kate721d79b2017-06-24 04:21:38 -07001280 ####### VIO Specific Changes #########
1281 def _genrate_vlanID(self):
1282 """
1283 Method to get unused vlanID
1284 Args:
1285 None
1286 Returns:
1287 vlanID
1288 """
1289 #Get used VLAN IDs
1290 usedVlanIDs = []
1291 networks = self.get_network_list()
1292 for net in networks:
1293 if net.get('provider:segmentation_id'):
1294 usedVlanIDs.append(net.get('provider:segmentation_id'))
1295 used_vlanIDs = set(usedVlanIDs)
1296
1297 #find unused VLAN ID
1298 for vlanID_range in self.config.get('dataplane_net_vlan_range'):
1299 try:
1300 start_vlanid , end_vlanid = map(int, vlanID_range.replace(" ", "").split("-"))
1301 for vlanID in xrange(start_vlanid, end_vlanid + 1):
1302 if vlanID not in used_vlanIDs:
1303 return vlanID
1304 except Exception as exp:
1305 raise vimconn.vimconnException("Exception {} occurred while generating VLAN ID.".format(exp))
1306 else:
1307 raise vimconn.vimconnConflictException("Unable to create the SRIOV VLAN network."\
1308 " All given Vlan IDs {} are in use.".format(self.config.get('dataplane_net_vlan_range')))
1309
1310
1311 def _validate_vlan_ranges(self, dataplane_net_vlan_range):
1312 """
1313 Method to validate user given vlanID ranges
1314 Args: None
1315 Returns: None
1316 """
1317 for vlanID_range in dataplane_net_vlan_range:
1318 vlan_range = vlanID_range.replace(" ", "")
1319 #validate format
1320 vlanID_pattern = r'(\d)*-(\d)*$'
1321 match_obj = re.match(vlanID_pattern, vlan_range)
1322 if not match_obj:
1323 raise vimconn.vimconnConflictException("Invalid dataplane_net_vlan_range {}.You must provide "\
1324 "'dataplane_net_vlan_range' in format [start_ID - end_ID].".format(vlanID_range))
1325
1326 start_vlanid , end_vlanid = map(int,vlan_range.split("-"))
1327 if start_vlanid <= 0 :
1328 raise vimconn.vimconnConflictException("Invalid dataplane_net_vlan_range {}."\
1329 "Start ID can not be zero. For VLAN "\
1330 "networks valid IDs are 1 to 4094 ".format(vlanID_range))
1331 if end_vlanid > 4094 :
1332 raise vimconn.vimconnConflictException("Invalid dataplane_net_vlan_range {}."\
1333 "End VLAN ID can not be greater than 4094. For VLAN "\
1334 "networks valid IDs are 1 to 4094 ".format(vlanID_range))
1335
1336 if start_vlanid > end_vlanid:
1337 raise vimconn.vimconnConflictException("Invalid dataplane_net_vlan_range {}."\
1338 "You must provide a 'dataplane_net_vlan_range' in format start_ID - end_ID and "\
1339 "start_ID < end_ID ".format(vlanID_range))
1340
tiernoae4a8d12016-07-08 12:30:39 +02001341#NOT USED FUNCTIONS
1342
1343 def new_external_port(self, port_data):
1344 #TODO openstack if needed
1345 '''Adds a external port to VIM'''
1346 '''Returns the port identifier'''
1347 return -vimconn.HTTP_Internal_Server_Error, "osconnector.new_external_port() not implemented"
1348
1349 def connect_port_network(self, port_id, network_id, admin=False):
1350 #TODO openstack if needed
1351 '''Connects a external port to a network'''
1352 '''Returns status code of the VIM response'''
1353 return -vimconn.HTTP_Internal_Server_Error, "osconnector.connect_port_network() not implemented"
1354
1355 def new_user(self, user_name, user_passwd, tenant_id=None):
1356 '''Adds a new user to openstack VIM'''
1357 '''Returns the user identifier'''
1358 self.logger.debug("osconnector: Adding a new user to VIM")
1359 try:
1360 self._reload_connection()
1361 user=self.keystone.users.create(user_name, user_passwd, tenant_id=tenant_id)
1362 #self.keystone.tenants.add_user(self.k_creds["username"], #role)
1363 return user.id
1364 except ksExceptions.ConnectionError as e:
1365 error_value=-vimconn.HTTP_Bad_Request
1366 error_text= type(e).__name__ + ": "+ (str(e) if len(e.args)==0 else str(e.args[0]))
1367 except ksExceptions.ClientException as e: #TODO remove
tierno7edb6752016-03-21 17:37:52 +01001368 error_value=-vimconn.HTTP_Bad_Request
1369 error_text= type(e).__name__ + ": "+ (str(e) if len(e.args)==0 else str(e.args[0]))
1370 #TODO insert exception vimconn.HTTP_Unauthorized
1371 #if reaching here is because an exception
1372 if self.debug:
tiernoae4a8d12016-07-08 12:30:39 +02001373 self.logger.debug("new_user " + error_text)
tierno7edb6752016-03-21 17:37:52 +01001374 return error_value, error_text
tiernoae4a8d12016-07-08 12:30:39 +02001375
1376 def delete_user(self, user_id):
1377 '''Delete a user from openstack VIM'''
1378 '''Returns the user identifier'''
1379 if self.debug:
1380 print "osconnector: Deleting a user from VIM"
1381 try:
1382 self._reload_connection()
1383 self.keystone.users.delete(user_id)
1384 return 1, user_id
1385 except ksExceptions.ConnectionError as e:
1386 error_value=-vimconn.HTTP_Bad_Request
1387 error_text= type(e).__name__ + ": "+ (str(e) if len(e.args)==0 else str(e.args[0]))
1388 except ksExceptions.NotFound as e:
1389 error_value=-vimconn.HTTP_Not_Found
1390 error_text= type(e).__name__ + ": "+ (str(e) if len(e.args)==0 else str(e.args[0]))
1391 except ksExceptions.ClientException as e: #TODO remove
1392 error_value=-vimconn.HTTP_Bad_Request
1393 error_text= type(e).__name__ + ": "+ (str(e) if len(e.args)==0 else str(e.args[0]))
1394 #TODO insert exception vimconn.HTTP_Unauthorized
1395 #if reaching here is because an exception
1396 if self.debug:
1397 print "delete_tenant " + error_text
1398 return error_value, error_text
1399
tierno7edb6752016-03-21 17:37:52 +01001400 def get_hosts_info(self):
1401 '''Get the information of deployed hosts
1402 Returns the hosts content'''
1403 if self.debug:
1404 print "osconnector: Getting Host info from VIM"
1405 try:
1406 h_list=[]
1407 self._reload_connection()
1408 hypervisors = self.nova.hypervisors.list()
1409 for hype in hypervisors:
1410 h_list.append( hype.to_dict() )
1411 return 1, {"hosts":h_list}
1412 except nvExceptions.NotFound as e:
1413 error_value=-vimconn.HTTP_Not_Found
1414 error_text= (str(e) if len(e.args)==0 else str(e.args[0]))
1415 except (ksExceptions.ClientException, nvExceptions.ClientException) as e:
1416 error_value=-vimconn.HTTP_Bad_Request
1417 error_text= type(e).__name__ + ": "+ (str(e) if len(e.args)==0 else str(e.args[0]))
1418 #TODO insert exception vimconn.HTTP_Unauthorized
1419 #if reaching here is because an exception
1420 if self.debug:
1421 print "get_hosts_info " + error_text
1422 return error_value, error_text
1423
1424 def get_hosts(self, vim_tenant):
1425 '''Get the hosts and deployed instances
1426 Returns the hosts content'''
1427 r, hype_dict = self.get_hosts_info()
1428 if r<0:
1429 return r, hype_dict
1430 hypervisors = hype_dict["hosts"]
1431 try:
1432 servers = self.nova.servers.list()
1433 for hype in hypervisors:
1434 for server in servers:
1435 if server.to_dict()['OS-EXT-SRV-ATTR:hypervisor_hostname']==hype['hypervisor_hostname']:
1436 if 'vm' in hype:
1437 hype['vm'].append(server.id)
1438 else:
1439 hype['vm'] = [server.id]
1440 return 1, hype_dict
1441 except nvExceptions.NotFound as e:
1442 error_value=-vimconn.HTTP_Not_Found
1443 error_text= (str(e) if len(e.args)==0 else str(e.args[0]))
1444 except (ksExceptions.ClientException, nvExceptions.ClientException) as e:
1445 error_value=-vimconn.HTTP_Bad_Request
1446 error_text= type(e).__name__ + ": "+ (str(e) if len(e.args)==0 else str(e.args[0]))
1447 #TODO insert exception vimconn.HTTP_Unauthorized
1448 #if reaching here is because an exception
1449 if self.debug:
1450 print "get_hosts " + error_text
1451 return error_value, error_text
tierno7edb6752016-03-21 17:37:52 +01001452