blob: 6df830dca4be3bd87361b083b83b3f6c1131feb1 [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'''
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +000025osconnector implements all the methods to interact with openstack using the python-neutronclient.
26
27For the VNF forwarding graph, The OpenStack VIM connector calls the
28networking-sfc Neutron extension methods, whose resources are mapped
29to the VIM connector's SFC resources as follows:
30- Classification (OSM) -> Flow Classifier (Neutron)
31- Service Function Instance (OSM) -> Port Pair (Neutron)
32- Service Function (OSM) -> Port Pair Group (Neutron)
33- Service Function Path (OSM) -> Port Chain (Neutron)
tierno7edb6752016-03-21 17:37:52 +010034'''
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +000035__author__ = "Alfonso Tierno, Gerardo Garcia, Pablo Montes, xFlow Research, Igor D.C."
36__date__ = "$22-sep-2017 23:59:59$"
tierno7edb6752016-03-21 17:37:52 +010037
38import vimconn
39import json
tiernoae4a8d12016-07-08 12:30:39 +020040import logging
garciadeblas9f8456e2016-09-05 05:02:59 +020041import netaddr
montesmoreno0c8def02016-12-22 12:16:23 +000042import time
tierno36c0b172017-01-12 18:32:28 +010043import yaml
garciadeblas2299e3b2017-01-26 14:35:55 +000044import random
kate721d79b2017-06-24 04:21:38 -070045import re
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +000046import copy
tierno7edb6752016-03-21 17:37:52 +010047
tiernob5cef372017-06-19 15:52:22 +020048from novaclient import client as nClient, exceptions as nvExceptions
49from keystoneauth1.identity import v2, v3
50from keystoneauth1 import session
tierno7edb6752016-03-21 17:37:52 +010051import keystoneclient.exceptions as ksExceptions
tiernof716aea2017-06-21 18:01:40 +020052import keystoneclient.v3.client as ksClient_v3
53import keystoneclient.v2_0.client as ksClient_v2
tiernob5cef372017-06-19 15:52:22 +020054from glanceclient import client as glClient
tierno7edb6752016-03-21 17:37:52 +010055import glanceclient.client as gl1Client
56import glanceclient.exc as gl1Exceptions
tiernob5cef372017-06-19 15:52:22 +020057from cinderclient import client as cClient
tierno7edb6752016-03-21 17:37:52 +010058from httplib import HTTPException
tiernob5cef372017-06-19 15:52:22 +020059from neutronclient.neutron import client as neClient
tierno7edb6752016-03-21 17:37:52 +010060from neutronclient.common import exceptions as neExceptions
61from requests.exceptions import ConnectionError
62
tierno40e1bce2017-08-09 09:12:04 +020063
64"""contain the openstack virtual machine status to openmano status"""
tierno7edb6752016-03-21 17:37:52 +010065vmStatus2manoFormat={'ACTIVE':'ACTIVE',
66 'PAUSED':'PAUSED',
67 'SUSPENDED': 'SUSPENDED',
68 'SHUTOFF':'INACTIVE',
69 'BUILD':'BUILD',
70 'ERROR':'ERROR','DELETED':'DELETED'
71 }
72netStatus2manoFormat={'ACTIVE':'ACTIVE','PAUSED':'PAUSED','INACTIVE':'INACTIVE','BUILD':'BUILD','ERROR':'ERROR','DELETED':'DELETED'
73 }
74
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +000075supportedClassificationTypes = ['legacy_flow_classifier']
76
montesmoreno0c8def02016-12-22 12:16:23 +000077#global var to have a timeout creating and deleting volumes
78volume_timeout = 60
garciadeblas05a1a612017-07-23 20:26:28 +020079server_timeout = 300
montesmoreno0c8def02016-12-22 12:16:23 +000080
tierno7edb6752016-03-21 17:37:52 +010081class vimconnector(vimconn.vimconnector):
tiernob3d36742017-03-03 23:51:05 +010082 def __init__(self, uuid, name, tenant_id, tenant_name, url, url_admin=None, user=None, passwd=None,
83 log_level=None, config={}, persistent_info={}):
ahmadsa96af9f42017-01-31 16:17:14 +050084 '''using common constructor parameters. In this case
tierno7edb6752016-03-21 17:37:52 +010085 'url' is the keystone authorization url,
86 'url_admin' is not use
87 '''
tiernof716aea2017-06-21 18:01:40 +020088 api_version = config.get('APIversion')
89 if api_version and api_version not in ('v3.3', 'v2.0', '2', '3'):
tiernob5cef372017-06-19 15:52:22 +020090 raise vimconn.vimconnException("Invalid value '{}' for config:APIversion. "
tiernof716aea2017-06-21 18:01:40 +020091 "Allowed values are 'v3.3', 'v2.0', '2' or '3'".format(api_version))
kate721d79b2017-06-24 04:21:38 -070092 vim_type = config.get('vim_type')
93 if vim_type and vim_type not in ('vio', 'VIO'):
94 raise vimconn.vimconnException("Invalid value '{}' for config:vim_type."
95 "Allowed values are 'vio' or 'VIO'".format(vim_type))
96
97 if config.get('dataplane_net_vlan_range') is not None:
98 #validate vlan ranges provided by user
99 self._validate_vlan_ranges(config.get('dataplane_net_vlan_range'))
100
tiernob5cef372017-06-19 15:52:22 +0200101 vimconn.vimconnector.__init__(self, uuid, name, tenant_id, tenant_name, url, url_admin, user, passwd, log_level,
102 config)
tiernob3d36742017-03-03 23:51:05 +0100103
tiernob5cef372017-06-19 15:52:22 +0200104 self.insecure = self.config.get("insecure", False)
tierno7edb6752016-03-21 17:37:52 +0100105 if not url:
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +0000106 raise TypeError('url param can not be NoneType')
tiernob5cef372017-06-19 15:52:22 +0200107 self.persistent_info = persistent_info
mirabal29356312017-07-27 12:21:22 +0200108 self.availability_zone = persistent_info.get('availability_zone', None)
tiernob5cef372017-06-19 15:52:22 +0200109 self.session = persistent_info.get('session', {'reload_client': True})
110 self.nova = self.session.get('nova')
111 self.neutron = self.session.get('neutron')
112 self.cinder = self.session.get('cinder')
113 self.glance = self.session.get('glance')
tiernob39d49e2017-08-02 14:02:15 +0200114 self.glancev1 = self.session.get('glancev1')
tiernof716aea2017-06-21 18:01:40 +0200115 self.keystone = self.session.get('keystone')
116 self.api_version3 = self.session.get('api_version3')
kate721d79b2017-06-24 04:21:38 -0700117 self.vim_type = self.config.get("vim_type")
118 if self.vim_type:
119 self.vim_type = self.vim_type.upper()
120 if self.config.get("use_internal_endpoint"):
121 self.endpoint_type = "internalURL"
122 else:
123 self.endpoint_type = None
montesmoreno0c8def02016-12-22 12:16:23 +0000124
tierno73ad9e42016-09-12 18:11:11 +0200125 self.logger = logging.getLogger('openmano.vim.openstack')
kate721d79b2017-06-24 04:21:38 -0700126
127 ####### VIO Specific Changes #########
128 if self.vim_type == "VIO":
129 self.logger = logging.getLogger('openmano.vim.vio')
130
tiernofe789902016-09-29 14:20:44 +0000131 if log_level:
kate54616752017-09-05 23:26:28 -0700132 self.logger.setLevel( getattr(logging, log_level))
tiernof716aea2017-06-21 18:01:40 +0200133
134 def __getitem__(self, index):
135 """Get individuals parameters.
136 Throw KeyError"""
137 if index == 'project_domain_id':
138 return self.config.get("project_domain_id")
139 elif index == 'user_domain_id':
140 return self.config.get("user_domain_id")
141 else:
tierno76a3c312017-06-29 16:42:15 +0200142 return vimconn.vimconnector.__getitem__(self, index)
tiernof716aea2017-06-21 18:01:40 +0200143
144 def __setitem__(self, index, value):
145 """Set individuals parameters and it is marked as dirty so to force connection reload.
146 Throw KeyError"""
147 if index == 'project_domain_id':
148 self.config["project_domain_id"] = value
149 elif index == 'user_domain_id':
150 self.config["user_domain_id"] = value
151 else:
152 vimconn.vimconnector.__setitem__(self, index, value)
tiernob5cef372017-06-19 15:52:22 +0200153 self.session['reload_client'] = True
tiernof716aea2017-06-21 18:01:40 +0200154
tierno7edb6752016-03-21 17:37:52 +0100155 def _reload_connection(self):
156 '''Called before any operation, it check if credentials has changed
157 Throw keystoneclient.apiclient.exceptions.AuthorizationFailure
158 '''
159 #TODO control the timing and possible token timeout, but it seams that python client does this task for us :-)
tiernob5cef372017-06-19 15:52:22 +0200160 if self.session['reload_client']:
tiernof716aea2017-06-21 18:01:40 +0200161 if self.config.get('APIversion'):
162 self.api_version3 = self.config['APIversion'] == 'v3.3' or self.config['APIversion'] == '3'
163 else: # get from ending auth_url that end with v3 or with v2.0
164 self.api_version3 = self.url.split("/")[-1] == "v3"
165 self.session['api_version3'] = self.api_version3
166 if self.api_version3:
167 auth = v3.Password(auth_url=self.url,
tiernob5cef372017-06-19 15:52:22 +0200168 username=self.user,
169 password=self.passwd,
170 project_name=self.tenant_name,
171 project_id=self.tenant_id,
172 project_domain_id=self.config.get('project_domain_id', 'default'),
173 user_domain_id=self.config.get('user_domain_id', 'default'))
ahmadsa95baa272016-11-30 09:14:11 +0500174 else:
tiernof716aea2017-06-21 18:01:40 +0200175 auth = v2.Password(auth_url=self.url,
tiernob5cef372017-06-19 15:52:22 +0200176 username=self.user,
177 password=self.passwd,
178 tenant_name=self.tenant_name,
179 tenant_id=self.tenant_id)
180 sess = session.Session(auth=auth, verify=not self.insecure)
tiernof716aea2017-06-21 18:01:40 +0200181 if self.api_version3:
kate721d79b2017-06-24 04:21:38 -0700182 self.keystone = ksClient_v3.Client(session=sess, endpoint_type=self.endpoint_type)
tiernof716aea2017-06-21 18:01:40 +0200183 else:
kate721d79b2017-06-24 04:21:38 -0700184 self.keystone = ksClient_v2.Client(session=sess, endpoint_type=self.endpoint_type)
tiernof716aea2017-06-21 18:01:40 +0200185 self.session['keystone'] = self.keystone
montesmoreno9317d302017-08-16 12:48:23 +0200186 # In order to enable microversion functionality an explicit microversion must be specified in 'config'.
187 # This implementation approach is due to the warning message in
188 # https://developer.openstack.org/api-guide/compute/microversions.html
189 # where it is stated that microversion backwards compatibility is not guaranteed and clients should
190 # always require an specific microversion.
191 # To be able to use 'device role tagging' functionality define 'microversion: 2.32' in datacenter config
192 version = self.config.get("microversion")
193 if not version:
194 version = "2.1"
kate54616752017-09-05 23:26:28 -0700195 self.nova = self.session['nova'] = nClient.Client(str(version), session=sess, endpoint_type=self.endpoint_type)
kate721d79b2017-06-24 04:21:38 -0700196 self.neutron = self.session['neutron'] = neClient.Client('2.0', session=sess, endpoint_type=self.endpoint_type)
197 self.cinder = self.session['cinder'] = cClient.Client(2, session=sess, endpoint_type=self.endpoint_type)
198 if self.endpoint_type == "internalURL":
199 glance_service_id = self.keystone.services.list(name="glance")[0].id
200 glance_endpoint = self.keystone.endpoints.list(glance_service_id, interface="internal")[0].url
201 else:
202 glance_endpoint = None
203 self.glance = self.session['glance'] = glClient.Client(2, session=sess, endpoint=glance_endpoint)
204 #using version 1 of glance client in new_image()
205 self.glancev1 = self.session['glancev1'] = glClient.Client('1', session=sess,
206 endpoint=glance_endpoint)
tiernob5cef372017-06-19 15:52:22 +0200207 self.session['reload_client'] = False
208 self.persistent_info['session'] = self.session
mirabal29356312017-07-27 12:21:22 +0200209 # add availablity zone info inside self.persistent_info
210 self._set_availablity_zones()
211 self.persistent_info['availability_zone'] = self.availability_zone
ahmadsa95baa272016-11-30 09:14:11 +0500212
tierno7edb6752016-03-21 17:37:52 +0100213 def __net_os2mano(self, net_list_dict):
214 '''Transform the net openstack format to mano format
215 net_list_dict can be a list of dict or a single dict'''
216 if type(net_list_dict) is dict:
217 net_list_=(net_list_dict,)
218 elif type(net_list_dict) is list:
219 net_list_=net_list_dict
220 else:
221 raise TypeError("param net_list_dict must be a list or a dictionary")
222 for net in net_list_:
223 if net.get('provider:network_type') == "vlan":
224 net['type']='data'
225 else:
226 net['type']='bridge'
Pablo Montes Moreno6a7785b2017-07-03 10:44:30 +0200227
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +0000228 def __classification_os2mano(self, class_list_dict):
229 """Transform the openstack format (Flow Classifier) to mano format
230 (Classification) class_list_dict can be a list of dict or a single dict
231 """
232 if isinstance(class_list_dict, dict):
233 class_list_ = [class_list_dict]
234 elif isinstance(class_list_dict, list):
235 class_list_ = class_list_dict
236 else:
237 raise TypeError(
238 "param class_list_dict must be a list or a dictionary")
239 for classification in class_list_:
240 id = classification.pop('id')
241 name = classification.pop('name')
242 description = classification.pop('description')
243 project_id = classification.pop('project_id')
244 tenant_id = classification.pop('tenant_id')
245 original_classification = copy.deepcopy(classification)
246 classification.clear()
247 classification['ctype'] = 'legacy_flow_classifier'
248 classification['definition'] = original_classification
249 classification['id'] = id
250 classification['name'] = name
251 classification['description'] = description
252 classification['project_id'] = project_id
253 classification['tenant_id'] = tenant_id
254
255 def __sfi_os2mano(self, sfi_list_dict):
256 """Transform the openstack format (Port Pair) to mano format (SFI)
257 sfi_list_dict can be a list of dict or a single dict
258 """
259 if isinstance(sfi_list_dict, dict):
260 sfi_list_ = [sfi_list_dict]
261 elif isinstance(sfi_list_dict, list):
262 sfi_list_ = sfi_list_dict
263 else:
264 raise TypeError(
265 "param sfi_list_dict must be a list or a dictionary")
266 for sfi in sfi_list_:
267 sfi['ingress_ports'] = []
268 sfi['egress_ports'] = []
269 if sfi.get('ingress'):
270 sfi['ingress_ports'].append(sfi['ingress'])
271 if sfi.get('egress'):
272 sfi['egress_ports'].append(sfi['egress'])
273 del sfi['ingress']
274 del sfi['egress']
275 params = sfi.get('service_function_parameters')
276 sfc_encap = False
277 if params:
278 correlation = params.get('correlation')
279 if correlation:
280 sfc_encap = True
281 sfi['sfc_encap'] = sfc_encap
282 del sfi['service_function_parameters']
283
284 def __sf_os2mano(self, sf_list_dict):
285 """Transform the openstack format (Port Pair Group) to mano format (SF)
286 sf_list_dict can be a list of dict or a single dict
287 """
288 if isinstance(sf_list_dict, dict):
289 sf_list_ = [sf_list_dict]
290 elif isinstance(sf_list_dict, list):
291 sf_list_ = sf_list_dict
292 else:
293 raise TypeError(
294 "param sf_list_dict must be a list or a dictionary")
295 for sf in sf_list_:
296 del sf['port_pair_group_parameters']
297 sf['sfis'] = sf['port_pairs']
298 del sf['port_pairs']
299
300 def __sfp_os2mano(self, sfp_list_dict):
301 """Transform the openstack format (Port Chain) to mano format (SFP)
302 sfp_list_dict can be a list of dict or a single dict
303 """
304 if isinstance(sfp_list_dict, dict):
305 sfp_list_ = [sfp_list_dict]
306 elif isinstance(sfp_list_dict, list):
307 sfp_list_ = sfp_list_dict
308 else:
309 raise TypeError(
310 "param sfp_list_dict must be a list or a dictionary")
311 for sfp in sfp_list_:
312 params = sfp.pop('chain_parameters')
313 sfc_encap = False
314 if params:
315 correlation = params.get('correlation')
316 if correlation:
317 sfc_encap = True
318 sfp['sfc_encap'] = sfc_encap
319 sfp['spi'] = sfp.pop('chain_id')
320 sfp['classifications'] = sfp.pop('flow_classifiers')
321 sfp['service_functions'] = sfp.pop('port_pair_groups')
322
323 # placeholder for now; read TODO note below
324 def _validate_classification(self, type, definition):
325 # only legacy_flow_classifier Type is supported at this point
326 return True
327 # TODO(igordcard): this method should be an abstract method of an
328 # abstract Classification class to be implemented by the specific
329 # Types. Also, abstract vimconnector should call the validation
330 # method before the implemented VIM connectors are called.
331
tiernoae4a8d12016-07-08 12:30:39 +0200332 def _format_exception(self, exception):
333 '''Transform a keystone, nova, neutron exception into a vimconn exception'''
334 if isinstance(exception, (HTTPException, gl1Exceptions.HTTPException, gl1Exceptions.CommunicationError,
tierno8e995ce2016-09-22 08:13:00 +0000335 ConnectionError, ksExceptions.ConnectionError, neExceptions.ConnectionFailed
336 )):
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +0000337 raise vimconn.vimconnConnectionException(type(exception).__name__ + ": " + str(exception))
338 elif isinstance(exception, (nvExceptions.ClientException, ksExceptions.ClientException,
tiernoae4a8d12016-07-08 12:30:39 +0200339 neExceptions.NeutronException, nvExceptions.BadRequest)):
340 raise vimconn.vimconnUnexpectedResponse(type(exception).__name__ + ": " + str(exception))
341 elif isinstance(exception, (neExceptions.NetworkNotFoundClient, nvExceptions.NotFound)):
342 raise vimconn.vimconnNotFoundException(type(exception).__name__ + ": " + str(exception))
343 elif isinstance(exception, nvExceptions.Conflict):
344 raise vimconn.vimconnConflictException(type(exception).__name__ + ": " + str(exception))
Pablo Montes Moreno6a7785b2017-07-03 10:44:30 +0200345 elif isinstance(exception, vimconn.vimconnException):
346 raise
tiernof716aea2017-06-21 18:01:40 +0200347 else: # ()
tiernob84cbdc2017-07-07 14:30:30 +0200348 self.logger.error("General Exception " + str(exception), exc_info=True)
tiernoae4a8d12016-07-08 12:30:39 +0200349 raise vimconn.vimconnConnectionException(type(exception).__name__ + ": " + str(exception))
350
351 def get_tenant_list(self, filter_dict={}):
352 '''Obtain tenants of VIM
353 filter_dict can contain the following keys:
354 name: filter by tenant name
355 id: filter by tenant uuid/id
356 <other VIM specific>
357 Returns the tenant list of dictionaries: [{'name':'<name>, 'id':'<id>, ...}, ...]
358 '''
ahmadsa95baa272016-11-30 09:14:11 +0500359 self.logger.debug("Getting tenants from VIM filter: '%s'", str(filter_dict))
tiernoae4a8d12016-07-08 12:30:39 +0200360 try:
361 self._reload_connection()
tiernof716aea2017-06-21 18:01:40 +0200362 if self.api_version3:
363 project_class_list = self.keystone.projects.list(name=filter_dict.get("name"))
ahmadsa95baa272016-11-30 09:14:11 +0500364 else:
tiernof716aea2017-06-21 18:01:40 +0200365 project_class_list = self.keystone.tenants.findall(**filter_dict)
ahmadsa95baa272016-11-30 09:14:11 +0500366 project_list=[]
367 for project in project_class_list:
tiernof716aea2017-06-21 18:01:40 +0200368 if filter_dict.get('id') and filter_dict["id"] != project.id:
369 continue
ahmadsa95baa272016-11-30 09:14:11 +0500370 project_list.append(project.to_dict())
371 return project_list
tiernof716aea2017-06-21 18:01:40 +0200372 except (ksExceptions.ConnectionError, ksExceptions.ClientException, ConnectionError) as e:
tiernoae4a8d12016-07-08 12:30:39 +0200373 self._format_exception(e)
374
375 def new_tenant(self, tenant_name, tenant_description):
376 '''Adds a new tenant to openstack VIM. Returns the tenant identifier'''
377 self.logger.debug("Adding a new tenant name: %s", tenant_name)
378 try:
379 self._reload_connection()
tiernof716aea2017-06-21 18:01:40 +0200380 if self.api_version3:
381 project = self.keystone.projects.create(tenant_name, self.config.get("project_domain_id", "default"),
382 description=tenant_description, is_domain=False)
ahmadsa95baa272016-11-30 09:14:11 +0500383 else:
tiernof716aea2017-06-21 18:01:40 +0200384 project = self.keystone.tenants.create(tenant_name, tenant_description)
ahmadsa95baa272016-11-30 09:14:11 +0500385 return project.id
tierno8e995ce2016-09-22 08:13:00 +0000386 except (ksExceptions.ConnectionError, ksExceptions.ClientException, ConnectionError) as e:
tiernoae4a8d12016-07-08 12:30:39 +0200387 self._format_exception(e)
388
389 def delete_tenant(self, tenant_id):
390 '''Delete a tenant from openstack VIM. Returns the old tenant identifier'''
391 self.logger.debug("Deleting tenant %s from VIM", tenant_id)
392 try:
393 self._reload_connection()
tiernof716aea2017-06-21 18:01:40 +0200394 if self.api_version3:
ahmadsa95baa272016-11-30 09:14:11 +0500395 self.keystone.projects.delete(tenant_id)
396 else:
397 self.keystone.tenants.delete(tenant_id)
tiernoae4a8d12016-07-08 12:30:39 +0200398 return tenant_id
tierno8e995ce2016-09-22 08:13:00 +0000399 except (ksExceptions.ConnectionError, ksExceptions.ClientException, ConnectionError) as e:
tiernoae4a8d12016-07-08 12:30:39 +0200400 self._format_exception(e)
ahmadsa95baa272016-11-30 09:14:11 +0500401
garciadeblas9f8456e2016-09-05 05:02:59 +0200402 def new_network(self,net_name, net_type, ip_profile=None, shared=False, vlan=None):
tiernoae4a8d12016-07-08 12:30:39 +0200403 '''Adds a tenant network to VIM. Returns the network identifier'''
404 self.logger.debug("Adding a new network to VIM name '%s', type '%s'", net_name, net_type)
garciadeblasedca7b32016-09-29 14:01:52 +0000405 #self.logger.debug(">>>>>>>>>>>>>>>>>> IP profile %s", str(ip_profile))
tierno7edb6752016-03-21 17:37:52 +0100406 try:
garciadeblasedca7b32016-09-29 14:01:52 +0000407 new_net = None
tierno7edb6752016-03-21 17:37:52 +0100408 self._reload_connection()
409 network_dict = {'name': net_name, 'admin_state_up': True}
410 if net_type=="data" or net_type=="ptp":
411 if self.config.get('dataplane_physical_net') == None:
tiernoae4a8d12016-07-08 12:30:39 +0200412 raise vimconn.vimconnConflictException("You must provide a 'dataplane_physical_net' at config value before creating sriov network")
tierno7edb6752016-03-21 17:37:52 +0100413 network_dict["provider:physical_network"] = self.config['dataplane_physical_net'] #"physnet_sriov" #TODO physical
414 network_dict["provider:network_type"] = "vlan"
415 if vlan!=None:
416 network_dict["provider:network_type"] = vlan
kate721d79b2017-06-24 04:21:38 -0700417
418 ####### VIO Specific Changes #########
419 if self.vim_type == "VIO":
420 if vlan is not None:
421 network_dict["provider:segmentation_id"] = vlan
422 else:
423 if self.config.get('dataplane_net_vlan_range') is None:
424 raise vimconn.vimconnConflictException("You must provide "\
425 "'dataplane_net_vlan_range' in format [start_ID - end_ID]"\
426 "at config value before creating sriov network with vlan tag")
427
428 network_dict["provider:segmentation_id"] = self._genrate_vlanID()
429
tiernoae4a8d12016-07-08 12:30:39 +0200430 network_dict["shared"]=shared
tierno7edb6752016-03-21 17:37:52 +0100431 new_net=self.neutron.create_network({'network':network_dict})
432 #print new_net
garciadeblas9f8456e2016-09-05 05:02:59 +0200433 #create subnetwork, even if there is no profile
434 if not ip_profile:
435 ip_profile = {}
436 if 'subnet_address' not in ip_profile:
garciadeblas2299e3b2017-01-26 14:35:55 +0000437 #Fake subnet is required
438 subnet_rand = random.randint(0, 255)
439 ip_profile['subnet_address'] = "192.168.{}.0/24".format(subnet_rand)
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +0000440 if 'ip_version' not in ip_profile:
garciadeblas9f8456e2016-09-05 05:02:59 +0200441 ip_profile['ip_version'] = "IPv4"
tiernoa1fb4462017-06-30 12:25:50 +0200442 subnet = {"name":net_name+"-subnet",
tierno7edb6752016-03-21 17:37:52 +0100443 "network_id": new_net["network"]["id"],
garciadeblas9f8456e2016-09-05 05:02:59 +0200444 "ip_version": 4 if ip_profile['ip_version']=="IPv4" else 6,
445 "cidr": ip_profile['subnet_address']
tierno7edb6752016-03-21 17:37:52 +0100446 }
tiernoa1fb4462017-06-30 12:25:50 +0200447 # Gateway should be set to None if not needed. Otherwise openstack assigns one by default
448 subnet['gateway_ip'] = ip_profile.get('gateway_address')
garciadeblasedca7b32016-09-29 14:01:52 +0000449 if ip_profile.get('dns_address'):
tierno455612d2017-05-30 16:40:10 +0200450 subnet['dns_nameservers'] = ip_profile['dns_address'].split(";")
garciadeblas9f8456e2016-09-05 05:02:59 +0200451 if 'dhcp_enabled' in ip_profile:
452 subnet['enable_dhcp'] = False if ip_profile['dhcp_enabled']=="false" else True
453 if 'dhcp_start_address' in ip_profile:
tiernoa1fb4462017-06-30 12:25:50 +0200454 subnet['allocation_pools'] = []
garciadeblas9f8456e2016-09-05 05:02:59 +0200455 subnet['allocation_pools'].append(dict())
456 subnet['allocation_pools'][0]['start'] = ip_profile['dhcp_start_address']
457 if 'dhcp_count' in ip_profile:
458 #parts = ip_profile['dhcp_start_address'].split('.')
459 #ip_int = (int(parts[0]) << 24) + (int(parts[1]) << 16) + (int(parts[2]) << 8) + int(parts[3])
460 ip_int = int(netaddr.IPAddress(ip_profile['dhcp_start_address']))
garciadeblas21d795b2016-09-29 17:31:46 +0200461 ip_int += ip_profile['dhcp_count'] - 1
garciadeblas9f8456e2016-09-05 05:02:59 +0200462 ip_str = str(netaddr.IPAddress(ip_int))
463 subnet['allocation_pools'][0]['end'] = ip_str
garciadeblasedca7b32016-09-29 14:01:52 +0000464 #self.logger.debug(">>>>>>>>>>>>>>>>>> Subnet: %s", str(subnet))
tierno7edb6752016-03-21 17:37:52 +0100465 self.neutron.create_subnet({"subnet": subnet} )
tiernoae4a8d12016-07-08 12:30:39 +0200466 return new_net["network"]["id"]
tierno8e995ce2016-09-22 08:13:00 +0000467 except (neExceptions.ConnectionFailed, ksExceptions.ClientException, neExceptions.NeutronException, ConnectionError) as e:
garciadeblasedca7b32016-09-29 14:01:52 +0000468 if new_net:
469 self.neutron.delete_network(new_net['network']['id'])
tiernoae4a8d12016-07-08 12:30:39 +0200470 self._format_exception(e)
tierno7edb6752016-03-21 17:37:52 +0100471
472 def get_network_list(self, filter_dict={}):
473 '''Obtain tenant networks of VIM
474 Filter_dict can be:
475 name: network name
476 id: network uuid
477 shared: boolean
478 tenant_id: tenant
479 admin_state_up: boolean
480 status: 'ACTIVE'
481 Returns the network list of dictionaries
482 '''
tiernoae4a8d12016-07-08 12:30:39 +0200483 self.logger.debug("Getting network from VIM filter: '%s'", str(filter_dict))
tierno7edb6752016-03-21 17:37:52 +0100484 try:
485 self._reload_connection()
tiernof716aea2017-06-21 18:01:40 +0200486 if self.api_version3 and "tenant_id" in filter_dict:
487 filter_dict['project_id'] = filter_dict.pop('tenant_id') #TODO check
tierno7edb6752016-03-21 17:37:52 +0100488 net_dict=self.neutron.list_networks(**filter_dict)
489 net_list=net_dict["networks"]
490 self.__net_os2mano(net_list)
tiernoae4a8d12016-07-08 12:30:39 +0200491 return net_list
tierno8e995ce2016-09-22 08:13:00 +0000492 except (neExceptions.ConnectionFailed, ksExceptions.ClientException, neExceptions.NeutronException, ConnectionError) as e:
tiernoae4a8d12016-07-08 12:30:39 +0200493 self._format_exception(e)
tierno7edb6752016-03-21 17:37:52 +0100494
tiernoae4a8d12016-07-08 12:30:39 +0200495 def get_network(self, net_id):
496 '''Obtain details of network from VIM
497 Returns the network information from a network id'''
498 self.logger.debug(" Getting tenant network %s from VIM", net_id)
tierno7edb6752016-03-21 17:37:52 +0100499 filter_dict={"id": net_id}
tiernoae4a8d12016-07-08 12:30:39 +0200500 net_list = self.get_network_list(filter_dict)
tierno7edb6752016-03-21 17:37:52 +0100501 if len(net_list)==0:
tiernoae4a8d12016-07-08 12:30:39 +0200502 raise vimconn.vimconnNotFoundException("Network '{}' not found".format(net_id))
tierno7edb6752016-03-21 17:37:52 +0100503 elif len(net_list)>1:
tiernoae4a8d12016-07-08 12:30:39 +0200504 raise vimconn.vimconnConflictException("Found more than one network with this criteria")
tierno7edb6752016-03-21 17:37:52 +0100505 net = net_list[0]
506 subnets=[]
507 for subnet_id in net.get("subnets", () ):
508 try:
509 subnet = self.neutron.show_subnet(subnet_id)
510 except Exception as e:
tiernoae4a8d12016-07-08 12:30:39 +0200511 self.logger.error("osconnector.get_network(): Error getting subnet %s %s" % (net_id, str(e)))
512 subnet = {"id": subnet_id, "fault": str(e)}
tierno7edb6752016-03-21 17:37:52 +0100513 subnets.append(subnet)
514 net["subnets"] = subnets
Pablo Montes Moreno51e553b2017-03-23 16:39:12 +0100515 net["encapsulation"] = net.get('provider:network_type')
Pablo Montes Moreno3fbff9b2017-03-08 11:28:15 +0100516 net["segmentation_id"] = net.get('provider:segmentation_id')
tiernoae4a8d12016-07-08 12:30:39 +0200517 return net
tierno7edb6752016-03-21 17:37:52 +0100518
tiernoae4a8d12016-07-08 12:30:39 +0200519 def delete_network(self, net_id):
520 '''Deletes a tenant network from VIM. Returns the old network identifier'''
521 self.logger.debug("Deleting network '%s' from VIM", net_id)
tierno7edb6752016-03-21 17:37:52 +0100522 try:
523 self._reload_connection()
524 #delete VM ports attached to this networks before the network
525 ports = self.neutron.list_ports(network_id=net_id)
526 for p in ports['ports']:
527 try:
528 self.neutron.delete_port(p["id"])
529 except Exception as e:
tiernoae4a8d12016-07-08 12:30:39 +0200530 self.logger.error("Error deleting port %s: %s", p["id"], str(e))
tierno7edb6752016-03-21 17:37:52 +0100531 self.neutron.delete_network(net_id)
tiernoae4a8d12016-07-08 12:30:39 +0200532 return net_id
533 except (neExceptions.ConnectionFailed, neExceptions.NetworkNotFoundClient, neExceptions.NeutronException,
tierno8e995ce2016-09-22 08:13:00 +0000534 ksExceptions.ClientException, neExceptions.NeutronException, ConnectionError) as e:
tiernoae4a8d12016-07-08 12:30:39 +0200535 self._format_exception(e)
tierno7edb6752016-03-21 17:37:52 +0100536
tiernoae4a8d12016-07-08 12:30:39 +0200537 def refresh_nets_status(self, net_list):
538 '''Get the status of the networks
539 Params: the list of network identifiers
540 Returns a dictionary with:
541 net_id: #VIM id of this network
542 status: #Mandatory. Text with one of:
543 # DELETED (not found at vim)
544 # VIM_ERROR (Cannot connect to VIM, VIM response error, ...)
545 # OTHER (Vim reported other status not understood)
546 # ERROR (VIM indicates an ERROR status)
547 # ACTIVE, INACTIVE, DOWN (admin down),
548 # BUILD (on building process)
549 #
550 error_msg: #Text with VIM error message, if any. Or the VIM connection ERROR
551 vim_info: #Text with plain information obtained from vim (yaml.safe_dump)
552
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +0000553 '''
tiernoae4a8d12016-07-08 12:30:39 +0200554 net_dict={}
555 for net_id in net_list:
556 net = {}
557 try:
558 net_vim = self.get_network(net_id)
559 if net_vim['status'] in netStatus2manoFormat:
560 net["status"] = netStatus2manoFormat[ net_vim['status'] ]
561 else:
562 net["status"] = "OTHER"
563 net["error_msg"] = "VIM status reported " + net_vim['status']
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +0000564
tierno8e995ce2016-09-22 08:13:00 +0000565 if net['status'] == "ACTIVE" and not net_vim['admin_state_up']:
tiernoae4a8d12016-07-08 12:30:39 +0200566 net['status'] = 'DOWN'
tierno8e995ce2016-09-22 08:13:00 +0000567 try:
568 net['vim_info'] = yaml.safe_dump(net_vim, default_flow_style=True, width=256)
569 except yaml.representer.RepresenterError:
570 net['vim_info'] = str(net_vim)
tiernoae4a8d12016-07-08 12:30:39 +0200571 if net_vim.get('fault'): #TODO
572 net['error_msg'] = str(net_vim['fault'])
573 except vimconn.vimconnNotFoundException as e:
574 self.logger.error("Exception getting net status: %s", str(e))
575 net['status'] = "DELETED"
576 net['error_msg'] = str(e)
577 except vimconn.vimconnException as e:
578 self.logger.error("Exception getting net status: %s", str(e))
579 net['status'] = "VIM_ERROR"
580 net['error_msg'] = str(e)
581 net_dict[net_id] = net
582 return net_dict
583
584 def get_flavor(self, flavor_id):
585 '''Obtain flavor details from the VIM. Returns the flavor dict details'''
586 self.logger.debug("Getting flavor '%s'", flavor_id)
tierno7edb6752016-03-21 17:37:52 +0100587 try:
588 self._reload_connection()
589 flavor = self.nova.flavors.find(id=flavor_id)
590 #TODO parse input and translate to VIM format (openmano_schemas.new_vminstance_response_schema)
tiernoae4a8d12016-07-08 12:30:39 +0200591 return flavor.to_dict()
tierno8e995ce2016-09-22 08:13:00 +0000592 except (nvExceptions.NotFound, nvExceptions.ClientException, ksExceptions.ClientException, ConnectionError) as e:
tiernoae4a8d12016-07-08 12:30:39 +0200593 self._format_exception(e)
tierno7edb6752016-03-21 17:37:52 +0100594
tiernocf157a82017-01-30 14:07:06 +0100595 def get_flavor_id_from_data(self, flavor_dict):
596 """Obtain flavor id that match the flavor description
597 Returns the flavor_id or raises a vimconnNotFoundException
tiernoe26fc7a2017-05-30 14:43:03 +0200598 flavor_dict: contains the required ram, vcpus, disk
599 If 'use_existing_flavors' is set to True at config, the closer flavor that provides same or more ram, vcpus
600 and disk is returned. Otherwise a flavor with exactly same ram, vcpus and disk is returned or a
601 vimconnNotFoundException is raised
tiernocf157a82017-01-30 14:07:06 +0100602 """
tiernoe26fc7a2017-05-30 14:43:03 +0200603 exact_match = False if self.config.get('use_existing_flavors') else True
tiernocf157a82017-01-30 14:07:06 +0100604 try:
605 self._reload_connection()
tiernoe26fc7a2017-05-30 14:43:03 +0200606 flavor_candidate_id = None
607 flavor_candidate_data = (10000, 10000, 10000)
608 flavor_target = (flavor_dict["ram"], flavor_dict["vcpus"], flavor_dict["disk"])
609 # numa=None
610 numas = flavor_dict.get("extended", {}).get("numas")
tiernocf157a82017-01-30 14:07:06 +0100611 if numas:
612 #TODO
613 raise vimconn.vimconnNotFoundException("Flavor with EPA still not implemted")
614 # if len(numas) > 1:
615 # raise vimconn.vimconnNotFoundException("Cannot find any flavor with more than one numa")
616 # numa=numas[0]
617 # numas = extended.get("numas")
618 for flavor in self.nova.flavors.list():
619 epa = flavor.get_keys()
620 if epa:
621 continue
tiernoe26fc7a2017-05-30 14:43:03 +0200622 # TODO
623 flavor_data = (flavor.ram, flavor.vcpus, flavor.disk)
624 if flavor_data == flavor_target:
625 return flavor.id
626 elif not exact_match and flavor_target < flavor_data < flavor_candidate_data:
627 flavor_candidate_id = flavor.id
628 flavor_candidate_data = flavor_data
629 if not exact_match and flavor_candidate_id:
630 return flavor_candidate_id
tiernocf157a82017-01-30 14:07:06 +0100631 raise vimconn.vimconnNotFoundException("Cannot find any flavor matching '{}'".format(str(flavor_dict)))
632 except (nvExceptions.NotFound, nvExceptions.ClientException, ksExceptions.ClientException, ConnectionError) as e:
633 self._format_exception(e)
634
tiernoae4a8d12016-07-08 12:30:39 +0200635 def new_flavor(self, flavor_data, change_name_if_used=True):
tierno7edb6752016-03-21 17:37:52 +0100636 '''Adds a tenant flavor to openstack VIM
tiernoae4a8d12016-07-08 12:30:39 +0200637 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 +0100638 Returns the flavor identifier
639 '''
tiernoae4a8d12016-07-08 12:30:39 +0200640 self.logger.debug("Adding flavor '%s'", str(flavor_data))
tierno7edb6752016-03-21 17:37:52 +0100641 retry=0
tiernoae4a8d12016-07-08 12:30:39 +0200642 max_retries=3
tierno7edb6752016-03-21 17:37:52 +0100643 name_suffix = 0
tiernoae4a8d12016-07-08 12:30:39 +0200644 name=flavor_data['name']
645 while retry<max_retries:
tierno7edb6752016-03-21 17:37:52 +0100646 retry+=1
647 try:
648 self._reload_connection()
649 if change_name_if_used:
650 #get used names
651 fl_names=[]
652 fl=self.nova.flavors.list()
653 for f in fl:
654 fl_names.append(f.name)
655 while name in fl_names:
656 name_suffix += 1
tiernoae4a8d12016-07-08 12:30:39 +0200657 name = flavor_data['name']+"-" + str(name_suffix)
kate721d79b2017-06-24 04:21:38 -0700658
tiernoae4a8d12016-07-08 12:30:39 +0200659 ram = flavor_data.get('ram',64)
660 vcpus = flavor_data.get('vcpus',1)
tierno7edb6752016-03-21 17:37:52 +0100661 numa_properties=None
662
tiernoae4a8d12016-07-08 12:30:39 +0200663 extended = flavor_data.get("extended")
tierno7edb6752016-03-21 17:37:52 +0100664 if extended:
665 numas=extended.get("numas")
666 if numas:
667 numa_nodes = len(numas)
668 if numa_nodes > 1:
669 return -1, "Can not add flavor with more than one numa"
670 numa_properties = {"hw:numa_nodes":str(numa_nodes)}
671 numa_properties["hw:mem_page_size"] = "large"
672 numa_properties["hw:cpu_policy"] = "dedicated"
673 numa_properties["hw:numa_mempolicy"] = "strict"
kate721d79b2017-06-24 04:21:38 -0700674 if self.vim_type == "VIO":
675 numa_properties["vmware:extra_config"] = '{"numa.nodeAffinity":"0"}'
676 numa_properties["vmware:latency_sensitivity_level"] = "high"
tierno7edb6752016-03-21 17:37:52 +0100677 for numa in numas:
678 #overwrite ram and vcpus
679 ram = numa['memory']*1024
Pablo Montes Morenoea1d6232017-05-24 11:33:24 +0200680 #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 +0100681 if 'paired-threads' in numa:
682 vcpus = numa['paired-threads']*2
Pablo Montes Morenoea1d6232017-05-24 11:33:24 +0200683 #cpu_thread_policy "require" implies that the compute node must have an STM architecture
684 numa_properties["hw:cpu_thread_policy"] = "require"
685 numa_properties["hw:cpu_policy"] = "dedicated"
tierno7edb6752016-03-21 17:37:52 +0100686 elif 'cores' in numa:
687 vcpus = numa['cores']
Pablo Montes Morenoea1d6232017-05-24 11:33:24 +0200688 # cpu_thread_policy "prefer" implies that the host must not have an SMT architecture, or a non-SMT architecture will be emulated
689 numa_properties["hw:cpu_thread_policy"] = "isolate"
690 numa_properties["hw:cpu_policy"] = "dedicated"
tierno7edb6752016-03-21 17:37:52 +0100691 elif 'threads' in numa:
692 vcpus = numa['threads']
Pablo Montes Morenoea1d6232017-05-24 11:33:24 +0200693 # cpu_thread_policy "prefer" implies that the host may or may not have an SMT architecture
694 numa_properties["hw:cpu_thread_policy"] = "prefer"
695 numa_properties["hw:cpu_policy"] = "dedicated"
Pablo Montes Moreno3be0b2a2017-03-30 13:22:15 +0200696 # for interface in numa.get("interfaces",() ):
697 # if interface["dedicated"]=="yes":
698 # raise vimconn.vimconnException("Passthrough interfaces are not supported for the openstack connector", http_code=vimconn.HTTP_Service_Unavailable)
699 # #TODO, add the key 'pci_passthrough:alias"="<label at config>:<number ifaces>"' when a way to connect it is available
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +0000700
tierno7edb6752016-03-21 17:37:52 +0100701 #create flavor
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +0000702 new_flavor=self.nova.flavors.create(name,
703 ram,
704 vcpus,
tiernoae4a8d12016-07-08 12:30:39 +0200705 flavor_data.get('disk',1),
706 is_public=flavor_data.get('is_public', True)
kate721d79b2017-06-24 04:21:38 -0700707 )
tierno7edb6752016-03-21 17:37:52 +0100708 #add metadata
709 if numa_properties:
710 new_flavor.set_keys(numa_properties)
tiernoae4a8d12016-07-08 12:30:39 +0200711 return new_flavor.id
tierno7edb6752016-03-21 17:37:52 +0100712 except nvExceptions.Conflict as e:
tiernoae4a8d12016-07-08 12:30:39 +0200713 if change_name_if_used and retry < max_retries:
tierno7edb6752016-03-21 17:37:52 +0100714 continue
tiernoae4a8d12016-07-08 12:30:39 +0200715 self._format_exception(e)
tierno7edb6752016-03-21 17:37:52 +0100716 #except nvExceptions.BadRequest as e:
717 except (ksExceptions.ClientException, nvExceptions.ClientException, ConnectionError) as e:
tiernoae4a8d12016-07-08 12:30:39 +0200718 self._format_exception(e)
tierno7edb6752016-03-21 17:37:52 +0100719
tiernoae4a8d12016-07-08 12:30:39 +0200720 def delete_flavor(self,flavor_id):
721 '''Deletes a tenant flavor from openstack VIM. Returns the old flavor_id
tierno7edb6752016-03-21 17:37:52 +0100722 '''
tiernoae4a8d12016-07-08 12:30:39 +0200723 try:
724 self._reload_connection()
725 self.nova.flavors.delete(flavor_id)
726 return flavor_id
727 #except nvExceptions.BadRequest as e:
tierno8e995ce2016-09-22 08:13:00 +0000728 except (nvExceptions.NotFound, ksExceptions.ClientException, nvExceptions.ClientException, ConnectionError) as e:
tiernoae4a8d12016-07-08 12:30:39 +0200729 self._format_exception(e)
tierno7edb6752016-03-21 17:37:52 +0100730
tiernoae4a8d12016-07-08 12:30:39 +0200731 def new_image(self,image_dict):
tierno7edb6752016-03-21 17:37:52 +0100732 '''
tiernoae4a8d12016-07-08 12:30:39 +0200733 Adds a tenant image to VIM. imge_dict is a dictionary with:
734 name: name
735 disk_format: qcow2, vhd, vmdk, raw (by default), ...
736 location: path or URI
737 public: "yes" or "no"
738 metadata: metadata of the image
739 Returns the image_id
tierno7edb6752016-03-21 17:37:52 +0100740 '''
tiernoae4a8d12016-07-08 12:30:39 +0200741 retry=0
742 max_retries=3
743 while retry<max_retries:
tierno7edb6752016-03-21 17:37:52 +0100744 retry+=1
745 try:
746 self._reload_connection()
747 #determine format http://docs.openstack.org/developer/glance/formats.html
748 if "disk_format" in image_dict:
749 disk_format=image_dict["disk_format"]
garciadeblas14480452017-01-10 13:08:07 +0100750 else: #autodiscover based on extension
tierno7edb6752016-03-21 17:37:52 +0100751 if image_dict['location'][-6:]==".qcow2":
752 disk_format="qcow2"
753 elif image_dict['location'][-4:]==".vhd":
754 disk_format="vhd"
755 elif image_dict['location'][-5:]==".vmdk":
756 disk_format="vmdk"
757 elif image_dict['location'][-4:]==".vdi":
758 disk_format="vdi"
759 elif image_dict['location'][-4:]==".iso":
760 disk_format="iso"
761 elif image_dict['location'][-4:]==".aki":
762 disk_format="aki"
763 elif image_dict['location'][-4:]==".ari":
764 disk_format="ari"
765 elif image_dict['location'][-4:]==".ami":
766 disk_format="ami"
767 else:
768 disk_format="raw"
tiernoae4a8d12016-07-08 12:30:39 +0200769 self.logger.debug("new_image: '%s' loading from '%s'", image_dict['name'], image_dict['location'])
tierno7edb6752016-03-21 17:37:52 +0100770 if image_dict['location'][0:4]=="http":
tiernob39d49e2017-08-02 14:02:15 +0200771 new_image = self.glancev1.images.create(name=image_dict['name'], is_public=image_dict.get('public',"yes")=="yes",
tierno7edb6752016-03-21 17:37:52 +0100772 container_format="bare", location=image_dict['location'], disk_format=disk_format)
773 else: #local path
774 with open(image_dict['location']) as fimage:
tiernob39d49e2017-08-02 14:02:15 +0200775 new_image = self.glancev1.images.create(name=image_dict['name'], is_public=image_dict.get('public',"yes")=="yes",
tierno7edb6752016-03-21 17:37:52 +0100776 container_format="bare", data=fimage, disk_format=disk_format)
777 #insert metadata. We cannot use 'new_image.properties.setdefault'
778 #because nova and glance are "INDEPENDENT" and we are using nova for reading metadata
779 new_image_nova=self.nova.images.find(id=new_image.id)
780 new_image_nova.metadata.setdefault('location',image_dict['location'])
781 metadata_to_load = image_dict.get('metadata')
782 if metadata_to_load:
783 for k,v in yaml.load(metadata_to_load).iteritems():
784 new_image_nova.metadata.setdefault(k,v)
tiernoae4a8d12016-07-08 12:30:39 +0200785 return new_image.id
786 except (nvExceptions.Conflict, ksExceptions.ClientException, nvExceptions.ClientException) as e:
787 self._format_exception(e)
tierno8e995ce2016-09-22 08:13:00 +0000788 except (HTTPException, gl1Exceptions.HTTPException, gl1Exceptions.CommunicationError, ConnectionError) as e:
tiernoae4a8d12016-07-08 12:30:39 +0200789 if retry==max_retries:
790 continue
791 self._format_exception(e)
tierno7edb6752016-03-21 17:37:52 +0100792 except IOError as e: #can not open the file
tiernoae4a8d12016-07-08 12:30:39 +0200793 raise vimconn.vimconnConnectionException(type(e).__name__ + ": " + str(e)+ " for " + image_dict['location'],
794 http_code=vimconn.HTTP_Bad_Request)
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +0000795
tiernoae4a8d12016-07-08 12:30:39 +0200796 def delete_image(self, image_id):
797 '''Deletes a tenant image from openstack VIM. Returns the old id
tierno7edb6752016-03-21 17:37:52 +0100798 '''
tiernoae4a8d12016-07-08 12:30:39 +0200799 try:
800 self._reload_connection()
801 self.nova.images.delete(image_id)
802 return image_id
tierno8e995ce2016-09-22 08:13:00 +0000803 except (nvExceptions.NotFound, ksExceptions.ClientException, nvExceptions.ClientException, gl1Exceptions.CommunicationError, ConnectionError) as e: #TODO remove
tiernoae4a8d12016-07-08 12:30:39 +0200804 self._format_exception(e)
805
806 def get_image_id_from_path(self, path):
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +0000807 '''Get the image id from image path in the VIM database. Returns the image_id'''
tiernoae4a8d12016-07-08 12:30:39 +0200808 try:
809 self._reload_connection()
810 images = self.nova.images.list()
811 for image in images:
812 if image.metadata.get("location")==path:
813 return image.id
814 raise vimconn.vimconnNotFoundException("image with location '{}' not found".format( path))
tierno8e995ce2016-09-22 08:13:00 +0000815 except (ksExceptions.ClientException, nvExceptions.ClientException, gl1Exceptions.CommunicationError, ConnectionError) as e:
tiernoae4a8d12016-07-08 12:30:39 +0200816 self._format_exception(e)
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +0000817
garciadeblasb69fa9f2016-09-28 12:04:10 +0200818 def get_image_list(self, filter_dict={}):
819 '''Obtain tenant images from VIM
820 Filter_dict can be:
821 id: image id
822 name: image name
823 checksum: image checksum
824 Returns the image list of dictionaries:
825 [{<the fields at Filter_dict plus some VIM specific>}, ...]
826 List can be empty
827 '''
828 self.logger.debug("Getting image list from VIM filter: '%s'", str(filter_dict))
829 try:
830 self._reload_connection()
831 filter_dict_os=filter_dict.copy()
832 #First we filter by the available filter fields: name, id. The others are removed.
833 filter_dict_os.pop('checksum',None)
834 image_list=self.nova.images.findall(**filter_dict_os)
835 if len(image_list)==0:
836 return []
837 #Then we filter by the rest of filter fields: checksum
838 filtered_list = []
839 for image in image_list:
tierno4540ea52017-01-18 17:44:32 +0100840 image_class=self.glance.images.get(image.id)
841 if 'checksum' not in filter_dict or image_class['checksum']==filter_dict.get('checksum'):
842 filtered_list.append(image_class.copy())
garciadeblasb69fa9f2016-09-28 12:04:10 +0200843 return filtered_list
844 except (ksExceptions.ClientException, nvExceptions.ClientException, gl1Exceptions.CommunicationError, ConnectionError) as e:
845 self._format_exception(e)
846
Pablo Montes Moreno6a7785b2017-07-03 10:44:30 +0200847 def __wait_for_vm(self, vm_id, status):
848 """wait until vm is in the desired status and return True.
849 If the VM gets in ERROR status, return false.
850 If the timeout is reached generate an exception"""
851 elapsed_time = 0
852 while elapsed_time < server_timeout:
853 vm_status = self.nova.servers.get(vm_id).status
854 if vm_status == status:
855 return True
856 if vm_status == 'ERROR':
857 return False
858 time.sleep(1)
859 elapsed_time += 1
860
861 # if we exceeded the timeout rollback
862 if elapsed_time >= server_timeout:
863 raise vimconn.vimconnException('Timeout waiting for instance ' + vm_id + ' to get ' + status,
864 http_code=vimconn.HTTP_Request_Timeout)
865
mirabal29356312017-07-27 12:21:22 +0200866 def _get_openstack_availablity_zones(self):
867 """
868 Get from openstack availability zones available
869 :return:
870 """
871 try:
872 openstack_availability_zone = self.nova.availability_zones.list()
873 openstack_availability_zone = [str(zone.zoneName) for zone in openstack_availability_zone
874 if zone.zoneName != 'internal']
875 return openstack_availability_zone
876 except Exception as e:
877 return None
878
879 def _set_availablity_zones(self):
880 """
881 Set vim availablity zone
882 :return:
883 """
884
885 if 'availability_zone' in self.config:
886 vim_availability_zones = self.config.get('availability_zone')
887 if isinstance(vim_availability_zones, str):
888 self.availability_zone = [vim_availability_zones]
889 elif isinstance(vim_availability_zones, list):
890 self.availability_zone = vim_availability_zones
891 else:
892 self.availability_zone = self._get_openstack_availablity_zones()
893
tierno5a3273c2017-08-29 11:43:46 +0200894 def _get_vm_availability_zone(self, availability_zone_index, availability_zone_list):
mirabal29356312017-07-27 12:21:22 +0200895 """
tierno5a3273c2017-08-29 11:43:46 +0200896 Return thge availability zone to be used by the created VM.
897 :return: The VIM availability zone to be used or None
mirabal29356312017-07-27 12:21:22 +0200898 """
tierno5a3273c2017-08-29 11:43:46 +0200899 if availability_zone_index is None:
900 if not self.config.get('availability_zone'):
901 return None
902 elif isinstance(self.config.get('availability_zone'), str):
903 return self.config['availability_zone']
904 else:
905 # TODO consider using a different parameter at config for default AV and AV list match
906 return self.config['availability_zone'][0]
mirabal29356312017-07-27 12:21:22 +0200907
tierno5a3273c2017-08-29 11:43:46 +0200908 vim_availability_zones = self.availability_zone
909 # check if VIM offer enough availability zones describe in the VNFD
910 if vim_availability_zones and len(availability_zone_list) <= len(vim_availability_zones):
911 # check if all the names of NFV AV match VIM AV names
912 match_by_index = False
913 for av in availability_zone_list:
914 if av not in vim_availability_zones:
915 match_by_index = True
916 break
917 if match_by_index:
918 return vim_availability_zones[availability_zone_index]
919 else:
920 return availability_zone_list[availability_zone_index]
mirabal29356312017-07-27 12:21:22 +0200921 else:
tierno5a3273c2017-08-29 11:43:46 +0200922 raise vimconn.vimconnConflictException("No enough availability zones at VIM for this deployment")
mirabal29356312017-07-27 12:21:22 +0200923
tierno5a3273c2017-08-29 11:43:46 +0200924 def new_vminstance(self, name, description, start, image_id, flavor_id, net_list, cloud_config=None, disk_list=None,
925 availability_zone_index=None, availability_zone_list=None):
tierno7edb6752016-03-21 17:37:52 +0100926 '''Adds a VM instance to VIM
927 Params:
928 start: indicates if VM must start or boot in pause mode. Ignored
929 image_id,flavor_id: iamge and flavor uuid
930 net_list: list of interfaces, each one is a dictionary with:
931 name:
932 net_id: network uuid to connect
933 vpci: virtual vcpi to assign, ignored because openstack lack #TODO
934 model: interface model, ignored #TODO
935 mac_address: used for SR-IOV ifaces #TODO for other types
936 use: 'data', 'bridge', 'mgmt'
937 type: 'virtual', 'PF', 'VF', 'VFnotShared'
938 vim_id: filled/added by this function
ahmadsaf853d452016-12-22 11:33:47 +0500939 floating_ip: True/False (or it can be None)
mirabal29356312017-07-27 12:21:22 +0200940 'cloud_config': (optional) dictionary with:
941 'key-pairs': (optional) list of strings with the public key to be inserted to the default user
942 'users': (optional) list of users to be inserted, each item is a dict with:
943 'name': (mandatory) user name,
944 'key-pairs': (optional) list of strings with the public key to be inserted to the user
945 'user-data': (optional) string is a text script to be passed directly to cloud-init
946 'config-files': (optional). List of files to be transferred. Each item is a dict with:
947 'dest': (mandatory) string with the destination absolute path
948 'encoding': (optional, by default text). Can be one of:
949 'b64', 'base64', 'gz', 'gz+b64', 'gz+base64', 'gzip+b64', 'gzip+base64'
950 'content' (mandatory): string with the content of the file
951 'permissions': (optional) string with file permissions, typically octal notation '0644'
952 'owner': (optional) file owner, string with the format 'owner:group'
953 'boot-data-drive': boolean to indicate if user-data must be passed using a boot drive (hard disk)
954 'disk_list': (optional) list with additional disks to the VM. Each item is a dict with:
955 'image_id': (optional). VIM id of an existing image. If not provided an empty disk must be mounted
956 'size': (mandatory) string with the size of the disk in GB
tierno5a3273c2017-08-29 11:43:46 +0200957 availability_zone_index: Index of availability_zone_list to use for this this VM. None if not AV required
958 availability_zone_list: list of availability zones given by user in the VNFD descriptor. Ignore if
959 availability_zone_index is None
tierno7edb6752016-03-21 17:37:52 +0100960 #TODO ip, security groups
tiernoae4a8d12016-07-08 12:30:39 +0200961 Returns the instance identifier
tierno7edb6752016-03-21 17:37:52 +0100962 '''
tiernofa51c202017-01-27 14:58:17 +0100963 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 +0100964 try:
Pablo Montes Moreno6a7785b2017-07-03 10:44:30 +0200965 server = None
tierno6e116232016-07-18 13:01:40 +0200966 metadata={}
tierno7edb6752016-03-21 17:37:52 +0100967 net_list_vim=[]
Pablo Montes Moreno6a7785b2017-07-03 10:44:30 +0200968 external_network=[] # list of external networks to be connected to instance, later on used to create floating_ip
969 no_secured_ports = [] # List of port-is with port-security disabled
tierno7edb6752016-03-21 17:37:52 +0100970 self._reload_connection()
Pablo Montes Moreno6a7785b2017-07-03 10:44:30 +0200971 metadata_vpci={} # For a specific neutron plugin
tiernob84cbdc2017-07-07 14:30:30 +0200972 block_device_mapping = None
tierno7edb6752016-03-21 17:37:52 +0100973 for net in net_list:
974 if not net.get("net_id"): #skip non connected iface
975 continue
Pablo Montes Moreno3be0b2a2017-03-30 13:22:15 +0200976
977 port_dict={
978 "network_id": net["net_id"],
979 "name": net.get("name"),
980 "admin_state_up": True
981 }
982 if net["type"]=="virtual":
983 if "vpci" in net:
984 metadata_vpci[ net["net_id"] ] = [[ net["vpci"], "" ]]
985 elif net["type"]=="VF": # for VF
986 if "vpci" in net:
987 if "VF" not in metadata_vpci:
988 metadata_vpci["VF"]=[]
989 metadata_vpci["VF"].append([ net["vpci"], "" ])
990 port_dict["binding:vnic_type"]="direct"
kate721d79b2017-06-24 04:21:38 -0700991 ########## VIO specific Changes #######
992 if self.vim_type == "VIO":
993 #Need to create port with port_security_enabled = False and no-security-groups
994 port_dict["port_security_enabled"]=False
995 port_dict["provider_security_groups"]=[]
996 port_dict["security_groups"]=[]
Pablo Montes Moreno3be0b2a2017-03-30 13:22:15 +0200997 else: #For PT
kate721d79b2017-06-24 04:21:38 -0700998 ########## VIO specific Changes #######
999 #Current VIO release does not support port with type 'direct-physical'
1000 #So no need to create virtual port in case of PCI-device.
1001 #Will update port_dict code when support gets added in next VIO release
1002 if self.vim_type == "VIO":
1003 raise vimconn.vimconnNotSupportedException("Current VIO release does not support full passthrough (PT)")
Pablo Montes Moreno3be0b2a2017-03-30 13:22:15 +02001004 if "vpci" in net:
1005 if "PF" not in metadata_vpci:
1006 metadata_vpci["PF"]=[]
1007 metadata_vpci["PF"].append([ net["vpci"], "" ])
1008 port_dict["binding:vnic_type"]="direct-physical"
1009 if not port_dict["name"]:
1010 port_dict["name"]=name
1011 if net.get("mac_address"):
1012 port_dict["mac_address"]=net["mac_address"]
Pablo Montes Moreno3be0b2a2017-03-30 13:22:15 +02001013 new_port = self.neutron.create_port({"port": port_dict })
1014 net["mac_adress"] = new_port["port"]["mac_address"]
1015 net["vim_id"] = new_port["port"]["id"]
tiernob84cbdc2017-07-07 14:30:30 +02001016 # if try to use a network without subnetwork, it will return a emtpy list
1017 fixed_ips = new_port["port"].get("fixed_ips")
1018 if fixed_ips:
1019 net["ip"] = fixed_ips[0].get("ip_address")
1020 else:
1021 net["ip"] = None
montesmoreno994a29d2017-08-22 11:23:06 +02001022
1023 port = {"port-id": new_port["port"]["id"]}
1024 if float(self.nova.api_version.get_string()) >= 2.32:
1025 port["tag"] = new_port["port"]["name"]
1026 net_list_vim.append(port)
Pablo Montes Moreno3be0b2a2017-03-30 13:22:15 +02001027
ahmadsaf853d452016-12-22 11:33:47 +05001028 if net.get('floating_ip', False):
tiernof8383b82017-01-18 15:49:48 +01001029 net['exit_on_floating_ip_error'] = True
ahmadsaf853d452016-12-22 11:33:47 +05001030 external_network.append(net)
tiernof8383b82017-01-18 15:49:48 +01001031 elif net['use'] == 'mgmt' and self.config.get('use_floating_ip'):
1032 net['exit_on_floating_ip_error'] = False
1033 external_network.append(net)
1034
Pablo Montes Moreno6a7785b2017-07-03 10:44:30 +02001035 # If port security is disabled when the port has not yet been attached to the VM, then all vm traffic is dropped.
1036 # As a workaround we wait until the VM is active and then disable the port-security
1037 if net.get("port_security") == False:
1038 no_secured_ports.append(new_port["port"]["id"])
1039
tierno7edb6752016-03-21 17:37:52 +01001040 if metadata_vpci:
1041 metadata = {"pci_assignement": json.dumps(metadata_vpci)}
tiernoafbced42016-07-23 01:43:53 +02001042 if len(metadata["pci_assignement"]) >255:
tierno6e116232016-07-18 13:01:40 +02001043 #limit the metadata size
1044 #metadata["pci_assignement"] = metadata["pci_assignement"][0:255]
1045 self.logger.warn("Metadata deleted since it exceeds the expected length (255) ")
1046 metadata = {}
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00001047
tiernoae4a8d12016-07-08 12:30:39 +02001048 self.logger.debug("name '%s' image_id '%s'flavor_id '%s' net_list_vim '%s' description '%s' metadata %s",
1049 name, image_id, flavor_id, str(net_list_vim), description, str(metadata))
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00001050
tierno7edb6752016-03-21 17:37:52 +01001051 security_groups = self.config.get('security_groups')
1052 if type(security_groups) is str:
1053 security_groups = ( security_groups, )
tierno36c0b172017-01-12 18:32:28 +01001054 #cloud config
tierno0a1437e2017-10-02 00:17:43 +02001055 config_drive, userdata = self._create_user_data(cloud_config)
montesmoreno0c8def02016-12-22 12:16:23 +00001056
1057 #Create additional volumes in case these are present in disk_list
montesmoreno0c8def02016-12-22 12:16:23 +00001058 base_disk_index = ord('b')
1059 if disk_list != None:
tiernob84cbdc2017-07-07 14:30:30 +02001060 block_device_mapping = {}
montesmoreno0c8def02016-12-22 12:16:23 +00001061 for disk in disk_list:
1062 if 'image_id' in disk:
1063 volume = self.cinder.volumes.create(size = disk['size'],name = name + '_vd' +
1064 chr(base_disk_index), imageRef = disk['image_id'])
1065 else:
1066 volume = self.cinder.volumes.create(size=disk['size'], name=name + '_vd' +
1067 chr(base_disk_index))
1068 block_device_mapping['_vd' + chr(base_disk_index)] = volume.id
1069 base_disk_index += 1
1070
1071 #wait until volumes are with status available
1072 keep_waiting = True
1073 elapsed_time = 0
1074 while keep_waiting and elapsed_time < volume_timeout:
1075 keep_waiting = False
1076 for volume_id in block_device_mapping.itervalues():
1077 if self.cinder.volumes.get(volume_id).status != 'available':
1078 keep_waiting = True
1079 if keep_waiting:
1080 time.sleep(1)
1081 elapsed_time += 1
1082
1083 #if we exceeded the timeout rollback
1084 if elapsed_time >= volume_timeout:
1085 #delete the volumes we just created
1086 for volume_id in block_device_mapping.itervalues():
1087 self.cinder.volumes.delete(volume_id)
1088
1089 #delete ports we just created
1090 for net_item in net_list_vim:
1091 if 'port-id' in net_item:
montesmorenocf227142017-01-12 12:24:21 +00001092 self.neutron.delete_port(net_item['port-id'])
montesmoreno0c8def02016-12-22 12:16:23 +00001093
1094 raise vimconn.vimconnException('Timeout creating volumes for instance ' + name,
1095 http_code=vimconn.HTTP_Request_Timeout)
mirabal29356312017-07-27 12:21:22 +02001096 # get availability Zone
tierno5a3273c2017-08-29 11:43:46 +02001097 vm_av_zone = self._get_vm_availability_zone(availability_zone_index, availability_zone_list)
montesmoreno0c8def02016-12-22 12:16:23 +00001098
mirabal29356312017-07-27 12:21:22 +02001099 self.logger.debug("nova.servers.create({}, {}, {}, nics={}, meta={}, security_groups={}, "
1100 "availability_zone={}, key_name={}, userdata={}, config_drive={}, "
1101 "block_device_mapping={})".format(name, image_id, flavor_id, net_list_vim, metadata,
1102 security_groups, vm_av_zone, self.config.get('keypair'),
1103 userdata, config_drive, block_device_mapping))
tierno7edb6752016-03-21 17:37:52 +01001104 server = self.nova.servers.create(name, image_id, flavor_id, nics=net_list_vim, meta=metadata,
montesmoreno0c8def02016-12-22 12:16:23 +00001105 security_groups=security_groups,
mirabal29356312017-07-27 12:21:22 +02001106 availability_zone=vm_av_zone,
montesmoreno0c8def02016-12-22 12:16:23 +00001107 key_name=self.config.get('keypair'),
1108 userdata=userdata,
tiernob84cbdc2017-07-07 14:30:30 +02001109 config_drive=config_drive,
1110 block_device_mapping=block_device_mapping
montesmoreno0c8def02016-12-22 12:16:23 +00001111 ) # , description=description)
Pablo Montes Moreno6a7785b2017-07-03 10:44:30 +02001112
1113 # Previously mentioned workaround to wait until the VM is active and then disable the port-security
1114 if no_secured_ports:
1115 self.__wait_for_vm(server.id, 'ACTIVE')
1116
1117 for port_id in no_secured_ports:
1118 try:
1119 self.neutron.update_port(port_id, {"port": {"port_security_enabled": False, "security_groups": None} })
1120
1121 except Exception as e:
1122 self.logger.error("It was not possible to disable port security for port {}".format(port_id))
1123 self.delete_vminstance(server.id)
1124 raise
1125
tiernoae4a8d12016-07-08 12:30:39 +02001126 #print "DONE :-)", server
ahmadsaf853d452016-12-22 11:33:47 +05001127 pool_id = None
1128 floating_ips = self.neutron.list_floatingips().get("floatingips", ())
Pablo Montes Moreno6a7785b2017-07-03 10:44:30 +02001129
1130 if external_network:
1131 self.__wait_for_vm(server.id, 'ACTIVE')
1132
ahmadsaf853d452016-12-22 11:33:47 +05001133 for floating_network in external_network:
tiernof8383b82017-01-18 15:49:48 +01001134 try:
tiernof8383b82017-01-18 15:49:48 +01001135 assigned = False
1136 while(assigned == False):
1137 if floating_ips:
1138 ip = floating_ips.pop(0)
1139 if not ip.get("port_id", False) and ip.get('tenant_id') == server.tenant_id:
1140 free_floating_ip = ip.get("floating_ip_address")
1141 try:
1142 fix_ip = floating_network.get('ip')
1143 server.add_floating_ip(free_floating_ip, fix_ip)
1144 assigned = True
1145 except Exception as e:
1146 raise vimconn.vimconnException(type(e).__name__ + ": Cannot create floating_ip "+ str(e), http_code=vimconn.HTTP_Conflict)
1147 else:
1148 #Find the external network
1149 external_nets = list()
1150 for net in self.neutron.list_networks()['networks']:
1151 if net['router:external']:
1152 external_nets.append(net)
1153
1154 if len(external_nets) == 0:
1155 raise vimconn.vimconnException("Cannot create floating_ip automatically since no external "
1156 "network is present",
1157 http_code=vimconn.HTTP_Conflict)
1158 if len(external_nets) > 1:
1159 raise vimconn.vimconnException("Cannot create floating_ip automatically since multiple "
1160 "external networks are present",
1161 http_code=vimconn.HTTP_Conflict)
1162
1163 pool_id = external_nets[0].get('id')
1164 param = {'floatingip': {'floating_network_id': pool_id, 'tenant_id': server.tenant_id}}
ahmadsaf853d452016-12-22 11:33:47 +05001165 try:
tiernof8383b82017-01-18 15:49:48 +01001166 #self.logger.debug("Creating floating IP")
1167 new_floating_ip = self.neutron.create_floatingip(param)
1168 free_floating_ip = new_floating_ip['floatingip']['floating_ip_address']
ahmadsaf853d452016-12-22 11:33:47 +05001169 fix_ip = floating_network.get('ip')
1170 server.add_floating_ip(free_floating_ip, fix_ip)
tiernof8383b82017-01-18 15:49:48 +01001171 assigned=True
ahmadsaf853d452016-12-22 11:33:47 +05001172 except Exception as e:
tiernof8383b82017-01-18 15:49:48 +01001173 raise vimconn.vimconnException(type(e).__name__ + ": Cannot assign floating_ip "+ str(e), http_code=vimconn.HTTP_Conflict)
1174 except Exception as e:
1175 if not floating_network['exit_on_floating_ip_error']:
1176 self.logger.warn("Cannot create floating_ip. %s", str(e))
1177 continue
tiernof8383b82017-01-18 15:49:48 +01001178 raise
montesmoreno2a1fc4e2017-01-09 16:46:04 +00001179
tiernoae4a8d12016-07-08 12:30:39 +02001180 return server.id
tierno7edb6752016-03-21 17:37:52 +01001181# except nvExceptions.NotFound as e:
1182# error_value=-vimconn.HTTP_Not_Found
1183# error_text= "vm instance %s not found" % vm_id
Pablo Montes Moreno6a7785b2017-07-03 10:44:30 +02001184# except TypeError as e:
1185# raise vimconn.vimconnException(type(e).__name__ + ": "+ str(e), http_code=vimconn.HTTP_Bad_Request)
1186
1187 except Exception as e:
montesmoreno2a1fc4e2017-01-09 16:46:04 +00001188 # delete the volumes we just created
tiernob84cbdc2017-07-07 14:30:30 +02001189 if block_device_mapping:
montesmoreno2a1fc4e2017-01-09 16:46:04 +00001190 for volume_id in block_device_mapping.itervalues():
1191 self.cinder.volumes.delete(volume_id)
1192
Pablo Montes Moreno6a7785b2017-07-03 10:44:30 +02001193 # Delete the VM
1194 if server != None:
1195 self.delete_vminstance(server.id)
1196 else:
1197 # delete ports we just created
1198 for net_item in net_list_vim:
1199 if 'port-id' in net_item:
1200 self.neutron.delete_port(net_item['port-id'])
1201
tiernoae4a8d12016-07-08 12:30:39 +02001202 self._format_exception(e)
tierno7edb6752016-03-21 17:37:52 +01001203
tiernoae4a8d12016-07-08 12:30:39 +02001204 def get_vminstance(self,vm_id):
tierno7edb6752016-03-21 17:37:52 +01001205 '''Returns the VM instance information from VIM'''
tiernoae4a8d12016-07-08 12:30:39 +02001206 #self.logger.debug("Getting VM from VIM")
tierno7edb6752016-03-21 17:37:52 +01001207 try:
1208 self._reload_connection()
1209 server = self.nova.servers.find(id=vm_id)
1210 #TODO parse input and translate to VIM format (openmano_schemas.new_vminstance_response_schema)
tiernoae4a8d12016-07-08 12:30:39 +02001211 return server.to_dict()
tierno8e995ce2016-09-22 08:13:00 +00001212 except (ksExceptions.ClientException, nvExceptions.ClientException, nvExceptions.NotFound, ConnectionError) as e:
tiernoae4a8d12016-07-08 12:30:39 +02001213 self._format_exception(e)
1214
1215 def get_vminstance_console(self,vm_id, console_type="vnc"):
tierno7edb6752016-03-21 17:37:52 +01001216 '''
1217 Get a console for the virtual machine
1218 Params:
1219 vm_id: uuid of the VM
1220 console_type, can be:
1221 "novnc" (by default), "xvpvnc" for VNC types,
1222 "rdp-html5" for RDP types, "spice-html5" for SPICE types
tiernoae4a8d12016-07-08 12:30:39 +02001223 Returns dict with the console parameters:
1224 protocol: ssh, ftp, http, https, ...
1225 server: usually ip address
1226 port: the http, ssh, ... port
1227 suffix: extra text, e.g. the http path and query string
tierno7edb6752016-03-21 17:37:52 +01001228 '''
tiernoae4a8d12016-07-08 12:30:39 +02001229 self.logger.debug("Getting VM CONSOLE from VIM")
tierno7edb6752016-03-21 17:37:52 +01001230 try:
1231 self._reload_connection()
1232 server = self.nova.servers.find(id=vm_id)
1233 if console_type == None or console_type == "novnc":
1234 console_dict = server.get_vnc_console("novnc")
1235 elif console_type == "xvpvnc":
1236 console_dict = server.get_vnc_console(console_type)
1237 elif console_type == "rdp-html5":
1238 console_dict = server.get_rdp_console(console_type)
1239 elif console_type == "spice-html5":
1240 console_dict = server.get_spice_console(console_type)
1241 else:
tiernoae4a8d12016-07-08 12:30:39 +02001242 raise vimconn.vimconnException("console type '{}' not allowed".format(console_type), http_code=vimconn.HTTP_Bad_Request)
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00001243
tierno7edb6752016-03-21 17:37:52 +01001244 console_dict1 = console_dict.get("console")
1245 if console_dict1:
1246 console_url = console_dict1.get("url")
1247 if console_url:
1248 #parse console_url
1249 protocol_index = console_url.find("//")
1250 suffix_index = console_url[protocol_index+2:].find("/") + protocol_index+2
1251 port_index = console_url[protocol_index+2:suffix_index].find(":") + protocol_index+2
1252 if protocol_index < 0 or port_index<0 or suffix_index<0:
1253 return -vimconn.HTTP_Internal_Server_Error, "Unexpected response from VIM"
1254 console_dict={"protocol": console_url[0:protocol_index],
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00001255 "server": console_url[protocol_index+2:port_index],
1256 "port": console_url[port_index:suffix_index],
1257 "suffix": console_url[suffix_index+1:]
tierno7edb6752016-03-21 17:37:52 +01001258 }
1259 protocol_index += 2
tiernoae4a8d12016-07-08 12:30:39 +02001260 return console_dict
1261 raise vimconn.vimconnUnexpectedResponse("Unexpected response from VIM")
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00001262
tierno8e995ce2016-09-22 08:13:00 +00001263 except (nvExceptions.NotFound, ksExceptions.ClientException, nvExceptions.ClientException, nvExceptions.BadRequest, ConnectionError) as e:
tiernoae4a8d12016-07-08 12:30:39 +02001264 self._format_exception(e)
tierno7edb6752016-03-21 17:37:52 +01001265
tiernoae4a8d12016-07-08 12:30:39 +02001266 def delete_vminstance(self, vm_id):
1267 '''Removes a VM instance from VIM. Returns the old identifier
tierno7edb6752016-03-21 17:37:52 +01001268 '''
tiernoae4a8d12016-07-08 12:30:39 +02001269 #print "osconnector: Getting VM from VIM"
tierno7edb6752016-03-21 17:37:52 +01001270 try:
1271 self._reload_connection()
1272 #delete VM ports attached to this networks before the virtual machine
1273 ports = self.neutron.list_ports(device_id=vm_id)
1274 for p in ports['ports']:
1275 try:
1276 self.neutron.delete_port(p["id"])
1277 except Exception as e:
tiernoae4a8d12016-07-08 12:30:39 +02001278 self.logger.error("Error deleting port: " + type(e).__name__ + ": "+ str(e))
montesmoreno0c8def02016-12-22 12:16:23 +00001279
1280 #commented because detaching the volumes makes the servers.delete not work properly ?!?
1281 #dettach volumes attached
1282 server = self.nova.servers.get(vm_id)
1283 volumes_attached_dict = server._info['os-extended-volumes:volumes_attached']
1284 #for volume in volumes_attached_dict:
1285 # self.cinder.volumes.detach(volume['id'])
1286
tierno7edb6752016-03-21 17:37:52 +01001287 self.nova.servers.delete(vm_id)
montesmoreno0c8def02016-12-22 12:16:23 +00001288
1289 #delete volumes.
1290 #Although having detached them should have them in active status
1291 #we ensure in this loop
1292 keep_waiting = True
1293 elapsed_time = 0
1294 while keep_waiting and elapsed_time < volume_timeout:
1295 keep_waiting = False
1296 for volume in volumes_attached_dict:
1297 if self.cinder.volumes.get(volume['id']).status != 'available':
1298 keep_waiting = True
1299 else:
1300 self.cinder.volumes.delete(volume['id'])
1301 if keep_waiting:
1302 time.sleep(1)
1303 elapsed_time += 1
1304
tiernoae4a8d12016-07-08 12:30:39 +02001305 return vm_id
tierno8e995ce2016-09-22 08:13:00 +00001306 except (nvExceptions.NotFound, ksExceptions.ClientException, nvExceptions.ClientException, ConnectionError) as e:
tiernoae4a8d12016-07-08 12:30:39 +02001307 self._format_exception(e)
tierno7edb6752016-03-21 17:37:52 +01001308 #TODO insert exception vimconn.HTTP_Unauthorized
1309 #if reaching here is because an exception
tierno7edb6752016-03-21 17:37:52 +01001310
tiernoae4a8d12016-07-08 12:30:39 +02001311 def refresh_vms_status(self, vm_list):
1312 '''Get the status of the virtual machines and their interfaces/ports
1313 Params: the list of VM identifiers
1314 Returns a dictionary with:
1315 vm_id: #VIM id of this Virtual Machine
1316 status: #Mandatory. Text with one of:
1317 # DELETED (not found at vim)
1318 # VIM_ERROR (Cannot connect to VIM, VIM response error, ...)
1319 # OTHER (Vim reported other status not understood)
1320 # ERROR (VIM indicates an ERROR status)
1321 # ACTIVE, PAUSED, SUSPENDED, INACTIVE (not running),
1322 # CREATING (on building process), ERROR
1323 # ACTIVE:NoMgmtIP (Active but any of its interface has an IP address
1324 #
1325 error_msg: #Text with VIM error message, if any. Or the VIM connection ERROR
1326 vim_info: #Text with plain information obtained from vim (yaml.safe_dump)
1327 interfaces:
1328 - vim_info: #Text with plain information obtained from vim (yaml.safe_dump)
1329 mac_address: #Text format XX:XX:XX:XX:XX:XX
1330 vim_net_id: #network id where this interface is connected
1331 vim_interface_id: #interface/port VIM id
1332 ip_address: #null, or text with IPv4, IPv6 address
tierno867ffe92017-03-27 12:50:34 +02001333 compute_node: #identification of compute node where PF,VF interface is allocated
1334 pci: #PCI address of the NIC that hosts the PF,VF
1335 vlan: #physical VLAN used for VF
tierno7edb6752016-03-21 17:37:52 +01001336 '''
tiernoae4a8d12016-07-08 12:30:39 +02001337 vm_dict={}
1338 self.logger.debug("refresh_vms status: Getting tenant VM instance information from VIM")
1339 for vm_id in vm_list:
1340 vm={}
1341 try:
1342 vm_vim = self.get_vminstance(vm_id)
1343 if vm_vim['status'] in vmStatus2manoFormat:
1344 vm['status'] = vmStatus2manoFormat[ vm_vim['status'] ]
tierno7edb6752016-03-21 17:37:52 +01001345 else:
tiernoae4a8d12016-07-08 12:30:39 +02001346 vm['status'] = "OTHER"
1347 vm['error_msg'] = "VIM status reported " + vm_vim['status']
tierno8e995ce2016-09-22 08:13:00 +00001348 try:
1349 vm['vim_info'] = yaml.safe_dump(vm_vim, default_flow_style=True, width=256)
1350 except yaml.representer.RepresenterError:
1351 vm['vim_info'] = str(vm_vim)
tiernoae4a8d12016-07-08 12:30:39 +02001352 vm["interfaces"] = []
1353 if vm_vim.get('fault'):
1354 vm['error_msg'] = str(vm_vim['fault'])
1355 #get interfaces
tierno7edb6752016-03-21 17:37:52 +01001356 try:
tiernoae4a8d12016-07-08 12:30:39 +02001357 self._reload_connection()
1358 port_dict=self.neutron.list_ports(device_id=vm_id)
1359 for port in port_dict["ports"]:
1360 interface={}
tierno8e995ce2016-09-22 08:13:00 +00001361 try:
1362 interface['vim_info'] = yaml.safe_dump(port, default_flow_style=True, width=256)
1363 except yaml.representer.RepresenterError:
1364 interface['vim_info'] = str(port)
tiernoae4a8d12016-07-08 12:30:39 +02001365 interface["mac_address"] = port.get("mac_address")
1366 interface["vim_net_id"] = port["network_id"]
1367 interface["vim_interface_id"] = port["id"]
Mike Marchetti5b9da422017-05-02 15:35:47 -04001368 # check if OS-EXT-SRV-ATTR:host is there,
1369 # in case of non-admin credentials, it will be missing
1370 if vm_vim.get('OS-EXT-SRV-ATTR:host'):
1371 interface["compute_node"] = vm_vim['OS-EXT-SRV-ATTR:host']
tierno867ffe92017-03-27 12:50:34 +02001372 interface["pci"] = None
Mike Marchetti5b9da422017-05-02 15:35:47 -04001373
1374 # check if binding:profile is there,
1375 # in case of non-admin credentials, it will be missing
1376 if port.get('binding:profile'):
1377 if port['binding:profile'].get('pci_slot'):
1378 # TODO: At the moment sr-iov pci addresses are converted to PF pci addresses by setting the slot to 0x00
1379 # TODO: This is just a workaround valid for niantinc. Find a better way to do so
1380 # CHANGE DDDD:BB:SS.F to DDDD:BB:00.(F%2) assuming there are 2 ports per nic
1381 pci = port['binding:profile']['pci_slot']
1382 # interface["pci"] = pci[:-4] + "00." + str(int(pci[-1]) % 2)
1383 interface["pci"] = pci
tierno867ffe92017-03-27 12:50:34 +02001384 interface["vlan"] = None
Pablo Montes Moreno3be0b2a2017-03-30 13:22:15 +02001385 #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 +01001386 network = self.neutron.show_network(port["network_id"])
Pablo Montes Moreno3be0b2a2017-03-30 13:22:15 +02001387 if network['network'].get('provider:network_type') == 'vlan' and \
1388 port.get("binding:vnic_type") == "direct":
tierno867ffe92017-03-27 12:50:34 +02001389 interface["vlan"] = network['network'].get('provider:segmentation_id')
tiernoae4a8d12016-07-08 12:30:39 +02001390 ips=[]
1391 #look for floating ip address
1392 floating_ip_dict = self.neutron.list_floatingips(port_id=port["id"])
1393 if floating_ip_dict.get("floatingips"):
1394 ips.append(floating_ip_dict["floatingips"][0].get("floating_ip_address") )
tierno7edb6752016-03-21 17:37:52 +01001395
tiernoae4a8d12016-07-08 12:30:39 +02001396 for subnet in port["fixed_ips"]:
1397 ips.append(subnet["ip_address"])
1398 interface["ip_address"] = ";".join(ips)
1399 vm["interfaces"].append(interface)
1400 except Exception as e:
1401 self.logger.error("Error getting vm interface information " + type(e).__name__ + ": "+ str(e))
1402 except vimconn.vimconnNotFoundException as e:
1403 self.logger.error("Exception getting vm status: %s", str(e))
1404 vm['status'] = "DELETED"
1405 vm['error_msg'] = str(e)
1406 except vimconn.vimconnException as e:
1407 self.logger.error("Exception getting vm status: %s", str(e))
1408 vm['status'] = "VIM_ERROR"
1409 vm['error_msg'] = str(e)
1410 vm_dict[vm_id] = vm
1411 return vm_dict
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00001412
tiernoae4a8d12016-07-08 12:30:39 +02001413 def action_vminstance(self, vm_id, action_dict):
tierno7edb6752016-03-21 17:37:52 +01001414 '''Send and action over a VM instance from VIM
tiernoae4a8d12016-07-08 12:30:39 +02001415 Returns the vm_id if the action was successfully sent to the VIM'''
1416 self.logger.debug("Action over VM '%s': %s", vm_id, str(action_dict))
tierno7edb6752016-03-21 17:37:52 +01001417 try:
1418 self._reload_connection()
1419 server = self.nova.servers.find(id=vm_id)
1420 if "start" in action_dict:
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00001421 if action_dict["start"]=="rebuild":
tierno7edb6752016-03-21 17:37:52 +01001422 server.rebuild()
1423 else:
1424 if server.status=="PAUSED":
1425 server.unpause()
1426 elif server.status=="SUSPENDED":
1427 server.resume()
1428 elif server.status=="SHUTOFF":
1429 server.start()
1430 elif "pause" in action_dict:
1431 server.pause()
1432 elif "resume" in action_dict:
1433 server.resume()
1434 elif "shutoff" in action_dict or "shutdown" in action_dict:
1435 server.stop()
1436 elif "forceOff" in action_dict:
1437 server.stop() #TODO
1438 elif "terminate" in action_dict:
1439 server.delete()
1440 elif "createImage" in action_dict:
1441 server.create_image()
1442 #"path":path_schema,
1443 #"description":description_schema,
1444 #"name":name_schema,
1445 #"metadata":metadata_schema,
1446 #"imageRef": id_schema,
1447 #"disk": {"oneOf":[{"type": "null"}, {"type":"string"}] },
1448 elif "rebuild" in action_dict:
1449 server.rebuild(server.image['id'])
1450 elif "reboot" in action_dict:
1451 server.reboot() #reboot_type='SOFT'
1452 elif "console" in action_dict:
1453 console_type = action_dict["console"]
1454 if console_type == None or console_type == "novnc":
1455 console_dict = server.get_vnc_console("novnc")
1456 elif console_type == "xvpvnc":
1457 console_dict = server.get_vnc_console(console_type)
1458 elif console_type == "rdp-html5":
1459 console_dict = server.get_rdp_console(console_type)
1460 elif console_type == "spice-html5":
1461 console_dict = server.get_spice_console(console_type)
1462 else:
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00001463 raise vimconn.vimconnException("console type '{}' not allowed".format(console_type),
tiernoae4a8d12016-07-08 12:30:39 +02001464 http_code=vimconn.HTTP_Bad_Request)
tierno7edb6752016-03-21 17:37:52 +01001465 try:
1466 console_url = console_dict["console"]["url"]
1467 #parse console_url
1468 protocol_index = console_url.find("//")
1469 suffix_index = console_url[protocol_index+2:].find("/") + protocol_index+2
1470 port_index = console_url[protocol_index+2:suffix_index].find(":") + protocol_index+2
1471 if protocol_index < 0 or port_index<0 or suffix_index<0:
tiernoae4a8d12016-07-08 12:30:39 +02001472 raise vimconn.vimconnException("Unexpected response from VIM " + str(console_dict))
tierno7edb6752016-03-21 17:37:52 +01001473 console_dict2={"protocol": console_url[0:protocol_index],
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00001474 "server": console_url[protocol_index+2 : port_index],
1475 "port": int(console_url[port_index+1 : suffix_index]),
1476 "suffix": console_url[suffix_index+1:]
tierno7edb6752016-03-21 17:37:52 +01001477 }
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00001478 return console_dict2
tiernoae4a8d12016-07-08 12:30:39 +02001479 except Exception as e:
1480 raise vimconn.vimconnException("Unexpected response from VIM " + str(console_dict))
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00001481
tiernoae4a8d12016-07-08 12:30:39 +02001482 return vm_id
tierno8e995ce2016-09-22 08:13:00 +00001483 except (ksExceptions.ClientException, nvExceptions.ClientException, nvExceptions.NotFound, ConnectionError) as e:
tiernoae4a8d12016-07-08 12:30:39 +02001484 self._format_exception(e)
1485 #TODO insert exception vimconn.HTTP_Unauthorized
1486
kate721d79b2017-06-24 04:21:38 -07001487 ####### VIO Specific Changes #########
1488 def _genrate_vlanID(self):
1489 """
1490 Method to get unused vlanID
1491 Args:
1492 None
1493 Returns:
1494 vlanID
1495 """
1496 #Get used VLAN IDs
1497 usedVlanIDs = []
1498 networks = self.get_network_list()
1499 for net in networks:
1500 if net.get('provider:segmentation_id'):
1501 usedVlanIDs.append(net.get('provider:segmentation_id'))
1502 used_vlanIDs = set(usedVlanIDs)
1503
1504 #find unused VLAN ID
1505 for vlanID_range in self.config.get('dataplane_net_vlan_range'):
1506 try:
1507 start_vlanid , end_vlanid = map(int, vlanID_range.replace(" ", "").split("-"))
1508 for vlanID in xrange(start_vlanid, end_vlanid + 1):
1509 if vlanID not in used_vlanIDs:
1510 return vlanID
1511 except Exception as exp:
1512 raise vimconn.vimconnException("Exception {} occurred while generating VLAN ID.".format(exp))
1513 else:
1514 raise vimconn.vimconnConflictException("Unable to create the SRIOV VLAN network."\
1515 " All given Vlan IDs {} are in use.".format(self.config.get('dataplane_net_vlan_range')))
1516
1517
1518 def _validate_vlan_ranges(self, dataplane_net_vlan_range):
1519 """
1520 Method to validate user given vlanID ranges
1521 Args: None
1522 Returns: None
1523 """
1524 for vlanID_range in dataplane_net_vlan_range:
1525 vlan_range = vlanID_range.replace(" ", "")
1526 #validate format
1527 vlanID_pattern = r'(\d)*-(\d)*$'
1528 match_obj = re.match(vlanID_pattern, vlan_range)
1529 if not match_obj:
1530 raise vimconn.vimconnConflictException("Invalid dataplane_net_vlan_range {}.You must provide "\
1531 "'dataplane_net_vlan_range' in format [start_ID - end_ID].".format(vlanID_range))
1532
1533 start_vlanid , end_vlanid = map(int,vlan_range.split("-"))
1534 if start_vlanid <= 0 :
1535 raise vimconn.vimconnConflictException("Invalid dataplane_net_vlan_range {}."\
1536 "Start ID can not be zero. For VLAN "\
1537 "networks valid IDs are 1 to 4094 ".format(vlanID_range))
1538 if end_vlanid > 4094 :
1539 raise vimconn.vimconnConflictException("Invalid dataplane_net_vlan_range {}."\
1540 "End VLAN ID can not be greater than 4094. For VLAN "\
1541 "networks valid IDs are 1 to 4094 ".format(vlanID_range))
1542
1543 if start_vlanid > end_vlanid:
1544 raise vimconn.vimconnConflictException("Invalid dataplane_net_vlan_range {}."\
1545 "You must provide a 'dataplane_net_vlan_range' in format start_ID - end_ID and "\
1546 "start_ID < end_ID ".format(vlanID_range))
1547
tiernoae4a8d12016-07-08 12:30:39 +02001548#NOT USED FUNCTIONS
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00001549
tiernoae4a8d12016-07-08 12:30:39 +02001550 def new_external_port(self, port_data):
1551 #TODO openstack if needed
1552 '''Adds a external port to VIM'''
1553 '''Returns the port identifier'''
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00001554 return -vimconn.HTTP_Internal_Server_Error, "osconnector.new_external_port() not implemented"
1555
tiernoae4a8d12016-07-08 12:30:39 +02001556 def connect_port_network(self, port_id, network_id, admin=False):
1557 #TODO openstack if needed
1558 '''Connects a external port to a network'''
1559 '''Returns status code of the VIM response'''
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00001560 return -vimconn.HTTP_Internal_Server_Error, "osconnector.connect_port_network() not implemented"
1561
tiernoae4a8d12016-07-08 12:30:39 +02001562 def new_user(self, user_name, user_passwd, tenant_id=None):
1563 '''Adds a new user to openstack VIM'''
1564 '''Returns the user identifier'''
1565 self.logger.debug("osconnector: Adding a new user to VIM")
1566 try:
1567 self._reload_connection()
1568 user=self.keystone.users.create(user_name, user_passwd, tenant_id=tenant_id)
1569 #self.keystone.tenants.add_user(self.k_creds["username"], #role)
1570 return user.id
1571 except ksExceptions.ConnectionError as e:
1572 error_value=-vimconn.HTTP_Bad_Request
1573 error_text= type(e).__name__ + ": "+ (str(e) if len(e.args)==0 else str(e.args[0]))
1574 except ksExceptions.ClientException as e: #TODO remove
tierno7edb6752016-03-21 17:37:52 +01001575 error_value=-vimconn.HTTP_Bad_Request
1576 error_text= type(e).__name__ + ": "+ (str(e) if len(e.args)==0 else str(e.args[0]))
1577 #TODO insert exception vimconn.HTTP_Unauthorized
1578 #if reaching here is because an exception
1579 if self.debug:
tiernoae4a8d12016-07-08 12:30:39 +02001580 self.logger.debug("new_user " + error_text)
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00001581 return error_value, error_text
tiernoae4a8d12016-07-08 12:30:39 +02001582
1583 def delete_user(self, user_id):
1584 '''Delete a user from openstack VIM'''
1585 '''Returns the user identifier'''
1586 if self.debug:
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00001587 print("osconnector: Deleting a user from VIM")
tiernoae4a8d12016-07-08 12:30:39 +02001588 try:
1589 self._reload_connection()
1590 self.keystone.users.delete(user_id)
1591 return 1, user_id
1592 except ksExceptions.ConnectionError as e:
1593 error_value=-vimconn.HTTP_Bad_Request
1594 error_text= type(e).__name__ + ": "+ (str(e) if len(e.args)==0 else str(e.args[0]))
1595 except ksExceptions.NotFound as e:
1596 error_value=-vimconn.HTTP_Not_Found
1597 error_text= type(e).__name__ + ": "+ (str(e) if len(e.args)==0 else str(e.args[0]))
1598 except ksExceptions.ClientException as e: #TODO remove
1599 error_value=-vimconn.HTTP_Bad_Request
1600 error_text= type(e).__name__ + ": "+ (str(e) if len(e.args)==0 else str(e.args[0]))
1601 #TODO insert exception vimconn.HTTP_Unauthorized
1602 #if reaching here is because an exception
1603 if self.debug:
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00001604 print("delete_tenant " + error_text)
tiernoae4a8d12016-07-08 12:30:39 +02001605 return error_value, error_text
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00001606
tierno7edb6752016-03-21 17:37:52 +01001607 def get_hosts_info(self):
1608 '''Get the information of deployed hosts
1609 Returns the hosts content'''
1610 if self.debug:
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00001611 print("osconnector: Getting Host info from VIM")
tierno7edb6752016-03-21 17:37:52 +01001612 try:
1613 h_list=[]
1614 self._reload_connection()
1615 hypervisors = self.nova.hypervisors.list()
1616 for hype in hypervisors:
1617 h_list.append( hype.to_dict() )
1618 return 1, {"hosts":h_list}
1619 except nvExceptions.NotFound as e:
1620 error_value=-vimconn.HTTP_Not_Found
1621 error_text= (str(e) if len(e.args)==0 else str(e.args[0]))
1622 except (ksExceptions.ClientException, nvExceptions.ClientException) as e:
1623 error_value=-vimconn.HTTP_Bad_Request
1624 error_text= type(e).__name__ + ": "+ (str(e) if len(e.args)==0 else str(e.args[0]))
1625 #TODO insert exception vimconn.HTTP_Unauthorized
1626 #if reaching here is because an exception
1627 if self.debug:
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00001628 print("get_hosts_info " + error_text)
1629 return error_value, error_text
tierno7edb6752016-03-21 17:37:52 +01001630
1631 def get_hosts(self, vim_tenant):
1632 '''Get the hosts and deployed instances
1633 Returns the hosts content'''
1634 r, hype_dict = self.get_hosts_info()
1635 if r<0:
1636 return r, hype_dict
1637 hypervisors = hype_dict["hosts"]
1638 try:
1639 servers = self.nova.servers.list()
1640 for hype in hypervisors:
1641 for server in servers:
1642 if server.to_dict()['OS-EXT-SRV-ATTR:hypervisor_hostname']==hype['hypervisor_hostname']:
1643 if 'vm' in hype:
1644 hype['vm'].append(server.id)
1645 else:
1646 hype['vm'] = [server.id]
1647 return 1, hype_dict
1648 except nvExceptions.NotFound as e:
1649 error_value=-vimconn.HTTP_Not_Found
1650 error_text= (str(e) if len(e.args)==0 else str(e.args[0]))
1651 except (ksExceptions.ClientException, nvExceptions.ClientException) as e:
1652 error_value=-vimconn.HTTP_Bad_Request
1653 error_text= type(e).__name__ + ": "+ (str(e) if len(e.args)==0 else str(e.args[0]))
1654 #TODO insert exception vimconn.HTTP_Unauthorized
1655 #if reaching here is because an exception
1656 if self.debug:
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00001657 print("get_hosts " + error_text)
1658 return error_value, error_text
tierno7edb6752016-03-21 17:37:52 +01001659
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00001660 def new_classification(self, name, ctype, definition):
1661 self.logger.debug(
1662 'Adding a new (Traffic) Classification to VIM, named %s', name)
1663 try:
1664 new_class = None
1665 self._reload_connection()
1666 if ctype not in supportedClassificationTypes:
1667 raise vimconn.vimconnNotSupportedException(
1668 'OpenStack VIM connector doesn\'t support provided '
1669 'Classification Type {}, supported ones are: '
1670 '{}'.format(ctype, supportedClassificationTypes))
1671 if not self._validate_classification(ctype, definition):
1672 raise vimconn.vimconnException(
1673 'Incorrect Classification definition '
1674 'for the type specified.')
1675 classification_dict = definition
1676 classification_dict['name'] = name
tierno7edb6752016-03-21 17:37:52 +01001677
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00001678 new_class = self.neutron.create_flow_classifier(
1679 {'flow_classifier': classification_dict})
1680 return new_class['flow_classifier']['id']
1681 except (neExceptions.ConnectionFailed, ksExceptions.ClientException,
1682 neExceptions.NeutronException, ConnectionError) as e:
1683 self.logger.error(
1684 'Creation of Classification failed.')
1685 self._format_exception(e)
1686
1687 def get_classification(self, class_id):
1688 self.logger.debug(" Getting Classification %s from VIM", class_id)
1689 filter_dict = {"id": class_id}
1690 class_list = self.get_classification_list(filter_dict)
1691 if len(class_list) == 0:
1692 raise vimconn.vimconnNotFoundException(
1693 "Classification '{}' not found".format(class_id))
1694 elif len(class_list) > 1:
1695 raise vimconn.vimconnConflictException(
1696 "Found more than one Classification with this criteria")
1697 classification = class_list[0]
1698 return classification
1699
1700 def get_classification_list(self, filter_dict={}):
1701 self.logger.debug("Getting Classifications from VIM filter: '%s'",
1702 str(filter_dict))
1703 try:
1704 self._reload_connection()
1705 if self.api_version3 and "tenant_id" in filter_dict:
1706 filter_dict['project_id'] = filter_dict.pop('tenant_id')
1707 classification_dict = self.neutron.list_flow_classifier(
1708 **filter_dict)
1709 classification_list = classification_dict["flow_classifiers"]
1710 self.__classification_os2mano(classification_list)
1711 return classification_list
1712 except (neExceptions.ConnectionFailed, ksExceptions.ClientException,
1713 neExceptions.NeutronException, ConnectionError) as e:
1714 self._format_exception(e)
1715
1716 def delete_classification(self, class_id):
1717 self.logger.debug("Deleting Classification '%s' from VIM", class_id)
1718 try:
1719 self._reload_connection()
1720 self.neutron.delete_flow_classifier(class_id)
1721 return class_id
1722 except (neExceptions.ConnectionFailed, neExceptions.NeutronException,
1723 ksExceptions.ClientException, neExceptions.NeutronException,
1724 ConnectionError) as e:
1725 self._format_exception(e)
1726
1727 def new_sfi(self, name, ingress_ports, egress_ports, sfc_encap=True):
1728 self.logger.debug(
1729 "Adding a new Service Function Instance to VIM, named '%s'", name)
1730 try:
1731 new_sfi = None
1732 self._reload_connection()
1733 correlation = None
1734 if sfc_encap:
1735 # TODO(igordc): must be changed to NSH in Queens
1736 # (MPLS is a workaround)
1737 correlation = 'mpls'
1738 if len(ingress_ports) != 1:
1739 raise vimconn.vimconnNotSupportedException(
1740 "OpenStack VIM connector can only have "
1741 "1 ingress port per SFI")
1742 if len(egress_ports) != 1:
1743 raise vimconn.vimconnNotSupportedException(
1744 "OpenStack VIM connector can only have "
1745 "1 egress port per SFI")
1746 sfi_dict = {'name': name,
1747 'ingress': ingress_ports[0],
1748 'egress': egress_ports[0],
1749 'service_function_parameters': {
1750 'correlation': correlation}}
1751 new_sfi = self.neutron.create_port_pair({'port_pair': sfi_dict})
1752 return new_sfi['port_pair']['id']
1753 except (neExceptions.ConnectionFailed, ksExceptions.ClientException,
1754 neExceptions.NeutronException, ConnectionError) as e:
1755 if new_sfi:
1756 try:
1757 self.neutron.delete_port_pair_group(
1758 new_sfi['port_pair']['id'])
1759 except Exception:
1760 self.logger.error(
1761 'Creation of Service Function Instance failed, with '
1762 'subsequent deletion failure as well.')
1763 self._format_exception(e)
1764
1765 def get_sfi(self, sfi_id):
1766 self.logger.debug(
1767 'Getting Service Function Instance %s from VIM', sfi_id)
1768 filter_dict = {"id": sfi_id}
1769 sfi_list = self.get_sfi_list(filter_dict)
1770 if len(sfi_list) == 0:
1771 raise vimconn.vimconnNotFoundException(
1772 "Service Function Instance '{}' not found".format(sfi_id))
1773 elif len(sfi_list) > 1:
1774 raise vimconn.vimconnConflictException(
1775 'Found more than one Service Function Instance '
1776 'with this criteria')
1777 sfi = sfi_list[0]
1778 return sfi
1779
1780 def get_sfi_list(self, filter_dict={}):
1781 self.logger.debug("Getting Service Function Instances from "
1782 "VIM filter: '%s'", str(filter_dict))
1783 try:
1784 self._reload_connection()
1785 if self.api_version3 and "tenant_id" in filter_dict:
1786 filter_dict['project_id'] = filter_dict.pop('tenant_id')
1787 sfi_dict = self.neutron.list_port_pair(**filter_dict)
1788 sfi_list = sfi_dict["port_pairs"]
1789 self.__sfi_os2mano(sfi_list)
1790 return sfi_list
1791 except (neExceptions.ConnectionFailed, ksExceptions.ClientException,
1792 neExceptions.NeutronException, ConnectionError) as e:
1793 self._format_exception(e)
1794
1795 def delete_sfi(self, sfi_id):
1796 self.logger.debug("Deleting Service Function Instance '%s' "
1797 "from VIM", sfi_id)
1798 try:
1799 self._reload_connection()
1800 self.neutron.delete_port_pair(sfi_id)
1801 return sfi_id
1802 except (neExceptions.ConnectionFailed, neExceptions.NeutronException,
1803 ksExceptions.ClientException, neExceptions.NeutronException,
1804 ConnectionError) as e:
1805 self._format_exception(e)
1806
1807 def new_sf(self, name, sfis, sfc_encap=True):
1808 self.logger.debug("Adding a new Service Function to VIM, "
1809 "named '%s'", name)
1810 try:
1811 new_sf = None
1812 self._reload_connection()
1813 correlation = None
1814 if sfc_encap:
1815 # TODO(igordc): must be changed to NSH in Queens
1816 # (MPLS is a workaround)
1817 correlation = 'mpls'
1818 for instance in sfis:
1819 sfi = self.get_sfi(instance)
1820 if sfi.get('sfc_encap') != correlation:
1821 raise vimconn.vimconnNotSupportedException(
1822 "OpenStack VIM connector requires all SFIs of the "
1823 "same SF to share the same SFC Encapsulation")
1824 sf_dict = {'name': name,
1825 'port_pairs': sfis}
1826 new_sf = self.neutron.create_port_pair_group({
1827 'port_pair_group': sf_dict})
1828 return new_sf['port_pair_group']['id']
1829 except (neExceptions.ConnectionFailed, ksExceptions.ClientException,
1830 neExceptions.NeutronException, ConnectionError) as e:
1831 if new_sf:
1832 try:
1833 self.neutron.delete_port_pair_group(
1834 new_sf['port_pair_group']['id'])
1835 except Exception:
1836 self.logger.error(
1837 'Creation of Service Function failed, with '
1838 'subsequent deletion failure as well.')
1839 self._format_exception(e)
1840
1841 def get_sf(self, sf_id):
1842 self.logger.debug("Getting Service Function %s from VIM", sf_id)
1843 filter_dict = {"id": sf_id}
1844 sf_list = self.get_sf_list(filter_dict)
1845 if len(sf_list) == 0:
1846 raise vimconn.vimconnNotFoundException(
1847 "Service Function '{}' not found".format(sf_id))
1848 elif len(sf_list) > 1:
1849 raise vimconn.vimconnConflictException(
1850 "Found more than one Service Function with this criteria")
1851 sf = sf_list[0]
1852 return sf
1853
1854 def get_sf_list(self, filter_dict={}):
1855 self.logger.debug("Getting Service Function from VIM filter: '%s'",
1856 str(filter_dict))
1857 try:
1858 self._reload_connection()
1859 if self.api_version3 and "tenant_id" in filter_dict:
1860 filter_dict['project_id'] = filter_dict.pop('tenant_id')
1861 sf_dict = self.neutron.list_port_pair_group(**filter_dict)
1862 sf_list = sf_dict["port_pair_groups"]
1863 self.__sf_os2mano(sf_list)
1864 return sf_list
1865 except (neExceptions.ConnectionFailed, ksExceptions.ClientException,
1866 neExceptions.NeutronException, ConnectionError) as e:
1867 self._format_exception(e)
1868
1869 def delete_sf(self, sf_id):
1870 self.logger.debug("Deleting Service Function '%s' from VIM", sf_id)
1871 try:
1872 self._reload_connection()
1873 self.neutron.delete_port_pair_group(sf_id)
1874 return sf_id
1875 except (neExceptions.ConnectionFailed, neExceptions.NeutronException,
1876 ksExceptions.ClientException, neExceptions.NeutronException,
1877 ConnectionError) as e:
1878 self._format_exception(e)
1879
1880 def new_sfp(self, name, classifications, sfs, sfc_encap=True, spi=None):
1881 self.logger.debug("Adding a new Service Function Path to VIM, "
1882 "named '%s'", name)
1883 try:
1884 new_sfp = None
1885 self._reload_connection()
1886 if not sfc_encap:
1887 raise vimconn.vimconnNotSupportedException(
1888 "OpenStack VIM connector only supports "
1889 "SFC-Encapsulated chains")
1890 # TODO(igordc): must be changed to NSH in Queens
1891 # (MPLS is a workaround)
1892 correlation = 'mpls'
1893 sfp_dict = {'name': name,
1894 'flow_classifiers': classifications,
1895 'port_pair_groups': sfs,
1896 'chain_parameters': {'correlation': correlation}}
1897 if spi:
1898 sfp_dict['chain_id'] = spi
1899 new_sfp = self.neutron.create_port_chain({'port_chain': sfp_dict})
1900 return new_sfp["port_chain"]["id"]
1901 except (neExceptions.ConnectionFailed, ksExceptions.ClientException,
1902 neExceptions.NeutronException, ConnectionError) as e:
1903 if new_sfp:
1904 try:
1905 self.neutron.delete_port_chain(new_sfp['port_chain']['id'])
1906 except Exception:
1907 self.logger.error(
1908 'Creation of Service Function Path failed, with '
1909 'subsequent deletion failure as well.')
1910 self._format_exception(e)
1911
1912 def get_sfp(self, sfp_id):
1913 self.logger.debug(" Getting Service Function Path %s from VIM", sfp_id)
1914 filter_dict = {"id": sfp_id}
1915 sfp_list = self.get_sfp_list(filter_dict)
1916 if len(sfp_list) == 0:
1917 raise vimconn.vimconnNotFoundException(
1918 "Service Function Path '{}' not found".format(sfp_id))
1919 elif len(sfp_list) > 1:
1920 raise vimconn.vimconnConflictException(
1921 "Found more than one Service Function Path with this criteria")
1922 sfp = sfp_list[0]
1923 return sfp
1924
1925 def get_sfp_list(self, filter_dict={}):
1926 self.logger.debug("Getting Service Function Paths from VIM filter: "
1927 "'%s'", str(filter_dict))
1928 try:
1929 self._reload_connection()
1930 if self.api_version3 and "tenant_id" in filter_dict:
1931 filter_dict['project_id'] = filter_dict.pop('tenant_id')
1932 sfp_dict = self.neutron.list_port_chain(**filter_dict)
1933 sfp_list = sfp_dict["port_chains"]
1934 self.__sfp_os2mano(sfp_list)
1935 return sfp_list
1936 except (neExceptions.ConnectionFailed, ksExceptions.ClientException,
1937 neExceptions.NeutronException, ConnectionError) as e:
1938 self._format_exception(e)
1939
1940 def delete_sfp(self, sfp_id):
1941 self.logger.debug(
1942 "Deleting Service Function Path '%s' from VIM", sfp_id)
1943 try:
1944 self._reload_connection()
1945 self.neutron.delete_port_chain(sfp_id)
1946 return sfp_id
1947 except (neExceptions.ConnectionFailed, neExceptions.NeutronException,
1948 ksExceptions.ClientException, neExceptions.NeutronException,
1949 ConnectionError) as e:
1950 self._format_exception(e)